@pipeline-builder/api-server 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 (61) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +79 -0
  3. package/lib/api/app-factory.d.ts +95 -0
  4. package/lib/api/app-factory.js +328 -0
  5. package/lib/api/check-quota.d.ts +13 -0
  6. package/lib/api/check-quota.js +74 -0
  7. package/lib/api/context-middleware.d.ts +34 -0
  8. package/lib/api/context-middleware.js +42 -0
  9. package/lib/api/etag-middleware.d.ts +7 -0
  10. package/lib/api/etag-middleware.js +44 -0
  11. package/lib/api/get-context.d.ts +22 -0
  12. package/lib/api/get-context.js +28 -0
  13. package/lib/api/health-checks.d.ts +62 -0
  14. package/lib/api/health-checks.js +87 -0
  15. package/lib/api/idempotency-middleware.d.ts +80 -0
  16. package/lib/api/idempotency-middleware.js +321 -0
  17. package/lib/api/index.d.ts +20 -0
  18. package/lib/api/index.js +34 -0
  19. package/lib/api/meter-quota.d.ts +32 -0
  20. package/lib/api/meter-quota.js +70 -0
  21. package/lib/api/metrics.d.ts +47 -0
  22. package/lib/api/metrics.js +221 -0
  23. package/lib/api/middleware-factory.d.ts +56 -0
  24. package/lib/api/middleware-factory.js +80 -0
  25. package/lib/api/middleware.d.ts +1 -0
  26. package/lib/api/middleware.js +8 -0
  27. package/lib/api/mongo-connect.d.ts +19 -0
  28. package/lib/api/mongo-connect.js +41 -0
  29. package/lib/api/quota-helpers.d.ts +23 -0
  30. package/lib/api/quota-helpers.js +22 -0
  31. package/lib/api/rate-limit-by-org.d.ts +29 -0
  32. package/lib/api/rate-limit-by-org.js +64 -0
  33. package/lib/api/readiness.d.ts +29 -0
  34. package/lib/api/readiness.js +65 -0
  35. package/lib/api/request-types.d.ts +55 -0
  36. package/lib/api/request-types.js +65 -0
  37. package/lib/api/require-org-id.d.ts +14 -0
  38. package/lib/api/require-org-id.js +28 -0
  39. package/lib/api/route-wrapper.d.ts +50 -0
  40. package/lib/api/route-wrapper.js +84 -0
  41. package/lib/api/server.d.ts +79 -0
  42. package/lib/api/server.js +234 -0
  43. package/lib/api/tenant-context.d.ts +28 -0
  44. package/lib/api/tenant-context.js +48 -0
  45. package/lib/api/tracing.d.ts +25 -0
  46. package/lib/api/tracing.js +69 -0
  47. package/lib/http/index.d.ts +4 -0
  48. package/lib/http/index.js +7 -0
  49. package/lib/http/sse-connection-manager.d.ts +357 -0
  50. package/lib/http/sse-connection-manager.js +665 -0
  51. package/lib/http/sse-relay.d.ts +63 -0
  52. package/lib/http/sse-relay.js +70 -0
  53. package/lib/http/sse-ticket-channel.d.ts +31 -0
  54. package/lib/http/sse-ticket-channel.js +52 -0
  55. package/lib/http/sse-ticket-store.d.ts +75 -0
  56. package/lib/http/sse-ticket-store.js +160 -0
  57. package/lib/index.d.ts +30 -0
  58. package/lib/index.js +35 -0
  59. package/lib/otel-bootstrap.d.ts +1 -0
  60. package/lib/otel-bootstrap.js +75 -0
  61. package/package.json +183 -0
