@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,47 @@
|
|
|
1
|
+
import type { QuotaType, QuotaService } from '@pipeline-builder/api-core';
|
|
2
|
+
import { RequestHandler } from 'express';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a middleware chain for protected routes requiring authentication, org ID, and quota check.
|
|
5
|
+
*
|
|
6
|
+
* Applies middleware in order:
|
|
7
|
+
* 1. requireAuth - Validates JWT and extracts user identity
|
|
8
|
+
* 2. requireOrgId - Ensures request has x-org-id header
|
|
9
|
+
* 3. checkQuota - Validates quota for the specified resource type
|
|
10
|
+
*
|
|
11
|
+
* @param quotaService - Quota service client
|
|
12
|
+
* @param quotaType - Which quota to check (e.g., 'apiCalls', 'pipelines', 'plugins')
|
|
13
|
+
* @returns Array of middleware handlers ready to spread into route definition
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* router.post('/',
|
|
18
|
+
* ...createProtectedRoute(quotaService, 'pipelines'),
|
|
19
|
+
* async (req, res) => {
|
|
20
|
+
* // Handler implementation
|
|
21
|
+
* }
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createProtectedRoute(quotaService: QuotaService, quotaType: QuotaType): RequestHandler[];
|
|
26
|
+
/**
|
|
27
|
+
* Creates a middleware chain for authenticated routes with org ID requirement but no quota check.
|
|
28
|
+
*
|
|
29
|
+
* Applies middleware in order:
|
|
30
|
+
* 1. requireAuth - Validates JWT and extracts user identity
|
|
31
|
+
* 2. requireOrgId - Ensures request has x-org-id header
|
|
32
|
+
*
|
|
33
|
+
* Use this for read-only routes that don't consume quota.
|
|
34
|
+
*
|
|
35
|
+
* @returns Array of middleware handlers ready to spread into route definition
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* router.get('/',
|
|
40
|
+
* ...createAuthenticatedWithOrgRoute(),
|
|
41
|
+
* async (req, res) => {
|
|
42
|
+
* // Handler implementation
|
|
43
|
+
* }
|
|
44
|
+
* );
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function createAuthenticatedWithOrgRoute(): RequestHandler[];
|
|
@@ -0,0 +1,66 @@
|
|
|
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.createProtectedRoute = createProtectedRoute;
|
|
6
|
+
exports.createAuthenticatedWithOrgRoute = createAuthenticatedWithOrgRoute;
|
|
7
|
+
const check_quota_1 = require("./check-quota");
|
|
8
|
+
const middleware_1 = require("./middleware");
|
|
9
|
+
const require_org_id_1 = require("./require-org-id");
|
|
10
|
+
/**
|
|
11
|
+
* Creates a middleware chain for protected routes requiring authentication, org ID, and quota check.
|
|
12
|
+
*
|
|
13
|
+
* Applies middleware in order:
|
|
14
|
+
* 1. requireAuth - Validates JWT and extracts user identity
|
|
15
|
+
* 2. requireOrgId - Ensures request has x-org-id header
|
|
16
|
+
* 3. checkQuota - Validates quota for the specified resource type
|
|
17
|
+
*
|
|
18
|
+
* @param quotaService - Quota service client
|
|
19
|
+
* @param quotaType - Which quota to check (e.g., 'apiCalls', 'pipelines', 'plugins')
|
|
20
|
+
* @returns Array of middleware handlers ready to spread into route definition
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* router.post('/',
|
|
25
|
+
* ...createProtectedRoute(quotaService, 'pipelines'),
|
|
26
|
+
* async (req, res) => {
|
|
27
|
+
* // Handler implementation
|
|
28
|
+
* }
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function createProtectedRoute(quotaService, quotaType) {
|
|
33
|
+
return [
|
|
34
|
+
middleware_1.requireAuth,
|
|
35
|
+
(0, require_org_id_1.requireOrgId)(),
|
|
36
|
+
(0, check_quota_1.checkQuota)(quotaService, quotaType),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates a middleware chain for authenticated routes with org ID requirement but no quota check.
|
|
41
|
+
*
|
|
42
|
+
* Applies middleware in order:
|
|
43
|
+
* 1. requireAuth - Validates JWT and extracts user identity
|
|
44
|
+
* 2. requireOrgId - Ensures request has x-org-id header
|
|
45
|
+
*
|
|
46
|
+
* Use this for read-only routes that don't consume quota.
|
|
47
|
+
*
|
|
48
|
+
* @returns Array of middleware handlers ready to spread into route definition
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* router.get('/',
|
|
53
|
+
* ...createAuthenticatedWithOrgRoute(),
|
|
54
|
+
* async (req, res) => {
|
|
55
|
+
* // Handler implementation
|
|
56
|
+
* }
|
|
57
|
+
* );
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
function createAuthenticatedWithOrgRoute() {
|
|
61
|
+
return [
|
|
62
|
+
middleware_1.requireAuth,
|
|
63
|
+
(0, require_org_id_1.requireOrgId)(),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS1mYWN0b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS9taWRkbGV3YXJlLWZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7O0FBOEJ0QyxvREFTQztBQXVCRCwwRUFLQztBQS9ERCwrQ0FBMkM7QUFDM0MsNkNBQTJDO0FBQzNDLHFEQUFnRDtBQUVoRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBcUJHO0FBQ0gsU0FBZ0Isb0JBQW9CLENBQ2xDLFlBQTBCLEVBQzFCLFNBQW9CO0lBRXBCLE9BQU87UUFDTCx3QkFBNkI7UUFDN0IsSUFBQSw2QkFBWSxHQUFvQjtRQUNoQyxJQUFBLHdCQUFVLEVBQUMsWUFBWSxFQUFFLFNBQVMsQ0FBbUI7S0FDdEQsQ0FBQztBQUNKLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvQkc7QUFDSCxTQUFnQiwrQkFBK0I7SUFDN0MsT0FBTztRQUNMLHdCQUE2QjtRQUM3QixJQUFBLDZCQUFZLEdBQW9CO0tBQ2pDLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHsgUXVvdGFUeXBlLCBRdW90YVNlcnZpY2UgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBSZXF1ZXN0SGFuZGxlciB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHsgY2hlY2tRdW90YSB9IGZyb20gJy4vY2hlY2stcXVvdGEnO1xuaW1wb3J0IHsgcmVxdWlyZUF1dGggfSBmcm9tICcuL21pZGRsZXdhcmUnO1xuaW1wb3J0IHsgcmVxdWlyZU9yZ0lkIH0gZnJvbSAnLi9yZXF1aXJlLW9yZy1pZCc7XG5cbi8qKlxuICogQ3JlYXRlcyBhIG1pZGRsZXdhcmUgY2hhaW4gZm9yIHByb3RlY3RlZCByb3V0ZXMgcmVxdWlyaW5nIGF1dGhlbnRpY2F0aW9uLCBvcmcgSUQsIGFuZCBxdW90YSBjaGVjay5cbiAqXG4gKiBBcHBsaWVzIG1pZGRsZXdhcmUgaW4gb3JkZXI6XG4gKiAxLiByZXF1aXJlQXV0aCAtIFZhbGlkYXRlcyBKV1QgYW5kIGV4dHJhY3RzIHVzZXIgaWRlbnRpdHlcbiAqIDIuIHJlcXVpcmVPcmdJZCAtIEVuc3VyZXMgcmVxdWVzdCBoYXMgeC1vcmctaWQgaGVhZGVyXG4gKiAzLiBjaGVja1F1b3RhIC0gVmFsaWRhdGVzIHF1b3RhIGZvciB0aGUgc3BlY2lmaWVkIHJlc291cmNlIHR5cGVcbiAqXG4gKiBAcGFyYW0gcXVvdGFTZXJ2aWNlIC0gUXVvdGEgc2VydmljZSBjbGllbnRcbiAqIEBwYXJhbSBxdW90YVR5cGUgLSBXaGljaCBxdW90YSB0byBjaGVjayAoZS5nLiwgJ2FwaUNhbGxzJywgJ3BpcGVsaW5lcycsICdwbHVnaW5zJylcbiAqIEByZXR1cm5zIEFycmF5IG9mIG1pZGRsZXdhcmUgaGFuZGxlcnMgcmVhZHkgdG8gc3ByZWFkIGludG8gcm91dGUgZGVmaW5pdGlvblxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiByb3V0ZXIucG9zdCgnLycsXG4gKiAgIC4uLmNyZWF0ZVByb3RlY3RlZFJvdXRlKHF1b3RhU2VydmljZSwgJ3BpcGVsaW5lcycpLFxuICogICBhc3luYyAocmVxLCByZXMpID0+IHtcbiAqICAgICAvLyBIYW5kbGVyIGltcGxlbWVudGF0aW9uXG4gKiAgIH1cbiAqICk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVByb3RlY3RlZFJvdXRlKFxuICBxdW90YVNlcnZpY2U6IFF1b3RhU2VydmljZSxcbiAgcXVvdGFUeXBlOiBRdW90YVR5cGUsXG4pOiBSZXF1ZXN0SGFuZGxlcltdIHtcbiAgcmV0dXJuIFtcbiAgICByZXF1aXJlQXV0aCBhcyBSZXF1ZXN0SGFuZGxlcixcbiAgICByZXF1aXJlT3JnSWQoKSBhcyBSZXF1ZXN0SGFuZGxlcixcbiAgICBjaGVja1F1b3RhKHF1b3RhU2VydmljZSwgcXVvdGFUeXBlKSBhcyBSZXF1ZXN0SGFuZGxlcixcbiAgXTtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgbWlkZGxld2FyZSBjaGFpbiBmb3IgYXV0aGVudGljYXRlZCByb3V0ZXMgd2l0aCBvcmcgSUQgcmVxdWlyZW1lbnQgYnV0IG5vIHF1b3RhIGNoZWNrLlxuICpcbiAqIEFwcGxpZXMgbWlkZGxld2FyZSBpbiBvcmRlcjpcbiAqIDEuIHJlcXVpcmVBdXRoIC0gVmFsaWRhdGVzIEpXVCBhbmQgZXh0cmFjdHMgdXNlciBpZGVudGl0eVxuICogMi4gcmVxdWlyZU9yZ0lkIC0gRW5zdXJlcyByZXF1ZXN0IGhhcyB4LW9yZy1pZCBoZWFkZXJcbiAqXG4gKiBVc2UgdGhpcyBmb3IgcmVhZC1vbmx5IHJvdXRlcyB0aGF0IGRvbid0IGNvbnN1bWUgcXVvdGEuXG4gKlxuICogQHJldHVybnMgQXJyYXkgb2YgbWlkZGxld2FyZSBoYW5kbGVycyByZWFkeSB0byBzcHJlYWQgaW50byByb3V0ZSBkZWZpbml0aW9uXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIHJvdXRlci5nZXQoJy8nLFxuICogICAuLi5jcmVhdGVBdXRoZW50aWNhdGVkV2l0aE9yZ1JvdXRlKCksXG4gKiAgIGFzeW5jIChyZXEsIHJlcykgPT4ge1xuICogICAgIC8vIEhhbmRsZXIgaW1wbGVtZW50YXRpb25cbiAqICAgfVxuICogKTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlQXV0aGVudGljYXRlZFdpdGhPcmdSb3V0ZSgpOiBSZXF1ZXN0SGFuZGxlcltdIHtcbiAgcmV0dXJuIFtcbiAgICByZXF1aXJlQXV0aCBhcyBSZXF1ZXN0SGFuZGxlcixcbiAgICByZXF1aXJlT3JnSWQoKSBhcyBSZXF1ZXN0SGFuZGxlcixcbiAgXTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { requireAuth, requireOrganization, requireAdmin, requireFeature, isSystemOrg, isSystemAdmin, type RequireAuthOptions, } from '@pipeline-builder/api-core';
|
|
@@ -0,0 +1,14 @@
|
|
|
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.isSystemAdmin = exports.isSystemOrg = exports.requireFeature = exports.requireAdmin = exports.requireOrganization = exports.requireAuth = void 0;
|
|
6
|
+
// Re-export all authentication middleware from api-core
|
|
7
|
+
var api_core_1 = require("@pipeline-builder/api-core");
|
|
8
|
+
Object.defineProperty(exports, "requireAuth", { enumerable: true, get: function () { return api_core_1.requireAuth; } });
|
|
9
|
+
Object.defineProperty(exports, "requireOrganization", { enumerable: true, get: function () { return api_core_1.requireOrganization; } });
|
|
10
|
+
Object.defineProperty(exports, "requireAdmin", { enumerable: true, get: function () { return api_core_1.requireAdmin; } });
|
|
11
|
+
Object.defineProperty(exports, "requireFeature", { enumerable: true, get: function () { return api_core_1.requireFeature; } });
|
|
12
|
+
Object.defineProperty(exports, "isSystemOrg", { enumerable: true, get: function () { return api_core_1.isSystemOrg; } });
|
|
13
|
+
Object.defineProperty(exports, "isSystemAdmin", { enumerable: true, get: function () { return api_core_1.isSystemAdmin; } });
|
|
14
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvbWlkZGxld2FyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7O0FBRXRDLHdEQUF3RDtBQUN4RCx1REFRb0M7QUFQbEMsdUdBQUEsV0FBVyxPQUFBO0FBQ1gsK0dBQUEsbUJBQW1CLE9BQUE7QUFDbkIsd0dBQUEsWUFBWSxPQUFBO0FBQ1osMEdBQUEsY0FBYyxPQUFBO0FBQ2QsdUdBQUEsV0FBVyxPQUFBO0FBQ1gseUdBQUEsYUFBYSxPQUFBIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8vIFJlLWV4cG9ydCBhbGwgYXV0aGVudGljYXRpb24gbWlkZGxld2FyZSBmcm9tIGFwaS1jb3JlXG5leHBvcnQge1xuICByZXF1aXJlQXV0aCxcbiAgcmVxdWlyZU9yZ2FuaXphdGlvbixcbiAgcmVxdWlyZUFkbWluLFxuICByZXF1aXJlRmVhdHVyZSxcbiAgaXNTeXN0ZW1PcmcsXG4gIGlzU3lzdGVtQWRtaW4sXG4gIHR5cGUgUmVxdWlyZUF1dGhPcHRpb25zLFxufSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG4iXX0=
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type QuotaService, type QuotaType } from '@pipeline-builder/api-core';
|
|
2
|
+
import type { Request } from 'express';
|
|
3
|
+
import type { RequestContext } from './request-types';
|
|
4
|
+
/**
|
|
5
|
+
* Increment a quota counter using values pulled from a route context.
|
|
6
|
+
*
|
|
7
|
+
* Wraps `incrementQuota(quotaService, orgId, type, authHeader, logWarn)` so
|
|
8
|
+
* route handlers don't have to re-derive `req.headers.authorization` and
|
|
9
|
+
* `ctx.log.bind(null, 'WARN')` at every call site.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* router.get('/', withRoute(async ({ req, res, ctx, orgId }) => {
|
|
14
|
+
* // ...
|
|
15
|
+
* incrementQuotaFromCtx(quotaService, { req, ctx, orgId }, 'apiCalls');
|
|
16
|
+
* }));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function incrementQuotaFromCtx(quotaService: QuotaService, rc: {
|
|
20
|
+
req: Request;
|
|
21
|
+
ctx: RequestContext;
|
|
22
|
+
orgId: string;
|
|
23
|
+
}, type: QuotaType): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
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.incrementQuotaFromCtx = incrementQuotaFromCtx;
|
|
6
|
+
const api_core_1 = require("@pipeline-builder/api-core");
|
|
7
|
+
/**
|
|
8
|
+
* Increment a quota counter using values pulled from a route context.
|
|
9
|
+
*
|
|
10
|
+
* Wraps `incrementQuota(quotaService, orgId, type, authHeader, logWarn)` so
|
|
11
|
+
* route handlers don't have to re-derive `req.headers.authorization` and
|
|
12
|
+
* `ctx.log.bind(null, 'WARN')` at every call site.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* router.get('/', withRoute(async ({ req, res, ctx, orgId }) => {
|
|
17
|
+
* // ...
|
|
18
|
+
* incrementQuotaFromCtx(quotaService, { req, ctx, orgId }, 'apiCalls');
|
|
19
|
+
* }));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function incrementQuotaFromCtx(quotaService, rc, type) {
|
|
23
|
+
(0, api_core_1.incrementQuota)(quotaService, rc.orgId, type, rc.req.headers.authorization || '', rc.ctx.log.bind(null, 'WARN'));
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVvdGEtaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvcXVvdGEtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUFxQnRDLHNEQVlDO0FBL0JELHlEQUErRjtBQUkvRjs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILFNBQWdCLHFCQUFxQixDQUNuQyxZQUEwQixFQUMxQixFQUF3RCxFQUN4RCxJQUFlO0lBRWYsSUFBQSx5QkFBYyxFQUNaLFlBQVksRUFDWixFQUFFLENBQUMsS0FBSyxFQUNSLElBQUksRUFDSixFQUFFLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxhQUFhLElBQUksRUFBRSxFQUNsQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUM5QixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBpbmNyZW1lbnRRdW90YSwgdHlwZSBRdW90YVNlcnZpY2UsIHR5cGUgUXVvdGFUeXBlIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0IH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3RDb250ZXh0IH0gZnJvbSAnLi9yZXF1ZXN0LXR5cGVzJztcblxuLyoqXG4gKiBJbmNyZW1lbnQgYSBxdW90YSBjb3VudGVyIHVzaW5nIHZhbHVlcyBwdWxsZWQgZnJvbSBhIHJvdXRlIGNvbnRleHQuXG4gKlxuICogV3JhcHMgYGluY3JlbWVudFF1b3RhKHF1b3RhU2VydmljZSwgb3JnSWQsIHR5cGUsIGF1dGhIZWFkZXIsIGxvZ1dhcm4pYCBzb1xuICogcm91dGUgaGFuZGxlcnMgZG9uJ3QgaGF2ZSB0byByZS1kZXJpdmUgYHJlcS5oZWFkZXJzLmF1dGhvcml6YXRpb25gIGFuZFxuICogYGN0eC5sb2cuYmluZChudWxsLCAnV0FSTicpYCBhdCBldmVyeSBjYWxsIHNpdGUuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIHJvdXRlci5nZXQoJy8nLCB3aXRoUm91dGUoYXN5bmMgKHsgcmVxLCByZXMsIGN0eCwgb3JnSWQgfSkgPT4ge1xuICogICAvLyAuLi5cbiAqICAgaW5jcmVtZW50UXVvdGFGcm9tQ3R4KHF1b3RhU2VydmljZSwgeyByZXEsIGN0eCwgb3JnSWQgfSwgJ2FwaUNhbGxzJyk7XG4gKiB9KSk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGluY3JlbWVudFF1b3RhRnJvbUN0eChcbiAgcXVvdGFTZXJ2aWNlOiBRdW90YVNlcnZpY2UsXG4gIHJjOiB7IHJlcTogUmVxdWVzdDsgY3R4OiBSZXF1ZXN0Q29udGV4dDsgb3JnSWQ6IHN0cmluZyB9LFxuICB0eXBlOiBRdW90YVR5cGUsXG4pOiB2b2lkIHtcbiAgaW5jcmVtZW50UXVvdGEoXG4gICAgcXVvdGFTZXJ2aWNlLFxuICAgIHJjLm9yZ0lkLFxuICAgIHR5cGUsXG4gICAgcmMucmVxLmhlYWRlcnMuYXV0aG9yaXphdGlvbiB8fCAnJyxcbiAgICByYy5jdHgubG9nLmJpbmQobnVsbCwgJ1dBUk4nKSxcbiAgKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { RequestIdentity } from '@pipeline-builder/api-core';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
import { SSEEventType, SSEManager } from '../http/sse-connection-manager';
|
|
4
|
+
declare global {
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
/** Unique request ID (set by app-factory middleware) */
|
|
8
|
+
requestId?: string;
|
|
9
|
+
/** Request context with identity, logging, and SSE */
|
|
10
|
+
context?: RequestContext;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Request logger function type
|
|
16
|
+
*/
|
|
17
|
+
export type RequestLogger = (type: SSEEventType, message: string, data?: unknown) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Request context with identity and logging
|
|
20
|
+
*/
|
|
21
|
+
export interface RequestContext {
|
|
22
|
+
/** Unique request ID */
|
|
23
|
+
requestId: string;
|
|
24
|
+
/** Identity from headers */
|
|
25
|
+
identity: RequestIdentity;
|
|
26
|
+
/** Logging function that sends to console and SSE */
|
|
27
|
+
log: RequestLogger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a request context with identity and logging
|
|
31
|
+
*
|
|
32
|
+
* Creates a logger that outputs to both console and SSE.
|
|
33
|
+
*
|
|
34
|
+
* @param req - Express request
|
|
35
|
+
* @param sseManager - SSE manager for real-time logs
|
|
36
|
+
* @returns Request context with identity and logger
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* app.post('/api/resource', requireAuth, async (req, res) => {
|
|
41
|
+
* const ctx = createRequestContext(req, sseManager);
|
|
42
|
+
*
|
|
43
|
+
* ctx.log('INFO', 'Processing request', { data: req.body });
|
|
44
|
+
*
|
|
45
|
+
* if (!ctx.identity.orgId) {
|
|
46
|
+
* ctx.log('ERROR', 'Missing organization ID');
|
|
47
|
+
* return sendBadRequest(res, 'x-org-id header required');
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* // Process request...
|
|
51
|
+
* ctx.log('COMPLETED', 'Request processed successfully');
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function createRequestContext(req: Request, sseManager: SSEManager): RequestContext;
|
|
@@ -0,0 +1,62 @@
|
|
|
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.createRequestContext = createRequestContext;
|
|
6
|
+
const api_core_1 = require("@pipeline-builder/api-core");
|
|
7
|
+
const uuid_1 = require("uuid");
|
|
8
|
+
const logger = (0, api_core_1.createLogger)('api-server');
|
|
9
|
+
/**
|
|
10
|
+
* Create a request context with identity and logging
|
|
11
|
+
*
|
|
12
|
+
* Creates a logger that outputs to both console and SSE.
|
|
13
|
+
*
|
|
14
|
+
* @param req - Express request
|
|
15
|
+
* @param sseManager - SSE manager for real-time logs
|
|
16
|
+
* @returns Request context with identity and logger
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* app.post('/api/resource', requireAuth, async (req, res) => {
|
|
21
|
+
* const ctx = createRequestContext(req, sseManager);
|
|
22
|
+
*
|
|
23
|
+
* ctx.log('INFO', 'Processing request', { data: req.body });
|
|
24
|
+
*
|
|
25
|
+
* if (!ctx.identity.orgId) {
|
|
26
|
+
* ctx.log('ERROR', 'Missing organization ID');
|
|
27
|
+
* return sendBadRequest(res, 'x-org-id header required');
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // Process request...
|
|
31
|
+
* ctx.log('COMPLETED', 'Request processed successfully');
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function createRequestContext(req, sseManager) {
|
|
36
|
+
const identity = (0, api_core_1.getIdentity)(req);
|
|
37
|
+
// Prefer the already-parsed requestId from app-factory middleware,
|
|
38
|
+
// fall back to identity header, then generate a new one.
|
|
39
|
+
const requestId = req.requestId || identity.requestId || (0, uuid_1.v7)();
|
|
40
|
+
// Create logger that outputs to Winston and SSE
|
|
41
|
+
const log = (type, message, data) => {
|
|
42
|
+
const meta = { requestId, orgId: identity.orgId, type, data };
|
|
43
|
+
switch (type) {
|
|
44
|
+
case 'ERROR':
|
|
45
|
+
logger.error(message, meta);
|
|
46
|
+
break;
|
|
47
|
+
case 'WARN':
|
|
48
|
+
logger.warn(message, meta);
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
logger.info(message, meta);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
sseManager.send(requestId, type, message, data);
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
requestId,
|
|
58
|
+
identity,
|
|
59
|
+
log,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWVzdC10eXBlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvcmVxdWVzdC10eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUFnRXRDLG9EQStCQztBQTdGRCx5REFBcUc7QUFFckcsK0JBQWtDO0FBZWxDLE1BQU0sTUFBTSxHQUFHLElBQUEsdUJBQVksRUFBQyxZQUFZLENBQUMsQ0FBQztBQW1CMUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F5Qkc7QUFDSCxTQUFnQixvQkFBb0IsQ0FDbEMsR0FBWSxFQUNaLFVBQXNCO0lBRXRCLE1BQU0sUUFBUSxHQUFHLElBQUEsc0JBQVcsRUFBQyxHQUFrQixDQUFDLENBQUM7SUFDakQsbUVBQW1FO0lBQ25FLHlEQUF5RDtJQUN6RCxNQUFNLFNBQVMsR0FBRyxHQUFHLENBQUMsU0FBUyxJQUFJLFFBQVEsQ0FBQyxTQUFTLElBQUksSUFBQSxTQUFJLEdBQUUsQ0FBQztJQUVoRSxnREFBZ0Q7SUFDaEQsTUFBTSxHQUFHLEdBQWtCLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRTtRQUNqRCxNQUFNLElBQUksR0FBRyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsUUFBUSxDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDOUQsUUFBUSxJQUFJLEVBQUUsQ0FBQztZQUNiLEtBQUssT0FBTztnQkFDVixNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztnQkFDNUIsTUFBTTtZQUNSLEtBQUssTUFBTTtnQkFDVCxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztnQkFDM0IsTUFBTTtZQUNSO2dCQUNFLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO2dCQUMzQixNQUFNO1FBQ1YsQ0FBQztRQUNELFVBQVUsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDbEQsQ0FBQyxDQUFDO0lBRUYsT0FBTztRQUNMLFNBQVM7UUFDVCxRQUFRO1FBQ1IsR0FBRztLQUNKLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGdldElkZW50aXR5LCBSZXF1ZXN0SWRlbnRpdHksIGNyZWF0ZUxvZ2dlciwgSHR0cFJlcXVlc3QgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBSZXF1ZXN0IH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyB2NyBhcyB1dWlkIH0gZnJvbSAndXVpZCc7XG5pbXBvcnQgeyBTU0VFdmVudFR5cGUsIFNTRU1hbmFnZXIgfSBmcm9tICcuLi9odHRwL3NzZS1jb25uZWN0aW9uLW1hbmFnZXInO1xuXG4vLyBDb25zb2xpZGF0ZWQgRXhwcmVzcyBSZXF1ZXN0IGF1Z21lbnRhdGlvbnNcbmRlY2xhcmUgZ2xvYmFsIHtcbiAgbmFtZXNwYWNlIEV4cHJlc3Mge1xuICAgIGludGVyZmFjZSBSZXF1ZXN0IHtcbiAgICAgIC8qKiBVbmlxdWUgcmVxdWVzdCBJRCAoc2V0IGJ5IGFwcC1mYWN0b3J5IG1pZGRsZXdhcmUpICovXG4gICAgICByZXF1ZXN0SWQ/OiBzdHJpbmc7XG4gICAgICAvKiogUmVxdWVzdCBjb250ZXh0IHdpdGggaWRlbnRpdHksIGxvZ2dpbmcsIGFuZCBTU0UgKi9cbiAgICAgIGNvbnRleHQ/OiBSZXF1ZXN0Q29udGV4dDtcbiAgICB9XG4gIH1cbn1cblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdhcGktc2VydmVyJyk7XG5cbi8qKlxuICogUmVxdWVzdCBsb2dnZXIgZnVuY3Rpb24gdHlwZVxuICovXG5leHBvcnQgdHlwZSBSZXF1ZXN0TG9nZ2VyID0gKHR5cGU6IFNTRUV2ZW50VHlwZSwgbWVzc2FnZTogc3RyaW5nLCBkYXRhPzogdW5rbm93bikgPT4gdm9pZDtcblxuLyoqXG4gKiBSZXF1ZXN0IGNvbnRleHQgd2l0aCBpZGVudGl0eSBhbmQgbG9nZ2luZ1xuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcXVlc3RDb250ZXh0IHtcbiAgLyoqIFVuaXF1ZSByZXF1ZXN0IElEICovXG4gIHJlcXVlc3RJZDogc3RyaW5nO1xuICAvKiogSWRlbnRpdHkgZnJvbSBoZWFkZXJzICovXG4gIGlkZW50aXR5OiBSZXF1ZXN0SWRlbnRpdHk7XG4gIC8qKiBMb2dnaW5nIGZ1bmN0aW9uIHRoYXQgc2VuZHMgdG8gY29uc29sZSBhbmQgU1NFICovXG4gIGxvZzogUmVxdWVzdExvZ2dlcjtcbn1cblxuLyoqXG4gKiBDcmVhdGUgYSByZXF1ZXN0IGNvbnRleHQgd2l0aCBpZGVudGl0eSBhbmQgbG9nZ2luZ1xuICpcbiAqIENyZWF0ZXMgYSBsb2dnZXIgdGhhdCBvdXRwdXRzIHRvIGJvdGggY29uc29sZSBhbmQgU1NFLlxuICpcbiAqIEBwYXJhbSByZXEgLSBFeHByZXNzIHJlcXVlc3RcbiAqIEBwYXJhbSBzc2VNYW5hZ2VyIC0gU1NFIG1hbmFnZXIgZm9yIHJlYWwtdGltZSBsb2dzXG4gKiBAcmV0dXJucyBSZXF1ZXN0IGNvbnRleHQgd2l0aCBpZGVudGl0eSBhbmQgbG9nZ2VyXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGFwcC5wb3N0KCcvYXBpL3Jlc291cmNlJywgcmVxdWlyZUF1dGgsIGFzeW5jIChyZXEsIHJlcykgPT4ge1xuICogICBjb25zdCBjdHggPSBjcmVhdGVSZXF1ZXN0Q29udGV4dChyZXEsIHNzZU1hbmFnZXIpO1xuICpcbiAqICAgY3R4LmxvZygnSU5GTycsICdQcm9jZXNzaW5nIHJlcXVlc3QnLCB7IGRhdGE6IHJlcS5ib2R5IH0pO1xuICpcbiAqICAgaWYgKCFjdHguaWRlbnRpdHkub3JnSWQpIHtcbiAqICAgICBjdHgubG9nKCdFUlJPUicsICdNaXNzaW5nIG9yZ2FuaXphdGlvbiBJRCcpO1xuICogICAgIHJldHVybiBzZW5kQmFkUmVxdWVzdChyZXMsICd4LW9yZy1pZCBoZWFkZXIgcmVxdWlyZWQnKTtcbiAqICAgfVxuICpcbiAqICAgLy8gUHJvY2VzcyByZXF1ZXN0Li4uXG4gKiAgIGN0eC5sb2coJ0NPTVBMRVRFRCcsICdSZXF1ZXN0IHByb2Nlc3NlZCBzdWNjZXNzZnVsbHknKTtcbiAqIH0pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVSZXF1ZXN0Q29udGV4dChcbiAgcmVxOiBSZXF1ZXN0LFxuICBzc2VNYW5hZ2VyOiBTU0VNYW5hZ2VyLFxuKTogUmVxdWVzdENvbnRleHQge1xuICBjb25zdCBpZGVudGl0eSA9IGdldElkZW50aXR5KHJlcSBhcyBIdHRwUmVxdWVzdCk7XG4gIC8vIFByZWZlciB0aGUgYWxyZWFkeS1wYXJzZWQgcmVxdWVzdElkIGZyb20gYXBwLWZhY3RvcnkgbWlkZGxld2FyZSxcbiAgLy8gZmFsbCBiYWNrIHRvIGlkZW50aXR5IGhlYWRlciwgdGhlbiBnZW5lcmF0ZSBhIG5ldyBvbmUuXG4gIGNvbnN0IHJlcXVlc3RJZCA9IHJlcS5yZXF1ZXN0SWQgfHwgaWRlbnRpdHkucmVxdWVzdElkIHx8IHV1aWQoKTtcblxuICAvLyBDcmVhdGUgbG9nZ2VyIHRoYXQgb3V0cHV0cyB0byBXaW5zdG9uIGFuZCBTU0VcbiAgY29uc3QgbG9nOiBSZXF1ZXN0TG9nZ2VyID0gKHR5cGUsIG1lc3NhZ2UsIGRhdGEpID0+IHtcbiAgICBjb25zdCBtZXRhID0geyByZXF1ZXN0SWQsIG9yZ0lkOiBpZGVudGl0eS5vcmdJZCwgdHlwZSwgZGF0YSB9O1xuICAgIHN3aXRjaCAodHlwZSkge1xuICAgICAgY2FzZSAnRVJST1InOlxuICAgICAgICBsb2dnZXIuZXJyb3IobWVzc2FnZSwgbWV0YSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAnV0FSTic6XG4gICAgICAgIGxvZ2dlci53YXJuKG1lc3NhZ2UsIG1ldGEpO1xuICAgICAgICBicmVhaztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGxvZ2dlci5pbmZvKG1lc3NhZ2UsIG1ldGEpO1xuICAgICAgICBicmVhaztcbiAgICB9XG4gICAgc3NlTWFuYWdlci5zZW5kKHJlcXVlc3RJZCwgdHlwZSwgbWVzc2FnZSwgZGF0YSk7XG4gIH07XG5cbiAgcmV0dXJuIHtcbiAgICByZXF1ZXN0SWQsXG4gICAgaWRlbnRpdHksXG4gICAgbG9nLFxuICB9O1xufVxuIl19
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Create middleware that validates the request has an orgId in the identity headers.
|
|
4
|
+
*
|
|
5
|
+
* @returns Express middleware
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const { app, sseManager } = createApp();
|
|
10
|
+
*
|
|
11
|
+
* app.get('/pipelines', requireAuth, requireOrgId(), handler);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function requireOrgId(): (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -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.requireOrgId = requireOrgId;
|
|
6
|
+
const api_core_1 = require("@pipeline-builder/api-core");
|
|
7
|
+
const get_context_1 = require("./get-context");
|
|
8
|
+
/**
|
|
9
|
+
* Create middleware that validates the request has an orgId in the identity headers.
|
|
10
|
+
*
|
|
11
|
+
* @returns Express middleware
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const { app, sseManager } = createApp();
|
|
16
|
+
*
|
|
17
|
+
* app.get('/pipelines', requireAuth, requireOrgId(), handler);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function requireOrgId() {
|
|
21
|
+
return (req, res, next) => {
|
|
22
|
+
const ctx = (0, get_context_1.getContext)(req);
|
|
23
|
+
if (!ctx.identity.orgId) {
|
|
24
|
+
ctx.log('ERROR', 'Organization ID is missing from request headers');
|
|
25
|
+
(0, api_core_1.sendError)(res, 400, 'Organization ID is required. Please provide x-org-id header.', api_core_1.ErrorCode.VALIDATION_ERROR);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
next();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWlyZS1vcmctaWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL3JlcXVpcmUtb3JnLWlkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDOztBQWtCdEMsb0NBWUM7QUE1QkQseURBQWtFO0FBRWxFLCtDQUEyQztBQUUzQzs7Ozs7Ozs7Ozs7R0FXRztBQUNILFNBQWdCLFlBQVk7SUFDMUIsT0FBTyxDQUFDLEdBQVksRUFBRSxHQUFhLEVBQUUsSUFBa0IsRUFBUSxFQUFFO1FBQy9ELE1BQU0sR0FBRyxHQUFHLElBQUEsd0JBQVUsRUFBQyxHQUFHLENBQUMsQ0FBQztRQUU1QixJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUN4QixHQUFHLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxpREFBaUQsQ0FBQyxDQUFDO1lBQ3BFLElBQUEsb0JBQVMsRUFBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLDhEQUE4RCxFQUFFLG9CQUFTLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztZQUNoSCxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksRUFBRSxDQUFDO0lBQ1QsQ0FBQyxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBFcnJvckNvZGUsIHNlbmRFcnJvciB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL2FwaS1jb3JlJztcbmltcG9ydCB7IFJlcXVlc3QsIFJlc3BvbnNlLCBOZXh0RnVuY3Rpb24gfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IGdldENvbnRleHQgfSBmcm9tICcuL2dldC1jb250ZXh0JztcblxuLyoqXG4gKiBDcmVhdGUgbWlkZGxld2FyZSB0aGF0IHZhbGlkYXRlcyB0aGUgcmVxdWVzdCBoYXMgYW4gb3JnSWQgaW4gdGhlIGlkZW50aXR5IGhlYWRlcnMuXG4gKlxuICogQHJldHVybnMgRXhwcmVzcyBtaWRkbGV3YXJlXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNvbnN0IHsgYXBwLCBzc2VNYW5hZ2VyIH0gPSBjcmVhdGVBcHAoKTtcbiAqXG4gKiBhcHAuZ2V0KCcvcGlwZWxpbmVzJywgcmVxdWlyZUF1dGgsIHJlcXVpcmVPcmdJZCgpLCBoYW5kbGVyKTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVxdWlyZU9yZ0lkKCkge1xuICByZXR1cm4gKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCA9PiB7XG4gICAgY29uc3QgY3R4ID0gZ2V0Q29udGV4dChyZXEpO1xuXG4gICAgaWYgKCFjdHguaWRlbnRpdHkub3JnSWQpIHtcbiAgICAgIGN0eC5sb2coJ0VSUk9SJywgJ09yZ2FuaXphdGlvbiBJRCBpcyBtaXNzaW5nIGZyb20gcmVxdWVzdCBoZWFkZXJzJyk7XG4gICAgICBzZW5kRXJyb3IocmVzLCA0MDAsICdPcmdhbml6YXRpb24gSUQgaXMgcmVxdWlyZWQuIFBsZWFzZSBwcm92aWRlIHgtb3JnLWlkIGhlYWRlci4nLCBFcnJvckNvZGUuVkFMSURBVElPTl9FUlJPUik7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbmV4dCgpO1xuICB9O1xufVxuIl19
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Request, Response, RequestHandler } from 'express';
|
|
2
|
+
import type { RequestContext } from './request-types';
|
|
3
|
+
/**
|
|
4
|
+
* Context object passed to every route handler wrapped with `withRoute()`.
|
|
5
|
+
*/
|
|
6
|
+
export interface RouteContext {
|
|
7
|
+
/** Original Express request */
|
|
8
|
+
req: Request;
|
|
9
|
+
/** Original Express response */
|
|
10
|
+
res: Response;
|
|
11
|
+
/** Request context with identity and logging */
|
|
12
|
+
ctx: RequestContext;
|
|
13
|
+
/** Lowercased organization ID (guaranteed non-empty when requireOrgId is true) */
|
|
14
|
+
orgId: string;
|
|
15
|
+
/** User ID from JWT (defaults to empty string) */
|
|
16
|
+
userId: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Options for `withRoute()`.
|
|
20
|
+
*/
|
|
21
|
+
export interface WithRouteOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Whether to require orgId on the request.
|
|
24
|
+
* When true (default), returns 400 if orgId is missing.
|
|
25
|
+
* Set to false for routes that don't need org context.
|
|
26
|
+
*/
|
|
27
|
+
requireOrgId?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Wrap an async route handler with standard boilerplate.
|
|
31
|
+
*
|
|
32
|
+
* Extracts context, orgId, userId from the request, validates orgId,
|
|
33
|
+
* and catches errors. Typed `AppError` subclasses are automatically
|
|
34
|
+
* mapped to the correct HTTP response.
|
|
35
|
+
*
|
|
36
|
+
* @param handler - Async function receiving a RouteContext
|
|
37
|
+
* @param options - Configuration options
|
|
38
|
+
* @returns Express RequestHandler
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* router.get('/:id', withRoute(async ({ req, res, ctx, orgId }) => {
|
|
43
|
+
* const id = getParam(req.params, 'id');
|
|
44
|
+
* const result = await pipelineService.findById(id, orgId);
|
|
45
|
+
* if (!result) throw new NotFoundError('Pipeline not found');
|
|
46
|
+
* return sendSuccess(res, 200, { pipeline: result });
|
|
47
|
+
* }));
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function withRoute(handler: (rc: RouteContext) => Promise<void>, options?: WithRouteOptions): RequestHandler;
|
|
@@ -0,0 +1,62 @@
|
|
|
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.withRoute = withRoute;
|
|
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)('route-wrapper');
|
|
9
|
+
/**
|
|
10
|
+
* Wrap an async route handler with standard boilerplate.
|
|
11
|
+
*
|
|
12
|
+
* Extracts context, orgId, userId from the request, validates orgId,
|
|
13
|
+
* and catches errors. Typed `AppError` subclasses are automatically
|
|
14
|
+
* mapped to the correct HTTP response.
|
|
15
|
+
*
|
|
16
|
+
* @param handler - Async function receiving a RouteContext
|
|
17
|
+
* @param options - Configuration options
|
|
18
|
+
* @returns Express RequestHandler
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* router.get('/:id', withRoute(async ({ req, res, ctx, orgId }) => {
|
|
23
|
+
* const id = getParam(req.params, 'id');
|
|
24
|
+
* const result = await pipelineService.findById(id, orgId);
|
|
25
|
+
* if (!result) throw new NotFoundError('Pipeline not found');
|
|
26
|
+
* return sendSuccess(res, 200, { pipeline: result });
|
|
27
|
+
* }));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function withRoute(handler, options = {}) {
|
|
31
|
+
const { requireOrgId = true } = options;
|
|
32
|
+
return async (req, res) => {
|
|
33
|
+
const ctx = (0, get_context_1.getContext)(req);
|
|
34
|
+
const orgId = ctx.identity.orgId?.toLowerCase() || '';
|
|
35
|
+
const userId = ctx.identity.userId || '';
|
|
36
|
+
if (requireOrgId && !orgId) {
|
|
37
|
+
return (0, api_core_1.sendBadRequest)(res, 'Organization ID is required');
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await handler({ req, res, ctx, orgId, userId });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// Don't send another response if one was already sent
|
|
44
|
+
if (res.headersSent) {
|
|
45
|
+
logger.error('Route handler error after response sent', {
|
|
46
|
+
requestId: ctx.requestId,
|
|
47
|
+
error: (0, api_core_1.errorMessage)(error),
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (error instanceof api_core_1.AppError) {
|
|
52
|
+
const status = error.statusCode >= 400 && error.statusCode < 600
|
|
53
|
+
? error.statusCode
|
|
54
|
+
: 500;
|
|
55
|
+
return (0, api_core_1.sendError)(res, status, error.message, error.code);
|
|
56
|
+
}
|
|
57
|
+
ctx.log('ERROR', 'Request failed', { error: (0, api_core_1.errorMessage)(error) });
|
|
58
|
+
return (0, api_core_1.sendInternalError)(res, (0, api_core_1.errorMessage)(error));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtd3JhcHBlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvcm91dGUtd3JhcHBlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUFpRXRDLDhCQXNDQztBQXJHRCx5REFPb0M7QUFFcEMsK0NBQTJDO0FBRzNDLE1BQU0sTUFBTSxHQUFHLElBQUEsdUJBQVksRUFBQyxlQUFlLENBQUMsQ0FBQztBQThCN0M7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBb0JHO0FBQ0gsU0FBZ0IsU0FBUyxDQUN2QixPQUE0QyxFQUM1QyxVQUE0QixFQUFFO0lBRTlCLE1BQU0sRUFBRSxZQUFZLEdBQUcsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDO0lBRXhDLE9BQU8sS0FBSyxFQUFFLEdBQVksRUFBRSxHQUFhLEVBQUUsRUFBRTtRQUMzQyxNQUFNLEdBQUcsR0FBRyxJQUFBLHdCQUFVLEVBQUMsR0FBRyxDQUFDLENBQUM7UUFDNUIsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsV0FBVyxFQUFFLElBQUksRUFBRSxDQUFDO1FBQ3RELE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxRQUFRLENBQUMsTUFBTSxJQUFJLEVBQUUsQ0FBQztRQUV6QyxJQUFJLFlBQVksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQzNCLE9BQU8sSUFBQSx5QkFBYyxFQUFDLEdBQUcsRUFBRSw2QkFBNkIsQ0FBQyxDQUFDO1FBQzVELENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sQ0FBQyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ2xELENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2Ysc0RBQXNEO1lBQ3RELElBQUksR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUNwQixNQUFNLENBQUMsS0FBSyxDQUFDLHlDQUF5QyxFQUFFO29CQUN0RCxTQUFTLEVBQUUsR0FBRyxDQUFDLFNBQVM7b0JBQ3hCLEtBQUssRUFBRSxJQUFBLHVCQUFZLEVBQUMsS0FBSyxDQUFDO2lCQUMzQixDQUFDLENBQUM7Z0JBQ0gsT0FBTztZQUNULENBQUM7WUFFRCxJQUFJLEtBQUssWUFBWSxtQkFBUSxFQUFFLENBQUM7Z0JBQzlCLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxVQUFVLElBQUksR0FBRyxJQUFJLEtBQUssQ0FBQyxVQUFVLEdBQUcsR0FBRztvQkFDOUQsQ0FBQyxDQUFDLEtBQUssQ0FBQyxVQUFVO29CQUNsQixDQUFDLENBQUMsR0FBRyxDQUFDO2dCQUNSLE9BQU8sSUFBQSxvQkFBUyxFQUFDLEdBQUcsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDM0QsQ0FBQztZQUVELEdBQUcsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUEsdUJBQVksRUFBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDbkUsT0FBTyxJQUFBLDRCQUFpQixFQUFDLEdBQUcsRUFBRSxJQUFBLHVCQUFZLEVBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUNyRCxDQUFDO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQge1xuICBBcHBFcnJvcixcbiAgZXJyb3JNZXNzYWdlLFxuICBzZW5kRXJyb3IsXG4gIHNlbmRCYWRSZXF1ZXN0LFxuICBzZW5kSW50ZXJuYWxFcnJvcixcbiAgY3JlYXRlTG9nZ2VyLFxufSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlLCBSZXF1ZXN0SGFuZGxlciB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHsgZ2V0Q29udGV4dCB9IGZyb20gJy4vZ2V0LWNvbnRleHQnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0Q29udGV4dCB9IGZyb20gJy4vcmVxdWVzdC10eXBlcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcigncm91dGUtd3JhcHBlcicpO1xuXG4vKipcbiAqIENvbnRleHQgb2JqZWN0IHBhc3NlZCB0byBldmVyeSByb3V0ZSBoYW5kbGVyIHdyYXBwZWQgd2l0aCBgd2l0aFJvdXRlKClgLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJvdXRlQ29udGV4dCB7XG4gIC8qKiBPcmlnaW5hbCBFeHByZXNzIHJlcXVlc3QgKi9cbiAgcmVxOiBSZXF1ZXN0O1xuICAvKiogT3JpZ2luYWwgRXhwcmVzcyByZXNwb25zZSAqL1xuICByZXM6IFJlc3BvbnNlO1xuICAvKiogUmVxdWVzdCBjb250ZXh0IHdpdGggaWRlbnRpdHkgYW5kIGxvZ2dpbmcgKi9cbiAgY3R4OiBSZXF1ZXN0Q29udGV4dDtcbiAgLyoqIExvd2VyY2FzZWQgb3JnYW5pemF0aW9uIElEIChndWFyYW50ZWVkIG5vbi1lbXB0eSB3aGVuIHJlcXVpcmVPcmdJZCBpcyB0cnVlKSAqL1xuICBvcmdJZDogc3RyaW5nO1xuICAvKiogVXNlciBJRCBmcm9tIEpXVCAoZGVmYXVsdHMgdG8gZW1wdHkgc3RyaW5nKSAqL1xuICB1c2VySWQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBPcHRpb25zIGZvciBgd2l0aFJvdXRlKClgLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFdpdGhSb3V0ZU9wdGlvbnMge1xuICAvKipcbiAgICogV2hldGhlciB0byByZXF1aXJlIG9yZ0lkIG9uIHRoZSByZXF1ZXN0LlxuICAgKiBXaGVuIHRydWUgKGRlZmF1bHQpLCByZXR1cm5zIDQwMCBpZiBvcmdJZCBpcyBtaXNzaW5nLlxuICAgKiBTZXQgdG8gZmFsc2UgZm9yIHJvdXRlcyB0aGF0IGRvbid0IG5lZWQgb3JnIGNvbnRleHQuXG4gICAqL1xuICByZXF1aXJlT3JnSWQ/OiBib29sZWFuO1xufVxuXG4vKipcbiAqIFdyYXAgYW4gYXN5bmMgcm91dGUgaGFuZGxlciB3aXRoIHN0YW5kYXJkIGJvaWxlcnBsYXRlLlxuICpcbiAqIEV4dHJhY3RzIGNvbnRleHQsIG9yZ0lkLCB1c2VySWQgZnJvbSB0aGUgcmVxdWVzdCwgdmFsaWRhdGVzIG9yZ0lkLFxuICogYW5kIGNhdGNoZXMgZXJyb3JzLiBUeXBlZCBgQXBwRXJyb3JgIHN1YmNsYXNzZXMgYXJlIGF1dG9tYXRpY2FsbHlcbiAqIG1hcHBlZCB0byB0aGUgY29ycmVjdCBIVFRQIHJlc3BvbnNlLlxuICpcbiAqIEBwYXJhbSBoYW5kbGVyIC0gQXN5bmMgZnVuY3Rpb24gcmVjZWl2aW5nIGEgUm91dGVDb250ZXh0XG4gKiBAcGFyYW0gb3B0aW9ucyAtIENvbmZpZ3VyYXRpb24gb3B0aW9uc1xuICogQHJldHVybnMgRXhwcmVzcyBSZXF1ZXN0SGFuZGxlclxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiByb3V0ZXIuZ2V0KCcvOmlkJywgd2l0aFJvdXRlKGFzeW5jICh7IHJlcSwgcmVzLCBjdHgsIG9yZ0lkIH0pID0+IHtcbiAqICAgY29uc3QgaWQgPSBnZXRQYXJhbShyZXEucGFyYW1zLCAnaWQnKTtcbiAqICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgcGlwZWxpbmVTZXJ2aWNlLmZpbmRCeUlkKGlkLCBvcmdJZCk7XG4gKiAgIGlmICghcmVzdWx0KSB0aHJvdyBuZXcgTm90Rm91bmRFcnJvcignUGlwZWxpbmUgbm90IGZvdW5kJyk7XG4gKiAgIHJldHVybiBzZW5kU3VjY2VzcyhyZXMsIDIwMCwgeyBwaXBlbGluZTogcmVzdWx0IH0pO1xuICogfSkpO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3aXRoUm91dGUoXG4gIGhhbmRsZXI6IChyYzogUm91dGVDb250ZXh0KSA9PiBQcm9taXNlPHZvaWQ+LFxuICBvcHRpb25zOiBXaXRoUm91dGVPcHRpb25zID0ge30sXG4pOiBSZXF1ZXN0SGFuZGxlciB7XG4gIGNvbnN0IHsgcmVxdWlyZU9yZ0lkID0gdHJ1ZSB9ID0gb3B0aW9ucztcblxuICByZXR1cm4gYXN5bmMgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGNvbnN0IGN0eCA9IGdldENvbnRleHQocmVxKTtcbiAgICBjb25zdCBvcmdJZCA9IGN0eC5pZGVudGl0eS5vcmdJZD8udG9Mb3dlckNhc2UoKSB8fCAnJztcbiAgICBjb25zdCB1c2VySWQgPSBjdHguaWRlbnRpdHkudXNlcklkIHx8ICcnO1xuXG4gICAgaWYgKHJlcXVpcmVPcmdJZCAmJiAhb3JnSWQpIHtcbiAgICAgIHJldHVybiBzZW5kQmFkUmVxdWVzdChyZXMsICdPcmdhbml6YXRpb24gSUQgaXMgcmVxdWlyZWQnKTtcbiAgICB9XG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgaGFuZGxlcih7IHJlcSwgcmVzLCBjdHgsIG9yZ0lkLCB1c2VySWQgfSk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIERvbid0IHNlbmQgYW5vdGhlciByZXNwb25zZSBpZiBvbmUgd2FzIGFscmVhZHkgc2VudFxuICAgICAgaWYgKHJlcy5oZWFkZXJzU2VudCkge1xuICAgICAgICBsb2dnZXIuZXJyb3IoJ1JvdXRlIGhhbmRsZXIgZXJyb3IgYWZ0ZXIgcmVzcG9uc2Ugc2VudCcsIHtcbiAgICAgICAgICByZXF1ZXN0SWQ6IGN0eC5yZXF1ZXN0SWQsXG4gICAgICAgICAgZXJyb3I6IGVycm9yTWVzc2FnZShlcnJvciksXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEFwcEVycm9yKSB7XG4gICAgICAgIGNvbnN0IHN0YXR1cyA9IGVycm9yLnN0YXR1c0NvZGUgPj0gNDAwICYmIGVycm9yLnN0YXR1c0NvZGUgPCA2MDBcbiAgICAgICAgICA/IGVycm9yLnN0YXR1c0NvZGVcbiAgICAgICAgICA6IDUwMDtcbiAgICAgICAgcmV0dXJuIHNlbmRFcnJvcihyZXMsIHN0YXR1cywgZXJyb3IubWVzc2FnZSwgZXJyb3IuY29kZSk7XG4gICAgICB9XG5cbiAgICAgIGN0eC5sb2coJ0VSUk9SJywgJ1JlcXVlc3QgZmFpbGVkJywgeyBlcnJvcjogZXJyb3JNZXNzYWdlKGVycm9yKSB9KTtcbiAgICAgIHJldHVybiBzZW5kSW50ZXJuYWxFcnJvcihyZXMsIGVycm9yTWVzc2FnZShlcnJvcikpO1xuICAgIH1cbiAgfTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Server } from 'http';
|
|
2
|
+
import { Express } from 'express';
|
|
3
|
+
import { SSEManager } from '../http/sse-connection-manager';
|
|
4
|
+
/**
|
|
5
|
+
* Options for starting a server
|
|
6
|
+
*/
|
|
7
|
+
export interface StartServerOptions {
|
|
8
|
+
/** Service name for logging */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** Port to listen on (default: from config) */
|
|
11
|
+
port?: number;
|
|
12
|
+
/** SSE manager for graceful shutdown */
|
|
13
|
+
sseManager?: SSEManager;
|
|
14
|
+
/** Shutdown timeout in milliseconds (default: 15000) */
|
|
15
|
+
shutdownTimeoutMs?: number;
|
|
16
|
+
/** Callback after server starts */
|
|
17
|
+
onStart?: (port: number) => void;
|
|
18
|
+
/** Callback before shutdown */
|
|
19
|
+
onShutdown?: () => Promise<void>;
|
|
20
|
+
/** Runs before database check (e.g., initialize external connections) */
|
|
21
|
+
onBeforeStart?: () => Promise<void>;
|
|
22
|
+
/** Custom database health check, or false to skip. Default: PostgreSQL testConnection */
|
|
23
|
+
testDatabase?: (() => Promise<boolean>) | false;
|
|
24
|
+
/** Custom database close, or false to skip. Default: PostgreSQL closeConnection */
|
|
25
|
+
closeDatabase?: (() => Promise<void>) | false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Result of starting a server
|
|
29
|
+
*/
|
|
30
|
+
export interface StartServerResult {
|
|
31
|
+
/** HTTP server instance */
|
|
32
|
+
server: Server;
|
|
33
|
+
/** Port the server is listening on */
|
|
34
|
+
port: number;
|
|
35
|
+
/** Function to manually trigger shutdown */
|
|
36
|
+
shutdown: () => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Start an Express server with graceful shutdown handling
|
|
40
|
+
*
|
|
41
|
+
* Features:
|
|
42
|
+
* - Automatic database connection testing
|
|
43
|
+
* - Graceful shutdown on SIGINT/SIGTERM
|
|
44
|
+
* - SSE connection cleanup
|
|
45
|
+
* - Configurable shutdown timeout
|
|
46
|
+
*
|
|
47
|
+
* @param app - Express application
|
|
48
|
+
* @param options - Server options
|
|
49
|
+
* @returns Server instance and control functions
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const { app, sseManager } = createApp();
|
|
54
|
+
*
|
|
55
|
+
* app.post('/api/resource', requireAuth, handler);
|
|
56
|
+
*
|
|
57
|
+
* await startServer(app, {
|
|
58
|
+
* name: 'My Microservice',
|
|
59
|
+
* sseManager,
|
|
60
|
+
* onStart: (port) => console.log(`Listening on ${port}`),
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function startServer(app: Express, options?: StartServerOptions): Promise<StartServerResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Simple server start wrapper with error handling
|
|
67
|
+
*
|
|
68
|
+
* @param app - Express application
|
|
69
|
+
* @param options - Server options
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const { app, sseManager } = createApp();
|
|
74
|
+
* app.post('/api/resource', handler);
|
|
75
|
+
*
|
|
76
|
+
* void runServer(app, { name: 'My Service', sseManager });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function runServer(app: Express, options?: StartServerOptions): void;
|