@discover-cloud/shared 1.0.10 → 1.2.0

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 (42) 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 +38 -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/logger.utils.d.ts +31 -16
  39. package/dist/utils/logger.utils.js +55 -20
  40. package/dist/utils/response.utils.d.ts +47 -5
  41. package/dist/utils/response.utils.js +50 -7
  42. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * ENVIRONMENT HELPERS (@discover-cloud/shared)
4
+ * ───────────────────────────────────────────────
5
+ * Typed accessors for process.env values.
6
+ *
7
+ * Why not read process.env directly?
8
+ * - process.env values are always string | undefined. Reading them inline
9
+ * forces every callsite to handle undefined or cast — this pushes that
10
+ * contract to one place.
11
+ * - getEnv() fails fast at startup (before serving any traffic) if a
12
+ * required variable is absent, surfacing misconfiguration immediately
13
+ * rather than at runtime inside a request handler.
14
+ * - Centralised access makes it straightforward to add validation, type
15
+ * coercion, or secret-redaction logic later without touching callsites.
16
+ *
17
+ * Usage:
18
+ * // Required — throws at startup if missing
19
+ * const dbUrl = getEnv("DATABASE_URL");
20
+ * const jwtSecret = getEnv("JWT_SECRET");
21
+ *
22
+ * // Optional — returns undefined (or a typed default) when absent
23
+ * const logLevel = getEnvOptional("LOG_LEVEL") ?? "info";
24
+ * const port = Number(getEnvOptional("PORT") ?? "3000");
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.getEnv = getEnv;
28
+ exports.getEnvOptional = getEnvOptional;
29
+ /**
30
+ * getEnv
31
+ * Returns the value of a required environment variable.
32
+ * Throws at call time (typically during service startup) if the variable
33
+ * is absent or empty — this is intentional: missing required config should
34
+ * crash the process before it begins serving traffic.
35
+ *
36
+ * Empty string ("") is treated as missing because it is almost always an
37
+ * accidental misconfiguration (e.g. `SECRET=` with no value in a .env file).
38
+ */
39
+ function getEnv(name) {
40
+ const value = process.env[name];
41
+ if (!value) {
42
+ throw new Error(`Missing required environment variable: ${name}. ` +
43
+ `Ensure it is set in your .env file or deployment environment.`);
44
+ }
45
+ return value;
46
+ }
47
+ /**
48
+ * getEnvOptional
49
+ * Returns the value of an optional environment variable, or undefined
50
+ * if it is absent or empty. Use with a nullish coalescing default:
51
+ *
52
+ * const logLevel = getEnvOptional("LOG_LEVEL") ?? "info";
53
+ *
54
+ * Returns undefined (not empty string) so callers can safely use `??`
55
+ * and `||` without needing to guard against empty strings separately.
56
+ */
57
+ function getEnvOptional(name) {
58
+ const value = process.env[name];
59
+ // Normalise empty string to undefined — same convention as getEnv.
60
+ return value === "" ? undefined : value;
61
+ }
@@ -1,30 +1,35 @@
1
1
  /**
2
2
  * LOGGER INTERFACE (@discover-cloud/shared)
3
3
  * ────────────────────────────────────────────
4
- * A minimal logger contract shared code can depend on.
5
- * Each service injects its own implementation (pino, winston, console).
4
+ * A minimal logger contract that shared code can depend on.
5
+ * Each service injects its own concrete implementation (pino, winston, etc.).
6
6
  *
7
- * Shared code (RequireAuthMiddleware, PermissionCacheService, etc.)
8
- * accepts this interfaceit never imports a concrete logger directly.
7
+ * Shared classes (RequireAuthMiddleware, PermissionCacheService, etc.)
8
+ * accept ILogger via constructor injection they never import a concrete
9
+ * logger directly, keeping the shared package dependency-free.
9
10
  *
10
11
  * ─── Usage in shared classes ────────────────────────────────────────
11
12
  * class RequireAuthMiddleware {
12
13
  * constructor(
13
14
  * private readonly verifier: InternalJwtVerifier,
14
- * private readonly logger: ILogger = noopLogger,
15
+ * private readonly logger: ILogger = noopLogger,
15
16
  * ) {}
16
17
  * }
17
18
  *
18
19
  * ─── Wiring in each service ─────────────────────────────────────────
19
- * // auth-service / composition root:
20
20
  * import pino from "pino";
21
- * const pinoLogger = pino({ level: "info" });
21
+ * const logger = pino({ level: "info" });
22
+ * const requireAuth = new RequireAuthMiddleware(jwtVerifier, logger);
22
23
  *
23
- * const requireAuth = new RequireAuthMiddleware(jwtVerifier, pinoLogger);
24
+ * // pino satisfies ILogger its method signatures are compatible.
24
25
  *
25
- * ─── noopLogger ─────────────────────────────────────────────────────
26
- * The default when no logger is injected — silently drops all logs.
27
- * Safe for tests and library consumers that don't want log noise.
26
+ * ─── Signature convention ───────────────────────────────────────────
27
+ * Follows pino's overloaded signature:
28
+ * logger.info({ userId }, "User logged in") — object first, then message
29
+ * logger.info("Simple message") — string only
30
+ *
31
+ * This matches pino natively and means a pino instance can be passed
32
+ * directly without any wrapping.
28
33
  */
