@pipeline-builder/api-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +79 -0
- package/lib/api/app-factory.d.ts +95 -0
- package/lib/api/app-factory.js +328 -0
- package/lib/api/check-quota.d.ts +13 -0
- package/lib/api/check-quota.js +74 -0
- package/lib/api/context-middleware.d.ts +34 -0
- package/lib/api/context-middleware.js +42 -0
- package/lib/api/etag-middleware.d.ts +7 -0
- package/lib/api/etag-middleware.js +44 -0
- package/lib/api/get-context.d.ts +22 -0
- package/lib/api/get-context.js +28 -0
- package/lib/api/health-checks.d.ts +62 -0
- package/lib/api/health-checks.js +87 -0
- package/lib/api/idempotency-middleware.d.ts +80 -0
- package/lib/api/idempotency-middleware.js +321 -0
- package/lib/api/index.d.ts +20 -0
- package/lib/api/index.js +34 -0
- package/lib/api/meter-quota.d.ts +32 -0
- package/lib/api/meter-quota.js +70 -0
- package/lib/api/metrics.d.ts +47 -0
- package/lib/api/metrics.js +221 -0
- package/lib/api/middleware-factory.d.ts +56 -0
- package/lib/api/middleware-factory.js +80 -0
- package/lib/api/middleware.d.ts +1 -0
- package/lib/api/middleware.js +8 -0
- package/lib/api/mongo-connect.d.ts +19 -0
- package/lib/api/mongo-connect.js +41 -0
- package/lib/api/quota-helpers.d.ts +23 -0
- package/lib/api/quota-helpers.js +22 -0
- package/lib/api/rate-limit-by-org.d.ts +29 -0
- package/lib/api/rate-limit-by-org.js +64 -0
- package/lib/api/readiness.d.ts +29 -0
- package/lib/api/readiness.js +65 -0
- package/lib/api/request-types.d.ts +55 -0
- package/lib/api/request-types.js +65 -0
- package/lib/api/require-org-id.d.ts +14 -0
- package/lib/api/require-org-id.js +28 -0
- package/lib/api/route-wrapper.d.ts +50 -0
- package/lib/api/route-wrapper.js +84 -0
- package/lib/api/server.d.ts +79 -0
- package/lib/api/server.js +234 -0
- package/lib/api/tenant-context.d.ts +28 -0
- package/lib/api/tenant-context.js +48 -0
- package/lib/api/tracing.d.ts +25 -0
- package/lib/api/tracing.js +69 -0
- package/lib/http/index.d.ts +4 -0
- package/lib/http/index.js +7 -0
- package/lib/http/sse-connection-manager.d.ts +357 -0
- package/lib/http/sse-connection-manager.js +665 -0
- package/lib/http/sse-relay.d.ts +63 -0
- package/lib/http/sse-relay.js +70 -0
- package/lib/http/sse-ticket-channel.d.ts +31 -0
- package/lib/http/sse-ticket-channel.js +52 -0
- package/lib/http/sse-ticket-store.d.ts +75 -0
- package/lib/http/sse-ticket-store.js +160 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +35 -0
- package/lib/otel-bootstrap.d.ts +1 -0
- package/lib/otel-bootstrap.js +75 -0
- package/package.json +183 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SSEPayload } from './sse-connection-manager.js';
|
|
2
|
+
/**
|
|
3
|
+
* A relayed SSE frame. `origin` is the id of the SSEManager instance that
|
|
4
|
+
* published it, so the publishing pod can ignore its own echo (it already wrote
|
|
5
|
+
* the frame to its local clients before publishing). `kind` distinguishes a
|
|
6
|
+
* per-subject `send` (carries `requestId`) from a `broadcast` (no subject —
|
|
7
|
+
* every pod re-emits to ALL of its local clients).
|
|
8
|
+
*/
|
|
9
|
+
export interface SSERelayMessage {
|
|
10
|
+
origin: string;
|
|
11
|
+
kind: 'send' | 'broadcast';
|
|
12
|
+
/** Present for `send`; the stream subject the payload targets. */
|
|
13
|
+
requestId?: string;
|
|
14
|
+
payload: SSEPayload;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cross-pod fan-out bus for SSE frames.
|
|
18
|
+
*
|
|
19
|
+
* Under multiple replicas the producer of a log frame (e.g. a plugin build
|
|
20
|
+
* worker on pod A) and the consumer (a browser EventSource on pod B) are on
|
|
21
|
+
* DIFFERENT pods. The in-process client map only reaches the producer's own
|
|
22
|
+
* pod, so B's stream would silently miss frames. The relay closes that: every
|
|
23
|
+
* pod SUBSCRIBES once on startup and re-emits received frames to its LOCAL
|
|
24
|
+
* clients, while `send()`/`broadcast()` PUBLISH to the bus (in addition to
|
|
25
|
+
* writing locally). The origin tag prevents a pod from double-delivering its own
|
|
26
|
+
* frame when it receives its own publish back.
|
|
27
|
+
*
|
|
28
|
+
* All methods are best-effort / fail-safe: a bus outage must degrade to
|
|
29
|
+
* local-only delivery (today's single-replica behavior), never crash the stream.
|
|
30
|
+
*/
|
|
31
|
+
export interface SSERelay {
|
|
32
|
+
/** Fire-and-forget publish of a frame to every subscribed pod. Never throws. */
|
|
33
|
+
publish(msg: SSERelayMessage): void;
|
|
34
|
+
/** Subscribe this pod's re-emit handler. Called once on manager construction. */
|
|
35
|
+
subscribe(handler: (msg: SSERelayMessage) => void): void;
|
|
36
|
+
/** Tear down the underlying connection(s). */
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Minimal ioredis pub/sub surface the Redis relay needs. `subscribe` requires a
|
|
41
|
+
* DEDICATED connection (a subscribed ioredis client can't run other commands),
|
|
42
|
+
* so the relay `duplicate()`s the publisher for the subscriber side.
|
|
43
|
+
*/
|
|
44
|
+
export interface RedisPubSubClient {
|
|
45
|
+
publish(channel: string, message: string): Promise<number>;
|
|
46
|
+
subscribe(...channels: string[]): Promise<unknown>;
|
|
47
|
+
on(event: 'message', cb: (channel: string, message: string) => void): void;
|
|
48
|
+
duplicate(): RedisPubSubClient;
|
|
49
|
+
quit(): Promise<unknown>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Redis-backed relay. Uses the given client for PUBLISH and a `duplicate()` for
|
|
53
|
+
* SUBSCRIBE (ioredis forbids mixing subscribe with normal commands on one
|
|
54
|
+
* connection). Publish is fire-and-forget with a swallowed rejection; a subscribe
|
|
55
|
+
* handler that throws is isolated so one bad frame can't kill the subscriber.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createRedisSSERelay(publisher: RedisPubSubClient): SSERelay;
|
|
58
|
+
/**
|
|
59
|
+
* Build a Redis-backed SSE relay from the shared env Redis (same wiring as the
|
|
60
|
+
* SSE ticket store / rate-limiter / audit-spool). Returns null when Redis isn't
|
|
61
|
+
* configured so the caller keeps local-only delivery. Never throws.
|
|
62
|
+
*/
|
|
63
|
+
export declare function createEnvRedisSSERelay(): SSERelay | null;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { createLogger, createEnvRedisClient } from '@pipeline-builder/api-core';
|
|
4
|
+
const logger = createLogger('sse-relay');
|
|
5
|
+
/**
|
|
6
|
+
* Single channel carrying every relayed frame. `requestId` rides in the message
|
|
7
|
+
* body rather than the channel name so each pod SUBSCRIBES exactly once on
|
|
8
|
+
* startup — no per-subject subscribe/unsubscribe churn as clients come and go.
|
|
9
|
+
*/
|
|
10
|
+
const RELAY_CHANNEL = 'sse:relay';
|
|
11
|
+
/**
|
|
12
|
+
* Redis-backed relay. Uses the given client for PUBLISH and a `duplicate()` for
|
|
13
|
+
* SUBSCRIBE (ioredis forbids mixing subscribe with normal commands on one
|
|
14
|
+
* connection). Publish is fire-and-forget with a swallowed rejection; a subscribe
|
|
15
|
+
* handler that throws is isolated so one bad frame can't kill the subscriber.
|
|
16
|
+
*/
|
|
17
|
+
export function createRedisSSERelay(publisher) {
|
|
18
|
+
const subscriber = publisher.duplicate();
|
|
19
|
+
let closed = false;
|
|
20
|
+
return {
|
|
21
|
+
publish(msg) {
|
|
22
|
+
if (closed)
|
|
23
|
+
return;
|
|
24
|
+
// Fire-and-forget: never await, never surface a rejection to the caller.
|
|
25
|
+
void Promise.resolve(publisher.publish(RELAY_CHANNEL, JSON.stringify(msg))).catch((err) => {
|
|
26
|
+
logger.warn('SSE relay publish failed', { error: err instanceof Error ? err.message : String(err) });
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
subscribe(handler) {
|
|
30
|
+
subscriber.on('message', (channel, message) => {
|
|
31
|
+
if (channel !== RELAY_CHANNEL)
|
|
32
|
+
return;
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(message);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
logger.warn('SSE relay received unparseable frame', { error: err instanceof Error ? err.message : String(err) });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
handler(parsed);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
logger.warn('SSE relay handler threw', { error: err instanceof Error ? err.message : String(err) });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
void Promise.resolve(subscriber.subscribe(RELAY_CHANNEL)).catch((err) => {
|
|
49
|
+
logger.warn('SSE relay subscribe failed — falling back to local-only delivery', { error: err instanceof Error ? err.message : String(err) });
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
async close() {
|
|
53
|
+
closed = true;
|
|
54
|
+
await Promise.allSettled([publisher.quit(), subscriber.quit()]);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a Redis-backed SSE relay from the shared env Redis (same wiring as the
|
|
60
|
+
* SSE ticket store / rate-limiter / audit-spool). Returns null when Redis isn't
|
|
61
|
+
* configured so the caller keeps local-only delivery. Never throws.
|
|
62
|
+
*/
|
|
63
|
+
export function createEnvRedisSSERelay() {
|
|
64
|
+
const client = createEnvRedisClient('sse-relay');
|
|
65
|
+
if (!client)
|
|
66
|
+
return null;
|
|
67
|
+
logger.info('Redis SSE relay initialized (cross-pod fan-out enabled)');
|
|
68
|
+
return createRedisSSERelay(client);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLXJlbGF5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2h0dHAvc3NlLXJlbGF5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLFlBQVksRUFBRSxvQkFBb0IsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBR2hGLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztBQXNEekM7Ozs7R0FJRztBQUNILE1BQU0sYUFBYSxHQUFHLFdBQVcsQ0FBQztBQUVsQzs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxTQUE0QjtJQUM5RCxNQUFNLFVBQVUsR0FBRyxTQUFTLENBQUMsU0FBUyxFQUFFLENBQUM7SUFDekMsSUFBSSxNQUFNLEdBQUcsS0FBSyxDQUFDO0lBRW5CLE9BQU87UUFDTCxPQUFPLENBQUMsR0FBRztZQUNULElBQUksTUFBTTtnQkFBRSxPQUFPO1lBQ25CLHlFQUF5RTtZQUN6RSxLQUFLLE9BQU8sQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7Z0JBQ3hGLE1BQU0sQ0FBQyxJQUFJLENBQUMsMEJBQTBCLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUN2RyxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUM7UUFDRCxTQUFTLENBQUMsT0FBTztZQUNmLFVBQVUsQ0FBQyxFQUFFLENBQUMsU0FBUyxFQUFFLENBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxFQUFFO2dCQUM1QyxJQUFJLE9BQU8sS0FBSyxhQUFhO29CQUFFLE9BQU87Z0JBQ3RDLElBQUksTUFBdUIsQ0FBQztnQkFDNUIsSUFBSSxDQUFDO29CQUNILE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBb0IsQ0FBQztnQkFDbEQsQ0FBQztnQkFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO29CQUNiLE1BQU0sQ0FBQyxJQUFJLENBQUMsc0NBQXNDLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztvQkFDakgsT0FBTztnQkFDVCxDQUFDO2dCQUNELElBQUksQ0FBQztvQkFDSCxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQ2xCLENBQUM7Z0JBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztvQkFDYixNQUFNLENBQUMsSUFBSSxDQUFDLHlCQUF5QixFQUFFLEVBQUUsS0FBSyxFQUFFLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ3RHLENBQUM7WUFDSCxDQUFDLENBQUMsQ0FBQztZQUNILEtBQUssT0FBTyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7Z0JBQ3RFLE1BQU0sQ0FBQyxJQUFJLENBQUMsa0VBQWtFLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUMvSSxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUM7UUFDRCxLQUFLLENBQUMsS0FBSztZQUNULE1BQU0sR0FBRyxJQUFJLENBQUM7WUFDZCxNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLEVBQUUsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNsRSxDQUFDO0tBQ0YsQ0FBQztBQUNKLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLHNCQUFzQjtJQUNwQyxNQUFNLE1BQU0sR0FBRyxvQkFBb0IsQ0FBb0IsV0FBVyxDQUFDLENBQUM7SUFDcEUsSUFBSSxDQUFDLE1BQU07UUFBRSxPQUFPLElBQUksQ0FBQztJQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLHlEQUF5RCxDQUFDLENBQUM7SUFDdkUsT0FBTyxtQkFBbUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUNyQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciwgY3JlYXRlRW52UmVkaXNDbGllbnQgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgdHlwZSB7IFNTRVBheWxvYWQgfSBmcm9tICcuL3NzZS1jb25uZWN0aW9uLW1hbmFnZXIuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3NzZS1yZWxheScpO1xuXG4vKipcbiAqIEEgcmVsYXllZCBTU0UgZnJhbWUuIGBvcmlnaW5gIGlzIHRoZSBpZCBvZiB0aGUgU1NFTWFuYWdlciBpbnN0YW5jZSB0aGF0XG4gKiBwdWJsaXNoZWQgaXQsIHNvIHRoZSBwdWJsaXNoaW5nIHBvZCBjYW4gaWdub3JlIGl0cyBvd24gZWNobyAoaXQgYWxyZWFkeSB3cm90ZVxuICogdGhlIGZyYW1lIHRvIGl0cyBsb2NhbCBjbGllbnRzIGJlZm9yZSBwdWJsaXNoaW5nKS4gYGtpbmRgIGRpc3Rpbmd1aXNoZXMgYVxuICogcGVyLXN1YmplY3QgYHNlbmRgIChjYXJyaWVzIGByZXF1ZXN0SWRgKSBmcm9tIGEgYGJyb2FkY2FzdGAgKG5vIHN1YmplY3Qg4oCUXG4gKiBldmVyeSBwb2QgcmUtZW1pdHMgdG8gQUxMIG9mIGl0cyBsb2NhbCBjbGllbnRzKS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VSZWxheU1lc3NhZ2Uge1xuICBvcmlnaW46IHN0cmluZztcbiAga2luZDogJ3NlbmQnIHwgJ2Jyb2FkY2FzdCc7XG4gIC8qKiBQcmVzZW50IGZvciBgc2VuZGA7IHRoZSBzdHJlYW0gc3ViamVjdCB0aGUgcGF5bG9hZCB0YXJnZXRzLiAqL1xuICByZXF1ZXN0SWQ/OiBzdHJpbmc7XG4gIHBheWxvYWQ6IFNTRVBheWxvYWQ7XG59XG5cbi8qKlxuICogQ3Jvc3MtcG9kIGZhbi1vdXQgYnVzIGZvciBTU0UgZnJhbWVzLlxuICpcbiAqIFVuZGVyIG11bHRpcGxlIHJlcGxpY2FzIHRoZSBwcm9kdWNlciBvZiBhIGxvZyBmcmFtZSAoZS5nLiBhIHBsdWdpbiBidWlsZFxuICogd29ya2VyIG9uIHBvZCBBKSBhbmQgdGhlIGNvbnN1bWVyIChhIGJyb3dzZXIgRXZlbnRTb3VyY2Ugb24gcG9kIEIpIGFyZSBvblxuICogRElGRkVSRU5UIHBvZHMuIFRoZSBpbi1wcm9jZXNzIGNsaWVudCBtYXAgb25seSByZWFjaGVzIHRoZSBwcm9kdWNlcidzIG93blxuICogcG9kLCBzbyBCJ3Mgc3RyZWFtIHdvdWxkIHNpbGVudGx5IG1pc3MgZnJhbWVzLiBUaGUgcmVsYXkgY2xvc2VzIHRoYXQ6IGV2ZXJ5XG4gKiBwb2QgU1VCU0NSSUJFUyBvbmNlIG9uIHN0YXJ0dXAgYW5kIHJlLWVtaXRzIHJlY2VpdmVkIGZyYW1lcyB0byBpdHMgTE9DQUxcbiAqIGNsaWVudHMsIHdoaWxlIGBzZW5kKClgL2Bicm9hZGNhc3QoKWAgUFVCTElTSCB0byB0aGUgYnVzIChpbiBhZGRpdGlvbiB0b1xuICogd3JpdGluZyBsb2NhbGx5KS4gVGhlIG9yaWdpbiB0YWcgcHJldmVudHMgYSBwb2QgZnJvbSBkb3VibGUtZGVsaXZlcmluZyBpdHMgb3duXG4gKiBmcmFtZSB3aGVuIGl0IHJlY2VpdmVzIGl0cyBvd24gcHVibGlzaCBiYWNrLlxuICpcbiAqIEFsbCBtZXRob2RzIGFyZSBiZXN0LWVmZm9ydCAvIGZhaWwtc2FmZTogYSBidXMgb3V0YWdlIG11c3QgZGVncmFkZSB0b1xuICogbG9jYWwtb25seSBkZWxpdmVyeSAodG9kYXkncyBzaW5nbGUtcmVwbGljYSBiZWhhdmlvciksIG5ldmVyIGNyYXNoIHRoZSBzdHJlYW0uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU1NFUmVsYXkge1xuICAvKiogRmlyZS1hbmQtZm9yZ2V0IHB1Ymxpc2ggb2YgYSBmcmFtZSB0byBldmVyeSBzdWJzY3JpYmVkIHBvZC4gTmV2ZXIgdGhyb3dzLiAqL1xuICBwdWJsaXNoKG1zZzogU1NFUmVsYXlNZXNzYWdlKTogdm9pZDtcbiAgLyoqIFN1YnNjcmliZSB0aGlzIHBvZCdzIHJlLWVtaXQgaGFuZGxlci4gQ2FsbGVkIG9uY2Ugb24gbWFuYWdlciBjb25zdHJ1Y3Rpb24uICovXG4gIHN1YnNjcmliZShoYW5kbGVyOiAobXNnOiBTU0VSZWxheU1lc3NhZ2UpID0+IHZvaWQpOiB2b2lkO1xuICAvKiogVGVhciBkb3duIHRoZSB1bmRlcmx5aW5nIGNvbm5lY3Rpb24ocykuICovXG4gIGNsb3NlKCk6IFByb21pc2U8dm9pZD47XG59XG5cbi8qKlxuICogTWluaW1hbCBpb3JlZGlzIHB1Yi9zdWIgc3VyZmFjZSB0aGUgUmVkaXMgcmVsYXkgbmVlZHMuIGBzdWJzY3JpYmVgIHJlcXVpcmVzIGFcbiAqIERFRElDQVRFRCBjb25uZWN0aW9uIChhIHN1YnNjcmliZWQgaW9yZWRpcyBjbGllbnQgY2FuJ3QgcnVuIG90aGVyIGNvbW1hbmRzKSxcbiAqIHNvIHRoZSByZWxheSBgZHVwbGljYXRlKClgcyB0aGUgcHVibGlzaGVyIGZvciB0aGUgc3Vic2NyaWJlciBzaWRlLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlZGlzUHViU3ViQ2xpZW50IHtcbiAgcHVibGlzaChjaGFubmVsOiBzdHJpbmcsIG1lc3NhZ2U6IHN0cmluZyk6IFByb21pc2U8bnVtYmVyPjtcbiAgc3Vic2NyaWJlKC4uLmNoYW5uZWxzOiBzdHJpbmdbXSk6IFByb21pc2U8dW5rbm93bj47XG4gIG9uKGV2ZW50OiAnbWVzc2FnZScsIGNiOiAoY2hhbm5lbDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpID0+IHZvaWQpOiB2b2lkO1xuICBkdXBsaWNhdGUoKTogUmVkaXNQdWJTdWJDbGllbnQ7XG4gIHF1aXQoKTogUHJvbWlzZTx1bmtub3duPjtcbn1cblxuLyoqXG4gKiBTaW5nbGUgY2hhbm5lbCBjYXJyeWluZyBldmVyeSByZWxheWVkIGZyYW1lLiBgcmVxdWVzdElkYCByaWRlcyBpbiB0aGUgbWVzc2FnZVxuICogYm9keSByYXRoZXIgdGhhbiB0aGUgY2hhbm5lbCBuYW1lIHNvIGVhY2ggcG9kIFNVQlNDUklCRVMgZXhhY3RseSBvbmNlIG9uXG4gKiBzdGFydHVwIOKAlCBubyBwZXItc3ViamVjdCBzdWJzY3JpYmUvdW5zdWJzY3JpYmUgY2h1cm4gYXMgY2xpZW50cyBjb21lIGFuZCBnby5cbiAqL1xuY29uc3QgUkVMQVlfQ0hBTk5FTCA9ICdzc2U6cmVsYXknO1xuXG4vKipcbiAqIFJlZGlzLWJhY2tlZCByZWxheS4gVXNlcyB0aGUgZ2l2ZW4gY2xpZW50IGZvciBQVUJMSVNIIGFuZCBhIGBkdXBsaWNhdGUoKWAgZm9yXG4gKiBTVUJTQ1JJQkUgKGlvcmVkaXMgZm9yYmlkcyBtaXhpbmcgc3Vic2NyaWJlIHdpdGggbm9ybWFsIGNvbW1hbmRzIG9uIG9uZVxuICogY29ubmVjdGlvbikuIFB1Ymxpc2ggaXMgZmlyZS1hbmQtZm9yZ2V0IHdpdGggYSBzd2FsbG93ZWQgcmVqZWN0aW9uOyBhIHN1YnNjcmliZVxuICogaGFuZGxlciB0aGF0IHRocm93cyBpcyBpc29sYXRlZCBzbyBvbmUgYmFkIGZyYW1lIGNhbid0IGtpbGwgdGhlIHN1YnNjcmliZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVSZWRpc1NTRVJlbGF5KHB1Ymxpc2hlcjogUmVkaXNQdWJTdWJDbGllbnQpOiBTU0VSZWxheSB7XG4gIGNvbnN0IHN1YnNjcmliZXIgPSBwdWJsaXNoZXIuZHVwbGljYXRlKCk7XG4gIGxldCBjbG9zZWQgPSBmYWxzZTtcblxuICByZXR1cm4ge1xuICAgIHB1Ymxpc2gobXNnKSB7XG4gICAgICBpZiAoY2xvc2VkKSByZXR1cm47XG4gICAgICAvLyBGaXJlLWFuZC1mb3JnZXQ6IG5ldmVyIGF3YWl0LCBuZXZlciBzdXJmYWNlIGEgcmVqZWN0aW9uIHRvIHRoZSBjYWxsZXIuXG4gICAgICB2b2lkIFByb21pc2UucmVzb2x2ZShwdWJsaXNoZXIucHVibGlzaChSRUxBWV9DSEFOTkVMLCBKU09OLnN0cmluZ2lmeShtc2cpKSkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICBsb2dnZXIud2FybignU1NFIHJlbGF5IHB1Ymxpc2ggZmFpbGVkJywgeyBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpIH0pO1xuICAgICAgfSk7XG4gICAgfSxcbiAgICBzdWJzY3JpYmUoaGFuZGxlcikge1xuICAgICAgc3Vic2NyaWJlci5vbignbWVzc2FnZScsIChjaGFubmVsLCBtZXNzYWdlKSA9PiB7XG4gICAgICAgIGlmIChjaGFubmVsICE9PSBSRUxBWV9DSEFOTkVMKSByZXR1cm47XG4gICAgICAgIGxldCBwYXJzZWQ6IFNTRVJlbGF5TWVzc2FnZTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBwYXJzZWQgPSBKU09OLnBhcnNlKG1lc3NhZ2UpIGFzIFNTRVJlbGF5TWVzc2FnZTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oJ1NTRSByZWxheSByZWNlaXZlZCB1bnBhcnNlYWJsZSBmcmFtZScsIHsgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9KTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBoYW5kbGVyKHBhcnNlZCk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIGxvZ2dlci53YXJuKCdTU0UgcmVsYXkgaGFuZGxlciB0aHJldycsIHsgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9KTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgICB2b2lkIFByb21pc2UucmVzb2x2ZShzdWJzY3JpYmVyLnN1YnNjcmliZShSRUxBWV9DSEFOTkVMKSkuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICBsb2dnZXIud2FybignU1NFIHJlbGF5IHN1YnNjcmliZSBmYWlsZWQg4oCUIGZhbGxpbmcgYmFjayB0byBsb2NhbC1vbmx5IGRlbGl2ZXJ5JywgeyBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpIH0pO1xuICAgICAgfSk7XG4gICAgfSxcbiAgICBhc3luYyBjbG9zZSgpIHtcbiAgICAgIGNsb3NlZCA9IHRydWU7XG4gICAgICBhd2FpdCBQcm9taXNlLmFsbFNldHRsZWQoW3B1Ymxpc2hlci5xdWl0KCksIHN1YnNjcmliZXIucXVpdCgpXSk7XG4gICAgfSxcbiAgfTtcbn1cblxuLyoqXG4gKiBCdWlsZCBhIFJlZGlzLWJhY2tlZCBTU0UgcmVsYXkgZnJvbSB0aGUgc2hhcmVkIGVudiBSZWRpcyAoc2FtZSB3aXJpbmcgYXMgdGhlXG4gKiBTU0UgdGlja2V0IHN0b3JlIC8gcmF0ZS1saW1pdGVyIC8gYXVkaXQtc3Bvb2wpLiBSZXR1cm5zIG51bGwgd2hlbiBSZWRpcyBpc24ndFxuICogY29uZmlndXJlZCBzbyB0aGUgY2FsbGVyIGtlZXBzIGxvY2FsLW9ubHkgZGVsaXZlcnkuIE5ldmVyIHRocm93cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVudlJlZGlzU1NFUmVsYXkoKTogU1NFUmVsYXkgfCBudWxsIHtcbiAgY29uc3QgY2xpZW50ID0gY3JlYXRlRW52UmVkaXNDbGllbnQ8UmVkaXNQdWJTdWJDbGllbnQ+KCdzc2UtcmVsYXknKTtcbiAgaWYgKCFjbGllbnQpIHJldHVybiBudWxsO1xuICBsb2dnZXIuaW5mbygnUmVkaXMgU1NFIHJlbGF5IGluaXRpYWxpemVkIChjcm9zcy1wb2QgZmFuLW91dCBlbmFibGVkKScpO1xuICByZXR1cm4gY3JlYXRlUmVkaXNTU0VSZWxheShjbGllbnQpO1xufVxuIl19
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type SseTicketStore } from '@pipeline-builder/api-core';
|
|
2
|
+
import type { Express, RequestHandler } from 'express';
|
|
3
|
+
import type { SSEManager } from './sse-connection-manager.js';
|
|
4
|
+
export interface SseTicketChannelOptions {
|
|
5
|
+
/** POST route that exchanges the JWT for a single-use ticket (e.g. '/messages/notifications/ticket'). */
|
|
6
|
+
ticketPath: string;
|
|
7
|
+
/** GET route that redeems the ticket and opens the EventSource (e.g. '/messages/notifications'). */
|
|
8
|
+
streamPath: string;
|
|
9
|
+
/** Org-scoped, single-use ticket store (Redis-backed for multi-replica). */
|
|
10
|
+
ticketStore: SseTicketStore;
|
|
11
|
+
/** SSE manager — the org id is the stream subject (`sseManager.send(orgId, …)`). */
|
|
12
|
+
sseManager: SSEManager;
|
|
13
|
+
/** Noun used in capacity/connection error messages (e.g. 'notification', 'execution-stream'). */
|
|
14
|
+
label: string;
|
|
15
|
+
/** Extra middleware applied AFTER requireAuth on the mint route (e.g. requirePermission('reports:read')). */
|
|
16
|
+
ticketGuards?: RequestHandler[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Register a per-ORG SSE channel: a POST that exchanges the caller's VERIFIED JWT
|
|
20
|
+
* org for a single-use, org-bound ticket, and a GET that redeems the ticket and
|
|
21
|
+
* attaches an EventSource keyed by that org. The org IS the stream subject, so a
|
|
22
|
+
* producer reaches subscribers with `sseManager.send(orgId, …)` (cross-pod via the
|
|
23
|
+
* relay). Shared by the message-notification and reporting execution-status
|
|
24
|
+
* channels so the subtle bits — the ticket→addClient→flushHeaders ordering (a 429
|
|
25
|
+
* MUST precede flushHeaders, which commits the 200) and the capacity semantics —
|
|
26
|
+
* live in exactly one place.
|
|
27
|
+
*
|
|
28
|
+
* Distinct from app-factory's `/logs` channel, which is SUBJECT-bound (per build,
|
|
29
|
+
* via SSEManager.createTicket) rather than org-bound.
|
|
30
|
+
*/
|
|
31
|
+
export declare function registerSseTicketChannel(app: Express, opts: SseTicketChannelOptions): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { requireAuth, sendSuccess, sendError, ErrorCode, createLogger } from '@pipeline-builder/api-core';
|
|
4
|
+
const logger = createLogger('sse-ticket-channel');
|
|
5
|
+
/**
|
|
6
|
+
* Register a per-ORG SSE channel: a POST that exchanges the caller's VERIFIED JWT
|
|
7
|
+
* org for a single-use, org-bound ticket, and a GET that redeems the ticket and
|
|
8
|
+
* attaches an EventSource keyed by that org. The org IS the stream subject, so a
|
|
9
|
+
* producer reaches subscribers with `sseManager.send(orgId, …)` (cross-pod via the
|
|
10
|
+
* relay). Shared by the message-notification and reporting execution-status
|
|
11
|
+
* channels so the subtle bits — the ticket→addClient→flushHeaders ordering (a 429
|
|
12
|
+
* MUST precede flushHeaders, which commits the 200) and the capacity semantics —
|
|
13
|
+
* live in exactly one place.
|
|
14
|
+
*
|
|
15
|
+
* Distinct from app-factory's `/logs` channel, which is SUBJECT-bound (per build,
|
|
16
|
+
* via SSEManager.createTicket) rather than org-bound.
|
|
17
|
+
*/
|
|
18
|
+
export function registerSseTicketChannel(app, opts) {
|
|
19
|
+
const { ticketPath, streamPath, ticketStore, sseManager, label, ticketGuards = [] } = opts;
|
|
20
|
+
app.post(ticketPath, requireAuth, ...ticketGuards, async (req, res) => {
|
|
21
|
+
const orgId = req.user?.organizationId?.toLowerCase();
|
|
22
|
+
if (!orgId)
|
|
23
|
+
return sendError(res, 400, 'Token missing organization', ErrorCode.VALIDATION_ERROR);
|
|
24
|
+
const result = await ticketStore.issue(orgId);
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
return result.reason === 'total'
|
|
27
|
+
? sendError(res, 503, `The ${label} subsystem is at capacity`, ErrorCode.QUOTA_EXCEEDED)
|
|
28
|
+
: sendError(res, 429, `Too many ${label} tickets issued`, ErrorCode.QUOTA_EXCEEDED);
|
|
29
|
+
}
|
|
30
|
+
return sendSuccess(res, 200, { ticket: result.ticket });
|
|
31
|
+
});
|
|
32
|
+
app.get(streamPath, async (req, res) => {
|
|
33
|
+
const ticketId = req.query.ticket;
|
|
34
|
+
if (!ticketId)
|
|
35
|
+
return void sendError(res, 401, 'Missing ticket parameter', ErrorCode.UNAUTHORIZED);
|
|
36
|
+
const ticket = await ticketStore.consume(ticketId); // atomic single-use
|
|
37
|
+
if (!ticket)
|
|
38
|
+
return void sendError(res, 401, 'Invalid or expired ticket', ErrorCode.UNAUTHORIZED);
|
|
39
|
+
// Reserve the connection slot BEFORE flushing SSE headers: once flushHeaders
|
|
40
|
+
// runs the response is committed at 200 and a later 429 is silently dropped.
|
|
41
|
+
const added = sseManager.addClient(ticket.orgId, res);
|
|
42
|
+
if (!added)
|
|
43
|
+
return void sendError(res, 429, `Too many ${label} connections`, ErrorCode.QUOTA_EXCEEDED);
|
|
44
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
45
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
46
|
+
res.setHeader('Connection', 'keep-alive');
|
|
47
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
48
|
+
res.flushHeaders();
|
|
49
|
+
logger.info('SSE client connected', { channel: label, orgId: ticket.orgId });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLXRpY2tldC1jaGFubmVsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2h0dHAvc3NlLXRpY2tldC1jaGFubmVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxZQUFZLEVBQXVCLE1BQU0sNEJBQTRCLENBQUM7QUFJL0gsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFpQmxEOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE1BQU0sVUFBVSx3QkFBd0IsQ0FBQyxHQUFZLEVBQUUsSUFBNkI7SUFDbEYsTUFBTSxFQUFFLFVBQVUsRUFBRSxVQUFVLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsWUFBWSxHQUFHLEVBQUUsRUFBRSxHQUFHLElBQUksQ0FBQztJQUUzRixHQUFHLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxXQUFXLEVBQUUsR0FBRyxZQUFZLEVBQUUsS0FBSyxFQUFFLEdBQVksRUFBRSxHQUFhLEVBQUUsRUFBRTtRQUN2RixNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLGNBQWMsRUFBRSxXQUFXLEVBQUUsQ0FBQztRQUN0RCxJQUFJLENBQUMsS0FBSztZQUFFLE9BQU8sU0FBUyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsNEJBQTRCLEVBQUUsU0FBUyxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDakcsTUFBTSxNQUFNLEdBQUcsTUFBTSxXQUFXLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzlDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDZixPQUFPLE1BQU0sQ0FBQyxNQUFNLEtBQUssT0FBTztnQkFDOUIsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLE9BQU8sS0FBSywyQkFBMkIsRUFBRSxTQUFTLENBQUMsY0FBYyxDQUFDO2dCQUN4RixDQUFDLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsWUFBWSxLQUFLLGlCQUFpQixFQUFFLFNBQVMsQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUN4RixDQUFDO1FBQ0QsT0FBTyxXQUFXLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUMxRCxDQUFDLENBQUMsQ0FBQztJQUVILEdBQUcsQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLEtBQUssRUFBRSxHQUFZLEVBQUUsR0FBYSxFQUFFLEVBQUU7UUFDeEQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUE0QixDQUFDO1FBQ3hELElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTyxLQUFLLFNBQVMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLDBCQUEwQixFQUFFLFNBQVMsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUNuRyxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxvQkFBb0I7UUFDeEUsSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPLEtBQUssU0FBUyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsMkJBQTJCLEVBQUUsU0FBUyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ2xHLDZFQUE2RTtRQUM3RSw2RUFBNkU7UUFDN0UsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ3RELElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTyxLQUFLLFNBQVMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLFlBQVksS0FBSyxjQUFjLEVBQUUsU0FBUyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ3ZHLEdBQUcsQ0FBQyxTQUFTLENBQUMsY0FBYyxFQUFFLG1CQUFtQixDQUFDLENBQUM7UUFDbkQsR0FBRyxDQUFDLFNBQVMsQ0FBQyxlQUFlLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDM0MsR0FBRyxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDMUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxtQkFBbUIsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUN6QyxHQUFHLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDbkIsTUFBTSxDQUFDLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQy9FLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyByZXF1aXJlQXV0aCwgc2VuZFN1Y2Nlc3MsIHNlbmRFcnJvciwgRXJyb3JDb2RlLCBjcmVhdGVMb2dnZXIsIHR5cGUgU3NlVGlja2V0U3RvcmUgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgdHlwZSB7IEV4cHJlc3MsIFJlcXVlc3QsIFJlcXVlc3RIYW5kbGVyLCBSZXNwb25zZSB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBTU0VNYW5hZ2VyIH0gZnJvbSAnLi9zc2UtY29ubmVjdGlvbi1tYW5hZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdzc2UtdGlja2V0LWNoYW5uZWwnKTtcblxuZXhwb3J0IGludGVyZmFjZSBTc2VUaWNrZXRDaGFubmVsT3B0aW9ucyB7XG4gIC8qKiBQT1NUIHJvdXRlIHRoYXQgZXhjaGFuZ2VzIHRoZSBKV1QgZm9yIGEgc2luZ2xlLXVzZSB0aWNrZXQgKGUuZy4gJy9tZXNzYWdlcy9ub3RpZmljYXRpb25zL3RpY2tldCcpLiAqL1xuICB0aWNrZXRQYXRoOiBzdHJpbmc7XG4gIC8qKiBHRVQgcm91dGUgdGhhdCByZWRlZW1zIHRoZSB0aWNrZXQgYW5kIG9wZW5zIHRoZSBFdmVudFNvdXJjZSAoZS5nLiAnL21lc3NhZ2VzL25vdGlmaWNhdGlvbnMnKS4gKi9cbiAgc3RyZWFtUGF0aDogc3RyaW5nO1xuICAvKiogT3JnLXNjb3BlZCwgc2luZ2xlLXVzZSB0aWNrZXQgc3RvcmUgKFJlZGlzLWJhY2tlZCBmb3IgbXVsdGktcmVwbGljYSkuICovXG4gIHRpY2tldFN0b3JlOiBTc2VUaWNrZXRTdG9yZTtcbiAgLyoqIFNTRSBtYW5hZ2VyIOKAlCB0aGUgb3JnIGlkIGlzIHRoZSBzdHJlYW0gc3ViamVjdCAoYHNzZU1hbmFnZXIuc2VuZChvcmdJZCwg4oCmKWApLiAqL1xuICBzc2VNYW5hZ2VyOiBTU0VNYW5hZ2VyO1xuICAvKiogTm91biB1c2VkIGluIGNhcGFjaXR5L2Nvbm5lY3Rpb24gZXJyb3IgbWVzc2FnZXMgKGUuZy4gJ25vdGlmaWNhdGlvbicsICdleGVjdXRpb24tc3RyZWFtJykuICovXG4gIGxhYmVsOiBzdHJpbmc7XG4gIC8qKiBFeHRyYSBtaWRkbGV3YXJlIGFwcGxpZWQgQUZURVIgcmVxdWlyZUF1dGggb24gdGhlIG1pbnQgcm91dGUgKGUuZy4gcmVxdWlyZVBlcm1pc3Npb24oJ3JlcG9ydHM6cmVhZCcpKS4gKi9cbiAgdGlja2V0R3VhcmRzPzogUmVxdWVzdEhhbmRsZXJbXTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlciBhIHBlci1PUkcgU1NFIGNoYW5uZWw6IGEgUE9TVCB0aGF0IGV4Y2hhbmdlcyB0aGUgY2FsbGVyJ3MgVkVSSUZJRUQgSldUXG4gKiBvcmcgZm9yIGEgc2luZ2xlLXVzZSwgb3JnLWJvdW5kIHRpY2tldCwgYW5kIGEgR0VUIHRoYXQgcmVkZWVtcyB0aGUgdGlja2V0IGFuZFxuICogYXR0YWNoZXMgYW4gRXZlbnRTb3VyY2Uga2V5ZWQgYnkgdGhhdCBvcmcuIFRoZSBvcmcgSVMgdGhlIHN0cmVhbSBzdWJqZWN0LCBzbyBhXG4gKiBwcm9kdWNlciByZWFjaGVzIHN1YnNjcmliZXJzIHdpdGggYHNzZU1hbmFnZXIuc2VuZChvcmdJZCwg4oCmKWAgKGNyb3NzLXBvZCB2aWEgdGhlXG4gKiByZWxheSkuIFNoYXJlZCBieSB0aGUgbWVzc2FnZS1ub3RpZmljYXRpb24gYW5kIHJlcG9ydGluZyBleGVjdXRpb24tc3RhdHVzXG4gKiBjaGFubmVscyBzbyB0aGUgc3VidGxlIGJpdHMg4oCUIHRoZSB0aWNrZXTihpJhZGRDbGllbnTihpJmbHVzaEhlYWRlcnMgb3JkZXJpbmcgKGEgNDI5XG4gKiBNVVNUIHByZWNlZGUgZmx1c2hIZWFkZXJzLCB3aGljaCBjb21taXRzIHRoZSAyMDApIGFuZCB0aGUgY2FwYWNpdHkgc2VtYW50aWNzIOKAlFxuICogbGl2ZSBpbiBleGFjdGx5IG9uZSBwbGFjZS5cbiAqXG4gKiBEaXN0aW5jdCBmcm9tIGFwcC1mYWN0b3J5J3MgYC9sb2dzYCBjaGFubmVsLCB3aGljaCBpcyBTVUJKRUNULWJvdW5kIChwZXIgYnVpbGQsXG4gKiB2aWEgU1NFTWFuYWdlci5jcmVhdGVUaWNrZXQpIHJhdGhlciB0aGFuIG9yZy1ib3VuZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlZ2lzdGVyU3NlVGlja2V0Q2hhbm5lbChhcHA6IEV4cHJlc3MsIG9wdHM6IFNzZVRpY2tldENoYW5uZWxPcHRpb25zKTogdm9pZCB7XG4gIGNvbnN0IHsgdGlja2V0UGF0aCwgc3RyZWFtUGF0aCwgdGlja2V0U3RvcmUsIHNzZU1hbmFnZXIsIGxhYmVsLCB0aWNrZXRHdWFyZHMgPSBbXSB9ID0gb3B0cztcblxuICBhcHAucG9zdCh0aWNrZXRQYXRoLCByZXF1aXJlQXV0aCwgLi4udGlja2V0R3VhcmRzLCBhc3luYyAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlKSA9PiB7XG4gICAgY29uc3Qgb3JnSWQgPSByZXEudXNlcj8ub3JnYW5pemF0aW9uSWQ/LnRvTG93ZXJDYXNlKCk7XG4gICAgaWYgKCFvcmdJZCkgcmV0dXJuIHNlbmRFcnJvcihyZXMsIDQwMCwgJ1Rva2VuIG1pc3Npbmcgb3JnYW5pemF0aW9uJywgRXJyb3JDb2RlLlZBTElEQVRJT05fRVJST1IpO1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHRpY2tldFN0b3JlLmlzc3VlKG9yZ0lkKTtcbiAgICBpZiAoIXJlc3VsdC5vaykge1xuICAgICAgcmV0dXJuIHJlc3VsdC5yZWFzb24gPT09ICd0b3RhbCdcbiAgICAgICAgPyBzZW5kRXJyb3IocmVzLCA1MDMsIGBUaGUgJHtsYWJlbH0gc3Vic3lzdGVtIGlzIGF0IGNhcGFjaXR5YCwgRXJyb3JDb2RlLlFVT1RBX0VYQ0VFREVEKVxuICAgICAgICA6IHNlbmRFcnJvcihyZXMsIDQyOSwgYFRvbyBtYW55ICR7bGFiZWx9IHRpY2tldHMgaXNzdWVkYCwgRXJyb3JDb2RlLlFVT1RBX0VYQ0VFREVEKTtcbiAgICB9XG4gICAgcmV0dXJuIHNlbmRTdWNjZXNzKHJlcywgMjAwLCB7IHRpY2tldDogcmVzdWx0LnRpY2tldCB9KTtcbiAgfSk7XG5cbiAgYXBwLmdldChzdHJlYW1QYXRoLCBhc3luYyAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlKSA9PiB7XG4gICAgY29uc3QgdGlja2V0SWQgPSByZXEucXVlcnkudGlja2V0IGFzIHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgICBpZiAoIXRpY2tldElkKSByZXR1cm4gdm9pZCBzZW5kRXJyb3IocmVzLCA0MDEsICdNaXNzaW5nIHRpY2tldCBwYXJhbWV0ZXInLCBFcnJvckNvZGUuVU5BVVRIT1JJWkVEKTtcbiAgICBjb25zdCB0aWNrZXQgPSBhd2FpdCB0aWNrZXRTdG9yZS5jb25zdW1lKHRpY2tldElkKTsgLy8gYXRvbWljIHNpbmdsZS11c2VcbiAgICBpZiAoIXRpY2tldCkgcmV0dXJuIHZvaWQgc2VuZEVycm9yKHJlcywgNDAxLCAnSW52YWxpZCBvciBleHBpcmVkIHRpY2tldCcsIEVycm9yQ29kZS5VTkFVVEhPUklaRUQpO1xuICAgIC8vIFJlc2VydmUgdGhlIGNvbm5lY3Rpb24gc2xvdCBCRUZPUkUgZmx1c2hpbmcgU1NFIGhlYWRlcnM6IG9uY2UgZmx1c2hIZWFkZXJzXG4gICAgLy8gcnVucyB0aGUgcmVzcG9uc2UgaXMgY29tbWl0dGVkIGF0IDIwMCBhbmQgYSBsYXRlciA0MjkgaXMgc2lsZW50bHkgZHJvcHBlZC5cbiAgICBjb25zdCBhZGRlZCA9IHNzZU1hbmFnZXIuYWRkQ2xpZW50KHRpY2tldC5vcmdJZCwgcmVzKTtcbiAgICBpZiAoIWFkZGVkKSByZXR1cm4gdm9pZCBzZW5kRXJyb3IocmVzLCA0MjksIGBUb28gbWFueSAke2xhYmVsfSBjb25uZWN0aW9uc2AsIEVycm9yQ29kZS5RVU9UQV9FWENFRURFRCk7XG4gICAgcmVzLnNldEhlYWRlcignQ29udGVudC1UeXBlJywgJ3RleHQvZXZlbnQtc3RyZWFtJyk7XG4gICAgcmVzLnNldEhlYWRlcignQ2FjaGUtQ29udHJvbCcsICduby1jYWNoZScpO1xuICAgIHJlcy5zZXRIZWFkZXIoJ0Nvbm5lY3Rpb24nLCAna2VlcC1hbGl2ZScpO1xuICAgIHJlcy5zZXRIZWFkZXIoJ1gtQWNjZWwtQnVmZmVyaW5nJywgJ25vJyk7XG4gICAgcmVzLmZsdXNoSGVhZGVycygpO1xuICAgIGxvZ2dlci5pbmZvKCdTU0UgY2xpZW50IGNvbm5lY3RlZCcsIHsgY2hhbm5lbDogbGFiZWwsIG9yZ0lkOiB0aWNrZXQub3JnSWQgfSk7XG4gIH0pO1xufVxuIl19
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** A minted ticket's bound identity: owning org + normalized stream subject. */
|
|
2
|
+
export interface SSEStreamTicketRecord {
|
|
3
|
+
orgId: string;
|
|
4
|
+
/** Normalized (dashes stripped, lowercased) stream subject id. */
|
|
5
|
+
requestId: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pluggable backend for SSE log-stream tickets AND stream-ownership.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: distinct from api-core's `SseTicketStore` (services/sse-ticket-store.ts),
|
|
11
|
+
* which is the message-service ticket store (issue + abuse counters). This one
|
|
12
|
+
* (api-server) additionally tracks stream ownership. The names deliberately differ
|
|
13
|
+
* (`SSEStreamTicketStore` here vs `SseTicketStore` there) so consumers don't pick
|
|
14
|
+
* the wrong one.
|
|
15
|
+
*
|
|
16
|
+
* Two concerns share one store because both must be consistent across pods:
|
|
17
|
+
* - **Tickets** — short-lived, single-use, bound to (org, subject). The default
|
|
18
|
+
* in-memory store is correct only single-replica; the Redis store makes the
|
|
19
|
+
* mint (one pod) / redeem (another pod) exchange work behind a load balancer.
|
|
20
|
+
* - **Stream ownership** — which org OWNS a given stream subject. The stream
|
|
21
|
+
* producer (platform, a different service) records this so ticket minting can
|
|
22
|
+
* assert the caller's org owns the subject, closing the cross-tenant attach
|
|
23
|
+
* gap where any org could mint a ticket for a guessed requestId.
|
|
24
|
+
*
|
|
25
|
+
* All methods are async (Redis is async) and best-effort on the failure side per
|
|
26
|
+
* their doc — a store outage must degrade gracefully, never crash the stream.
|
|
27
|
+
*/
|
|
28
|
+
export interface SSEStreamTicketStore {
|
|
29
|
+
/** Persist a single-use ticket bound to `record`, expiring after `ttlMs`. */
|
|
30
|
+
put(ticketId: string, record: SSEStreamTicketRecord, ttlMs: number): Promise<void>;
|
|
31
|
+
/** Atomically fetch-and-delete a ticket (single-use). Null if absent/expired. */
|
|
32
|
+
consume(ticketId: string): Promise<SSEStreamTicketRecord | null>;
|
|
33
|
+
/** Count of live (unexpired) tickets held for an org (per-org cap). */
|
|
34
|
+
countForOrg(orgId: string): Promise<number>;
|
|
35
|
+
/** Count of all live tickets across the store (capacity cap). */
|
|
36
|
+
total(): Promise<number>;
|
|
37
|
+
/** Record the org that OWNS a stream subject (producer-side), TTL `ttlMs`. */
|
|
38
|
+
bindStreamOwner(requestId: string, orgId: string, ttlMs: number): Promise<void>;
|
|
39
|
+
/** The owning org of a stream subject, or null when none is bound. */
|
|
40
|
+
getStreamOwner(requestId: string): Promise<string | null>;
|
|
41
|
+
/** Optional hygiene sweep of expired entries (Redis TTL makes this a no-op). */
|
|
42
|
+
sweep?(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* In-memory ticket store — the default. Correct for a single replica only;
|
|
46
|
+
* mirrors the behavior the SSEManager previously implemented inline.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createMemoryTicketStore(): SSEStreamTicketStore;
|
|
49
|
+
/**
|
|
50
|
+
* Minimal ioredis surface the Redis ticket store needs. `set` takes the variadic
|
|
51
|
+
* option tail (`EX <s>`); `zadd`/`zremrangebyscore`/`zcard` back the cap counters.
|
|
52
|
+
*/
|
|
53
|
+
export interface RedisTicketClient {
|
|
54
|
+
get(key: string): Promise<string | null>;
|
|
55
|
+
set(key: string, value: string, ...args: (string | number)[]): Promise<string | null>;
|
|
56
|
+
del(...keys: string[]): Promise<number>;
|
|
57
|
+
zadd(key: string, score: number, member: string): Promise<number>;
|
|
58
|
+
zrem(key: string, ...members: string[]): Promise<number>;
|
|
59
|
+
zcard(key: string): Promise<number>;
|
|
60
|
+
zremrangebyscore(key: string, min: number | string, max: number | string): Promise<number>;
|
|
61
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Redis-backed ticket store — required for multi-pod correctness. Tickets are
|
|
65
|
+
* per-key with native TTL; cap counters are ZSETs scored by expiry (pruned on
|
|
66
|
+
* read). Stream ownership is a per-subject key with its own TTL. All reads are
|
|
67
|
+
* fail-safe (null / 0) so a Redis blip never wedges the stream.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createRedisTicketStore(redis: RedisTicketClient): SSEStreamTicketStore;
|
|
70
|
+
/**
|
|
71
|
+
* Build a Redis-backed ticket store from the shared env Redis (same wiring as
|
|
72
|
+
* the rate-limiter / audit-spool). Returns null when Redis isn't configured so
|
|
73
|
+
* the caller keeps the in-memory default. Never throws.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createEnvRedisTicketStore(): SSEStreamTicketStore | null;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { createLogger, createEnvRedisClient } from '@pipeline-builder/api-core';
|
|
4
|
+
const logger = createLogger('sse-ticket-store');
|
|
5
|
+
/**
|
|
6
|
+
* In-memory ticket store — the default. Correct for a single replica only;
|
|
7
|
+
* mirrors the behavior the SSEManager previously implemented inline.
|
|
8
|
+
*/
|
|
9
|
+
export function createMemoryTicketStore() {
|
|
10
|
+
const tickets = new Map();
|
|
11
|
+
const owners = new Map();
|
|
12
|
+
return {
|
|
13
|
+
async put(ticketId, record, ttlMs) {
|
|
14
|
+
tickets.set(ticketId, { ...record, expiresAt: Date.now() + ttlMs });
|
|
15
|
+
},
|
|
16
|
+
async consume(ticketId) {
|
|
17
|
+
const t = tickets.get(ticketId);
|
|
18
|
+
tickets.delete(ticketId); // single-use — remove whether or not valid
|
|
19
|
+
if (!t || Date.now() > t.expiresAt)
|
|
20
|
+
return null;
|
|
21
|
+
return { orgId: t.orgId, requestId: t.requestId };
|
|
22
|
+
},
|
|
23
|
+
async countForOrg(orgId) {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
let n = 0;
|
|
26
|
+
for (const t of tickets.values())
|
|
27
|
+
if (t.orgId === orgId && t.expiresAt > now)
|
|
28
|
+
n++;
|
|
29
|
+
return n;
|
|
30
|
+
},
|
|
31
|
+
async total() {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
let n = 0;
|
|
34
|
+
for (const t of tickets.values())
|
|
35
|
+
if (t.expiresAt > now)
|
|
36
|
+
n++;
|
|
37
|
+
return n;
|
|
38
|
+
},
|
|
39
|
+
async bindStreamOwner(requestId, orgId, ttlMs) {
|
|
40
|
+
owners.set(requestId, { orgId, expiresAt: Date.now() + ttlMs });
|
|
41
|
+
},
|
|
42
|
+
async getStreamOwner(requestId) {
|
|
43
|
+
const o = owners.get(requestId);
|
|
44
|
+
if (!o)
|
|
45
|
+
return null;
|
|
46
|
+
if (Date.now() > o.expiresAt) {
|
|
47
|
+
owners.delete(requestId);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return o.orgId;
|
|
51
|
+
},
|
|
52
|
+
async sweep() {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const [id, t] of tickets)
|
|
55
|
+
if (now > t.expiresAt)
|
|
56
|
+
tickets.delete(id);
|
|
57
|
+
for (const [id, o] of owners)
|
|
58
|
+
if (now > o.expiresAt)
|
|
59
|
+
owners.delete(id);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const TICKET_PREFIX = 'sse:tkt:';
|
|
64
|
+
const ORG_ZSET_PREFIX = 'sse:tkt:org:';
|
|
65
|
+
const ALL_ZSET = 'sse:tkt:all';
|
|
66
|
+
const OWNER_PREFIX = 'sse:owner:';
|
|
67
|
+
/**
|
|
68
|
+
* Redis-backed ticket store — required for multi-pod correctness. Tickets are
|
|
69
|
+
* per-key with native TTL; cap counters are ZSETs scored by expiry (pruned on
|
|
70
|
+
* read). Stream ownership is a per-subject key with its own TTL. All reads are
|
|
71
|
+
* fail-safe (null / 0) so a Redis blip never wedges the stream.
|
|
72
|
+
*/
|
|
73
|
+
export function createRedisTicketStore(redis) {
|
|
74
|
+
const tkey = (id) => `${TICKET_PREFIX}${id}`;
|
|
75
|
+
const orgKey = (orgId) => `${ORG_ZSET_PREFIX}${orgId}`;
|
|
76
|
+
const ownerKey = (requestId) => `${OWNER_PREFIX}${requestId}`;
|
|
77
|
+
const ttlSec = (ttlMs) => Math.max(1, Math.ceil(ttlMs / 1000));
|
|
78
|
+
return {
|
|
79
|
+
async put(ticketId, record, ttlMs) {
|
|
80
|
+
const expiresAt = Date.now() + ttlMs;
|
|
81
|
+
const sec = ttlSec(ttlMs);
|
|
82
|
+
try {
|
|
83
|
+
await redis.set(tkey(ticketId), JSON.stringify(record), 'EX', sec);
|
|
84
|
+
// Track for the caps. Score = expiry so pruning is a range delete.
|
|
85
|
+
await redis.zadd(orgKey(record.orgId), expiresAt, ticketId);
|
|
86
|
+
await redis.zadd(ALL_ZSET, expiresAt, ticketId);
|
|
87
|
+
// Bound the per-org ZSET's own lifetime so an idle org's key can't leak.
|
|
88
|
+
await redis.expire(orgKey(record.orgId), sec);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
logger.warn('SSE ticket put failed', { error: err instanceof Error ? err.message : String(err) });
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
async consume(ticketId) {
|
|
95
|
+
try {
|
|
96
|
+
const raw = await redis.get(tkey(ticketId));
|
|
97
|
+
// Single-use: delete regardless. A concurrent double-redeem: at most one
|
|
98
|
+
// saw a non-null value before the delete; the ZREM cleans the counters.
|
|
99
|
+
await redis.del(tkey(ticketId));
|
|
100
|
+
if (!raw)
|
|
101
|
+
return null;
|
|
102
|
+
const rec = JSON.parse(raw);
|
|
103
|
+
await redis.zrem(orgKey(rec.orgId), ticketId);
|
|
104
|
+
await redis.zrem(ALL_ZSET, ticketId);
|
|
105
|
+
return rec;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
logger.warn('SSE ticket consume failed', { error: err instanceof Error ? err.message : String(err) });
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
async countForOrg(orgId) {
|
|
113
|
+
try {
|
|
114
|
+
await redis.zremrangebyscore(orgKey(orgId), 0, Date.now());
|
|
115
|
+
return await redis.zcard(orgKey(orgId));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
async total() {
|
|
122
|
+
try {
|
|
123
|
+
await redis.zremrangebyscore(ALL_ZSET, 0, Date.now());
|
|
124
|
+
return await redis.zcard(ALL_ZSET);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
async bindStreamOwner(requestId, orgId, ttlMs) {
|
|
131
|
+
try {
|
|
132
|
+
await redis.set(ownerKey(requestId), orgId, 'EX', ttlSec(ttlMs));
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
logger.warn('SSE stream-owner bind failed', { error: err instanceof Error ? err.message : String(err) });
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
async getStreamOwner(requestId) {
|
|
139
|
+
try {
|
|
140
|
+
return await redis.get(ownerKey(requestId));
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build a Redis-backed ticket store from the shared env Redis (same wiring as
|
|
150
|
+
* the rate-limiter / audit-spool). Returns null when Redis isn't configured so
|
|
151
|
+
* the caller keeps the in-memory default. Never throws.
|
|
152
|
+
*/
|
|
153
|
+
export function createEnvRedisTicketStore() {
|
|
154
|
+
const client = createEnvRedisClient('sse-ticket');
|
|
155
|
+
if (!client)
|
|
156
|
+
return null;
|
|
157
|
+
logger.info('Redis SSE ticket store initialized');
|
|
158
|
+
return createRedisTicketStore(client);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLXRpY2tldC1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL3NzZS10aWNrZXQtc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsWUFBWSxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFaEYsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGtCQUFrQixDQUFDLENBQUM7QUF3RGhEOzs7R0FHRztBQUNILE1BQU0sVUFBVSx1QkFBdUI7SUFDckMsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLEVBQXdCLENBQUM7SUFDaEQsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLEVBQXVCLENBQUM7SUFFOUMsT0FBTztRQUNMLEtBQUssQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxLQUFLO1lBQy9CLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLEVBQUUsR0FBRyxNQUFNLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQ3RFLENBQUM7UUFDRCxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVE7WUFDcEIsTUFBTSxDQUFDLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNoQyxPQUFPLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsMkNBQTJDO1lBQ3JFLElBQUksQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxTQUFTO2dCQUFFLE9BQU8sSUFBSSxDQUFDO1lBQ2hELE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ3BELENBQUM7UUFDRCxLQUFLLENBQUMsV0FBVyxD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QUFPLElBQUksQ0FBQztJQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLG9DQUFvQyxDQUFDLENBQUM7SUFDbEQsT0FBTyxzQkFBc0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUN4QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciwgY3JlYXRlRW52UmVkaXNDbGllbnQgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignc3NlLXRpY2tldC1zdG9yZScpO1xuXG4vKiogQSBtaW50ZWQgdGlja2V0J3MgYm91bmQgaWRlbnRpdHk6IG93bmluZyBvcmcgKyBub3JtYWxpemVkIHN0cmVhbSBzdWJqZWN0LiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VTdHJlYW1UaWNrZXRSZWNvcmQge1xuICBvcmdJZDogc3RyaW5nO1xuICAvKiogTm9ybWFsaXplZCAoZGFzaGVzIHN0cmlwcGVkLCBsb3dlcmNhc2VkKSBzdHJlYW0gc3ViamVjdCBpZC4gKi9cbiAgcmVxdWVzdElkOiBzdHJpbmc7XG59XG5cbi8qKlxuICogUGx1Z2dhYmxlIGJhY2tlbmQgZm9yIFNTRSBsb2ctc3RyZWFtIHRpY2tldHMgQU5EIHN0cmVhbS1vd25lcnNoaXAuXG4gKlxuICogTk9URTogZGlzdGluY3QgZnJvbSBhcGktY29yZSdzIGBTc2VUaWNrZXRTdG9yZWAgKHNlcnZpY2VzL3NzZS10aWNrZXQtc3RvcmUudHMpLFxuICogd2hpY2ggaXMgdGhlIG1lc3NhZ2Utc2VydmljZSB0aWNrZXQgc3RvcmUgKGlzc3VlICsgYWJ1c2UgY291bnRlcnMpLiBUaGlzIG9uZVxuICogKGFwaS1zZXJ2ZXIpIGFkZGl0aW9uYWxseSB0cmFja3Mgc3RyZWFtIG93bmVyc2hpcC4gVGhlIG5hbWVzIGRlbGliZXJhdGVseSBkaWZmZXJcbiAqIChgU1NFU3RyZWFtVGlja2V0U3RvcmVgIGhlcmUgdnMgYFNzZVRpY2tldFN0b3JlYCB0aGVyZSkgc28gY29uc3VtZXJzIGRvbid0IHBpY2tcbiAqIHRoZSB3cm9uZyBvbmUuXG4gKlxuICogVHdvIGNvbmNlcm5zIHNoYXJlIG9uZSBzdG9yZSBiZWNhdXNlIGJvdGggbXVzdCBiZSBjb25zaXN0ZW50IGFjcm9zcyBwb2RzOlxuICogLSAqKlRpY2tldHMqKiDigJQgc2hvcnQtbGl2ZWQsIHNpbmdsZS11c2UsIGJvdW5kIHRvIChvcmcsIHN1YmplY3QpLiBUaGUgZGVmYXVsdFxuICogICBpbi1tZW1vcnkgc3RvcmUgaXMgY29ycmVjdCBvbmx5IHNpbmdsZS1yZXBsaWNhOyB0aGUgUmVkaXMgc3RvcmUgbWFrZXMgdGhlXG4gKiAgIG1pbnQgKG9uZSBwb2QpIC8gcmVkZWVtIChhbm90aGVyIHBvZCkgZXhjaGFuZ2Ugd29yayBiZWhpbmQgYSBsb2FkIGJhbGFuY2VyLlxuICogLSAqKlN0cmVhbSBvd25lcnNoaXAqKiDigJQgd2hpY2ggb3JnIE9XTlMgYSBnaXZlbiBzdHJlYW0gc3ViamVjdC4gVGhlIHN0cmVhbVxuICogICBwcm9kdWNlciAocGxhdGZvcm0sIGEgZGlmZmVyZW50IHNlcnZpY2UpIHJlY29yZHMgdGhpcyBzbyB0aWNrZXQgbWludGluZyBjYW5cbiAqICAgYXNzZXJ0IHRoZSBjYWxsZXIncyBvcmcgb3ducyB0aGUgc3ViamVjdCwgY2xvc2luZyB0aGUgY3Jvc3MtdGVuYW50IGF0dGFjaFxuICogICBnYXAgd2hlcmUgYW55IG9yZyBjb3VsZCBtaW50IGEgdGlja2V0IGZvciBhIGd1ZXNzZWQgcmVxdWVzdElkLlxuICpcbiAqIEFsbCBtZXRob2RzIGFyZSBhc3luYyAoUmVkaXMgaXMgYXN5bmMpIGFuZCBiZXN0LWVmZm9ydCBvbiB0aGUgZmFpbHVyZSBzaWRlIHBlclxuICogdGhlaXIgZG9jIOKAlCBhIHN0b3JlIG91dGFnZSBtdXN0IGRlZ3JhZGUgZ3JhY2VmdWxseSwgbmV2ZXIgY3Jhc2ggdGhlIHN0cmVhbS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VTdHJlYW1UaWNrZXRTdG9yZSB7XG4gIC8qKiBQZXJzaXN0IGEgc2luZ2xlLXVzZSB0aWNrZXQgYm91bmQgdG8gYHJlY29yZGAsIGV4cGlyaW5nIGFmdGVyIGB0dGxNc2AuICovXG4gIHB1dCh0aWNrZXRJZDogc3RyaW5nLCByZWNvcmQ6IFNTRVN0cmVhbVRpY2tldFJlY29yZCwgdHRsTXM6IG51bWJlcik6IFByb21pc2U8dm9pZD47XG4gIC8qKiBBdG9taWNhbGx5IGZldGNoLWFuZC1kZWxldGUgYSB0aWNrZXQgKHNpbmdsZS11c2UpLiBOdWxsIGlmIGFic2VudC9leHBpcmVkLiAqL1xuICBjb25zdW1lKHRpY2tldElkOiBzdHJpbmcpOiBQcm9taXNlPFNTRVN0cmVhbVRpY2tldFJlY29yZCB8IG51bGw+O1xuICAvKiogQ291bnQgb2YgbGl2ZSAodW5leHBpcmVkKSB0aWNrZXRzIGhlbGQgZm9yIGFuIG9yZyAocGVyLW9yZyBjYXApLiAqL1xuICBjb3VudEZvck9yZyhvcmdJZDogc3RyaW5nKTogUHJvbWlzZTxudW1iZXI+O1xuICAvKiogQ291bnQgb2YgYWxsIGxpdmUgdGlja2V0cyBhY3Jvc3MgdGhlIHN0b3JlIChjYXBhY2l0eSBjYXApLiAqL1xuICB0b3RhbCgpOiBQcm9taXNlPG51bWJlcj47XG4gIC8qKiBSZWNvcmQgdGhlIG9yZyB0aGF0IE9XTlMgYSBzdHJlYW0gc3ViamVjdCAocHJvZHVjZXItc2lkZSksIFRUTCBgdHRsTXNgLiAqL1xuICBiaW5kU3RyZWFtT3duZXIocmVxdWVzdElkOiBzdHJpbmcsIG9yZ0lkOiBzdHJpbmcsIHR0bE1zOiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+O1xuICAvKiogVGhlIG93bmluZyBvcmcgb2YgYSBzdHJlYW0gc3ViamVjdCwgb3IgbnVsbCB3aGVuIG5vbmUgaXMgYm91bmQuICovXG4gIGdldFN0cmVhbU93bmVyKHJlcXVlc3RJZDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPjtcbiAgLyoqIE9wdGlvbmFsIGh5Z2llbmUgc3dlZXAgb2YgZXhwaXJlZCBlbnRyaWVzIChSZWRpcyBUVEwgbWFrZXMgdGhpcyBhIG5vLW9wKS4gKi9cbiAgc3dlZXA/KCk6IFByb21pc2U8dm9pZD47XG59XG5cbmludGVyZmFjZSBTdG9yZWRUaWNrZXQgZXh0ZW5kcyBTU0VTdHJlYW1UaWNrZXRSZWNvcmQge1xuICBleHBpcmVzQXQ6IG51bWJlcjtcbn1cblxuaW50ZXJmYWNlIFN0b3JlZE93bmVyIHtcbiAgb3JnSWQ6IHN0cmluZztcbiAgZXhwaXJlc0F0OiBudW1iZXI7XG59XG5cbi8qKlxuICogSW4tbWVtb3J5IHRpY2tldCBzdG9yZSDigJQgdGhlIGRlZmF1bHQuIENvcnJlY3QgZm9yIGEgc2luZ2xlIHJlcGxpY2Egb25seTtcbiAqIG1pcnJvcnMgdGhlIGJlaGF2aW9yIHRoZSBTU0VNYW5hZ2VyIHByZXZpb3VzbHkgaW1wbGVtZW50ZWQgaW5saW5lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlTWVtb3J5VGlja2V0U3RvcmUoKTogU1NFU3RyZWFtVGlja2V0U3RvcmUge1xuICBjb25zdCB0aWNrZXRzID0gbmV3IE1hcDxzdHJpbmcsIFN0b3JlZFRpY2tldD4oKTtcbiAgY29uc3Qgb3duZXJzID0gbmV3IE1hcDxzdHJpbmcsIFN0b3JlZE93bmVyPigpO1xuXG4gIHJldHVybiB7XG4gICAgYXN5bmMgcHV0KHRpY2tldElkLCByZWNvcmQsIHR0bE1zKSB7XG4gICAgICB0aWNrZXRzLnNldCh0aWNrZXRJZCwgeyAuLi5yZWNvcmQsIGV4cGlyZXNBdDogRGF0ZS5ub3coKSArIHR0bE1zIH0pO1xuICAgIH0sXG4gICAgYXN5bmMgY29uc3VtZSh0aWNrZXRJZCkge1xuICAgICAgY29uc3QgdCA9IHRpY2tldHMuZ2V0KHRpY2tldElkKTtcbiAgICAgIHRpY2tldHMuZGVsZXRlKHRpY2tldElkKTsgLy8gc2luZ2xlLXVzZSDigJQgcmVtb3ZlIHdoZXRoZXIgb3Igbm90IHZhbGlkXG4gICAgICBpZiAoIXQgfHwgRGF0ZS5ub3coKSA+IHQuZXhwaXJlc0F0KSByZXR1cm4gbnVsbDtcbiAgICAgIHJldHVybiB7IG9yZ0lkOiB0Lm9yZ0lkLCByZXF1ZXN0SWQ6IHQucmVxdWVzdElkIH07XG4gICAgfSxcbiAgICBhc3luYyBjb3VudEZvck9yZyhvcmdJZCkge1xuICAgICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICAgIGxldCBuID0gMDtcbiAgICAgIGZvciAoY29uc3QgdCBvZiB0aWNrZXRzLnZhbHVlcygpKSBpZiAodC5vcmdJZCA9PT0gb3JnSWQgJiYgdC5leHBpcmVzQXQgPiBub3cpIG4rKztcbiAgICAgIHJldHVybiBuO1xuICAgIH0sXG4gICAgYXN5bmMgdG90YWwoKSB7XG4gICAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgICAgbGV0IG4gPSAwO1xuICAgICAgZm9yIChjb25zdCB0IG9mIHRpY2tldHMudmFsdWVzKCkpIGlmICh0LmV4cGlyZXNBdCA+IG5vdykgbisrO1xuICAgICAgcmV0dXJuIG47XG4gICAgfSxcbiAgICBhc3luYyBiaW5kU3RyZWFtT3duZXIocmVxdWVzdElkLCBvcmdJZCwgdHRsTXMpIHtcbiAgICAgIG93bmVycy5zZXQocmVxdWVzdElkLCB7IG9yZ0lkLCBleHBpcmVzQXQ6IERhdGUubm93KCkgKyB0dGxNcyB9KTtcbiAgICB9LFxuICAgIGFzeW5jIGdldFN0cmVhbU93bmVyKHJlcXVlc3RJZCkge1xuICAgICAgY29uc3QgbyA9IG93bmVycy5nZXQocmVxdWVzdElkKTtcbiAgICAgIGlmICghbykgcmV0dXJuIG51bGw7XG4gICAgICBpZiAoRGF0ZS5ub3coKSA+IG8uZXhwaXJlc0F0KSB7IG93bmVycy5kZWxldGUocmVxdWVzdElkKTsgcmV0dXJuIG51bGw7IH1cbiAgICAgIHJldHVybiBvLm9yZ0lkO1xuICAgIH0sXG4gICAgYXN5bmMgc3dlZXAoKSB7XG4gICAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgICAgZm9yIChjb25zdCBbaWQsIHRdIG9mIHRpY2tldHMpIGlmIChub3cgPiB0LmV4cGlyZXNBdCkgdGlja2V0cy5kZWxldGUoaWQpO1xuICAgICAgZm9yIChjb25zdCBbaWQsIG9dIG9mIG93bmVycykgaWYgKG5vdyA+IG8uZXhwaXJlc0F0KSBvd25lcnMuZGVsZXRlKGlkKTtcbiAgICB9LFxuICB9O1xufVxuXG4vKipcbiAqIE1pbmltYWwgaW9yZWRpcyBzdXJmYWNlIHRoZSBSZWRpcyB0aWNrZXQgc3RvcmUgbmVlZHMuIGBzZXRgIHRha2VzIHRoZSB2YXJpYWRpY1xuICogb3B0aW9uIHRhaWwgKGBFWCA8cz5gKTsgYHphZGRgL2B6cmVtcmFuZ2VieXNjb3JlYC9gemNhcmRgIGJhY2sgdGhlIGNhcCBjb3VudGVycy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZWRpc1RpY2tldENsaWVudCB7XG4gIGdldChrZXk6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIHNldChrZXk6IHN0cmluZywgdmFsdWU6IHN0cmluZywgLi4uYXJnczogKHN0cmluZyB8IG51bWJlcilbXSk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIGRlbCguLi5rZXlzOiBzdHJpbmdbXSk6IFByb21pc2U8bnVtYmVyPjtcbiAgemFkZChrZXk6IHN0cmluZywgc2NvcmU6IG51bWJlciwgbWVtYmVyOiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj47XG4gIHpyZW0oa2V5OiBzdHJpbmcsIC4uLm1lbWJlcnM6IHN0cmluZ1tdKTogUHJvbWlzZTxudW1iZXI+O1xuICB6Y2FyZChrZXk6IHN0cmluZyk6IFByb21pc2U8bnVtYmVyPjtcbiAgenJlbXJhbmdlYnlzY29yZShrZXk6IHN0cmluZywgbWluOiBudW1iZXIgfCBzdHJpbmcsIG1heDogbnVtYmVyIHwgc3RyaW5nKTogUHJvbWlzZTxudW1iZXI+O1xuICBleHBpcmUoa2V5OiBzdHJpbmcsIHNlY29uZHM6IG51bWJlcik6IFByb21pc2U8bnVtYmVyPjtcbn1cblxuY29uc3QgVElDS0VUX1BSRUZJWCA9ICdzc2U6dGt0Oic7XG5jb25zdCBPUkdfWlNFVF9QUkVGSVggPSAnc3NlOnRrdDpvcmc6JztcbmNvbnN0IEFMTF9aU0VUID0gJ3NzZTp0a3Q6YWxsJztcbmNvbnN0IE9XTkVSX1BSRUZJWCA9ICdzc2U6b3duZXI6JztcblxuLyoqXG4gKiBSZWRpcy1iYWNrZWQgdGlja2V0IHN0b3JlIOKAlCByZXF1aXJlZCBmb3IgbXVsdGktcG9kIGNvcnJlY3RuZXNzLiBUaWNrZXRzIGFyZVxuICogcGVyLWtleSB3aXRoIG5hdGl2ZSBUVEw7IGNhcCBjb3VudGVycyBhcmUgWlNFVHMgc2NvcmVkIGJ5IGV4cGlyeSAocHJ1bmVkIG9uXG4gKiByZWFkKS4gU3RyZWFtIG93bmVyc2hpcCBpcyBhIHBlci1zdWJqZWN0IGtleSB3aXRoIGl0cyBvd24gVFRMLiBBbGwgcmVhZHMgYXJlXG4gKiBmYWlsLXNhZmUgKG51bGwgLyAwKSBzbyBhIFJlZGlzIGJsaXAgbmV2ZXIgd2VkZ2VzIHRoZSBzdHJlYW0uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVSZWRpc1RpY2tldFN0b3JlKHJlZGlzOiBSZWRpc1RpY2tldENsaWVudCk6IFNTRVN0cmVhbVRpY2tldFN0b3JlIHtcbiAgY29uc3QgdGtleSA9IChpZDogc3RyaW5nKTogc3RyaW5nID0+IGAke1RJQ0tFVF9QUkVGSVh9JHtpZH1gO1xuICBjb25zdCBvcmdLZXkgPSAob3JnSWQ6IHN0cmluZyk6IHN0cmluZyA9PiBgJHtPUkdfWlNFVF9QUkVGSVh9JHtvcmdJZH1gO1xuICBjb25zdCBvd25lcktleSA9IChyZXF1ZXN0SWQ6IHN0cmluZyk6IHN0cmluZyA9PiBgJHtPV05FUl9QUkVGSVh9JHtyZXF1ZXN0SWR9YDtcbiAgY29uc3QgdHRsU2VjID0gKHR0bE1zOiBudW1iZXIpOiBudW1iZXIgPT4gTWF0aC5tYXgoMSwgTWF0aC5jZWlsKHR0bE1zIC8gMTAwMCkpO1xuXG4gIHJldHVybiB7XG4gICAgYXN5bmMgcHV0KHRpY2tldElkLCByZWNvcmQsIHR0bE1zKSB7XG4gICAgICBjb25zdCBleHBpcmVzQXQgPSBEYXRlLm5vdygpICsgdHRsTXM7XG4gICAgICBjb25zdCBzZWMgPSB0dGxTZWModHRsTXMpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgcmVkaXMuc2V0KHRrZXkodGlja2V0SWQpLCBKU09OLnN0cmluZ2lmeShyZWNvcmQpLCAnRVgnLCBzZWMpO1xuICAgICAgICAvLyBUcmFjayBmb3IgdGhlIGNhcHMuIFNjb3JlID0gZXhwaXJ5IHNvIHBydW5pbmcgaXMgYSByYW5nZSBkZWxldGUuXG4gICAgICAgIGF3YWl0IHJlZGlzLnphZGQob3JnS2V5KHJlY29yZC5vcmdJZCksIGV4cGlyZXNBdCwgdGlja2V0SWQpO1xuICAgICAgICBhd2FpdCByZWRpcy56YWRkKEFMTF9aU0VULCBleHBpcmVzQXQsIHRpY2tldElkKTtcbiAgICAgICAgLy8gQm91bmQgdGhlIHBlci1vcmcgWlNFVCdzIG93biBsaWZldGltZSBzbyBhbiBpZGxlIG9yZydzIGtleSBjYW4ndCBsZWFrLlxuICAgICAgICBhd2FpdCByZWRpcy5leHBpcmUob3JnS2V5KHJlY29yZC5vcmdJZCksIHNlYyk7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1NTRSB0aWNrZXQgcHV0IGZhaWxlZCcsIHsgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9LFxuICAgIGFzeW5jIGNvbnN1bWUodGlja2V0SWQpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJhdyA9IGF3YWl0IHJlZGlzLmdldCh0a2V5KHRpY2tldElkKSk7XG4gICAgICAgIC8vIFNpbmdsZS11c2U6IGRlbGV0ZSByZWdhcmRsZXNzLiBBIGNvbmN1cnJlbnQgZG91YmxlLXJlZGVlbTogYXQgbW9zdCBvbmVcbiAgICAgICAgLy8gc2F3IGEgbm9uLW51bGwgdmFsdWUgYmVmb3JlIHRoZSBkZWxldGU7IHRoZSBaUkVNIGNsZWFucyB0aGUgY291bnRlcnMuXG4gICAgICAgIGF3YWl0IHJlZGlzLmRlbCh0a2V5KHRpY2tldElkKSk7XG4gICAgICAgIGlmICghcmF3KSByZXR1cm4gbnVsbDtcbiAgICAgICAgY29uc3QgcmVjID0gSlNPTi5wYXJzZShyYXcpIGFzIFNTRVN0cmVhbVRpY2tldFJlY29yZDtcbiAgICAgICAgYXdhaXQgcmVkaXMuenJlbShvcmdLZXkocmVjLm9yZ0lkKSwgdGlja2V0SWQpO1xuICAgICAgICBhd2FpdCByZWRpcy56cmVtKEFMTF9aU0VULCB0aWNrZXRJZCk7XG4gICAgICAgIHJldHVybiByZWM7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1NTRSB0aWNrZXQgY29uc3VtZSBmYWlsZWQnLCB7IGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycikgfSk7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgIH0sXG4gICAgYXN5bmMgY291bnRGb3JPcmcob3JnSWQpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHJlZGlzLnpyZW1yYW5nZWJ5c2NvcmUob3JnS2V5KG9yZ0lkKSwgMCwgRGF0ZS5ub3coKSk7XG4gICAgICAgIHJldHVybiBhd2FpdCByZWRpcy56Y2FyZChvcmdLZXkob3JnSWQpKTtcbiAgICAgIH0gY2F0Y2ggeyByZXR1cm4gMDsgfVxuICAgIH0sXG4gICAgYXN5bmMgdG90YWwoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCByZWRpcy56cmVtcmFuZ2VieXNjb3JlKEFMTF9aU0VULCAwLCBEYXRlLm5vdygpKTtcbiAgICAgICAgcmV0dXJuIGF3YWl0IHJlZGlzLnpjYXJkKEFMTF9aU0VUKTtcbiAgICAgIH0gY2F0Y2ggeyByZXR1cm4gMDsgfVxuICAgIH0sXG4gICAgYXN5bmMgYmluZFN0cmVhbU93bmVyKHJlcXVlc3RJZCwgb3JnSWQsIHR0bE1zKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCByZWRpcy5zZXQob3duZXJLZXkocmVxdWVzdElkKSwgb3JnSWQsICdFWCcsIHR0bFNlYyh0dGxNcykpO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdTU0Ugc3RyZWFtLW93bmVyIGJpbmQgZmFpbGVkJywgeyBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpIH0pO1xuICAgICAgfVxuICAgIH0sXG4gICAgYXN5bmMgZ2V0U3RyZWFtT3duZXIocmVxdWVzdElkKSB7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gYXdhaXQgcmVkaXMuZ2V0KG93bmVyS2V5KHJlcXVlc3RJZCkpO1xuICAgICAgfSBjYXRjaCB7IHJldHVybiBudWxsOyB9XG4gICAgfSxcbiAgfTtcbn1cblxuLyoqXG4gKiBCdWlsZCBhIFJlZGlzLWJhY2tlZCB0aWNrZXQgc3RvcmUgZnJvbSB0aGUgc2hhcmVkIGVudiBSZWRpcyAoc2FtZSB3aXJpbmcgYXNcbiAqIHRoZSByYXRlLWxpbWl0ZXIgLyBhdWRpdC1zcG9vbCkuIFJldHVybnMgbnVsbCB3aGVuIFJlZGlzIGlzbid0IGNvbmZpZ3VyZWQgc29cbiAqIHRoZSBjYWxsZXIga2VlcHMgdGhlIGluLW1lbW9yeSBkZWZhdWx0LiBOZXZlciB0aHJvd3MuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVFbnZSZWRpc1RpY2tldFN0b3JlKCk6IFNTRVN0cmVhbVRpY2tldFN0b3JlIHwgbnVsbCB7XG4gIGNvbnN0IGNsaWVudCA9IGNyZWF0ZUVudlJlZGlzQ2xpZW50PFJlZGlzVGlja2V0Q2xpZW50Pignc3NlLXRpY2tldCcpO1xuICBpZiAoIWNsaWVudCkgcmV0dXJuIG51bGw7XG4gIGxvZ2dlci5pbmZvKCdSZWRpcyBTU0UgdGlja2V0IHN0b3JlIGluaXRpYWxpemVkJyk7XG4gIHJldHVybiBjcmVhdGVSZWRpc1RpY2tldFN0b3JlKGNsaWVudCk7XG59XG4iXX0=
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @pipeline-builder/api-server
|
|
3
|
+
*
|
|
4
|
+
* Express server infrastructure and request lifecycle utilities.
|
|
5
|
+
*
|
|
6
|
+
* **App Factory**
|
|
7
|
+
* - createApp — creates a configured Express app with CORS, Helmet, rate limiting
|
|
8
|
+
* - runServer, startServer — server lifecycle with graceful shutdown
|
|
9
|
+
*
|
|
10
|
+
* **Middleware**
|
|
11
|
+
* - attachRequestContext / createRequestContext — attaches identity + logging to each request
|
|
12
|
+
* - requireOrgId — validates organization ID is present on the request
|
|
13
|
+
* - withTenantContext — opens the RLS tenant scope for the request
|
|
14
|
+
* - checkQuota — quota enforcement middleware
|
|
15
|
+
* - idempotencyMiddleware — idempotent request handling
|
|
16
|
+
* - createProtectedRoute / createAuthenticatedWithOrgRoute — composable middleware chains
|
|
17
|
+
*
|
|
18
|
+
* **Route Helpers**
|
|
19
|
+
* - withRoute — wraps async route handlers with context extraction, orgId validation, and error handling
|
|
20
|
+
* - getContext — retrieves RequestContext from the Express request
|
|
21
|
+
* - RouteContext, RequestContext — route and request context types
|
|
22
|
+
*
|
|
23
|
+
* **SSE**
|
|
24
|
+
* - SSEManager — Server-Sent Events connection manager
|
|
25
|
+
*
|
|
26
|
+
* **Observability**
|
|
27
|
+
* - Tracing and metrics collection utilities
|
|
28
|
+
*/
|
|
29
|
+
export * from './api/index.js';
|
|
30
|
+
export * from './http/index.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* @module @pipeline-builder/api-server
|
|
5
|
+
*
|
|
6
|
+
* Express server infrastructure and request lifecycle utilities.
|
|
7
|
+
*
|
|
8
|
+
* **App Factory**
|
|
9
|
+
* - createApp — creates a configured Express app with CORS, Helmet, rate limiting
|
|
10
|
+
* - runServer, startServer — server lifecycle with graceful shutdown
|
|
11
|
+
*
|
|
12
|
+
* **Middleware**
|
|
13
|
+
* - attachRequestContext / createRequestContext — attaches identity + logging to each request
|
|
14
|
+
* - requireOrgId — validates organization ID is present on the request
|
|
15
|
+
* - withTenantContext — opens the RLS tenant scope for the request
|
|
16
|
+
* - checkQuota — quota enforcement middleware
|
|
17
|
+
* - idempotencyMiddleware — idempotent request handling
|
|
18
|
+
* - createProtectedRoute / createAuthenticatedWithOrgRoute — composable middleware chains
|
|
19
|
+
*
|
|
20
|
+
* **Route Helpers**
|
|
21
|
+
* - withRoute — wraps async route handlers with context extraction, orgId validation, and error handling
|
|
22
|
+
* - getContext — retrieves RequestContext from the Express request
|
|
23
|
+
* - RouteContext, RequestContext — route and request context types
|
|
24
|
+
*
|
|
25
|
+
* **SSE**
|
|
26
|
+
* - SSEManager — Server-Sent Events connection manager
|
|
27
|
+
*
|
|
28
|
+
* **Observability**
|
|
29
|
+
* - Tracing and metrics collection utilities
|
|
30
|
+
*/
|
|
31
|
+
// API Infrastructure
|
|
32
|
+
export * from './api/index.js';
|
|
33
|
+
// HTTP Utilities
|
|
34
|
+
export * from './http/index.js';
|
|
35
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBMkJHO0FBRUgscUJBQXFCO0FBQ3JCLGNBQWMsZ0JBQWdCLENBQUM7QUFFL0IsaUJBQWlCO0FBQ2pCLGNBQWMsaUJBQWlCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBAbW9kdWxlIEBwaXBlbGluZS1idWlsZGVyL2FwaS1zZXJ2ZXJcbiAqXG4gKiBFeHByZXNzIHNlcnZlciBpbmZyYXN0cnVjdHVyZSBhbmQgcmVxdWVzdCBsaWZlY3ljbGUgdXRpbGl0aWVzLlxuICpcbiAqICoqQXBwIEZhY3RvcnkqKlxuICogLSBjcmVhdGVBcHAg4oCUIGNyZWF0ZXMgYSBjb25maWd1cmVkIEV4cHJlc3MgYXBwIHdpdGggQ09SUywgSGVsbWV0LCByYXRlIGxpbWl0aW5nXG4gKiAtIHJ1blNlcnZlciwgc3RhcnRTZXJ2ZXIg4oCUIHNlcnZlciBsaWZlY3ljbGUgd2l0aCBncmFjZWZ1bCBzaHV0ZG93blxuICpcbiAqICoqTWlkZGxld2FyZSoqXG4gKiAtIGF0dGFjaFJlcXVlc3RDb250ZXh0IC8gY3JlYXRlUmVxdWVzdENvbnRleHQg4oCUIGF0dGFjaGVzIGlkZW50aXR5ICsgbG9nZ2luZyB0byBlYWNoIHJlcXVlc3RcbiAqIC0gcmVxdWlyZU9yZ0lkIOKAlCB2YWxpZGF0ZXMgb3JnYW5pemF0aW9uIElEIGlzIHByZXNlbnQgb24gdGhlIHJlcXVlc3RcbiAqIC0gd2l0aFRlbmFudENvbnRleHQg4oCUIG9wZW5zIHRoZSBSTFMgdGVuYW50IHNjb3BlIGZvciB0aGUgcmVxdWVzdFxuICogLSBjaGVja1F1b3RhIOKAlCBxdW90YSBlbmZvcmNlbWVudCBtaWRkbGV3YXJlXG4gKiAtIGlkZW1wb3RlbmN5TWlkZGxld2FyZSDigJQgaWRlbXBvdGVudCByZXF1ZXN0IGhhbmRsaW5nXG4gKiAtIGNyZWF0ZVByb3RlY3RlZFJvdXRlIC8gY3JlYXRlQXV0aGVudGljYXRlZFdpdGhPcmdSb3V0ZSDigJQgY29tcG9zYWJsZSBtaWRkbGV3YXJlIGNoYWluc1xuICpcbiAqICoqUm91dGUgSGVscGVycyoqXG4gKiAtIHdpdGhSb3V0ZSDigJQgd3JhcHMgYXN5bmMgcm91dGUgaGFuZGxlcnMgd2l0aCBjb250ZXh0IGV4dHJhY3Rpb24sIG9yZ0lkIHZhbGlkYXRpb24sIGFuZCBlcnJvciBoYW5kbGluZ1xuICogLSBnZXRDb250ZXh0IOKAlCByZXRyaWV2ZXMgUmVxdWVzdENvbnRleHQgZnJvbSB0aGUgRXhwcmVzcyByZXF1ZXN0XG4gKiAtIFJvdXRlQ29udGV4dCwgUmVxdWVzdENvbnRleHQg4oCUIHJvdXRlIGFuZCByZXF1ZXN0IGNvbnRleHQgdHlwZXNcbiAqXG4gKiAqKlNTRSoqXG4gKiAtIFNTRU1hbmFnZXIg4oCUIFNlcnZlci1TZW50IEV2ZW50cyBjb25uZWN0aW9uIG1hbmFnZXJcbiAqXG4gKiAqKk9ic2VydmFiaWxpdHkqKlxuICogLSBUcmFjaW5nIGFuZCBtZXRyaWNzIGNvbGxlY3Rpb24gdXRpbGl0aWVzXG4gKi9cblxuLy8gQVBJIEluZnJhc3RydWN0dXJlXG5leHBvcnQgKiBmcm9tICcuL2FwaS9pbmRleC5qcyc7XG5cbi8vIEhUVFAgVXRpbGl0aWVzXG5leHBvcnQgKiBmcm9tICcuL2h0dHAvaW5kZXguanMnOyJdfQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|