@discover-cloud/shared 1.0.11 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/authorization/permission-cache.service.d.ts +14 -1
  2. package/dist/authorization/permission-cache.service.js +84 -16
  3. package/dist/authorization/permissions.d.ts +19 -15
  4. package/dist/authorization/permissions.js +24 -21
  5. package/dist/dtos/auth-service.dto.d.ts +13 -0
  6. package/dist/dtos/cloud-service.dto.d.ts +25 -10
  7. package/dist/dtos/insights-service.dto.d.ts +24 -8
  8. package/dist/dtos/response.dto.d.ts +80 -10
  9. package/dist/dtos/response.dto.js +23 -4
  10. package/dist/dtos/user-service.dto.d.ts +7 -0
  11. package/dist/enums/domain.enums.d.ts +45 -14
  12. package/dist/enums/domain.enums.js +40 -6
  13. package/dist/enums/permissions.enums.d.ts +12 -52
  14. package/dist/enums/permissions.enums.js +55 -59
  15. package/dist/errors/app-error.d.ts +17 -14
  16. package/dist/errors/app-error.js +19 -15
  17. package/dist/errors/http-errors.d.ts +12 -5
  18. package/dist/errors/http-errors.js +27 -5
  19. package/dist/http/service-client.d.ts +37 -12
  20. package/dist/http/service-client.js +43 -17
  21. package/dist/jwt/internal-jwt-verifier.d.ts +9 -3
  22. package/dist/jwt/internal-jwt-verifier.js +49 -26
  23. package/dist/middleware/authorize.middleware.d.ts +38 -6
  24. package/dist/middleware/authorize.middleware.js +62 -37
  25. package/dist/middleware/error-handler.middleware.d.ts +21 -7
  26. package/dist/middleware/error-handler.middleware.js +42 -14
  27. package/dist/middleware/request-id.middleware.d.ts +12 -10
  28. package/dist/middleware/request-id.middleware.js +15 -15
  29. package/dist/middleware/validate.middleware.d.ts +22 -13
  30. package/dist/middleware/validate.middleware.js +25 -16
  31. package/dist/middleware/validated-merge.middleware.js +1 -2
  32. package/dist/types/express.types.d.ts +37 -32
  33. package/dist/types/express.types.js +13 -11
  34. package/dist/utils/date.utils.d.ts +18 -4
  35. package/dist/utils/date.utils.js +18 -4
  36. package/dist/utils/env.d.ts +46 -0
  37. package/dist/utils/env.js +61 -0
  38. package/dist/utils/env.utils.d.ts +46 -0
  39. package/dist/utils/env.utils.js +61 -0
  40. package/dist/utils/index.d.ts +1 -0
  41. package/dist/utils/index.js +1 -0
  42. package/dist/utils/logger.utils.d.ts +31 -16
  43. package/dist/utils/logger.utils.js +55 -20
  44. package/dist/utils/response.utils.d.ts +47 -5
  45. package/dist/utils/response.utils.js +50 -7
  46. package/package.json +1 -1
@@ -1,15 +1,29 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
2
  /**
3
- * GLOBAL ERROR HANDLER
4
- * ─────────────────────
5
- * Centralized error handler for all Express services.
3
+ * GLOBAL ERROR HANDLER (@discover-cloud/shared)
4
+ * ────────────────────────────────────────────────
5
+ * Centralised Express error handler. Register as the last middleware in
6
+ * every service's app.ts:
7
+ *
8
+ * app.use(GlobalErrorHandler.handle);
6
9
  *
7
10
  * Handles in order:
8
- * 1. ZodError → 400 with flattened validation details
9
- * 2. AppError → mapped statusCode + code
10
- * 3. Unknown → 500 Internal Server Error (internals never leaked)
11
+ * 1. ZodError → 400 VALIDATION_ERROR with flattened field errors
12
+ * 2. AppError → mapped statusCode + code (details forwarded to client)
13
+ * 3. Unknown → 500 INTERNAL_SERVER_ERROR (internals never leaked)
14
+ *
15
+ * Logging strategy:
16
+ * Development — full error object printed for stack traces during development.
17
+ * Production — message only for AppError/Error (no stack, no details in logs).
18
+ * Unknown errors log the raw value at warn level.
19
+ * In production, a structured logger (Pino) replaces console.*
20
+ * in each service — these console calls are captured by stdout.
11
21
  *
12
- * Every response includes requestId for traceability.
22
+ * Security:
23
+ * Unknown (unexpected) errors never expose internals to the client.
24
+ * AppError.details IS forwarded — callers that set details on an AppError
25
+ * are intentionally choosing to surface that context (e.g. validation hints).
26
+ * Never put secrets, stack traces, or raw DB errors in AppError.details.
13
27
  */