29
34
  export interface ILogger {
30
35
  debug(obj: object, msg?: string): void;
@@ -38,14 +43,24 @@ export interface ILogger {
38
43
  }
39
44
  /**
40
45
  * noopLogger
41
- * Silent defaultused when no logger is injected.
42
- * Satisfies ILogger without importing any logging library.
46
+ * Silent no-op implementation the safe default when no logger is injected.
47
+ * All log calls are discarded. Use in tests and library consumers that
48
+ * don't want log noise.
43
49
  */
44
50
  export declare const noopLogger: ILogger;
45
51
  /**
46
52
  * consoleLogger
47
- * Thin console wrapper useful for local dev and simple services
48
- * that don't need structured logging.
49
- * Pass this when you want logs without wiring up pino/winston.
53
+ * Thin console wrapper that follows the pino (obj, msg?) calling convention.
54
+ * Useful for local development and simple services that don't need structured
55
+ * logging pass this instead of wiring up pino.
56
+ *
57
+ * Output order mirrors pino: message first, then the context object on a
58
+ * separate argument so it appears as structured context in most terminals.
59
+ *
60
+ * consoleLogger.info({ userId: "abc" }, "User logged in")
61
+ * → console.info("User logged in", { userId: "abc" })
62
+ *
63
+ * consoleLogger.info("Simple message")
64
+ * → console.info("Simple message")
50
65
  */
51
66
  export declare const consoleLogger: ILogger;
@@ -2,37 +2,43 @@
2
2
  /**
3
3
  * LOGGER INTERFACE (@discover-cloud/shared)
4
4
  * ────────────────────────────────────────────
5
- * A minimal logger contract shared code can depend on.
6
- * Each service injects its own implementation (pino, winston, console).
5
+ * A minimal logger contract that shared code can depend on.
6
+ * Each service injects its own concrete implementation (pino, winston, etc.).
7
7
  *
8
- * Shared code (RequireAuthMiddleware, PermissionCacheService, etc.)
9
- * accepts this interfaceit never imports a concrete logger directly.
8
+ * Shared classes (RequireAuthMiddleware, PermissionCacheService, etc.)
9
+ * accept ILogger via constructor injection they never import a concrete
10
+ * logger directly, keeping the shared package dependency-free.
10
11
  *
11
12
  * ─── Usage in shared classes ────────────────────────────────────────
12
13
  * class RequireAuthMiddleware {
13
14
  * constructor(
14
15
  * private readonly verifier: InternalJwtVerifier,
15
- * private readonly logger: ILogger = noopLogger,
16
+ * private readonly logger: ILogger = noopLogger,
16
17
  * ) {}
17
18
  * }
18
19
  *
19
20
  * ─── Wiring in each service ─────────────────────────────────────────
20
- * // auth-service / composition root:
21
21
  * import pino from "pino";
22
- * const pinoLogger = pino({ level: "info" });
22
+ * const logger = pino({ level: "info" });
23
+ * const requireAuth = new RequireAuthMiddleware(jwtVerifier, logger);
23
24
  *
24
- * const requireAuth = new RequireAuthMiddleware(jwtVerifier, pinoLogger);
25
+ * // pino satisfies ILogger its method signatures are compatible.
25
26
  *
26
- * ─── noopLogger ─────────────────────────────────────────────────────
27
- * The default when no logger is injected — silently drops all logs.
28
- * Safe for tests and library consumers that don't want log noise.
27
+ * ─── Signature convention ───────────────────────────────────────────
28
+ * Follows pino's overloaded signature:
29
+ * logger.info({ userId }, "User logged in") — object first, then message
30
+ * logger.info("Simple message") — string only
31
+ *
32
+ * This matches pino natively and means a pino instance can be passed
33
+ * directly without any wrapping.
29
34
  */
30
35
  Object.defineProperty(exports, "__esModule", { value: true });
31
36
  exports.consoleLogger = exports.noopLogger = void 0;
32
37
  /**
33
38
  * noopLogger
34
- * Silent defaultused when no logger is injected.
35
- * Satisfies ILogger without importing any logging library.
39
+ * Silent no-op implementation the safe default when no logger is injected.
40
+ * All log calls are discarded. Use in tests and library consumers that
41
+ * don't want log noise.
36
42
  */
37
43
  exports.noopLogger = {
38
44
  debug: () => { },
@@ -42,21 +48,50 @@ exports.noopLogger = {
42
48
  };
43
49
  /**
44
50
  * consoleLogger
45
- * Thin console wrapper useful for local dev and simple services
46
- * that don't need structured logging.
47
- * Pass this when you want logs without wiring up pino/winston.
51
+ * Thin console wrapper that follows the pino (obj, msg?) calling convention.
52
+ * Useful for local development and simple services that don't need structured
53
+ * logging pass this instead of wiring up pino.
54
+ *
55
+ * Output order mirrors pino: message first, then the context object on a
56
+ * separate argument so it appears as structured context in most terminals.
57
+ *
58
+ * consoleLogger.info({ userId: "abc" }, "User logged in")
59
+ * → console.info("User logged in", { userId: "abc" })
60
+ *
61
+ * consoleLogger.info("Simple message")
62
+ * → console.info("Simple message")
48
63
  */
49
64
  exports.consoleLogger = {
50
65
  debug: (objOrMsg, msg) => {
51
- console.debug(msg ?? objOrMsg, typeof objOrMsg === "object" ? objOrMsg : "");
66
+ if (typeof objOrMsg === "string") {
67
+ console.debug(objOrMsg);
68
+ }
69
+ else {
70
+ console.debug(msg ?? "", objOrMsg);
71
+ }
52
72
  },
53
73
  info: (objOrMsg, msg) => {
54
- console.info(msg ?? objOrMsg, typeof objOrMsg === "object" ? objOrMsg : "");
74
+ if (typeof objOrMsg === "string") {
75
+ console.info(objOrMsg);
76
+ }
77
+ else {
78
+ console.info(msg ?? "", objOrMsg);
79
+ }
55
80
  },
56
81
  warn: (objOrMsg, msg) => {
57
- console.warn(msg ?? objOrMsg, typeof objOrMsg === "object" ? objOrMsg : "");
82
+ if (typeof objOrMsg === "string") {
83
+ console.warn(objOrMsg);
84
+ }
85
+ else {
86
+ console.warn(msg ?? "", objOrMsg);
87
+ }
58
88
  },
59
89
  error: (objOrMsg, msg) => {
60
- console.error(msg ?? objOrMsg, typeof objOrMsg === "object" ? objOrMsg : "");
90
+ if (typeof objOrMsg === "string") {
91
+ console.error(objOrMsg);
92
+ }
93
+ else {
94
+ console.error(msg ?? "", objOrMsg);
95
+ }
61
96
  },
62
97
  };
@@ -1,12 +1,54 @@
1
1
  import { Response, Request } from "express";
2
2
  /**
3
- * RESPONSE HELPERS
4
- * ─────────────────
3
+ * RESPONSE HELPERS (@discover-cloud/shared)
4
+ * ────────────────────────────────────────────
5
5
  * Typed wrappers around res.json() that enforce the ApiSuccessResponse
6
- * and ApiErrorResponse envelope shapes.
6
+ * and ApiErrorResponse envelope shapes across all services.
7
7
  *
8
- * Both require req explicitly avoids the res.req cast anti-pattern
9
- * and makes the dependency clear at the call site.
8
+ * Both helpers accept req explicitly rather than reading res.req this
9
+ * makes the dependency visible at the call site and avoids the res.req
10
+ * cast anti-pattern.
11
+ *
12
+ * requestId fallback:
13
+ * req.id is set by requestId middleware. The randomUUID() fallback should
14
+ * never fire in a correctly wired service — if it does, it means requestId
15
+ * middleware was not registered before this helper was called. The fallback
16
+ * keeps the response well-formed but the generated ID won't correlate with
17
+ * any upstream trace. Check middleware registration order if you see UUIDs
18
+ * in responses that don't match the x-request-id header.
19
+ */
20
+ /**
21
+ * success
22
+ * Wraps data in an ApiSuccessResponse envelope and sends it.
23
+ *
24
+ * @param res - Express response object
25
+ * @param req - Express request object (for requestId + timestamp)
26
+ * @param data - The response payload; typed as T for type inference
27
+ * @param statusCode - HTTP status code (default 200)
28
+ *
29
+ * Usage:
30
+ * success<CloudAccountDto>(res, req, accountDto);
31
+ * success<PaginatedResponseDto<CloudAccountDto>>(res, req, paginatedResult, 200);
32
+ * success<MessageResponseDto>(res, req, { message: "Deleted" }, 200);
10
33
  */
11
34
  export declare const success: <T>(res: Response, req: Request, data: T, statusCode?: number) => void;
35
+ /**
36
+ * failure
37
+ * Wraps an error in an ApiErrorResponse envelope and sends it.
38
+ *
39
+ * @param res - Express response object
40
+ * @param req - Express request object (for requestId + timestamp)
41
+ * @param message - Human-readable error description
42
+ * @param code - Machine-readable error code (maps to AppError.code)
43
+ * @param statusCode - HTTP status code (default 400)
44
+ * @param details - Optional structured context (e.g. Zod flatten() output).
45
+ * Never put secrets, stack traces, or raw DB errors here.
46
+ *
47
+ * details is omitted from the response body when not provided — this avoids
48
+ * "details": null noise in responses and keeps the shape clean for clients.
49
+ *
50
+ * Usage:
51
+ * failure(res, req, "Account not found", "NOT_FOUND", 404);
52
+ * failure(res, req, "Validation failed", "VALIDATION_ERROR", 400, err.flatten());
53
+ */
12
54
  export declare const failure: (res: Response, req: Request, message: string, code: string, statusCode?: number, details?: unknown) => void;
@@ -3,33 +3,76 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.failure = exports.success = void 0;
4
4
  const crypto_1 = require("crypto");
5
5
  /**
6
- * RESPONSE HELPERS
7
- * ─────────────────
6
+ * RESPONSE HELPERS (@discover-cloud/shared)
7
+ * ────────────────────────────────────────────
8
8
  * Typed wrappers around res.json() that enforce the ApiSuccessResponse
9
- * and ApiErrorResponse envelope shapes.
9
+ * and ApiErrorResponse envelope shapes across all services.
10
10
  *
11
- * Both require req explicitly avoids the res.req cast anti-pattern
12
- * and makes the dependency clear at the call site.
11
+ * Both helpers accept req explicitly rather than reading res.req this
12
+ * makes the dependency visible at the call site and avoids the res.req
13
+ * cast anti-pattern.
14
+ *
15
+ * requestId fallback:
16
+ * req.id is set by requestId middleware. The randomUUID() fallback should
17
+ * never fire in a correctly wired service — if it does, it means requestId
18
+ * middleware was not registered before this helper was called. The fallback
19
+ * keeps the response well-formed but the generated ID won't correlate with
20
+ * any upstream trace. Check middleware registration order if you see UUIDs
21
+ * in responses that don't match the x-request-id header.
22
+ */
23
+ /**
24
+ * success
25
+ * Wraps data in an ApiSuccessResponse envelope and sends it.
26
+ *
27
+ * @param res - Express response object
28
+ * @param req - Express request object (for requestId + timestamp)
29
+ * @param data - The response payload; typed as T for type inference
30
+ * @param statusCode - HTTP status code (default 200)
31
+ *
32
+ * Usage:
33
+ * success<CloudAccountDto>(res, req, accountDto);
34
+ * success<PaginatedResponseDto<CloudAccountDto>>(res, req, paginatedResult, 200);
35
+ * success<MessageResponseDto>(res, req, { message: "Deleted" }, 200);
13
36
  */
14
37
  const success = (res, req, data, statusCode = 200) => {
15
38
  const response = {
16
39
  success: true,
17
40
  data,
18
41
  meta: {
19
- requestId: req.id ?? (0, crypto_1.randomUUID)(), // randomUUID fallback — "unknown" breaks log search
42
+ requestId: req.id ?? (0, crypto_1.randomUUID)(),
20
43
  timestamp: new Date().toISOString(),
21
44
  },
22
45
  };
23
46
  res.status(statusCode).json(response);
24
47
  };
25
48
  exports.success = success;
49
+ /**
50
+ * failure
51
+ * Wraps an error in an ApiErrorResponse envelope and sends it.
52
+ *
53
+ * @param res - Express response object
54
+ * @param req - Express request object (for requestId + timestamp)
55
+ * @param message - Human-readable error description
56
+ * @param code - Machine-readable error code (maps to AppError.code)
57
+ * @param statusCode - HTTP status code (default 400)
58
+ * @param details - Optional structured context (e.g. Zod flatten() output).
59
+ * Never put secrets, stack traces, or raw DB errors here.
60
+ *
61
+ * details is omitted from the response body when not provided — this avoids
62
+ * "details": null noise in responses and keeps the shape clean for clients.
63
+ *
64
+ * Usage:
65
+ * failure(res, req, "Account not found", "NOT_FOUND", 404);
66
+ * failure(res, req, "Validation failed", "VALIDATION_ERROR", 400, err.flatten());
67
+ */
26
68
  const failure = (res, req, message, code, statusCode = 400, details) => {
27
69
  const response = {
28
70
  success: false,
29
71
  error: {
30
72
  code,
31
73
  message,
32
- // Omit details entirely when not provided avoids "details": null noise
74
+ // Spread details only when presentomitting the key entirely is
75
+ // cleaner than sending "details": undefined (which JSON.stringify drops anyway).
33
76
  ...(details !== undefined ? { details } : {}),
34
77
  },
35
78
  meta: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discover-cloud/shared",
3
- "version": "1.0.10",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",