-
Notifications
You must be signed in to change notification settings - Fork 1
Closed
Labels
backendBackend services and APIsBackend services and APIsepic-foundationFoundational platform workFoundational platform workinfrastructureInfrastructure-related workInfrastructure-related workp1High priority (important for iteration)High priority (important for iteration)
Description
Priority
P1
Story Points
5
Dependencies
Description
Create shared library packages for common functionality used across all services including logging, monitoring, utilities, type definitions, and configuration management.
Acceptance Criteria
- Structured logging library with context propagation
- Monitoring and metrics library
- Common utility functions package
- Shared TypeScript types package
- Configuration management library
- All packages properly versioned
- Documentation for each library
Technical Specification
Workspace Layout
packages/
├── logging/
│ ├── package.json
│ ├── tsconfig.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── logger.ts
│ │ └── context.ts
│ ├── tests/
│ │ └── logger.test.ts
│ └── README.md
├── monitoring/
│ ├── package.json
│ ├── tsconfig.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── metrics.ts
│ │ └── tracing.ts
│ ├── tests/
│ │ └── metrics.test.ts
│ └── README.md
├── utils/
│ ├── package.json
│ ├── tsconfig.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── error.util.ts
│ │ ├── date.util.ts
│ │ ├── string.util.ts
│ │ └── retry.util.ts
│ ├── tests/
│ │ ├── error.util.test.ts
│ │ └── retry.util.test.ts
│ └── README.md
├── types/
│ ├── package.json
│ ├── tsconfig.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── auth.types.ts
│ │ ├── database.types.ts
│ │ ├── events.types.ts
│ │ └── shared.enums.ts
│ └── README.md
└── config/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts
│ ├── config.schema.ts
│ ├── config.loader.ts
│ └── secrets.provider.ts
├── tests/
│ └── config.loader.test.ts
└── README.md
Package Tooling (applies to every shared package)
- Build with
tsuptargeting ESM + CJS outputs (dist/index.js,dist/index.cjs) - Type declarations emitted via
tsc - Unit tests using
vitest - Lint/format via root commands (
pnpm lint,pnpm format) - Version field aligned to workspace versioning (start at
0.1.0) publishConfigset to private ("private": true) until release strategy finalized
Logging Library (packages/logging/src/index.ts)
import pino from 'pino';
import { AsyncLocalStorage } from 'node:async_hooks';
export interface LogContext {
requestId?: string;
userId?: string;
organizationId?: string;
sessionId?: string;
[key: string]: unknown;
}
const contextStore = new AsyncLocalStorage<LogContext>();
const baseLogger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard' } }
: undefined,
});
export function withLogContext<T>(context: LogContext, callback: () => Promise<T>): Promise<T> {
return contextStore.run(context, callback);
}
export function getLogger(bindings: LogContext = {}) {
const storeContext = contextStore.getStore();
return baseLogger.child({ ...storeContext, ...bindings });
}
export const logger = getLogger();Monitoring Library (packages/monitoring/src/metrics.ts)
import { collectDefaultMetrics, Counter, Gauge, Histogram, Registry } from 'prom-client';
const registry = new Registry();
collectDefaultMetrics({ register: registry });
export const httpRequestDurationSeconds = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5],
registers: [registry],
});
export const activeSessionsGauge = new Gauge({
name: 'active_sessions',
help: 'Number of active authenticated sessions',
registers: [registry],
});
export const authFailuresCounter = new Counter({
name: 'auth_failures_total',
help: 'Total number of authentication failures',
labelNames: ['reason'],
registers: [registry],
});
export function getMetrics(): Promise<string> {
return registry.metrics();
}
export { registry as metricsRegistry };Configuration Library (packages/config/src/config.loader.ts)
import { z } from 'zod';
import dotenv from 'dotenv';
const baseSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'staging', 'production']),
LOG_LEVEL: z.string().default('info'),
AWS_REGION: z.string().optional(),
DATABASE_URL: z.string().url().optional(),
JWT_ACCESS_SECRET: z.string(),
JWT_REFRESH_SECRET: z.string(),
});
type BaseConfig = z.infer<typeof baseSchema>;
export function loadConfig(overrides: Record<string, unknown> = {}): BaseConfig {
dotenv.config();
const merged = { ...process.env, ...overrides };
const parsed = baseSchema.safeParse(merged);
if (!parsed.success) {
throw new Error(`Configuration validation failed: ${parsed.error.message}`);
}
return parsed.data;
}Utilities (packages/utils/src/retry.util.ts)
export interface RetryOptions {
retries?: number;
delayMs?: number;
backoffFactor?: number;
onRetry?: (attempt: number, error: unknown) => void;
}
type AsyncFn<T> = () => Promise<T>;
export async function retry<T>(fn: AsyncFn<T>, options: RetryOptions = {}): Promise<T> {
const { retries = 3, delayMs = 100, backoffFactor = 2, onRetry } = options;
let attempt = 0;
let currentDelay = delayMs;
// eslint-disable-next-line no-constant-condition
while (true) {
try {
return await fn();
} catch (error) {
attempt += 1;
if (attempt > retries) {
throw error;
}
onRetry?.(attempt, error);
await new Promise((resolve) => setTimeout(resolve, currentDelay));
currentDelay *= backoffFactor;
}
}
}Shared Types (packages/types/src/auth.types.ts)
export interface AuthenticatedUser {
id: string;
email: string;
role: string;
organizationId: string;
sessionId: string;
}
export interface AccessTokenPayload extends AuthenticatedUser {
exp: number;
iat: number;
iss: string;
aud: string;
}
export interface RefreshTokenPayload {
userId: string;
sessionId: string;
exp: number;
iat: number;
iss: string;
aud: string;
}Package Dependencies
logging:pino,pino-pretty,@types/pino,tsupmonitoring:prom-client,@opentelemetry/api,@opentelemetry/sdk-trace-node,tsuputils:tsuptypes:typescriptonly peer dependencyconfig:dotenv,zod,tsup
Each package should expose its primary API via src/index.ts and re-export internal modules to simplify downstream imports.
Implementation Steps
- Scaffold shared package directories under
packages/withpackage.json,tsconfig.json, and.npmrc(setpackage-lock=false) - Configure build scripts using
tsupandtscin each package (e.g.,"build": "tsup src/index.ts --dts") - Implement logging context helper, base logger factory, and Express middleware for request correlation
- Implement monitoring exports (metrics registry, timers, tracing helpers) with example Express/fastify instrumentation
- Implement utilities package with reusable helpers (error wrappers, retry, date formatting, object sanitization)
- Define shared TypeScript types for auth, database entities, events, and expose barrel file
- Build configuration loader with schema validation, secrets manager integration stub, and environment-specific overrides
- Add Vitest suites per package covering key behaviors (logger context, metrics increments, retry logic, config validation)
- Update root
pnpm-workspace.yamlto ensure packages are included (if not already) and wire root scripts (pnpm buildrunsturbo run buildacross packages) - Document usage examples in each package README (installation, usage snippet, environment variables)
- Version each package at
0.1.0and ensurepnpm install+pnpm buildsucceed from fresh clone
Testing Requirements
- Unit tests for logger child context propagation
- Metrics endpoints export Prometheus text format
- Retry utility retries and respects backoff options
- Config loader rejects invalid/missing required environment variables
- Shared types compile in a sample consumer (TypeScript project references)
- Packages integrate cleanly with Auth service (lint/type-check passes when imported)
Documentation
- README per package with purpose, installation, and usage examples
- Central
docs/architecture/shared-libraries.mdsummarizing available packages and integration points - Guidelines on when to create new shared utilities vs. service-local helpers
- Versioning and release checklist for shared packages
Metadata
Metadata
Assignees
Labels
backendBackend services and APIsBackend services and APIsepic-foundationFoundational platform workFoundational platform workinfrastructureInfrastructure-related workInfrastructure-related workp1High priority (important for iteration)High priority (important for iteration)