@pipeline-builder/api-server 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +39 -0
  3. package/lib/api/app-factory.d.ts +71 -0
  4. package/lib/api/app-factory.js +212 -0
  5. package/lib/api/check-quota.d.ts +13 -0
  6. package/lib/api/check-quota.js +67 -0
  7. package/lib/api/context-middleware.d.ts +34 -0
  8. package/lib/api/context-middleware.js +45 -0
  9. package/lib/api/etag-middleware.d.ts +7 -0
  10. package/lib/api/etag-middleware.js +37 -0
  11. package/lib/api/get-context.d.ts +22 -0
  12. package/lib/api/get-context.js +31 -0
  13. package/lib/api/health-checks.d.ts +25 -0
  14. package/lib/api/health-checks.js +36 -0
  15. package/lib/api/idempotency-middleware.d.ts +9 -0
  16. package/lib/api/idempotency-middleware.js +64 -0
  17. package/lib/api/index.d.ts +15 -0
  18. package/lib/api/index.js +43 -0
  19. package/lib/api/metrics.d.ts +12 -0
  20. package/lib/api/metrics.js +83 -0
  21. package/lib/api/middleware-factory.d.ts +47 -0
  22. package/lib/api/middleware-factory.js +66 -0
  23. package/lib/api/middleware.d.ts +1 -0
  24. package/lib/api/middleware.js +14 -0
  25. package/lib/api/quota-helpers.d.ts +23 -0
  26. package/lib/api/quota-helpers.js +25 -0
  27. package/lib/api/request-types.d.ts +55 -0
  28. package/lib/api/request-types.js +62 -0
  29. package/lib/api/require-org-id.d.ts +14 -0
  30. package/lib/api/require-org-id.js +31 -0
  31. package/lib/api/route-wrapper.d.ts +50 -0
  32. package/lib/api/route-wrapper.js +62 -0
  33. package/lib/api/server.d.ts +79 -0
  34. package/lib/api/server.js +144 -0
  35. package/lib/api/tracing.d.ts +15 -0
  36. package/lib/api/tracing.js +53 -0
  37. package/lib/http/index.d.ts +2 -0
  38. package/lib/http/index.js +21 -0
  39. package/lib/http/sse-connection-manager.d.ts +145 -0
  40. package/lib/http/sse-connection-manager.js +329 -0
  41. package/lib/http/ws-manager.d.ts +37 -0
  42. package/lib/http/ws-manager.js +105 -0
  43. package/lib/index.d.ts +30 -0
  44. package/lib/index.js +51 -0
  45. package/package.json +143 -0
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.SSEManager = void 0;
6
+ const api_core_1 = require("@pipeline-builder/api-core");
7
+ const pipeline_core_1 = require("@pipeline-builder/pipeline-core");
8
+ const uuid_1 = require("uuid");
9
+ const logger = (0, api_core_1.createLogger)('SSEManager');
10
+ /**
11
+ * SSE helper class with memory leak protection
12
+ *
13
+ * Features:
14
+ * - Client limits per request ID
15
+ * - Automatic timeout for idle connections
16
+ * - Periodic cleanup of stale connections
17
+ * - Connection statistics
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const sseManager = new SSEManager({ maxClientsPerRequest: 5 });
22
+ * app.get('/logs/:requestId', sseManager.middleware());
23
+ *
24
+ * // Send events
25
+ * sseManager.send('request-123', 'INFO', 'Processing...');
26
+ * ```
27
+ */
28
+ class SSEManager {
29
+ clients = new Map();
30
+ maxClientsPerRequest;
31
+ clientTimeoutMs;
32
+ cleanupInterval = null;
33
+ constructor(options = {}) {
34
+ this.maxClientsPerRequest = options.maxClientsPerRequest ?? parseInt(process.env.SSE_MAX_CLIENTS_PER_REQUEST || '10', 10);
35
+ this.clientTimeoutMs = options.clientTimeoutMs ?? parseInt(process.env.SSE_CLIENT_TIMEOUT_MS || '1800000', 10); // 30 minutes
36
+ const cleanupIntervalMs = options.cleanupIntervalMs ?? parseInt(process.env.SSE_CLEANUP_INTERVAL_MS || '300000', 10); // 5 minutes
37
+ this.startCleanupInterval(cleanupIntervalMs);
38
+ }
39
+ /**
40
+ * Adds a client to the SSE manager
41
+ *
42
+ * @param requestId - Unique request ID
43
+ * @param res - Express Response object
44
+ * @returns true if client was added, false if rejected (limit reached)
45
+ */
46
+ addClient(requestId, res) {
47
+ const existing = this.clients.get(requestId) || [];
48
+ // Check client limit
49
+ if (existing.length >= this.maxClientsPerRequest) {
50
+ logger.warn(`Client limit reached for request ${requestId} (max: ${this.maxClientsPerRequest})`);
51
+ return false;
52
+ }
53
+ // Create timeout for this client
54
+ const clientId = (0, uuid_1.v7)();
55
+ const timeout = setTimeout(() => {
56
+ logger.debug(`Client ${clientId} timed out for request ${requestId}`);
57
+ this.removeClient(requestId, clientId);
58
+ try {
59
+ res.end();
60
+ }
61
+ catch (err) {
62
+ logger.debug('Response already closed on timeout', { requestId, clientId, error: err instanceof Error ? err.message : String(err) });
63
+ }
64
+ }, this.clientTimeoutMs);
65
+ const client = {
66
+ id: clientId,
67
+ res,
68
+ connectedAt: Date.now(),
69
+ timeout,
70
+ backpressureCount: 0,
71
+ };
72
+ // Handle disconnection
73
+ res.on('close', () => {
74
+ clearTimeout(timeout);
75
+ this.removeClient(requestId, clientId);
76
+ });
77
+ res.on('error', (err) => {
78
+ logger.error(`SSE client error for request ${requestId}:`, err);
79
+ clearTimeout(timeout);
80
+ this.removeClient(requestId, clientId);
81
+ });
82
+ existing.push(client);
83
+ this.clients.set(requestId, existing);
84
+ logger.debug(`Client ${clientId} connected for request ${requestId} (total: ${existing.length})`);
85
+ return true;
86
+ }
87
+ /**
88
+ * Removes a client from the manager
89
+ */
90
+ removeClient(requestId, clientId) {
91
+ const clients = this.clients.get(requestId);
92
+ if (!clients)
93
+ return;
94
+ const remaining = clients.filter(c => {
95
+ if (c.id === clientId) {
96
+ clearTimeout(c.timeout);
97
+ return false;
98
+ }
99
+ return true;
100
+ });
101
+ if (remaining.length === 0) {
102
+ this.clients.delete(requestId);
103
+ logger.debug(`All clients disconnected for request ${requestId}`);
104
+ }
105
+ else {
106
+ this.clients.set(requestId, remaining);
107
+ }
108
+ }
109
+ /**
110
+ * Sends a message to all SSE clients for a requestId
111
+ *
112
+ * @param requestId - Request ID
113
+ * @param type - Event type
114
+ * @param message - Message string
115
+ * @param data - Optional additional data
116
+ * @returns Number of clients the message was sent to
117
+ */
118
+ send(requestId, type, message, data) {
119
+ const payload = {
120
+ ts: new Date().toISOString(),
121
+ type,
122
+ message,
123
+ data,
124
+ };
125
+ const clients = [...(this.clients.get(requestId) || [])];
126
+ let sentCount = 0;
127
+ const serialized = `data: ${JSON.stringify(payload)}\n\n`;
128
+ for (const client of clients) {
129
+ try {
130
+ // Backpressure: skip clients whose write buffer is full
131
+ if (client.res.writableEnded) {
132
+ this.removeClient(requestId, client.id);
133
+ continue;
134
+ }
135
+ const canWrite = client.res.write(serialized);
136
+ if (!canWrite) {
137
+ client.backpressureCount++;
138
+ // Disconnect clients that consistently can't keep up (10 consecutive backpressure events)
139
+ if (client.backpressureCount >= pipeline_core_1.CoreConstants.SSE_BACKPRESSURE_THRESHOLD) {
140
+ logger.warn(`Disconnecting slow client ${client.id} for request ${requestId} (${client.backpressureCount} backpressure events)`);
141
+ this.removeClient(requestId, client.id);
142
+ try {
143
+ client.res.end();
144
+ }
145
+ catch { /* already closed */ }
146
+ continue;
147
+ }
148
+ }
149
+ else {
150
+ client.backpressureCount = 0; // Reset on successful write
151
+ }
152
+ sentCount++;
153
+ }
154
+ catch (error) {
155
+ logger.error(`Failed to send to client ${client.id}:`, error);
156
+ this.removeClient(requestId, client.id);
157
+ }
158
+ }
159
+ return sentCount;
160
+ }
161
+ /**
162
+ * Broadcast a message to all connected clients across all requests
163
+ *
164
+ * @param type - Event type
165
+ * @param message - Message string
166
+ * @param data - Optional additional data
167
+ * @returns Total number of clients the message was sent to
168
+ */
169
+ broadcast(type, message, data) {
170
+ let totalSent = 0;
171
+ for (const requestId of this.clients.keys()) {
172
+ totalSent += this.send(requestId, type, message, data);
173
+ }
174
+ return totalSent;
175
+ }
176
+ /**
177
+ * Close all clients for a specific request
178
+ *
179
+ * @param requestId - Request ID to close
180
+ * @param finalMessage - Optional final message to send before closing
181
+ */
182
+ closeRequest(requestId, finalMessage) {
183
+ const clients = this.clients.get(requestId);
184
+ if (!clients)
185
+ return;
186
+ if (finalMessage) {
187
+ this.send(requestId, 'COMPLETED', finalMessage);
188
+ }
189
+ for (const client of clients) {
190
+ clearTimeout(client.timeout);
191
+ try {
192
+ client.res.end();
193
+ }
194
+ catch (err) {
195
+ logger.debug('Response already closed on request close', { requestId, clientId: client.id, error: err instanceof Error ? err.message : String(err) });
196
+ }
197
+ }
198
+ this.clients.delete(requestId);
199
+ logger.debug(`Closed all clients for request ${requestId}`);
200
+ }
201
+ /**
202
+ * Get statistics about current connections
203
+ */
204
+ getStats() {
205
+ let totalClients = 0;
206
+ let oldestConnection = null;
207
+ const now = Date.now();
208
+ for (const clients of this.clients.values()) {
209
+ totalClients += clients.length;
210
+ for (const client of clients) {
211
+ const age = now - client.connectedAt;
212
+ if (oldestConnection === null || age > oldestConnection) {
213
+ oldestConnection = age;
214
+ }
215
+ }
216
+ }
217
+ return {
218
+ totalRequests: this.clients.size,
219
+ totalClients,
220
+ oldestConnectionMs: oldestConnection,
221
+ };
222
+ }
223
+ /**
224
+ * Check if a request has any connected clients
225
+ */
226
+ hasClients(requestId) {
227
+ const clients = this.clients.get(requestId);
228
+ return clients !== undefined && clients.length > 0;
229
+ }
230
+ /**
231
+ * Get the number of clients for a specific request
232
+ */
233
+ getClientCount(requestId) {
234
+ return this.clients.get(requestId)?.length ?? 0;
235
+ }
236
+ /**
237
+ * Middleware to initialize SSE connection
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * app.get('/logs/:requestId', sseManager.middleware());
242
+ * ```
243
+ */
244
+ middleware() {
245
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
246
+ return (req, res) => {
247
+ const { requestId } = req.params;
248
+ if (!UUID_RE.test(requestId)) {
249
+ res.status(400).end('Invalid requestId format');
250
+ return;
251
+ }
252
+ // Check client limit BEFORE flushing headers, so we can still send 429
253
+ const existing = this.clients.get(requestId) || [];
254
+ if (existing.length >= this.maxClientsPerRequest) {
255
+ logger.warn(`Client limit reached for request ${requestId} (max: ${this.maxClientsPerRequest})`);
256
+ res.status(429).end('Too many connections for this request');
257
+ return;
258
+ }
259
+ res.setHeader('Content-Type', 'text/event-stream');
260
+ res.setHeader('Cache-Control', 'no-cache');
261
+ res.setHeader('Connection', 'keep-alive');
262
+ res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
263
+ res.flushHeaders();
264
+ this.addClient(requestId, res);
265
+ };
266
+ }
267
+ /**
268
+ * Start periodic cleanup of stale connections
269
+ */
270
+ startCleanupInterval(intervalMs) {
271
+ this.cleanupInterval = setInterval(() => {
272
+ this.cleanup();
273
+ }, intervalMs);
274
+ // Don't prevent process exit
275
+ this.cleanupInterval.unref();
276
+ }
277
+ /**
278
+ * Clean up stale connections using single-pass partition.
279
+ * O(R × C) instead of O(R × C²), and avoids mutation-during-iteration.
280
+ */
281
+ cleanup() {
282
+ const now = Date.now();
283
+ let cleaned = 0;
284
+ for (const [requestId, clients] of this.clients.entries()) {
285
+ const stale = [];
286
+ const active = [];
287
+ for (const client of clients) {
288
+ if (now - client.connectedAt > this.clientTimeoutMs) {
289
+ stale.push(client);
290
+ }
291
+ else {
292
+ active.push(client);
293
+ }
294
+ }
295
+ for (const client of stale) {
296
+ clearTimeout(client.timeout);
297
+ try {
298
+ client.res.end();
299
+ }
300
+ catch { /* already closed */ }
301
+ cleaned++;
302
+ }
303
+ if (active.length === 0) {
304
+ this.clients.delete(requestId);
305
+ }
306
+ else if (stale.length > 0) {
307
+ this.clients.set(requestId, active);
308
+ }
309
+ }
310
+ if (cleaned > 0) {
311
+ logger.info(`Cleaned up ${cleaned} stale SSE connections`);
312
+ }
313
+ }
314
+ /**
315
+ * Shutdown the SSE manager and close all connections
316
+ */
317
+ shutdown() {
318
+ if (this.cleanupInterval) {
319
+ clearInterval(this.cleanupInterval);
320
+ this.cleanupInterval = null;
321
+ }
322
+ for (const requestId of [...this.clients.keys()]) {
323
+ this.closeRequest(requestId, 'Server shutting down');
324
+ }
325
+ logger.info('SSE Manager shut down');
326
+ }
327
+ }
328
+ exports.SSEManager = SSEManager;
329
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLWNvbm5lY3Rpb24tbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL3NzZS1jb25uZWN0aW9uLW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7OztBQUV0Qyx5REFBMEQ7QUFDMUQsbUVBQWdFO0FBRWhFLCtCQUFrQztBQUVsQyxNQUFNLE1BQU0sR0FBRyxJQUFBLHVCQUFZLEVBQUMsWUFBWSxDQUFDLENBQUM7QUFrRDFDOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUNILE1BQWEsVUFBVTtJQUNiLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBdUIsQ0FBQztJQUNoQyxvQkFBb0IsQ0FBUztJQUM3QixlQUFlLENBQVM7SUFDakMsZUFBZSxHQUEwQixJQUFJLENBQUM7SUFFdEQsWUFBWSxVQUE2QixFQUFFO1FBQ3pDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxPQUFPLENBQUMsb0JBQW9CLElBQUksUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLElBQUksSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzFILElBQUksQ0FBQyxlQUFlLEdBQUcsT0FBTyxDQUFDLGVBQWUsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsSUFBSSxTQUFTLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxhQUFhO1FBRTdILE1BQU0saUJBQWlCLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixJQUFJLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLHVCQUF1QixJQUFJLFFBQVEsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFlBQVk7UUFDbEksSUFBSSxDQUFDLG9CQUFvQixDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFDL0MsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILFNBQVMsQ0FBQyxTQUFpQixFQUFFLEdBQWE7UUFDeEMsTUFBTSxR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QUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ3hCLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ2pDLENBQUM7aUJBQU0sSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDdEMsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLE9BQU8sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNoQixNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsT0FBTyx3QkFBd0IsQ0FBQyxDQUFDO1FBQzdELENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxRQUFRO1FBQ04sSUFBSSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7WUFDekIsYUFBYSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztZQUNwQyxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztRQUM5QixDQUFDO1FBRUQsS0FBSyxNQUFNLFNBQVMsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDakQsSUFBSSxDQUFDLFlBQVksQ0FBQyxTQUFTLEVBQUUsc0JBQXNCLENBQUMsQ0FBQztRQUN2RCxDQUFDO1FBRUQsTUFBTSxDQUFDLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7Q0FDRjtBQXhVRCxnQ0F3VUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgQ29yZUNvbnN0YW50cyB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWNvcmUnO1xuaW1wb3J0IHsgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IHY3IGFzIHV1aWQgfSBmcm9tICd1dWlkJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdTU0VNYW5hZ2VyJyk7XG5cbi8qKlxuICogRXZlbnQgdHlwZXMgZm9yIFNTRSBsb2dnaW5nXG4gKi9cbmV4cG9ydCB0eXBlIFNTRUV2ZW50VHlwZSA9ICdJTkZPJyB8ICdXQVJOJyB8ICdFUlJPUicgfCAnQ09NUExFVEVEJyB8ICdST0xMQkFDSycgfCAnTUVTU0FHRSc7XG5cbi8qKlxuICogU1NFIHBheWxvYWQgc3RydWN0dXJlXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU1NFUGF5bG9hZCB7XG4gIHRzOiBzdHJpbmc7XG4gIHR5cGU6IFNTRUV2ZW50VHlwZTtcbiAgbWVzc2FnZTogc3RyaW5nO1xuICBkYXRhPzogdW5rbm93bjtcbn1cblxuLyoqXG4gKiBTU0UgY2xpZW50IHdpdGggY29ubmVjdGlvbiB0cmFja2luZ1xuICovXG5leHBvcnQgaW50ZXJmYWNlIFNTRUNsaWVudCB7XG4gIGlkOiBzdHJpbmc7XG4gIHJlczogUmVzcG9uc2U7XG4gIGNvbm5lY3RlZEF0OiBudW1iZXI7XG4gIHRpbWVvdXQ6IE5vZGVKUy5UaW1lb3V0O1xuICAvKiogTnVtYmVyIG9mIGNvbnNlY3V0aXZlIGJhY2twcmVzc3VyZSBldmVudHMgKHdyaXRlIHJldHVybmVkIGZhbHNlKS4gKi9cbiAgYmFja3ByZXNzdXJlQ291bnQ6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBTU0UgTWFuYWdlciBjb25maWd1cmF0aW9uIG9wdGlvbnNcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VNYW5hZ2VyT3B0aW9ucyB7XG4gIC8qKiBNYXhpbXVtIGNsaWVudHMgYWxsb3dlZCBwZXIgcmVxdWVzdCBJRCAoZGVmYXVsdDogMTApICovXG4gIG1heENsaWVudHNQZXJSZXF1ZXN0PzogbnVtYmVyO1xuICAvKiogQ2xpZW50IHRpbWVvdXQgaW4gbWlsbGlzZWNvbmRzIChkZWZhdWx0OiAzMCBtaW51dGVzKSAqL1xuICBjbGllbnRUaW1lb3V0TXM/OiBudW1iZXI7XG4gIC8qKiBJbnRlcnZhbCBmb3IgY2xlYW51cCBjaGVja3MgaW4gbWlsbGlzZWNvbmRzIChkZWZhdWx0OiA1IG1pbnV0ZXMpICovXG4gIGNsZWFudXBJbnRlcnZhbE1zPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIFNTRSBNYW5hZ2VyIHN0YXRpc3RpY3NcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VNYW5hZ2VyU3RhdHMge1xuICB0b3RhbFJlcXVlc3RzOiBudW1iZXI7XG4gIHRvdGFsQ2xpZW50czogbnVtYmVyO1xuICBvbGRlc3RDb25uZWN0aW9uTXM6IG51bWJlciB8IG51bGw7XG59XG5cbi8qKlxuICogU1NFIGhlbHBlciBjbGFzcyB3aXRoIG1lbW9yeSBsZWFrIHByb3RlY3Rpb25cbiAqXG4gKiBGZWF0dXJlczpcbiAqIC0gQ2xpZW50IGxpbWl0cyBwZXIgcmVxdWVzdCBJRFxuICogLSBBdXRvbWF0aWMgdGltZW91dCBmb3IgaWRsZSBjb25uZWN0aW9uc1xuICogLSBQZXJpb2RpYyBjbGVhbnVwIG9mIHN0YWxlIGNvbm5lY3Rpb25zXG4gKiAtIENvbm5lY3Rpb24gc3RhdGlzdGljc1xuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCBzc2VNYW5hZ2VyID0gbmV3IFNTRU1hbmFnZXIoeyBtYXhDbGllbnRzUGVyUmVxdWVzdDogNSB9KTtcbiAqIGFwcC5nZXQoJy9sb2dzLzpyZXF1ZXN0SWQnLCBzc2VNYW5hZ2VyLm1pZGRsZXdhcmUoKSk7XG4gKlxuICogLy8gU2VuZCBldmVudHNcbiAqIHNzZU1hbmFnZXIuc2VuZCgncmVxdWVzdC0xMjMnLCAnSU5GTycsICdQcm9jZXNzaW5nLi4uJyk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNsYXNzIFNTRU1hbmFnZXIge1xuICBwcml2YXRlIGNsaWVudHMgPSBuZXcgTWFwPHN0cmluZywgU1NFQ2xpZW50W10+KCk7XG4gIHByaXZhdGUgcmVhZG9ubHkgbWF4Q2xpZW50c1BlclJlcXVlc3Q6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBjbGllbnRUaW1lb3V0TXM6IG51bWJlcjtcbiAgcHJpdmF0ZSBjbGVhbnVwSW50ZXJ2YWw6IE5vZGVKUy5UaW1lb3V0IHwgbnVsbCA9IG51bGw7XG5cbiAgY29uc3RydWN0b3Iob3B0aW9uczogU1NFTWFuYWdlck9wdGlvbnMgPSB7fSkge1xuICAgIHRoaXMubWF4Q2xpZW50c1BlclJlcXVlc3QgPSBvcHRpb25zLm1heENsaWVudHNQZXJSZXF1ZXN0ID8/IHBhcnNlSW50KHByb2Nlc3MuZW52LlNTRV9NQVhfQ0xJRU5UU19QRVJfUkVRVUVTVCB8fCAnMTAnLCAxMCk7XG4gICAgdGhpcy5jbGllbnRUaW1lb3V0TXMgPSBvcHRpb25zLmNsaWVudFRpbWVvdXRNcyA/PyBwYXJzZUludChwcm9jZXNzLmVudi5TU0VfQ0xJRU5UX1RJTUVPVVRfTVMgfHwgJzE4MDAwMDAnLCAxMCk7IC8vIDMwIG1pbnV0ZXNcblxuICAgIGNvbnN0IGNsZWFudXBJbnRlcnZhbE1zID0gb3B0aW9ucy5jbGVhbnVwSW50ZXJ2YWxNcyA/PyBwYXJzZUludChwcm9jZXNzLmVudi5TU0VfQ0xFQU5VUF9JTlRFUlZBTF9NUyB8fCAnMzAwMDAwJywgMTApOyAvLyA1IG1pbnV0ZXNcbiAgICB0aGlzLnN0YXJ0Q2xlYW51cEludGVydmFsKGNsZWFudXBJbnRlcnZhbE1zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGRzIGEgY2xpZW50IHRvIHRoZSBTU0UgbWFuYWdlclxuICAgKlxuICAgKiBAcGFyYW0gcmVxdWVzdElkIC0gVW5pcXVlIHJlcXVlc3QgSURcbiAgICogQHBhcmFtIHJlcyAtIEV4cHJlc3MgUmVzcG9uc2Ugb2JqZWN0XG4gICAqIEByZXR1cm5zIHRydWUgaWYgY2xpZW50IHdhcyBhZGRlZCwgZmFsc2UgaWYgcmVqZWN0ZWQgKGxpbWl0IHJlYWNoZWQpXG4gICAqL1xuICBhZGRDbGllbnQocmVxdWVzdElkOiBzdHJpbmcsIHJlczogUmVzcG9uc2UpOiBib29sZWFuIHtcbiAgICBjb25zdCBleGlzdGluZyA9IHRoaXMuY2xpZW50cy5nZXQocmVxdWVzdElkKSB8fCBbXTtcblxuICAgIC8vIENoZWNrIGNsaWVudCBsaW1pdFxuICAgIGlmIChleGlzdGluZy5sZW5ndGggPj0gdGhpcy5tYXhDbGllbnRzUGVyUmVxdWVzdCkge1xuICAgICAgbG9nZ2VyLndhcm4oYENsaWVudCBsaW1pdCByZWFjaGVkIGZvciByZXF1ZXN0ICR7cmVxdWVzdElkfSAobWF4OiAke3RoaXMubWF4Q2xpZW50c1BlclJlcXVlc3R9KWApO1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZSB0aW1lb3V0IGZvciB0aGlzIGNsaWVudFxuICAgIGNvbnN0IGNsaWVudElkID0gdXVpZCgpO1xuICAgIGNvbnN0IHRpbWVvdXQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgQ2xpZW50ICR7Y2xpZW50SWR9IHRpbWVkIG91dCBmb3IgcmVxdWVzdCAke3JlcXVlc3RJZH1gKTtcbiAgICAgIHRoaXMucmVtb3ZlQ2xpZW50KHJlcXVlc3RJZCwgY2xpZW50SWQpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmVzLmVuZCgpO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZygnUmVzcG9uc2UgYWxyZWFkeSBjbG9zZWQgb24gdGltZW91dCcsIHsgcmVxdWVzdElkLCBjbGllbnRJZCwgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9LCB0aGlzLmNsaWVudFRpbWVvdXRNcyk7XG5cbiAgICBjb25zdCBjbGllbnQ6IFNTRUNsaWVudCA9IHtcbiAgICAgIGlkOiBjbGllbnRJZCxcbiAgICAgIHJlcyxcbiAgICAgIGNvbm5lY3RlZEF0OiBEYXRlLm5vdygpLFxuICAgICAgdGltZW91dCxcbiAgICAgIGJhY2twcmVzc3VyZUNvdW50OiAwLFxuICAgIH07XG5cbiAgICAvLyBIYW5kbGUgZGlzY29ubmVjdGlvblxuICAgIHJlcy5vbignY2xvc2UnLCAoKSA9PiB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICB0aGlzLnJlbW92ZUNsaWVudChyZXF1ZXN0SWQsIGNsaWVudElkKTtcbiAgICB9KTtcblxuICAgIHJlcy5vbignZXJyb3InLCAoZXJyKSA9PiB7XG4gICAgICBsb2dnZXIuZXJyb3IoYFNTRSBjbGllbnQgZXJyb3IgZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9OmAsIGVycik7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICB0aGlzLnJlbW92ZUNsaWVudChyZXF1ZXN0SWQsIGNsaWVudElkKTtcbiAgICB9KTtcblxuICAgIGV4aXN0aW5nLnB1c2goY2xpZW50KTtcbiAgICB0aGlzLmNsaWVudHMuc2V0KHJlcXVlc3RJZCwgZXhpc3RpbmcpO1xuXG4gICAgbG9nZ2VyLmRlYnVnKGBDbGllbnQgJHtjbGllbnRJZH0gY29ubmVjdGVkIGZvciByZXF1ZXN0ICR7cmVxdWVzdElkfSAodG90YWw6ICR7ZXhpc3RpbmcubGVuZ3RofSlgKTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmVzIGEgY2xpZW50IGZyb20gdGhlIG1hbmFnZXJcbiAgICovXG4gIHByaXZhdGUgcmVtb3ZlQ2xpZW50KHJlcXVlc3RJZDogc3RyaW5nLCBjbGllbnRJZDogc3RyaW5nKTogdm9pZCB7XG4gICAgY29uc3QgY2xpZW50cyA9IHRoaXMuY2xpZW50cy5nZXQocmVxdWVzdElkKTtcbiAgICBpZiAoIWNsaWVudHMpIHJldHVybjtcblxuICAgIGNvbnN0IHJlbWFpbmluZyA9IGNsaWVudHMuZmlsdGVyKGMgPT4ge1xuICAgICAgaWYgKGMuaWQgPT09IGNsaWVudElkKSB7XG4gICAgICAgIGNsZWFyVGltZW91dChjLnRpbWVvdXQpO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9KTtcblxuICAgIGlmIChyZW1haW5pbmcubGVuZ3RoID09PSAwKSB7XG4gICAgICB0aGlzLmNsaWVudHMuZGVsZXRlKHJlcXVlc3RJZCk7XG4gICAgICBsb2dnZXIuZGVidWcoYEFsbCBjbGllbnRzIGRpc2Nvbm5lY3RlZCBmb3IgcmVxdWVzdCAke3JlcXVlc3RJZH1gKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5jbGllbnRzLnNldChyZXF1ZXN0SWQsIHJlbWFpbmluZyk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNlbmRzIGEgbWVzc2FnZSB0byBhbGwgU1NFIGNsaWVudHMgZm9yIGEgcmVxdWVzdElkXG4gICAqXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBSZXF1ZXN0IElEXG4gICAqIEBwYXJhbSB0eXBlIC0gRXZlbnQgdHlwZVxuICAgKiBAcGFyYW0gbWVzc2FnZSAtIE1lc3NhZ2Ugc3RyaW5nXG4gICAqIEBwYXJhbSBkYXRhIC0gT3B0aW9uYWwgYWRkaXRpb25hbCBkYXRhXG4gICAqIEByZXR1cm5zIE51bWJlciBvZiBjbGllbnRzIHRoZSBtZXNzYWdlIHdhcyBzZW50IHRvXG4gICAqL1xuICBzZW5kKHJlcXVlc3RJZDogc3RyaW5nLCB0eXBlOiBTU0VFdmVudFR5cGUsIG1lc3NhZ2U6IHN0cmluZywgZGF0YT86IHVua25vd24pOiBudW1iZXIge1xuICAgIGNvbnN0IHBheWxvYWQ6IFNTRVBheWxvYWQgPSB7XG4gICAgICB0czogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgdHlwZSxcbiAgICAgIG1lc3NhZ2UsXG4gICAgICBkYXRhLFxuICAgIH07XG5cbiAgICBjb25zdCBjbGllbnRzID0gWy4uLih0aGlzLmNsaWVudHMuZ2V0KHJlcXVlc3RJZCkgfHwgW10pXTtcbiAgICBsZXQgc2VudENvdW50ID0gMDtcbiAgICBjb25zdCBzZXJpYWxpemVkID0gYGRhdGE6ICR7SlNPTi5zdHJpbmdpZnkocGF5bG9hZCl9XFxuXFxuYDtcblxuICAgIGZvciAoY29uc3QgY2xpZW50IG9mIGNsaWVudHMpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIC8vIEJhY2twcmVzc3VyZTogc2tpcCBjbGllbnRzIHdob3NlIHdyaXRlIGJ1ZmZlciBpcyBmdWxsXG4gICAgICAgIGlmIChjbGllbnQucmVzLndyaXRhYmxlRW5kZWQpIHtcbiAgICAgICAgICB0aGlzLnJlbW92ZUNsaWVudChyZXF1ZXN0SWQsIGNsaWVudC5pZCk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgY2FuV3JpdGUgPSBjbGllbnQucmVzLndyaXRlKHNlcmlhbGl6ZWQpO1xuICAgICAgICBpZiAoIWNhbldyaXRlKSB7XG4gICAgICAgICAgY2xpZW50LmJhY2twcmVzc3VyZUNvdW50Kys7XG4gICAgICAgICAgLy8gRGlzY29ubmVjdCBjbGllbnRzIHRoYXQgY29uc2lzdGVudGx5IGNhbid0IGtlZXAgdXAgKDEwIGNvbnNlY3V0aXZlIGJhY2twcmVzc3VyZSBldmVudHMpXG4gICAgICAgICAgaWYgKGNsaWVudC5iYWNrcHJlc3N1cmVDb3VudCA+PSBDb3JlQ29uc3RhbnRzLlNTRV9CQUNLUFJFU1NVUkVfVEhSRVNIT0xEKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihgRGlzY29ubmVjdGluZyBzbG93IGNsaWVudCAke2NsaWVudC5pZH0gZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9ICgke2NsaWVudC5iYWNrcHJlc3N1cmVDb3VudH0gYmFja3ByZXNzdXJlIGV2ZW50cylgKTtcbiAgICAgICAgICAgIHRoaXMucmVtb3ZlQ2xpZW50KHJlcXVlc3RJZCwgY2xpZW50LmlkKTtcbiAgICAgICAgICAgIHRyeSB7IGNsaWVudC5yZXMuZW5kKCk7IH0gY2F0Y2ggeyAvKiBhbHJlYWR5IGNsb3NlZCAqLyB9XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY2xpZW50LmJhY2twcmVzc3VyZUNvdW50ID0gMDsgLy8gUmVzZXQgb24gc3VjY2Vzc2Z1bCB3cml0ZVxuICAgICAgICB9XG4gICAgICAgIHNlbnRDb3VudCsrO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gc2VuZCB0byBjbGllbnQgJHtjbGllbnQuaWR9OmAsIGVycm9yKTtcbiAgICAgICAgdGhpcy5yZW1vdmVDbGllbnQocmVxdWVzdElkLCBjbGllbnQuaWQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBzZW50Q291bnQ7XG4gIH1cblxuICAvKipcbiAgICogQnJvYWRjYXN0IGEgbWVzc2FnZSB0byBhbGwgY29ubmVjdGVkIGNsaWVudHMgYWNyb3NzIGFsbCByZXF1ZXN0c1xuICAgKlxuICAgKiBAcGFyYW0gdHlwZSAtIEV2ZW50IHR5cGVcbiAgICogQHBhcmFtIG1lc3NhZ2UgLSBNZXNzYWdlIHN0cmluZ1xuICAgKiBAcGFyYW0gZGF0YSAtIE9wdGlvbmFsIGFkZGl0aW9uYWwgZGF0YVxuICAgKiBAcmV0dXJucyBUb3RhbCBudW1iZXIgb2YgY2xpZW50cyB0aGUgbWVzc2FnZSB3YXMgc2VudCB0b1xuICAgKi9cbiAgYnJvYWRjYXN0KHR5cGU6IFNTRUV2ZW50VHlwZSwgbWVzc2FnZTogc3RyaW5nLCBkYXRhPzogdW5rbm93bik6IG51bWJlciB7XG4gICAgbGV0IHRvdGFsU2VudCA9IDA7XG4gICAgZm9yIChjb25zdCByZXF1ZXN0SWQgb2YgdGhpcy5jbGllbnRzLmtleXMoKSkge1xuICAgICAgdG90YWxTZW50ICs9IHRoaXMuc2VuZChyZXF1ZXN0SWQsIHR5cGUsIG1lc3NhZ2UsIGRhdGEpO1xuICAgIH1cbiAgICByZXR1cm4gdG90YWxTZW50O1xuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlIGFsbCBjbGllbnRzIGZvciBhIHNwZWNpZmljIHJlcXVlc3RcbiAgICpcbiAgICogQHBhcmFtIHJlcXVlc3RJZCAtIFJlcXVlc3QgSUQgdG8gY2xvc2VcbiAgICogQHBhcmFtIGZpbmFsTWVzc2FnZSAtIE9wdGlvbmFsIGZpbmFsIG1lc3NhZ2UgdG8gc2VuZCBiZWZvcmUgY2xvc2luZ1xuICAgKi9cbiAgY2xvc2VSZXF1ZXN0KHJlcXVlc3RJZDogc3RyaW5nLCBmaW5hbE1lc3NhZ2U/OiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCBjbGllbnRzID0gdGhpcy5jbGllbnRzLmdldChyZXF1ZXN0SWQpO1xuICAgIGlmICghY2xpZW50cykgcmV0dXJuO1xuXG4gICAgaWYgKGZpbmFsTWVzc2FnZSkge1xuICAgICAgdGhpcy5zZW5kKHJlcXVlc3RJZCwgJ0NPTVBMRVRFRCcsIGZpbmFsTWVzc2FnZSk7XG4gICAgfVxuXG4gICAgZm9yIChjb25zdCBjbGllbnQgb2YgY2xpZW50cykge1xuICAgICAgY2xlYXJUaW1lb3V0KGNsaWVudC50aW1lb3V0KTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNsaWVudC5yZXMuZW5kKCk7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdSZXNwb25zZSBhbHJlYWR5IGNsb3NlZCBvbiByZXF1ZXN0IGNsb3NlJywgeyByZXF1ZXN0SWQsIGNsaWVudElkOiBjbGllbnQuaWQsIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycikgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5jbGllbnRzLmRlbGV0ZShyZXF1ZXN0SWQpO1xuICAgIGxvZ2dlci5kZWJ1ZyhgQ2xvc2VkIGFsbCBjbGllbnRzIGZvciByZXF1ZXN0ICR7cmVxdWVzdElkfWApO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBzdGF0aXN0aWNzIGFib3V0IGN1cnJlbnQgY29ubmVjdGlvbnNcbiAgICovXG4gIGdldFN0YXRzKCk6IFNTRU1hbmFnZXJTdGF0cyB7XG4gICAgbGV0IHRvdGFsQ2xpZW50cyA9IDA7XG4gICAgbGV0IG9sZGVzdENvbm5lY3Rpb246IG51bWJlciB8IG51bGwgPSBudWxsO1xuICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG5cbiAgICBmb3IgKGNvbnN0IGNsaWVudHMgb2YgdGhpcy5jbGllbnRzLnZhbHVlcygpKSB7XG4gICAgICB0b3RhbENsaWVudHMgKz0gY2xpZW50cy5sZW5ndGg7XG4gICAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBjbGllbnRzKSB7XG4gICAgICAgIGNvbnN0IGFnZSA9IG5vdyAtIGNsaWVudC5jb25uZWN0ZWRBdDtcbiAgICAgICAgaWYgKG9sZGVzdENvbm5lY3Rpb24gPT09IG51bGwgfHwgYWdlID4gb2xkZXN0Q29ubmVjdGlvbikge1xuICAgICAgICAgIG9sZGVzdENvbm5lY3Rpb24gPSBhZ2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgdG90YWxSZXF1ZXN0czogdGhpcy5jbGllbnRzLnNpemUsXG4gICAgICB0b3RhbENsaWVudHMsXG4gICAgICBvbGRlc3RDb25uZWN0aW9uTXM6IG9sZGVzdENvbm5lY3Rpb24sXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBhIHJlcXVlc3QgaGFzIGFueSBjb25uZWN0ZWQgY2xpZW50c1xuICAgKi9cbiAgaGFzQ2xpZW50cyhyZXF1ZXN0SWQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IGNsaWVudHMgPSB0aGlzLmNsaWVudHMuZ2V0KHJlcXVlc3RJZCk7XG4gICAgcmV0dXJuIGNsaWVudHMgIT09IHVuZGVmaW5lZCAmJiBjbGllbnRzLmxlbmd0aCA+IDA7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBudW1iZXIgb2YgY2xpZW50cyBmb3IgYSBzcGVjaWZpYyByZXF1ZXN0XG4gICAqL1xuICBnZXRDbGllbnRDb3VudChyZXF1ZXN0SWQ6IHN0cmluZyk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMuY2xpZW50cy5nZXQocmVxdWVzdElkKT8ubGVuZ3RoID8/IDA7XG4gIH1cblxuICAvKipcbiAgICogTWlkZGxld2FyZSB0byBpbml0aWFsaXplIFNTRSBjb25uZWN0aW9uXG4gICAqXG4gICAqIEBleGFtcGxlXG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogYXBwLmdldCgnL2xvZ3MvOnJlcXVlc3RJZCcsIHNzZU1hbmFnZXIubWlkZGxld2FyZSgpKTtcbiAgICogYGBgXG4gICAqL1xuICBtaWRkbGV3YXJlKCkge1xuICAgIGNvbnN0IFVVSURfUkUgPSAvXlswLTlhLWZdezh9LVswLTlhLWZdezR9LVswLTlhLWZdezR9LVswLTlhLWZdezR9LVswLTlhLWZdezEyfSQvaTtcblxuICAgIHJldHVybiAocmVxOiB7IHBhcmFtczogeyByZXF1ZXN0SWQ6IHN0cmluZyB9IH0sIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICAgIGNvbnN0IHsgcmVxdWVzdElkIH0gPSByZXEucGFyYW1zO1xuXG4gICAgICBpZiAoIVVVSURfUkUudGVzdChyZXF1ZXN0SWQpKSB7XG4gICAgICAgIHJlcy5zdGF0dXMoNDAwKS5lbmQoJ0ludmFsaWQgcmVxdWVzdElkIGZvcm1hdCcpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIC8vIENoZWNrIGNsaWVudCBsaW1pdCBCRUZPUkUgZmx1c2hpbmcgaGVhZGVycywgc28gd2UgY2FuIHN0aWxsIHNlbmQgNDI5XG4gICAgICBjb25zdCBleGlzdGluZyA9IHRoaXMuY2xpZW50cy5nZXQocmVxdWVzdElkKSB8fCBbXTtcbiAgICAgIGlmIChleGlzdGluZy5sZW5ndGggPj0gdGhpcy5tYXhDbGllbnRzUGVyUmVxdWVzdCkge1xuICAgICAgICBsb2dnZXIud2FybihgQ2xpZW50IGxpbWl0IHJlYWNoZWQgZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9IChtYXg6ICR7dGhpcy5tYXhDbGllbnRzUGVyUmVxdWVzdH0pYCk7XG4gICAgICAgIHJlcy5zdGF0dXMoNDI5KS5lbmQoJ1RvbyBtYW55IGNvbm5lY3Rpb25zIGZvciB0aGlzIHJlcXVlc3QnKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICByZXMuc2V0SGVhZGVyKCdDb250ZW50LVR5cGUnLCAndGV4dC9ldmVudC1zdHJlYW0nKTtcbiAgICAgIHJlcy5zZXRIZWFkZXIoJ0NhY2hlLUNvbnRyb2wnLCAnbm8tY2FjaGUnKTtcbiAgICAgIHJlcy5zZXRIZWFkZXIoJ0Nvbm5lY3Rpb24nLCAna2VlcC1hbGl2ZScpO1xuICAgICAgcmVzLnNldEhlYWRlcignWC1BY2NlbC1CdWZmZXJpbmcnLCAnbm8nKTsgLy8gRGlzYWJsZSBuZ2lueCBidWZmZXJpbmdcbiAgICAgIHJlcy5mbHVzaEhlYWRlcnMoKTtcblxuICAgICAgdGhpcy5hZGRDbGllbnQocmVxdWVzdElkLCByZXMpO1xuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogU3RhcnQgcGVyaW9kaWMgY2xlYW51cCBvZiBzdGFsZSBjb25uZWN0aW9uc1xuICAgKi9cbiAgcHJpdmF0ZSBzdGFydENsZWFudXBJbnRlcnZhbChpbnRlcnZhbE1zOiBudW1iZXIpOiB2b2lkIHtcbiAgICB0aGlzLmNsZWFudXBJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHtcbiAgICAgIHRoaXMuY2xlYW51cCgpO1xuICAgIH0sIGludGVydmFsTXMpO1xuXG4gICAgLy8gRG9uJ3QgcHJldmVudCBwcm9jZXNzIGV4aXRcbiAgICB0aGlzLmNsZWFudXBJbnRlcnZhbC51bnJlZigpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFuIHVwIHN0YWxlIGNvbm5lY3Rpb25zIHVzaW5nIHNpbmdsZS1wYXNzIHBhcnRpdGlvbi5cbiAgICogTyhSIMOXIEMpIGluc3RlYWQgb2YgTyhSIMOXIEPCsiksIGFuZCBhdm9pZHMgbXV0YXRpb24tZHVyaW5nLWl0ZXJhdGlvbi5cbiAgICovXG4gIHByaXZhdGUgY2xlYW51cCgpOiB2b2lkIHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGxldCBjbGVhbmVkID0gMDtcblxuICAgIGZvciAoY29uc3QgW3JlcXVlc3RJZCwgY2xpZW50c10gb2YgdGhpcy5jbGllbnRzLmVudHJpZXMoKSkge1xuICAgICAgY29uc3Qgc3RhbGU6IFNTRUNsaWVudFtdID0gW107XG4gICAgICBjb25zdCBhY3RpdmU6IFNTRUNsaWVudFtdID0gW107XG5cbiAgICAgIGZvciAoY29uc3QgY2xpZW50IG9mIGNsaWVudHMpIHtcbiAgICAgICAgaWYgKG5vdyAtIGNsaWVudC5jb25uZWN0ZWRBdCA+IHRoaXMuY2xpZW50VGltZW91dE1zKSB7XG4gICAgICAgICAgc3RhbGUucHVzaChjbGllbnQpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGFjdGl2ZS5wdXNoKGNsaWVudCk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgZm9yIChjb25zdCBjbGllbnQgb2Ygc3RhbGUpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KGNsaWVudC50aW1lb3V0KTtcbiAgICAgICAgdHJ5IHsgY2xpZW50LnJlcy5lbmQoKTsgfSBjYXRjaCB7IC8qIGFscmVhZHkgY2xvc2VkICovIH1cbiAgICAgICAgY2xlYW5lZCsrO1xuICAgICAgfVxuXG4gICAgICBpZiAoYWN0aXZlLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICB0aGlzLmNsaWVudHMuZGVsZXRlKHJlcXVlc3RJZCk7XG4gICAgICB9IGVsc2UgaWYgKHN0YWxlLmxlbmd0aCA+IDApIHtcbiAgICAgICAgdGhpcy5jbGllbnRzLnNldChyZXF1ZXN0SWQsIGFjdGl2ZSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGNsZWFuZWQgPiAwKSB7XG4gICAgICBsb2dnZXIuaW5mbyhgQ2xlYW5lZCB1cCAke2NsZWFuZWR9IHN0YWxlIFNTRSBjb25uZWN0aW9uc2ApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTaHV0ZG93biB0aGUgU1NFIG1hbmFnZXIgYW5kIGNsb3NlIGFsbCBjb25uZWN0aW9uc1xuICAgKi9cbiAgc2h1dGRvd24oKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuY2xlYW51cEludGVydmFsKSB7XG4gICAgICBjbGVhckludGVydmFsKHRoaXMuY2xlYW51cEludGVydmFsKTtcbiAgICAgIHRoaXMuY2xlYW51cEludGVydmFsID0gbnVsbDtcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IHJlcXVlc3RJZCBvZiBbLi4udGhpcy5jbGllbnRzLmtleXMoKV0pIHtcbiAgICAgIHRoaXMuY2xvc2VSZXF1ZXN0KHJlcXVlc3RJZCwgJ1NlcnZlciBzaHV0dGluZyBkb3duJyk7XG4gICAgfVxuXG4gICAgbG9nZ2VyLmluZm8oJ1NTRSBNYW5hZ2VyIHNodXQgZG93bicpO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,37 @@
1
+ export interface WSClient {
2
+ id: string;
3
+ orgId: string;
4
+ send(data: string): void;
5
+ close(): void;
6
+ }
7
+ type MessageHandler = (client: WSClient, message: Record<string, unknown>) => void;
8
+ /**
9
+ * WebSocket connection manager.
10
+ * Manages authenticated WebSocket connections per org.
11
+ * Works alongside SSE for backward compatibility.
12
+ *
13
+ * Requires a WebSocket library (ws) to be wired in at server startup.
14
+ * This module provides the management layer only.
15
+ */
16
+ export declare class WSManager {
17
+ private clients;
18
+ private handlers;
19
+ private maxClientsPerOrg;
20
+ constructor(options?: {
21
+ maxClientsPerOrg?: number;
22
+ });
23
+ addClient(client: WSClient): boolean;
24
+ removeClient(client: WSClient): void;
25
+ onMessage(type: string, handler: MessageHandler): void;
26
+ handleMessage(client: WSClient, raw: string): void;
27
+ sendToOrg(orgId: string, type: string, data: unknown): number;
28
+ broadcast(type: string, data: unknown): number;
29
+ getStats(): {
30
+ orgs: number;
31
+ clients: number;
32
+ };
33
+ }
34
+ export declare function createWSManager(options?: {
35
+ maxClientsPerOrg?: number;
36
+ }): WSManager;
37
+ export {};
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.WSManager = void 0;
6
+ exports.createWSManager = createWSManager;
7
+ const api_core_1 = require("@pipeline-builder/api-core");
8
+ const logger = (0, api_core_1.createLogger)('ws-manager');
9
+ /**
10
+ * WebSocket connection manager.
11
+ * Manages authenticated WebSocket connections per org.
12
+ * Works alongside SSE for backward compatibility.
13
+ *
14
+ * Requires a WebSocket library (ws) to be wired in at server startup.
15
+ * This module provides the management layer only.
16
+ */
17
+ class WSManager {
18
+ clients = new Map();
19
+ handlers = new Map();
20
+ maxClientsPerOrg;
21
+ constructor(options = {}) {
22
+ this.maxClientsPerOrg = options.maxClientsPerOrg ?? 50;
23
+ }
24
+ addClient(client) {
25
+ if (!this.clients.has(client.orgId)) {
26
+ this.clients.set(client.orgId, new Set());
27
+ }
28
+ const orgClients = this.clients.get(client.orgId);
29
+ if (orgClients.size >= this.maxClientsPerOrg) {
30
+ logger.warn('Max WebSocket clients reached for org', { orgId: client.orgId });
31
+ return false;
32
+ }
33
+ orgClients.add(client);
34
+ logger.debug('WebSocket client connected', { orgId: client.orgId, clientId: client.id, total: orgClients.size });
35
+ return true;
36
+ }
37
+ removeClient(client) {
38
+ const orgClients = this.clients.get(client.orgId);
39
+ if (orgClients) {
40
+ orgClients.delete(client);
41
+ if (orgClients.size === 0)
42
+ this.clients.delete(client.orgId);
43
+ }
44
+ }
45
+ onMessage(type, handler) {
46
+ this.handlers.set(type, handler);
47
+ }
48
+ handleMessage(client, raw) {
49
+ try {
50
+ const msg = JSON.parse(raw);
51
+ if (!msg.type)
52
+ return;
53
+ const handler = this.handlers.get(msg.type);
54
+ if (handler)
55
+ handler(client, msg);
56
+ }
57
+ catch {
58
+ logger.debug('Invalid WebSocket message', { clientId: client.id });
59
+ }
60
+ }
61
+ sendToOrg(orgId, type, data) {
62
+ const orgClients = this.clients.get(orgId);
63
+ if (!orgClients)
64
+ return 0;
65
+ const payload = JSON.stringify({ type, data, ts: new Date().toISOString() });
66
+ let sent = 0;
67
+ for (const client of orgClients) {
68
+ try {
69
+ client.send(payload);
70
+ sent++;
71
+ }
72
+ catch {
73
+ this.removeClient(client);
74
+ }
75
+ }
76
+ return sent;
77
+ }
78
+ broadcast(type, data) {
79
+ const payload = JSON.stringify({ type, data, ts: new Date().toISOString() });
80
+ let sent = 0;
81
+ for (const [, orgClients] of this.clients) {
82
+ for (const client of orgClients) {
83
+ try {
84
+ client.send(payload);
85
+ sent++;
86
+ }
87
+ catch {
88
+ this.removeClient(client);
89
+ }
90
+ }
91
+ }
92
+ return sent;
93
+ }
94
+ getStats() {
95
+ let clients = 0;
96
+ for (const [, orgClients] of this.clients)
97
+ clients += orgClients.size;
98
+ return { orgs: this.clients.size, clients };
99
+ }
100
+ }
101
+ exports.WSManager = WSManager;
102
+ function createWSManager(options) {
103
+ return new WSManager(options);
104
+ }
105
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid3MtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL3dzLW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7OztBQWtHdEMsMENBRUM7QUFsR0QseURBQTBEO0FBRTFELE1BQU0sTUFBTSxHQUFHLElBQUEsdUJBQVksRUFBQyxZQUFZLENBQUMsQ0FBQztBQVcxQzs7Ozs7OztHQU9HO0FBQ0gsTUFBYSxTQUFTO0lBQ1osT0FBTyxHQUFHLElBQUksR0FBRyxFQUF5QixDQUFDO0lBQzNDLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBMEIsQ0FBQztJQUM3QyxnQkFBZ0IsQ0FBUztJQUVqQyxZQUFZLFVBQXlDLEVBQUU7UUFDckQsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE9BQU8sQ0FBQyxnQkFBZ0IsSUFBSSxFQUFFLENBQUM7SUFDekQsQ0FBQztJQUVELFNBQVMsQ0FBQyxNQUFnQjtRQUN4QixJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDcEMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLEdBQUcsRUFBRSxDQUFDLENBQUM7UUFDNUMsQ0FBQztRQUNELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUUsQ0FBQztRQUNuRCxJQUFJLFVBQVUsQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7WUFDN0MsTUFBTSxDQUFDLElBQUksQ0FBQyx1Q0FBdUMsRUFBRSxFQUFFLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztZQUM5RSxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFDRCxVQUFVLENBQUMsR0FBRyxDQUFD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QUM7Z0JBQ2hDLElBQUksQ0FBQztvQkFBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO29CQUFDLElBQUksRUFBRSxDQUFDO2dCQUFDLENBQUM7Z0JBQUMsTUFBTSxDQUFDO29CQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQUMsQ0FBQztZQUM1RSxDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELFFBQVE7UUFDTixJQUFJLE9BQU8sR0FBRyxDQUFDLENBQUM7UUFDaEIsS0FBSyxNQUFNLENBQUMsRUFBRSxVQUFVLENBQUMsSUFBSSxJQUFJLENBQUMsT0FBTztZQUFFLE9BQU8sSUFBSSxVQUFVLENBQUMsSUFBSSxDQUFDO1FBQ3RFLE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUM7SUFDOUMsQ0FBQztDQUNGO0FBekVELDhCQXlFQztBQUVELFNBQWdCLGVBQWUsQ0FBQyxPQUF1QztJQUNyRSxPQUFPLElBQUksU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQ2hDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3dzLW1hbmFnZXInKTtcblxuZXhwb3J0IGludGVyZmFjZSBXU0NsaWVudCB7XG4gIGlkOiBzdHJpbmc7XG4gIG9yZ0lkOiBzdHJpbmc7XG4gIHNlbmQoZGF0YTogc3RyaW5nKTogdm9pZDtcbiAgY2xvc2UoKTogdm9pZDtcbn1cblxudHlwZSBNZXNzYWdlSGFuZGxlciA9IChjbGllbnQ6IFdTQ2xpZW50LCBtZXNzYWdlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikgPT4gdm9pZDtcblxuLyoqXG4gKiBXZWJTb2NrZXQgY29ubmVjdGlvbiBtYW5hZ2VyLlxuICogTWFuYWdlcyBhdXRoZW50aWNhdGVkIFdlYlNvY2tldCBjb25uZWN0aW9ucyBwZXIgb3JnLlxuICogV29ya3MgYWxvbmdzaWRlIFNTRSBmb3IgYmFja3dhcmQgY29tcGF0aWJpbGl0eS5cbiAqXG4gKiBSZXF1aXJlcyBhIFdlYlNvY2tldCBsaWJyYXJ5ICh3cykgdG8gYmUgd2lyZWQgaW4gYXQgc2VydmVyIHN0YXJ0dXAuXG4gKiBUaGlzIG1vZHVsZSBwcm92aWRlcyB0aGUgbWFuYWdlbWVudCBsYXllciBvbmx5LlxuICovXG5leHBvcnQgY2xhc3MgV1NNYW5hZ2VyIHtcbiAgcHJpdmF0ZSBjbGllbnRzID0gbmV3IE1hcDxzdHJpbmcsIFNldDxXU0NsaWVudD4+KCk7XG4gIHByaXZhdGUgaGFuZGxlcnMgPSBuZXcgTWFwPHN0cmluZywgTWVzc2FnZUhhbmRsZXI+KCk7XG4gIHByaXZhdGUgbWF4Q2xpZW50c1Blck9yZzogbnVtYmVyO1xuXG4gIGNvbnN0cnVjdG9yKG9wdGlvbnM6IHsgbWF4Q2xpZW50c1Blck9yZz86IG51bWJlciB9ID0ge30pIHtcbiAgICB0aGlzLm1heENsaWVudHNQZXJPcmcgPSBvcHRpb25zLm1heENsaWVudHNQZXJPcmcgPz8gNTA7XG4gIH1cblxuICBhZGRDbGllbnQoY2xpZW50OiBXU0NsaWVudCk6IGJvb2xlYW4ge1xuICAgIGlmICghdGhpcy5jbGllbnRzLmhhcyhjbGllbnQub3JnSWQpKSB7XG4gICAgICB0aGlzLmNsaWVudHMuc2V0KGNsaWVudC5vcmdJZCwgbmV3IFNldCgpKTtcbiAgICB9XG4gICAgY29uc3Qgb3JnQ2xpZW50cyA9IHRoaXMuY2xpZW50cy5nZXQoY2xpZW50Lm9yZ0lkKSE7XG4gICAgaWYgKG9yZ0NsaWVudHMuc2l6ZSA+PSB0aGlzLm1heENsaWVudHNQZXJPcmcpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdNYXggV2ViU29ja2V0IGNsaWVudHMgcmVhY2hlZCBmb3Igb3JnJywgeyBvcmdJZDogY2xpZW50Lm9yZ0lkIH0pO1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBvcmdDbGllbnRzLmFkZChjbGllbnQpO1xuICAgIGxvZ2dlci5kZWJ1ZygnV2ViU29ja2V0IGNsaWVudCBjb25uZWN0ZWQnLCB7IG9yZ0lkOiBjbGllbnQub3JnSWQsIGNsaWVudElkOiBjbGllbnQuaWQsIHRvdGFsOiBvcmdDbGllbnRzLnNpemUgfSk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICByZW1vdmVDbGllbnQoY2xpZW50OiBXU0NsaWVudCk6IHZvaWQge1xuICAgIGNvbnN0IG9yZ0NsaWVudHMgPSB0aGlzLmNsaWVudHMuZ2V0KGNsaWVudC5vcmdJZCk7XG4gICAgaWYgKG9yZ0NsaWVudHMpIHtcbiAgICAgIG9yZ0NsaWVudHMuZGVsZXRlKGNsaWVudCk7XG4gICAgICBpZiAob3JnQ2xpZW50cy5zaXplID09PSAwKSB0aGlzLmNsaWVudHMuZGVsZXRlKGNsaWVudC5vcmdJZCk7XG4gICAgfVxuICB9XG5cbiAgb25NZXNzYWdlKHR5cGU6IHN0cmluZywgaGFuZGxlcjogTWVzc2FnZUhhbmRsZXIpOiB2b2lkIHtcbiAgICB0aGlzLmhhbmRsZXJzLnNldCh0eXBlLCBoYW5kbGVyKTtcbiAgfVxuXG4gIGhhbmRsZU1lc3NhZ2UoY2xpZW50OiBXU0NsaWVudCwgcmF3OiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgbXNnID0gSlNPTi5wYXJzZShyYXcpIGFzIHsgdHlwZT86IHN0cmluZzsgW2tleTogc3RyaW5nXTogdW5rbm93biB9O1xuICAgICAgaWYgKCFtc2cudHlwZSkgcmV0dXJuO1xuICAgICAgY29uc3QgaGFuZGxlciA9IHRoaXMuaGFuZGxlcnMuZ2V0KG1zZy50eXBlKTtcbiAgICAgIGlmIChoYW5kbGVyKSBoYW5kbGVyKGNsaWVudCwgbXNnKTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnSW52YWxpZCBXZWJTb2NrZXQgbWVzc2FnZScsIHsgY2xpZW50SWQ6IGNsaWVudC5pZCB9KTtcbiAgICB9XG4gIH1cblxuICBzZW5kVG9Pcmcob3JnSWQ6IHN0cmluZywgdHlwZTogc3RyaW5nLCBkYXRhOiB1bmtub3duKTogbnVtYmVyIHtcbiAgICBjb25zdCBvcmdDbGllbnRzID0gdGhpcy5jbGllbnRzLmdldChvcmdJZCk7XG4gICAgaWYgKCFvcmdDbGllbnRzKSByZXR1cm4gMDtcbiAgICBjb25zdCBwYXlsb2FkID0gSlNPTi5zdHJpbmdpZnkoeyB0eXBlLCBkYXRhLCB0czogbmV3IERhdGUoKS50b0lTT1N0cmluZygpIH0pO1xuICAgIGxldCBzZW50ID0gMDtcbiAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBvcmdDbGllbnRzKSB7XG4gICAgICB0cnkgeyBjbGllbnQuc2VuZChwYXlsb2FkKTsgc2VudCsrOyB9IGNhdGNoIHsgdGhpcy5yZW1vdmVDbGllbnQoY2xpZW50KTsgfVxuICAgIH1cbiAgICByZXR1cm4gc2VudDtcbiAgfVxuXG4gIGJyb2FkY2FzdCh0eXBlOiBzdHJpbmcsIGRhdGE6IHVua25vd24pOiBudW1iZXIge1xuICAgIGNvbnN0IHBheWxvYWQgPSBKU09OLnN0cmluZ2lmeSh7IHR5cGUsIGRhdGEsIHRzOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCkgfSk7XG4gICAgbGV0IHNlbnQgPSAwO1xuICAgIGZvciAoY29uc3QgWywgb3JnQ2xpZW50c10gb2YgdGhpcy5jbGllbnRzKSB7XG4gICAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBvcmdDbGllbnRzKSB7XG4gICAgICAgIHRyeSB7IGNsaWVudC5zZW5kKHBheWxvYWQpOyBzZW50Kys7IH0gY2F0Y2ggeyB0aGlzLnJlbW92ZUNsaWVudChjbGllbnQpOyB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBzZW50O1xuICB9XG5cbiAgZ2V0U3RhdHMoKTogeyBvcmdzOiBudW1iZXI7IGNsaWVudHM6IG51bWJlciB9IHtcbiAgICBsZXQgY2xpZW50cyA9IDA7XG4gICAgZm9yIChjb25zdCBbLCBvcmdDbGllbnRzXSBvZiB0aGlzLmNsaWVudHMpIGNsaWVudHMgKz0gb3JnQ2xpZW50cy5zaXplO1xuICAgIHJldHVybiB7IG9yZ3M6IHRoaXMuY2xpZW50cy5zaXplLCBjbGllbnRzIH07XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVdTTWFuYWdlcihvcHRpb25zPzogeyBtYXhDbGllbnRzUGVyT3JnPzogbnVtYmVyIH0pOiBXU01hbmFnZXIge1xuICByZXR1cm4gbmV3IFdTTWFuYWdlcihvcHRpb25zKTtcbn1cbiJdfQ==
package/lib/index.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @module @pipeline-builder/api-server
3
+ *
4
+ * Express server infrastructure and request lifecycle utilities.
5
+ *
6
+ * **App Factory**
7
+ * - createApp — creates a configured Express app with CORS, Helmet, rate limiting
8
+ * - runServer, startServer — server lifecycle with graceful shutdown
9
+ *
10
+ * **Middleware**
11
+ * - attachRequestContext / createRequestContext — attaches identity + logging to each request
12
+ * - requireOrgId — validates organization ID is present on the request
13
+ * - checkQuota — quota enforcement middleware
14
+ * - etagMiddleware — ETag-based conditional response support
15
+ * - idempotencyMiddleware — idempotent request handling
16
+ * - middlewareFactory — composable middleware builder
17
+ *
18
+ * **Route Helpers**
19
+ * - withRoute — wraps async route handlers with context extraction, orgId validation, and error handling
20
+ * - getContext — retrieves RequestContext from the Express request
21
+ * - RouteContext, RequestContext — route and request context types
22
+ *
23
+ * **SSE**
24
+ * - SSEManager — Server-Sent Events connection manager
25
+ *
26
+ * **Observability**
27
+ * - Tracing and metrics collection utilities
28
+ */
29
+ export * from './api';
30
+ export * from './http';
package/lib/index.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ /**
20
+ * @module @pipeline-builder/api-server
21
+ *
22
+ * Express server infrastructure and request lifecycle utilities.
23
+ *
24
+ * **App Factory**
25
+ * - createApp — creates a configured Express app with CORS, Helmet, rate limiting
26
+ * - runServer, startServer — server lifecycle with graceful shutdown
27
+ *
28
+ * **Middleware**
29
+ * - attachRequestContext / createRequestContext — attaches identity + logging to each request
30
+ * - requireOrgId — validates organization ID is present on the request
31
+ * - checkQuota — quota enforcement middleware
32
+ * - etagMiddleware — ETag-based conditional response support
33
+ * - idempotencyMiddleware — idempotent request handling
34
+ * - middlewareFactory — composable middleware builder
35
+ *
36
+ * **Route Helpers**
37
+ * - withRoute — wraps async route handlers with context extraction, orgId validation, and error handling
38
+ * - getContext — retrieves RequestContext from the Express request
39
+ * - RouteContext, RequestContext — route and request context types
40
+ *
41
+ * **SSE**
42
+ * - SSEManager — Server-Sent Events connection manager
43
+ *
44
+ * **Observability**
45
+ * - Tracing and metrics collection utilities
46
+ */
47
+ // API Infrastructure
48
+ __exportStar(require("./api"), exports);
49
+ // HTTP Utilities
50
+ __exportStar(require("./http"), exports);
51
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJCRztBQUVILHFCQUFxQjtBQUNyQix3Q0FBc0I7QUFFdEIsaUJBQWlCO0FBQ2pCLHlDQUF1QiIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIEBtb2R1bGUgQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLXNlcnZlclxuICpcbiAqIEV4cHJlc3Mgc2VydmVyIGluZnJhc3RydWN0dXJlIGFuZCByZXF1ZXN0IGxpZmVjeWNsZSB1dGlsaXRpZXMuXG4gKlxuICogKipBcHAgRmFjdG9yeSoqXG4gKiAtIGNyZWF0ZUFwcCDigJQgY3JlYXRlcyBhIGNvbmZpZ3VyZWQgRXhwcmVzcyBhcHAgd2l0aCBDT1JTLCBIZWxtZXQsIHJhdGUgbGltaXRpbmdcbiAqIC0gcnVuU2VydmVyLCBzdGFydFNlcnZlciDigJQgc2VydmVyIGxpZmVjeWNsZSB3aXRoIGdyYWNlZnVsIHNodXRkb3duXG4gKlxuICogKipNaWRkbGV3YXJlKipcbiAqIC0gYXR0YWNoUmVxdWVzdENvbnRleHQgLyBjcmVhdGVSZXF1ZXN0Q29udGV4dCDigJQgYXR0YWNoZXMgaWRlbnRpdHkgKyBsb2dnaW5nIHRvIGVhY2ggcmVxdWVzdFxuICogLSByZXF1aXJlT3JnSWQg4oCUIHZhbGlkYXRlcyBvcmdhbml6YXRpb24gSUQgaXMgcHJlc2VudCBvbiB0aGUgcmVxdWVzdFxuICogLSBjaGVja1F1b3RhIOKAlCBxdW90YSBlbmZvcmNlbWVudCBtaWRkbGV3YXJlXG4gKiAtIGV0YWdNaWRkbGV3YXJlIOKAlCBFVGFnLWJhc2VkIGNvbmRpdGlvbmFsIHJlc3BvbnNlIHN1cHBvcnRcbiAqIC0gaWRlbXBvdGVuY3lNaWRkbGV3YXJlIOKAlCBpZGVtcG90ZW50IHJlcXVlc3QgaGFuZGxpbmdcbiAqIC0gbWlkZGxld2FyZUZhY3Rvcnkg4oCUIGNvbXBvc2FibGUgbWlkZGxld2FyZSBidWlsZGVyXG4gKlxuICogKipSb3V0ZSBIZWxwZXJzKipcbiAqIC0gd2l0aFJvdXRlIOKAlCB3cmFwcyBhc3luYyByb3V0ZSBoYW5kbGVycyB3aXRoIGNvbnRleHQgZXh0cmFjdGlvbiwgb3JnSWQgdmFsaWRhdGlvbiwgYW5kIGVycm9yIGhhbmRsaW5nXG4gKiAtIGdldENvbnRleHQg4oCUIHJldHJpZXZlcyBSZXF1ZXN0Q29udGV4dCBmcm9tIHRoZSBFeHByZXNzIHJlcXVlc3RcbiAqIC0gUm91dGVDb250ZXh0LCBSZXF1ZXN0Q29udGV4dCDigJQgcm91dGUgYW5kIHJlcXVlc3QgY29udGV4dCB0eXBlc1xuICpcbiAqICoqU1NFKipcbiAqIC0gU1NFTWFuYWdlciDigJQgU2VydmVyLVNlbnQgRXZlbnRzIGNvbm5lY3Rpb24gbWFuYWdlclxuICpcbiAqICoqT2JzZXJ2YWJpbGl0eSoqXG4gKiAtIFRyYWNpbmcgYW5kIG1ldHJpY3MgY29sbGVjdGlvbiB1dGlsaXRpZXNcbiAqL1xuXG4vLyBBUEkgSW5mcmFzdHJ1Y3R1cmVcbmV4cG9ydCAqIGZyb20gJy4vYXBpJztcblxuLy8gSFRUUCBVdGlsaXRpZXNcbmV4cG9ydCAqIGZyb20gJy4vaHR0cCc7Il19