@@ -0,0 +1,321 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { createHash } from 'crypto';
4
+ import { createLogger, sendError, ErrorCode, createEnvRedisClient } from '@pipeline-builder/api-core';
5
+ import { CoreConstants } from '@pipeline-builder/pipeline-core';
6
+ const logger = createLogger('idempotency');
7
+ /**
8
+ * Canonical (key-sorted) JSON serialization so the SAME logical payload hashes
9
+ * identically regardless of property insertion order — a plain `JSON.stringify`
10
+ * would let `{a,b}` and `{b,a}` produce different fingerprints for one request.
11
+ */
12
+ function stableStringify(value) {
13
+ if (value === null || typeof value !== 'object')
14
+ return JSON.stringify(value) ?? 'null';
15
+ if (Array.isArray(value))
16
+ return '[' + value.map(stableStringify).join(',') + ']';
17
+ const obj = value;
18
+ return '{' + Object.keys(obj).sort().map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';
19
+ }
20
+ /**
21
+ * Short, stable fingerprint of a request body. Folds the payload into the
22
+ * idempotency key so replaying the SAME key with a DIFFERENT body is treated as
23
+ * a distinct operation (fresh execution) rather than replaying the old response.
24
+ * Empty/absent bodies collapse to '-' so body-less mutations stay unaffected.
25
+ */
26
+ function bodyFingerprint(body) {
27
+ if (body === undefined || body === null)
28
+ return '-';
29
+ if (typeof body === 'object' && Object.keys(body).length === 0)
30
+ return '-';
31
+ try {
32
+ return createHash('sha256').update(stableStringify(body)).digest('hex').slice(0, 16);
33
+ }
34
+ catch {
35
+ // Non-serializable body (e.g. a stream/circular ref): skip fingerprinting
36
+ // rather than fail the request — key stays scoped by method+route+org.
37
+ return '-';
38
+ }
39
+ }
40
+ /** Default in-memory store. Single-replica only. Exported for testing. */
41
+ export function createMemoryStore() {
42
+ const map = new Map();
43
+ const cleanupTimer = setInterval(() => {
44
+ const now = Date.now();
45
+ for (const [k, v] of map) {
46
+ if (now > v.expiresAt)
47
+ map.delete(k);
48
+ }
49
+ }, CoreConstants.IDEMPOTENCY_CLEANUP_INTERVAL_MS);
50
+ cleanupTimer.unref();
51
+ /** Return the live (non-expired) entry, evicting it lazily if expired. */
52
+ const live = (key) => {
53
+ const entry = map.get(key);
54
+ if (!entry)
55
+ return null;
56
+ if (Date.now() > entry.expiresAt) {
57
+ map.delete(key);
58
+ return null;
59
+ }
60
+ return entry;
61
+ };
62
+ /**
63
+ * At capacity, evict the OLDEST entry (Map preserves insertion order) rather
64
+ * than silently dropping the NEW key — otherwise, once full, the store stops
65
+ * deduplicating fresh requests until the TTL sweep runs. Stays bounded; favors
66
+ * recent keys (most likely to be retried).
67
+ */
68
+ const evictIfFull = (key) => {
69
+ if (!map.has(key) && map.size >= CoreConstants.IDEMPOTENCY_MAX_STORE_SIZE) {
70
+ const oldest = map.keys().next().value;
71
+ if (oldest !== undefined)
72
+ map.delete(oldest);
73
+ }
74
+ };
75
+ return {
76
+ async get(key) {
77
+ return live(key);
78
+ },
79
+ async set(key, entry) {
80
+ evictIfFull(key);
81
+ map.set(key, entry);
82
+ },
83
+ async reserve(key, entry) {
84
+ // Atomic in a single-threaded runtime: no await between the liveness check
85
+ // and the set, so two concurrent callers can't both observe "absent".
86
+ if (live(key))
87
+ return false;
88
+ evictIfFull(key);
89
+ map.set(key, entry);
90
+ return true;
91
+ },
92
+ async delete(key) {
93
+ map.delete(key);
94
+ },
95
+ };
96
+ }
97
+ const REDIS_KEY_PREFIX = 'idem:';
98
+ /**
99
+ * Redis-backed {@link IdempotencyStore} for correct deduplication across
100
+ * horizontally-scaled replicas. `reserve` maps to an atomic `SET … NX` so two
101
+ * pods behind a load-balancer can't both win a first-time request sharing an
102
+ * Idempotency-Key; a retry landing on ANY pod sees the same committed response.
103
+ * Redis TTL owns expiry (the `CachedEntry.expiresAt` field is carried only for
104
+ * API-shape parity with the memory store).
105
+ */
106
+ export function createRedisIdempotencyStore(redis) {
107
+ const k = (key) => `${REDIS_KEY_PREFIX}${key}`;
108
+ return {
109
+ async get(key) {
110
+ const raw = await redis.get(k(key));
111
+ if (!raw)
112
+ return null;
113
+ try {
114
+ return JSON.parse(raw);
115
+ }
116
+ catch {
117
+ // Corrupt entry → treat as absent so the request proceeds fresh.
118
+ return null;
119
+ }
120
+ },
121
+ async set(key, entry, ttlSeconds) {
122
+ await redis.set(k(key), JSON.stringify(entry), 'EX', Math.max(1, ttlSeconds));
123
+ },
124
+ async reserve(key, entry, ttlSeconds) {
125
+ // Atomic claim: SET key val EX ttl NX → 'OK' only if the key was absent.
126
+ const res = await redis.set(k(key), JSON.stringify(entry), 'EX', Math.max(1, ttlSeconds), 'NX');
127
+ return res === 'OK';
128
+ },
129
+ async delete(key) {
130
+ await redis.del(k(key));
131
+ },
132
+ };
133
+ }
134
+ /**
135
+ * Build a Redis-backed idempotency store from the shared env Redis (the same
136
+ * `REDIS_URL` / `REDIS_HOST` wiring the rate-limiter and audit-spool use).
137
+ * Returns `null` when Redis isn't configured/available so the caller keeps the
138
+ * in-memory default (single-replica correctness). Never throws.
139
+ */
140
+ export function createEnvRedisIdempotencyStore() {
141
+ const client = createEnvRedisClient('idempotency');
142
+ if (!client)
143
+ return null;
144
+ logger.info('Redis idempotency store initialized');
145
+ return createRedisIdempotencyStore(client);
146
+ }
147
+ const memoryStore = createMemoryStore();
148
+ /**
149
+ * Process-wide default store used by `idempotencyMiddleware()` when no explicit
150
+ * `store` is passed. Defaults to the in-memory store; `createApp` promotes it to
151
+ * the shared env-Redis store (multi-replica dedup) at startup via
152
+ * {@link setIdempotencyStore}. The route factories keep calling
153
+ * `idempotencyMiddleware()` with no args and transparently pick this up.
154
+ */
155
+ let defaultStore = memoryStore;
156
+ /** Inject the process-wide idempotency store (e.g. a Redis-backed one). */
157
+ export function setIdempotencyStore(store) {
158
+ defaultStore = store;
159
+ }
160
+ /** The current process-wide default store (in-memory unless overridden). */
161
+ export function getIdempotencyStore() {
162
+ return defaultStore;
163
+ }
164
+ /**
165
+ * Middleware that supports idempotency keys for POST/PUT/DELETE mutations.
166
+ *
167
+ * When a request includes the `Idempotency-Key` header:
168
+ * - First call: processes normally, caches the response
169
+ * - Subsequent calls with same key: returns cached response (prevents duplicate mutations)
170
+ *
171
+ * Defaults to an in-memory store (single-replica). Pass a custom
172
+ * cross-replica `{ store }` implementing `IdempotencyStore` to dedupe across
173
+ * replicas.
174
+ */
175
+ export function idempotencyMiddleware(options = {}) {
176
+ // Resolve the store PER REQUEST (not at factory time) so a store injected by
177
+ // createApp AFTER the route chains are built still takes effect. An explicit
178
+ // `options.store` always wins.
179
+ const explicitStore = options.store;
180
+ const ttlMs = CoreConstants.IDEMPOTENCY_TTL_MS;
181
+ const ttlSec = Math.floor(ttlMs / 1000);
182
+ return (req, res, next) => {
183
+ const store = explicitStore ?? defaultStore;
184
+ const key = req.headers['idempotency-key'];
185
+ if (!key)
186
+ return next();
187
+ // Only apply to mutation methods
188
+ if (!['POST', 'PUT', 'DELETE'].includes(req.method))
189
+ return next();
190
+ // Namespace by orgId to prevent cross-org cache collisions. Prefer the
191
+ // VERIFIED auth org (`req.user`, populated by requireAuth after signature
192
+ // verification) over `req.context.identity`, which some services populate
193
+ // pre-auth from an UNVERIFIED token peek — namespacing a mutation's replay
194
+ // cache on an unverified, caller-influenced org id would be unsafe.
195
+ const orgId = req.user?.organizationId || req.context?.identity?.orgId;
196
+ if (!orgId)
197
+ return next(); // skip idempotency for unauthenticated requests
198
+ // Namespace the stored key by (method, route, org, body) so an Idempotency-Key
199
+ // is scoped to ONE specific endpoint+payload. Reusing a single key across two
200
+ // routes (e.g. POST /pipelines then POST /plugins) previously replayed the
201
+ // first endpoint's 2xx for the second — the store is a module-level singleton
202
+ // shared by every route. Folding in method + route path + a body fingerprint
203
+ // only makes keys MORE specific, so there is no cross-contamination risk.
204
+ const routePath = (req.baseUrl ?? '') + (req.route?.path ?? req.path ?? '');
205
+ const fullKey = `${req.method}:${routePath}:${orgId}:${bodyFingerprint(req.body)}:${key}`;
206
+ /** Reject a duplicate whose original is still in-flight. */
207
+ const sendInProgress = () => {
208
+ res.setHeader('Retry-After', '1');
209
+ sendError(res, 409, 'A request with this Idempotency-Key is already being processed', ErrorCode.CONFLICT);
210
+ };
211
+ store.get(fullKey).then(async (cached) => {
212
+ if (cached && !cached.pending) {
213
+ // Completed response on record → replay it.
214
+ logger.debug('Idempotent request replayed', { key: fullKey });
215
+ res.setHeader('X-Idempotent-Replayed', 'true');
216
+ res.status(cached.statusCode).json(cached.body);
217
+ return;
218
+ }
219
+ if (cached && cached.pending) {
220
+ // The original is still running — don't run the mutation a second time.
221
+ logger.debug('Idempotent request rejected (in progress)', { key: fullKey });
222
+ return sendInProgress();
223
+ }
224
+ // Atomically reserve the key. Losing this race means a concurrent request
225
+ // with the same key got there first and is in-flight → 409.
226
+ const won = await store.reserve(fullKey, { statusCode: 0, body: null, pending: true, expiresAt: Date.now() + ttlMs }, ttlSec);
227
+ if (!won) {
228
+ logger.debug('Idempotent request rejected (lost reserve race)', { key: fullKey });
229
+ return sendInProgress();
230
+ }
231
+ // We own the reservation. It MUST be resolved exactly once — either the
232
+ // real 2xx response is cached for replay, or the reservation is released so
233
+ // a retry can proceed. `settled` guards against a double-resolve.
234
+ let settled = false;
235
+ let capturedBody;
236
+ let bodyCaptured = false;
237
+ /** Remember the response body for replay (first capture wins). */
238
+ const captureBody = (body) => {
239
+ if (!bodyCaptured) {
240
+ capturedBody = body;
241
+ bodyCaptured = true;
242
+ }
243
+ };
244
+ const release = () => {
245
+ if (settled)
246
+ return;
247
+ settled = true;
248
+ store.delete(fullKey).catch((err) => {
249
+ logger.warn('Idempotency reservation release failed', { key: fullKey, error: err instanceof Error ? err.message : String(err) });
250
+ });
251
+ };
252
+ const cacheSuccess = () => {
253
+ if (settled)
254
+ return;
255
+ settled = true;
256
+ store.set(fullKey, {
257
+ statusCode: res.statusCode,
258
+ body: bodyCaptured ? capturedBody : null,
259
+ expiresAt: Date.now() + ttlMs,
260
+ }, ttlSec).catch((err) => {
261
+ logger.warn('Idempotency store.set failed', { key: fullKey, error: err instanceof Error ? err.message : String(err) });
262
+ });
263
+ };
264
+ /** Resolve the reservation from the ACTUAL final status: cache 2xx, else release. */
265
+ const settleFromStatus = () => {
266
+ if (settled)
267
+ return;
268
+ if (res.statusCode >= 200 && res.statusCode < 300)
269
+ cacheSuccess();
270
+ else
271
+ release();
272
+ };
273
+ // Fast path: res.json is the common completion. Capture the body and settle
274
+ // immediately off the status set by res.status(...).json(...).
275
+ const originalJson = res.json.bind(res);
276
+ res.json = (body) => {
277
+ res.setHeader('X-Idempotent-Replayed', 'false');
278
+ captureBody(body);
279
+ settleFromStatus();
280
+ return originalJson(body);
281
+ };
282
+ // Body-capture backstops for completions that bypass res.json
283
+ // (res.send / res.end / res.sendFile → res.end). These only RECORD the body;
284
+ // the actual settle happens on `finish` from the real status code. First
285
+ // capture wins, so res.json's body is never clobbered by its internal
286
+ // res.send/res.end calls.
287
+ const patched = res;
288
+ if (typeof patched.send === 'function') {
289
+ const originalSend = patched.send.bind(res);
290
+ patched.send = (body) => {
291
+ captureBody(body);
292
+ return originalSend(body);
293
+ };
294
+ }
295
+ if (typeof patched.end === 'function') {
296
+ const originalEnd = patched.end.bind(res);
297
+ patched.end = (...args) => {
298
+ const chunk = args[0];
299
+ if (chunk !== undefined && typeof chunk !== 'function')
300
+ captureBody(chunk);
301
+ return originalEnd(...args);
302
+ };
303
+ }
304
+ // Backstop for ANY completion path — including a 2xx that finished via
305
+ // res.send/res.end/res.sendFile/204 and never hit the patched res.json.
306
+ // Deciding on the real final status here means a successful op is cached
307
+ // (or a failed one released) instead of stranding the reservation and
308
+ // 409-ing every retry for the full TTL. Guarded — test doubles may not be
309
+ // EventEmitters; res.json's fast path already settled the common case.
310
+ if (typeof patched.on === 'function') {
311
+ patched.on('finish', settleFromStatus);
312
+ }
313
+ next();
314
+ }).catch((err) => {
315
+ // If the store backend is down, fail open — process the request normally.
316
+ logger.warn('Idempotency store.get failed; proceeding without dedup', { key: fullKey, error: err instanceof Error ? err.message : String(err) });
317
+ next();
318
+ });
319
+ };
320
+ }
321
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWRlbXBvdGVuY3ktbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvaWRlbXBvdGVuY3ktbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDcEMsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDdEcsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBR2hFLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQztBQUUzQzs7OztHQUlHO0FBQ0gsU0FBUyxlQUFlLENBQUMsS0FBYztJQUNyQyxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtRQUFFLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUM7SUFDeEYsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sR0FBRyxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEdBQUcsQ0FBQztJQUNsRixNQUFNLEdBQUcsR0FBRyxLQUFnQyxDQUFDO0lBQzdDLE9BQU8sR0FBRyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxHQUFHLEdBQUcsR0FBRyxlQUFlLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcs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LENBQUM7b0JBQzNFLE9BQU8sV0FBVyxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUM7Z0JBQzlCLENBQUMsQ0FBQztZQUNKLENBQUM7WUFFRCx1RUFBdUU7WUFDdkUsd0VBQXdFO1lBQ3hFLHlFQUF5RTtZQUN6RSxzRUFBc0U7WUFDdEUsMEVBQTBFO1lBQzFFLHVFQUF1RTtZQUN2RSxJQUFJLE9BQU8sT0FBTyxDQUFDLEVBQUUsS0FBSyxVQUFVLEVBQUUsQ0FBQztnQkFDckMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztZQUN6QyxDQUFDO1lBRUQsSUFBSSxFQUFFLENBQUM7UUFDVCxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRTtZQUNmLDBFQUEwRTtZQUMxRSxNQUFNLENBQUMsSUFBSSxDQUFDLHdEQUF3RCxFQUFFLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNqSixJQUFJLEVBQUUsQ0FBQztRQUNULENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBjcmVhdGVIYXNoIH0gZnJvbSAnY3J5cHRvJztcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciwgc2VuZEVycm9yLCBFcnJvckNvZGUsIGNyZWF0ZUVudlJlZGlzQ2xpZW50IH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgQ29yZUNvbnN0YW50cyB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWNvcmUnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignaWRlbXBvdGVuY3knKTtcblxuLyoqXG4gKiBDYW5vbmljYWwgKGtleS1zb3J0ZWQpIEpTT04gc2VyaWFsaXphdGlvbiBzbyB0aGUgU0FNRSBsb2dpY2FsIHBheWxvYWQgaGFzaGVzXG4gKiBpZGVudGljYWxseSByZWdhcmRsZXNzIG9mIHByb3BlcnR5IGluc2VydGlvbiBvcmRlciDigJQgYSBwbGFpbiBgSlNPTi5zdHJpbmdpZnlgXG4gKiB3b3VsZCBsZXQgYHthLGJ9YCBhbmQgYHtiLGF9YCBwcm9kdWNlIGRpZmZlcmVudCBmaW5nZXJwcmludHMgZm9yIG9uZSByZXF1ZXN0LlxuICovXG5mdW5jdGlvbiBzdGFibGVTdHJpbmdpZnkodmFsdWU6IHVua25vd24pOiBzdHJpbmcge1xuICBpZiAodmFsdWUgPT09IG51bGwgfHwgdHlwZW9mIHZhbHVlICE9PSAnb2JqZWN0JykgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHZhbHVlKSA/PyAnbnVsbCc7XG4gIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkgcmV0dXJuICdbJyArIHZhbHVlLm1hcChzdGFibGVTdHJpbmdpZnkpLmpvaW4oJywnKSArICddJztcbiAgY29uc3Qgb2JqID0gdmFsdWUgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIHJldHVybiAneycgKyBPYmplY3Qua2V5cyhvYmopLnNvcnQoKS5tYXAoKGspID0+IEpTT04uc3RyaW5naWZ5KGspICsgJzonICsgc3RhYmxlU3RyaW5naWZ5KG9ialtrXSkpLmpvaW4oJywnKSArICd9Jztcbn1cblxuLyoqXG4gKiBTaG9ydCwgc3RhYmxlIGZpbmdlcnByaW50IG9mIGEgcmVxdWVzdCBib2R5LiBGb2xkcyB0aGUgcGF5bG9hZCBpbnRvIHRoZVxuICogaWRlbXBvdGVuY3kga2V5IHNvIHJlcGxheWluZyB0aGUgU0FNRSBrZXkgd2l0aCBhIERJRkZFUkVOVCBib2R5IGlzIHRyZWF0ZWQgYXNcbiAqIGEgZGlzdGluY3Qgb3BlcmF0aW9uIChmcmVzaCBleGVjdXRpb24pIHJhdGhlciB0aGFuIHJlcGxheWluZyB0aGUgb2xkIHJlc3BvbnNlLlxuICogRW1wdHkvYWJzZW50IGJvZGllcyBjb2xsYXBzZSB0byAnLScgc28gYm9keS1sZXNzIG11dGF0aW9ucyBzdGF5IHVuYWZmZWN0ZWQuXG4gKi9cbmZ1bmN0aW9uIGJvZHlGaW5nZXJwcmludChib2R5OiB1bmtub3duKTogc3RyaW5nIHtcbiAgaWYgKGJvZHkgPT09IHVuZGVmaW5lZCB8fCBib2R5ID09PSBudWxsKSByZXR1cm4gJy0nO1xuICBpZiAodHlwZW9mIGJvZHkgPT09ICdvYmplY3QnICYmIE9iamVjdC5rZXlzKGJvZHkgYXMgb2JqZWN0KS5sZW5ndGggPT09IDApIHJldHVybiAnLSc7XG4gIHRyeSB7XG4gICAgcmV0dXJuIGNyZWF0ZUhhc2goJ3NoYTI1NicpLnVwZGF0ZShzdGFibGVTdHJpbmdpZnkoYm9keSkpLmRpZ2VzdCgnaGV4Jykuc2xpY2UoMCwgMTYpO1xuICB9IGNhdGNoIHtcbiAgICAvLyBOb24tc2VyaWFsaXphYmxlIGJvZHkgKGUuZy4gYSBzdHJlYW0vY2lyY3VsYXIgcmVmKTogc2tpcCBmaW5nZXJwcmludGluZ1xuICAgIC8vIHJhdGhlciB0aGFuIGZhaWwgdGhlIHJlcXVlc3Qg4oCUIGtleSBzdGF5cyBzY29wZWQgYnkgbWV0aG9kK3JvdXRlK29yZy5cbiAgICByZXR1cm4gJy0nO1xuICB9XG59XG5cbmludGVyZmFjZSBDYWNoZWRFbnRyeSB7XG4gIHN0YXR1c0NvZGU6IG51bWJlcjtcbiAgYm9keTogdW5rbm93bjtcbiAgZXhwaXJlc0F0OiBudW1iZXI7XG4gIC8qKiBSZXNlcnZhdGlvbiBwbGFjZWhvbGRlcjogdGhlIHJlcXVlc3QgaXMgaW4tZmxpZ2h0LCBubyByZWFsIHJlc3BvbnNlIHlldC5cbiAgICogIEEgZHVwbGljYXRlIHRoYXQgc2VlcyBhIHBlbmRpbmcgZW50cnkgaXMgcmVqZWN0ZWQgd2l0aCA0MDksIG5vdCByZXBsYXllZC4gKi9cbiAgcGVuZGluZz86IGJvb2xlYW47XG59XG5cbi8qKlxuICogUGx1Z2dhYmxlIGJhY2tlbmQgZm9yIGlkZW1wb3RlbmN5LWtleSByZXBsYXkgY2FjaGUuIFRoZSBkZWZhdWx0IGluLW1lbW9yeVxuICogaW1wbGVtZW50YXRpb24gd29ya3MgZmluZSBzaW5nbGUtcmVwbGljYTsgYSBSZWRpcy1iYWNrZWQgaW1wbGVtZW50YXRpb25cbiAqIGlzIHJlcXVpcmVkIGZvciBjb3JyZWN0IGRlZHVwbGljYXRpb24gYWNyb3NzIGhvcml6b250YWxseS1zY2FsZWQgcmVwbGljYXNcbiAqIChvdGhlcndpc2UgdHdvIHJlcGxpY2FzIGJlaGluZCBhIGxvYWQtYmFsYW5jZXIgY2FjaGUgaW5kZXBlbmRlbnRseSBhbmQgYVxuICogcmV0cnkgaGl0dGluZyBhIGRpZmZlcmVudCBwb2Qgd29uJ3QgcmVwbGF5KS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBJZGVtcG90ZW5jeVN0b3JlIHtcbiAgZ2V0KGtleTogc3RyaW5nKTogUHJvbWlzZTxDYWNoZWRFbnRyeSB8IG51bGw+O1xuICBzZXQoa2V5OiBzdHJpbmcsIGVudHJ5OiBDYWNoZWRFbnRyeSwgdHRsU2Vjb25kczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPjtcbiAgLyoqXG4gICAqIEF0b21pY2FsbHkgY2xhaW0gYGtleWAgSUZGIGl0IGlzIGN1cnJlbnRseSBhYnNlbnQuIFJldHVybnMgYHRydWVgIHdoZW4gVEhJU1xuICAgKiBjYWxsZXIgc2V0IHRoZSBlbnRyeSAod29uIHRoZSByYWNlKSwgYGZhbHNlYCB3aGVuIGFuIGVudHJ5IGFscmVhZHkgZXhpc3RzLlxuICAgKiBUaGlzIGlzIHdoYXQgc2VyaWFsaXplcyBjb25jdXJyZW50IGZpcnN0LXRpbWUgcmVxdWVzdHMgdGhhdCBzaGFyZSBhblxuICAgKiBJZGVtcG90ZW5jeS1LZXkg4oCUIGEgcGxhaW4gZ2V0LXRoZW4tc2V0IGxlYXZlcyBhIHdpbmRvdyB3aGVyZSB0d28gcmVxdWVzdHNcbiAgICogYm90aCBzZWUgXCJub3QgY2FjaGVkXCIgYW5kIGJvdGggZXhlY3V0ZSB0aGUgbXV0YXRpb24uXG4gICAqL1xuICByZXNlcnZlKGtleTogc3RyaW5nLCBlbnRyeTogQ2FjaGVkRW50cnksIHR0bFNlY29uZHM6IG51bWJlcik6IFByb21pc2U8Ym9vbGVhbj47XG4gIC8qKiBSZW1vdmUgYSBrZXkg4oCUIHJlbGVhc2VzIGEgcmVzZXJ2YXRpb24gYWZ0ZXIgYSBub24tY2FjaGVhYmxlIChub24tMnh4KSByZXNwb25zZS4gKi9cbiAgZGVsZXRlKGtleTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPjtcbn1cblxuLyoqIERlZmF1bHQgaW4tbWVtb3J5IHN0b3JlLiBTaW5nbGUtcmVwbGljYSBvbmx5LiBFeHBvcnRlZCBmb3IgdGVzdGluZy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVNZW1vcnlTdG9yZSgpOiBJZGVtcG90ZW5jeVN0b3JlIHtcbiAgY29uc3QgbWFwID0gbmV3IE1hcDxzdHJpbmcsIENhY2hlZEVudHJ5PigpO1xuICBjb25zdCBjbGVhbnVwVGltZXIgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICBmb3IgKGNvbnN0IFtrLCB2XSBvZiBtYXApIHtcbiAgICAgIGlmIChub3cgPiB2LmV4cGlyZXNBdCkgbWFwLmRlbGV0ZShrKTtcbiAgICB9XG4gIH0sIENvcmVDb25zdGFudHMuSURFTVBPVEVOQ1lfQ0xFQU5VUF9JTlRFUlZBTF9NUyk7XG4gIGNsZWFudXBUaW1lci51bnJlZigpO1xuXG4gIC8qKiBSZXR1cm4gdGhlIGxpdmUgKG5vbi1leHBpcmVkKSBlbnRyeSwgZXZpY3RpbmcgaXQgbGF6aWx5IGlmIGV4cGlyZWQuICovXG4gIGNvbnN0IGxpdmUgPSAoa2V5OiBzdHJpbmcpOiBDYWNoZWRFbnRyeSB8IG51bGwgPT4ge1xuICAgIGNvbnN0IGVudHJ5ID0gbWFwLmdldChrZXkpO1xuICAgIGlmICghZW50cnkpIHJldHVybiBudWxsO1xuICAgIGlmIChEYXRlLm5vdygpID4gZW50cnkuZXhwaXJlc0F0KSB7XG4gICAgICBtYXAuZGVsZXRlKGtleSk7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIGVudHJ5O1xuICB9O1xuICAvKipcbiAgICogQXQgY2FwYWNpdHksIGV2aWN0IHRoZSBPTERFU1QgZW50cnkgKE1hcCBwcmVzZXJ2ZXMgaW5zZXJ0aW9uIG9yZGVyKSByYXRoZXJcbiAgICogdGhhbiBzaWxlbnRseSBkcm9wcGluZyB0aGUgTkVXIGtleSDigJQgb3RoZXJ3aXNlLCBvbmNlIGZ1bGwsIHRoZSBzdG9yZSBzdG9wc1xuICAgKiBkZWR1cGxpY2F0aW5nIGZyZXNoIHJlcXVlc3RzIHVudGlsIHRoZSBUVEwgc3dlZXAgcnVucy4gU3RheXMgYm91bmRlZDsgZmF2b3JzXG4gICAqIHJlY2VudCBrZXlzIChtb3N0IGxpa2VseSB0byBiZSByZXRyaWVkKS5cbiAgICovXG4gIGNvbnN0IGV2aWN0SWZGdWxsID0gKGtleTogc3RyaW5nKTogdm9pZCA9PiB7XG4gICAgaWYgKCFtYXAuaGFzKGtleSkgJiYgbWFwLnNpemUgPj0gQ29yZUNvbnN0YW50cy5JREVNUE9URU5DWV9NQVhfU1RPUkVfU0laRSkge1xuICAgICAgY29uc3Qgb2xkZXN0ID0gbWFwLmtleXMoKS5uZXh0KCkudmFsdWU7XG4gICAgICBpZiAob2xkZXN0ICE9PSB1bmRlZmluZWQpIG1hcC5kZWxldGUob2xkZXN0KTtcbiAgICB9XG4gIH07XG4gIHJldHVybiB7XG4gICAgYXN5bmMgZ2V0KGtleSkge1xuICAgICAgcmV0dXJuIGxpdmUoa2V5KTtcbiAgICB9LFxuICAgIGFzeW5jIHNldChrZXksIGVudHJ5KSB7XG4gICAgICBldmljdElmRnVsbChrZXkpO1xuICAgICAgbWFwLnNldChrZXksIGVudHJ5KTtcbiAgICB9LFxuICAgIGFzeW5jIHJlc2VydmUoa2V5LCBlbnRyeSkge1xuICAgICAgLy8gQXRvbWljIGluIGEgc2luZ2xlLXRocmVhZGVkIHJ1bnRpbWU6IG5vIGF3YWl0IGJldHdlZW4gdGhlIGxpdmVuZXNzIGNoZWNrXG4gICAgICAvLyBhbmQgdGhlIHNldCwgc28gdHdvIGNvbmN1cnJlbnQgY2FsbGVycyBjYW4ndCBib3RoIG9ic2VydmUgXCJhYnNlbnRcIi5cbiAgICAgIGlmIChsaXZlKGtleSkpIHJldHVybiBmYWxzZTtcbiAgICAgIGV2aWN0SWZGdWxsKGtleSk7XG4gICAgICBtYXAuc2V0KGtleSwgZW50cnkpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfSxcbiAgICBhc3luYyBkZWxldGUoa2V5KSB7XG4gICAgICBtYXAuZGVsZXRlKGtleSk7XG4gICAgfSxcbiAgfTtcbn1cblxuLyoqXG4gKiBNaW5pbWFsIFJlZGlzIHN1cmZhY2UgdGhlIGlkZW1wb3RlbmN5IHN0b3JlIG5lZWRzIChhbiBpb3JlZGlzIHN1YnNldCkuXG4gKiBgc2V0YCBzdXBwb3J0cyB0aGUgdmFyaWFkaWMgb3B0aW9uIHRhaWw6IGBTRVQga2V5IHZhbCBFWCA8dHRsPiBbTlhdYCwgd2hpY2hcbiAqIHJldHVybnMgYCdPSydgIG9uIHN1Y2Nlc3MgYW5kIGBudWxsYCB3aGVuIGFuIGBOWGAgc2V0IHdhcyByZWZ1c2VkLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlZGlzSWRlbXBvdGVuY3lDbGllbnQge1xuICBnZXQoa2V5OiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZyB8IG51bGw+O1xuICBzZXQoa2V5OiBzdHJpbmcsIHZhbHVlOiBzdHJpbmcsIC4uLmFyZ3M6IChzdHJpbmcgfCBudW1iZXIpW10pOiBQcm9taXNlPHN0cmluZyB8IG51bGw+O1xuICBkZWwoa2V5OiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj47XG59XG5cbmNvbnN0IFJFRElTX0tFWV9QUkVGSVggPSAnaWRlbTonO1xuXG4vKipcbiAqIFJlZGlzLWJhY2tlZCB7QGxpbmsgSWRlbXBvdGVuY3lTdG9yZX0gZm9yIGNvcnJlY3QgZGVkdXBsaWNhdGlvbiBhY3Jvc3NcbiAqIGhvcml6b250YWxseS1zY2FsZWQgcmVwbGljYXMuIGByZXNlcnZlYCBtYXBzIHRvIGFuIGF0b21pYyBgU0VUIOKApiBOWGAgc28gdHdvXG4gKiBwb2RzIGJlaGluZCBhIGxvYWQtYmFsYW5jZXIgY2FuJ3QgYm90aCB3aW4gYSBmaXJzdC10aW1lIHJlcXVlc3Qgc2hhcmluZyBhblxuICogSWRlbXBvdGVuY3ktS2V5OyBhIHJldHJ5IGxhbmRpbmcgb24gQU5ZIHBvZCBzZWVzIHRoZSBzYW1lIGNvbW1pdHRlZCByZXNwb25zZS5cbiAqIFJlZGlzIFRUTCBvd25zIGV4cGlyeSAodGhlIGBDYWNoZWRFbnRyeS5leHBpcmVzQXRgIGZpZWxkIGlzIGNhcnJpZWQgb25seSBmb3JcbiAqIEFQSS1zaGFwZSBwYXJpdHkgd2l0aCB0aGUgbWVtb3J5IHN0b3JlKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVJlZGlzSWRlbXBvdGVuY3lTdG9yZShyZWRpczogUmVkaXNJZGVtcG90ZW5jeUNsaWVudCk6IElkZW1wb3RlbmN5U3RvcmUge1xuICBjb25zdCBrID0gKGtleTogc3RyaW5nKTogc3RyaW5nID0+IGAke1JFRElTX0tFWV9QUkVGSVh9JHtrZXl9YDtcbiAgcmV0dXJuIHtcbiAgICBhc3luYyBnZXQoa2V5KSB7XG4gICAgICBjb25zdCByYXcgPSBhd2FpdCByZWRpcy5nZXQoayhrZXkpKTtcbiAgICAgIGlmICghcmF3KSByZXR1cm4gbnVsbDtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBKU09OLnBhcnNlKHJhdykgYXMgQ2FjaGVkRW50cnk7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gQ29ycnVwdCBlbnRyeSDihpIgdHJlYXQgYXMgYWJzZW50IHNvIHRoZSByZXF1ZXN0IHByb2NlZWRzIGZyZXNoLlxuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICB9LFxuICAgIGFzeW5jIHNldChrZXksIGVudHJ5LCB0dGxTZWNvbmRzKSB7XG4gICAgICBhd2FpdCByZWRpcy5zZXQoayhrZXkpLCBKU09OLnN0cmluZ2lmeShlbnRyeSksICdFWCcsIE1hdGgubWF4KDEsIHR0bFNlY29uZHMpKTtcbiAgICB9LFxuICAgIGFzeW5jIHJlc2VydmUoa2V5LCBlbnRyeSwgdHRsU2Vjb25kcykge1xuICAgICAgLy8gQXRvbWljIGNsYWltOiBTRVQga2V5IHZhbCBFWCB0dGwgTlgg4oaSICdPSycgb25seSBpZiB0aGUga2V5IHdhcyBhYnNlbnQuXG4gICAgICBjb25zdCByZXMgPSBhd2FpdCByZWRpcy5zZXQoayhrZXkpLCBKU09OLnN0cmluZ2lmeShlbnRyeSksICdFWCcsIE1hdGgubWF4KDEsIHR0bFNlY29uZHMpLCAnTlgnKTtcbiAgICAgIHJldHVybiByZXMgPT09ICdPSyc7XG4gICAgfSxcbiAgICBhc3luYyBkZWxldGUoa2V5KSB7XG4gICAgICBhd2FpdCByZWRpcy5kZWwoayhrZXkpKTtcbiAgICB9LFxuICB9O1xufVxuXG4vKipcbiAqIEJ1aWxkIGEgUmVkaXMtYmFja2VkIGlkZW1wb3RlbmN5IHN0b3JlIGZyb20gdGhlIHNoYXJlZCBlbnYgUmVkaXMgKHRoZSBzYW1lXG4gKiBgUkVESVNfVVJMYCAvIGBSRURJU19IT1NUYCB3aXJpbmcgdGhlIHJhdGUtbGltaXRlciBhbmQgYXVkaXQtc3Bvb2wgdXNlKS5cbiAqIFJldHVybnMgYG51bGxgIHdoZW4gUmVkaXMgaXNuJ3QgY29uZmlndXJlZC9hdmFpbGFibGUgc28gdGhlIGNhbGxlciBrZWVwcyB0aGVcbiAqIGluLW1lbW9yeSBkZWZhdWx0IChzaW5nbGUtcmVwbGljYSBjb3JyZWN0bmVzcykuIE5ldmVyIHRocm93cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVudlJlZGlzSWRlbXBvdGVuY3lTdG9yZSgpOiBJZGVtcG90ZW5jeVN0b3JlIHwgbnVsbCB7XG4gIGNvbnN0IGNsaWVudCA9IGNyZWF0ZUVudlJlZGlzQ2xpZW50PFJlZGlzSWRlbXBvdGVuY3lDbGllbnQ+KCdpZGVtcG90ZW5jeScpO1xuICBpZiAoIWNsaWVudCkgcmV0dXJuIG51bGw7XG4gIGxvZ2dlci5pbmZvKCdSZWRpcyBpZGVtcG90ZW5jeSBzdG9yZSBpbml0aWFsaXplZCcpO1xuICByZXR1cm4gY3JlYXRlUmVkaXNJZGVtcG90ZW5jeVN0b3JlKGNsaWVudCk7XG59XG5cbmNvbnN0IG1lbW9yeVN0b3JlID0gY3JlYXRlTWVtb3J5U3RvcmUoKTtcblxuLyoqXG4gKiBQcm9jZXNzLXdpZGUgZGVmYXVsdCBzdG9yZSB1c2VkIGJ5IGBpZGVtcG90ZW5jeU1pZGRsZXdhcmUoKWAgd2hlbiBubyBleHBsaWNpdFxuICogYHN0b3JlYCBpcyBwYXNzZWQuIERlZmF1bHRzIHRvIHRoZSBpbi1tZW1vcnkgc3RvcmU7IGBjcmVhdGVBcHBgIHByb21vdGVzIGl0IHRvXG4gKiB0aGUgc2hhcmVkIGVudi1SZWRpcyBzdG9yZSAobXVsdGktcmVwbGljYSBkZWR1cCkgYXQgc3RhcnR1cCB2aWFcbiAqIHtAbGluayBzZXRJZGVtcG90ZW5jeVN0b3JlfS4gVGhlIHJvdXRlIGZhY3RvcmllcyBrZWVwIGNhbGxpbmdcbiAqIGBpZGVtcG90ZW5jeU1pZGRsZXdhcmUoKWAgd2l0aCBubyBhcmdzIGFuZCB0cmFuc3BhcmVudGx5IHBpY2sgdGhpcyB1cC5cbiAqL1xubGV0IGRlZmF1bHRTdG9yZTogSWRlbXBvdGVuY3lTdG9yZSA9IG1lbW9yeVN0b3JlO1xuXG4vKiogSW5qZWN0IHRoZSBwcm9jZXNzLXdpZGUgaWRlbXBvdGVuY3kgc3RvcmUgKGUuZy4gYSBSZWRpcy1iYWNrZWQgb25lKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZXRJZGVtcG90ZW5jeVN0b3JlKHN0b3JlOiBJZGVtcG90ZW5jeVN0b3JlKTogdm9pZCB7XG4gIGRlZmF1bHRTdG9yZSA9IHN0b3JlO1xufVxuXG4vKiogVGhlIGN1cnJlbnQgcHJvY2Vzcy13aWRlIGRlZmF1bHQgc3RvcmUgKGluLW1lbW9yeSB1bmxlc3Mgb3ZlcnJpZGRlbikuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0SWRlbXBvdGVuY3lTdG9yZSgpOiBJZGVtcG90ZW5jeVN0b3JlIHtcbiAgcmV0dXJuIGRlZmF1bHRTdG9yZTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJZGVtcG90ZW5jeU1pZGRsZXdhcmVPcHRpb25zIHtcbiAgLyoqIEN1c3RvbSBzdG9yZSBiYWNrZW5kLiBEZWZhdWx0cyB0byB0aGUgcHJvY2Vzcy13aWRlIHN0b3JlIHNldCB2aWFcbiAgICogIHtAbGluayBzZXRJZGVtcG90ZW5jeVN0b3JlfSAoaW4tbWVtb3J5IHVudGlsIGBjcmVhdGVBcHBgIHdpcmVzIFJlZGlzKS4gKi9cbiAgc3RvcmU/OiBJZGVtcG90ZW5jeVN0b3JlO1xufVxuXG4vKipcbiAqIE1pZGRsZXdhcmUgdGhhdCBzdXBwb3J0cyBpZGVtcG90ZW5jeSBrZXlzIGZvciBQT1NUL1BVVC9ERUxFVEUgbXV0YXRpb25zLlxuICpcbiAqIFdoZW4gYSByZXF1ZXN0IGluY2x1ZGVzIHRoZSBgSWRlbXBvdGVuY3ktS2V5YCBoZWFkZXI6XG4gKiAtIEZpcnN0IGNhbGw6IHByb2Nlc3NlcyBub3JtYWxseSwgY2FjaGVzIHRoZSByZXNwb25zZVxuICogLSBTdWJzZXF1ZW50IGNhbGxzIHdpdGggc2FtZSBrZXk6IHJldHVybnMgY2FjaGVkIHJlc3BvbnNlIChwcmV2ZW50cyBkdXBsaWNhdGUgbXV0YXRpb25zKVxuICpcbiAqIERlZmF1bHRzIHRvIGFuIGluLW1lbW9yeSBzdG9yZSAoc2luZ2xlLXJlcGxpY2EpLiBQYXNzIGEgY3VzdG9tXG4gKiBjcm9zcy1yZXBsaWNhIGB7IHN0b3JlIH1gIGltcGxlbWVudGluZyBgSWRlbXBvdGVuY3lTdG9yZWAgdG8gZGVkdXBlIGFjcm9zc1xuICogcmVwbGljYXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpZGVtcG90ZW5jeU1pZGRsZXdhcmUob3B0aW9uczogSWRlbXBvdGVuY3lNaWRkbGV3YXJlT3B0aW9ucyA9IHt9KSB7XG4gIC8vIFJlc29sdmUgdGhlIHN0b3JlIFBFUiBSRVFVRVNUIChub3QgYXQgZmFjdG9yeSB0aW1lKSBzbyBhIHN0b3JlIGluamVjdGVkIGJ5XG4gIC8vIGNyZWF0ZUFwcCBBRlRFUiB0aGUgcm91dGUgY2hhaW5zIGFyZSBidWlsdCBzdGlsbCB0YWtlcyBlZmZlY3QuIEFuIGV4cGxpY2l0XG4gIC8vIGBvcHRpb25zLnN0b3JlYCBhbHdheXMgd2lucy5cbiAgY29uc3QgZXhwbGljaXRTdG9yZSA9IG9wdGlvbnMuc3RvcmU7XG4gIGNvbnN0IHR0bE1zID0gQ29yZUNvbnN0YW50cy5JREVNUE9URU5DWV9UVExfTVM7XG4gIGNvbnN0IHR0bFNlYyA9IE1hdGguZmxvb3IodHRsTXMgLyAxMDAwKTtcblxuICByZXR1cm4gKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKSA9PiB7XG4gICAgY29uc3Qgc3RvcmUgPSBleHBsaWNpdFN0b3JlID8/IGRlZmF1bHRTdG9yZTtcbiAgICBjb25zdCBrZXkgPSByZXEuaGVhZGVyc1snaWRlbXBvdGVuY3kta2V5J10gYXMgc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIGlmICgha2V5KSByZXR1cm4gbmV4dCgpO1xuXG4gICAgLy8gT25seSBhcHBseSB0byBtdXRhdGlvbiBtZXRob2RzXG4gICAgaWYgKCFbJ1BPU1QnLCAnUFVUJywgJ0RFTEVURSddLmluY2x1ZGVzKHJlcS5tZXRob2QpKSByZXR1cm4gbmV4dCgpO1xuXG4gICAgLy8gTmFtZXNwYWNlIGJ5IG9yZ0lkIHRvIHByZXZlbnQgY3Jvc3Mtb3JnIGNhY2hlIGNvbGxpc2lvbnMuIFByZWZlciB0aGVcbiAgICAvLyBWRVJJRklFRCBhdXRoIG9yZyAoYHJlcS51c2VyYCwgcG9wdWxhdGVkIGJ5IHJlcXVpcmVBdXRoIGFmdGVyIHNpZ25hdHVyZVxuICAgIC8vIHZlcmlmaWNhdGlvbikgb3ZlciBgcmVxLmNvbnRleHQuaWRlbnRpdHlgLCB3aGljaCBzb21lIHNlcnZpY2VzIHBvcHVsYXRlXG4gICAgLy8gcHJlLWF1dGggZnJvbSBhbiBVTlZFUklGSUVEIHRva2VuIHBlZWsg4oCUIG5hbWVzcGFjaW5nIGEgbXV0YXRpb24ncyByZXBsYXlcbiAgICAvLyBjYWNoZSBvbiBhbiB1bnZlcmlmaWVkLCBjYWxsZXItaW5mbHVlbmNlZCBvcmcgaWQgd291bGQgYmUgdW5zYWZlLlxuICAgIGNvbnN0IG9yZ0lkID0gcmVxLnVzZXI/Lm9yZ2FuaXphdGlvbklkIHx8IHJlcS5jb250ZXh0Py5pZGVudGl0eT8ub3JnSWQ7XG4gICAgaWYgKCFvcmdJZCkgcmV0dXJuIG5leHQoKTsgLy8gc2tpcCBpZGVtcG90ZW5jeSBmb3IgdW5hdXRoZW50aWNhdGVkIHJlcXVlc3RzXG5cbiAgICAvLyBOYW1lc3BhY2UgdGhlIHN0b3JlZCBrZXkgYnkgKG1ldGhvZCwgcm91dGUsIG9yZywgYm9keSkgc28gYW4gSWRlbXBvdGVuY3ktS2V5XG4gICAgLy8gaXMgc2NvcGVkIHRvIE9ORSBzcGVjaWZpYyBlbmRwb2ludCtwYXlsb2FkLiBSZXVzaW5nIGEgc2luZ2xlIGtleSBhY3Jvc3MgdHdvXG4gICAgLy8gcm91dGVzIChlLmcuIFBPU1QgL3BpcGVsaW5lcyB0aGVuIFBPU1QgL3BsdWdpbnMpIHByZXZpb3VzbHkgcmVwbGF5ZWQgdGhlXG4gICAgLy8gZmlyc3QgZW5kcG9pbnQncyAyeHggZm9yIHRoZSBzZWNvbmQg4oCUIHRoZSBzdG9yZSBpcyBhIG1vZHVsZS1sZXZlbCBzaW5nbGV0b25cbiAgICAvLyBzaGFyZWQgYnkgZXZlcnkgcm91dGUuIEZvbGRpbmcgaW4gbWV0aG9kICsgcm91dGUgcGF0aCArIGEgYm9keSBmaW5nZXJwcmludFxuICAgIC8vIG9ubHkgbWFrZXMga2V5cyBNT1JFIHNwZWNpZmljLCBzbyB0aGVyZSBpcyBubyBjcm9zcy1jb250YW1pbmF0aW9uIHJpc2suXG4gICAgY29uc3Qgcm91dGVQYXRoID0gKHJlcS5iYXNlVXJsID8/ICcnKSArIChyZXEucm91dGU/LnBhdGggPz8gcmVxLnBhdGggPz8gJycpO1xuICAgIGNvbnN0IGZ1bGxLZXkgPSBgJHtyZXEubWV0aG9kfToke3JvdXRlUGF0aH06JHtvcmdJZH06JHtib2R5RmluZ2VycHJpbnQocmVxLmJvZHkpfToke2tleX1gO1xuXG4gICAgLyoqIFJlamVjdCBhIGR1cGxpY2F0ZSB3aG9zZSBvcmlnaW5hbCBpcyBzdGlsbCBpbi1mbGlnaHQuICovXG4gICAgY29uc3Qgc2VuZEluUHJvZ3Jlc3MgPSAoKTogdm9pZCA9PiB7XG4gICAgICByZXMuc2V0SGVhZGVyKCdSZXRyeS1BZnRlcicsICcxJyk7XG4gICAgICBzZW5kRXJyb3IocmVzLCA0MDksICdBIHJlcXVlc3Qgd2l0aCB0aGlzIElkZW1wb3RlbmN5LUtleSBpcyBhbHJlYWR5IGJlaW5nIHByb2Nlc3NlZCcsIEVycm9yQ29kZS5DT05GTElDVCk7XG4gICAgfTtcblxuICAgIHN0b3JlLmdldChmdWxsS2V5KS50aGVuKGFzeW5jIChjYWNoZWQpID0+IHtcbiAgICAgIGlmIChjYWNoZWQgJiYgIWNhY2hlZC5wZW5kaW5nKSB7XG4gICAgICAgIC8vIENvbXBsZXRlZCByZXNwb25zZSBvbiByZWNvcmQg4oaSIHJlcGxheSBpdC5cbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdJZGVtcG90ZW50IHJlcXVlc3QgcmVwbGF5ZWQnLCB7IGtleTogZnVsbEtleSB9KTtcbiAgICAgICAgcmVzLnNldEhlYWRlcignWC1JZGVtcG90ZW50LVJlcGxheWVkJywgJ3RydWUnKTtcbiAgICAgICAgcmVzLnN0YXR1cyhjYWNoZWQuc3RhdHVzQ29kZSkuanNvbihjYWNoZWQuYm9keSk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGlmIChjYWNoZWQgJiYgY2FjaGVkLnBlbmRpbmcpIHtcbiAgICAgICAgLy8gVGhlIG9yaWdpbmFsIGlzIHN0aWxsIHJ1bm5pbmcg4oCUIGRvbid0IHJ1biB0aGUgbXV0YXRpb24gYSBzZWNvbmQgdGltZS5cbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdJZGVtcG90ZW50IHJlcXVlc3QgcmVqZWN0ZWQgKGluIHByb2dyZXNzKScsIHsga2V5OiBmdWxsS2V5IH0pO1xuICAgICAgICByZXR1cm4gc2VuZEluUHJvZ3Jlc3MoKTtcbiAgICAgIH1cblxuICAgICAgLy8gQXRvbWljYWxseSByZXNlcnZlIHRoZSBrZXkuIExvc2luZyB0aGlzIHJhY2UgbWVhbnMgYSBjb25jdXJyZW50IHJlcXVlc3RcbiAgICAgIC8vIHdpdGggdGhlIHNhbWUga2V5IGdvdCB0aGVyZSBmaXJzdCBhbmQgaXMgaW4tZmxpZ2h0IOKGkiA0MDkuXG4gICAgICBjb25zdCB3b24gPSBhd2FpdCBzdG9yZS5yZXNlcnZlKFxuICAgICAgICBmdWxsS2V5LFxuICAgICAgICB7IHN0YXR1c0NvZGU6IDAsIGJvZHk6IG51bGwsIHBlbmRpbmc6IHRydWUsIGV4cGlyZXNBdDogRGF0ZS5ub3coKSArIHR0bE1zIH0sXG4gICAgICAgIHR0bFNlYyxcbiAgICAgICk7XG4gICAgICBpZiAoIXdvbikge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ0lkZW1wb3RlbnQgcmVxdWVzdCByZWplY3RlZCAobG9zdCByZXNlcnZlIHJhY2UpJywgeyBrZXk6IGZ1bGxLZXkgfSk7XG4gICAgICAgIHJldHVybiBzZW5kSW5Qcm9ncmVzcygpO1xuICAgICAgfVxuXG4gICAgICAvLyBXZSBvd24gdGhlIHJlc2VydmF0aW9uLiBJdCBNVVNUIGJlIHJlc29sdmVkIGV4YWN0bHkgb25jZSDigJQgZWl0aGVyIHRoZVxuICAgICAgLy8gcmVhbCAyeHggcmVzcG9uc2UgaXMgY2FjaGVkIGZvciByZXBsYXksIG9yIHRoZSByZXNlcnZhdGlvbiBpcyByZWxlYXNlZCBzb1xuICAgICAgLy8gYSByZXRyeSBjYW4gcHJvY2VlZC4gYHNldHRsZWRgIGd1YXJkcyBhZ2FpbnN0IGEgZG91YmxlLXJlc29sdmUuXG4gICAgICBsZXQgc2V0dGxlZCA9IGZhbHNlO1xuICAgICAgbGV0IGNhcHR1cmVkQm9keTogdW5rbm93bjtcbiAgICAgIGxldCBib2R5Q2FwdHVyZWQgPSBmYWxzZTtcblxuICAgICAgLyoqIFJlbWVtYmVyIHRoZSByZXNwb25zZSBib2R5IGZvciByZXBsYXkgKGZpcnN0IGNhcHR1cmUgd2lucykuICovXG4gICAgICBjb25zdCBjYXB0dXJlQm9keSA9IChib2R5OiB1bmtub3duKTogdm9pZCA9PiB7XG4gICAgICAgIGlmICghYm9keUNhcHR1cmVkKSB7XG4gICAgICAgICAgY2FwdHVyZWRCb2R5ID0gYm9keTtcbiAgICAgICAgICBib2R5Q2FwdHVyZWQgPSB0cnVlO1xuICAgICAgICB9XG4gICAgICB9O1xuXG4gICAgICBjb25zdCByZWxlYXNlID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBpZiAoc2V0dGxlZCkgcmV0dXJuO1xuICAgICAgICBzZXR0bGVkID0gdHJ1ZTtcbiAgICAgICAgc3RvcmUuZGVsZXRlKGZ1bGxLZXkpLmNhdGNoKChlcnIpID0+IHtcbiAgICAgICAgICBsb2dnZXIud2FybignSWRlbXBvdGVuY3kgcmVzZXJ2YXRpb24gcmVsZWFzZSBmYWlsZWQnLCB7IGtleTogZnVsbEtleSwgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9KTtcbiAgICAgICAgfSk7XG4gICAgICB9O1xuXG4gICAgICBjb25zdCBjYWNoZVN1Y2Nlc3MgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIGlmIChzZXR0bGVkKSByZXR1cm47XG4gICAgICAgIHNldHRsZWQgPSB0cnVlO1xuICAgICAgICBzdG9yZS5zZXQoZnVsbEtleSwge1xuICAgICAgICAgIHN0YXR1c0NvZGU6IHJlcy5zdGF0dXNDb2RlLFxuICAgICAgICAgIGJvZHk6IGJvZHlDYXB0dXJlZCA/IGNhcHR1cmVkQm9keSA6IG51bGwsXG4gICAgICAgICAgZXhwaXJlc0F0OiBEYXRlLm5vdygpICsgdHRsTXMsXG4gICAgICAgIH0sIHR0bFNlYykuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICAgIGxvZ2dlci53YXJuKCdJZGVtcG90ZW5jeSBzdG9yZS5zZXQgZmFpbGVkJywgeyBrZXk6IGZ1bGxLZXksIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycikgfSk7XG4gICAgICAgIH0pO1xuICAgICAgfTtcblxuICAgICAgLyoqIFJlc29sdmUgdGhlIHJlc2VydmF0aW9uIGZyb20gdGhlIEFDVFVBTCBmaW5hbCBzdGF0dXM6IGNhY2hlIDJ4eCwgZWxzZSByZWxlYXNlLiAqL1xuICAgICAgY29uc3Qgc2V0dGxlRnJvbVN0YXR1cyA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKHNldHRsZWQpIHJldHVybjtcbiAgICAgICAgaWYgKHJlcy5zdGF0dXNDb2RlID49IDIwMCAmJiByZXMuc3RhdHVzQ29kZSA8IDMwMCkgY2FjaGVTdWNjZXNzKCk7XG4gICAgICAgIGVsc2UgcmVsZWFzZSgpO1xuICAgICAgfTtcblxuICAgICAgLy8gRmFzdCBwYXRoOiByZXMuanNvbiBpcyB0aGUgY29tbW9uIGNvbXBsZXRpb24uIENhcHR1cmUgdGhlIGJvZHkgYW5kIHNldHRsZVxuICAgICAgLy8gaW1tZWRpYXRlbHkgb2ZmIHRoZSBzdGF0dXMgc2V0IGJ5IHJlcy5zdGF0dXMoLi4uKS5qc29uKC4uLikuXG4gICAgICBjb25zdCBvcmlnaW5hbEpzb24gPSByZXMuanNvbi5iaW5kKHJlcyk7XG4gICAgICByZXMuanNvbiA9IChib2R5OiB1bmtub3duKSA9PiB7XG4gICAgICAgIHJlcy5zZXRIZWFkZXIoJ1gtSWRlbXBvdGVudC1SZXBsYXllZCcsICdmYWxzZScpO1xuICAgICAgICBjYXB0dXJlQm9keShib2R5KTtcbiAgICAgICAgc2V0dGxlRnJvbVN0YXR1cygpO1xuICAgICAgICByZXR1cm4gb3JpZ2luYWxKc29uKGJvZHkpO1xuICAgICAgfTtcblxuICAgICAgLy8gQm9keS1jYXB0dXJlIGJhY2tzdG9wcyBmb3IgY29tcGxldGlvbnMgdGhhdCBieXBhc3MgcmVzLmpzb25cbiAgICAgIC8vIChyZXMuc2VuZCAvIHJlcy5lbmQgLyByZXMuc2VuZEZpbGUg4oaSIHJlcy5lbmQpLiBUaGVzZSBvbmx5IFJFQ09SRCB0aGUgYm9keTtcbiAgICAgIC8vIHRoZSBhY3R1YWwgc2V0dGxlIGhhcHBlbnMgb24gYGZpbmlzaGAgZnJvbSB0aGUgcmVhbCBzdGF0dXMgY29kZS4gRmlyc3RcbiAgICAgIC8vIGNhcHR1cmUgd2lucywgc28gcmVzLmpzb24ncyBib2R5IGlzIG5ldmVyIGNsb2JiZXJlZCBieSBpdHMgaW50ZXJuYWxcbiAgICAgIC8vIHJlcy5zZW5kL3Jlcy5lbmQgY2FsbHMuXG4gICAgICBjb25zdCBwYXRjaGVkID0gcmVzIGFzIHVua25vd24gYXMge1xuICAgICAgICBzZW5kPzogKGJvZHk/OiB1bmtub3duKSA9PiB1bmtub3duO1xuICAgICAgICBlbmQ/OiAoLi4uYXJnczogdW5rbm93bltdKSA9PiB1bmtub3duO1xuICAgICAgICBvbj86IChldmVudDogc3RyaW5nLCBjYjogKCkgPT4gdm9pZCkgPT4gdW5rbm93bjtcbiAgICAgIH07XG4gICAgICBpZiAodHlwZW9mIHBhdGNoZWQuc2VuZCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBjb25zdCBvcmlnaW5hbFNlbmQgPSBwYXRjaGVkLnNlbmQuYmluZChyZXMpO1xuICAgICAgICBwYXRjaGVkLnNlbmQgPSAoYm9keT86IHVua25vd24pID0+IHtcbiAgICAgICAgICBjYXB0dXJlQm9keShib2R5KTtcbiAgICAgICAgICByZXR1cm4gb3JpZ2luYWxTZW5kKGJvZHkpO1xuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgaWYgKHR5cGVvZiBwYXRjaGVkLmVuZCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBjb25zdCBvcmlnaW5hbEVuZCA9IHBhdGNoZWQuZW5kLmJpbmQocmVzKTtcbiAgICAgICAgcGF0Y2hlZC5lbmQgPSAoLi4uYXJnczogdW5rbm93bltdKSA9PiB7XG4gICAgICAgICAgY29uc3QgY2h1bmsgPSBhcmdzWzBdO1xuICAgICAgICAgIGlmIChjaHVuayAhPT0gdW5kZWZpbmVkICYmIHR5cGVvZiBjaHVuayAhPT0gJ2Z1bmN0aW9uJykgY2FwdHVyZUJvZHkoY2h1bmspO1xuICAgICAgICAgIHJldHVybiBvcmlnaW5hbEVuZCguLi5hcmdzKTtcbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgLy8gQmFja3N0b3AgZm9yIEFOWSBjb21wbGV0aW9uIHBhdGgg4oCUIGluY2x1ZGluZyBhIDJ4eCB0aGF0IGZpbmlzaGVkIHZpYVxuICAgICAgLy8gcmVzLnNlbmQvcmVzLmVuZC9yZXMuc2VuZEZpbGUvMjA0IGFuZCBuZXZlciBoaXQgdGhlIHBhdGNoZWQgcmVzLmpzb24uXG4gICAgICAvLyBEZWNpZGluZyBvbiB0aGUgcmVhbCBmaW5hbCBzdGF0dXMgaGVyZSBtZWFucyBhIHN1Y2Nlc3NmdWwgb3AgaXMgY2FjaGVkXG4gICAgICAvLyAob3IgYSBmYWlsZWQgb25lIHJlbGVhc2VkKSBpbnN0ZWFkIG9mIHN0cmFuZGluZyB0aGUgcmVzZXJ2YXRpb24gYW5kXG4gICAgICAvLyA0MDktaW5nIGV2ZXJ5IHJldHJ5IGZvciB0aGUgZnVsbCBUVEwuIEd1YXJkZWQg4oCUIHRlc3QgZG91YmxlcyBtYXkgbm90IGJlXG4gICAgICAvLyBFdmVudEVtaXR0ZXJzOyByZXMuanNvbidzIGZhc3QgcGF0aCBhbHJlYWR5IHNldHRsZWQgdGhlIGNvbW1vbiBjYXNlLlxuICAgICAgaWYgKHR5cGVvZiBwYXRjaGVkLm9uID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIHBhdGNoZWQub24oJ2ZpbmlzaCcsIHNldHRsZUZyb21TdGF0dXMpO1xuICAgICAgfVxuXG4gICAgICBuZXh0KCk7XG4gICAgfSkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgLy8gSWYgdGhlIHN0b3JlIGJhY2tlbmQgaXMgZG93biwgZmFpbCBvcGVuIOKAlCBwcm9jZXNzIHRoZSByZXF1ZXN0IG5vcm1hbGx5LlxuICAgICAgbG9nZ2VyLndhcm4oJ0lkZW1wb3RlbmN5IHN0b3JlLmdldCBmYWlsZWQ7IHByb2NlZWRpbmcgd2l0aG91dCBkZWR1cCcsIHsga2V5OiBmdWxsS2V5LCBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpIH0pO1xuICAgICAgbmV4dCgpO1xuICAgIH0pO1xuICB9O1xufVxuIl19
@@ -0,0 +1,20 @@
1
+ export * from './middleware.js';
2
+ export * from './middleware-factory.js';
3
+ export * from './context-middleware.js';
4
+ export * from './check-quota.js';
5
+ export * from './meter-quota.js';
6
+ export * from './rate-limit-by-org.js';
7
+ export * from './require-org-id.js';
8
+ export * from './tenant-context.js';
9
+ export * from './get-context.js';
10
+ export * from './app-factory.js';
11
+ export * from './health-checks.js';
12
+ export * from './mongo-connect.js';
13
+ export * from './quota-helpers.js';
14
+ export * from './idempotency-middleware.js';
15
+ export * from './tracing.js';
16
+ export * from './metrics.js';
17
+ export * from './server.js';
18
+ export * from './readiness.js';
19
+ export * from './route-wrapper.js';
20
+ export * from './request-types.js';
@@ -0,0 +1,34 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Middleware
4
+ export * from './middleware.js';
5
+ export * from './middleware-factory.js';
6
+ export * from './context-middleware.js';
7
+ export * from './check-quota.js';
8
+ export * from './meter-quota.js';
9
+ export * from './rate-limit-by-org.js';
10
+ export * from './require-org-id.js';
11
+ export * from './tenant-context.js';
12
+ export * from './get-context.js';
13
+ // App factory
14
+ export * from './app-factory.js';
15
+ // Health-check helpers
16
+ export * from './health-checks.js';
17
+ // MongoDB connection helper (dependency-injected mongoose)
18
+ export * from './mongo-connect.js';
19
+ // Quota helpers
20
+ export * from './quota-helpers.js';
21
+ // Idempotency
22
+ export * from './idempotency-middleware.js';
23
+ // Observability
24
+ export * from './tracing.js';
25
+ export * from './metrics.js';
26
+ // Server utilities
27
+ export * from './server.js';
28
+ // Readiness state + guard middleware
29
+ export * from './readiness.js';
30
+ // Route wrapper
31
+ export * from './route-wrapper.js';
32
+ // Request/Response types
33
+ export * from './request-types.js';
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsYUFBYTtBQUNiLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGtCQUFrQixDQUFDO0FBRWpDLGNBQWM7QUFDZCxjQUFjLGtCQUFrQixDQUFDO0FBRWpDLHVCQUF1QjtBQUN2QixjQUFjLG9CQUFvQixDQUFDO0FBRW5DLDJEQUEyRDtBQUMzRCxjQUFjLG9CQUFvQixDQUFDO0FBRW5DLGdCQUFnQjtBQUNoQixjQUFjLG9CQUFvQixDQUFDO0FBRW5DLGNBQWM7QUFDZCxjQUFjLDZCQUE2QixDQUFDO0FBRTVDLGdCQUFnQjtBQUNoQixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGNBQWMsQ0FBQztBQUU3QixtQkFBbUI7QUFDbkIsY0FBYyxhQUFhLENBQUM7QUFFNUIscUNBQXFDO0FBQ3JDLGNBQWMsZ0JBQWdCLENBQUM7QUFFL0IsZ0JBQWdCO0FBQ2hCLGNBQWMsb0JBQW9CLENBQUM7QUFFbkMseUJBQXlCO0FBQ3pCLGNBQWMsb0JBQW9CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLy8gTWlkZGxld2FyZVxuZXhwb3J0ICogZnJvbSAnLi9taWRkbGV3YXJlLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vbWlkZGxld2FyZS1mYWN0b3J5LmpzJztcbmV4cG9ydCAqIGZyb20gJy4vY29udGV4dC1taWRkbGV3YXJlLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vY2hlY2stcXVvdGEuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9tZXRlci1xdW90YS5qcyc7XG5leHBvcnQgKiBmcm9tICcuL3JhdGUtbGltaXQtYnktb3JnLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vcmVxdWlyZS1vcmctaWQuanMnO1xuZXhwb3J0ICogZnJvbSAnLi90ZW5hbnQtY29udGV4dC5qcyc7XG5leHBvcnQgKiBmcm9tICcuL2dldC1jb250ZXh0LmpzJztcblxuLy8gQXBwIGZhY3RvcnlcbmV4cG9ydCAqIGZyb20gJy4vYXBwLWZhY3RvcnkuanMnO1xuXG4vLyBIZWFsdGgtY2hlY2sgaGVscGVyc1xuZXhwb3J0ICogZnJvbSAnLi9oZWFsdGgtY2hlY2tzLmpzJztcblxuLy8gTW9uZ29EQiBjb25uZWN0aW9uIGhlbHBlciAoZGVwZW5kZW5jeS1pbmplY3RlZCBtb25nb29zZSlcbmV4cG9ydCAqIGZyb20gJy4vbW9uZ28tY29ubmVjdC5qcyc7XG5cbi8vIFF1b3RhIGhlbHBlcnNcbmV4cG9ydCAqIGZyb20gJy4vcXVvdGEtaGVscGVycy5qcyc7XG5cbi8vIElkZW1wb3RlbmN5XG5leHBvcnQgKiBmcm9tICcuL2lkZW1wb3RlbmN5LW1pZGRsZXdhcmUuanMnO1xuXG4vLyBPYnNlcnZhYmlsaXR5XG5leHBvcnQgKiBmcm9tICcuL3RyYWNpbmcuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9tZXRyaWNzLmpzJztcblxuLy8gU2VydmVyIHV0aWxpdGllc1xuZXhwb3J0ICogZnJvbSAnLi9zZXJ2ZXIuanMnO1xuXG4vLyBSZWFkaW5lc3Mgc3RhdGUgKyBndWFyZCBtaWRkbGV3YXJlXG5leHBvcnQgKiBmcm9tICcuL3JlYWRpbmVzcy5qcyc7XG5cbi8vIFJvdXRlIHdyYXBwZXJcbmV4cG9ydCAqIGZyb20gJy4vcm91dGUtd3JhcHBlci5qcyc7XG5cbi8vIFJlcXVlc3QvUmVzcG9uc2UgdHlwZXNcbmV4cG9ydCAqIGZyb20gJy4vcmVxdWVzdC10eXBlcy5qcyc7XG4iXX0=
@@ -0,0 +1,32 @@
1
+ import type { QuotaType, QuotaService } from '@pipeline-builder/api-core';
2
+ import type { Request, Response, NextFunction } from 'express';
3
+ /**
4
+ * Middleware that METERS (increments) a quota counter once per SUCCESSFUL
5
+ * request, on response `finish`.
6
+ *
7
+ * The complement to {@link checkQuota}: `checkQuota` only GATES (reads current
8
+ * usage and 429s when over) — it never increments, so a service that mounts it
9
+ * but never calls `incrementQuota` checks against a counter that its own traffic
10
+ * never moves (the metering gap this fixes for the compliance service). Mount
11
+ * this ALONGSIDE the auth/org chain to close the loop with one line instead of
12
+ * threading `quotaService` + a manual `incrementQuotaFromCtx` through every
13
+ * route factory and handler.
14
+ *
15
+ * Fire-and-forget + fail-safe by construction:
16
+ * - runs in a `finish` listener, so it never blocks or fails the response;
17
+ * - `incrementQuota` swallows its own errors (logs a warning);
18
+ * - only 2xx responses are metered (a rejected/errored request costs nothing);
19
+ * - a request with no VERIFIED org (`req.user.organizationId` — set by
20
+ * requireAuth, never the spoofable header identity) is skipped
21
+ * (unauthenticated / health / early-terminated);
22
+ * - SERVICE-PRINCIPAL callers are skipped: internal service-to-service traffic
23
+ * (e.g. a peer calling compliance `validate`) must not burn a tenant's quota —
24
+ * the originating service already meters the user action that triggered it.
25
+ *
26
+ * Do NOT combine this with a handler that already calls `incrementQuotaFromCtx`
27
+ * for the same quota type on the same route — that double-counts.
28
+ *
29
+ * @param quotaService - Quota service client
30
+ * @param quotaType - Which quota to meter (e.g. 'apiCalls')
31
+ */
32
+ export declare function meterQuotaOnSuccess(quotaService: QuotaService, quotaType: QuotaType): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,70 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { incrementQuota, isServicePrincipal, createLogger } from '@pipeline-builder/api-core';
4
+ import { getContext } from './get-context.js';
5
+ const logger = createLogger('meter-quota');
6
+ /**
7
+ * Middleware that METERS (increments) a quota counter once per SUCCESSFUL
8
+ * request, on response `finish`.
9
+ *
10
+ * The complement to {@link checkQuota}: `checkQuota` only GATES (reads current
11
+ * usage and 429s when over) — it never increments, so a service that mounts it
12
+ * but never calls `incrementQuota` checks against a counter that its own traffic
13
+ * never moves (the metering gap this fixes for the compliance service). Mount
14
+ * this ALONGSIDE the auth/org chain to close the loop with one line instead of
15
+ * threading `quotaService` + a manual `incrementQuotaFromCtx` through every
16
+ * route factory and handler.
17
+ *
18
+ * Fire-and-forget + fail-safe by construction:
19
+ * - runs in a `finish` listener, so it never blocks or fails the response;
20
+ * - `incrementQuota` swallows its own errors (logs a warning);
21
+ * - only 2xx responses are metered (a rejected/errored request costs nothing);
22
+ * - a request with no VERIFIED org (`req.user.organizationId` — set by
23
+ * requireAuth, never the spoofable header identity) is skipped
24
+ * (unauthenticated / health / early-terminated);
25
+ * - SERVICE-PRINCIPAL callers are skipped: internal service-to-service traffic
26
+ * (e.g. a peer calling compliance `validate`) must not burn a tenant's quota —
27
+ * the originating service already meters the user action that triggered it.
28
+ *
29
+ * Do NOT combine this with a handler that already calls `incrementQuotaFromCtx`
30
+ * for the same quota type on the same route — that double-counts.
31
+ *
32
+ * @param quotaService - Quota service client
33
+ * @param quotaType - Which quota to meter (e.g. 'apiCalls')
34
+ */
35
+ export function meterQuotaOnSuccess(quotaService, quotaType) {
36
+ return (req, res, next) => {
37
+ res.on('finish', () => {
38
+ try {
39
+ // Only successful responses are billable.
40
+ if (res.statusCode < 200 || res.statusCode >= 300)
41
+ return;
42
+ // Resolve the org id from the SAME source `checkQuota` reads —
43
+ // `getContext().identity.orgId`, which is normalized (trimmed + lowercased).
44
+ // Metering off the raw `req.user.organizationId` while the gate checks the
45
+ // normalized id would land increments on a different key than the check
46
+ // reads (a silent bypass if an org id ever carries mixed case). Fall back to
47
+ // the raw auth org only when context middleware isn't mounted.
48
+ let orgId;
49
+ try {
50
+ orgId = getContext(req).identity.orgId;
51
+ }
52
+ catch {
53
+ orgId = req.user?.organizationId;
54
+ }
55
+ if (!orgId)
56
+ return;
57
+ // Internal service-to-service calls must not consume a tenant's quota.
58
+ if (isServicePrincipal(req))
59
+ return;
60
+ incrementQuota(quotaService, orgId, quotaType, req.headers.authorization || '', (message, data) => logger.warn(message, data));
61
+ }
62
+ catch {
63
+ // Metering must never affect the response; a missing user/context just
64
+ // means an unauthenticated or early-terminated request — skip silently.
65
+ }
66
+ });
67
+ next();
68
+ };
69
+ }
70
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0ZXItcXVvdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL21ldGVyLXF1b3RhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBRSxZQUFZLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUc5RixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFOUMsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0FBRTNDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBNEJHO0FBQ0gsTUFBTSxVQUFVLG1CQUFtQixDQUFDLFlBQTBCLEVBQUUsU0FBb0I7SUFDbEYsT0FBTyxDQUFDLEdBQVksRUFBRSxHQUFhLEVBQUUsSUFBa0IsRUFBUSxFQUFFO1FBQy9ELEdBQUcsQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRTtZQUNwQixJQUFJLENBQUM7Z0JBQ0gsMENBQTBDO2dCQUMxQyxJQUFJLEdBQUcsQ0FBQyxVQUFVLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxVQUFVLElBQUksR0FBRztvQkFBRSxPQUFPO2dCQUMxRCwrREFBK0Q7Z0JBQy9ELDZFQUE2RTtnQkFDN0UsMkVBQTJFO2dCQUMzRSx3RUFBd0U7Z0JBQ3hFLDZFQUE2RTtnQkFDN0UsK0RBQStEO2dCQUMvRCxJQUFJLEtBQXlCLENBQUM7Z0JBQzlCLElBQUksQ0FBQztvQkFBQyxLQUFLLEdBQUcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUM7Z0JBQUMsQ0FBQztnQkFBQyxNQUFNLENBQUM7b0JBQUMsS0FBSyxHQUFHLEdBQUcsQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDO2dCQUFDLENBQUM7Z0JBQzNGLElBQUksQ0FBQyxLQUFLO29CQUFFLE9BQU87Z0JBQ25CLHVFQUF1RTtnQkFDdkUsSUFBSSxrQkFBa0IsQ0FBQyxHQUFHLENBQUM7b0JBQUUsT0FBTztnQkFDcEMsY0FBYyxDQUNaLFlBQVksRUFDWixLQUFLLEVBQ0wsU0FBUyxFQUNULEdBQUcsQ0FBQyxPQUFPLENBQUMsYUFBYSxJQUFJLEVBQUUsRUFDL0IsQ0FBQyxPQUFPLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUErQixDQUFDLENBQ3pFLENBQUM7WUFDSixDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNQLHVFQUF1RTtnQkFDdkUsd0VBQXdFO1lBQzFFLENBQUM7UUFDSCxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksRUFBRSxDQUFDO0lBQ1QsQ0FBQyxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBpbmNyZW1lbnRRdW90YSwgaXNTZXJ2aWNlUHJpbmNpcGFsLCBjcmVhdGVMb2dnZXIgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgdHlwZSB7IFF1b3RhVHlwZSwgUXVvdGFTZXJ2aWNlIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBnZXRDb250ZXh0IH0gZnJvbSAnLi9nZXQtY29udGV4dC5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignbWV0ZXItcXVvdGEnKTtcblxuLyoqXG4gKiBNaWRkbGV3YXJlIHRoYXQgTUVURVJTIChpbmNyZW1lbnRzKSBhIHF1b3RhIGNvdW50ZXIgb25jZSBwZXIgU1VDQ0VTU0ZVTFxuICogcmVxdWVzdCwgb24gcmVzcG9uc2UgYGZpbmlzaGAuXG4gKlxuICogVGhlIGNvbXBsZW1lbnQgdG8ge0BsaW5rIGNoZWNrUXVvdGF9OiBgY2hlY2tRdW90YWAgb25seSBHQVRFUyAocmVhZHMgY3VycmVudFxuICogdXNhZ2UgYW5kIDQyOXMgd2hlbiBvdmVyKSDigJQgaXQgbmV2ZXIgaW5jcmVtZW50cywgc28gYSBzZXJ2aWNlIHRoYXQgbW91bnRzIGl0XG4gKiBidXQgbmV2ZXIgY2FsbHMgYGluY3JlbWVudFF1b3RhYCBjaGVja3MgYWdhaW5zdCBhIGNvdW50ZXIgdGhhdCBpdHMgb3duIHRyYWZmaWNcbiAqIG5ldmVyIG1vdmVzICh0aGUgbWV0ZXJpbmcgZ2FwIHRoaXMgZml4ZXMgZm9yIHRoZSBjb21wbGlhbmNlIHNlcnZpY2UpLiBNb3VudFxuICogdGhpcyBBTE9OR1NJREUgdGhlIGF1dGgvb3JnIGNoYWluIHRvIGNsb3NlIHRoZSBsb29wIHdpdGggb25lIGxpbmUgaW5zdGVhZCBvZlxuICogdGhyZWFkaW5nIGBxdW90YVNlcnZpY2VgICsgYSBtYW51YWwgYGluY3JlbWVudFF1b3RhRnJvbUN0eGAgdGhyb3VnaCBldmVyeVxuICogcm91dGUgZmFjdG9yeSBhbmQgaGFuZGxlci5cbiAqXG4gKiBGaXJlLWFuZC1mb3JnZXQgKyBmYWlsLXNhZmUgYnkgY29uc3RydWN0aW9uOlxuICogLSBydW5zIGluIGEgYGZpbmlzaGAgbGlzdGVuZXIsIHNvIGl0IG5ldmVyIGJsb2NrcyBvciBmYWlscyB0aGUgcmVzcG9uc2U7XG4gKiAtIGBpbmNyZW1lbnRRdW90YWAgc3dhbGxvd3MgaXRzIG93biBlcnJvcnMgKGxvZ3MgYSB3YXJuaW5nKTtcbiAqIC0gb25seSAyeHggcmVzcG9uc2VzIGFyZSBtZXRlcmVkIChhIHJlamVjdGVkL2Vycm9yZWQgcmVxdWVzdCBjb3N0cyBub3RoaW5nKTtcbiAqIC0gYSByZXF1ZXN0IHdpdGggbm8gVkVSSUZJRUQgb3JnIChgcmVxLnVzZXIub3JnYW5pemF0aW9uSWRgIOKAlCBzZXQgYnlcbiAqICAgcmVxdWlyZUF1dGgsIG5ldmVyIHRoZSBzcG9vZmFibGUgaGVhZGVyIGlkZW50aXR5KSBpcyBza2lwcGVkXG4gKiAgICh1bmF1dGhlbnRpY2F0ZWQgLyBoZWFsdGggLyBlYXJseS10ZXJtaW5hdGVkKTtcbiAqIC0gU0VSVklDRS1QUklOQ0lQQUwgY2FsbGVycyBhcmUgc2tpcHBlZDogaW50ZXJuYWwgc2VydmljZS10by1zZXJ2aWNlIHRyYWZmaWNcbiAqICAgKGUuZy4gYSBwZWVyIGNhbGxpbmcgY29tcGxpYW5jZSBgdmFsaWRhdGVgKSBtdXN0IG5vdCBidXJuIGEgdGVuYW50J3MgcXVvdGEg4oCUXG4gKiAgIHRoZSBvcmlnaW5hdGluZyBzZXJ2aWNlIGFscmVhZHkgbWV0ZXJzIHRoZSB1c2VyIGFjdGlvbiB0aGF0IHRyaWdnZXJlZCBpdC5cbiAqXG4gKiBEbyBOT1QgY29tYmluZSB0aGlzIHdpdGggYSBoYW5kbGVyIHRoYXQgYWxyZWFkeSBjYWxscyBgaW5jcmVtZW50UXVvdGFGcm9tQ3R4YFxuICogZm9yIHRoZSBzYW1lIHF1b3RhIHR5cGUgb24gdGhlIHNhbWUgcm91dGUg4oCUIHRoYXQgZG91YmxlLWNvdW50cy5cbiAqXG4gKiBAcGFyYW0gcXVvdGFTZXJ2aWNlIC0gUXVvdGEgc2VydmljZSBjbGllbnRcbiAqIEBwYXJhbSBxdW90YVR5cGUgLSBXaGljaCBxdW90YSB0byBtZXRlciAoZS5nLiAnYXBpQ2FsbHMnKVxuICovXG5leHBvcnQgZnVuY3Rpb24gbWV0ZXJRdW90YU9uU3VjY2VzcyhxdW90YVNlcnZpY2U6IFF1b3RhU2VydmljZSwgcXVvdGFUeXBlOiBRdW90YVR5cGUpIHtcbiAgcmV0dXJuIChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UsIG5leHQ6IE5leHRGdW5jdGlvbik6IHZvaWQgPT4ge1xuICAgIHJlcy5vbignZmluaXNoJywgKCkgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gT25seSBzdWNjZXNzZnVsIHJlc3BvbnNlcyBhcmUgYmlsbGFibGUuXG4gICAgICAgIGlmIChyZXMuc3RhdHVzQ29kZSA8IDIwMCB8fCByZXMuc3RhdHVzQ29kZSA+PSAzMDApIHJldHVybjtcbiAgICAgICAgLy8gUmVzb2x2ZSB0aGUgb3JnIGlkIGZyb20gdGhlIFNBTUUgc291cmNlIGBjaGVja1F1b3RhYCByZWFkcyDigJRcbiAgICAgICAgLy8gYGdldENvbnRleHQoKS5pZGVudGl0eS5vcmdJZGAsIHdoaWNoIGlzIG5vcm1hbGl6ZWQgKHRyaW1tZWQgKyBsb3dlcmNhc2VkKS5cbiAgICAgICAgLy8gTWV0ZXJpbmcgb2ZmIHRoZSByYXcgYHJlcS51c2VyLm9yZ2FuaXphdGlvbklkYCB3aGlsZSB0aGUgZ2F0ZSBjaGVja3MgdGhlXG4gICAgICAgIC8vIG5vcm1hbGl6ZWQgaWQgd291bGQgbGFuZCBpbmNyZW1lbnRzIG9uIGEgZGlmZmVyZW50IGtleSB0aGFuIHRoZSBjaGVja1xuICAgICAgICAvLyByZWFkcyAoYSBzaWxlbnQgYnlwYXNzIGlmIGFuIG9yZyBpZCBldmVyIGNhcnJpZXMgbWl4ZWQgY2FzZSkuIEZhbGwgYmFjayB0b1xuICAgICAgICAvLyB0aGUgcmF3IGF1dGggb3JnIG9ubHkgd2hlbiBjb250ZXh0IG1pZGRsZXdhcmUgaXNuJ3QgbW91bnRlZC5cbiAgICAgICAgbGV0IG9yZ0lkOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gICAgICAgIHRyeSB7IG9yZ0lkID0gZ2V0Q29udGV4dChyZXEpLmlkZW50aXR5Lm9yZ0lkOyB9IGNhdGNoIHsgb3JnSWQgPSByZXEudXNlcj8ub3JnYW5pemF0aW9uSWQ7IH1cbiAgICAgICAgaWYgKCFvcmdJZCkgcmV0dXJuO1xuICAgICAgICAvLyBJbnRlcm5hbCBzZXJ2aWNlLXRvLXNlcnZpY2UgY2FsbHMgbXVzdCBub3QgY29uc3VtZSBhIHRlbmFudCdzIHF1b3RhLlxuICAgICAgICBpZiAoaXNTZXJ2aWNlUHJpbmNpcGFsKHJlcSkpIHJldHVybjtcbiAgICAgICAgaW5jcmVtZW50UXVvdGEoXG4gICAgICAgICAgcXVvdGFTZXJ2aWNlLFxuICAgICAgICAgIG9yZ0lkLFxuICAgICAgICAgIHF1b3RhVHlwZSxcbiAgICAgICAgICByZXEuaGVhZGVycy5hdXRob3JpemF0aW9uIHx8ICcnLFxuICAgICAgICAgIChtZXNzYWdlLCBkYXRhKSA9PiBsb2dnZXIud2FybihtZXNzYWdlLCBkYXRhIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KSxcbiAgICAgICAgKTtcbiAgICAgIH0gY2F0Y2gge1xuICAgICAgICAvLyBNZXRlcmluZyBtdXN0IG5ldmVyIGFmZmVjdCB0aGUgcmVzcG9uc2U7IGEgbWlzc2luZyB1c2VyL2NvbnRleHQganVzdFxuICAgICAgICAvLyBtZWFucyBhbiB1bmF1dGhlbnRpY2F0ZWQgb3IgZWFybHktdGVybWluYXRlZCByZXF1ZXN0IOKAlCBza2lwIHNpbGVudGx5LlxuICAgICAgfVxuICAgIH0pO1xuICAgIG5leHQoKTtcbiAgfTtcbn1cbiJdfQ==
@@ -0,0 +1,47 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ /**
3
+ * Express middleware that records request duration and count.
4
+ *
5
+ * Must be registered before route handlers so `res.on('finish')` fires
6
+ * after the response is sent.
7
+ */
8
+ export declare function metricsMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
9
+ /**
10
+ * Express handler that returns Prometheus metrics in text exposition format.
11
+ */
12
+ export declare function metricsHandler(): (_req: Request, res: Response) => Promise<void>;
13
+ /**
14
+ * Increment a business counter, creating it on first use.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * incCounter('pipelines_generated_total', { provider: 'anthropic' });
19
+ * incCounter('plugin_builds_total', { status: 'completed' });
20
+ * incCounter('quota_threshold_crossed_total', { type: 'aiCalls', tier: 'pro' });
21
+ * incCounter('compliance_violations_found_total', { severity: 'critical' });
22
+ * ```
23
+ */
24
+ export declare function incCounter(name: string, labels?: Record<string, string>, value?: number): void;
25
+ /**
26
+ * Record an observation on a business histogram, creating it on first use.
27
+ * Use for durations, sizes, anything where percentiles matter.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * observe('ai_generation_duration_seconds', { provider: 'anthropic', model: 'sonnet' }, durationSec);
32
+ * observe('plugin_build_duration_seconds', { buildType: 'docker' }, durationSec);
33
+ * ```
34
+ */
35
+ export declare function observe(name: string, labels: Record<string, string>, value: number): void;
36
+ /**
37
+ * Set the value of a business gauge, creating it on first use. Use for
38
+ * point-in-time observations sampled by a scraper — queue depth, active
39
+ * connection counts, in-flight requests.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * setGauge('plugin_queue_jobs', { queue: 'plugin-build', state: 'waiting' }, 12);
44
+ * setGauge('plugin_queue_jobs', { queue: 'plugin-build', state: 'active' }, 3);
45
+ * ```
46
+ */
47
+ export declare function setGauge(name: string, labels: Record<string, string>, value: number): void;