@parsrun/service 0.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/README.md +375 -0
- package/dist/client.d.ts +55 -0
- package/dist/client.js +1474 -0
- package/dist/client.js.map +1 -0
- package/dist/define.d.ts +82 -0
- package/dist/define.js +120 -0
- package/dist/define.js.map +1 -0
- package/dist/events/index.d.ts +285 -0
- package/dist/events/index.js +853 -0
- package/dist/events/index.js.map +1 -0
- package/dist/handler-CmiDUWZv.d.ts +204 -0
- package/dist/index-CVOAoJjZ.d.ts +268 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +3589 -0
- package/dist/index.js.map +1 -0
- package/dist/resilience/index.d.ts +197 -0
- package/dist/resilience/index.js +387 -0
- package/dist/resilience/index.js.map +1 -0
- package/dist/rpc/index.d.ts +5 -0
- package/dist/rpc/index.js +1175 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/serialization/index.d.ts +37 -0
- package/dist/serialization/index.js +320 -0
- package/dist/serialization/index.js.map +1 -0
- package/dist/server-DFE8n2Sx.d.ts +106 -0
- package/dist/tracing/index.d.ts +406 -0
- package/dist/tracing/index.js +820 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/transports/cloudflare/index.d.ts +237 -0
- package/dist/transports/cloudflare/index.js +746 -0
- package/dist/transports/cloudflare/index.js.map +1 -0
- package/dist/types-n4LLSPQU.d.ts +473 -0
- package/package.json +91 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
// src/events/format.ts
|
|
2
|
+
import { generateId } from "@parsrun/core";
|
|
3
|
+
function createEvent(options) {
|
|
4
|
+
const event = {
|
|
5
|
+
specversion: "1.0",
|
|
6
|
+
type: options.type,
|
|
7
|
+
source: options.source,
|
|
8
|
+
id: options.id ?? generateId(),
|
|
9
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10
|
+
datacontenttype: "application/json",
|
|
11
|
+
data: options.data
|
|
12
|
+
};
|
|
13
|
+
if (options.subject) event.subject = options.subject;
|
|
14
|
+
if (options.tenantId) event.parstenantid = options.tenantId;
|
|
15
|
+
if (options.requestId) event.parsrequestid = options.requestId;
|
|
16
|
+
if (options.traceContext) event.parstracecontext = formatTraceContext(options.traceContext);
|
|
17
|
+
if (options.delivery) event.parsdelivery = options.delivery;
|
|
18
|
+
return event;
|
|
19
|
+
}
|
|
20
|
+
function toCloudEvent(event) {
|
|
21
|
+
return { ...event };
|
|
22
|
+
}
|
|
23
|
+
function toCompactEvent(event) {
|
|
24
|
+
const compact = {
|
|
25
|
+
e: event.type,
|
|
26
|
+
s: event.source,
|
|
27
|
+
i: event.id,
|
|
28
|
+
t: new Date(event.time).getTime(),
|
|
29
|
+
d: event.data
|
|
30
|
+
};
|
|
31
|
+
if (event.parstracecontext) compact.ctx = event.parstracecontext;
|
|
32
|
+
if (event.parstenantid) compact.tid = event.parstenantid;
|
|
33
|
+
return compact;
|
|
34
|
+
}
|
|
35
|
+
function fromCompactEvent(compact, source) {
|
|
36
|
+
const event = {
|
|
37
|
+
specversion: "1.0",
|
|
38
|
+
type: compact.e,
|
|
39
|
+
source: source ?? compact.s,
|
|
40
|
+
id: compact.i,
|
|
41
|
+
time: new Date(compact.t).toISOString(),
|
|
42
|
+
datacontenttype: "application/json",
|
|
43
|
+
data: compact.d
|
|
44
|
+
};
|
|
45
|
+
if (compact.ctx) event.parstracecontext = compact.ctx;
|
|
46
|
+
if (compact.tid) event.parstenantid = compact.tid;
|
|
47
|
+
return event;
|
|
48
|
+
}
|
|
49
|
+
function formatEventType(source, type) {
|
|
50
|
+
return `com.pars.${source}.${type}`;
|
|
51
|
+
}
|
|
52
|
+
function parseEventType(fullType) {
|
|
53
|
+
const prefix = "com.pars.";
|
|
54
|
+
if (!fullType.startsWith(prefix)) {
|
|
55
|
+
const parts = fullType.split(".");
|
|
56
|
+
if (parts.length >= 2) {
|
|
57
|
+
const [source, ...rest] = parts;
|
|
58
|
+
return { source, type: rest.join(".") };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const withoutPrefix = fullType.slice(prefix.length);
|
|
63
|
+
const dotIndex = withoutPrefix.indexOf(".");
|
|
64
|
+
if (dotIndex === -1) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
source: withoutPrefix.slice(0, dotIndex),
|
|
69
|
+
type: withoutPrefix.slice(dotIndex + 1)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function matchEventType(type, pattern) {
|
|
73
|
+
if (pattern === "*" || pattern === "**") {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const typeParts = type.split(".");
|
|
77
|
+
const patternParts = pattern.split(".");
|
|
78
|
+
let ti = 0;
|
|
79
|
+
let pi = 0;
|
|
80
|
+
while (ti < typeParts.length && pi < patternParts.length) {
|
|
81
|
+
const pp = patternParts[pi];
|
|
82
|
+
if (pp === "**") {
|
|
83
|
+
if (pi === patternParts.length - 1) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
for (let i = ti; i <= typeParts.length; i++) {
|
|
87
|
+
const remaining = typeParts.slice(i).join(".");
|
|
88
|
+
const remainingPattern = patternParts.slice(pi + 1).join(".");
|
|
89
|
+
if (matchEventType(remaining, remainingPattern)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (pp === "*") {
|
|
96
|
+
ti++;
|
|
97
|
+
pi++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (pp !== typeParts[ti]) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
ti++;
|
|
104
|
+
pi++;
|
|
105
|
+
}
|
|
106
|
+
return ti === typeParts.length && pi === patternParts.length;
|
|
107
|
+
}
|
|
108
|
+
function formatTraceContext(ctx) {
|
|
109
|
+
const flags = ctx.traceFlags.toString(16).padStart(2, "0");
|
|
110
|
+
return `00-${ctx.traceId}-${ctx.spanId}-${flags}`;
|
|
111
|
+
}
|
|
112
|
+
function validateEvent(event) {
|
|
113
|
+
if (!event || typeof event !== "object") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const e = event;
|
|
117
|
+
if (e["specversion"] !== "1.0") return false;
|
|
118
|
+
if (typeof e["type"] !== "string" || e["type"].length === 0) return false;
|
|
119
|
+
if (typeof e["source"] !== "string" || e["source"].length === 0) return false;
|
|
120
|
+
if (typeof e["id"] !== "string" || e["id"].length === 0) return false;
|
|
121
|
+
if (typeof e["time"] !== "string") return false;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
function validateCompactEvent(event) {
|
|
125
|
+
if (!event || typeof event !== "object") {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const e = event;
|
|
129
|
+
if (typeof e["e"] !== "string" || e["e"].length === 0) return false;
|
|
130
|
+
if (typeof e["s"] !== "string" || e["s"].length === 0) return false;
|
|
131
|
+
if (typeof e["i"] !== "string" || e["i"].length === 0) return false;
|
|
132
|
+
if (typeof e["t"] !== "number") return false;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/events/emitter.ts
|
|
137
|
+
import { createLogger } from "@parsrun/core";
|
|
138
|
+
var EventEmitter = class {
|
|
139
|
+
service;
|
|
140
|
+
definition;
|
|
141
|
+
transport;
|
|
142
|
+
logger;
|
|
143
|
+
defaultTenantId;
|
|
144
|
+
validateEvents;
|
|
145
|
+
constructor(options) {
|
|
146
|
+
this.service = options.service;
|
|
147
|
+
if (options.definition) {
|
|
148
|
+
this.definition = options.definition;
|
|
149
|
+
}
|
|
150
|
+
this.transport = options.transport;
|
|
151
|
+
this.logger = options.logger ?? createLogger({ name: `events:${options.service}` });
|
|
152
|
+
if (options.defaultTenantId) {
|
|
153
|
+
this.defaultTenantId = options.defaultTenantId;
|
|
154
|
+
}
|
|
155
|
+
this.validateEvents = options.validateEvents ?? true;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Emit an event
|
|
159
|
+
*/
|
|
160
|
+
async emit(type, data, options) {
|
|
161
|
+
if (this.validateEvents && this.definition?.events?.emits) {
|
|
162
|
+
const emits = this.definition.events.emits;
|
|
163
|
+
if (!(type in emits)) {
|
|
164
|
+
this.logger.warn(`Event type not declared in service definition: ${type}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
let delivery;
|
|
168
|
+
if (this.definition?.events?.emits?.[type]) {
|
|
169
|
+
delivery = this.definition.events.emits[type].delivery;
|
|
170
|
+
}
|
|
171
|
+
const eventOptions = {
|
|
172
|
+
type,
|
|
173
|
+
source: this.service,
|
|
174
|
+
data
|
|
175
|
+
};
|
|
176
|
+
if (options?.eventId) eventOptions.id = options.eventId;
|
|
177
|
+
if (options?.subject) eventOptions.subject = options.subject;
|
|
178
|
+
const tenantId = options?.tenantId ?? this.defaultTenantId;
|
|
179
|
+
if (tenantId) eventOptions.tenantId = tenantId;
|
|
180
|
+
if (options?.requestId) eventOptions.requestId = options.requestId;
|
|
181
|
+
if (options?.traceContext) eventOptions.traceContext = options.traceContext;
|
|
182
|
+
const eventDelivery = options?.delivery ?? delivery;
|
|
183
|
+
if (eventDelivery) eventOptions.delivery = eventDelivery;
|
|
184
|
+
const event = createEvent(eventOptions);
|
|
185
|
+
try {
|
|
186
|
+
await this.transport.emit(event);
|
|
187
|
+
this.logger.debug(`Event emitted: ${type}`, {
|
|
188
|
+
eventId: event.id,
|
|
189
|
+
tenantId: event.parstenantid
|
|
190
|
+
});
|
|
191
|
+
return event.id;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger.error(`Failed to emit event: ${type}`, error, {
|
|
194
|
+
eventId: event.id
|
|
195
|
+
});
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Emit multiple events
|
|
201
|
+
*/
|
|
202
|
+
async emitBatch(events) {
|
|
203
|
+
const results = [];
|
|
204
|
+
for (const { type, data, options } of events) {
|
|
205
|
+
const eventId = await this.emit(type, data, options);
|
|
206
|
+
results.push(eventId);
|
|
207
|
+
}
|
|
208
|
+
return results;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Create a scoped emitter with preset options
|
|
212
|
+
*/
|
|
213
|
+
scoped(options) {
|
|
214
|
+
return new ScopedEmitter(this, options);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get service name
|
|
218
|
+
*/
|
|
219
|
+
get serviceName() {
|
|
220
|
+
return this.service;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var ScopedEmitter = class {
|
|
224
|
+
emitter;
|
|
225
|
+
defaultOptions;
|
|
226
|
+
constructor(emitter, defaultOptions) {
|
|
227
|
+
this.emitter = emitter;
|
|
228
|
+
this.defaultOptions = defaultOptions;
|
|
229
|
+
}
|
|
230
|
+
async emit(type, data, options) {
|
|
231
|
+
return this.emitter.emit(type, data, {
|
|
232
|
+
...this.defaultOptions,
|
|
233
|
+
...options
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
function createEventEmitter(options) {
|
|
238
|
+
return new EventEmitter(options);
|
|
239
|
+
}
|
|
240
|
+
function createTypedEmitter(definition, options) {
|
|
241
|
+
const emitter = new EventEmitter({
|
|
242
|
+
...options,
|
|
243
|
+
service: definition.name,
|
|
244
|
+
definition
|
|
245
|
+
});
|
|
246
|
+
return emitter;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/events/handler.ts
|
|
250
|
+
import { createLogger as createLogger2 } from "@parsrun/core";
|
|
251
|
+
var EventHandlerRegistry = class {
|
|
252
|
+
handlers = /* @__PURE__ */ new Map();
|
|
253
|
+
logger;
|
|
254
|
+
deadLetterQueue;
|
|
255
|
+
defaultOptions;
|
|
256
|
+
constructor(options = {}) {
|
|
257
|
+
this.logger = options.logger ?? createLogger2({ name: "event-handler" });
|
|
258
|
+
if (options.deadLetterQueue) {
|
|
259
|
+
this.deadLetterQueue = options.deadLetterQueue;
|
|
260
|
+
}
|
|
261
|
+
const defaultOpts = {
|
|
262
|
+
retries: options.defaultOptions?.retries ?? 3,
|
|
263
|
+
backoff: options.defaultOptions?.backoff ?? "exponential",
|
|
264
|
+
maxDelay: options.defaultOptions?.maxDelay ?? 3e4,
|
|
265
|
+
onExhausted: options.defaultOptions?.onExhausted ?? "log"
|
|
266
|
+
};
|
|
267
|
+
if (options.defaultOptions?.deadLetter) {
|
|
268
|
+
defaultOpts.deadLetter = options.defaultOptions.deadLetter;
|
|
269
|
+
}
|
|
270
|
+
this.defaultOptions = defaultOpts;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Register an event handler
|
|
274
|
+
*/
|
|
275
|
+
register(pattern, handler, options) {
|
|
276
|
+
const registration = {
|
|
277
|
+
pattern,
|
|
278
|
+
handler,
|
|
279
|
+
options: {
|
|
280
|
+
...this.defaultOptions,
|
|
281
|
+
...options
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const handlers = this.handlers.get(pattern) ?? [];
|
|
285
|
+
handlers.push(registration);
|
|
286
|
+
this.handlers.set(pattern, handlers);
|
|
287
|
+
this.logger.debug(`Handler registered for pattern: ${pattern}`);
|
|
288
|
+
return () => {
|
|
289
|
+
const currentHandlers = this.handlers.get(pattern);
|
|
290
|
+
if (currentHandlers) {
|
|
291
|
+
const index = currentHandlers.indexOf(registration);
|
|
292
|
+
if (index !== -1) {
|
|
293
|
+
currentHandlers.splice(index, 1);
|
|
294
|
+
if (currentHandlers.length === 0) {
|
|
295
|
+
this.handlers.delete(pattern);
|
|
296
|
+
}
|
|
297
|
+
this.logger.debug(`Handler unregistered for pattern: ${pattern}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Handle an event
|
|
304
|
+
*/
|
|
305
|
+
async handle(event) {
|
|
306
|
+
const matchingHandlers = this.getMatchingHandlers(event.type);
|
|
307
|
+
if (matchingHandlers.length === 0) {
|
|
308
|
+
this.logger.debug(`No handlers for event type: ${event.type}`, {
|
|
309
|
+
eventId: event.id
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
this.logger.debug(`Handling event: ${event.type}`, {
|
|
314
|
+
eventId: event.id,
|
|
315
|
+
handlerCount: matchingHandlers.length
|
|
316
|
+
});
|
|
317
|
+
const results = await Promise.allSettled(
|
|
318
|
+
matchingHandlers.map((reg) => this.executeHandler(event, reg))
|
|
319
|
+
);
|
|
320
|
+
for (let i = 0; i < results.length; i++) {
|
|
321
|
+
const result = results[i];
|
|
322
|
+
if (result?.status === "rejected") {
|
|
323
|
+
this.logger.error(
|
|
324
|
+
`Handler failed for ${event.type}`,
|
|
325
|
+
result.reason,
|
|
326
|
+
{ eventId: event.id, pattern: matchingHandlers[i]?.pattern }
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Execute a single handler with retry logic
|
|
333
|
+
*/
|
|
334
|
+
async executeHandler(event, registration) {
|
|
335
|
+
const { handler, options } = registration;
|
|
336
|
+
const maxAttempts = options.retries + 1;
|
|
337
|
+
let lastError;
|
|
338
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
339
|
+
try {
|
|
340
|
+
const context = {
|
|
341
|
+
logger: this.logger.child({
|
|
342
|
+
eventId: event.id,
|
|
343
|
+
pattern: registration.pattern,
|
|
344
|
+
attempt
|
|
345
|
+
}),
|
|
346
|
+
attempt,
|
|
347
|
+
maxAttempts,
|
|
348
|
+
isRetry: attempt > 1
|
|
349
|
+
};
|
|
350
|
+
if (event.parstracecontext) {
|
|
351
|
+
const traceCtx = parseTraceContext(event.parstracecontext);
|
|
352
|
+
if (traceCtx) {
|
|
353
|
+
context.traceContext = traceCtx;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
await handler(event, context);
|
|
357
|
+
return;
|
|
358
|
+
} catch (error) {
|
|
359
|
+
lastError = error;
|
|
360
|
+
if (attempt < maxAttempts) {
|
|
361
|
+
const delay = this.calculateBackoff(attempt, options);
|
|
362
|
+
this.logger.warn(
|
|
363
|
+
`Handler failed, retrying in ${delay}ms`,
|
|
364
|
+
{ eventId: event.id, attempt, maxAttempts }
|
|
365
|
+
);
|
|
366
|
+
await sleep(delay);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
await this.handleExhausted(event, registration, lastError);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Calculate backoff delay
|
|
374
|
+
*/
|
|
375
|
+
calculateBackoff(attempt, options) {
|
|
376
|
+
const baseDelay = 100;
|
|
377
|
+
if (options.backoff === "exponential") {
|
|
378
|
+
return Math.min(baseDelay * Math.pow(2, attempt - 1), options.maxDelay);
|
|
379
|
+
}
|
|
380
|
+
return Math.min(baseDelay * attempt, options.maxDelay);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Handle exhausted retries
|
|
384
|
+
*/
|
|
385
|
+
async handleExhausted(event, registration, error) {
|
|
386
|
+
const { options } = registration;
|
|
387
|
+
if (options.deadLetter && this.deadLetterQueue) {
|
|
388
|
+
await this.deadLetterQueue.add({
|
|
389
|
+
event,
|
|
390
|
+
error: error.message,
|
|
391
|
+
pattern: registration.pattern,
|
|
392
|
+
attempts: options.retries + 1
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
switch (options.onExhausted) {
|
|
396
|
+
case "alert":
|
|
397
|
+
this.logger.error(
|
|
398
|
+
`[ALERT] Event handler exhausted all retries`,
|
|
399
|
+
error,
|
|
400
|
+
{
|
|
401
|
+
eventId: event.id,
|
|
402
|
+
eventType: event.type,
|
|
403
|
+
pattern: registration.pattern
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
break;
|
|
407
|
+
case "discard":
|
|
408
|
+
this.logger.debug(`Event discarded after exhausted retries`, {
|
|
409
|
+
eventId: event.id
|
|
410
|
+
});
|
|
411
|
+
break;
|
|
412
|
+
case "log":
|
|
413
|
+
default:
|
|
414
|
+
this.logger.warn(`Event handler exhausted all retries`, {
|
|
415
|
+
eventId: event.id,
|
|
416
|
+
error: error.message
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get handlers matching an event type
|
|
422
|
+
*/
|
|
423
|
+
getMatchingHandlers(eventType) {
|
|
424
|
+
const matching = [];
|
|
425
|
+
for (const [pattern, handlers] of this.handlers) {
|
|
426
|
+
if (matchEventType(eventType, pattern)) {
|
|
427
|
+
matching.push(...handlers);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return matching;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Get all registered patterns
|
|
434
|
+
*/
|
|
435
|
+
getPatterns() {
|
|
436
|
+
return Array.from(this.handlers.keys());
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Check if a pattern has handlers
|
|
440
|
+
*/
|
|
441
|
+
hasHandlers(pattern) {
|
|
442
|
+
return this.handlers.has(pattern);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Clear all handlers
|
|
446
|
+
*/
|
|
447
|
+
clear() {
|
|
448
|
+
this.handlers.clear();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
function createEventHandlerRegistry(options) {
|
|
452
|
+
return new EventHandlerRegistry(options);
|
|
453
|
+
}
|
|
454
|
+
function sleep(ms) {
|
|
455
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
456
|
+
}
|
|
457
|
+
function parseTraceContext(traceparent) {
|
|
458
|
+
const parts = traceparent.split("-");
|
|
459
|
+
if (parts.length !== 4) return void 0;
|
|
460
|
+
const [, traceId, spanId, flags] = parts;
|
|
461
|
+
if (!traceId || !spanId || !flags) return void 0;
|
|
462
|
+
return {
|
|
463
|
+
traceId,
|
|
464
|
+
spanId,
|
|
465
|
+
traceFlags: parseInt(flags, 16)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/events/transports/memory.ts
|
|
470
|
+
import { createLogger as createLogger3 } from "@parsrun/core";
|
|
471
|
+
var MemoryEventTransport = class {
|
|
472
|
+
name = "memory";
|
|
473
|
+
registry;
|
|
474
|
+
logger;
|
|
475
|
+
sync;
|
|
476
|
+
pendingEvents = [];
|
|
477
|
+
processing = false;
|
|
478
|
+
constructor(options = {}) {
|
|
479
|
+
this.logger = options.logger ?? createLogger3({ name: "memory-transport" });
|
|
480
|
+
this.sync = options.sync ?? false;
|
|
481
|
+
const registryOptions = {
|
|
482
|
+
logger: this.logger
|
|
483
|
+
};
|
|
484
|
+
if (options.defaultHandlerOptions) {
|
|
485
|
+
registryOptions.defaultOptions = options.defaultHandlerOptions;
|
|
486
|
+
}
|
|
487
|
+
this.registry = new EventHandlerRegistry(registryOptions);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Emit an event
|
|
491
|
+
*/
|
|
492
|
+
async emit(event) {
|
|
493
|
+
this.logger.debug(`Event emitted: ${event.type}`, {
|
|
494
|
+
eventId: event.id,
|
|
495
|
+
tenantId: event.parstenantid
|
|
496
|
+
});
|
|
497
|
+
if (this.sync) {
|
|
498
|
+
await this.registry.handle(event);
|
|
499
|
+
} else {
|
|
500
|
+
this.pendingEvents.push(event);
|
|
501
|
+
this.processQueue();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Subscribe to events
|
|
506
|
+
*/
|
|
507
|
+
subscribe(eventType, handler, options) {
|
|
508
|
+
return this.registry.register(eventType, handler, options);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Process pending events asynchronously
|
|
512
|
+
*/
|
|
513
|
+
async processQueue() {
|
|
514
|
+
if (this.processing) return;
|
|
515
|
+
this.processing = true;
|
|
516
|
+
try {
|
|
517
|
+
while (this.pendingEvents.length > 0) {
|
|
518
|
+
const event = this.pendingEvents.shift();
|
|
519
|
+
if (event) {
|
|
520
|
+
await this.registry.handle(event);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} finally {
|
|
524
|
+
this.processing = false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Wait for all pending events to be processed
|
|
529
|
+
*/
|
|
530
|
+
async flush() {
|
|
531
|
+
while (this.pendingEvents.length > 0 || this.processing) {
|
|
532
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Get pending event count
|
|
537
|
+
*/
|
|
538
|
+
get pendingCount() {
|
|
539
|
+
return this.pendingEvents.length;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Get registered patterns
|
|
543
|
+
*/
|
|
544
|
+
getPatterns() {
|
|
545
|
+
return this.registry.getPatterns();
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Clear all subscriptions
|
|
549
|
+
*/
|
|
550
|
+
clear() {
|
|
551
|
+
this.registry.clear();
|
|
552
|
+
this.pendingEvents.length = 0;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Close the transport
|
|
556
|
+
*/
|
|
557
|
+
async close() {
|
|
558
|
+
await this.flush();
|
|
559
|
+
this.clear();
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
function createMemoryEventTransport(options) {
|
|
563
|
+
return new MemoryEventTransport(options);
|
|
564
|
+
}
|
|
565
|
+
var GlobalEventBus = class _GlobalEventBus {
|
|
566
|
+
static instance = null;
|
|
567
|
+
transports = /* @__PURE__ */ new Map();
|
|
568
|
+
logger;
|
|
569
|
+
constructor() {
|
|
570
|
+
this.logger = createLogger3({ name: "global-event-bus" });
|
|
571
|
+
}
|
|
572
|
+
static getInstance() {
|
|
573
|
+
if (!_GlobalEventBus.instance) {
|
|
574
|
+
_GlobalEventBus.instance = new _GlobalEventBus();
|
|
575
|
+
}
|
|
576
|
+
return _GlobalEventBus.instance;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Register a service's event transport
|
|
580
|
+
*/
|
|
581
|
+
register(serviceName, transport) {
|
|
582
|
+
if (this.transports.has(serviceName)) {
|
|
583
|
+
throw new Error(`Service already registered: ${serviceName}`);
|
|
584
|
+
}
|
|
585
|
+
this.transports.set(serviceName, transport);
|
|
586
|
+
this.logger.debug(`Service registered: ${serviceName}`);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Unregister a service
|
|
590
|
+
*/
|
|
591
|
+
unregister(serviceName) {
|
|
592
|
+
const deleted = this.transports.delete(serviceName);
|
|
593
|
+
if (deleted) {
|
|
594
|
+
this.logger.debug(`Service unregistered: ${serviceName}`);
|
|
595
|
+
}
|
|
596
|
+
return deleted;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Broadcast an event to all services (except source)
|
|
600
|
+
*/
|
|
601
|
+
async broadcast(event, excludeSource) {
|
|
602
|
+
const promises = [];
|
|
603
|
+
for (const [name, transport] of this.transports) {
|
|
604
|
+
if (name !== excludeSource) {
|
|
605
|
+
promises.push(transport.emit(event));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
await Promise.allSettled(promises);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Send an event to a specific service
|
|
612
|
+
*/
|
|
613
|
+
async send(serviceName, event) {
|
|
614
|
+
const transport = this.transports.get(serviceName);
|
|
615
|
+
if (!transport) {
|
|
616
|
+
this.logger.warn(`Target service not found: ${serviceName}`, {
|
|
617
|
+
eventId: event.id
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
await transport.emit(event);
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Get all registered service names
|
|
625
|
+
*/
|
|
626
|
+
getServices() {
|
|
627
|
+
return Array.from(this.transports.keys());
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Clear all registrations
|
|
631
|
+
*/
|
|
632
|
+
clear() {
|
|
633
|
+
this.transports.clear();
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Reset singleton (for testing)
|
|
637
|
+
*/
|
|
638
|
+
static reset() {
|
|
639
|
+
_GlobalEventBus.instance = null;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
function getGlobalEventBus() {
|
|
643
|
+
return GlobalEventBus.getInstance();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/events/dead-letter.ts
|
|
647
|
+
import { createLogger as createLogger4, generateId as generateId2 } from "@parsrun/core";
|
|
648
|
+
var DeadLetterQueue = class {
|
|
649
|
+
entries = /* @__PURE__ */ new Map();
|
|
650
|
+
resolvedOptions;
|
|
651
|
+
logger;
|
|
652
|
+
cleanupTimer = null;
|
|
653
|
+
constructor(options = {}) {
|
|
654
|
+
this.resolvedOptions = {
|
|
655
|
+
maxSize: options.maxSize ?? 1e3,
|
|
656
|
+
retentionMs: options.retentionMs ?? 30 * 24 * 60 * 60 * 1e3,
|
|
657
|
+
// 30 days
|
|
658
|
+
alertThreshold: options.alertThreshold ?? 10
|
|
659
|
+
};
|
|
660
|
+
if (options.onAdd) this.resolvedOptions.onAdd = options.onAdd;
|
|
661
|
+
if (options.onThreshold) this.resolvedOptions.onThreshold = options.onThreshold;
|
|
662
|
+
if (options.logger) this.resolvedOptions.logger = options.logger;
|
|
663
|
+
this.logger = options.logger ?? createLogger4({ name: "dlq" });
|
|
664
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), 60 * 60 * 1e3);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Add an entry to the DLQ
|
|
668
|
+
*/
|
|
669
|
+
async add(options) {
|
|
670
|
+
const entry = {
|
|
671
|
+
id: generateId2(),
|
|
672
|
+
event: options.event,
|
|
673
|
+
error: options.error,
|
|
674
|
+
pattern: options.pattern,
|
|
675
|
+
attempts: options.attempts,
|
|
676
|
+
addedAt: /* @__PURE__ */ new Date()
|
|
677
|
+
};
|
|
678
|
+
if (options.metadata) {
|
|
679
|
+
entry.metadata = options.metadata;
|
|
680
|
+
}
|
|
681
|
+
if (this.entries.size >= this.resolvedOptions.maxSize) {
|
|
682
|
+
const oldest = this.getOldest();
|
|
683
|
+
if (oldest) {
|
|
684
|
+
this.entries.delete(oldest.id);
|
|
685
|
+
this.logger.debug(`DLQ: Removed oldest entry to make room`, {
|
|
686
|
+
removedId: oldest.id
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
this.entries.set(entry.id, entry);
|
|
691
|
+
this.logger.warn(`DLQ: Entry added`, {
|
|
692
|
+
id: entry.id,
|
|
693
|
+
eventId: entry.event.id,
|
|
694
|
+
eventType: entry.event.type,
|
|
695
|
+
error: entry.error
|
|
696
|
+
});
|
|
697
|
+
this.resolvedOptions.onAdd?.(entry);
|
|
698
|
+
if (this.entries.size >= this.resolvedOptions.alertThreshold) {
|
|
699
|
+
this.resolvedOptions.onThreshold?.(this.entries.size);
|
|
700
|
+
}
|
|
701
|
+
return entry.id;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get an entry by ID
|
|
705
|
+
*/
|
|
706
|
+
get(id) {
|
|
707
|
+
return this.entries.get(id);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Get all entries
|
|
711
|
+
*/
|
|
712
|
+
getAll() {
|
|
713
|
+
return Array.from(this.entries.values());
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Get entries by event type
|
|
717
|
+
*/
|
|
718
|
+
getByEventType(eventType) {
|
|
719
|
+
return Array.from(this.entries.values()).filter(
|
|
720
|
+
(e) => e.event.type === eventType
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Get entries by pattern
|
|
725
|
+
*/
|
|
726
|
+
getByPattern(pattern) {
|
|
727
|
+
return Array.from(this.entries.values()).filter(
|
|
728
|
+
(e) => e.pattern === pattern
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Remove an entry
|
|
733
|
+
*/
|
|
734
|
+
remove(id) {
|
|
735
|
+
const deleted = this.entries.delete(id);
|
|
736
|
+
if (deleted) {
|
|
737
|
+
this.logger.debug(`DLQ: Entry removed`, { id });
|
|
738
|
+
}
|
|
739
|
+
return deleted;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Retry an entry (remove from DLQ and return event)
|
|
743
|
+
*/
|
|
744
|
+
retry(id) {
|
|
745
|
+
const entry = this.entries.get(id);
|
|
746
|
+
if (!entry) return void 0;
|
|
747
|
+
this.entries.delete(id);
|
|
748
|
+
this.logger.info(`DLQ: Entry removed for retry`, {
|
|
749
|
+
id,
|
|
750
|
+
eventId: entry.event.id
|
|
751
|
+
});
|
|
752
|
+
return entry.event;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Get count
|
|
756
|
+
*/
|
|
757
|
+
get size() {
|
|
758
|
+
return this.entries.size;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Clear all entries
|
|
762
|
+
*/
|
|
763
|
+
clear() {
|
|
764
|
+
this.entries.clear();
|
|
765
|
+
this.logger.info(`DLQ: Cleared all entries`);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Cleanup expired entries
|
|
769
|
+
*/
|
|
770
|
+
cleanup() {
|
|
771
|
+
const now = Date.now();
|
|
772
|
+
let removed = 0;
|
|
773
|
+
for (const [id, entry] of this.entries) {
|
|
774
|
+
const age = now - entry.addedAt.getTime();
|
|
775
|
+
if (age > this.resolvedOptions.retentionMs) {
|
|
776
|
+
this.entries.delete(id);
|
|
777
|
+
removed++;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (removed > 0) {
|
|
781
|
+
this.logger.debug(`DLQ: Cleaned up ${removed} expired entries`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Get oldest entry
|
|
786
|
+
*/
|
|
787
|
+
getOldest() {
|
|
788
|
+
let oldest;
|
|
789
|
+
for (const entry of this.entries.values()) {
|
|
790
|
+
if (!oldest || entry.addedAt < oldest.addedAt) {
|
|
791
|
+
oldest = entry;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return oldest;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Stop cleanup timer
|
|
798
|
+
*/
|
|
799
|
+
close() {
|
|
800
|
+
if (this.cleanupTimer) {
|
|
801
|
+
clearInterval(this.cleanupTimer);
|
|
802
|
+
this.cleanupTimer = null;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Export entries for persistence
|
|
807
|
+
*/
|
|
808
|
+
export() {
|
|
809
|
+
return Array.from(this.entries.values()).map((e) => ({
|
|
810
|
+
...e,
|
|
811
|
+
addedAt: e.addedAt
|
|
812
|
+
}));
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Import entries from persistence
|
|
816
|
+
*/
|
|
817
|
+
import(entries) {
|
|
818
|
+
for (const entry of entries) {
|
|
819
|
+
this.entries.set(entry.id, {
|
|
820
|
+
...entry,
|
|
821
|
+
addedAt: new Date(entry.addedAt)
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
this.logger.info(`DLQ: Imported ${entries.length} entries`);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
function createDeadLetterQueue(options) {
|
|
828
|
+
return new DeadLetterQueue(options);
|
|
829
|
+
}
|
|
830
|
+
export {
|
|
831
|
+
DeadLetterQueue,
|
|
832
|
+
EventEmitter,
|
|
833
|
+
EventHandlerRegistry,
|
|
834
|
+
GlobalEventBus,
|
|
835
|
+
MemoryEventTransport,
|
|
836
|
+
ScopedEmitter,
|
|
837
|
+
createDeadLetterQueue,
|
|
838
|
+
createEvent,
|
|
839
|
+
createEventEmitter,
|
|
840
|
+
createEventHandlerRegistry,
|
|
841
|
+
createMemoryEventTransport,
|
|
842
|
+
createTypedEmitter,
|
|
843
|
+
formatEventType,
|
|
844
|
+
fromCompactEvent,
|
|
845
|
+
getGlobalEventBus,
|
|
846
|
+
matchEventType,
|
|
847
|
+
parseEventType,
|
|
848
|
+
toCloudEvent,
|
|
849
|
+
toCompactEvent,
|
|
850
|
+
validateCompactEvent,
|
|
851
|
+
validateEvent
|
|
852
|
+
};
|
|
853
|
+
//# sourceMappingURL=index.js.map
|