@pipeline-builder/api-server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +79 -0
  3. package/lib/api/app-factory.d.ts +95 -0
  4. package/lib/api/app-factory.js +328 -0
  5. package/lib/api/check-quota.d.ts +13 -0
  6. package/lib/api/check-quota.js +74 -0
  7. package/lib/api/context-middleware.d.ts +34 -0
  8. package/lib/api/context-middleware.js +42 -0
  9. package/lib/api/etag-middleware.d.ts +7 -0
  10. package/lib/api/etag-middleware.js +44 -0
  11. package/lib/api/get-context.d.ts +22 -0
  12. package/lib/api/get-context.js +28 -0
  13. package/lib/api/health-checks.d.ts +62 -0
  14. package/lib/api/health-checks.js +87 -0
  15. package/lib/api/idempotency-middleware.d.ts +80 -0
  16. package/lib/api/idempotency-middleware.js +321 -0
  17. package/lib/api/index.d.ts +20 -0
  18. package/lib/api/index.js +34 -0
  19. package/lib/api/meter-quota.d.ts +32 -0
  20. package/lib/api/meter-quota.js +70 -0
  21. package/lib/api/metrics.d.ts +47 -0
  22. package/lib/api/metrics.js +221 -0
  23. package/lib/api/middleware-factory.d.ts +56 -0
  24. package/lib/api/middleware-factory.js +80 -0
  25. package/lib/api/middleware.d.ts +1 -0
  26. package/lib/api/middleware.js +8 -0
  27. package/lib/api/mongo-connect.d.ts +19 -0
  28. package/lib/api/mongo-connect.js +41 -0
  29. package/lib/api/quota-helpers.d.ts +23 -0
  30. package/lib/api/quota-helpers.js +22 -0
  31. package/lib/api/rate-limit-by-org.d.ts +29 -0
  32. package/lib/api/rate-limit-by-org.js +64 -0
  33. package/lib/api/readiness.d.ts +29 -0
  34. package/lib/api/readiness.js +65 -0
  35. package/lib/api/request-types.d.ts +55 -0
  36. package/lib/api/request-types.js +65 -0
  37. package/lib/api/require-org-id.d.ts +14 -0
  38. package/lib/api/require-org-id.js +28 -0
  39. package/lib/api/route-wrapper.d.ts +50 -0
  40. package/lib/api/route-wrapper.js +84 -0
  41. package/lib/api/server.d.ts +79 -0
  42. package/lib/api/server.js +234 -0
  43. package/lib/api/tenant-context.d.ts +28 -0
  44. package/lib/api/tenant-context.js +48 -0
  45. package/lib/api/tracing.d.ts +25 -0
  46. package/lib/api/tracing.js +69 -0
  47. package/lib/http/index.d.ts +4 -0
  48. package/lib/http/index.js +7 -0
  49. package/lib/http/sse-connection-manager.d.ts +357 -0
  50. package/lib/http/sse-connection-manager.js +665 -0
  51. package/lib/http/sse-relay.d.ts +63 -0
  52. package/lib/http/sse-relay.js +70 -0
  53. package/lib/http/sse-ticket-channel.d.ts +31 -0
  54. package/lib/http/sse-ticket-channel.js +52 -0
  55. package/lib/http/sse-ticket-store.d.ts +75 -0
  56. package/lib/http/sse-ticket-store.js +160 -0
  57. package/lib/index.d.ts +30 -0
  58. package/lib/index.js +35 -0
  59. package/lib/otel-bootstrap.d.ts +1 -0
  60. package/lib/otel-bootstrap.js +75 -0
  61. package/package.json +183 -0
