@pipeline-builder/api-server 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +39 -0
- package/lib/api/app-factory.d.ts +71 -0
- package/lib/api/app-factory.js +212 -0
- package/lib/api/check-quota.d.ts +13 -0
- package/lib/api/check-quota.js +67 -0
- package/lib/api/context-middleware.d.ts +34 -0
- package/lib/api/context-middleware.js +45 -0
- package/lib/api/etag-middleware.d.ts +7 -0
- package/lib/api/etag-middleware.js +37 -0
- package/lib/api/get-context.d.ts +22 -0
- package/lib/api/get-context.js +31 -0
- package/lib/api/health-checks.d.ts +25 -0
- package/lib/api/health-checks.js +36 -0
- package/lib/api/idempotency-middleware.d.ts +9 -0
- package/lib/api/idempotency-middleware.js +64 -0
- package/lib/api/index.d.ts +15 -0
- package/lib/api/index.js +43 -0
- package/lib/api/metrics.d.ts +12 -0
- package/lib/api/metrics.js +83 -0
- package/lib/api/middleware-factory.d.ts +47 -0
- package/lib/api/middleware-factory.js +66 -0
- package/lib/api/middleware.d.ts +1 -0
- package/lib/api/middleware.js +14 -0
- package/lib/api/quota-helpers.d.ts +23 -0
- package/lib/api/quota-helpers.js +25 -0
- package/lib/api/request-types.d.ts +55 -0
- package/lib/api/request-types.js +62 -0
- package/lib/api/require-org-id.d.ts +14 -0
- package/lib/api/require-org-id.js +31 -0
- package/lib/api/route-wrapper.d.ts +50 -0
- package/lib/api/route-wrapper.js +62 -0
- package/lib/api/server.d.ts +79 -0
- package/lib/api/server.js +144 -0
- package/lib/api/tracing.d.ts +15 -0
- package/lib/api/tracing.js +53 -0
- package/lib/http/index.d.ts +2 -0
- package/lib/http/index.js +21 -0
- package/lib/http/sse-connection-manager.d.ts +145 -0
- package/lib/http/sse-connection-manager.js +329 -0
- package/lib/http/ws-manager.d.ts +37 -0
- package/lib/http/ws-manager.js +105 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +51 -0
- package/package.json +143 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.checkQuota = checkQuota;
|
|
6
|
+
const api_core_1 = require("@pipeline-builder/api-core");
|
|
7
|
+
const get_context_1 = require("./get-context");
|
|
8
|
+
const logger = (0, api_core_1.createLogger)('check-quota');
|
|
9
|
+
/** Human-readable labels for quota exceeded messages. */
|
|
10
|
+
const QUOTA_LABELS = {
|
|
11
|
+
apiCalls: 'API call',
|
|
12
|
+
pipelines: 'Pipeline',
|
|
13
|
+
plugins: 'Plugin',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create middleware that checks a specific quota type before proceeding.
|
|
17
|
+
*
|
|
18
|
+
* On quota exceeded, returns a 429 response with quota details.
|
|
19
|
+
* On quota service failure, fails open (allows the request).
|
|
20
|
+
*
|
|
21
|
+
* @param quotaService - Quota service client
|
|
22
|
+
* @param quotaType - Which quota to check (e.g. 'apiCalls', 'pipelines', 'plugins')
|
|
23
|
+
* @returns Express middleware
|
|
24
|
+
*/
|
|
25
|
+
function checkQuota(quotaService, quotaType) {
|
|
26
|
+
return async (req, res, next) => {
|
|
27
|
+
let orgId;
|
|
28
|
+
let authHeader = '';
|
|
29
|
+
try {
|
|
30
|
+
const ctx = (0, get_context_1.getContext)(req);
|
|
31
|
+
orgId = ctx.identity.orgId;
|
|
32
|
+
authHeader = req.headers.authorization || '';
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Context middleware not applied — fail open (log and continue)
|
|
36
|
+
logger.warn('Quota check skipped: request context not initialized');
|
|
37
|
+
return next();
|
|
38
|
+
}
|
|
39
|
+
if (!orgId) {
|
|
40
|
+
(0, api_core_1.sendError)(res, 400, 'Organization ID is required for quota check', api_core_1.ErrorCode.VALIDATION_ERROR);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const quotaStatus = await quotaService.check(orgId, quotaType, authHeader);
|
|
45
|
+
if (!quotaStatus.allowed) {
|
|
46
|
+
logger.warn(`${quotaType} quota exceeded`, {
|
|
47
|
+
orgId,
|
|
48
|
+
limit: quotaStatus.limit,
|
|
49
|
+
used: quotaStatus.used,
|
|
50
|
+
});
|
|
51
|
+
(0, api_core_1.sendError)(res, 429, `${QUOTA_LABELS[quotaType]} quota exceeded. Please contact your administrator to increase your quota.`, api_core_1.ErrorCode.QUOTA_EXCEEDED, { quota: { type: quotaType, limit: quotaStatus.limit, used: quotaStatus.used, remaining: quotaStatus.remaining } });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
next();
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Fail open — allow the request if quota service is unavailable
|
|
58
|
+
logger.warn('QUOTA_FAIL_OPEN: Quota check exception, allowing request', {
|
|
59
|
+
orgId,
|
|
60
|
+
quotaType,
|
|
61
|
+
error: error instanceof Error ? error.message : String(error),
|
|
62
|
+
});
|
|
63
|
+
next();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2stcXVvdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL2NoZWNrLXF1b3RhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDOztBQTBCdEMsZ0NBc0RDO0FBOUVELHlEQUFnRjtBQUdoRiwrQ0FBMkM7QUFFM0MsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBWSxFQUFDLGFBQWEsQ0FBQyxDQUFDO0FBRTNDLHlEQUF5RDtBQUN6RCxNQUFNLFlBQVksR0FBOEI7SUFDOUMsUUFBUSxFQUFFLFVBQVU7SUFDcEIsU0FBUyxFQUFFLFVBQVU7SUFDckIsT0FBTyxFQUFFLFFBQVE7Q0FDbEIsQ0FBQztBQUVGOzs7Ozs7Ozs7R0FTRztBQUNILFNBQWdCLFVBQVUsQ0FDeEIsWUFBMEIsRUFDMUIsU0FBb0I7SUFFcEIsT0FBTyxLQUFLLEVBQUUsR0FBWSxFQUFFLEdBQWEsRUFBRSxJQUFrQixFQUFpQixFQUFFO1FBQzlFLElBQUksS0FBeUIsQ0FBQztRQUM5QixJQUFJLFVBQVUsR0FBRyxFQUFFLENBQUM7UUFFcEIsSUFBSSxDQUFDO1lBQ0gsTUFBTSxHQUFHLEdBQUcsSUFBQSx3QkFBVSxFQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzVCLEtBQUssR0FBRyxHQUFHLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztZQUMzQixVQUFVLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxhQUFhLElBQUksRUFBRSxDQUFDO1FBQy9DLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxnRUFBZ0U7WUFDaEUsTUFBTSxDQUFDLElBQUksQ0FBQyxzREFBc0QsQ0FBQyxDQUFDO1lBQ3BFLE9BQU8sSUFBSSxFQUFFLENBQUM7UUFDaEIsQ0FBQztRQUVELElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNYLElBQUEsb0JBQVMsRUFBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLDZDQUE2QyxFQUFFLG9CQUFTLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztZQUMvRixPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILE1BQU0sV0FBVyxHQUFHLE1BQU0sWUFBWSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsU0FBUyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1lBRTNFLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxFQUFFLENBQUM7Z0JBQ3pCLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxTQUFTLGlCQUFpQixFQUFFO29CQUN6QyxLQUFLO29CQUNMLEtBQUssRUFBRSxXQUFXLENBQUMsS0FBSztvQkFDeEIsSUFBSSxFQUFFLFdBQVcsQ0FBQyxJQUFJO2lCQUN2QixDQUFDLENBQUM7Z0JBRUgsSUFBQSxvQkFBUyxFQUNQLEdBQUcsRUFDSCxHQUFHLEVBQ0gsR0FBRyxZQUFZLENBQUMsU0FBUyxDQUFDLDRFQUE0RSxFQUN0RyxvQkFBUyxDQUFDLGNBQWMsRUFDeEIsRUFBRSxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxXQUFXLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxXQUFXLENBQUMsSUFBSSxFQUFFLFNBQVMsRUFBRSxXQUFXLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FDbkgsQ0FBQztnQkFDRixPQUFPO1lBQ1QsQ0FBQztZQUVELElBQUksRUFBRSxDQUFDO1FBQ1QsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixnRUFBZ0U7WUFDaEUsTUFBTSxDQUFDLElBQUksQ0FBQywwREFBMEQsRUFBRTtnQkFDdEUsS0FBSztnQkFDTCxTQUFTO2dCQUNULEtBQUssRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDO2FBQzlELENBQUMsQ0FBQztZQUNILElBQUksRUFBRSxDQUFDO1FBQ1QsQ0FBQztJQUNILENBQUMsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgRXJyb3JDb2RlLCBjcmVhdGVMb2dnZXIsIHNlbmRFcnJvciB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL2FwaS1jb3JlJztcbmltcG9ydCB0eXBlIHsgUXVvdGFUeXBlLCBRdW90YVNlcnZpY2UgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBSZXF1ZXN0LCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBnZXRDb250ZXh0IH0gZnJvbSAnLi9nZXQtY29udGV4dCc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignY2hlY2stcXVvdGEnKTtcblxuLyoqIEh1bWFuLXJlYWRhYmxlIGxhYmVscyBmb3IgcXVvdGEgZXhjZWVkZWQgbWVzc2FnZXMuICovXG5jb25zdCBRVU9UQV9MQUJFTFM6IFJlY29yZDxRdW90YVR5cGUsIHN0cmluZz4gPSB7XG4gIGFwaUNhbGxzOiAnQVBJIGNhbGwnLFxuICBwaXBlbGluZXM6ICdQaXBlbGluZScsXG4gIHBsdWdpbnM6ICdQbHVnaW4nLFxufTtcblxuLyoqXG4gKiBDcmVhdGUgbWlkZGxld2FyZSB0aGF0IGNoZWNrcyBhIHNwZWNpZmljIHF1b3RhIHR5cGUgYmVmb3JlIHByb2NlZWRpbmcuXG4gKlxuICogT24gcXVvdGEgZXhjZWVkZWQsIHJldHVybnMgYSA0MjkgcmVzcG9uc2Ugd2l0aCBxdW90YSBkZXRhaWxzLlxuICogT24gcXVvdGEgc2VydmljZSBmYWlsdXJlLCBmYWlscyBvcGVuIChhbGxvd3MgdGhlIHJlcXVlc3QpLlxuICpcbiAqIEBwYXJhbSBxdW90YVNlcnZpY2UgLSBRdW90YSBzZXJ2aWNlIGNsaWVudFxuICogQHBhcmFtIHF1b3RhVHlwZSAtIFdoaWNoIHF1b3RhIHRvIGNoZWNrIChlLmcuICdhcGlDYWxscycsICdwaXBlbGluZXMnLCAncGx1Z2lucycpXG4gKiBAcmV0dXJucyBFeHByZXNzIG1pZGRsZXdhcmVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNoZWNrUXVvdGEoXG4gIHF1b3RhU2VydmljZTogUXVvdGFTZXJ2aWNlLFxuICBxdW90YVR5cGU6IFF1b3RhVHlwZSxcbikge1xuICByZXR1cm4gYXN5bmMgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgbGV0IG9yZ0lkOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gICAgbGV0IGF1dGhIZWFkZXIgPSAnJztcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCBjdHggPSBnZXRDb250ZXh0KHJlcSk7XG4gICAgICBvcmdJZCA9IGN0eC5pZGVudGl0eS5vcmdJZDtcbiAgICAgIGF1dGhIZWFkZXIgPSByZXEuaGVhZGVycy5hdXRob3JpemF0aW9uIHx8ICcnO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gQ29udGV4dCBtaWRkbGV3YXJlIG5vdCBhcHBsaWVkIOKAlCBmYWlsIG9wZW4gKGxvZyBhbmQgY29udGludWUpXG4gICAgICBsb2dnZXIud2FybignUXVvdGEgY2hlY2sgc2tpcHBlZDogcmVxdWVzdCBjb250ZXh0IG5vdCBpbml0aWFsaXplZCcpO1xuICAgICAgcmV0dXJuIG5leHQoKTtcbiAgICB9XG5cbiAgICBpZiAoIW9yZ0lkKSB7XG4gICAgICBzZW5kRXJyb3IocmVzLCA0MDAsICdPcmdhbml6YXRpb24gSUQgaXMgcmVxdWlyZWQgZm9yIHF1b3RhIGNoZWNrJywgRXJyb3JDb2RlLlZBTElEQVRJT05fRVJST1IpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICBjb25zdCBxdW90YVN0YXR1cyA9IGF3YWl0IHF1b3RhU2VydmljZS5jaGVjayhvcmdJZCwgcXVvdGFUeXBlLCBhdXRoSGVhZGVyKTtcblxuICAgICAgaWYgKCFxdW90YVN0YXR1cy5hbGxvd2VkKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGAke3F1b3RhVHlwZX0gcXVvdGEgZXhjZWVkZWRgLCB7XG4gICAgICAgICAgb3JnSWQsXG4gICAgICAgICAgbGltaXQ6IHF1b3RhU3RhdHVzLmxpbWl0LFxuICAgICAgICAgIHVzZWQ6IHF1b3RhU3RhdHVzLnVzZWQsXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHNlbmRFcnJvcihcbiAgICAgICAgICByZXMsXG4gICAgICAgICAgNDI5LFxuICAgICAgICAgIGAke1FVT1RBX0xBQkVMU1txdW90YVR5cGVdfSBxdW90YSBleGNlZWRlZC4gUGxlYXNlIGNvbnRhY3QgeW91ciBhZG1pbmlzdHJhdG9yIHRvIGluY3JlYXNlIHlvdXIgcXVvdGEuYCxcbiAgICAgICAgICBFcnJvckNvZGUuUVVPVEFfRVhDRUVERUQsXG4gICAgICAgICAgeyBxdW90YTogeyB0eXBlOiBxdW90YVR5cGUsIGxpbWl0OiBxdW90YVN0YXR1cy5saW1pdCwgdXNlZDogcXVvdGFTdGF0dXMudXNlZCwgcmVtYWluaW5nOiBxdW90YVN0YXR1cy5yZW1haW5pbmcgfSB9LFxuICAgICAgICApO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIG5leHQoKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gRmFpbCBvcGVuIOKAlCBhbGxvdyB0aGUgcmVxdWVzdCBpZiBxdW90YSBzZXJ2aWNlIGlzIHVuYXZhaWxhYmxlXG4gICAgICBsb2dnZXIud2FybignUVVPVEFfRkFJTF9PUEVOOiBRdW90YSBjaGVjayBleGNlcHRpb24sIGFsbG93aW5nIHJlcXVlc3QnLCB7XG4gICAgICAgIG9yZ0lkLFxuICAgICAgICBxdW90YVR5cGUsXG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvciksXG4gICAgICB9KTtcbiAgICAgIG5leHQoKTtcbiAgICB9XG4gIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
import type { SSEManager } from '../http/sse-connection-manager';
|
|
3
|
+
/**
|
|
4
|
+
* Creates middleware that attaches RequestContext to req.context
|
|
5
|
+
*
|
|
6
|
+
* The context includes:
|
|
7
|
+
* - requestId: Unique request identifier
|
|
8
|
+
* - identity: User identity from JWT (orgId, userId, role)
|
|
9
|
+
* - log: Logger function that sends to both Winston and SSE
|
|
10
|
+
*
|
|
11
|
+
* @param sseManager - SSE manager for real-time logging
|
|
12
|
+
* @returns Express middleware that attaches context to req.context
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const { app, sseManager } = createApp();
|
|
17
|
+
*
|
|
18
|
+
* // Apply context middleware globally
|
|
19
|
+
* app.use(attachRequestContext(sseManager));
|
|
20
|
+
*
|
|
21
|
+
* // Use context in route handlers
|
|
22
|
+
* app.get('/pipelines', requireAuth, async (req, res) => {
|
|
23
|
+
* const ctx = getContext(req);
|
|
24
|
+
* ctx.log('INFO', 'Fetching pipelines');
|
|
25
|
+
*
|
|
26
|
+
* if (!ctx.identity.orgId) {
|
|
27
|
+
* return sendError(res, 400, 'Organization ID required');
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // ... rest of handler
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function attachRequestContext(sseManager: SSEManager): RequestHandler;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.attachRequestContext = attachRequestContext;
|
|
6
|
+
const request_types_1 = require("./request-types");
|
|
7
|
+
/**
|
|
8
|
+
* Creates middleware that attaches RequestContext to req.context
|
|
9
|
+
*
|
|
10
|
+
* The context includes:
|
|
11
|
+
* - requestId: Unique request identifier
|
|
12
|
+
* - identity: User identity from JWT (orgId, userId, role)
|
|
13
|
+
* - log: Logger function that sends to both Winston and SSE
|
|
14
|
+
*
|
|
15
|
+
* @param sseManager - SSE manager for real-time logging
|
|
16
|
+
* @returns Express middleware that attaches context to req.context
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const { app, sseManager } = createApp();
|
|
21
|
+
*
|
|
22
|
+
* // Apply context middleware globally
|
|
23
|
+
* app.use(attachRequestContext(sseManager));
|
|
24
|
+
*
|
|
25
|
+
* // Use context in route handlers
|
|
26
|
+
* app.get('/pipelines', requireAuth, async (req, res) => {
|
|
27
|
+
* const ctx = getContext(req);
|
|
28
|
+
* ctx.log('INFO', 'Fetching pipelines');
|
|
29
|
+
*
|
|
30
|
+
* if (!ctx.identity.orgId) {
|
|
31
|
+
* return sendError(res, 400, 'Organization ID required');
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* // ... rest of handler
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function attachRequestContext(sseManager) {
|
|
39
|
+
return (req, _res, next) => {
|
|
40
|
+
// Create and attach context to request
|
|
41
|
+
req.context = (0, request_types_1.createRequestContext)(req, sseManager);
|
|
42
|
+
next();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS9jb250ZXh0LW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7O0FBcUN0QyxvREFNQztBQXhDRCxtREFBdUQ7QUFHdkQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQThCRztBQUNILFNBQWdCLG9CQUFvQixDQUFDLFVBQXNCO0lBQ3pELE9BQU8sQ0FBQyxHQUFZLEVBQUUsSUFBYyxFQUFFLElBQWtCLEVBQVEsRUFBRTtRQUNoRSx1Q0FBdUM7UUFDdkMsR0FBRyxDQUFDLE9BQU8sR0FBRyxJQUFBLG9DQUFvQixFQUFDLEdBQUcsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNwRCxJQUFJLEVBQUUsQ0FBQztJQUNULENBQUMsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgUmVxdWVzdCwgUmVzcG9uc2UsIE5leHRGdW5jdGlvbiwgUmVxdWVzdEhhbmRsZXIgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IGNyZWF0ZVJlcXVlc3RDb250ZXh0IH0gZnJvbSAnLi9yZXF1ZXN0LXR5cGVzJztcbmltcG9ydCB0eXBlIHsgU1NFTWFuYWdlciB9IGZyb20gJy4uL2h0dHAvc3NlLWNvbm5lY3Rpb24tbWFuYWdlcic7XG5cbi8qKlxuICogQ3JlYXRlcyBtaWRkbGV3YXJlIHRoYXQgYXR0YWNoZXMgUmVxdWVzdENvbnRleHQgdG8gcmVxLmNvbnRleHRcbiAqXG4gKiBUaGUgY29udGV4dCBpbmNsdWRlczpcbiAqIC0gcmVxdWVzdElkOiBVbmlxdWUgcmVxdWVzdCBpZGVudGlmaWVyXG4gKiAtIGlkZW50aXR5OiBVc2VyIGlkZW50aXR5IGZyb20gSldUIChvcmdJZCwgdXNlcklkLCByb2xlKVxuICogLSBsb2c6IExvZ2dlciBmdW5jdGlvbiB0aGF0IHNlbmRzIHRvIGJvdGggV2luc3RvbiBhbmQgU1NFXG4gKlxuICogQHBhcmFtIHNzZU1hbmFnZXIgLSBTU0UgbWFuYWdlciBmb3IgcmVhbC10aW1lIGxvZ2dpbmdcbiAqIEByZXR1cm5zIEV4cHJlc3MgbWlkZGxld2FyZSB0aGF0IGF0dGFjaGVzIGNvbnRleHQgdG8gcmVxLmNvbnRleHRcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogY29uc3QgeyBhcHAsIHNzZU1hbmFnZXIgfSA9IGNyZWF0ZUFwcCgpO1xuICpcbiAqIC8vIEFwcGx5IGNvbnRleHQgbWlkZGxld2FyZSBnbG9iYWxseVxuICogYXBwLnVzZShhdHRhY2hSZXF1ZXN0Q29udGV4dChzc2VNYW5hZ2VyKSk7XG4gKlxuICogLy8gVXNlIGNvbnRleHQgaW4gcm91dGUgaGFuZGxlcnNcbiAqIGFwcC5nZXQoJy9waXBlbGluZXMnLCByZXF1aXJlQXV0aCwgYXN5bmMgKHJlcSwgcmVzKSA9PiB7XG4gKiAgIGNvbnN0IGN0eCA9IGdldENvbnRleHQocmVxKTtcbiAqICAgY3R4LmxvZygnSU5GTycsICdGZXRjaGluZyBwaXBlbGluZXMnKTtcbiAqXG4gKiAgIGlmICghY3R4LmlkZW50aXR5Lm9yZ0lkKSB7XG4gKiAgICAgcmV0dXJuIHNlbmRFcnJvcihyZXMsIDQwMCwgJ09yZ2FuaXphdGlvbiBJRCByZXF1aXJlZCcpO1xuICogICB9XG4gKlxuICogICAvLyAuLi4gcmVzdCBvZiBoYW5kbGVyXG4gKiB9KTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gYXR0YWNoUmVxdWVzdENvbnRleHQoc3NlTWFuYWdlcjogU1NFTWFuYWdlcik6IFJlcXVlc3RIYW5kbGVyIHtcbiAgcmV0dXJuIChyZXE6IFJlcXVlc3QsIF9yZXM6IFJlc3BvbnNlLCBuZXh0OiBOZXh0RnVuY3Rpb24pOiB2b2lkID0+IHtcbiAgICAvLyBDcmVhdGUgYW5kIGF0dGFjaCBjb250ZXh0IHRvIHJlcXVlc3RcbiAgICByZXEuY29udGV4dCA9IGNyZWF0ZVJlcXVlc3RDb250ZXh0KHJlcSwgc3NlTWFuYWdlcik7XG4gICAgbmV4dCgpO1xuICB9O1xufVxuIl19
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware that adds ETag support for conditional GET requests.
|
|
4
|
+
* Generates a weak ETag from the response body hash.
|
|
5
|
+
* Returns 304 Not Modified when the client's If-None-Match header matches.
|
|
6
|
+
*/
|
|
7
|
+
export declare function etagMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.etagMiddleware = etagMiddleware;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
/**
|
|
8
|
+
* Middleware that adds ETag support for conditional GET requests.
|
|
9
|
+
* Generates a weak ETag from the response body hash.
|
|
10
|
+
* Returns 304 Not Modified when the client's If-None-Match header matches.
|
|
11
|
+
*/
|
|
12
|
+
function etagMiddleware() {
|
|
13
|
+
return (req, res, next) => {
|
|
14
|
+
// Only apply to GET requests
|
|
15
|
+
if (req.method !== 'GET')
|
|
16
|
+
return next();
|
|
17
|
+
res.json = (body) => {
|
|
18
|
+
// Serialize once, hash, and send — avoids double JSON.stringify
|
|
19
|
+
const serialized = JSON.stringify(body);
|
|
20
|
+
const hash = (0, crypto_1.createHash)('md5').update(serialized).digest('hex').slice(0, 16);
|
|
21
|
+
const etag = `W/"${hash}"`;
|
|
22
|
+
res.setHeader('ETag', etag);
|
|
23
|
+
// Check If-None-Match header
|
|
24
|
+
const ifNoneMatch = req.headers['if-none-match'];
|
|
25
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
26
|
+
res.status(304).end();
|
|
27
|
+
return res;
|
|
28
|
+
}
|
|
29
|
+
// Send pre-serialized body to avoid double JSON.stringify
|
|
30
|
+
res.setHeader('Content-Type', 'application/json');
|
|
31
|
+
res.end(serialized);
|
|
32
|
+
return res;
|
|
33
|
+
};
|
|
34
|
+
next();
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXRhZy1taWRkbGV3YXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS9ldGFnLW1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7O0FBVXRDLHdDQTRCQztBQXBDRCxtQ0FBb0M7QUFHcEM7Ozs7R0FJRztBQUNILFNBQWdCLGNBQWM7SUFDNUIsT0FBTyxDQUFDLEdBQVksRUFBRSxHQUFhLEVBQUUsSUFBa0IsRUFBRSxFQUFFO1FBQ3pELDZCQUE2QjtRQUM3QixJQUFJLEdBQUcsQ0FBQyxNQUFNLEtBQUssS0FBSztZQUFFLE9BQU8sSUFBSSxFQUFFLENBQUM7UUFFeEMsR0FBRyxDQUFDLElBQUksR0FBRyxDQUFDLElBQWEsRUFBRSxFQUFFO1lBQzNCLGdFQUFnRTtZQUNoRSxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3hDLE1BQU0sSUFBSSxHQUFHLElBQUEsbUJBQVUsRUFBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDN0UsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLEdBQUcsQ0FBQztZQUUzQixHQUFHLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztZQUU1Qiw2QkFBNkI7WUFDN0IsTUFBTSxXQUFXLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztZQUNqRCxJQUFJLFdBQVcsSUFBSSxXQUFXLEtBQUssSUFBSSxFQUFFLENBQUM7Z0JBQ3hDLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUM7Z0JBQ3RCLE9BQU8sR0FBRyxDQUFDO1lBQ2IsQ0FBQztZQUVELDBEQUEwRDtZQUMxRCxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1lBQ2xELEdBQUcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDcEIsT0FBTyxHQUFHLENBQUM7UUFDYixDQUFDLENBQUM7UUFFRixJQUFJLEVBQUUsQ0FBQztJQUNULENBQUMsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgY3JlYXRlSGFzaCB9IGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgeyBSZXF1ZXN0LCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5cbi8qKlxuICogTWlkZGxld2FyZSB0aGF0IGFkZHMgRVRhZyBzdXBwb3J0IGZvciBjb25kaXRpb25hbCBHRVQgcmVxdWVzdHMuXG4gKiBHZW5lcmF0ZXMgYSB3ZWFrIEVUYWcgZnJvbSB0aGUgcmVzcG9uc2UgYm9keSBoYXNoLlxuICogUmV0dXJucyAzMDQgTm90IE1vZGlmaWVkIHdoZW4gdGhlIGNsaWVudCdzIElmLU5vbmUtTWF0Y2ggaGVhZGVyIG1hdGNoZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBldGFnTWlkZGxld2FyZSgpIHtcbiAgcmV0dXJuIChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UsIG5leHQ6IE5leHRGdW5jdGlvbikgPT4ge1xuICAgIC8vIE9ubHkgYXBwbHkgdG8gR0VUIHJlcXVlc3RzXG4gICAgaWYgKHJlcS5tZXRob2QgIT09ICdHRVQnKSByZXR1cm4gbmV4dCgpO1xuXG4gICAgcmVzLmpzb24gPSAoYm9keTogdW5rbm93bikgPT4ge1xuICAgICAgLy8gU2VyaWFsaXplIG9uY2UsIGhhc2gsIGFuZCBzZW5kIOKAlCBhdm9pZHMgZG91YmxlIEpTT04uc3RyaW5naWZ5XG4gICAgICBjb25zdCBzZXJpYWxpemVkID0gSlNPTi5zdHJpbmdpZnkoYm9keSk7XG4gICAgICBjb25zdCBoYXNoID0gY3JlYXRlSGFzaCgnbWQ1JykudXBkYXRlKHNlcmlhbGl6ZWQpLmRpZ2VzdCgnaGV4Jykuc2xpY2UoMCwgMTYpO1xuICAgICAgY29uc3QgZXRhZyA9IGBXL1wiJHtoYXNofVwiYDtcblxuICAgICAgcmVzLnNldEhlYWRlcignRVRhZycsIGV0YWcpO1xuXG4gICAgICAvLyBDaGVjayBJZi1Ob25lLU1hdGNoIGhlYWRlclxuICAgICAgY29uc3QgaWZOb25lTWF0Y2ggPSByZXEuaGVhZGVyc1snaWYtbm9uZS1tYXRjaCddO1xuICAgICAgaWYgKGlmTm9uZU1hdGNoICYmIGlmTm9uZU1hdGNoID09PSBldGFnKSB7XG4gICAgICAgIHJlcy5zdGF0dXMoMzA0KS5lbmQoKTtcbiAgICAgICAgcmV0dXJuIHJlcztcbiAgICAgIH1cblxuICAgICAgLy8gU2VuZCBwcmUtc2VyaWFsaXplZCBib2R5IHRvIGF2b2lkIGRvdWJsZSBKU09OLnN0cmluZ2lmeVxuICAgICAgcmVzLnNldEhlYWRlcignQ29udGVudC1UeXBlJywgJ2FwcGxpY2F0aW9uL2pzb24nKTtcbiAgICAgIHJlcy5lbmQoc2VyaWFsaXplZCk7XG4gICAgICByZXR1cm4gcmVzO1xuICAgIH07XG5cbiAgICBuZXh0KCk7XG4gIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Request } from 'express';
|
|
2
|
+
import type { RequestContext } from './request-types';
|
|
3
|
+
/**
|
|
4
|
+
* Safely retrieve the RequestContext from an Express request.
|
|
5
|
+
*
|
|
6
|
+
* Throws a descriptive error if the context middleware has not been applied,
|
|
7
|
+
* instead of silently returning undefined or crashing with a cryptic error.
|
|
8
|
+
*
|
|
9
|
+
* @param req - Express request object
|
|
10
|
+
* @returns The attached RequestContext
|
|
11
|
+
* @throws Error if req.context is not initialized
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* app.get('/pipelines', requireAuth, async (req, res) => {
|
|
16
|
+
* const ctx = getContext(req);
|
|
17
|
+
* ctx.log('INFO', 'Fetching pipelines');
|
|
18
|
+
* // ...
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function getContext(req: Request): RequestContext;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.getContext = getContext;
|
|
6
|
+
/**
|
|
7
|
+
* Safely retrieve the RequestContext from an Express request.
|
|
8
|
+
*
|
|
9
|
+
* Throws a descriptive error if the context middleware has not been applied,
|
|
10
|
+
* instead of silently returning undefined or crashing with a cryptic error.
|
|
11
|
+
*
|
|
12
|
+
* @param req - Express request object
|
|
13
|
+
* @returns The attached RequestContext
|
|
14
|
+
* @throws Error if req.context is not initialized
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* app.get('/pipelines', requireAuth, async (req, res) => {
|
|
19
|
+
* const ctx = getContext(req);
|
|
20
|
+
* ctx.log('INFO', 'Fetching pipelines');
|
|
21
|
+
* // ...
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function getContext(req) {
|
|
26
|
+
if (!req.context) {
|
|
27
|
+
throw new Error('Request context not initialized. Ensure attachRequestContext middleware is applied.');
|
|
28
|
+
}
|
|
29
|
+
return req.context;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2V0LWNvbnRleHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL2dldC1jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDOztBQXdCdEMsZ0NBT0M7QUExQkQ7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQWdCLFVBQVUsQ0FBQyxHQUFZO0lBQ3JDLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDakIsTUFBTSxJQUFJLEtBQUssQ0FDYixxRkFBcUYsQ0FDdEYsQ0FBQztJQUNKLENBQUM7SUFDRCxPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUM7QUFDckIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdENvbnRleHQgfSBmcm9tICcuL3JlcXVlc3QtdHlwZXMnO1xuXG4vKipcbiAqIFNhZmVseSByZXRyaWV2ZSB0aGUgUmVxdWVzdENvbnRleHQgZnJvbSBhbiBFeHByZXNzIHJlcXVlc3QuXG4gKlxuICogVGhyb3dzIGEgZGVzY3JpcHRpdmUgZXJyb3IgaWYgdGhlIGNvbnRleHQgbWlkZGxld2FyZSBoYXMgbm90IGJlZW4gYXBwbGllZCxcbiAqIGluc3RlYWQgb2Ygc2lsZW50bHkgcmV0dXJuaW5nIHVuZGVmaW5lZCBvciBjcmFzaGluZyB3aXRoIGEgY3J5cHRpYyBlcnJvci5cbiAqXG4gKiBAcGFyYW0gcmVxIC0gRXhwcmVzcyByZXF1ZXN0IG9iamVjdFxuICogQHJldHVybnMgVGhlIGF0dGFjaGVkIFJlcXVlc3RDb250ZXh0XG4gKiBAdGhyb3dzIEVycm9yIGlmIHJlcS5jb250ZXh0IGlzIG5vdCBpbml0aWFsaXplZFxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBhcHAuZ2V0KCcvcGlwZWxpbmVzJywgcmVxdWlyZUF1dGgsIGFzeW5jIChyZXEsIHJlcykgPT4ge1xuICogICBjb25zdCBjdHggPSBnZXRDb250ZXh0KHJlcSk7XG4gKiAgIGN0eC5sb2coJ0lORk8nLCAnRmV0Y2hpbmcgcGlwZWxpbmVzJyk7XG4gKiAgIC8vIC4uLlxuICogfSk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldENvbnRleHQocmVxOiBSZXF1ZXN0KTogUmVxdWVzdENvbnRleHQge1xuICBpZiAoIXJlcS5jb250ZXh0KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgJ1JlcXVlc3QgY29udGV4dCBub3QgaW5pdGlhbGl6ZWQuIEVuc3VyZSBhdHRhY2hSZXF1ZXN0Q29udGV4dCBtaWRkbGV3YXJlIGlzIGFwcGxpZWQuJyxcbiAgICApO1xuICB9XG4gIHJldHVybiByZXEuY29udGV4dDtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type HealthStatus = 'connected' | 'disconnected' | 'unknown';
|
|
2
|
+
type HealthResult = Record<string, HealthStatus>;
|
|
3
|
+
/**
|
|
4
|
+
* Postgres health probe: returns `{ postgres: 'connected' }` when the pool's
|
|
5
|
+
* connection test succeeds, `{ postgres: 'unknown' }` otherwise. Use as the
|
|
6
|
+
* `checkDependencies` option of `createApp()` for any service backed by the
|
|
7
|
+
* shared pipeline-data Drizzle connection.
|
|
8
|
+
*/
|
|
9
|
+
export declare function postgresHealthCheck(): Promise<HealthResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Minimal shape expected of a mongoose connection for the health probe.
|
|
12
|
+
* Accepts `mongoose.connection` directly without requiring mongoose as a
|
|
13
|
+
* dependency of api-server.
|
|
14
|
+
*/
|
|
15
|
+
interface MongooseConnectionLike {
|
|
16
|
+
readyState: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* MongoDB health probe factory: pass `mongoose.connection` and get back a
|
|
20
|
+
* `checkDependencies` callback that reports `{ mongodb: 'connected' |
|
|
21
|
+
* 'disconnected' | 'unknown' }` based on mongoose's readyState. Use for
|
|
22
|
+
* services backed by mongoose.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mongoHealthCheck(connection: MongooseConnectionLike): () => Promise<HealthResult>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.postgresHealthCheck = postgresHealthCheck;
|
|
6
|
+
exports.mongoHealthCheck = mongoHealthCheck;
|
|
7
|
+
const pipeline_core_1 = require("@pipeline-builder/pipeline-core");
|
|
8
|
+
/**
|
|
9
|
+
* Postgres health probe: returns `{ postgres: 'connected' }` when the pool's
|
|
10
|
+
* connection test succeeds, `{ postgres: 'unknown' }` otherwise. Use as the
|
|
11
|
+
* `checkDependencies` option of `createApp()` for any service backed by the
|
|
12
|
+
* shared pipeline-data Drizzle connection.
|
|
13
|
+
*/
|
|
14
|
+
async function postgresHealthCheck() {
|
|
15
|
+
try {
|
|
16
|
+
await (0, pipeline_core_1.getConnection)().testConnection();
|
|
17
|
+
return { postgres: 'connected' };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { postgres: 'unknown' };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* MongoDB health probe factory: pass `mongoose.connection` and get back a
|
|
25
|
+
* `checkDependencies` callback that reports `{ mongodb: 'connected' |
|
|
26
|
+
* 'disconnected' | 'unknown' }` based on mongoose's readyState. Use for
|
|
27
|
+
* services backed by mongoose.
|
|
28
|
+
*/
|
|
29
|
+
function mongoHealthCheck(connection) {
|
|
30
|
+
return async () => ({
|
|
31
|
+
mongodb: connection.readyState === 1 ? 'connected'
|
|
32
|
+
: connection.readyState === 0 ? 'unknown' // starting up
|
|
33
|
+
: 'disconnected',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVhbHRoLWNoZWNrcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvaGVhbHRoLWNoZWNrcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUFhdEMsa0RBT0M7QUFpQkQsNENBTUM7QUF6Q0QsbUVBQWdFO0FBS2hFOzs7OztHQUtHO0FBQ0ksS0FBSyxVQUFVLG1CQUFtQjtJQUN2QyxJQUFJLENBQUM7UUFDSCxNQUFNLElBQUEsNkJBQWEsR0FBRSxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3ZDLE9BQU8sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLENBQUM7SUFDbkMsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLENBQUM7SUFDakMsQ0FBQztBQUNILENBQUM7QUFXRDs7Ozs7R0FLRztBQUNILFNBQWdCLGdCQUFnQixDQUFDLFVBQWtDO0lBQ2pFLE9BQU8sS0FBSyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ2xCLE9BQU8sRUFBRSxVQUFVLENBQUMsVUFBVSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVztZQUNoRCxDQUFDLENBQUMsVUFBVSxDQUFDLFVBQVUsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxjQUFjO2dCQUN0RCxDQUFDLENBQUMsY0FBYztLQUNyQixDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGdldENvbm5lY3Rpb24gfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9waXBlbGluZS1jb3JlJztcblxudHlwZSBIZWFsdGhTdGF0dXMgPSAnY29ubmVjdGVkJyB8ICdkaXNjb25uZWN0ZWQnIHwgJ3Vua25vd24nO1xudHlwZSBIZWFsdGhSZXN1bHQgPSBSZWNvcmQ8c3RyaW5nLCBIZWFsdGhTdGF0dXM+O1xuXG4vKipcbiAqIFBvc3RncmVzIGhlYWx0aCBwcm9iZTogcmV0dXJucyBgeyBwb3N0Z3JlczogJ2Nvbm5lY3RlZCcgfWAgd2hlbiB0aGUgcG9vbCdzXG4gKiBjb25uZWN0aW9uIHRlc3Qgc3VjY2VlZHMsIGB7IHBvc3RncmVzOiAndW5rbm93bicgfWAgb3RoZXJ3aXNlLiBVc2UgYXMgdGhlXG4gKiBgY2hlY2tEZXBlbmRlbmNpZXNgIG9wdGlvbiBvZiBgY3JlYXRlQXBwKClgIGZvciBhbnkgc2VydmljZSBiYWNrZWQgYnkgdGhlXG4gKiBzaGFyZWQgcGlwZWxpbmUtZGF0YSBEcml6emxlIGNvbm5lY3Rpb24uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBwb3N0Z3Jlc0hlYWx0aENoZWNrKCk6IFByb21pc2U8SGVhbHRoUmVzdWx0PiB7XG4gIHRyeSB7XG4gICAgYXdhaXQgZ2V0Q29ubmVjdGlvbigpLnRlc3RDb25uZWN0aW9uKCk7XG4gICAgcmV0dXJuIHsgcG9zdGdyZXM6ICdjb25uZWN0ZWQnIH07XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB7IHBvc3RncmVzOiAndW5rbm93bicgfTtcbiAgfVxufVxuXG4vKipcbiAqIE1pbmltYWwgc2hhcGUgZXhwZWN0ZWQgb2YgYSBtb25nb29zZSBjb25uZWN0aW9uIGZvciB0aGUgaGVhbHRoIHByb2JlLlxuICogQWNjZXB0cyBgbW9uZ29vc2UuY29ubmVjdGlvbmAgZGlyZWN0bHkgd2l0aG91dCByZXF1aXJpbmcgbW9uZ29vc2UgYXMgYVxuICogZGVwZW5kZW5jeSBvZiBhcGktc2VydmVyLlxuICovXG5pbnRlcmZhY2UgTW9uZ29vc2VDb25uZWN0aW9uTGlrZSB7XG4gIHJlYWR5U3RhdGU6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBNb25nb0RCIGhlYWx0aCBwcm9iZSBmYWN0b3J5OiBwYXNzIGBtb25nb29zZS5jb25uZWN0aW9uYCBhbmQgZ2V0IGJhY2sgYVxuICogYGNoZWNrRGVwZW5kZW5jaWVzYCBjYWxsYmFjayB0aGF0IHJlcG9ydHMgYHsgbW9uZ29kYjogJ2Nvbm5lY3RlZCcgfFxuICogJ2Rpc2Nvbm5lY3RlZCcgfCAndW5rbm93bicgfWAgYmFzZWQgb24gbW9uZ29vc2UncyByZWFkeVN0YXRlLiBVc2UgZm9yXG4gKiBzZXJ2aWNlcyBiYWNrZWQgYnkgbW9uZ29vc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBtb25nb0hlYWx0aENoZWNrKGNvbm5lY3Rpb246IE1vbmdvb3NlQ29ubmVjdGlvbkxpa2UpOiAoKSA9PiBQcm9taXNlPEhlYWx0aFJlc3VsdD4ge1xuICByZXR1cm4gYXN5bmMgKCkgPT4gKHtcbiAgICBtb25nb2RiOiBjb25uZWN0aW9uLnJlYWR5U3RhdGUgPT09IDEgPyAnY29ubmVjdGVkJ1xuICAgICAgOiBjb25uZWN0aW9uLnJlYWR5U3RhdGUgPT09IDAgPyAndW5rbm93bicgLy8gc3RhcnRpbmcgdXBcbiAgICAgICAgOiAnZGlzY29ubmVjdGVkJyxcbiAgfSk7XG59XG4iXX0=
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware that supports idempotency keys for POST/PUT/DELETE mutations.
|
|
4
|
+
*
|
|
5
|
+
* When a request includes the `Idempotency-Key` header:
|
|
6
|
+
* - First call: processes normally, caches the response
|
|
7
|
+
* - Subsequent calls with same key: returns cached response (prevents duplicate mutations)
|
|
8
|
+
*/
|
|
9
|
+
export declare function idempotencyMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.idempotencyMiddleware = idempotencyMiddleware;
|
|
6
|
+
const api_core_1 = require("@pipeline-builder/api-core");
|
|
7
|
+
const pipeline_core_1 = require("@pipeline-builder/pipeline-core");
|
|
8
|
+
const logger = (0, api_core_1.createLogger)('Idempotency');
|
|
9
|
+
/** In-memory store for idempotency results (TTL-based). */
|
|
10
|
+
const store = new Map();
|
|
11
|
+
// Periodic cleanup of expired entries
|
|
12
|
+
const cleanupTimer = setInterval(() => {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
for (const [key, entry] of store) {
|
|
15
|
+
if (now > entry.expiresAt)
|
|
16
|
+
store.delete(key);
|
|
17
|
+
}
|
|
18
|
+
}, pipeline_core_1.CoreConstants.IDEMPOTENCY_CLEANUP_INTERVAL_MS);
|
|
19
|
+
cleanupTimer.unref();
|
|
20
|
+
/**
|
|
21
|
+
* Middleware that supports idempotency keys for POST/PUT/DELETE mutations.
|
|
22
|
+
*
|
|
23
|
+
* When a request includes the `Idempotency-Key` header:
|
|
24
|
+
* - First call: processes normally, caches the response
|
|
25
|
+
* - Subsequent calls with same key: returns cached response (prevents duplicate mutations)
|
|
26
|
+
*/
|
|
27
|
+
function idempotencyMiddleware() {
|
|
28
|
+
return (req, res, next) => {
|
|
29
|
+
const key = req.headers['idempotency-key'];
|
|
30
|
+
if (!key)
|
|
31
|
+
return next();
|
|
32
|
+
// Only apply to mutation methods
|
|
33
|
+
if (!['POST', 'PUT', 'DELETE'].includes(req.method))
|
|
34
|
+
return next();
|
|
35
|
+
// Namespace by orgId to prevent cross-org cache collisions
|
|
36
|
+
const orgId = req.context?.identity?.orgId || req.user?.organizationId;
|
|
37
|
+
if (!orgId)
|
|
38
|
+
return next(); // skip idempotency for unauthenticated requests
|
|
39
|
+
const fullKey = `${orgId}:${key}`;
|
|
40
|
+
// Check for cached response
|
|
41
|
+
const cached = store.get(fullKey);
|
|
42
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
43
|
+
logger.debug('Idempotent request replayed', { key: fullKey });
|
|
44
|
+
res.setHeader('X-Idempotent-Replayed', 'true');
|
|
45
|
+
res.status(cached.statusCode).json(cached.body);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Intercept res.json to cache the response
|
|
49
|
+
const originalJson = res.json.bind(res);
|
|
50
|
+
res.json = (body) => {
|
|
51
|
+
res.setHeader('X-Idempotent-Replayed', 'false');
|
|
52
|
+
if (res.statusCode >= 200 && res.statusCode < 300 && store.size < pipeline_core_1.CoreConstants.IDEMPOTENCY_MAX_STORE_SIZE) {
|
|
53
|
+
store.set(fullKey, {
|
|
54
|
+
statusCode: res.statusCode,
|
|
55
|
+
body,
|
|
56
|
+
expiresAt: Date.now() + pipeline_core_1.CoreConstants.IDEMPOTENCY_TTL_MS,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return originalJson(body);
|
|
60
|
+
};
|
|
61
|
+
next();
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWRlbXBvdGVuY3ktbWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvaWRlbXBvdGVuY3ktbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUEyQnRDLHNEQXNDQztBQS9ERCx5REFBMEQ7QUFDMUQsbUVBQWdFO0FBR2hFLE1BQU0sTUFBTSxHQUFHLElBQUEsdUJBQVksRUFBQyxhQUFhLENBQUMsQ0FBQztBQUUzQywyREFBMkQ7QUFDM0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLEVBQW9FLENBQUM7QUFFMUYsc0NBQXNDO0FBQ3RDLE1BQU0sWUFBWSxHQUFHLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDcEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ3ZCLEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUNqQyxJQUFJLEdBQUcsR0FBRyxLQUFLLENBQUMsU0FBUztZQUFFLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDL0MsQ0FBQztBQUNILENBQUMsRUFBRSw2QkFBYSxDQUFDLCtCQUErQixDQUFDLENBQUM7QUFDbEQsWUFBWSxDQUFDLEtBQUssRUFBRSxDQUFDO0FBRXJCOzs7Ozs7R0FNRztBQUNILFNBQWdCLHFCQUFxQjtJQUNuQyxPQUFPLENBQUMsR0FBWSxFQUFFLEdBQWEsRUFBRSxJQUFrQixFQUFFLEVBQUU7UUFDekQsTUFBTSxHQUFHLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBdUIsQ0FBQztRQUNqRSxJQUFJLENBQUMsR0FBRztZQUFFLE9BQU8sSUFBSSxFQUFFLENBQUM7UUFFeEIsaUNBQWlDO1FBQ2pDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUM7WUFBRSxPQUFPLElBQUksRUFBRSxDQUFDO1FBRW5FLDJEQUEyRDtRQUMzRCxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxLQUFLLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxjQUFjLENBQUM7UUFDdkUsSUFBSSxDQUFDLEtBQUs7WUFBRSxPQUFPLElBQUksRUFBRSxDQUFDLENBQUMsZ0RBQWdEO1FBQzNFLE1BQU0sT0FBTyxHQUFHLEdBQUcsS0FBSyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBRWxDLDRCQUE0QjtRQUM1QixNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2xDLElBQUksTUFBTSxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDNUMsTUFBTSxDQUFDLEtBQUssQ0FBQyw2QkFBNkIsRUFBRSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQzlELEdBQUcsQ0FBQyxTQUFTLENBQUMsdUJBQXVCLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDL0MsR0FBRyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNoRCxPQUFPO1FBQ1QsQ0FBQztRQUVELDJDQUEyQztRQUMzQyxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN4QyxHQUFHLENBQUMsSUFBSSxHQUFHLENBQUMsSUFBYSxFQUFFLEVBQUU7WUFDM0IsR0FBRyxDQUFDLFNBQVMsQ0FBQyx1QkFBdUIsRUFBRSxPQUFPLENBQUMsQ0FBQztZQUNoRCxJQUFJLEdBQUcsQ0FBQyxVQUFVLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxVQUFVLEdBQUcsR0FBRyxJQUFJLEtBQUssQ0FBQyxJQUFJLEdBQUcsNkJBQWEsQ0FBQywwQkFBMEIsRUFBRSxDQUFDO2dCQUMzRyxLQUFLLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRTtvQkFDakIsVUFBVSxFQUFFLEdBQUcsQ0FBQyxVQUFVO29CQUMxQixJQUFJO29CQUNKLFNBQVMsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsNkJBQWEsQ0FBQyxrQkFBa0I7aUJBQ3pELENBQUMsQ0FBQztZQUNMLENBQUM7WUFDRCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUM7UUFFRixJQUFJLEVBQUUsQ0FBQztJQUNULENBQUMsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgQ29yZUNvbnN0YW50cyB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWNvcmUnO1xuaW1wb3J0IHsgUmVxdWVzdCwgUmVzcG9uc2UsIE5leHRGdW5jdGlvbiB9IGZyb20gJ2V4cHJlc3MnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ0lkZW1wb3RlbmN5Jyk7XG5cbi8qKiBJbi1tZW1vcnkgc3RvcmUgZm9yIGlkZW1wb3RlbmN5IHJlc3VsdHMgKFRUTC1iYXNlZCkuICovXG5jb25zdCBzdG9yZSA9IG5ldyBNYXA8c3RyaW5nLCB7IHN0YXR1c0NvZGU6IG51bWJlcjsgYm9keTogdW5rbm93bjsgZXhwaXJlc0F0OiBudW1iZXIgfT4oKTtcblxuLy8gUGVyaW9kaWMgY2xlYW51cCBvZiBleHBpcmVkIGVudHJpZXNcbmNvbnN0IGNsZWFudXBUaW1lciA9IHNldEludGVydmFsKCgpID0+IHtcbiAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgZm9yIChjb25zdCBba2V5LCBlbnRyeV0gb2Ygc3RvcmUpIHtcbiAgICBpZiAobm93ID4gZW50cnkuZXhwaXJlc0F0KSBzdG9yZS5kZWxldGUoa2V5KTtcbiAgfVxufSwgQ29yZUNvbnN0YW50cy5JREVNUE9URU5DWV9DTEVBTlVQX0lOVEVSVkFMX01TKTtcbmNsZWFudXBUaW1lci51bnJlZigpO1xuXG4vKipcbiAqIE1pZGRsZXdhcmUgdGhhdCBzdXBwb3J0cyBpZGVtcG90ZW5jeSBrZXlzIGZvciBQT1NUL1BVVC9ERUxFVEUgbXV0YXRpb25zLlxuICpcbiAqIFdoZW4gYSByZXF1ZXN0IGluY2x1ZGVzIHRoZSBgSWRlbXBvdGVuY3ktS2V5YCBoZWFkZXI6XG4gKiAtIEZpcnN0IGNhbGw6IHByb2Nlc3NlcyBub3JtYWxseSwgY2FjaGVzIHRoZSByZXNwb25zZVxuICogLSBTdWJzZXF1ZW50IGNhbGxzIHdpdGggc2FtZSBrZXk6IHJldHVybnMgY2FjaGVkIHJlc3BvbnNlIChwcmV2ZW50cyBkdXBsaWNhdGUgbXV0YXRpb25zKVxuICovXG5leHBvcnQgZnVuY3Rpb24gaWRlbXBvdGVuY3lNaWRkbGV3YXJlKCkge1xuICByZXR1cm4gKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKSA9PiB7XG4gICAgY29uc3Qga2V5ID0gcmVxLmhlYWRlcnNbJ2lkZW1wb3RlbmN5LWtleSddIGFzIHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgICBpZiAoIWtleSkgcmV0dXJuIG5leHQoKTtcblxuICAgIC8vIE9ubHkgYXBwbHkgdG8gbXV0YXRpb24gbWV0aG9kc1xuICAgIGlmICghWydQT1NUJywgJ1BVVCcsICdERUxFVEUnXS5pbmNsdWRlcyhyZXEubWV0aG9kKSkgcmV0dXJuIG5leHQoKTtcblxuICAgIC8vIE5hbWVzcGFjZSBieSBvcmdJZCB0byBwcmV2ZW50IGNyb3NzLW9yZyBjYWNoZSBjb2xsaXNpb25zXG4gICAgY29uc3Qgb3JnSWQgPSByZXEuY29udGV4dD8uaWRlbnRpdHk/Lm9yZ0lkIHx8IHJlcS51c2VyPy5vcmdhbml6YXRpb25JZDtcbiAgICBpZiAoIW9yZ0lkKSByZXR1cm4gbmV4dCgpOyAvLyBza2lwIGlkZW1wb3RlbmN5IGZvciB1bmF1dGhlbnRpY2F0ZWQgcmVxdWVzdHNcbiAgICBjb25zdCBmdWxsS2V5ID0gYCR7b3JnSWR9OiR7a2V5fWA7XG5cbiAgICAvLyBDaGVjayBmb3IgY2FjaGVkIHJlc3BvbnNlXG4gICAgY29uc3QgY2FjaGVkID0gc3RvcmUuZ2V0KGZ1bGxLZXkpO1xuICAgIGlmIChjYWNoZWQgJiYgRGF0ZS5ub3coKSA8IGNhY2hlZC5leHBpcmVzQXQpIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnSWRlbXBvdGVudCByZXF1ZXN0IHJlcGxheWVkJywgeyBrZXk6IGZ1bGxLZXkgfSk7XG4gICAgICByZXMuc2V0SGVhZGVyKCdYLUlkZW1wb3RlbnQtUmVwbGF5ZWQnLCAndHJ1ZScpO1xuICAgICAgcmVzLnN0YXR1cyhjYWNoZWQuc3RhdHVzQ29kZSkuanNvbihjYWNoZWQuYm9keSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gSW50ZXJjZXB0IHJlcy5qc29uIHRvIGNhY2hlIHRoZSByZXNwb25zZVxuICAgIGNvbnN0IG9yaWdpbmFsSnNvbiA9IHJlcy5qc29uLmJpbmQocmVzKTtcbiAgICByZXMuanNvbiA9IChib2R5OiB1bmtub3duKSA9PiB7XG4gICAgICByZXMuc2V0SGVhZGVyKCdYLUlkZW1wb3RlbnQtUmVwbGF5ZWQnLCAnZmFsc2UnKTtcbiAgICAgIGlmIChyZXMuc3RhdHVzQ29kZSA+PSAyMDAgJiYgcmVzLnN0YXR1c0NvZGUgPCAzMDAgJiYgc3RvcmUuc2l6ZSA8IENvcmVDb25zdGFudHMuSURFTVBPVEVOQ1lfTUFYX1NUT1JFX1NJWkUpIHtcbiAgICAgICAgc3RvcmUuc2V0KGZ1bGxLZXksIHtcbiAgICAgICAgICBzdGF0dXNDb2RlOiByZXMuc3RhdHVzQ29kZSxcbiAgICAgICAgICBib2R5LFxuICAgICAgICAgIGV4cGlyZXNBdDogRGF0ZS5ub3coKSArIENvcmVDb25zdGFudHMuSURFTVBPVEVOQ1lfVFRMX01TLFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBvcmlnaW5hbEpzb24oYm9keSk7XG4gICAgfTtcblxuICAgIG5leHQoKTtcbiAgfTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './middleware';
|
|
2
|
+
export * from './middleware-factory';
|
|
3
|
+
export * from './context-middleware';
|
|
4
|
+
export * from './check-quota';
|
|
5
|
+
export * from './require-org-id';
|
|
6
|
+
export * from './get-context';
|
|
7
|
+
export * from './app-factory';
|
|
8
|
+
export * from './health-checks';
|
|
9
|
+
export * from './quota-helpers';
|
|
10
|
+
export * from './idempotency-middleware';
|
|
11
|
+
export * from './tracing';
|
|
12
|
+
export * from './metrics';
|
|
13
|
+
export * from './server';
|
|
14
|
+
export * from './route-wrapper';
|
|
15
|
+
export * from './request-types';
|
package/lib/api/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
16
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
// Middleware
|
|
20
|
+
__exportStar(require("./middleware"), exports);
|
|
21
|
+
__exportStar(require("./middleware-factory"), exports);
|
|
22
|
+
__exportStar(require("./context-middleware"), exports);
|
|
23
|
+
__exportStar(require("./check-quota"), exports);
|
|
24
|
+
__exportStar(require("./require-org-id"), exports);
|
|
25
|
+
__exportStar(require("./get-context"), exports);
|
|
26
|
+
// App factory
|
|
27
|
+
__exportStar(require("./app-factory"), exports);
|
|
28
|
+
// Health-check helpers
|
|
29
|
+
__exportStar(require("./health-checks"), exports);
|
|
30
|
+
// Quota helpers
|
|
31
|
+
__exportStar(require("./quota-helpers"), exports);
|
|
32
|
+
// Idempotency
|
|
33
|
+
__exportStar(require("./idempotency-middleware"), exports);
|
|
34
|
+
// Observability
|
|
35
|
+
__exportStar(require("./tracing"), exports);
|
|
36
|
+
__exportStar(require("./metrics"), exports);
|
|
37
|
+
// Server utilities
|
|
38
|
+
__exportStar(require("./server"), exports);
|
|
39
|
+
// Route wrapper
|
|
40
|
+
__exportStar(require("./route-wrapper"), exports);
|
|
41
|
+
// Request/Response types
|
|
42
|
+
__exportStar(require("./request-types"), exports);
|
|
43
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDOzs7Ozs7Ozs7Ozs7Ozs7O0FBRXRDLGFBQWE7QUFDYiwrQ0FBNkI7QUFDN0IsdURBQXFDO0FBQ3JDLHVEQUFxQztBQUNyQyxnREFBOEI7QUFDOUIsbURBQWlDO0FBQ2pDLGdEQUE4QjtBQUU5QixjQUFjO0FBQ2QsZ0RBQThCO0FBRTlCLHVCQUF1QjtBQUN2QixrREFBZ0M7QUFFaEMsZ0JBQWdCO0FBQ2hCLGtEQUFnQztBQUVoQyxjQUFjO0FBQ2QsMkRBQXlDO0FBRXpDLGdCQUFnQjtBQUNoQiw0Q0FBMEI7QUFDMUIsNENBQTBCO0FBRTFCLG1CQUFtQjtBQUNuQiwyQ0FBeUI7QUFFekIsZ0JBQWdCO0FBQ2hCLGtEQUFnQztBQUVoQyx5QkFBeUI7QUFDekIsa0RBQWdDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8vIE1pZGRsZXdhcmVcbmV4cG9ydCAqIGZyb20gJy4vbWlkZGxld2FyZSc7XG5leHBvcnQgKiBmcm9tICcuL21pZGRsZXdhcmUtZmFjdG9yeSc7XG5leHBvcnQgKiBmcm9tICcuL2NvbnRleHQtbWlkZGxld2FyZSc7XG5leHBvcnQgKiBmcm9tICcuL2NoZWNrLXF1b3RhJztcbmV4cG9ydCAqIGZyb20gJy4vcmVxdWlyZS1vcmctaWQnO1xuZXhwb3J0ICogZnJvbSAnLi9nZXQtY29udGV4dCc7XG5cbi8vIEFwcCBmYWN0b3J5XG5leHBvcnQgKiBmcm9tICcuL2FwcC1mYWN0b3J5JztcblxuLy8gSGVhbHRoLWNoZWNrIGhlbHBlcnNcbmV4cG9ydCAqIGZyb20gJy4vaGVhbHRoLWNoZWNrcyc7XG5cbi8vIFF1b3RhIGhlbHBlcnNcbmV4cG9ydCAqIGZyb20gJy4vcXVvdGEtaGVscGVycyc7XG5cbi8vIElkZW1wb3RlbmN5XG5leHBvcnQgKiBmcm9tICcuL2lkZW1wb3RlbmN5LW1pZGRsZXdhcmUnO1xuXG4vLyBPYnNlcnZhYmlsaXR5XG5leHBvcnQgKiBmcm9tICcuL3RyYWNpbmcnO1xuZXhwb3J0ICogZnJvbSAnLi9tZXRyaWNzJztcblxuLy8gU2VydmVyIHV0aWxpdGllc1xuZXhwb3J0ICogZnJvbSAnLi9zZXJ2ZXInO1xuXG4vLyBSb3V0ZSB3cmFwcGVyXG5leHBvcnQgKiBmcm9tICcuL3JvdXRlLXdyYXBwZXInO1xuXG4vLyBSZXF1ZXN0L1Jlc3BvbnNlIHR5cGVzXG5leHBvcnQgKiBmcm9tICcuL3JlcXVlc3QtdHlwZXMnO1xuIl19
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { 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>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.metricsMiddleware = metricsMiddleware;
|
|
6
|
+
exports.metricsHandler = metricsHandler;
|
|
7
|
+
const pipeline_core_1 = require("@pipeline-builder/pipeline-core");
|
|
8
|
+
const prom_client_1 = require("prom-client");
|
|
9
|
+
const SERVICE_NAME = pipeline_core_1.Config.getAny('observability').serviceName;
|
|
10
|
+
/** Shared Prometheus registry */
|
|
11
|
+
const register = new prom_client_1.Registry();
|
|
12
|
+
// Set default labels for all metrics
|
|
13
|
+
register.setDefaultLabels({ service: SERVICE_NAME });
|
|
14
|
+
// Collect default Node.js process metrics (CPU, memory, heap, event loop lag, GC)
|
|
15
|
+
(0, prom_client_1.collectDefaultMetrics)({ register });
|
|
16
|
+
/** HTTP request duration histogram (seconds) */
|
|
17
|
+
const httpRequestDuration = new prom_client_1.Histogram({
|
|
18
|
+
name: 'http_request_duration_seconds',
|
|
19
|
+
help: 'Duration of HTTP requests in seconds',
|
|
20
|
+
labelNames: ['method', 'route', 'status_code'],
|
|
21
|
+
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
22
|
+
registers: [register],
|
|
23
|
+
});
|
|
24
|
+
/** HTTP request counter */
|
|
25
|
+
const httpRequestsTotal = new prom_client_1.Counter({
|
|
26
|
+
name: 'http_requests_total',
|
|
27
|
+
help: 'Total number of HTTP requests',
|
|
28
|
+
labelNames: ['method', 'route', 'status_code'],
|
|
29
|
+
registers: [register],
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Normalize an Express route path to prevent label cardinality explosion.
|
|
33
|
+
*
|
|
34
|
+
* Uses Express's matched route pattern (e.g. `/plugins/:id`) when available,
|
|
35
|
+
* otherwise falls back to the raw path with UUID/numeric segments replaced.
|
|
36
|
+
*/
|
|
37
|
+
function normalizeRoute(req) {
|
|
38
|
+
// Prefer Express matched route pattern
|
|
39
|
+
if (req.route?.path) {
|
|
40
|
+
return req.baseUrl + req.route.path;
|
|
41
|
+
}
|
|
42
|
+
// Fallback: replace UUIDs and numeric IDs with :id
|
|
43
|
+
return req.path
|
|
44
|
+
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':id')
|
|
45
|
+
.replace(/\/\d+(?=\/|$)/g, '/:id');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Express middleware that records request duration and count.
|
|
49
|
+
*
|
|
50
|
+
* Must be registered before route handlers so `res.on('finish')` fires
|
|
51
|
+
* after the response is sent.
|
|
52
|
+
*/
|
|
53
|
+
function metricsMiddleware() {
|
|
54
|
+
return (req, res, next) => {
|
|
55
|
+
// Skip recording the /metrics and /health endpoints themselves
|
|
56
|
+
if (req.path === '/metrics' || req.path === '/health') {
|
|
57
|
+
next();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const end = httpRequestDuration.startTimer();
|
|
61
|
+
res.on('finish', () => {
|
|
62
|
+
const route = normalizeRoute(req);
|
|
63
|
+
const labels = {
|
|
64
|
+
method: req.method,
|
|
65
|
+
route,
|
|
66
|
+
status_code: String(res.statusCode),
|
|
67
|
+
};
|
|
68
|
+
end(labels);
|
|
69
|
+
httpRequestsTotal.inc(labels);
|
|
70
|
+
});
|
|
71
|
+
next();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Express handler that returns Prometheus metrics in text exposition format.
|
|
76
|
+
*/
|
|
77
|
+
function metricsHandler() {
|
|
78
|
+
return async (_req, res) => {
|
|
79
|
+
res.set('Content-Type', register.contentType);
|
|
80
|
+
res.end(await register.metrics());
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvbWV0cmljcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUEwRHRDLDhDQXdCQztBQUtELHdDQUtDO0FBMUZELG1FQUF5RDtBQUV6RCw2Q0FBa0Y7QUFFbEYsTUFBTSxZQUFZLEdBQUksc0JBQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUE2QixDQUFDLFdBQVcsQ0FBQztBQUU3RixpQ0FBaUM7QUFDakMsTUFBTSxRQUFRLEdBQUcsSUFBSSxzQkFBUSxFQUFFLENBQUM7QUFFaEMscUNBQXFDO0FBQ3JDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsQ0FBQyxDQUFDO0FBRXJELGtGQUFrRjtBQUNsRixJQUFBLG1DQUFxQixFQUFDLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztBQUVwQyxnREFBZ0Q7QUFDaEQsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLHVCQUFTLENBQUM7SUFDeEMsSUFBSSxFQUFFLCtCQUErQjtJQUNyQyxJQUFJLEVBQUUsc0NBQXNDO0lBQzVDLFVBQVUsRUFBRSxDQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUUsYUFBYSxDQUFVO0lBQ3ZELE9BQU8sRUFBRSxDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7SUFDbEUsU0FBUyxFQUFFLENBQUMsUUFBUSxDQUFDO0NBQ3RCLENBQUMsQ0FBQztBQUVILDJCQUEyQjtBQUMzQixNQUFNLGlCQUFpQixHQUFHLElBQUkscUJBQU8sQ0FBQztJQUNwQyxJQUFJLEVBQUUscUJBQXFCO0lBQzNCLElBQUksRUFBRSwrQkFBK0I7SUFDckMsVUFBVSxFQUFFLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxhQUFhLENBQVU7SUFDdkQsU0FBUyxFQUFFLENBQUMsUUFBUSxDQUFDO0NBQ3RCLENBQUMsQ0FBQztBQUVIOzs7OztHQUtHO0FBQ0gsU0FBUyxjQUFjLENBQUMsR0FBWTtJQUNsQyx1Q0FBdUM7SUFDdkMsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDO1FBQ3BCLE9BQU8sR0FBRyxDQUFDLE9BQU8sR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQztJQUN0QyxDQUFDO0lBRUQsbURBQW1EO0lBQ25ELE9BQU8sR0FBRyxDQUFDLElBQUk7U0FDWixPQUFPLENBQUMsZ0VBQWdFLEVBQUUsS0FBSyxDQUFDO1NBQ2hGLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxNQUFNLENBQUMsQ0FBQztBQUN2QyxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFnQixpQkFBaUI7SUFDL0IsT0FBTyxDQUFDLEdBQVksRUFBRSxHQUFhLEVBQUUsSUFBa0IsRUFBUSxFQUFFO1FBQy9ELCtEQUErRDtRQUMvRCxJQUFJLEdBQUcsQ0FBQyxJQUFJLEtBQUssVUFBVSxJQUFJLEdBQUcsQ0FBQyxJQUFJLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDdEQsSUFBSSxFQUFFLENBQUM7WUFDUCxPQUFPO1FBQ1QsQ0FBQztRQUVELE1BQU0sR0FBRyxHQUFHLG1CQUFtQixDQUFDLFVBQVUsRUFBRSxDQUFDO1FBRTdDLEdBQUcsQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRTtZQUNwQixNQUFNLEtBQUssR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDbEMsTUFBTSxNQUFNLEdBQUc7Z0JBQ2IsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNO2dCQUNsQixLQUFLO2dCQUNMLFdBQVcsRUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQzthQUNwQyxDQUFDO1lBRUYsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ1osaUJBQWlCLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2hDLENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxFQUFFLENBQUM7SUFDVCxDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFnQixjQUFjO0lBQzVCLE9BQU8sS0FBSyxFQUFFLElBQWEsRUFBRSxHQUFhLEVBQWlCLEVBQUU7UUFDM0QsR0FBRyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQzlDLEdBQUcsQ0FBQyxHQUFHLENBQUMsTUFBTSxRQUFRLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IENvbmZpZyB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWNvcmUnO1xuaW1wb3J0IHsgUmVxdWVzdCwgUmVzcG9uc2UsIE5leHRGdW5jdGlvbiB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHsgUmVnaXN0cnksIGNvbGxlY3REZWZhdWx0TWV0cmljcywgQ291bnRlciwgSGlzdG9ncmFtIH0gZnJvbSAncHJvbS1jbGllbnQnO1xuXG5jb25zdCBTRVJWSUNFX05BTUUgPSAoQ29uZmlnLmdldEFueSgnb2JzZXJ2YWJpbGl0eScpIGFzIHsgc2VydmljZU5hbWU6IHN0cmluZyB9KS5zZXJ2aWNlTmFtZTtcblxuLyoqIFNoYXJlZCBQcm9tZXRoZXVzIHJlZ2lzdHJ5ICovXG5jb25zdCByZWdpc3RlciA9IG5ldyBSZWdpc3RyeSgpO1xuXG4vLyBTZXQgZGVmYXVsdCBsYWJlbHMgZm9yIGFsbCBtZXRyaWNzXG5yZWdpc3Rlci5zZXREZWZhdWx0TGFiZWxzKHsgc2VydmljZTogU0VSVklDRV9OQU1FIH0pO1xuXG4vLyBDb2xsZWN0IGRlZmF1bHQgTm9kZS5qcyBwcm9jZXNzIG1ldHJpY3MgKENQVSwgbWVtb3J5LCBoZWFwLCBldmVudCBsb29wIGxhZywgR0MpXG5jb2xsZWN0RGVmYXVsdE1ldHJpY3MoeyByZWdpc3RlciB9KTtcblxuLyoqIEhUVFAgcmVxdWVzdCBkdXJhdGlvbiBoaXN0b2dyYW0gKHNlY29uZHMpICovXG5jb25zdCBodHRwUmVxdWVzdER1cmF0aW9uID0gbmV3IEhpc3RvZ3JhbSh7XG4gIG5hbWU6ICdodHRwX3JlcXVlc3RfZHVyYXRpb25fc2Vjb25kcycsXG4gIGhlbHA6ICdEdXJhdGlvbiBvZiBIVFRQIHJlcXVlc3RzIGluIHNlY29uZHMnLFxuICBsYWJlbE5hbWVzOiBbJ21ldGhvZCcsICdyb3V0ZScsICdzdGF0dXNfY29kZSddIGFzIGNvbnN0LFxuICBidWNrZXRzOiBbMC4wMDUsIDAuMDEsIDAuMDI1LCAwLjA1LCAwLjEsIDAuMjUsIDAuNSwgMSwgMi41LCA1LCAxMF0sXG4gIHJlZ2lzdGVyczogW3JlZ2lzdGVyXSxcbn0pO1xuXG4vKiogSFRUUCByZXF1ZXN0IGNvdW50ZXIgKi9cbmNvbnN0IGh0dHBSZXF1ZXN0c1RvdGFsID0gbmV3IENvdW50ZXIoe1xuICBuYW1lOiAnaHR0cF9yZXF1ZXN0c190b3RhbCcsXG4gIGhlbHA6ICdUb3RhbCBudW1iZXIgb2YgSFRUUCByZXF1ZXN0cycsXG4gIGxhYmVsTmFtZXM6IFsnbWV0aG9kJywgJ3JvdXRlJywgJ3N0YXR1c19jb2RlJ10gYXMgY29uc3QsXG4gIHJlZ2lzdGVyczogW3JlZ2lzdGVyXSxcbn0pO1xuXG4vKipcbiAqIE5vcm1hbGl6ZSBhbiBFeHByZXNzIHJvdXRlIHBhdGggdG8gcHJldmVudCBsYWJlbCBjYXJkaW5hbGl0eSBleHBsb3Npb24uXG4gKlxuICogVXNlcyBFeHByZXNzJ3MgbWF0Y2hlZCByb3V0ZSBwYXR0ZXJuIChlLmcuIGAvcGx1Z2lucy86aWRgKSB3aGVuIGF2YWlsYWJsZSxcbiAqIG90aGVyd2lzZSBmYWxscyBiYWNrIHRvIHRoZSByYXcgcGF0aCB3aXRoIFVVSUQvbnVtZXJpYyBzZWdtZW50cyByZXBsYWNlZC5cbiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplUm91dGUocmVxOiBSZXF1ZXN0KTogc3RyaW5nIHtcbiAgLy8gUHJlZmVyIEV4cHJlc3MgbWF0Y2hlZCByb3V0ZSBwYXR0ZXJuXG4gIGlmIChyZXEucm91dGU/LnBhdGgpIHtcbiAgICByZXR1cm4gcmVxLmJhc2VVcmwgKyByZXEucm91dGUucGF0aDtcbiAgfVxuXG4gIC8vIEZhbGxiYWNrOiByZXBsYWNlIFVVSURzIGFuZCBudW1lcmljIElEcyB3aXRoIDppZFxuICByZXR1cm4gcmVxLnBhdGhcbiAgICAucmVwbGFjZSgvWzAtOWEtZl17OH0tWzAtOWEtZl17NH0tWzAtOWEtZl17NH0tWzAtOWEtZl17NH0tWzAtOWEtZl17MTJ9L2dpLCAnOmlkJylcbiAgICAucmVwbGFjZSgvXFwvXFxkKyg/PVxcL3wkKS9nLCAnLzppZCcpO1xufVxuXG4vKipcbiAqIEV4cHJlc3MgbWlkZGxld2FyZSB0aGF0IHJlY29yZHMgcmVxdWVzdCBkdXJhdGlvbiBhbmQgY291bnQuXG4gKlxuICogTXVzdCBiZSByZWdpc3RlcmVkIGJlZm9yZSByb3V0ZSBoYW5kbGVycyBzbyBgcmVzLm9uKCdmaW5pc2gnKWAgZmlyZXNcbiAqIGFmdGVyIHRoZSByZXNwb25zZSBpcyBzZW50LlxuICovXG5leHBvcnQgZnVuY3Rpb24gbWV0cmljc01pZGRsZXdhcmUoKSB7XG4gIHJldHVybiAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlLCBuZXh0OiBOZXh0RnVuY3Rpb24pOiB2b2lkID0+IHtcbiAgICAvLyBTa2lwIHJlY29yZGluZyB0aGUgL21ldHJpY3MgYW5kIC9oZWFsdGggZW5kcG9pbnRzIHRoZW1zZWx2ZXNcbiAgICBpZiAocmVxLnBhdGggPT09ICcvbWV0cmljcycgfHwgcmVxLnBhdGggPT09ICcvaGVhbHRoJykge1xuICAgICAgbmV4dCgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IGVuZCA9IGh0dHBSZXF1ZXN0RHVyYXRpb24uc3RhcnRUaW1lcigpO1xuXG4gICAgcmVzLm9uKCdmaW5pc2gnLCAoKSA9PiB7XG4gICAgICBjb25zdCByb3V0ZSA9IG5vcm1hbGl6ZVJvdXRlKHJlcSk7XG4gICAgICBjb25zdCBsYWJlbHMgPSB7XG4gICAgICAgIG1ldGhvZDogcmVxLm1ldGhvZCxcbiAgICAgICAgcm91dGUsXG4gICAgICAgIHN0YXR1c19jb2RlOiBTdHJpbmcocmVzLnN0YXR1c0NvZGUpLFxuICAgICAgfTtcblxuICAgICAgZW5kKGxhYmVscyk7XG4gICAgICBodHRwUmVxdWVzdHNUb3RhbC5pbmMobGFiZWxzKTtcbiAgICB9KTtcblxuICAgIG5leHQoKTtcbiAgfTtcbn1cblxuLyoqXG4gKiBFeHByZXNzIGhhbmRsZXIgdGhhdCByZXR1cm5zIFByb21ldGhldXMgbWV0cmljcyBpbiB0ZXh0IGV4cG9zaXRpb24gZm9ybWF0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gbWV0cmljc0hhbmRsZXIoKSB7XG4gIHJldHVybiBhc3luYyAoX3JlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgIHJlcy5zZXQoJ0NvbnRlbnQtVHlwZScsIHJlZ2lzdGVyLmNvbnRlbnRUeXBlKTtcbiAgICByZXMuZW5kKGF3YWl0IHJlZ2lzdGVyLm1ldHJpY3MoKSk7XG4gIH07XG59XG4iXX0=
|