14
28
  export declare class GlobalErrorHandler {
15
29
  static handle(err: unknown, req: Request, res: Response, _next: NextFunction): void;
@@ -5,37 +5,65 @@ const zod_1 = require("zod");
5
5
  const errors_1 = require("../errors");
6
6
  const utils_1 = require("../utils");
7
7
  /**
8
- * GLOBAL ERROR HANDLER
9
- * ─────────────────────
10
- * Centralized error handler for all Express services.
8
+ * GLOBAL ERROR HANDLER (@discover-cloud/shared)
9
+ * ────────────────────────────────────────────────
10
+ * Centralised Express error handler. Register as the last middleware in
11
+ * every service's app.ts:
12
+ *
13
+ * app.use(GlobalErrorHandler.handle);
11
14
  *
12
15
  * Handles in order:
13
- * 1. ZodError → 400 with flattened validation details
14
- * 2. AppError → mapped statusCode + code
15
- * 3. Unknown → 500 Internal Server Error (internals never leaked)
16
+ * 1. ZodError → 400 VALIDATION_ERROR with flattened field errors
17
+ * 2. AppError → mapped statusCode + code (details forwarded to client)
18
+ * 3. Unknown → 500 INTERNAL_SERVER_ERROR (internals never leaked)
19
+ *
20
+ * Logging strategy:
21
+ * Development — full error object printed for stack traces during development.
22
+ * Production — message only for AppError/Error (no stack, no details in logs).
23
+ * Unknown errors log the raw value at warn level.
24
+ * In production, a structured logger (Pino) replaces console.*
25
+ * in each service — these console calls are captured by stdout.
16
26
  *
17
- * Every response includes requestId for traceability.
27
+ * Security:
28
+ * Unknown (unexpected) errors never expose internals to the client.
29
+ * AppError.details IS forwarded — callers that set details on an AppError
30
+ * are intentionally choosing to surface that context (e.g. validation hints).
31
+ * Never put secrets, stack traces, or raw DB errors in AppError.details.
18
32
  */
19
33
  class GlobalErrorHandler {
20
34
  static handle(err, req, res, _next) {
21
- if (process.env.NODE_ENV !== "production") {
35
+ // ── Logging ──────────────────────────────────────────────────────────
36
+ if (process.env["NODE_ENV"] !== "production") {
37
+ // Full object in development — stack traces + details are useful locally.
22
38
  console.error(`[req ${req.id}]`, err);
23
39
  }
40
+ else if (err instanceof Error) {
41
+ // Production: message only — no stack, no details, no internal paths.
42
+ console.error(`[req ${req.id}] ${err.name}: ${err.message}`);
43
+ }
24
44
  else {
25
- console.error(`[req ${req.id}]`, err instanceof Error ? err.message : err);
45
+ // Non-Error throw (string, object, etc.) log at warn; this should not happen.
46
+ console.warn(`[req ${req.id}] Non-Error thrown:`, typeof err);
26
47
  }
27
- // 1. Zod validation errors
48
+ // ── 1. Zod validation errors ─────────────────────────────────────────
28
49
  if (err instanceof zod_1.ZodError) {
29
- (0, utils_1.failure)(res, req, "Validation failed", "VALIDATION_ERROR", 400, err.flatten() // default flatten — preserves fieldErrors + formErrors + codes
30
- );
50
+ // flatten() groups errors into fieldErrors (keyed by path) and formErrors
51
+ // (top-level). This shape is stable across Zod versions and is what
52
+ // clients should key on for field-level error display.
53
+ (0, utils_1.failure)(res, req, "Validation failed", "VALIDATION_ERROR", 400, err.flatten());
31
54
  return;
32
55
  }
33
- // 2. Known AppError subclasses — instanceof, never duck-typing
56
+ // ── 2. Known AppError subclasses ─────────────────────────────────────
57
+ // instanceof check — no duck-typing, no accidental matches.
58
+ // AppError.details is caller-controlled context (e.g. validation hints)
59
+ // and is intentionally forwarded to the client.
34
60
  if (err instanceof errors_1.AppError) {
35
61
  (0, utils_1.failure)(res, req, err.message, err.code, err.statusCode, err.details);
36
62
  return;
37
63
  }
38
- // 3. Unexpected errors — never leak internals to the client
64
+ // ── 3. Unexpected errors — never leak internals ───────────────────────
65
+ // Generic 500 with no details. The actual error is logged above (production:
66
+ // message only; development: full object). Nothing internal reaches the client.
39
67
  (0, utils_1.failure)(res, req, "Internal Server Error", "INTERNAL_SERVER_ERROR", 500);
40
68
  }
41
69
  }
@@ -1,20 +1,22 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
2
  /**
3
- * REQUEST ID MIDDLEWARE
4
- * ──────────────────────
3
+ * REQUEST ID MIDDLEWARE (@discover-cloud/shared)
4
+ * ─────────────────────────────────────────────────
5
5
  * Attaches a unique request ID to req.id for distributed tracing.
6
+ * Register early in the middleware stack — before any logger or handler
7
+ * that needs to include the request ID in its output.
6
8
  *
7
9
  * Priority:
8
10
  * 1. x-request-id header forwarded by the API Gateway (or load balancer)
9
- * 2. Generate a new UUID if no upstream header is present
11
+ * 2. Newly generated UUID v4 if no upstream header is present
10
12
  *
11
- * Fix vs original:
12
- * The original always generated a new UUID, breaking distributed tracing.
13
- * If the gateway mints a requestId and forwards it via x-request-id,
14
- * the downstream service must carry that same ID — otherwise logs across
15
- * services cannot be correlated.
13
+ * Why forward the upstream header?
14
+ * The gateway mints a requestId and stamps it on every internal request.
15
+ * If downstream services generate their own IDs, the same logical request
16
+ * carries different IDs in each service's logs, breaking cross-service
17
+ * log correlation. Forwarding the upstream ID keeps the trace consistent.
16
18
  *
17
- * Also sets x-request-id on the response so clients and proxies can
18
- * correlate their logs with the service's logs.
19
+ * The resolved ID is also echoed back on the response via x-request-id
20
+ * so clients and proxies can correlate their own logs with the service's.
19
21
  */
20
22
  export declare const requestId: (req: Request, res: Response, next: NextFunction) => void;
@@ -3,31 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.requestId = void 0;
4
4
  const crypto_1 = require("crypto");
5
5
  /**
6
- * REQUEST ID MIDDLEWARE
7
- * ──────────────────────
6
+ * REQUEST ID MIDDLEWARE (@discover-cloud/shared)
7
+ * ─────────────────────────────────────────────────
8
8
  * Attaches a unique request ID to req.id for distributed tracing.
9
+ * Register early in the middleware stack — before any logger or handler
10
+ * that needs to include the request ID in its output.
9
11
  *
10
12
  * Priority:
11
13
  * 1. x-request-id header forwarded by the API Gateway (or load balancer)
12
- * 2. Generate a new UUID if no upstream header is present
14
+ * 2. Newly generated UUID v4 if no upstream header is present
13
15
  *
14
- * Fix vs original:
15
- * The original always generated a new UUID, breaking distributed tracing.
16
- * If the gateway mints a requestId and forwards it via x-request-id,
17
- * the downstream service must carry that same ID — otherwise logs across
18
- * services cannot be correlated.
16
+ * Why forward the upstream header?
17
+ * The gateway mints a requestId and stamps it on every internal request.
18
+ * If downstream services generate their own IDs, the same logical request
19
+ * carries different IDs in each service's logs, breaking cross-service
20
+ * log correlation. Forwarding the upstream ID keeps the trace consistent.
19
21
  *
20
- * Also sets x-request-id on the response so clients and proxies can
21
- * correlate their logs with the service's logs.
22
+ * The resolved ID is also echoed back on the response via x-request-id
23
+ * so clients and proxies can correlate their own logs with the service's.
22
24
  */
23
25
  const requestId = (req, res, next) => {
24
26
  const upstream = req.headers["x-request-id"];
25
- // Header can technically be a string array (multi-value) — take the first
26
- const id = Array.isArray(upstream)
27
- ? upstream[0]
28
- : upstream ?? (0, crypto_1.randomUUID)();
27
+ // Header can be a string array (multi-value header) — take the first value.
28
+ const id = Array.isArray(upstream) ? upstream[0] : (upstream ?? (0, crypto_1.randomUUID)());
29
29
  req.id = id;
30
- // Echo back on the response for client-side correlation
30
+ // Echo back on the response for client-side and proxy-side correlation.
31
31
  res.setHeader("x-request-id", id);
32
32
  next();
33
33
  };
@@ -1,25 +1,34 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
2
  import { ZodType } from "zod";
3
3
  /**
4
- * VALIDATOR MIDDLEWARE
5
- * ─────────────────────
6
- * Validates and parses request input using a Zod schema.
7
- * On success, attaches the parsed data to req.validated.
8
- * On failure, passes a ZodError to GlobalErrorHandler → 400 response.
4
+ * VALIDATOR MIDDLEWARE (@discover-cloud/shared)
5
+ * ────────────────────────────────────────────────
6
+ * Validates and parses request input against a Zod schema.
7
+ * On success, attaches the parsed (coerced + stripped) data to req.validated.
8
+ * On failure, passes the ZodError to next() → GlobalErrorHandler → 400.
9
9
  *
10
- * Changes vs original:
11
- * - Added "headers" as a valid source (useful for API key / custom header validation)
12
- * - req.validated typed as unknown — consumers must narrow via z.infer at route level
10
+ * Source options:
11
+ * "body" req.body (POST/PUT/PATCH payloads)
12
+ * "params" — req.params (URL path parameters)
13
+ * "query" — req.query (URL query string)
14
+ * "headers" — req.headers (custom header validation, e.g. API keys)
15
+ *
16
+ * Narrowing req.validated in a controller:
17
+ * req.validated is typed as unknown in express.d.ts — consumers must
18
+ * narrow it to the schema's inferred type at the call site:
19
+ *
20
+ * const body = req.validated as z.infer<typeof CreateOrderSchema>;
21
+ *
22
+ * This is intentional: the middleware is schema-agnostic and cannot
23
+ * carry the generic type through Express's untyped req object.
13
24
  *
14
25
  * Usage:
15
- * router.post("/orders",
26
+ * router.post(
27
+ * "/orders",
16
28
  * requireAuth,
17
29
  * Validator.validate(CreateOrderSchema, "body"),
18
- * controller.create
30
+ * controller.create,
19
31
  * );
20
- *
21
- * // In controller:
22
- * const body = req.validated as z.infer<typeof CreateOrderSchema>;
23
32
  */
24
33
  export declare class Validator {
25
34
  static validate<T>(schema: ZodType<T>, source?: "body" | "params" | "query" | "headers"): (req: Request, _res: Response, next: NextFunction) => void;
@@ -2,37 +2,46 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Validator = void 0;
4
4
  /**
5
- * VALIDATOR MIDDLEWARE
6
- * ─────────────────────
7
- * Validates and parses request input using a Zod schema.
8
- * On success, attaches the parsed data to req.validated.
9
- * On failure, passes a ZodError to GlobalErrorHandler → 400 response.
5
+ * VALIDATOR MIDDLEWARE (@discover-cloud/shared)
6
+ * ────────────────────────────────────────────────
7
+ * Validates and parses request input against a Zod schema.
8
+ * On success, attaches the parsed (coerced + stripped) data to req.validated.
9
+ * On failure, passes the ZodError to next() → GlobalErrorHandler → 400.
10
10
  *
11
- * Changes vs original:
12
- * - Added "headers" as a valid source (useful for API key / custom header validation)
13
- * - req.validated typed as unknown — consumers must narrow via z.infer at route level
11
+ * Source options:
12
+ * "body" req.body (POST/PUT/PATCH payloads)
13
+ * "params" — req.params (URL path parameters)
14
+ * "query" — req.query (URL query string)
15
+ * "headers" — req.headers (custom header validation, e.g. API keys)
16
+ *
17
+ * Narrowing req.validated in a controller:
18
+ * req.validated is typed as unknown in express.d.ts — consumers must
19
+ * narrow it to the schema's inferred type at the call site:
20
+ *
21
+ * const body = req.validated as z.infer<typeof CreateOrderSchema>;
22
+ *
23
+ * This is intentional: the middleware is schema-agnostic and cannot
24
+ * carry the generic type through Express's untyped req object.
14
25
  *
15
26
  * Usage:
16
- * router.post("/orders",
27
+ * router.post(
28
+ * "/orders",
17
29
  * requireAuth,
18
30
  * Validator.validate(CreateOrderSchema, "body"),
19
- * controller.create
31
+ * controller.create,
20
32
  * );
21
- *
22
- * // In controller:
23
- * const body = req.validated as z.infer<typeof CreateOrderSchema>;
24
33
  */
25
34
  class Validator {
26
35
  static validate(schema, source = "body") {
27
36
  return (req, _res, next) => {
28
37
  const result = schema.safeParse(req[source]);
29
38
  if (!result.success) {
30
- // ZodError propagates to GlobalErrorHandler 400 with flatten()
39
+ // Pass the ZodError to GlobalErrorHandler it flattens and returns 400.
31
40
  next(result.error);
32
41
  return;
33
42
  }
34
- // req.validated typed as unknown in express.d.ts
35
- // Narrow to your schema type at the route/controller level
43
+ // Attach the parsed data (coerced + unknown keys stripped by Zod).
44
+ // req.validated is typed as unknown — narrow to your schema type in the controller.
36
45
  req.validated = result.data;
37
46
  next();
38
47
  };
@@ -19,8 +19,7 @@ exports.mergeValidated = exports.captureValidated = void 0;
19
19
  * enough. Casting to unknown first tells TypeScript we know what we're doing.
20
20
  */
21
21
  const captureValidated = (key, tempKey) => (req, _res, next) => {
22
- req[tempKey] =
23
- req.validated[key];
22
+ req[tempKey] = req.validated[key];
24
23
  next();
25
24
  };
26
25
  exports.captureValidated = captureValidated;
@@ -1,42 +1,47 @@
1
1
  /**
2
- * EXPRESS TYPE AUGMENTATION + JWT PAYLOAD TYPES
3
- * ───────────────────────────────────────────────
2
+ * EXPRESS TYPE AUGMENTATION + JWT PAYLOAD TYPES (@discover-cloud/shared)
3
+ * ──────────────────────────────────────────────────────────────────────────
4
4
  * Single source of truth for everything attached to Express's Request object
5
5
  * and all internal JWT payload shapes.
6
6
  *
7
- * Belongs in @discover-cloud/shared — import in each service's entrypoint:
7
+ * Import in each service's entrypoint to activate the augmentation:
8
8
  * import "@discover-cloud/shared/types/express";
9
9
  *
10
- * ─── Permission flow ────────────────────────────────────────────────
11
- * JWT carries: accountId + accountRole (lean, no perms)
12
- * Cache derives: GlobalPermission[] (Redis → role map on miss)
13
- * AccessContext has: perms (populated by auth middleware)
10
+ * ─── Permission flow ────────────────────────────────────────────────────
11
+ * JWT carries: accountId + accountRole (lean payload, no perms)
12
+ * Redis derives: GlobalPermission[] (role map on cache miss)
13
+ * AccessContext has: perms (resolved by auth middleware)
14
14
  *
15
- * perms never touch the JWT — they live in the permission cache and are
16
- * resolved into req.accessContext by RequireAuthMiddleware on every request.
15
+ * perms never touch the JWT — they live in the Redis permission cache and
16
+ * are resolved into req.accessContext by RequireAuthMiddleware per request.
17
+ * ────────────────────────────────────────────────────────────────────────
17
18
  */
18
19
  import { JWTPayload } from "jose";
19
- import { AccountRole } from "../enums";
20
- import { GlobalPermission } from "../enums";
20
+ import { AccountRole, GlobalPermission } from "../enums";
21
21
  interface BaseInternalJwtPayload extends JWTPayload {
22
22
  /**
23
- * JWT ID — REQUIRED.
23
+ * JWT ID — required.
24
24
  * Used for the Redis kill-switch (session revocation / account suspension).
25
- * Overrides JWTPayload's optional jti with required.
25
+ * Overrides JWTPayload's optional jti with a required string.
26
26
  */
27
27
  jti: string;
28
- /** typ discriminator — guards against token type confusion attacks */
28
+ /**
29
+ * Token type discriminator — guards against token confusion attacks.
30
+ * InternalJwtVerifier rejects any token where typ !== "internal",
31
+ * even if it passes signature verification.
32
+ */
29
33
  typ: "internal";
30
34
  /**
31
35
  * The entity that last minted or forwarded this token.
32
- * "api-gateway" → direct user-initiated request
33
- * "order-service" → service-to-service call on behalf of a user
36
+ * "api-gateway" → direct user-initiated request
37
+ * "notification-service" → service-to-service call on behalf of a user
34
38
  * Used for audit logging and conditional authorization.
35
39
  */
36
40
  caller: string;
37
41
  /**
38
42
  * Propagated request ID for distributed tracing.
39
43
  * Ties together logs across the entire service call chain.
44
+ * Set by the gateway from the incoming x-request-id header.
40
45
  */
41
46
  requestId?: string;
42
47
  iss: string;
@@ -46,11 +51,10 @@ interface BaseInternalJwtPayload extends JWTPayload {
46
51
  nbf: number;
47
52
  }
48
53
  /**
49
- * Human JWT payload — issued on behalf of an authenticated user.
50
- * Minted by the gateway after verifying an external JWT.
51
- *
52
- * accountRole is carried so the permission cache can derive perms
53
- * from globalRolePermissions on a cold cache miss — without a DB call.
54
+ * HumanInternalJwtPayload
55
+ * Issued on behalf of an authenticated user after external JWT verification.
56
+ * accountRole is carried so the permission cache can derive perms from
57
+ * globalRolePermissions on a cold cache miss without a DB call.
54
58
  */
55
59
  export interface HumanInternalJwtPayload extends BaseInternalJwtPayload {
56
60
  isMachine: false;
@@ -59,14 +63,15 @@ export interface HumanInternalJwtPayload extends BaseInternalJwtPayload {
59
63
  externalJti?: string;
60
64
  }
61
65
  /**
62
- * Machine JWT payload — service-to-service calls with no user context.
63
- * Intentionally carries NO user fields absence is enforced by the type.
66
+ * MachineInternalJwtPayload
67
+ * Issued for service-to-service calls with no user context.
68
+ * Intentionally carries no user fields — their absence is enforced by the type.
64
69
  */
65
70
  export interface MachineInternalJwtPayload extends BaseInternalJwtPayload {
66
71
  isMachine: true;
67
72
  serviceId: string;
68
73
  }
69
- /** The full internal JWT payload union — use this in verifiers and auth middleware */
74
+ /** Full internal JWT payload union — use in verifiers and auth middleware */
70
75
  export type InternalJwtPayload = HumanInternalJwtPayload | MachineInternalJwtPayload;
71
76
  export interface HumanAccessContext {
72
77
  kind: "human";
@@ -74,7 +79,7 @@ export interface HumanAccessContext {
74
79
  accountRole: AccountRole;
75
80
  /**
76
81
  * Resolved from Redis permission cache — NOT from the JWT.
77
- * Always present by the time req.accessContext is set.
82
+ * Always present by the time req.accessContext is populated.
78
83
  * Invalidated on role change, suspension, and account deletion.
79
84
  */
80
85
  perms: GlobalPermission[];
@@ -113,13 +118,13 @@ declare module "express-serve-static-core" {
113
118
  interface Request {
114
119
  /**
115
120
  * Unique request ID — set by requestId middleware.
116
- * Respects upstream x-request-id from the gateway / load balancer.
121
+ * Respects upstream x-request-id forwarded by the gateway / load balancer.
117
122
  * Always present after requestId middleware runs.
118
123
  */
119
124
  id: string;
120
125
  /**
121
- * Validated + parsed request body/query/params/headers.
122
- * Typed as unknown — narrow at the route level:
126
+ * Validated + parsed request input (body / params / query / headers).
127
+ * Typed as unknown — narrow at the route level using z.infer:
123
128
  *
124
129
  * const body = req.validated as z.infer<typeof CreateOrderSchema>;
125
130
  *
@@ -130,13 +135,13 @@ declare module "express-serve-static-core" {
130
135
  * Raw verified internal JWT payload.
131
136
  * Set by RequireAuthMiddleware after InternalJwtVerifier.verifyInternal().
132
137
  *
133
- * Prefer req.accessContext in route handlers.
134
- * Access this only when you need raw JWT claims (e.g. jti for blacklisting).
138
+ * Prefer req.accessContext in route handlers and controllers.
139
+ * Access internalAuth only when you need raw JWT claims (e.g. jti).
135
140
  */
136
141
  internalAuth?: InternalJwtPayload;
137
142
  /**
138
- * Clean access context — built from internalAuth + permission cache.
139
- * This is what route handlers, controllers, and domain services use.
143
+ * Clean access context — built from internalAuth + Redis permission cache.
144
+ * This is what route handlers, controllers, and domain services consume.
140
145
  *
141
146
  * if (isHumanContext(req.accessContext)) {
142
147
  * const { accountId, accountRole, perms } = req.accessContext;
@@ -1,20 +1,21 @@
1
1
  "use strict";
2
2
  /**
3
- * EXPRESS TYPE AUGMENTATION + JWT PAYLOAD TYPES
4
- * ───────────────────────────────────────────────
3
+ * EXPRESS TYPE AUGMENTATION + JWT PAYLOAD TYPES (@discover-cloud/shared)
4
+ * ──────────────────────────────────────────────────────────────────────────
5
5
  * Single source of truth for everything attached to Express's Request object
6
6
  * and all internal JWT payload shapes.
7
7
  *
8
- * Belongs in @discover-cloud/shared — import in each service's entrypoint:
8
+ * Import in each service's entrypoint to activate the augmentation:
9
9
  * import "@discover-cloud/shared/types/express";
10
10
  *
11
- * ─── Permission flow ────────────────────────────────────────────────
12
- * JWT carries: accountId + accountRole (lean, no perms)
13
- * Cache derives: GlobalPermission[] (Redis → role map on miss)
14
- * AccessContext has: perms (populated by auth middleware)
11
+ * ─── Permission flow ────────────────────────────────────────────────────
12
+ * JWT carries: accountId + accountRole (lean payload, no perms)
13
+ * Redis derives: GlobalPermission[] (role map on cache miss)
14
+ * AccessContext has: perms (resolved by auth middleware)
15
15
  *
16
- * perms never touch the JWT — they live in the permission cache and are
17
- * resolved into req.accessContext by RequireAuthMiddleware on every request.
16
+ * perms never touch the JWT — they live in the Redis permission cache and
17
+ * are resolved into req.accessContext by RequireAuthMiddleware per request.
18
+ * ────────────────────────────────────────────────────────────────────────
18
19
  */
19
20
  Object.defineProperty(exports, "__esModule", { value: true });
20
21
  exports.isHumanPayload = isHumanPayload;
@@ -24,8 +25,9 @@ exports.isMachineContext = isMachineContext;
24
25
  /* ====================================================================
25
26
  4. TYPE GUARDS
26
27
 
27
- Use these over raw property checks — TypeScript narrows correctly
28
- through them on both InternalJwtPayload and AccessContext.
28
+ Use these over raw property checks — TypeScript narrows the union
29
+ correctly through predicate functions on both InternalJwtPayload
30
+ and AccessContext.
29
31
  ==================================================================== */
30
32
  function isHumanPayload(payload) {
31
33
  return payload.isMachine === false;
@@ -1,11 +1,25 @@
1
1
  /**
2
- * DATE SERIALIZATION UTILS
3
- * ─────────────────────────
2
+ * DATE SERIALIZATION UTILS (@discover-cloud/shared)
3
+ * ─────────────────────────────────────────────────────
4
4
  * Converts Date objects to ISO 8601 strings for HTTP responses.
5
5
  * Use these in toDto() mappers — never call .toISOString() inline.
6
6
  *
7
- * Why: DTOs cross HTTP boundaries where JSON has no Date type.
8
- * Domain models keep Date internally; these helpers handle the boundary.
7
+ * Why centralise this?
8
+ * DTOs cross HTTP boundaries where JSON has no Date type. Domain models
9
+ * keep Date objects internally; these helpers handle the conversion at
10
+ * the boundary in a single consistent place. If the serialisation format
11
+ * ever needs to change (e.g. add milliseconds truncation, force UTC
12
+ * suffix), there is one place to update.
13
+ */
14
+ /**
15
+ * toIso
16
+ * Converts a Date to an ISO 8601 string.
17
+ * Use for non-nullable timestamp fields in DTOs.
9
18
  */
10
19
  export declare const toIso: (date: Date) => string;
20
+ /**
21
+ * toIsoOrNull
22
+ * Converts a Date to an ISO 8601 string, or returns null if the value
23
+ * is absent. Use for nullable timestamp fields (e.g. revokedAt, deletedAt).
24
+ */
11
25
  export declare const toIsoOrNull: (date: Date | null | undefined) => string | null;
@@ -1,16 +1,30 @@
1
1
  "use strict";
2
2
  /**
3
- * DATE SERIALIZATION UTILS
4
- * ─────────────────────────
3
+ * DATE SERIALIZATION UTILS (@discover-cloud/shared)
4
+ * ─────────────────────────────────────────────────────
5
5
  * Converts Date objects to ISO 8601 strings for HTTP responses.
6
6
  * Use these in toDto() mappers — never call .toISOString() inline.
7
7
  *
8
- * Why: DTOs cross HTTP boundaries where JSON has no Date type.
9
- * Domain models keep Date internally; these helpers handle the boundary.
8
+ * Why centralise this?
9
+ * DTOs cross HTTP boundaries where JSON has no Date type. Domain models
10
+ * keep Date objects internally; these helpers handle the conversion at
11
+ * the boundary in a single consistent place. If the serialisation format
12
+ * ever needs to change (e.g. add milliseconds truncation, force UTC
13
+ * suffix), there is one place to update.
10
14
  */
11
15
  Object.defineProperty(exports, "__esModule", { value: true });
12
16
  exports.toIsoOrNull = exports.toIso = void 0;
17
+ /**
18
+ * toIso
19
+ * Converts a Date to an ISO 8601 string.
20
+ * Use for non-nullable timestamp fields in DTOs.
21
+ */
13
22
  const toIso = (date) => date.toISOString();
14
23
  exports.toIso = toIso;
24
+ /**
25
+ * toIsoOrNull
26
+ * Converts a Date to an ISO 8601 string, or returns null if the value
27
+ * is absent. Use for nullable timestamp fields (e.g. revokedAt, deletedAt).
28
+ */
15
29
  const toIsoOrNull = (date) => date ? date.toISOString() : null;
16
30
  exports.toIsoOrNull = toIsoOrNull;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ENVIRONMENT HELPERS (@discover-cloud/shared)
3
+ * ───────────────────────────────────────────────
4
+ * Typed accessors for process.env values.
5
+ *
6
+ * Why not read process.env directly?
7
+ * - process.env values are always string | undefined. Reading them inline
8
+ * forces every callsite to handle undefined or cast — this pushes that
9
+ * contract to one place.
10
+ * - getEnv() fails fast at startup (before serving any traffic) if a
11
+ * required variable is absent, surfacing misconfiguration immediately
12
+ * rather than at runtime inside a request handler.
13
+ * - Centralised access makes it straightforward to add validation, type
14
+ * coercion, or secret-redaction logic later without touching callsites.
15
+ *
16
+ * Usage:
17
+ * // Required — throws at startup if missing
18
+ * const dbUrl = getEnv("DATABASE_URL");
19
+ * const jwtSecret = getEnv("JWT_SECRET");
20
+ *
21
+ * // Optional — returns undefined (or a typed default) when absent
22
+ * const logLevel = getEnvOptional("LOG_LEVEL") ?? "info";
23
+ * const port = Number(getEnvOptional("PORT") ?? "3000");
24
+ */
25
+ /**
26
+ * getEnv
27
+ * Returns the value of a required environment variable.
28
+ * Throws at call time (typically during service startup) if the variable
29
+ * is absent or empty — this is intentional: missing required config should
30
+ * crash the process before it begins serving traffic.
31
+ *
32
+ * Empty string ("") is treated as missing because it is almost always an
33
+ * accidental misconfiguration (e.g. `SECRET=` with no value in a .env file).
34
+ */
35
+ export declare function getEnv(name: string): string;
36
+ /**
37
+ * getEnvOptional
38
+ * Returns the value of an optional environment variable, or undefined
39
+ * if it is absent or empty. Use with a nullish coalescing default:
40
+ *
41
+ * const logLevel = getEnvOptional("LOG_LEVEL") ?? "info";
42
+ *
43
+ * Returns undefined (not empty string) so callers can safely use `??`
44
+ * and `||` without needing to guard against empty strings separately.
45
+ */
46
+ export declare function getEnvOptional(name: string): string | undefined;