@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,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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwaS9zZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzlCLE9BQU8sRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDOUYsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pELE9BQU8sRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFakYsT0FBTyxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNuRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQy9DLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUUvRCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFdEMsTUFBTSxLQUFLLEdBQUcsQ0FBQyxFQUFVLEVBQWlCLEVBQUUsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBRS9GLDhFQUE4RTtBQUM5RSw2RUFBNkU7QUFDN0UsNkVBQTZFO0FBQzdFLHlDQUF5QztBQUN6QyxNQUFNLG1CQUFtQixHQUFHLElBQUksQ0FBQztBQUNqQyxNQUFNLGtCQUFrQixHQUFHLEtBQUssQ0FBQztBQUNqQyxNQUFNLHlCQUF5QixHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLDZCQUE2QixJQUFJLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQztBQUVyRzs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsS0FBSyxVQUFVLHFCQUFxQixD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QUN4Qyw0RUFBNEU7UUFDNUUsMkVBQTJFO1FBQzNFLDRFQUE0RTtRQUM1RSxxRUFBcUU7UUFDckUsNkVBQTZFO1FBQzdFLE1BQU0sQ0FBQyxLQUFLLENBQUMsd0JBQXdCLEVBQUU7WUFDckMsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7WUFDN0QsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDeEQsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgU2VydmVyIH0gZnJvbSAnaHR0cCc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIsIGVycm9yTWVzc2FnZSwgaW5zdGFsbENyYXNoSGFuZGxlcnMgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBDb25maWcgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9waXBlbGluZS1jb3JlJztcbmltcG9ydCB7IGdldENvbm5lY3Rpb24sIGNsb3NlQ29ubmVjdGlvbiB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWRhdGEnO1xuaW1wb3J0IHR5cGUgeyBFeHByZXNzIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBzZXRSZWFkeSwgaXNSZWFkeSB9IGZyb20gJy4vcmVhZGluZXNzLmpzJztcbmltcG9ydCB7IHNodXRkb3duVHJhY2luZyB9IGZyb20gJy4vdHJhY2luZy5qcyc7XG5pbXBvcnQgeyBTU0VNYW5hZ2VyIH0gZnJvbSAnLi4vaHR0cC9zc2UtY29ubmVjdGlvbi1tYW5hZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdzZXJ2ZXInKTtcblxuY29uc3Qgc2xlZXAgPSAobXM6IG51bWJlcik6IFByb21pc2U8dm9pZD4gPT4gbmV3IFByb21pc2UoKHJlc29sdmUpID0+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,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=
|