@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.
- package/LICENSE +202 -0
- package/README.md +39 -0
- package/lib/api/app-factory.d.ts +71 -0
- package/lib/api/app-factory.js +212 -0
- package/lib/api/check-quota.d.ts +13 -0
- package/lib/api/check-quota.js +67 -0
- package/lib/api/context-middleware.d.ts +34 -0
- package/lib/api/context-middleware.js +45 -0
- package/lib/api/etag-middleware.d.ts +7 -0
- package/lib/api/etag-middleware.js +37 -0
- package/lib/api/get-context.d.ts +22 -0
- package/lib/api/get-context.js +31 -0
- package/lib/api/health-checks.d.ts +25 -0
- package/lib/api/health-checks.js +36 -0
- package/lib/api/idempotency-middleware.d.ts +9 -0
- package/lib/api/idempotency-middleware.js +64 -0
- package/lib/api/index.d.ts +15 -0
- package/lib/api/index.js +43 -0
- package/lib/api/metrics.d.ts +12 -0
- package/lib/api/metrics.js +83 -0
- package/lib/api/middleware-factory.d.ts +47 -0
- package/lib/api/middleware-factory.js +66 -0
- package/lib/api/middleware.d.ts +1 -0
- package/lib/api/middleware.js +14 -0
- package/lib/api/quota-helpers.d.ts +23 -0
- package/lib/api/quota-helpers.js +25 -0
- package/lib/api/request-types.d.ts +55 -0
- package/lib/api/request-types.js +62 -0
- package/lib/api/require-org-id.d.ts +14 -0
- package/lib/api/require-org-id.js +31 -0
- package/lib/api/route-wrapper.d.ts +50 -0
- package/lib/api/route-wrapper.js +62 -0
- package/lib/api/server.d.ts +79 -0
- package/lib/api/server.js +144 -0
- package/lib/api/tracing.d.ts +15 -0
- package/lib/api/tracing.js +53 -0
- package/lib/http/index.d.ts +2 -0
- package/lib/http/index.js +21 -0
- package/lib/http/sse-connection-manager.d.ts +145 -0
- package/lib/http/sse-connection-manager.js +329 -0
- package/lib/http/ws-manager.d.ts +37 -0
- package/lib/http/ws-manager.js +105 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +51 -0
- 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
|