@@ -0,0 +1,234 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Server } from 'http';
4
+ import { createLogger, errorMessage, installCrashHandlers } from '@pipeline-builder/api-core';
5
+ import { Config } from '@pipeline-builder/pipeline-core';
6
+ import { getConnection, closeConnection } from '@pipeline-builder/pipeline-data';
7
+ import { setReady, isReady } from './readiness.js';
8
+ import { shutdownTracing } from './tracing.js';
9
+ import { SSEManager } from '../http/sse-connection-manager.js';
10
+ const logger = createLogger('server');
11
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
12
+ // Dependency-supervisor cadence. Initial connect retries with capped backoff;
13
+ // once connected, re-checks at a steady interval so a later outage flips the
14
+ // service NotReady (draining traffic) and a recovery flips it back — without
15
+ // ever exiting / restarting the process.
16
+ const READY_RETRY_BASE_MS = 1000;
17
+ const READY_RETRY_MAX_MS = 10000;
18
+ const READY_MONITOR_INTERVAL_MS = parseInt(process.env.READINESS_MONITOR_INTERVAL_MS || '15000', 10);
19
+ /**
20
+ * Establish and continuously monitor the service's datastore dependency,
21
+ * flipping readiness state instead of crash-looping.
22
+ *
23
+ * - Establish phase: retry `onBeforeStart` (e.g. connectMongo) with capped
24
+ * backoff until it succeeds — a cold datastore must never exit the process.
25
+ * - Monitor phase: poll `testDatabase` for the life of the process; flip
26
+ * ready/NotReady on transitions. While NotReady the readiness guard 503s
27
+ * business traffic and orchestrator readiness probes drain the instance.
28
+ *
29
+ * Runs in the background (the HTTP server is already listening). Stops when
30
+ * `aborted()` returns true (graceful shutdown), so it never keeps a closing
31
+ * process alive.
32
+ */
33
+ async function superviseDependencies(name, onBeforeStart, testDatabase, aborted) {
34
+ if (onBeforeStart) {
35
+ let delay = READY_RETRY_BASE_MS;
36
+ while (!aborted()) {
37
+ try {
38
+ await onBeforeStart();
39
+ break;
40
+ }
41
+ catch (error) {
42
+ logger.warn(`${name}: dependency init failed, retrying in ${delay}ms`, { error: errorMessage(error) });
43
+ await sleep(delay);
44
+ delay = Math.min(delay * 2, READY_RETRY_MAX_MS);
45
+ }
46
+ }
47
+ }
48
+ // No datastore to gate on → ready as soon as we are listening.
49
+ if (testDatabase === false) {
50
+ setReady(true);
51
+ logger.info(`${name} ready (no datastore dependency)`);
52
+ return;
53
+ }
54
+ const probe = testDatabase ?? (() => getConnection().testConnection());
55
+ let waitDelay = READY_RETRY_BASE_MS;
56
+ while (!aborted()) {
57
+ let ok = false;
58
+ try {
59
+ ok = await probe();
60
+ }
61
+ catch {
62
+ ok = false;
63
+ }
64
+ if (ok && !isReady()) {
65
+ setReady(true);
66
+ waitDelay = READY_RETRY_BASE_MS;
67
+ logger.info(`${name} ready — dependencies connected`);
68
+ }
69
+ else if (!ok && isReady()) {
70
+ setReady(false);
71
+ logger.warn(`${name} degraded — dependency check failing (now NotReady)`);
72
+ }
73
+ // Steady cadence once ready; faster backoff while waiting so we flip to
74
+ // ready promptly when the datastore comes up.
75
+ if (isReady()) {
76
+ await sleep(READY_MONITOR_INTERVAL_MS);
77
+ }
78
+ else {
79
+ await sleep(waitDelay);
80
+ waitDelay = Math.min(waitDelay * 2, READY_RETRY_MAX_MS);
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * Start an Express server with graceful shutdown handling
86
+ *
87
+ * Features:
88
+ * - Automatic database connection testing
89
+ * - Graceful shutdown on SIGINT/SIGTERM
90
+ * - SSE connection cleanup
91
+ * - Configurable shutdown timeout
92
+ *
93
+ * @param app - Express application
94
+ * @param options - Server options
95
+ * @returns Server instance and control functions
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * const { app, sseManager } = createApp();
100
+ *
101
+ * app.post('/api/resource', requireAuth, handler);
102
+ *
103
+ * await startServer(app, {
104
+ * name: 'My Microservice',
105
+ * sseManager,
106
+ * onStart: (port) => console.log(`Listening on ${port}`),
107
+ * });
108
+ * ```
109
+ */
110
+ export async function startServer(app, options = {}) {
111
+ const serverConfig = Config.get('server');
112
+ const { name = 'Microservice', sseManager, shutdownTimeoutMs = 15000, onStart, onShutdown, onBeforeStart, testDatabase, closeDatabase, } = options;
113
+ const port = options.port ?? serverConfig.port;
114
+ // Validate auth configuration at server startup (not during CDK synthesis)
115
+ Config.validateAuth();
116
+ logger.info(`Starting ${name}...`);
117
+ // Mark NotReady synchronously, BEFORE the port opens, so no request slips
118
+ // through the readiness guard before the supervisor has run. The supervisor
119
+ // flips it true once dependencies connect.
120
+ setReady(false);
121
+ // Listen FIRST. Previously the process tested the DB and `process.exit(1)`'d
122
+ // on failure (to be restarted), so during a cold-DB stampede services
123
+ // crash-looped and never even opened their port. Now we open the port
124
+ // immediately — /health (liveness), /ready (readiness) and the readiness
125
+ // guard all respond at once — and establish + monitor the datastore in the
126
+ // background, reporting NotReady until it connects instead of exiting.
127
+ const server = app.listen(port, () => {
128
+ logger.info(`${name} listening on port: ${port}`);
129
+ logger.info(`Platform URL: ${serverConfig.platformUrl}`);
130
+ onStart?.(port);
131
+ });
132
+ // Shutdown handler (guarded against concurrent signals)
133
+ let shuttingDown = false;
134
+ // Background dependency supervisor — established + monitored after listen,
135
+ // stopped on shutdown so it never keeps a closing process alive. Guard the
136
+ // floating promise: every expected failure is handled inside, so a rejection
137
+ // here means a programming error — log it and leave the service NotReady
138
+ // rather than letting it become an unhandledRejection that crash-exits the
139
+ // process (which would defeat the whole "never exit on dependency trouble"
140
+ // goal).
141
+ superviseDependencies(name, onBeforeStart, testDatabase, () => shuttingDown).catch((error) => {
142
+ logger.error('Dependency supervisor crashed; service will remain NotReady', { error: errorMessage(error) });
143
+ });
144
+ const shutdown = async (signal) => {
145
+ if (shuttingDown)
146
+ return;
147
+ shuttingDown = true;
148
+ if (signal) {
149
+ logger.info(`${signal} received, shutting down gracefully...`);
150
+ }
151
+ // Custom shutdown callback
152
+ if (onShutdown) {
153
+ try {
154
+ await onShutdown();
155
+ }
156
+ catch (error) {
157
+ logger.error('Error in onShutdown callback', { error });
158
+ }
159
+ }
160
+ // Close SSE connections
161
+ if (sseManager) {
162
+ sseManager.shutdown();
163
+ logger.info('SSE connections closed');
164
+ }
165
+ // Close HTTP server
166
+ server.close(async () => {
167
+ logger.info('HTTP server closed');
168
+ // Shutdown OpenTelemetry tracing
169
+ await shutdownTracing().catch(err => logger.error('Error shutting down tracing', { error: err }));
170
+ // Close database connection
171
+ if (closeDatabase !== false) {
172
+ try {
173
+ if (closeDatabase) {
174
+ await closeDatabase();
175
+ }
176
+ else {
177
+ await closeConnection();
178
+ }
179
+ logger.info('Database connection closed');
180
+ }
181
+ catch (error) {
182
+ logger.error('Error closing database', { error });
183
+ }
184
+ }
185
+ process.exit(0);
186
+ });
187
+ // Force shutdown after timeout
188
+ setTimeout(() => {
189
+ logger.error('Forced shutdown after timeout');
190
+ process.exit(1);
191
+ }, shutdownTimeoutMs).unref();
192
+ };
193
+ // Register signal handlers
194
+ process.once('SIGINT', () => void shutdown('SIGINT'));
195
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
196
+ return {
197
+ server,
198
+ port,
199
+ shutdown: () => shutdown(),
200
+ };
201
+ }
202
+ /**
203
+ * Simple server start wrapper with error handling
204
+ *
205
+ * @param app - Express application
206
+ * @param options - Server options
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * const { app, sseManager } = createApp();
211
+ * app.post('/api/resource', handler);
212
+ *
213
+ * void runServer(app, { name: 'My Service', sseManager });
214
+ * ```
215
+ */
216
+ export function runServer(app, options = {}) {
217
+ // Last-resort fault handlers for the production entrypoint: an unhandled
218
+ // rejection / uncaught exception is logged (not silently fatal) then the
219
+ // process exits for the orchestrator to restart. No-op under NODE_ENV=test.
220
+ installCrashHandlers(logger);
221
+ startServer(app, options).catch((error) => {
222
+ // startServer now listens-first and supervises the DB in the background, so
223
+ // it only rejects on a genuinely fatal SETUP error (invalid auth config, a
224
+ // failed onStart, the port already in use). Log message + stack explicitly:
225
+ // an Error in winston metadata serializes to `{}` (message/stack are
226
+ // non-enumerable), which would otherwise hide the cause behind `{error:{}}`.
227
+ logger.error('Failed to start server', {
228
+ error: error instanceof Error ? error.message : String(error),
229
+ stack: error instanceof Error ? error.stack : undefined,
230
+ });
231
+ process.exit(1);
232
+ });
233
+ }
234
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS9zZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzlCLE9BQU8sRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDOUYsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pELE9BQU8sRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFakYsT0FBTyxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNuRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQy9DLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUUvRCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFdEMsTUFBTSxLQUFLLEdBQUcsQ0FBQyxFQUFVLEVBQWlCLEVBQUUsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBRS9GLDhFQUE4RTtBQUM5RSw2RUFBNkU7QUFDN0UsNkVBQTZFO0FBQzdFLHlDQUF5QztBQUN6QyxNQUFNLG1CQUFtQixHQUFHLElBQUksQ0FBQztBQUNqQyxNQUFNLGtCQUFrQixHQUFHLEtBQUssQ0FBQztBQUNqQyxNQUFNLHlCQUF5QixHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLDZCQUE2QixJQUFJLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQztBQUVyRzs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsS0FBSyxVQUFVLHFCQUFxQixDQUNsQyxJQUFZLEVBQ1osYUFBZ0QsRUFDaEQsWUFBMEQsRUFDMUQsT0FBc0I7SUFFdEIsSUFBSSxhQUFhLEVBQUUsQ0FBQztRQUNsQixJQUFJLEtBQUssR0FBRyxtQkFBbUIsQ0FBQztRQUNoQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUNsQixJQUFJLENBQUM7Z0JBQ0gsTUFBTSxhQUFhLEVBQUUsQ0FBQztnQkFDdEIsTUFBTTtZQUNSLENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNmLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxJQUFJLHlDQUF5QyxLQUFLLElBQUksRUFBRSxFQUFFLEtBQUssRUFBRSxZQUFZLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO2dCQUN2RyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDbkIsS0FBSyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxHQUFHLENBQUMsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1lBQ2xELENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVELCtEQUErRDtJQUMvRCxJQUFJLFlBQVksS0FBSyxLQUFLLEVBQUUsQ0FBQztRQUMzQixRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDZixNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsSUFBSSxrQ0FBa0MsQ0FBQyxDQUFDO1FBQ3ZELE9BQU87SUFDVCxDQUFDO0lBRUQsTUFBTSxLQUFLLEdBQUcsWUFBWSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsYUFBYSxFQUFFLENBQUMsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUN2RSxJQUFJLFNBQVMsR0FBRyxtQkFBbUIsQ0FBQztJQUNwQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztRQUNsQixJQUFJLEVBQUUsR0FBRyxLQUFLLENBQUM7UUFDZixJQUFJLENBQUM7WUFDSCxFQUFFLEdBQUcsTUFBTSxLQUFLLEVBQUUsQ0FBQztRQUNyQixDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsRUFBRSxHQUFHLEtBQUssQ0FBQztRQUNiLENBQUM7UUFFRCxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7WUFDckIsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2YsU0FBUyxHQUFHLG1CQUFtQixDQUFDO1lBQ2hDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxJQUFJLGlDQUFpQyxDQUFDLENBQUM7UUFDeEQsQ0FBQzthQUFNLElBQUksQ0FBQyxFQUFFLElBQUksT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUM1QixRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDaEIsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUkscURBQXFELENBQUMsQ0FBQztRQUM1RSxDQUFDO1FBRUQsd0VBQXdFO1FBQ3hFLDhDQUE4QztRQUM5QyxJQUFJLE9BQU8sRUFBRSxFQUFFLENBQUM7WUFDZCxNQUFNLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO1FBQ3pDLENBQUM7YUFBTSxDQUFDO1lBQ04sTUFBTSxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDdkIsU0FBUyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsU0FBUyxHQUFHLENBQUMsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1FBQzFELENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQztBQXNDRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlCRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsV0FBVyxDQUMvQixHQUFZLEVBQ1osT0FBTyxHQUF1QixFQUFFO0lBRWhDLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDMUMsTUFBTSxFQUNKLElBQUksR0FBRyxjQUFjLEVBQ3JCLFVBQVUsRUFDVixpQkFBaUIsR0FBRyxLQUFLLEVBQ3pCLE9BQU8sRUFDUCxVQUFVLEVBQ1YsYUFBYSxFQUNiLFlBQVksRUFDWixhQUFhLEdBQ2QsR0FBRyxPQUFPLENBQUM7SUFDWixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxJQUFJLFlBQVksQ0FBQyxJQUFJLENBQUM7SUFFL0MsMkVBQTJFO0lBQzNFLE1BQU0sQ0FBQyxZQUFZLEVBQUUsQ0FBQztJQUV0QixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksSUFBSSxLQUFLLENBQUMsQ0FBQztJQUVuQywwRUFBMEU7SUFDMUUsNEVBQTRFO0lBQzVFLDJDQUEyQztJQUMzQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7SUFFaEIsNkVBQTZFO0lBQzdFLHNFQUFzRTtJQUN0RSxzRUFBc0U7SUFDdEUseUVBQXlFO0lBQ3pFLDJFQUEyRTtJQUMzRSx1RUFBdUU7SUFDdkUsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFO1FBQ25DLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxJQUFJLHVCQUF1QixJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ2xELE1BQU0sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLFlBQVksQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO1FBQ3pELE9BQU8sRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2xCLENBQUMsQ0FBQyxDQUFDO0lBRUgsd0RBQXdEO0lBQ3hELElBQUksWUFBWSxHQUFHLEtBQUssQ0FBQztJQUV6QiwyRUFBMkU7SUFDM0UsMkVBQTJFO0lBQzNFLDZFQUE2RTtJQUM3RSx5RUFBeUU7SUFDekUsMkVBQTJFO0lBQzNFLDJFQUEyRTtJQUMzRSxTQUFTO0lBQ1QscUJBQXFCLENBQUMsSUFBSSxFQUFFLGFBQWEsRUFBRSxZQUFZLEVBQUUsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUU7UUFDM0YsTUFBTSxDQUFDLEtBQUssQ0FBQyw2REFBNkQsRUFBRSxFQUFFLEtBQUssRUFBRSxZQUFZLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzlHLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxRQUFRLEdBQUcsS0FBSyxFQUFFLE1BQWUsRUFBRSxFQUFFO1FBQ3pDLElBQUksWUFBWTtZQUFFLE9BQU87UUFDekIsWUFBWSxHQUFHLElBQUksQ0FBQztRQUVwQixJQUFJLE1BQU0sRUFBRSxDQUFDO1lBQ1gsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE1BQU0sd0NBQXdDLENBQUMsQ0FBQztRQUNqRSxDQUFDO1FBRUQsMkJBQTJCO1FBQzNCLElBQUksVUFBVSxFQUFFLENBQUM7WUFDZixJQUFJLENBQUM7Z0JBQ0gsTUFBTSxVQUFVLEVBQUUsQ0FBQztZQUNyQixDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixNQUFNLENBQUMsS0FBSyxDQUFDLDhCQUE4QixFQUFFLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztZQUMxRCxDQUFDO1FBQ0gsQ0FBQztRQUVELHdCQUF3QjtRQUN4QixJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ2YsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3RCLE1BQU0sQ0FBQyxJQUFJLENBQUMsd0JBQXdCLENBQUMsQ0FBQztRQUN4QyxDQUFDO1FBRUQsb0JBQW9CO1FBQ3BCLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDdEIsTUFBTSxDQUFDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1lBRWxDLGlDQUFpQztZQUNqQyxNQUFNLGVBQWUsRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBRWxHLDRCQUE0QjtZQUM1QixJQUFJLGFBQWEsS0FBSyxLQUFLLEVBQUUsQ0FBQztnQkFDNUIsSUFBSSxDQUFDO29CQUNILElBQUksYUFBYSxFQUFFLENBQUM7d0JBQ2xCLE1BQU0sYUFBYSxFQUFFLENBQUM7b0JBQ3hCLENBQUM7eUJBQU0sQ0FBQzt3QkFDTixNQUFNLGVBQWUsRUFBRSxDQUFDO29CQUMxQixDQUFDO29CQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMsNEJBQTRCLENBQUMsQ0FBQztnQkFDNUMsQ0FBQztnQkFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO29CQUNmLE1BQU0sQ0FBQyxLQUFLLENBQUMsd0JBQXdCLEVBQUUsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO2dCQUNwRCxDQUFDO1lBQ0gsQ0FBQztZQUVELE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbEIsQ0FBQyxDQUFDLENBQUM7UUFFSCwrQkFBK0I7UUFDL0IsVUFBVSxDQUFDLEdBQUcsRUFBRTtZQUNkLE1BQU0sQ0FBQyxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztZQUM5QyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2xCLENBQUMsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ2hDLENBQUMsQ0FBQztJQUVGLDJCQUEyQjtJQUMzQixPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUUsQ0FBQyxLQUFLLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQ3RELE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7SUFFeEQsT0FBTztRQUNMLE1BQU07UUFDTixJQUFJO1FBQ0osUUFBUSxFQUFFLEdBQUcsRUFBRSxDQUFDLFFBQVEsRUFBRTtLQUMzQixDQUFDO0FBQ0osQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLFVBQVUsU0FBUyxDQUFDLEdBQVksRUFBRSxPQUFPLEdBQXVCLEVBQUU7SUFDdEUseUVBQXlFO0lBQ3pFLHlFQUF5RTtJQUN6RSw0RUFBNEU7SUFDNUUsb0JBQW9CLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDN0IsV0FBVyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRTtRQUN4Qyw0RUFBNEU7UUFDNUUsMkVBQTJFO1FBQzNFLDRFQUE0RTtRQUM1RSxxRUFBcUU7UUFDckUsNkVBQTZFO1FBQzdFLE1BQU0sQ0FBQyxLQUFLLENBQUMsd0JBQXdCLEVBQUU7WUFDckMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7WUFDN0QsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDeEQsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgU2VydmVyIH0gZnJvbSAnaHR0cCc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIsIGVycm9yTWVzc2FnZSwgaW5zdGFsbENyYXNoSGFuZGxlcnMgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBDb25maWcgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9waXBlbGluZS1jb3JlJztcbmltcG9ydCB7IGdldENvbm5lY3Rpb24sIGNsb3NlQ29ubmVjdGlvbiB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWRhdGEnO1xuaW1wb3J0IHR5cGUgeyBFeHByZXNzIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBzZXRSZWFkeSwgaXNSZWFkeSB9IGZyb20gJy4vcmVhZGluZXNzLmpzJztcbmltcG9ydCB7IHNodXRkb3duVHJhY2luZyB9IGZyb20gJy4vdHJhY2luZy5qcyc7XG5pbXBvcnQgeyBTU0VNYW5hZ2VyIH0gZnJvbSAnLi4vaHR0cC9zc2UtY29ubmVjdGlvbi1tYW5hZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdzZXJ2ZXInKTtcblxuY29uc3Qgc2xlZXAgPSAobXM6IG51bWJlcik6IFByb21pc2U8dm9pZD4gPT4gbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgbXMpKTtcblxuLy8gRGVwZW5kZW5jeS1zdXBlcnZpc29yIGNhZGVuY2UuIEluaXRpYWwgY29ubmVjdCByZXRyaWVzIHdpdGggY2FwcGVkIGJhY2tvZmY7XG4vLyBvbmNlIGNvbm5lY3RlZCwgcmUtY2hlY2tzIGF0IGEgc3RlYWR5IGludGVydmFsIHNvIGEgbGF0ZXIgb3V0YWdlIGZsaXBzIHRoZVxuLy8gc2VydmljZSBOb3RSZWFkeSAoZHJhaW5pbmcgdHJhZmZpYykgYW5kIGEgcmVjb3ZlcnkgZmxpcHMgaXQgYmFjayDigJQgd2l0aG91dFxuLy8gZXZlciBleGl0aW5nIC8gcmVzdGFydGluZyB0aGUgcHJvY2Vzcy5cbmNvbnN0IFJFQURZX1JFVFJZX0JBU0VfTVMgPSAxMDAwO1xuY29uc3QgUkVBRFlfUkVUUllfTUFYX01TID0gMTAwMDA7XG5jb25zdCBSRUFEWV9NT05JVE9SX0lOVEVSVkFMX01TID0gcGFyc2VJbnQocHJvY2Vzcy5lbnYuUkVBRElORVNTX01PTklUT1JfSU5URVJWQUxfTVMgfHwgJzE1MDAwJywgMTApO1xuXG4vKipcbiAqIEVzdGFibGlzaCBhbmQgY29udGludW91c2x5IG1vbml0b3IgdGhlIHNlcnZpY2UncyBkYXRhc3RvcmUgZGVwZW5kZW5jeSxcbiAqIGZsaXBwaW5nIHJlYWRpbmVzcyBzdGF0ZSBpbnN0ZWFkIG9mIGNyYXNoLWxvb3BpbmcuXG4gKlxuICogLSBFc3RhYmxpc2ggcGhhc2U6IHJldHJ5IGBvbkJlZm9yZVN0YXJ0YCAoZS5nLiBjb25uZWN0TW9uZ28pIHdpdGggY2FwcGVkXG4gKiAgIGJhY2tvZmYgdW50aWwgaXQgc3VjY2VlZHMg4oCUIGEgY29sZCBkYXRhc3RvcmUgbXVzdCBuZXZlciBleGl0IHRoZSBwcm9jZXNzLlxuICogLSBNb25pdG9yIHBoYXNlOiBwb2xsIGB0ZXN0RGF0YWJhc2VgIGZvciB0aGUgbGlmZSBvZiB0aGUgcHJvY2VzczsgZmxpcFxuICogICByZWFkeS9Ob3RSZWFkeSBvbiB0cmFuc2l0aW9ucy4gV2hpbGUgTm90UmVhZHkgdGhlIHJlYWRpbmVzcyBndWFyZCA1MDNzXG4gKiAgIGJ1c2luZXNzIHRyYWZmaWMgYW5kIG9yY2hlc3RyYXRvciByZWFkaW5lc3MgcHJvYmVzIGRyYWluIHRoZSBpbnN0YW5jZS5cbiAqXG4gKiBSdW5zIGluIHRoZSBiYWNrZ3JvdW5kICh0aGUgSFRUUCBzZXJ2ZXIgaXMgYWxyZWFkeSBsaXN0ZW5pbmcpLiBTdG9wcyB3aGVuXG4gKiBgYWJvcnRlZCgpYCByZXR1cm5zIHRydWUgKGdyYWNlZnVsIHNodXRkb3duKSwgc28gaXQgbmV2ZXIga2VlcHMgYSBjbG9zaW5nXG4gKiBwcm9jZXNzIGFsaXZlLlxuICovXG5hc3luYyBmdW5jdGlvbiBzdXBlcnZpc2VEZXBlbmRlbmNpZXMoXG4gIG5hbWU6IHN0cmluZyxcbiAgb25CZWZvcmVTdGFydDogKCgpID0+IFByb21pc2U8dm9pZD4pIHwgdW5kZWZpbmVkLFxuICB0ZXN0RGF0YWJhc2U6ICgoKSA9PiBQcm9taXNlPGJvb2xlYW4+KSB8IGZhbHNlIHwgdW5kZWZpbmVkLFxuICBhYm9ydGVkOiAoKSA9PiBib29sZWFuLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIGlmIChvbkJlZm9yZVN0YXJ0KSB7XG4gICAgbGV0IGRlbGF5ID0gUkVBRFlfUkVUUllfQkFTRV9NUztcbiAgICB3aGlsZSAoIWFib3J0ZWQoKSkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgb25CZWZvcmVTdGFydCgpO1xuICAgICAgICBicmVhaztcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGAke25hbWV9OiBkZXBlbmRlbmN5IGluaXQgZmFpbGVkLCByZXRyeWluZyBpbiAke2RlbGF5fW1zYCwgeyBlcnJvcjogZXJyb3JNZXNzYWdlKGVycm9yKSB9KTtcbiAgICAgICAgYXdhaXQgc2xlZXAoZGVsYXkpO1xuICAgICAgICBkZWxheSA9IE1hdGgubWluKGRlbGF5ICogMiwgUkVBRFlfUkVUUllfTUFYX01TKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvLyBObyBkYXRhc3RvcmUgdG8gZ2F0ZSBvbiDihpIgcmVhZHkgYXMgc29vbiBhcyB3ZSBhcmUgbGlzdGVuaW5nLlxuICBpZiAodGVzdERhdGFiYXNlID09PSBmYWxzZSkge1xuICAgIHNldFJlYWR5KHRydWUpO1xuICAgIGxvZ2dlci5pbmZvKGAke25hbWV9IHJlYWR5IChubyBkYXRhc3RvcmUgZGVwZW5kZW5jeSlgKTtcbiAgICByZXR1cm47XG4gIH1cblxuICBjb25zdCBwcm9iZSA9IHRlc3REYXRhYmFzZSA/PyAoKCkgPT4gZ2V0Q29ubmVjdGlvbigpLnRlc3RDb25uZWN0aW9uKCkpO1xuICBsZXQgd2FpdERlbGF5ID0gUkVBRFlfUkVUUllfQkFTRV9NUztcbiAgd2hpbGUgKCFhYm9ydGVkKCkpIHtcbiAgICBsZXQgb2sgPSBmYWxzZTtcbiAgICB0cnkge1xuICAgICAgb2sgPSBhd2FpdCBwcm9iZSgpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgb2sgPSBmYWxzZTtcbiAgICB9XG5cbiAgICBpZiAob2sgJiYgIWlzUmVhZHkoKSkge1xuICAgICAgc2V0UmVhZHkodHJ1ZSk7XG4gICAgICB3YWl0RGVsYXkgPSBSRUFEWV9SRVRSWV9CQVNFX01TO1xuICAgICAgbG9nZ2VyLmluZm8oYCR7bmFtZX0gcmVhZHkg4oCUIGRlcGVuZGVuY2llcyBjb25uZWN0ZWRgKTtcbiAgICB9IGVsc2UgaWYgKCFvayAmJiBpc1JlYWR5KCkpIHtcbiAgICAgIHNldFJlYWR5KGZhbHNlKTtcbiAgICAgIGxvZ2dlci53YXJuKGAke25hbWV9IGRlZ3JhZGVkIOKAlCBkZXBlbmRlbmN5IGNoZWNrIGZhaWxpbmcgKG5vdyBOb3RSZWFkeSlgKTtcbiAgICB9XG5cbiAgICAvLyBTdGVhZHkgY2FkZW5jZSBvbmNlIHJlYWR5OyBmYXN0ZXIgYmFja29mZiB3aGlsZSB3YWl0aW5nIHNvIHdlIGZsaXAgdG9cbiAgICAvLyByZWFkeSBwcm9tcHRseSB3aGVuIHRoZSBkYXRhc3RvcmUgY29tZXMgdXAuXG4gICAgaWYgKGlzUmVhZHkoKSkge1xuICAgICAgYXdhaXQgc2xlZXAoUkVBRFlfTU9OSVRPUl9JTlRFUlZBTF9NUyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGF3YWl0IHNsZWVwKHdhaXREZWxheSk7XG4gICAgICB3YWl0RGVsYXkgPSBNYXRoLm1pbih3YWl0RGVsYXkgKiAyLCBSRUFEWV9SRVRSWV9NQVhfTVMpO1xuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIE9wdGlvbnMgZm9yIHN0YXJ0aW5nIGEgc2VydmVyXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3RhcnRTZXJ2ZXJPcHRpb25zIHtcbiAgLyoqIFNlcnZpY2UgbmFtZSBmb3IgbG9nZ2luZyAqL1xuICBuYW1lPzogc3RyaW5nO1xuICAvKiogUG9ydCB0byBsaXN0ZW4gb24gKGRlZmF1bHQ6IGZyb20gY29uZmlnKSAqL1xuICBwb3J0PzogbnVtYmVyO1xuICAvKiogU1NFIG1hbmFnZXIgZm9yIGdyYWNlZnVsIHNodXRkb3duICovXG4gIHNzZU1hbmFnZXI/OiBTU0VNYW5hZ2VyO1xuICAvKiogU2h1dGRvd24gdGltZW91dCBpbiBtaWxsaXNlY29uZHMgKGRlZmF1bHQ6IDE1MDAwKSAqL1xuICBzaHV0ZG93blRpbWVvdXRNcz86IG51bWJlcjtcbiAgLyoqIENhbGxiYWNrIGFmdGVyIHNlcnZlciBzdGFydHMgKi9cbiAgb25TdGFydD86IChwb3J0OiBudW1iZXIpID0+IHZvaWQ7XG4gIC8qKiBDYWxsYmFjayBiZWZvcmUgc2h1dGRvd24gKi9cbiAgb25TaHV0ZG93bj86ICgpID0+IFByb21pc2U8dm9pZD47XG4gIC8qKiBSdW5zIGJlZm9yZSBkYXRhYmFzZSBjaGVjayAoZS5nLiwgaW5pdGlhbGl6ZSBleHRlcm5hbCBjb25uZWN0aW9ucykgKi9cbiAgb25CZWZvcmVTdGFydD86ICgpID0+IFByb21pc2U8dm9pZD47XG4gIC8qKiBDdXN0b20gZGF0YWJhc2UgaGVhbHRoIGNoZWNrLCBvciBmYWxzZSB0byBza2lwLiBEZWZhdWx0OiBQb3N0Z3JlU1FMIHRlc3RDb25uZWN0aW9uICovXG4gIHRlc3REYXRhYmFzZT86ICgoKSA9PiBQcm9taXNlPGJvb2xlYW4+KSB8IGZhbHNlO1xuICAvKiogQ3VzdG9tIGRhdGFiYXNlIGNsb3NlLCBvciBmYWxzZSB0byBza2lwLiBEZWZhdWx0OiBQb3N0Z3JlU1FMIGNsb3NlQ29ubmVjdGlvbiAqL1xuICBjbG9zZURhdGFiYXNlPzogKCgpID0+IFByb21pc2U8dm9pZD4pIHwgZmFsc2U7XG59XG5cbi8qKlxuICogUmVzdWx0IG9mIHN0YXJ0aW5nIGEgc2VydmVyXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3RhcnRTZXJ2ZXJSZXN1bHQge1xuICAvKiogSFRUUCBzZXJ2ZXIgaW5zdGFuY2UgKi9cbiAgc2VydmVyOiBTZXJ2ZXI7XG4gIC8qKiBQb3J0IHRoZSBzZXJ2ZXIgaXMgbGlzdGVuaW5nIG9uICovXG4gIHBvcnQ6IG51bWJlcjtcbiAgLyoqIEZ1bmN0aW9uIHRvIG1hbnVhbGx5IHRyaWdnZXIgc2h1dGRvd24gKi9cbiAgc2h1dGRvd246ICgpID0+IFByb21pc2U8dm9pZD47XG59XG5cbi8qKlxuICogU3RhcnQgYW4gRXhwcmVzcyBzZXJ2ZXIgd2l0aCBncmFjZWZ1bCBzaHV0ZG93biBoYW5kbGluZ1xuICpcbiAqIEZlYXR1cmVzOlxuICogLSBBdXRvbWF0aWMgZGF0YWJhc2UgY29ubmVjdGlvbiB0ZXN0aW5nXG4gKiAtIEdyYWNlZnVsIHNodXRkb3duIG9uIFNJR0lOVC9TSUdURVJNXG4gKiAtIFNTRSBjb25uZWN0aW9uIGNsZWFudXBcbiAqIC0gQ29uZmlndXJhYmxlIHNodXRkb3duIHRpbWVvdXRcbiAqXG4gKiBAcGFyYW0gYXBwIC0gRXhwcmVzcyBhcHBsaWNhdGlvblxuICogQHBhcmFtIG9wdGlvbnMgLSBTZXJ2ZXIgb3B0aW9uc1xuICogQHJldHVybnMgU2VydmVyIGluc3RhbmNlIGFuZCBjb250cm9sIGZ1bmN0aW9uc1xuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCB7IGFwcCwgc3NlTWFuYWdlciB9ID0gY3JlYXRlQXBwKCk7XG4gKlxuICogYXBwLnBvc3QoJy9hcGkvcmVzb3VyY2UnLCByZXF1aXJlQXV0aCwgaGFuZGxlcik7XG4gKlxuICogYXdhaXQgc3RhcnRTZXJ2ZXIoYXBwLCB7XG4gKiAgIG5hbWU6ICdNeSBNaWNyb3NlcnZpY2UnLFxuICogICBzc2VNYW5hZ2VyLFxuICogICBvblN0YXJ0OiAocG9ydCkgPT4gY29uc29sZS5sb2coYExpc3RlbmluZyBvbiAke3BvcnR9YCksXG4gKiB9KTtcbiAqIGBgYFxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3RhcnRTZXJ2ZXIoXG4gIGFwcDogRXhwcmVzcyxcbiAgb3B0aW9uczogU3RhcnRTZXJ2ZXJPcHRpb25zID0ge30sXG4pOiBQcm9taXNlPFN0YXJ0U2VydmVyUmVzdWx0PiB7XG4gIGNvbnN0IHNlcnZlckNvbmZpZyA9IENvbmZpZy5nZXQoJ3NlcnZlcicpO1xuICBjb25zdCB7XG4gICAgbmFtZSA9ICdNaWNyb3NlcnZpY2UnLFxuICAgIHNzZU1hbmFnZXIsXG4gICAgc2h1dGRvd25UaW1lb3V0TXMgPSAxNTAwMCxcbiAgICBvblN0YXJ0LFxuICAgIG9uU2h1dGRvd24sXG4gICAgb25CZWZvcmVTdGFydCxcbiAgICB0ZXN0RGF0YWJhc2UsXG4gICAgY2xvc2VEYXRhYmFzZSxcbiAgfSA9IG9wdGlvbnM7XG4gIGNvbnN0IHBvcnQgPSBvcHRpb25zLnBvcnQgPz8gc2VydmVyQ29uZmlnLnBvcnQ7XG5cbiAgLy8gVmFsaWRhdGUgYXV0aCBjb25maWd1cmF0aW9uIGF0IHNlcnZlciBzdGFydHVwIChub3QgZHVyaW5nIENESyBzeW50aGVzaXMpXG4gIENvbmZpZy52YWxpZGF0ZUF1dGgoKTtcblxuICBsb2dnZXIuaW5mbyhgU3RhcnRpbmcgJHtuYW1lfS4uLmApO1xuXG4gIC8vIE1hcmsgTm90UmVhZHkgc3luY2hyb25vdXNseSwgQkVGT1JFIHRoZSBwb3J0IG9wZW5zLCBzbyBubyByZXF1ZXN0IHNsaXBzXG4gIC8vIHRocm91Z2ggdGhlIHJlYWRpbmVzcyBndWFyZCBiZWZvcmUgdGhlIHN1cGVydmlzb3IgaGFzIHJ1bi4gVGhlIHN1cGVydmlzb3JcbiAgLy8gZmxpcHMgaXQgdHJ1ZSBvbmNlIGRlcGVuZGVuY2llcyBjb25uZWN0LlxuICBzZXRSZWFkeShmYWxzZSk7XG5cbiAgLy8gTGlzdGVuIEZJUlNULiBQcmV2aW91c2x5IHRoZSBwcm9jZXNzIHRlc3RlZCB0aGUgREIgYW5kIGBwcm9jZXNzLmV4aXQoMSlgJ2RcbiAgLy8gb24gZmFpbHVyZSAodG8gYmUgcmVzdGFydGVkKSwgc28gZHVyaW5nIGEgY29sZC1EQiBzdGFtcGVkZSBzZXJ2aWNlc1xuICAvLyBjcmFzaC1sb29wZWQgYW5kIG5ldmVyIGV2ZW4gb3BlbmVkIHRoZWlyIHBvcnQuIE5vdyB3ZSBvcGVuIHRoZSBwb3J0XG4gIC8vIGltbWVkaWF0ZWx5IOKAlCAvaGVhbHRoIChsaXZlbmVzcyksIC9yZWFkeSAocmVhZGluZXNzKSBhbmQgdGhlIHJlYWRpbmVzc1xuICAvLyBndWFyZCBhbGwgcmVzcG9uZCBhdCBvbmNlIOKAlCBhbmQgZXN0YWJsaXNoICsgbW9uaXRvciB0aGUgZGF0YXN0b3JlIGluIHRoZVxuICAvLyBiYWNrZ3JvdW5kLCByZXBvcnRpbmcgTm90UmVhZHkgdW50aWwgaXQgY29ubmVjdHMgaW5zdGVhZCBvZiBleGl0aW5nLlxuICBjb25zdCBzZXJ2ZXIgPSBhcHAubGlzdGVuKHBvcnQsICgpID0+IHtcbiAgICBsb2dnZXIuaW5mbyhgJHtuYW1lfSBsaXN0ZW5pbmcgb24gcG9ydDogJHtwb3J0fWApO1xuICAgIGxvZ2dlci5pbmZvKGBQbGF0Zm9ybSBVUkw6ICR7c2VydmVyQ29uZmlnLnBsYXRmb3JtVXJsfWApO1xuICAgIG9uU3RhcnQ/Lihwb3J0KTtcbiAgfSk7XG5cbiAgLy8gU2h1dGRvd24gaGFuZGxlciAoZ3VhcmRlZCBhZ2FpbnN0IGNvbmN1cnJlbnQgc2lnbmFscylcbiAgbGV0IHNodXR0aW5nRG93biA9IGZhbHNlO1xuXG4gIC8vIEJhY2tncm91bmQgZGVwZW5kZW5jeSBzdXBlcnZpc29yIOKAlCBlc3RhYmxpc2hlZCArIG1vbml0b3JlZCBhZnRlciBsaXN0ZW4sXG4gIC8vIHN0b3BwZWQgb24gc2h1dGRvd24gc28gaXQgbmV2ZXIga2VlcHMgYSBjbG9zaW5nIHByb2Nlc3MgYWxpdmUuIEd1YXJkIHRoZVxuICAvLyBmbG9hdGluZyBwcm9taXNlOiBldmVyeSBleHBlY3RlZCBmYWlsdXJlIGlzIGhhbmRsZWQgaW5zaWRlLCBzbyBhIHJlamVjdGlvblxuICAvLyBoZXJlIG1lYW5zIGEgcHJvZ3JhbW1pbmcgZXJyb3Ig4oCUIGxvZyBpdCBhbmQgbGVhdmUgdGhlIHNlcnZpY2UgTm90UmVhZHlcbiAgLy8gcmF0aGVyIHRoYW4gbGV0dGluZyBpdCBiZWNvbWUgYW4gdW5oYW5kbGVkUmVqZWN0aW9uIHRoYXQgY3Jhc2gtZXhpdHMgdGhlXG4gIC8vIHByb2Nlc3MgKHdoaWNoIHdvdWxkIGRlZmVhdCB0aGUgd2hvbGUgXCJuZXZlciBleGl0IG9uIGRlcGVuZGVuY3kgdHJvdWJsZVwiXG4gIC8vIGdvYWwpLlxuICBzdXBlcnZpc2VEZXBlbmRlbmNpZXMobmFtZSwgb25CZWZvcmVTdGFydCwgdGVzdERhdGFiYXNlLCAoKSA9PiBzaHV0dGluZ0Rvd24pLmNhdGNoKChlcnJvcikgPT4ge1xuICAgIGxvZ2dlci5lcnJvcignRGVwZW5kZW5jeSBzdXBlcnZpc29yIGNyYXNoZWQ7IHNlcnZpY2Ugd2lsbCByZW1haW4gTm90UmVhZHknLCB7IGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyb3IpIH0pO1xuICB9KTtcblxuICBjb25zdCBzaHV0ZG93biA9IGFzeW5jIChzaWduYWw/OiBzdHJpbmcpID0+IHtcbiAgICBpZiAoc2h1dHRpbmdEb3duKSByZXR1cm47XG4gICAgc2h1dHRpbmdEb3duID0gdHJ1ZTtcblxuICAgIGlmIChzaWduYWwpIHtcbiAgICAgIGxvZ2dlci5pbmZvKGAke3NpZ25hbH0gcmVjZWl2ZWQsIHNodXR0aW5nIGRvd24gZ3JhY2VmdWxseS4uLmApO1xuICAgIH1cblxuICAgIC8vIEN1c3RvbSBzaHV0ZG93biBjYWxsYmFja1xuICAgIGlmIChvblNodXRkb3duKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCBvblNodXRkb3duKCk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBsb2dnZXIuZXJyb3IoJ0Vycm9yIGluIG9uU2h1dGRvd24gY2FsbGJhY2snLCB7IGVycm9yIH0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIENsb3NlIFNTRSBjb25uZWN0aW9uc1xuICAgIGlmIChzc2VNYW5hZ2VyKSB7XG4gICAgICBzc2VNYW5hZ2VyLnNodXRkb3duKCk7XG4gICAgICBsb2dnZXIuaW5mbygnU1NFIGNvbm5lY3Rpb25zIGNsb3NlZCcpO1xuICAgIH1cblxuICAgIC8vIENsb3NlIEhUVFAgc2VydmVyXG4gICAgc2VydmVyLmNsb3NlKGFzeW5jICgpID0+IHtcbiAgICAgIGxvZ2dlci5pbmZvKCdIVFRQIHNlcnZlciBjbG9zZWQnKTtcblxuICAgICAgLy8gU2h1dGRvd24gT3BlblRlbGVtZXRyeSB0cmFjaW5nXG4gICAgICBhd2FpdCBzaHV0ZG93blRyYWNpbmcoKS5jYXRjaChlcnIgPT4gbG9nZ2VyLmVycm9yKCdFcnJvciBzaHV0dGluZyBkb3duIHRyYWNpbmcnLCB7IGVycm9yOiBlcnIgfSkpO1xuXG4gICAgICAvLyBDbG9zZSBkYXRhYmFzZSBjb25uZWN0aW9uXG4gICAgICBpZiAoY2xvc2VEYXRhYmFzZSAhPT0gZmFsc2UpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBpZiAoY2xvc2VEYXRhYmFzZSkge1xuICAgICAgICAgICAgYXdhaXQgY2xvc2VEYXRhYmFzZSgpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBhd2FpdCBjbG9zZUNvbm5lY3Rpb24oKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgbG9nZ2VyLmluZm8oJ0RhdGFiYXNlIGNvbm5lY3Rpb24gY2xvc2VkJyk7XG4gICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgbG9nZ2VyLmVycm9yKCdFcnJvciBjbG9zaW5nIGRhdGFiYXNlJywgeyBlcnJvciB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBwcm9jZXNzLmV4aXQoMCk7XG4gICAgfSk7XG5cbiAgICAvLyBGb3JjZSBzaHV0ZG93biBhZnRlciB0aW1lb3V0XG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICBsb2dnZXIuZXJyb3IoJ0ZvcmNlZCBzaHV0ZG93biBhZnRlciB0aW1lb3V0Jyk7XG4gICAgICBwcm9jZXNzLmV4aXQoMSk7XG4gICAgfSwgc2h1dGRvd25UaW1lb3V0TXMpLnVucmVmKCk7XG4gIH07XG5cbiAgLy8gUmVnaXN0ZXIgc2lnbmFsIGhhbmRsZXJzXG4gIHByb2Nlc3Mub25jZSgnU0lHSU5UJywgKCkgPT4gdm9pZCBzaHV0ZG93bignU0lHSU5UJykpO1xuICBwcm9jZXNzLm9uY2UoJ1NJR1RFUk0nLCAoKSA9PiB2b2lkIHNodXRkb3duKCdTSUdURVJNJykpO1xuXG4gIHJldHVybiB7XG4gICAgc2VydmVyLFxuICAgIHBvcnQsXG4gICAgc2h1dGRvd246ICgpID0+IHNodXRkb3duKCksXG4gIH07XG59XG5cbi8qKlxuICogU2ltcGxlIHNlcnZlciBzdGFydCB3cmFwcGVyIHdpdGggZXJyb3IgaGFuZGxpbmdcbiAqXG4gKiBAcGFyYW0gYXBwIC0gRXhwcmVzcyBhcHBsaWNhdGlvblxuICogQHBhcmFtIG9wdGlvbnMgLSBTZXJ2ZXIgb3B0aW9uc1xuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCB7IGFwcCwgc3NlTWFuYWdlciB9ID0gY3JlYXRlQXBwKCk7XG4gKiBhcHAucG9zdCgnL2FwaS9yZXNvdXJjZScsIGhhbmRsZXIpO1xuICpcbiAqIHZvaWQgcnVuU2VydmVyKGFwcCwgeyBuYW1lOiAnTXkgU2VydmljZScsIHNzZU1hbmFnZXIgfSk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJ1blNlcnZlcihhcHA6IEV4cHJlc3MsIG9wdGlvbnM6IFN0YXJ0U2VydmVyT3B0aW9ucyA9IHt9KTogdm9pZCB7XG4gIC8vIExhc3QtcmVzb3J0IGZhdWx0IGhhbmRsZXJzIGZvciB0aGUgcHJvZHVjdGlvbiBlbnRyeXBvaW50OiBhbiB1bmhhbmRsZWRcbiAgLy8gcmVqZWN0aW9uIC8gdW5jYXVnaHQgZXhjZXB0aW9uIGlzIGxvZ2dlZCAobm90IHNpbGVudGx5IGZhdGFsKSB0aGVuIHRoZVxuICAvLyBwcm9jZXNzIGV4aXRzIGZvciB0aGUgb3JjaGVzdHJhdG9yIHRvIHJlc3RhcnQuIE5vLW9wIHVuZGVyIE5PREVfRU5WPXRlc3QuXG4gIGluc3RhbGxDcmFzaEhhbmRsZXJzKGxvZ2dlcik7XG4gIHN0YXJ0U2VydmVyKGFwcCwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgLy8gc3RhcnRTZXJ2ZXIgbm93IGxpc3RlbnMtZmlyc3QgYW5kIHN1cGVydmlzZXMgdGhlIERCIGluIHRoZSBiYWNrZ3JvdW5kLCBzb1xuICAgIC8vIGl0IG9ubHkgcmVqZWN0cyBvbiBhIGdlbnVpbmVseSBmYXRhbCBTRVRVUCBlcnJvciAoaW52YWxpZCBhdXRoIGNvbmZpZywgYVxuICAgIC8vIGZhaWxlZCBvblN0YXJ0LCB0aGUgcG9ydCBhbHJlYWR5IGluIHVzZSkuIExvZyBtZXNzYWdlICsgc3RhY2sgZXhwbGljaXRseTpcbiAgICAvLyBhbiBFcnJvciBpbiB3aW5zdG9uIG1ldGFkYXRhIHNlcmlhbGl6ZXMgdG8gYHt9YCAobWVzc2FnZS9zdGFjayBhcmVcbiAgICAvLyBub24tZW51bWVyYWJsZSksIHdoaWNoIHdvdWxkIG90aGVyd2lzZSBoaWRlIHRoZSBjYXVzZSBiZWhpbmQgYHtlcnJvcjp7fX1gLlxuICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIHN0YXJ0IHNlcnZlcicsIHtcbiAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvciksXG4gICAgICBzdGFjazogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLnN0YWNrIDogdW5kZWZpbmVkLFxuICAgIH0pO1xuICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgfSk7XG59XG4iXX0=
@@ -0,0 +1,28 @@
1
+ import { type TenantContext } from '@pipeline-builder/pipeline-data';
2
+ import type { Request, Response, NextFunction } from 'express';
3
+ /**
4
+ * Resolves the RLS tenant scope for a request. The default reads the CALLER's
5
+ * authenticated identity org (`ctx.identity.orgId`, validated by requireOrgId) — NOT
6
+ * `getOrgId(req)`, which prefers route params (the queried scope), the wrong tenant for
7
+ * RLS. Services with a different request boundary supply their own resolver (e.g. platform
8
+ * sets the scope pre-auth from an unverified JWT peek so unauthenticated endpoints still
9
+ * get a sane default).
10
+ */
11
+ export type TenantScopeResolver = (req: Request) => TenantContext;
12
+ /**
13
+ * Establish the request's row-level-security tenant scope.
14
+ *
15
+ * Runs the rest of the request inside `runWithTenantContext({ orgId, isSuperAdmin })` (an
16
+ * AsyncLocalStorage scope) so every `withTenantTx` query downstream can `SET LOCAL` the
17
+ * Postgres GUCs that RLS policies read. Without this, reads against FORCE-RLS tables (e.g.
18
+ * `plugins`, `pipelines`) run with empty GUCs and silently return no rows — the "set once
19
+ * at the request boundary" bookend the RLS enforcement plan calls for.
20
+ *
21
+ * Default placement is AFTER `requireAuth` + `requireOrgId` (the route factories do this);
22
+ * pass a custom `resolve` for a different boundary. A thrown resolver means tenant identity
23
+ * could NOT be established (a wiring bug — this runs after auth) — we hard-FAIL the request
24
+ * (500 via next(err)) rather than proceed. Proceeding would run queries with empty RLS GUCs;
25
+ * under FORCE RLS that hides rows, but while RLS is in owner-bypass mode the app-layer WHERE
26
+ * clause is the only tenant gate, so a scopeless request must not silently continue.
27
+ */
28
+ export declare function withTenantContext(resolve?: TenantScopeResolver): (req: Request, _res: Response, next: NextFunction) => void;
@@ -0,0 +1,48 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { isSystemAdmin } from '@pipeline-builder/api-core';
4
+ import { runWithTenantContext } from '@pipeline-builder/pipeline-data';
5
+ import { getContext } from './get-context.js';
6
+ /** Default resolver: the authenticated identity's org + super-admin flag, plus
7
+ * the active-org parent (org → team hierarchy) so downstream side-effects can
8
+ * reach it without a request, and the caller's user id for the app-layer
9
+ * predicates that carry a per-user rung. */
10
+ const identityScope = (req) => ({
11
+ // `identity.orgId` is already normalized (trimmed + lowercased) once at
12
+ // resolution in api-core's `getIdentity`, so the GUC set here matches the
13
+ // app-layer WHERE clauses exactly — no ad-hoc re-normalization needed.
14
+ orgId: getContext(req).identity.orgId,
15
+ userId: getContext(req).identity.userId || undefined,
16
+ isSuperAdmin: isSystemAdmin(req),
17
+ parentOrgId: req.user?.parentOrganizationId,
18
+ });
19
+ /**
20
+ * Establish the request's row-level-security tenant scope.
21
+ *
22
+ * Runs the rest of the request inside `runWithTenantContext({ orgId, isSuperAdmin })` (an
23
+ * AsyncLocalStorage scope) so every `withTenantTx` query downstream can `SET LOCAL` the
24
+ * Postgres GUCs that RLS policies read. Without this, reads against FORCE-RLS tables (e.g.
25
+ * `plugins`, `pipelines`) run with empty GUCs and silently return no rows — the "set once
26
+ * at the request boundary" bookend the RLS enforcement plan calls for.
27
+ *
28
+ * Default placement is AFTER `requireAuth` + `requireOrgId` (the route factories do this);
29
+ * pass a custom `resolve` for a different boundary. A thrown resolver means tenant identity
30
+ * could NOT be established (a wiring bug — this runs after auth) — we hard-FAIL the request
31
+ * (500 via next(err)) rather than proceed. Proceeding would run queries with empty RLS GUCs;
32
+ * under FORCE RLS that hides rows, but while RLS is in owner-bypass mode the app-layer WHERE
33
+ * clause is the only tenant gate, so a scopeless request must not silently continue.
34
+ */
35
+ export function withTenantContext(resolve = identityScope) {
36
+ return (req, _res, next) => {
37
+ let scope;
38
+ try {
39
+ scope = resolve(req);
40
+ }
41
+ catch (err) {
42
+ next(err);
43
+ return;
44
+ }
45
+ runWithTenantContext(scope, () => next());
46
+ };
47
+ }
48
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVuYW50LWNvbnRleHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL3RlbmFudC1jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzNELE9BQU8sRUFBRSxvQkFBb0IsRUFBc0IsTUFBTSxpQ0FBaUMsQ0FBQztBQUUzRixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFZOUM7Ozs2Q0FHNkM7QUFDN0MsTUFBTSxhQUFhLEdBQXdCLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ25ELHdFQUF3RTtJQUN4RSwwRUFBMEU7SUFDMUUsdUVBQXVFO0lBQ3ZFLEtBQUssRUFBRSxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLEtBQUs7SUFDckMsTUFBTSxFQUFFLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxJQUFJLFNBQVM7SUFDcEQsWUFBWSxFQUFFLGFBQWEsQ0FBQyxHQUFHLENBQUM7SUFDaEMsV0FBVyxFQUFFLEdBQUcsQ0FBQyxJQUFJLEVBQUUsb0JBQW9CO0NBQzVDLENBQUMsQ0FBQztBQUVIOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxPQUFPLEdBQXdCLGFBQWE7SUFDNUUsT0FBTyxDQUFDLEdBQVksRUFBRSxJQUFjLEVBQUUsSUFBa0IsRUFBUSxFQUFFO1FBQ2hFLElBQUksS0FBb0IsQ0FBQztRQUN6QixJQUFJLENBQUM7WUFDSCxLQUFLLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZCLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ1YsT0FBTztRQUNULENBQUM7UUFDRCxvQkFBb0IsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUM1QyxDQUFDLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGlzU3lzdGVtQWRtaW4gfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBydW5XaXRoVGVuYW50Q29udGV4dCwgdHlwZSBUZW5hbnRDb250ZXh0IH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvcGlwZWxpbmUtZGF0YSc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlLCBOZXh0RnVuY3Rpb24gfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IGdldENvbnRleHQgfSBmcm9tICcuL2dldC1jb250ZXh0LmpzJztcblxuLyoqXG4gKiBSZXNvbHZlcyB0aGUgUkxTIHRlbmFudCBzY29wZSBmb3IgYSByZXF1ZXN0LiBUaGUgZGVmYXVsdCByZWFkcyB0aGUgQ0FMTEVSJ3NcbiAqIGF1dGhlbnRpY2F0ZWQgaWRlbnRpdHkgb3JnIChgY3R4LmlkZW50aXR5Lm9yZ0lkYCwgdmFsaWRhdGVkIGJ5IHJlcXVpcmVPcmdJZCkg4oCUIE5PVFxuICogYGdldE9yZ0lkKHJlcSlgLCB3aGljaCBwcmVmZXJzIHJvdXRlIHBhcmFtcyAodGhlIHF1ZXJpZWQgc2NvcGUpLCB0aGUgd3JvbmcgdGVuYW50IGZvclxuICogUkxTLiBTZXJ2aWNlcyB3aXRoIGEgZGlmZmVyZW50IHJlcXVlc3QgYm91bmRhcnkgc3VwcGx5IHRoZWlyIG93biByZXNvbHZlciAoZS5nLiBwbGF0Zm9ybVxuICogc2V0cyB0aGUgc2NvcGUgcHJlLWF1dGggZnJvbSBhbiB1bnZlcmlmaWVkIEpXVCBwZWVrIHNvIHVuYXV0aGVudGljYXRlZCBlbmRwb2ludHMgc3RpbGxcbiAqIGdldCBhIHNhbmUgZGVmYXVsdCkuXG4gKi9cbmV4cG9ydCB0eXBlIFRlbmFudFNjb3BlUmVzb2x2ZXIgPSAocmVxOiBSZXF1ZXN0KSA9PiBUZW5hbnRDb250ZXh0O1xuXG4vKiogRGVmYXVsdCByZXNvbHZlcjogdGhlIGF1dGhlbnRpY2F0ZWQgaWRlbnRpdHkncyBvcmcgKyBzdXBlci1hZG1pbiBmbGFnLCBwbHVzXG4gKiAgdGhlIGFjdGl2ZS1vcmcgcGFyZW50IChvcmcg4oaSIHRlYW0gaGllcmFyY2h5KSBzbyBkb3duc3RyZWFtIHNpZGUtZWZmZWN0cyBjYW5cbiAqICByZWFjaCBpdCB3aXRob3V0IGEgcmVxdWVzdCwgYW5kIHRoZSBjYWxsZXIncyB1c2VyIGlkIGZvciB0aGUgYXBwLWxheWVyXG4gKiAgcHJlZGljYXRlcyB0aGF0IGNhcnJ5IGEgcGVyLXVzZXIgcnVuZy4gKi9cbmNvbnN0IGlkZW50aXR5U2NvcGU6IFRlbmFudFNjb3BlUmVzb2x2ZXIgPSAocmVxKSA9PiAoe1xuICAvLyBgaWRlbnRpdHkub3JnSWRgIGlzIGFscmVhZHkgbm9ybWFsaXplZCAodHJpbW1lZCArIGxvd2VyY2FzZWQpIG9uY2UgYXRcbiAgLy8gcmVzb2x1dGlvbiBpbiBhcGktY29yZSdzIGBnZXRJZGVudGl0eWAsIHNvIHRoZSBHVUMgc2V0IGhlcmUgbWF0Y2hlcyB0aGVcbiAgLy8gYXBwLWxheWVyIFdIRVJFIGNsYXVzZXMgZXhhY3RseSDigJQgbm8gYWQtaG9jIHJlLW5vcm1hbGl6YXRpb24gbmVlZGVkLlxuICBvcmdJZDogZ2V0Q29udGV4dChyZXEpLmlkZW50aXR5Lm9yZ0lkLFxuICB1c2VySWQ6IGdldENvbnRleHQocmVxKS5pZGVudGl0eS51c2VySWQgfHwgdW5kZWZpbmVkLFxuICBpc1N1cGVyQWRtaW46IGlzU3lzdGVtQWRtaW4ocmVxKSxcbiAgcGFyZW50T3JnSWQ6IHJlcS51c2VyPy5wYXJlbnRPcmdhbml6YXRpb25JZCxcbn0pO1xuXG4vKipcbiAqIEVzdGFibGlzaCB0aGUgcmVxdWVzdCdzIHJvdy1sZXZlbC1zZWN1cml0eSB0ZW5hbnQgc2NvcGUuXG4gKlxuICogUnVucyB0aGUgcmVzdCBvZiB0aGUgcmVxdWVzdCBpbnNpZGUgYHJ1bldpdGhUZW5hbnRDb250ZXh0KHsgb3JnSWQsIGlzU3VwZXJBZG1pbiB9KWAgKGFuXG4gKiBBc3luY0xvY2FsU3RvcmFnZSBzY29wZSkgc28gZXZlcnkgYHdpdGhUZW5hbnRUeGAgcXVlcnkgZG93bnN0cmVhbSBjYW4gYFNFVCBMT0NBTGAgdGhlXG4gKiBQb3N0Z3JlcyBHVUNzIHRoYXQgUkxTIHBvbGljaWVzIHJlYWQuIFdpdGhvdXQgdGhpcywgcmVhZHMgYWdhaW5zdCBGT1JDRS1STFMgdGFibGVzIChlLmcuXG4gKiBgcGx1Z2luc2AsIGBwaXBlbGluZXNgKSBydW4gd2l0aCBlbXB0eSBHVUNzIGFuZCBzaWxlbnRseSByZXR1cm4gbm8gcm93cyDigJQgdGhlIFwic2V0IG9uY2VcbiAqIGF0IHRoZSByZXF1ZXN0IGJvdW5kYXJ5XCIgYm9va2VuZCB0aGUgUkxTIGVuZm9yY2VtZW50IHBsYW4gY2FsbHMgZm9yLlxuICpcbiAqIERlZmF1bHQgcGxhY2VtZW50IGlzIEFGVEVSIGByZXF1aXJlQXV0aGAgKyBgcmVxdWlyZU9yZ0lkYCAodGhlIHJvdXRlIGZhY3RvcmllcyBkbyB0aGlzKTtcbiAqIHBhc3MgYSBjdXN0b20gYHJlc29sdmVgIGZvciBhIGRpZmZlcmVudCBib3VuZGFyeS4gQSB0aHJvd24gcmVzb2x2ZXIgbWVhbnMgdGVuYW50IGlkZW50aXR5XG4gKiBjb3VsZCBOT1QgYmUgZXN0YWJsaXNoZWQgKGEgd2lyaW5nIGJ1ZyDigJQgdGhpcyBydW5zIGFmdGVyIGF1dGgpIOKAlCB3ZSBoYXJkLUZBSUwgdGhlIHJlcXVlc3RcbiAqICg1MDAgdmlhIG5leHQoZXJyKSkgcmF0aGVyIHRoYW4gcHJvY2VlZC4gUHJvY2VlZGluZyB3b3VsZCBydW4gcXVlcmllcyB3aXRoIGVtcHR5IFJMUyBHVUNzO1xuICogdW5kZXIgRk9SQ0UgUkxTIHRoYXQgaGlkZXMgcm93cywgYnV0IHdoaWxlIFJMUyBpcyBpbiBvd25lci1ieXBhc3MgbW9kZSB0aGUgYXBwLWxheWVyIFdIRVJFXG4gKiBjbGF1c2UgaXMgdGhlIG9ubHkgdGVuYW50IGdhdGUsIHNvIGEgc2NvcGVsZXNzIHJlcXVlc3QgbXVzdCBub3Qgc2lsZW50bHkgY29udGludWUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3aXRoVGVuYW50Q29udGV4dChyZXNvbHZlOiBUZW5hbnRTY29wZVJlc29sdmVyID0gaWRlbnRpdHlTY29wZSkge1xuICByZXR1cm4gKHJlcTogUmVxdWVzdCwgX3JlczogUmVzcG9uc2UsIG5leHQ6IE5leHRGdW5jdGlvbik6IHZvaWQgPT4ge1xuICAgIGxldCBzY29wZTogVGVuYW50Q29udGV4dDtcbiAgICB0cnkge1xuICAgICAgc2NvcGUgPSByZXNvbHZlKHJlcSk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBuZXh0KGVycik7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHJ1bldpdGhUZW5hbnRDb250ZXh0KHNjb3BlLCAoKSA9PiBuZXh0KCkpO1xuICB9O1xufVxuIl19
@@ -0,0 +1,25 @@
1
+ import { type Span, type Attributes } from '@opentelemetry/api';
2
+ /**
3
+ * Run `fn` inside a new active span named `name`.
4
+ *
5
+ * Auto-instrumentation only covers inbound HTTP/express/mongo/pg; the paths that
6
+ * actually get debugged in an incident — AI generation, the out-of-band BullMQ
7
+ * plugin build (runs detached from any inbound span), long async work — show no
8
+ * span detail without a hand-authored one. Wrap those with this.
9
+ *
10
+ * Safe when tracing is disabled: with no SDK registered, `startActiveSpan` runs
11
+ * the callback with a no-op span, so this adds negligible overhead and never
12
+ * changes behavior. On throw, the span is marked ERROR and the exception
13
+ * recorded, then re-thrown unchanged. Always ends the span.
14
+ */
15
+ export declare function withSpan<T>(name: string, fn: (span: Span) => Promise<T>, attributes?: Attributes): Promise<T>;
16
+ /**
17
+ * Shutdown the OpenTelemetry SDK gracefully.
18
+ */
19
+ export declare function shutdownTracing(): Promise<void>;
20
+ /**
21
+ * Returns the current active trace ID, or undefined when tracing is
22
+ * disabled / no active span. Useful for enriching structured logs so an
23
+ * operator can correlate a request across services via its trace ID.
24
+ */
25
+ export declare function currentTraceId(): string | undefined;
@@ -0,0 +1,69 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { trace, SpanStatusCode } from '@opentelemetry/api';
4
+ import { NodeSDK } from '@opentelemetry/sdk-node';
5
+ import { createLogger } from '@pipeline-builder/api-core';
6
+ const logger = createLogger('tracing');
7
+ /** Shared tracer for hand-authored spans. */
8
+ const tracer = trace.getTracer('pipeline-builder');
9
+ /**
10
+ * Run `fn` inside a new active span named `name`.
11
+ *
12
+ * Auto-instrumentation only covers inbound HTTP/express/mongo/pg; the paths that
13
+ * actually get debugged in an incident — AI generation, the out-of-band BullMQ
14
+ * plugin build (runs detached from any inbound span), long async work — show no
15
+ * span detail without a hand-authored one. Wrap those with this.
16
+ *
17
+ * Safe when tracing is disabled: with no SDK registered, `startActiveSpan` runs
18
+ * the callback with a no-op span, so this adds negligible overhead and never
19
+ * changes behavior. On throw, the span is marked ERROR and the exception
20
+ * recorded, then re-thrown unchanged. Always ends the span.
21
+ */
22
+ export async function withSpan(name, fn, attributes) {
23
+ return tracer.startActiveSpan(name, async (span) => {
24
+ if (attributes)
25
+ span.setAttributes(attributes);
26
+ try {
27
+ const result = await fn(span);
28
+ span.setStatus({ code: SpanStatusCode.OK });
29
+ return result;
30
+ }
31
+ catch (err) {
32
+ const error = err instanceof Error ? err : new Error(String(err));
33
+ span.recordException(error);
34
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
35
+ throw err;
36
+ }
37
+ finally {
38
+ span.end();
39
+ }
40
+ });
41
+ }
42
+ // Module-local SDK handle. Production services start tracing through the
43
+ // `otel-bootstrap.js` `--import` preload, which registers its own SDK on the
44
+ // global provider — so this stays null and `shutdownTracing()` is a no-op.
45
+ // It is retained because `server.ts` calls `shutdownTracing()` on shutdown.
46
+ let sdk = null;
47
+ /**
48
+ * Shutdown the OpenTelemetry SDK gracefully.
49
+ */
50
+ export async function shutdownTracing() {
51
+ if (sdk) {
52
+ await sdk.shutdown();
53
+ sdk = null;
54
+ logger.info('OpenTelemetry tracing shut down');
55
+ }
56
+ }
57
+ /**
58
+ * Returns the current active trace ID, or undefined when tracing is
59
+ * disabled / no active span. Useful for enriching structured logs so an
60
+ * operator can correlate a request across services via its trace ID.
61
+ */
62
+ export function currentTraceId() {
63
+ // Read the globally-registered tracer provider's active span. The SDK is
64
+ // started by the `otel-bootstrap.js` `--import` preload, which registers the
65
+ // global provider via @opentelemetry/api. Returns undefined when tracing is
66
+ // disabled / there is no active span.
67
+ return trace.getActiveSpan()?.spanContext()?.traceId;
68
+ }
69
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhY2luZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hcGkvdHJhY2luZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxLQUFLLEVBQUUsY0FBYyxFQUE4QixNQUFNLG9CQUFvQixDQUFDO0FBQ3ZGLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNsRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFMUQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRXZDLDZDQUE2QztBQUM3QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsU0FBUyxDQUFDLGtCQUFrQixDQUFDLENBQUM7QUFFbkQ7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxRQUFRLENBQzVCLElBQVksRUFDWixFQUE4QixFQUM5QixVQUF1QjtJQUV2QixPQUFPLE1BQU0sQ0FBQyxlQUFlLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsRUFBRTtRQUNqRCxJQUFJLFVBQVU7WUFBRSxJQUFJLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQy9DLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFHLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzlCLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxJQUFJLEVBQUUsY0FBYyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDNUMsT0FBTyxNQUFNLENBQUM7UUFDaEIsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDYixNQUFNLEtBQUssR0FBRyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ2xFLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDNUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLElBQUksRUFBRSxjQUFjLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RSxNQUFNLEdBQUcsQ0FBQztRQUNaLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNiLENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCx5RUFBeUU7QUFDekUsNkVBQTZFO0FBQzdFLDJFQUEyRTtBQUMzRSw0RUFBNEU7QUFDNUUsSUFBSSxHQUFHLEdBQW1CLElBQUksQ0FBQztBQUUvQjs7R0FFRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsZUFBZTtJQUNuQyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ1IsTUFBTSxHQUFHLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDckIsR0FBRyxHQUFHLElBQUksQ0FBQztRQUNYLE1BQU0sQ0FBQyxJQUFJLENBQUMsaUNBQWlDLENBQUMsQ0FBQztJQUNqRCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsY0FBYztJQUM1Qix5RUFBeUU7SUFDekUsNkVBQTZFO0lBQzdFLDRFQUE0RTtJQUM1RSxzQ0FBc0M7SUFDdEMsT0FBTyxLQUFLLENBQUMsYUFBYSxFQUFFLEVBQUUsV0FBVyxFQUFFLEVBQUUsT0FBTyxDQUFDO0FBQ3ZELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgdHJhY2UsIFNwYW5TdGF0dXNDb2RlLCB0eXBlIFNwYW4sIHR5cGUgQXR0cmlidXRlcyB9IGZyb20gJ0BvcGVudGVsZW1ldHJ5L2FwaSc7XG5pbXBvcnQgeyBOb2RlU0RLIH0gZnJvbSAnQG9wZW50ZWxlbWV0cnkvc2RrLW5vZGUnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3RyYWNpbmcnKTtcblxuLyoqIFNoYXJlZCB0cmFjZXIgZm9yIGhhbmQtYXV0aG9yZWQgc3BhbnMuICovXG5jb25zdCB0cmFjZXIgPSB0cmFjZS5nZXRUcmFjZXIoJ3BpcGVsaW5lLWJ1aWxkZXInKTtcblxuLyoqXG4gKiBSdW4gYGZuYCBpbnNpZGUgYSBuZXcgYWN0aXZlIHNwYW4gbmFtZWQgYG5hbWVgLlxuICpcbiAqIEF1dG8taW5zdHJ1bWVudGF0aW9uIG9ubHkgY292ZXJzIGluYm91bmQgSFRUUC9leHByZXNzL21vbmdvL3BnOyB0aGUgcGF0aHMgdGhhdFxuICogYWN0dWFsbHkgZ2V0IGRlYnVnZ2VkIGluIGFuIGluY2lkZW50IOKAlCBBSSBnZW5lcmF0aW9uLCB0aGUgb3V0LW9mLWJhbmQgQnVsbE1RXG4gKiBwbHVnaW4gYnVpbGQgKHJ1bnMgZGV0YWNoZWQgZnJvbSBhbnkgaW5ib3VuZCBzcGFuKSwgbG9uZyBhc3luYyB3b3JrIOKAlCBzaG93IG5vXG4gKiBzcGFuIGRldGFpbCB3aXRob3V0IGEgaGFuZC1hdXRob3JlZCBvbmUuIFdyYXAgdGhvc2Ugd2l0aCB0aGlzLlxuICpcbiAqIFNhZmUgd2hlbiB0cmFjaW5nIGlzIGRpc2FibGVkOiB3aXRoIG5vIFNESyByZWdpc3RlcmVkLCBgc3RhcnRBY3RpdmVTcGFuYCBydW5zXG4gKiB0aGUgY2FsbGJhY2sgd2l0aCBhIG5vLW9wIHNwYW4sIHNvIHRoaXMgYWRkcyBuZWdsaWdpYmxlIG92ZXJoZWFkIGFuZCBuZXZlclxuICogY2hhbmdlcyBiZWhhdmlvci4gT24gdGhyb3csIHRoZSBzcGFuIGlzIG1hcmtlZCBFUlJPUiBhbmQgdGhlIGV4Y2VwdGlvblxuICogcmVjb3JkZWQsIHRoZW4gcmUtdGhyb3duIHVuY2hhbmdlZC4gQWx3YXlzIGVuZHMgdGhlIHNwYW4uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3aXRoU3BhbjxUPihcbiAgbmFtZTogc3RyaW5nLFxuICBmbjogKHNwYW46IFNwYW4pID0+IFByb21pc2U8VD4sXG4gIGF0dHJpYnV0ZXM/OiBBdHRyaWJ1dGVzLFxuKTogUHJvbWlzZTxUPiB7XG4gIHJldHVybiB0cmFjZXIuc3RhcnRBY3RpdmVTcGFuKG5hbWUsIGFzeW5jIChzcGFuKSA9PiB7XG4gICAgaWYgKGF0dHJpYnV0ZXMpIHNwYW4uc2V0QXR0cmlidXRlcyhhdHRyaWJ1dGVzKTtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZm4oc3Bhbik7XG4gICAgICBzcGFuLnNldFN0YXR1cyh7IGNvZGU6IFNwYW5TdGF0dXNDb2RlLk9LIH0pO1xuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGNvbnN0IGVycm9yID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIgOiBuZXcgRXJyb3IoU3RyaW5nKGVycikpO1xuICAgICAgc3Bhbi5yZWNvcmRFeGNlcHRpb24oZXJyb3IpO1xuICAgICAgc3Bhbi5zZXRTdGF0dXMoeyBjb2RlOiBTcGFuU3RhdHVzQ29kZS5FUlJPUiwgbWVzc2FnZTogZXJyb3IubWVzc2FnZSB9KTtcbiAgICAgIHRocm93IGVycjtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgc3Bhbi5lbmQoKTtcbiAgICB9XG4gIH0pO1xufVxuXG4vLyBNb2R1bGUtbG9jYWwgU0RLIGhhbmRsZS4gUHJvZHVjdGlvbiBzZXJ2aWNlcyBzdGFydCB0cmFjaW5nIHRocm91Z2ggdGhlXG4vLyBgb3RlbC1ib290c3RyYXAuanNgIGAtLWltcG9ydGAgcHJlbG9hZCwgd2hpY2ggcmVnaXN0ZXJzIGl0cyBvd24gU0RLIG9uIHRoZVxuLy8gZ2xvYmFsIHByb3ZpZGVyIOKAlCBzbyB0aGlzIHN0YXlzIG51bGwgYW5kIGBzaHV0ZG93blRyYWNpbmcoKWAgaXMgYSBuby1vcC5cbi8vIEl0IGlzIHJldGFpbmVkIGJlY2F1c2UgYHNlcnZlci50c2AgY2FsbHMgYHNodXRkb3duVHJhY2luZygpYCBvbiBzaHV0ZG93bi5cbmxldCBzZGs6IE5vZGVTREsgfCBudWxsID0gbnVsbDtcblxuLyoqXG4gKiBTaHV0ZG93biB0aGUgT3BlblRlbGVtZXRyeSBTREsgZ3JhY2VmdWxseS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNodXRkb3duVHJhY2luZygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgaWYgKHNkaykge1xuICAgIGF3YWl0IHNkay5zaHV0ZG93bigpO1xuICAgIHNkayA9IG51bGw7XG4gICAgbG9nZ2VyLmluZm8oJ09wZW5UZWxlbWV0cnkgdHJhY2luZyBzaHV0IGRvd24nKTtcbiAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIGN1cnJlbnQgYWN0aXZlIHRyYWNlIElELCBvciB1bmRlZmluZWQgd2hlbiB0cmFjaW5nIGlzXG4gKiBkaXNhYmxlZCAvIG5vIGFjdGl2ZSBzcGFuLiBVc2VmdWwgZm9yIGVucmljaGluZyBzdHJ1Y3R1cmVkIGxvZ3Mgc28gYW5cbiAqIG9wZXJhdG9yIGNhbiBjb3JyZWxhdGUgYSByZXF1ZXN0IGFjcm9zcyBzZXJ2aWNlcyB2aWEgaXRzIHRyYWNlIElELlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3VycmVudFRyYWNlSWQoKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgLy8gUmVhZCB0aGUgZ2xvYmFsbHktcmVnaXN0ZXJlZCB0cmFjZXIgcHJvdmlkZXIncyBhY3RpdmUgc3Bhbi4gVGhlIFNESyBpc1xuICAvLyBzdGFydGVkIGJ5IHRoZSBgb3RlbC1ib290c3RyYXAuanNgIGAtLWltcG9ydGAgcHJlbG9hZCwgd2hpY2ggcmVnaXN0ZXJzIHRoZVxuICAvLyBnbG9iYWwgcHJvdmlkZXIgdmlhIEBvcGVudGVsZW1ldHJ5L2FwaS4gUmV0dXJucyB1bmRlZmluZWQgd2hlbiB0cmFjaW5nIGlzXG4gIC8vIGRpc2FibGVkIC8gdGhlcmUgaXMgbm8gYWN0aXZlIHNwYW4uXG4gIHJldHVybiB0cmFjZS5nZXRBY3RpdmVTcGFuKCk/LnNwYW5Db250ZXh0KCk/LnRyYWNlSWQ7XG59XG4iXX0=
@@ -0,0 +1,4 @@
1
+ export * from './sse-connection-manager.js';
2
+ export * from './sse-ticket-channel.js';
3
+ export * from './sse-ticket-store.js';
4
+ export * from './sse-relay.js';
@@ -0,0 +1,7 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export * from './sse-connection-manager.js';
4
+ export * from './sse-ticket-channel.js';
5
+ export * from './sse-ticket-store.js';
6
+ export * from './sse-relay.js';
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsZ0JBQWdCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuZXhwb3J0ICogZnJvbSAnLi9zc2UtY29ubmVjdGlvbi1tYW5hZ2VyLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vc3NlLXRpY2tldC1jaGFubmVsLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vc3NlLXRpY2tldC1zdG9yZS5qcyc7XG5leHBvcnQgKiBmcm9tICcuL3NzZS1yZWxheS5qcyc7XG4iXX0=