@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.
Files changed (45) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +39 -0
  3. package/lib/api/app-factory.d.ts +71 -0
  4. package/lib/api/app-factory.js +212 -0
  5. package/lib/api/check-quota.d.ts +13 -0
  6. package/lib/api/check-quota.js +67 -0
  7. package/lib/api/context-middleware.d.ts +34 -0
  8. package/lib/api/context-middleware.js +45 -0
  9. package/lib/api/etag-middleware.d.ts +7 -0
  10. package/lib/api/etag-middleware.js +37 -0
  11. package/lib/api/get-context.d.ts +22 -0
  12. package/lib/api/get-context.js +31 -0
  13. package/lib/api/health-checks.d.ts +25 -0
  14. package/lib/api/health-checks.js +36 -0
  15. package/lib/api/idempotency-middleware.d.ts +9 -0
  16. package/lib/api/idempotency-middleware.js +64 -0
  17. package/lib/api/index.d.ts +15 -0
  18. package/lib/api/index.js +43 -0
  19. package/lib/api/metrics.d.ts +12 -0
  20. package/lib/api/metrics.js +83 -0
  21. package/lib/api/middleware-factory.d.ts +47 -0
  22. package/lib/api/middleware-factory.js +66 -0
  23. package/lib/api/middleware.d.ts +1 -0
  24. package/lib/api/middleware.js +14 -0
  25. package/lib/api/quota-helpers.d.ts +23 -0
  26. package/lib/api/quota-helpers.js +25 -0
  27. package/lib/api/request-types.d.ts +55 -0
  28. package/lib/api/request-types.js +62 -0
  29. package/lib/api/require-org-id.d.ts +14 -0
  30. package/lib/api/require-org-id.js +31 -0
  31. package/lib/api/route-wrapper.d.ts +50 -0
  32. package/lib/api/route-wrapper.js +62 -0
  33. package/lib/api/server.d.ts +79 -0
  34. package/lib/api/server.js +144 -0
  35. package/lib/api/tracing.d.ts +15 -0
  36. package/lib/api/tracing.js +53 -0
  37. package/lib/http/index.d.ts +2 -0
  38. package/lib/http/index.js +21 -0
  39. package/lib/http/sse-connection-manager.d.ts +145 -0
  40. package/lib/http/sse-connection-manager.js +329 -0
  41. package/lib/http/ws-manager.d.ts +37 -0
  42. package/lib/http/ws-manager.js +105 -0
  43. package/lib/index.d.ts +30 -0
  44. package/lib/index.js +51 -0
  45. 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;