@pipeline-builder/api-core 0.0.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 (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/lib/constants/ai-providers.d.ts +52 -0
  4. package/lib/constants/ai-providers.js +129 -0
  5. package/lib/constants/http-status.d.ts +20 -0
  6. package/lib/constants/http-status.js +26 -0
  7. package/lib/constants/index.d.ts +3 -0
  8. package/lib/constants/index.js +6 -0
  9. package/lib/constants/time.d.ts +10 -0
  10. package/lib/constants/time.js +13 -0
  11. package/lib/errors/app-errors.d.ts +30 -0
  12. package/lib/errors/app-errors.js +53 -0
  13. package/lib/errors/index.d.ts +1 -0
  14. package/lib/errors/index.js +4 -0
  15. package/lib/helpers/access-helpers.d.ts +63 -0
  16. package/lib/helpers/access-helpers.js +82 -0
  17. package/lib/helpers/crud-helpers.d.ts +9 -0
  18. package/lib/helpers/crud-helpers.js +23 -0
  19. package/lib/helpers/index.d.ts +6 -0
  20. package/lib/helpers/index.js +9 -0
  21. package/lib/helpers/org-hierarchy-http.d.ts +74 -0
  22. package/lib/helpers/org-hierarchy-http.js +144 -0
  23. package/lib/helpers/org-hierarchy.d.ts +57 -0
  24. package/lib/helpers/org-hierarchy.js +110 -0
  25. package/lib/helpers/restore-helpers.d.ts +49 -0
  26. package/lib/helpers/restore-helpers.js +78 -0
  27. package/lib/helpers/sse-helpers.d.ts +20 -0
  28. package/lib/helpers/sse-helpers.js +45 -0
  29. package/lib/index.d.ts +58 -0
  30. package/lib/index.js +71 -0
  31. package/lib/middleware/auth.d.ts +208 -0
  32. package/lib/middleware/auth.js +588 -0
  33. package/lib/middleware/index.d.ts +3 -0
  34. package/lib/middleware/index.js +6 -0
  35. package/lib/middleware/mongo-sanitize.d.ts +32 -0
  36. package/lib/middleware/mongo-sanitize.js +100 -0
  37. package/lib/middleware/step-up.d.ts +45 -0
  38. package/lib/middleware/step-up.js +138 -0
  39. package/lib/openapi/extend-zod.d.ts +1 -0
  40. package/lib/openapi/extend-zod.js +6 -0
  41. package/lib/openapi/index.d.ts +2 -0
  42. package/lib/openapi/index.js +5 -0
  43. package/lib/openapi/registry.d.ts +17 -0
  44. package/lib/openapi/registry.js +38 -0
  45. package/lib/openapi/routes/ask-routes.d.ts +7 -0
  46. package/lib/openapi/routes/ask-routes.js +62 -0
  47. package/lib/openapi/routes/billing-routes.d.ts +1 -0
  48. package/lib/openapi/routes/billing-routes.js +66 -0
  49. package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
  50. package/lib/openapi/routes/image-registry-routes.js +98 -0
  51. package/lib/openapi/routes/index.d.ts +5 -0
  52. package/lib/openapi/routes/index.js +28 -0
  53. package/lib/openapi/routes/message-routes.d.ts +1 -0
  54. package/lib/openapi/routes/message-routes.js +105 -0
  55. package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
  56. package/lib/openapi/routes/pipeline-routes.js +105 -0
  57. package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
  58. package/lib/openapi/routes/pipeline-template-routes.js +61 -0
  59. package/lib/openapi/routes/plugin-routes.d.ts +1 -0
  60. package/lib/openapi/routes/plugin-routes.js +96 -0
  61. package/lib/openapi/routes/quota-routes.d.ts +1 -0
  62. package/lib/openapi/routes/quota-routes.js +62 -0
  63. package/lib/openapi/schema-registry.d.ts +25 -0
  64. package/lib/openapi/schema-registry.js +91 -0
  65. package/lib/routes/health.d.ts +41 -0
  66. package/lib/routes/health.js +82 -0
  67. package/lib/routes/index.d.ts +1 -0
  68. package/lib/routes/index.js +4 -0
  69. package/lib/services/audit-spool.d.ts +80 -0
  70. package/lib/services/audit-spool.js +172 -0
  71. package/lib/services/cache-service.d.ts +124 -0
  72. package/lib/services/cache-service.js +276 -0
  73. package/lib/services/circuit-breaker.d.ts +45 -0
  74. package/lib/services/circuit-breaker.js +138 -0
  75. package/lib/services/compliance-client.d.ts +46 -0
  76. package/lib/services/compliance-client.js +128 -0
  77. package/lib/services/compliance-event-subscriber.d.ts +16 -0
  78. package/lib/services/compliance-event-subscriber.js +72 -0
  79. package/lib/services/durable-event-bus.d.ts +96 -0
  80. package/lib/services/durable-event-bus.js +204 -0
  81. package/lib/services/entity-events.d.ts +48 -0
  82. package/lib/services/entity-events.js +79 -0
  83. package/lib/services/env-redis.d.ts +44 -0
  84. package/lib/services/env-redis.js +94 -0
  85. package/lib/services/http-client.d.ts +143 -0
  86. package/lib/services/http-client.js +335 -0
  87. package/lib/services/index.d.ts +18 -0
  88. package/lib/services/index.js +20 -0
  89. package/lib/services/leader-lock.d.ts +45 -0
  90. package/lib/services/leader-lock.js +85 -0
  91. package/lib/services/quota.d.ts +130 -0
  92. package/lib/services/quota.js +264 -0
  93. package/lib/services/remote-audit-client.d.ts +160 -0
  94. package/lib/services/remote-audit-client.js +390 -0
  95. package/lib/services/retry-strategy.d.ts +74 -0
  96. package/lib/services/retry-strategy.js +118 -0
  97. package/lib/services/scheduler.d.ts +41 -0
  98. package/lib/services/scheduler.js +86 -0
  99. package/lib/services/service-boot.d.ts +20 -0
  100. package/lib/services/service-boot.js +28 -0
  101. package/lib/services/sse-ticket-store.d.ts +51 -0
  102. package/lib/services/sse-ticket-store.js +130 -0
  103. package/lib/services/token-revocation.d.ts +52 -0
  104. package/lib/services/token-revocation.js +120 -0
  105. package/lib/testing/tier-mock.d.ts +44 -0
  106. package/lib/testing/tier-mock.js +65 -0
  107. package/lib/types/audit-events.d.ts +69 -0
  108. package/lib/types/audit-events.js +4 -0
  109. package/lib/types/catalog-metadata.d.ts +36 -0
  110. package/lib/types/catalog-metadata.js +21 -0
  111. package/lib/types/common.d.ts +255 -0
  112. package/lib/types/common.js +41 -0
  113. package/lib/types/error-codes.d.ts +54 -0
  114. package/lib/types/error-codes.js +107 -0
  115. package/lib/types/feature-flags.d.ts +36 -0
  116. package/lib/types/feature-flags.js +128 -0
  117. package/lib/types/http.d.ts +27 -0
  118. package/lib/types/http.js +4 -0
  119. package/lib/types/index.d.ts +11 -0
  120. package/lib/types/index.js +14 -0
  121. package/lib/types/permissions.d.ts +65 -0
  122. package/lib/types/permissions.js +131 -0
  123. package/lib/types/pipeline-template.d.ts +30 -0
  124. package/lib/types/pipeline-template.js +13 -0
  125. package/lib/types/pipeline.d.ts +59 -0
  126. package/lib/types/pipeline.js +31 -0
  127. package/lib/types/quota-tiers.d.ts +79 -0
  128. package/lib/types/quota-tiers.js +221 -0
  129. package/lib/types/visibility.d.ts +20 -0
  130. package/lib/types/visibility.js +22 -0
  131. package/lib/utils/alias-resolver.d.ts +37 -0
  132. package/lib/utils/alias-resolver.js +68 -0
  133. package/lib/utils/audit.d.ts +15 -0
  134. package/lib/utils/audit.js +39 -0
  135. package/lib/utils/aws-scrub.d.ts +11 -0
  136. package/lib/utils/aws-scrub.js +67 -0
  137. package/lib/utils/compliance-attributes.d.ts +6 -0
  138. package/lib/utils/compliance-attributes.js +69 -0
  139. package/lib/utils/concurrency.d.ts +9 -0
  140. package/lib/utils/concurrency.js +29 -0
  141. package/lib/utils/crash-handlers.d.ts +18 -0
  142. package/lib/utils/crash-handlers.js +36 -0
  143. package/lib/utils/env.d.ts +28 -0
  144. package/lib/utils/env.js +54 -0
  145. package/lib/utils/headers.d.ts +18 -0
  146. package/lib/utils/headers.js +21 -0
  147. package/lib/utils/identity.d.ts +40 -0
  148. package/lib/utils/identity.js +51 -0
  149. package/lib/utils/index.d.ts +17 -0
  150. package/lib/utils/index.js +20 -0
  151. package/lib/utils/logger.d.ts +38 -0
  152. package/lib/utils/logger.js +174 -0
  153. package/lib/utils/metric-emitter.d.ts +36 -0
  154. package/lib/utils/metric-emitter.js +32 -0
  155. package/lib/utils/object.d.ts +13 -0
  156. package/lib/utils/object.js +18 -0
  157. package/lib/utils/params.d.ts +150 -0
  158. package/lib/utils/params.js +206 -0
  159. package/lib/utils/response.d.ts +117 -0
  160. package/lib/utils/response.js +223 -0
  161. package/lib/utils/safe-require.d.ts +13 -0
  162. package/lib/utils/safe-require.js +19 -0
  163. package/lib/utils/secret-encryption.d.ts +176 -0
  164. package/lib/utils/secret-encryption.js +374 -0
  165. package/lib/utils/ssrf.d.ts +30 -0
  166. package/lib/utils/ssrf.js +106 -0
  167. package/lib/validation/ai-schemas.d.ts +56 -0
  168. package/lib/validation/ai-schemas.js +78 -0
  169. package/lib/validation/common-schemas.d.ts +133 -0
  170. package/lib/validation/common-schemas.js +94 -0
  171. package/lib/validation/index.d.ts +7 -0
  172. package/lib/validation/index.js +10 -0
  173. package/lib/validation/message-schemas.d.ts +160 -0
  174. package/lib/validation/message-schemas.js +156 -0
  175. package/lib/validation/middleware.d.ts +72 -0
  176. package/lib/validation/middleware.js +65 -0
  177. package/lib/validation/pipeline-schemas.d.ts +181 -0
  178. package/lib/validation/pipeline-schemas.js +101 -0
  179. package/lib/validation/pipeline-template-schemas.d.ts +155 -0
  180. package/lib/validation/pipeline-template-schemas.js +66 -0
  181. package/lib/validation/plugin-schemas.d.ts +171 -0
  182. package/lib/validation/plugin-schemas.js +81 -0
  183. package/openapi.yaml +292 -0
  184. package/package.json +167 -0
@@ -0,0 +1,174 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import winston from 'winston';
4
+ import { safeCreateRequire } from './safe-require.js';
5
+ const { combine, timestamp, printf, colorize, errors, json } = winston.format;
6
+ // ESM has no global `require`; safeCreateRequire enables the synchronous,
7
+ // OPTIONAL lazy-load of @opentelemetry/api in getCurrentTraceId() below — a
8
+ // winston format callback must stay synchronous, so a dynamic import() won't
9
+ // work there. (CJS-bundle safe — see safe-require.ts.)
10
+ const require = safeCreateRequire(import.meta.url);
11
+ /**
12
+ * Custom log format for human-readable console output.
13
+ */
14
+ const consoleFormat = printf(({ level, message, timestamp, service, ...meta }) => {
15
+ const serviceName = service ? `[${service}]` : '';
16
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
17
+ return `${timestamp} ${level} ${serviceName} ${message}${metaStr}`;
18
+ });
19
+ /**
20
+ * Lazy lookup of OpenTelemetry trace ID. Returns undefined when tracing
21
+ * isn't initialized or @opentelemetry/api isn't installed — never throws.
22
+ * Cheap: a single property read after the first successful lookup.
23
+ */
24
+ let _otelApi;
25
+ function getCurrentTraceId() {
26
+ if (_otelApi === undefined) {
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
29
+ _otelApi = require('@opentelemetry/api');
30
+ }
31
+ catch {
32
+ _otelApi = null;
33
+ }
34
+ }
35
+ if (!_otelApi)
36
+ return undefined;
37
+ try {
38
+ return _otelApi.trace?.getActiveSpan()?.spanContext().traceId;
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ /** Winston format that stamps `trace_id` on every entry when an OTel span is active. */
45
+ const traceIdFormat = winston.format((info) => {
46
+ const traceId = getCurrentTraceId();
47
+ if (traceId)
48
+ info.trace_id = traceId;
49
+ return info;
50
+ })();
51
+ /**
52
+ * Keys that should never appear in logs. Match is case-insensitive and
53
+ * anchored substring — `Authorization`, `auth_header`, `MY_PASSWORD` all hit.
54
+ * Add a new term here only if you've seen real-world leakage; over-redaction
55
+ * makes incident debugging harder.
56
+ */
57
+ const SENSITIVE_KEY_PATTERN = /password|secret|bearer|api[_-]?key|cookie|token|^auth(orization|_header)?$|stripe[_-]?(key|secret)|mongo(db)?[_-]?uri/i;
58
+ const REDACTED = '[REDACTED]';
59
+ function redactDeep(value, depth = 0) {
60
+ // Cap depth so a malicious / pathological circular object can't lock the logger.
61
+ // Degrade to a placeholder rather than the raw subtree: returning `value` here
62
+ // would log a secret-keyed field nested deeper than the cap in cleartext.
63
+ if (depth > 6)
64
+ return '[TRUNCATED]';
65
+ if (value == null || typeof value !== 'object')
66
+ return value;
67
+ if (Array.isArray(value))
68
+ return value.map((v) => redactDeep(v, depth + 1));
69
+ const out = {};
70
+ for (const [k, v] of Object.entries(value)) {
71
+ if (SENSITIVE_KEY_PATTERN.test(k)) {
72
+ out[k] = REDACTED;
73
+ }
74
+ else {
75
+ out[k] = redactDeep(v, depth + 1);
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * Redact sensitive values from an ARBITRARY payload using the same key-pattern
82
+ * rules the Winston `redactFormat` applies to log metadata. Use this for
83
+ * payloads that reach an output channel OUTSIDE the logger — e.g. an SSE frame
84
+ * pushed straight to a client — so a `password`/`token`/`secret` field is
85
+ * masked there too. A top-level sensitive key is masked; nested objects/arrays
86
+ * are walked (depth-capped) exactly as in the logger. Returns a redacted COPY;
87
+ * primitives pass through unchanged.
88
+ */
89
+ export function redactSensitive(value) {
90
+ if (value == null || typeof value !== 'object')
91
+ return value;
92
+ return redactDeep(value);
93
+ }
94
+ /** Winston format that masks values for sensitive-looking keys (PII / secrets).
95
+ * Mutates `info` in place so winston's internal Symbol-keyed properties
96
+ * (`Symbol.for('level')`, `Symbol.for('message')`, `Symbol.for('splat')`) survive
97
+ * — rebuilding a fresh object via `Object.entries`/spread silently strips them,
98
+ * which makes the Console transport drop every entry without warning. */
99
+ const PRESERVED_KEYS = new Set(['level', 'message', 'timestamp', 'service', 'trace_id']);
100
+ const redactFormat = winston.format((info) => {
101
+ for (const key of Object.keys(info)) {
102
+ if (PRESERVED_KEYS.has(key))
103
+ continue;
104
+ if (SENSITIVE_KEY_PATTERN.test(key)) {
105
+ info[key] = REDACTED;
106
+ }
107
+ else {
108
+ info[key] = redactDeep(info[key]);
109
+ }
110
+ }
111
+ return info;
112
+ })();
113
+ /**
114
+ * Create a logger instance for a service.
115
+ *
116
+ * When LOG_FORMAT=json (default), outputs structured JSON for Loki ingestion:
117
+ * {"level":"info","message":"Server started","service":"pipeline","timestamp":"..."}
118
+ *
119
+ * When LOG_FORMAT=text, outputs colorized human-readable format:
120
+ * 2026-02-13T10:30:00.000Z info [pipeline] Server started
121
+ *
122
+ * @param serviceName - Name of the service for log identification
123
+ * @returns Configured Winston logger instance
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * import { createLogger } from '@pipeline-builder/api-core';
128
+ *
129
+ * const logger = createLogger('get-plugin');
130
+ * logger.info('Server started', { port: 3000 });
131
+ * logger.error('Database error', { error: err.message });
132
+ * ```
133
+ */
134
+ export function createLogger(serviceName) {
135
+ const logLevel = process.env.LOG_LEVEL || 'info';
136
+ const logFormat = process.env.LOG_FORMAT || 'json';
137
+ if (logFormat !== 'json' && logFormat !== 'text') {
138
+ // eslint-disable-next-line no-console -- startup warning before logger is available
139
+ console.warn(`Invalid LOG_FORMAT="${logFormat}", expected "json" or "text". Defaulting to "json".`);
140
+ }
141
+ const useJson = logFormat !== 'text';
142
+ // Order matters: redact BEFORE serialization so masked keys never reach
143
+ // the output. trace_id is stamped first so it survives redaction.
144
+ const baseFormats = [
145
+ errors({ stack: true }),
146
+ timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }),
147
+ traceIdFormat,
148
+ redactFormat,
149
+ ];
150
+ if (useJson) {
151
+ return winston.createLogger({
152
+ level: logLevel,
153
+ defaultMeta: { service: serviceName },
154
+ format: combine(...baseFormats, json()),
155
+ transports: [new winston.transports.Console()],
156
+ });
157
+ }
158
+ return winston.createLogger({
159
+ level: logLevel,
160
+ defaultMeta: { service: serviceName },
161
+ format: combine(...baseFormats),
162
+ transports: [
163
+ new winston.transports.Console({
164
+ format: combine(colorize(), consoleFormat),
165
+ }),
166
+ ],
167
+ });
168
+ }
169
+ /**
170
+ * Default logger instance (service name from SERVICE_NAME env var or 'api').
171
+ */
172
+ export const logger = createLogger(process.env.SERVICE_NAME || 'api');
173
+ export default logger;
174
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9nZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL2xvZ2dlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sT0FBTyxNQUFNLFNBQVMsQ0FBQztBQUM5QixPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUV0RCxNQUFNLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO0FBRTlFLDBFQUEwRTtBQUMxRSw0RUFBNEU7QUFDNUUsNkVBQTZFO0FBQzdFLHVEQUF1RDtBQUN2RCxNQUFNLE9BQU8sR0FBRyxpQkFBaUIsQ0FBQyxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUVuRDs7R0FFRztBQUNILE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLEdBQUcsSUFBSSxFQUFFLEVBQUUsRUFBRTtJQUMvRSxNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNsRCxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUMzRSxPQUFPLEdBQUcsU0FBUyxJQUFJLEtBQUssSUFBSSxXQUFXLElBQUksT0FBTyxHQUFHLE9BQU8sRUFBRSxDQUFDO0FBQ3JFLENBQUMsQ0FBQyxDQUFDO0FBRUg7Ozs7R0FJRztBQUNILElBQUksUUFBZ0gsQ0FBQztBQUNySCxT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QUFXLEVBQUUsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFO1lBQ3JDLE1BQU0sRUFBRSxPQUFPLENBQUMsR0FBRyxXQUFXLEVBQUUsSUFBSSxFQUFFLENBQUM7WUFDdkMsVUFBVSxFQUFFLENBQUMsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO1NBQy9DLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxPQUFPLE9BQU8sQ0FBQyxZQUFZLENBQUM7UUFDMUIsS0FBSyxFQUFFLFFBQVE7UUFDZixXQUFXLEVBQUUsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFO1FBQ3JDLE1BQU0sRUFBRSxPQUFPLENBQUMsR0FBRyxXQUFXLENBQUM7UUFDL0IsVUFBVSxFQUFFO1lBQ1YsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQztnQkFDN0IsTUFBTSxFQUFFLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRSxhQUFhLENBQUM7YUFDM0MsQ0FBQztTQUNIO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksSUFBSSxLQUFLLENBQUMsQ0FBQztBQUV0RSxlQUFlLE1BQU0sQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgd2luc3RvbiBmcm9tICd3aW5zdG9uJztcbmltcG9ydCB7IHNhZmVDcmVhdGVSZXF1aXJlIH0gZnJvbSAnLi9zYWZlLXJlcXVpcmUuanMnO1xuXG5jb25zdCB7IGNvbWJpbmUsIHRpbWVzdGFtcCwgcHJpbnRmLCBjb2xvcml6ZSwgZXJyb3JzLCBqc29uIH0gPSB3aW5zdG9uLmZvcm1hdDtcblxuLy8gRVNNIGhhcyBubyBnbG9iYWwgYHJlcXVpcmVgOyBzYWZlQ3JlYXRlUmVxdWlyZSBlbmFibGVzIHRoZSBzeW5jaHJvbm91cyxcbi8vIE9QVElPTkFMIGxhenktbG9hZCBvZiBAb3BlbnRlbGVtZXRyeS9hcGkgaW4gZ2V0Q3VycmVudFRyYWNlSWQoKSBiZWxvdyDigJQgYVxuLy8gd2luc3RvbiBmb3JtYXQgY2FsbGJhY2sgbXVzdCBzdGF5IHN5bmNocm9ub3VzLCBzbyBhIGR5bmFtaWMgaW1wb3J0KCkgd29uJ3Rcbi8vIHdvcmsgdGhlcmUuIChDSlMtYnVuZGxlIHNhZmUg4oCUIHNlZSBzYWZlLXJlcXVpcmUudHMuKVxuY29uc3QgcmVxdWlyZSA9IHNhZmVDcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCk7XG5cbi8qKlxuICogQ3VzdG9tIGxvZyBmb3JtYXQgZm9yIGh1bWFuLXJlYWRhYmxlIGNvbnNvbGUgb3V0cHV0LlxuICovXG5jb25zdCBjb25zb2xlRm9ybWF0ID0gcHJpbnRmKCh7IGxldmVsLCBtZXNzYWdlLCB0aW1lc3RhbXAsIHNlcnZpY2UsIC4uLm1ldGEgfSkgPT4ge1xuICBjb25zdCBzZXJ2aWNlTmFtZSA9IHNlcnZpY2UgPyBgWyR7c2VydmljZX1dYCA6ICcnO1xuICBjb25zdCBtZXRhU3RyID0gT2JqZWN0LmtleXMobWV0YSkubGVuZ3RoID8gYCAke0pTT04uc3RyaW5naWZ5KG1ldGEpfWAgOiAnJztcbiAgcmV0dXJuIGAke3RpbWVzdGFtcH0gJHtsZXZlbH0gJHtzZXJ2aWNlTmFtZX0gJHttZXNzYWdlfSR7bWV0YVN0cn1gO1xufSk7XG5cbi8qKlxuICogTGF6eSBsb29rdXAgb2YgT3BlblRlbGVtZXRyeSB0cmFjZSBJRC4gUmV0dXJucyB1bmRlZmluZWQgd2hlbiB0cmFjaW5nXG4gKiBpc24ndCBpbml0aWFsaXplZCBvciBAb3BlbnRlbGVtZXRyeS9hcGkgaXNuJ3QgaW5zdGFsbGVkIOKAlCBuZXZlciB0aHJvd3MuXG4gKiBDaGVhcDogYSBzaW5nbGUgcHJvcGVydHkgcmVhZCBhZnRlciB0aGUgZmlyc3Qgc3VjY2Vzc2Z1bCBsb29rdXAuXG4gKi9cbmxldCBfb3RlbEFwaTogeyB0cmFjZT86IHsgZ2V0QWN0aXZlU3BhbigpOiB7IHNwYW5Db250ZXh0KCk6IHsgdHJhY2VJZDogc3RyaW5nIH0gfSB8IHVuZGVmaW5lZCB9IH0gfCBudWxsIHwgdW5kZWZpbmVkO1xuZnVuY3Rpb24gZ2V0Q3VycmVudFRyYWNlSWQoKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgaWYgKF9vdGVsQXBpID09PSB1bmRlZmluZWQpIHtcbiAgICB0cnkge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICAgIF9vdGVsQXBpID0gcmVxdWlyZSgnQG9wZW50ZWxlbWV0cnkvYXBpJyk7XG4gICAgfSBjYXRjaCB7XG4gICAgICBfb3RlbEFwaSA9IG51bGw7XG4gICAgfVxuICB9XG4gIGlmICghX290ZWxBcGkpIHJldHVybiB1bmRlZmluZWQ7XG4gIHRyeSB7XG4gICAgcmV0dXJuIF9vdGVsQXBpLnRyYWNlPy5nZXRBY3RpdmVTcGFuKCk/LnNwYW5Db250ZXh0KCkudHJhY2VJZDtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxufVxuXG4vKiogV2luc3RvbiBmb3JtYXQgdGhhdCBzdGFtcHMgYHRyYWNlX2lkYCBvbiBldmVyeSBlbnRyeSB3aGVuIGFuIE9UZWwgc3BhbiBpcyBhY3RpdmUuICovXG5jb25zdCB0cmFjZUlkRm9ybWF0ID0gd2luc3Rvbi5mb3JtYXQoKGluZm8pID0+IHtcbiAgY29uc3QgdHJhY2VJZCA9IGdldEN1cnJlbnRUcmFjZUlkKCk7XG4gIGlmICh0cmFjZUlkKSBpbmZvLnRyYWNlX2lkID0gdHJhY2VJZDtcbiAgcmV0dXJuIGluZm87XG59KSgpO1xuXG4vKipcbiAqIEtleXMgdGhhdCBzaG91bGQgbmV2ZXIgYXBwZWFyIGluIGxvZ3MuIE1hdGNoIGlzIGNhc2UtaW5zZW5zaXRpdmUgYW5kXG4gKiBhbmNob3JlZCBzdWJzdHJpbmcg4oCUIGBBdXRob3JpemF0aW9uYCwgYGF1dGhfaGVhZGVyYCwgYE1ZX1BBU1NXT1JEYCBhbGwgaGl0LlxuICogQWRkIGEgbmV3IHRlcm0gaGVyZSBvbmx5IGlmIHlvdSd2ZSBzZWVuIHJlYWwtd29ybGQgbGVha2FnZTsgb3Zlci1yZWRhY3Rpb25cbiAqIG1ha2VzIGluY2lkZW50IGRlYnVnZ2luZyBoYXJkZXIuXG4gKi9cbmNvbnN0IFNFTlNJVElWRV9LRVlfUEFUVEVSTiA9IC9wYXNzd29yZHxzZWNyZXR8YmVhcmVyfGFwaVtfLV0/a2V5fGNvb2tpZXx0b2tlbnxeYXV0aChvcml6YXRpb258X2hlYWRlcik/JHxzdHJpcGVbXy1dPyhrZXl8c2VjcmV0KXxtb25nbyhkYik/W18tXT91cmkvaTtcbmNvbnN0IFJFREFDVEVEID0gJ1tSRURBQ1RFRF0nO1xuXG5mdW5jdGlvbiByZWRhY3REZWVwKHZhbHVlOiB1bmtub3duLCBkZXB0aCA9IDApOiB1bmtub3duIHtcbiAgLy8gQ2FwIGRlcHRoIHNvIGEgbWFsaWNpb3VzIC8gcGF0aG9sb2dpY2FsIGNpcmN1bGFyIG9iamVjdCBjYW4ndCBsb2NrIHRoZSBsb2dnZXIuXG4gIC8vIERlZ3JhZGUgdG8gYSBwbGFjZWhvbGRlciByYXRoZXIgdGhhbiB0aGUgcmF3IHN1YnRyZWU6IHJldHVybmluZyBgdmFsdWVgIGhlcmVcbiAgLy8gd291bGQgbG9nIGEgc2VjcmV0LWtleWVkIGZpZWxkIG5lc3RlZCBkZWVwZXIgdGhhbiB0aGUgY2FwIGluIGNsZWFydGV4dC5cbiAgaWYgKGRlcHRoID4gNikgcmV0dXJuICdbVFJVTkNBVEVEXSc7XG4gIGlmICh2YWx1ZSA9PSBudWxsIHx8IHR5cGVvZiB2YWx1ZSAhPT0gJ29iamVjdCcpIHJldHVybiB2YWx1ZTtcbiAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSByZXR1cm4gdmFsdWUubWFwKCh2KSA9PiByZWRhY3REZWVwKHYsIGRlcHRoICsgMSkpO1xuICBjb25zdCBvdXQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG4gIGZvciAoY29uc3QgW2ssIHZdIG9mIE9iamVjdC5lbnRyaWVzKHZhbHVlIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KSkge1xuICAgIGlmIChTRU5TSVRJVkVfS0VZX1BBVFRFUk4udGVzdChrKSkge1xuICAgICAgb3V0W2tdID0gUkVEQUNURUQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIG91dFtrXSA9IHJlZGFjdERlZXAodiwgZGVwdGggKyAxKTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIG91dDtcbn1cblxuLyoqXG4gKiBSZWRhY3Qgc2Vuc2l0aXZlIHZhbHVlcyBmcm9tIGFuIEFSQklUUkFSWSBwYXlsb2FkIHVzaW5nIHRoZSBzYW1lIGtleS1wYXR0ZXJuXG4gKiBydWxlcyB0aGUgV2luc3RvbiBgcmVkYWN0Rm9ybWF0YCBhcHBsaWVzIHRvIGxvZyBtZXRhZGF0YS4gVXNlIHRoaXMgZm9yXG4gKiBwYXlsb2FkcyB0aGF0IHJlYWNoIGFuIG91dHB1dCBjaGFubmVsIE9VVFNJREUgdGhlIGxvZ2dlciDigJQgZS5nLiBhbiBTU0UgZnJhbWVcbiAqIHB1c2hlZCBzdHJhaWdodCB0byBhIGNsaWVudCDigJQgc28gYSBgcGFzc3dvcmRgL2B0b2tlbmAvYHNlY3JldGAgZmllbGQgaXNcbiAqIG1hc2tlZCB0aGVyZSB0b28uIEEgdG9wLWxldmVsIHNlbnNpdGl2ZSBrZXkgaXMgbWFza2VkOyBuZXN0ZWQgb2JqZWN0cy9hcnJheXNcbiAqIGFyZSB3YWxrZWQgKGRlcHRoLWNhcHBlZCkgZXhhY3RseSBhcyBpbiB0aGUgbG9nZ2VyLiBSZXR1cm5zIGEgcmVkYWN0ZWQgQ09QWTtcbiAqIHByaW1pdGl2ZXMgcGFzcyB0aHJvdWdoIHVuY2hhbmdlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlZGFjdFNlbnNpdGl2ZSh2YWx1ZTogdW5rbm93bik6IHVua25vd24ge1xuICBpZiAodmFsdWUgPT0gbnVsbCB8fCB0eXBlb2YgdmFsdWUgIT09ICdvYmplY3QnKSByZXR1cm4gdmFsdWU7XG4gIHJldHVybiByZWRhY3REZWVwKHZhbHVlKTtcbn1cblxuLyoqIFdpbnN0b24gZm9ybWF0IHRoYXQgbWFza3MgdmFsdWVzIGZvciBzZW5zaXRpdmUtbG9va2luZyBrZXlzIChQSUkgLyBzZWNyZXRzKS5cbiAqIE11dGF0ZXMgYGluZm9gIGluIHBsYWNlIHNvIHdpbnN0b24ncyBpbnRlcm5hbCBTeW1ib2wta2V5ZWQgcHJvcGVydGllc1xuICogKGBTeW1ib2wuZm9yKCdsZXZlbCcpYCwgYFN5bWJvbC5mb3IoJ21lc3NhZ2UnKWAsIGBTeW1ib2wuZm9yKCdzcGxhdCcpYCkgc3Vydml2ZVxuICog4oCUIHJlYnVpbGRpbmcgYSBmcmVzaCBvYmplY3QgdmlhIGBPYmplY3QuZW50cmllc2Avc3ByZWFkIHNpbGVudGx5IHN0cmlwcyB0aGVtLFxuICogd2hpY2ggbWFrZXMgdGhlIENvbnNvbGUgdHJhbnNwb3J0IGRyb3AgZXZlcnkgZW50cnkgd2l0aG91dCB3YXJuaW5nLiAqL1xuY29uc3QgUFJFU0VSVkVEX0tFWVMgPSBuZXcgU2V0KFsnbGV2ZWwnLCAnbWVzc2FnZScsICd0aW1lc3RhbXAnLCAnc2VydmljZScsICd0cmFjZV9pZCddKTtcbmNvbnN0IHJlZGFjdEZvcm1hdCA9IHdpbnN0b24uZm9ybWF0KChpbmZvKSA9PiB7XG4gIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKGluZm8pKSB7XG4gICAgaWYgKFBSRVNFUlZFRF9LRVlTLmhhcyhrZXkpKSBjb250aW51ZTtcbiAgICBpZiAoU0VOU0lUSVZFX0tFWV9QQVRURVJOLnRlc3Qoa2V5KSkge1xuICAgICAgaW5mb1trZXldID0gUkVEQUNURUQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIGluZm9ba2V5XSA9IHJlZGFjdERlZXAoaW5mb1trZXldKTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGluZm87XG59KSgpO1xuXG4vKipcbiAqIENyZWF0ZSBhIGxvZ2dlciBpbnN0YW5jZSBmb3IgYSBzZXJ2aWNlLlxuICpcbiAqIFdoZW4gTE9HX0ZPUk1BVD1qc29uIChkZWZhdWx0KSwgb3V0cHV0cyBzdHJ1Y3R1cmVkIEpTT04gZm9yIExva2kgaW5nZXN0aW9uOlxuICogICB7XCJsZXZlbFwiOlwiaW5mb1wiLFwibWVzc2FnZVwiOlwiU2VydmVyIHN0YXJ0ZWRcIixcInNlcnZpY2VcIjpcInBpcGVsaW5lXCIsXCJ0aW1lc3RhbXBcIjpcIi4uLlwifVxuICpcbiAqIFdoZW4gTE9HX0ZPUk1BVD10ZXh0LCBvdXRwdXRzIGNvbG9yaXplZCBodW1hbi1yZWFkYWJsZSBmb3JtYXQ6XG4gKiAgIDIwMjYtMDItMTNUMTA6MzA6MDAuMDAwWiBpbmZvIFtwaXBlbGluZV0gU2VydmVyIHN0YXJ0ZWRcbiAqXG4gKiBAcGFyYW0gc2VydmljZU5hbWUgLSBOYW1lIG9mIHRoZSBzZXJ2aWNlIGZvciBsb2cgaWRlbnRpZmljYXRpb25cbiAqIEByZXR1cm5zIENvbmZpZ3VyZWQgV2luc3RvbiBsb2dnZXIgaW5zdGFuY2VcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuICpcbiAqIGNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignZ2V0LXBsdWdpbicpO1xuICogbG9nZ2VyLmluZm8oJ1NlcnZlciBzdGFydGVkJywgeyBwb3J0OiAzMDAwIH0pO1xuICogbG9nZ2VyLmVycm9yKCdEYXRhYmFzZSBlcnJvcicsIHsgZXJyb3I6IGVyci5tZXNzYWdlIH0pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVMb2dnZXIoc2VydmljZU5hbWU6IHN0cmluZyk6IHdpbnN0b24uTG9nZ2VyIHtcbiAgY29uc3QgbG9nTGV2ZWwgPSBwcm9jZXNzLmVudi5MT0dfTEVWRUwgfHwgJ2luZm8nO1xuICBjb25zdCBsb2dGb3JtYXQgPSBwcm9jZXNzLmVudi5MT0dfRk9STUFUIHx8ICdqc29uJztcbiAgaWYgKGxvZ0Zvcm1hdCAhPT0gJ2pzb24nICYmIGxvZ0Zvcm1hdCAhPT0gJ3RleHQnKSB7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGUgLS0gc3RhcnR1cCB3YXJuaW5nIGJlZm9yZSBsb2dnZXIgaXMgYXZhaWxhYmxlXG4gICAgY29uc29sZS53YXJuKGBJbnZhbGlkIExPR19GT1JNQVQ9XCIke2xvZ0Zvcm1hdH1cIiwgZXhwZWN0ZWQgXCJqc29uXCIgb3IgXCJ0ZXh0XCIuIERlZmF1bHRpbmcgdG8gXCJqc29uXCIuYCk7XG4gIH1cbiAgY29uc3QgdXNlSnNvbiA9IGxvZ0Zvcm1hdCAhPT0gJ3RleHQnO1xuXG4gIC8vIE9yZGVyIG1hdHRlcnM6IHJlZGFjdCBCRUZPUkUgc2VyaWFsaXphdGlvbiBzbyBtYXNrZWQga2V5cyBuZXZlciByZWFjaFxuICAvLyB0aGUgb3V0cHV0LiB0cmFjZV9pZCBpcyBzdGFtcGVkIGZpcnN0IHNvIGl0IHN1cnZpdmVzIHJlZGFjdGlvbi5cbiAgY29uc3QgYmFzZUZvcm1hdHMgPSBbXG4gICAgZXJyb3JzKHsgc3RhY2s6IHRydWUgfSksXG4gICAgdGltZXN0YW1wKHsgZm9ybWF0OiAnWVlZWS1NTS1ERFRISDptbTpzcy5TU1NaJyB9KSxcbiAgICB0cmFjZUlkRm9ybWF0LFxuICAgIHJlZGFjdEZvcm1hdCxcbiAgXTtcblxuICBpZiAodXNlSnNvbikge1xuICAgIHJldHVybiB3aW5zdG9uLmNyZWF0ZUxvZ2dlcih7XG4gICAgICBsZXZlbDogbG9nTGV2ZWwsXG4gICAgICBkZWZhdWx0TWV0YTogeyBzZXJ2aWNlOiBzZXJ2aWNlTmFtZSB9LFxuICAgICAgZm9ybWF0OiBjb21iaW5lKC4uLmJhc2VGb3JtYXRzLCBqc29uKCkpLFxuICAgICAgdHJhbnNwb3J0czogW25ldyB3aW5zdG9uLnRyYW5zcG9ydHMuQ29uc29sZSgpXSxcbiAgICB9KTtcbiAgfVxuXG4gIHJldHVybiB3aW5zdG9uLmNyZWF0ZUxvZ2dlcih7XG4gICAgbGV2ZWw6IGxvZ0xldmVsLFxuICAgIGRlZmF1bHRNZXRhOiB7IHNlcnZpY2U6IHNlcnZpY2VOYW1lIH0sXG4gICAgZm9ybWF0OiBjb21iaW5lKC4uLmJhc2VGb3JtYXRzKSxcbiAgICB0cmFuc3BvcnRzOiBbXG4gICAgICBuZXcgd2luc3Rvbi50cmFuc3BvcnRzLkNvbnNvbGUoe1xuICAgICAgICBmb3JtYXQ6IGNvbWJpbmUoY29sb3JpemUoKSwgY29uc29sZUZvcm1hdCksXG4gICAgICB9KSxcbiAgICBdLFxuICB9KTtcbn1cblxuLyoqXG4gKiBEZWZhdWx0IGxvZ2dlciBpbnN0YW5jZSAoc2VydmljZSBuYW1lIGZyb20gU0VSVklDRV9OQU1FIGVudiB2YXIgb3IgJ2FwaScpLlxuICovXG5leHBvcnQgY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKHByb2Nlc3MuZW52LlNFUlZJQ0VfTkFNRSB8fCAnYXBpJyk7XG5cbmV4cG9ydCBkZWZhdWx0IGxvZ2dlcjtcbiJdfQ==
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pluggable counter-emit shim.
3
+ *
4
+ * Background: several api-core primitives (quota client fail-open, future
5
+ * security-event hooks) want to record Prometheus counters when something
6
+ * notable happens. The actual `prom-client` Counter registration lives in
7
+ * api-server, and api-core must not depend on api-server (api-core is
8
+ * upstream — Express infrastructure imports it, not the other way around).
9
+ *
10
+ * This module is the seam. api-core code calls `emitCounter(name, labels)`;
11
+ * by default it's a no-op (tests, CLIs, environments without Prometheus).
12
+ * api-server's app-factory calls `setCounterEmitter(incCounter)` at startup
13
+ * so production processes start incrementing the real counter on the
14
+ * shared registry.
15
+ *
16
+ * Why a callback instead of an interface registered via DI? The call sites
17
+ * (quota fail-open, etc.) sit in deep helpers that never touch the Express
18
+ * context, so requiring a `metrics` parameter on every call would mean
19
+ * threading it through dozens of unrelated functions. A process-singleton
20
+ * callback is the simplest non-invasive option; the trade-off is one
21
+ * shared global, which is acceptable for a sink that's strictly write-only.
22
+ */
23
+ /** Callable signature mirroring api-server's `incCounter`. */
24
+ export type CounterEmitter = (name: string, labels?: Record<string, string>, value?: number) => void;
25
+ /**
26
+ * Register the real counter implementation. Idempotent — the last call wins;
27
+ * typically called once at service startup from api-server.
28
+ */
29
+ export declare function setCounterEmitter(fn: CounterEmitter): void;
30
+ /** Reset to the no-op emitter. For tests that want to clear any wiring. */
31
+ export declare function resetCounterEmitter(): void;
32
+ /**
33
+ * Record a counter event. Safe to call from any api-core module — if no
34
+ * emitter is wired, this is a no-op.
35
+ */
36
+ export declare function emitCounter(name: string, labels?: Record<string, string>, value?: number): void;
@@ -0,0 +1,32 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ let emitter = () => {
4
+ // Default no-op. api-server wires this to a real Prometheus counter at
5
+ // startup; until then, calls from api-core helpers are silently dropped
6
+ // (which is what we want in tests and CLIs that don't run a Prom server).
7
+ };
8
+ /**
9
+ * Register the real counter implementation. Idempotent — the last call wins;
10
+ * typically called once at service startup from api-server.
11
+ */
12
+ export function setCounterEmitter(fn) {
13
+ emitter = fn;
14
+ }
15
+ /** Reset to the no-op emitter. For tests that want to clear any wiring. */
16
+ export function resetCounterEmitter() {
17
+ emitter = () => undefined;
18
+ }
19
+ /**
20
+ * Record a counter event. Safe to call from any api-core module — if no
21
+ * emitter is wired, this is a no-op.
22
+ */
23
+ export function emitCounter(name, labels = {}, value = 1) {
24
+ try {
25
+ emitter(name, labels, value);
26
+ }
27
+ catch {
28
+ // Never let metric emission break the calling path. A misbehaving
29
+ // emitter shouldn't be able to take down quota checks (or anything else).
30
+ }
31
+ }
32
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljLWVtaXR0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvbWV0cmljLWVtaXR0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQTRCdEMsSUFBSSxPQUFPLEdBQW1CLEdBQUcsRUFBRTtJQUNqQyx1RUFBdUU7SUFDdkUsd0VBQXdFO0lBQ3hFLDBFQUEwRTtBQUM1RSxDQUFDLENBQUM7QUFFRjs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsaUJBQWlCLENBQUMsRUFBa0I7SUFDbEQsT0FBTyxHQUFHLEVBQUUsQ0FBQztBQUNmLENBQUM7QUFFRCwyRUFBMkU7QUFDM0UsTUFBTSxVQUFVLG1CQUFtQjtJQUNqQyxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUMsU0FBUyxDQUFDO0FBQzVCLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUFDLElBQVksRUFBRSxNQUFNLEdBQTJCLEVBQUUsRUFBRSxLQUFLLEdBQUcsQ0FBQztJQUN0RixJQUFJLENBQUM7UUFDSCxPQUFPLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQztJQUMvQixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1Asa0VBQWtFO1FBQ2xFLDBFQUEwRTtJQUM1RSxDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFBsdWdnYWJsZSBjb3VudGVyLWVtaXQgc2hpbS5cbiAqXG4gKiBCYWNrZ3JvdW5kOiBzZXZlcmFsIGFwaS1jb3JlIHByaW1pdGl2ZXMgKHF1b3RhIGNsaWVudCBmYWlsLW9wZW4sIGZ1dHVyZVxuICogc2VjdXJpdHktZXZlbnQgaG9va3MpIHdhbnQgdG8gcmVjb3JkIFByb21ldGhldXMgY291bnRlcnMgd2hlbiBzb21ldGhpbmdcbiAqIG5vdGFibGUgaGFwcGVucy4gVGhlIGFjdHVhbCBgcHJvbS1jbGllbnRgIENvdW50ZXIgcmVnaXN0cmF0aW9uIGxpdmVzIGluXG4gKiBhcGktc2VydmVyLCBhbmQgYXBpLWNvcmUgbXVzdCBub3QgZGVwZW5kIG9uIGFwaS1zZXJ2ZXIgKGFwaS1jb3JlIGlzXG4gKiB1cHN0cmVhbSDigJQgRXhwcmVzcyBpbmZyYXN0cnVjdHVyZSBpbXBvcnRzIGl0LCBub3QgdGhlIG90aGVyIHdheSBhcm91bmQpLlxuICpcbiAqIFRoaXMgbW9kdWxlIGlzIHRoZSBzZWFtLiBhcGktY29yZSBjb2RlIGNhbGxzIGBlbWl0Q291bnRlcihuYW1lLCBsYWJlbHMpYDtcbiAqIGJ5IGRlZmF1bHQgaXQncyBhIG5vLW9wICh0ZXN0cywgQ0xJcywgZW52aXJvbm1lbnRzIHdpdGhvdXQgUHJvbWV0aGV1cykuXG4gKiBhcGktc2VydmVyJ3MgYXBwLWZhY3RvcnkgY2FsbHMgYHNldENvdW50ZXJFbWl0dGVyKGluY0NvdW50ZXIpYCBhdCBzdGFydHVwXG4gKiBzbyBwcm9kdWN0aW9uIHByb2Nlc3NlcyBzdGFydCBpbmNyZW1lbnRpbmcgdGhlIHJlYWwgY291bnRlciBvbiB0aGVcbiAqIHNoYXJlZCByZWdpc3RyeS5cbiAqXG4gKiBXaHkgYSBjYWxsYmFjayBpbnN0ZWFkIG9mIGFuIGludGVyZmFjZSByZWdpc3RlcmVkIHZpYSBEST8gVGhlIGNhbGwgc2l0ZXNcbiAqIChxdW90YSBmYWlsLW9wZW4sIGV0Yy4pIHNpdCBpbiBkZWVwIGhlbHBlcnMgdGhhdCBuZXZlciB0b3VjaCB0aGUgRXhwcmVzc1xuICogY29udGV4dCwgc28gcmVxdWlyaW5nIGEgYG1ldHJpY3NgIHBhcmFtZXRlciBvbiBldmVyeSBjYWxsIHdvdWxkIG1lYW5cbiAqIHRocmVhZGluZyBpdCB0aHJvdWdoIGRvemVucyBvZiB1bnJlbGF0ZWQgZnVuY3Rpb25zLiBBIHByb2Nlc3Mtc2luZ2xldG9uXG4gKiBjYWxsYmFjayBpcyB0aGUgc2ltcGxlc3Qgbm9uLWludmFzaXZlIG9wdGlvbjsgdGhlIHRyYWRlLW9mZiBpcyBvbmVcbiAqIHNoYXJlZCBnbG9iYWwsIHdoaWNoIGlzIGFjY2VwdGFibGUgZm9yIGEgc2luayB0aGF0J3Mgc3RyaWN0bHkgd3JpdGUtb25seS5cbiAqL1xuXG4vKiogQ2FsbGFibGUgc2lnbmF0dXJlIG1pcnJvcmluZyBhcGktc2VydmVyJ3MgYGluY0NvdW50ZXJgLiAqL1xuZXhwb3J0IHR5cGUgQ291bnRlckVtaXR0ZXIgPSAobmFtZTogc3RyaW5nLCBsYWJlbHM/OiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+LCB2YWx1ZT86IG51bWJlcikgPT4gdm9pZDtcblxubGV0IGVtaXR0ZXI6IENvdW50ZXJFbWl0dGVyID0gKCkgPT4ge1xuICAvLyBEZWZhdWx0IG5vLW9wLiBhcGktc2VydmVyIHdpcmVzIHRoaXMgdG8gYSByZWFsIFByb21ldGhldXMgY291bnRlciBhdFxuICAvLyBzdGFydHVwOyB1bnRpbCB0aGVuLCBjYWxscyBmcm9tIGFwaS1jb3JlIGhlbHBlcnMgYXJlIHNpbGVudGx5IGRyb3BwZWRcbiAgLy8gKHdoaWNoIGlzIHdoYXQgd2Ugd2FudCBpbiB0ZXN0cyBhbmQgQ0xJcyB0aGF0IGRvbid0IHJ1biBhIFByb20gc2VydmVyKS5cbn07XG5cbi8qKlxuICogUmVnaXN0ZXIgdGhlIHJlYWwgY291bnRlciBpbXBsZW1lbnRhdGlvbi4gSWRlbXBvdGVudCDigJQgdGhlIGxhc3QgY2FsbCB3aW5zO1xuICogdHlwaWNhbGx5IGNhbGxlZCBvbmNlIGF0IHNlcnZpY2Ugc3RhcnR1cCBmcm9tIGFwaS1zZXJ2ZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZXRDb3VudGVyRW1pdHRlcihmbjogQ291bnRlckVtaXR0ZXIpOiB2b2lkIHtcbiAgZW1pdHRlciA9IGZuO1xufVxuXG4vKiogUmVzZXQgdG8gdGhlIG5vLW9wIGVtaXR0ZXIuIEZvciB0ZXN0cyB0aGF0IHdhbnQgdG8gY2xlYXIgYW55IHdpcmluZy4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNldENvdW50ZXJFbWl0dGVyKCk6IHZvaWQge1xuICBlbWl0dGVyID0gKCkgPT4gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFJlY29yZCBhIGNvdW50ZXIgZXZlbnQuIFNhZmUgdG8gY2FsbCBmcm9tIGFueSBhcGktY29yZSBtb2R1bGUg4oCUIGlmIG5vXG4gKiBlbWl0dGVyIGlzIHdpcmVkLCB0aGlzIGlzIGEgbm8tb3AuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBlbWl0Q291bnRlcihuYW1lOiBzdHJpbmcsIGxhYmVsczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9LCB2YWx1ZSA9IDEpOiB2b2lkIHtcbiAgdHJ5IHtcbiAgICBlbWl0dGVyKG5hbWUsIGxhYmVscywgdmFsdWUpO1xuICB9IGNhdGNoIHtcbiAgICAvLyBOZXZlciBsZXQgbWV0cmljIGVtaXNzaW9uIGJyZWFrIHRoZSBjYWxsaW5nIHBhdGguIEEgbWlzYmVoYXZpbmdcbiAgICAvLyBlbWl0dGVyIHNob3VsZG4ndCBiZSBhYmxlIHRvIHRha2UgZG93biBxdW90YSBjaGVja3MgKG9yIGFueXRoaW5nIGVsc2UpLlxuICB9XG59XG4iXX0=
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Filter an object to only include entries where the value is not `undefined`.
3
+ * Keeps `null`, `false`, `0`, and empty string — only removes `undefined`.
4
+ *
5
+ * Useful for building partial update payloads from validated request bodies.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const body = { name: 'foo', description: undefined, isActive: false };
10
+ * pickDefined(body); // { name: 'foo', isActive: false }
11
+ * ```
12
+ */
13
+ export declare function pickDefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
@@ -0,0 +1,18 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Filter an object to only include entries where the value is not `undefined`.
5
+ * Keeps `null`, `false`, `0`, and empty string — only removes `undefined`.
6
+ *
7
+ * Useful for building partial update payloads from validated request bodies.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const body = { name: 'foo', description: undefined, isActive: false };
12
+ * pickDefined(body); // { name: 'foo', isActive: false }
13
+ * ```
14
+ */
15
+ export function pickDefined(obj) {
16
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
17
+ }
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2JqZWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL29iamVjdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBb0MsR0FBTTtJQUNuRSxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQ3ZCLE1BQU0sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssU0FBUyxDQUFDLENBQ3pDLENBQUM7QUFDbEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIEZpbHRlciBhbiBvYmplY3QgdG8gb25seSBpbmNsdWRlIGVudHJpZXMgd2hlcmUgdGhlIHZhbHVlIGlzIG5vdCBgdW5kZWZpbmVkYC5cbiAqIEtlZXBzIGBudWxsYCwgYGZhbHNlYCwgYDBgLCBhbmQgZW1wdHkgc3RyaW5nIOKAlCBvbmx5IHJlbW92ZXMgYHVuZGVmaW5lZGAuXG4gKlxuICogVXNlZnVsIGZvciBidWlsZGluZyBwYXJ0aWFsIHVwZGF0ZSBwYXlsb2FkcyBmcm9tIHZhbGlkYXRlZCByZXF1ZXN0IGJvZGllcy5cbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogY29uc3QgYm9keSA9IHsgbmFtZTogJ2ZvbycsIGRlc2NyaXB0aW9uOiB1bmRlZmluZWQsIGlzQWN0aXZlOiBmYWxzZSB9O1xuICogcGlja0RlZmluZWQoYm9keSk7IC8vIHsgbmFtZTogJ2ZvbycsIGlzQWN0aXZlOiBmYWxzZSB9XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBpY2tEZWZpbmVkPFQgZXh0ZW5kcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4ob2JqOiBUKTogUGFydGlhbDxUPiB7XG4gIHJldHVybiBPYmplY3QuZnJvbUVudHJpZXMoXG4gICAgT2JqZWN0LmVudHJpZXMob2JqKS5maWx0ZXIoKFssIHZdKSA9PiB2ICE9PSB1bmRlZmluZWQpLFxuICApIGFzIFBhcnRpYWw8VD47XG59XG4iXX0=
@@ -0,0 +1,150 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Shape convention for the inline parse-guard helpers below.
4
+ *
5
+ * These return a lightweight `{ value } | { error }` (narrowed with
6
+ * `'error' in result`) — deliberately DISTINCT from the schema-validation
7
+ * `ValidationResult<T>` (`{ ok, value } | { ok, error }`) in
8
+ * `validation/middleware.ts`. Zod schema parsing uses the `ok`-discriminated
9
+ * shape; these ad-hoc request-parameter guards use `'error' in`. The split is
10
+ * intentional and documented (see the note on `ValidationResult`): converging
11
+ * them would churn every `'error' in result` call site across the downstream
12
+ * services with no behavioural change, so it's left as a documented convention.
13
+ */
14
+ /**
15
+ * Express 5 parameter type.
16
+ */
17
+ type ParamValue = string | string[] | undefined;
18
+ /**
19
+ * Extract a single string parameter from Express 5 route params.
20
+ *
21
+ * @param params - Request params object
22
+ * @param key - Parameter key
23
+ * @returns The parameter value as a string, or undefined if not present
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Route: GET /plugins/:id
28
+ * const id = getParam(req.params, 'id');
29
+ * if (!id) {
30
+ * return sendError(res, 400, 'Missing id parameter');
31
+ * }
32
+ * ```
33
+ */
34
+ export declare function getParam(params: Record<string, ParamValue>, key: string): string | undefined;
35
+ /**
36
+ * Extract the organization ID from request.
37
+ * Checks params, headers, and user object.
38
+ *
39
+ * @param req - Express request object
40
+ * @returns Organization ID or undefined
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const orgId = getOrgId(req);
45
+ * if (!orgId) {
46
+ * return sendError(res, 400, 'Organization ID required');
47
+ * }
48
+ * ```
49
+ */
50
+ export declare function getOrgId(req: Request): string | undefined;
51
+ /**
52
+ * Parse a query parameter as an integer.
53
+ *
54
+ * @param value - Query parameter value
55
+ * @param defaultValue - Default value if parsing fails
56
+ * @returns Parsed integer
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const limit = parseQueryInt(req.query.limit, 10);
61
+ * const offset = parseQueryInt(req.query.offset, 0);
62
+ * ```
63
+ */
64
+ export declare function parseQueryInt(value: unknown, defaultValue: number): number;
65
+ /**
66
+ * Parse a query parameter as a string.
67
+ *
68
+ * @param value - Query parameter value
69
+ * @returns String value or undefined
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const search = parseQueryString(req.query.search);
74
+ * ```
75
+ */
76
+ export declare function parseQueryString(value: unknown): string | undefined;
77
+ /**
78
+ * Parse a string to a positive integer, returning a fallback if invalid.
79
+ * Useful for parsing environment variables with numeric defaults.
80
+ */
81
+ export declare function parsePositiveInt(value: string | undefined, fallback: number): number;
82
+ /**
83
+ * Parse a query int, then clamp to `[1, max]`. Returns `defaultValue` (after
84
+ * the same clamp) when the input is missing/invalid. Use for `limit`-style
85
+ * query parameters where an unbounded value would cost real money.
86
+ *
87
+ * @example
88
+ * const limit = parseQueryIntClamped(req.query.limit, 50, 1000);
89
+ */
90
+ export declare function parseQueryIntClamped(value: unknown, defaultValue: number, max: number): number;
91
+ /**
92
+ * Bulk-array request guard. Validates that `value` is an array, non-empty,
93
+ * and within the configured cap. Returns the typed array or an error object
94
+ * the caller can forward to `sendError`/`sendBadRequest`.
95
+ *
96
+ * Replaces a hand-rolled `if (!Array.isArray(...)) { ... }` + length check
97
+ * block that was duplicated across bulk endpoints in pipeline, plugin,
98
+ * compliance, etc.
99
+ *
100
+ * @example
101
+ * const result = validateBulkArray(req.body.ids, 'ids');
102
+ * if ('error' in result) return sendBadRequest(res, result.error, ErrorCode.VALIDATION_ERROR);
103
+ * const ids = result.value;
104
+ */
105
+ export declare function validateBulkArray<T = unknown>(value: unknown, fieldName: string, maxItems?: number): {
106
+ value: T[];
107
+ } | {
108
+ error: string;
109
+ };
110
+ /**
111
+ * Supported intervals for time-bucketed report queries. Used as the
112
+ * `DATE_TRUNC` first argument in Postgres; restricted to a fixed enum so
113
+ * the value can never be injected into raw SQL.
114
+ */
115
+ export declare const REPORT_INTERVALS: readonly ['day', 'week', 'month'];
116
+ export type ReportInterval = (typeof REPORT_INTERVALS)[number];
117
+ /**
118
+ * Parse + validate a `?interval=` report bucket (defaults to 'week'). Returns the
119
+ * validated interval or `{ error }`. Shared by the timeseries report routes so the
120
+ * default + allow-list + error message live in one place.
121
+ *
122
+ * @example
123
+ * const interval = parseReportInterval(req.query);
124
+ * if (typeof interval === 'object') return sendBadRequest(res, interval.error, ErrorCode.VALIDATION_ERROR);
125
+ */
126
+ export declare function parseReportInterval(query: Record<string, unknown>): ReportInterval | {
127
+ error: string;
128
+ };
129
+ /**
130
+ * Parse a `?from=&to=` date range with defaults of "30 days ago → now".
131
+ * Rejects non-string inputs (Express's parser can produce string[] for
132
+ * repeated keys), invalid ISO timestamps, and inverted ranges.
133
+ *
134
+ * Returns either `{ from, to }` ISO strings or `{ error }`.
135
+ *
136
+ * @example
137
+ * const range = parseDateRange(req.query);
138
+ * if ('error' in range) return sendBadRequest(res, range.error, ErrorCode.VALIDATION_ERROR);
139
+ * const rows = await svc.list(range.from, range.to);
140
+ */
141
+ export declare function parseDateRange(query: Record<string, unknown>, options?: {
142
+ maxRangeMs?: number;
143
+ defaultDaysBack?: number;
144
+ }): {
145
+ from: string;
146
+ to: string;
147
+ } | {
148
+ error: string;
149
+ };
150
+ export {};