Skip to content

Shared Libraries Package Setup #6

@Sakeeb91

Description

@Sakeeb91

Priority

P1

Story Points

5

Dependencies

Depends on #1, #4, #5

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 tsup targeting 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)
  • publishConfig set 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, tsup
  • monitoring: prom-client, @opentelemetry/api, @opentelemetry/sdk-trace-node, tsup
  • utils: tsup
  • types: typescript only peer dependency
  • config: 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

  1. Scaffold shared package directories under packages/ with package.json, tsconfig.json, and .npmrc (set package-lock=false)
  2. Configure build scripts using tsup and tsc in each package (e.g., "build": "tsup src/index.ts --dts")
  3. Implement logging context helper, base logger factory, and Express middleware for request correlation
  4. Implement monitoring exports (metrics registry, timers, tracing helpers) with example Express/fastify instrumentation
  5. Implement utilities package with reusable helpers (error wrappers, retry, date formatting, object sanitization)
  6. Define shared TypeScript types for auth, database entities, events, and expose barrel file
  7. Build configuration loader with schema validation, secrets manager integration stub, and environment-specific overrides
  8. Add Vitest suites per package covering key behaviors (logger context, metrics increments, retry logic, config validation)
  9. Update root pnpm-workspace.yaml to ensure packages are included (if not already) and wire root scripts (pnpm build runs turbo run build across packages)
  10. Document usage examples in each package README (installation, usage snippet, environment variables)
  11. Version each package at 0.1.0 and ensure pnpm install + pnpm build succeed 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.md summarizing 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

No one assigned

    Labels

    backendBackend services and APIsepic-foundationFoundational platform workinfrastructureInfrastructure-related workp1High priority (important for iteration)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions