@classytic/arc 1.1.0 → 2.1.3
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 +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
- package/dist/HookSystem-BsGV-j2l.mjs +404 -0
- package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +81 -0
- package/dist/audit/index.mjs +275 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-CGdLiSlE.mjs +140 -0
- package/dist/auth/index.d.mts +188 -0
- package/dist/auth/index.mjs +1096 -0
- package/dist/auth/redis-session.d.mts +43 -0
- package/dist/auth/redis-session.mjs +75 -0
- package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
- package/dist/cache/index.d.mts +145 -0
- package/dist/cache/index.mjs +91 -0
- package/dist/caching-GSDJcA6-.mjs +93 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
- package/dist/cli/commands/describe.d.mts +18 -0
- package/dist/cli/commands/describe.mjs +238 -0
- package/dist/cli/commands/docs.d.mts +13 -0
- package/dist/cli/commands/docs.mjs +52 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
- package/dist/cli/commands/generate.mjs +357 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
- package/dist/cli/commands/{init.js → init.mjs} +807 -617
- package/dist/cli/commands/introspect.d.mts +10 -0
- package/dist/cli/commands/introspect.mjs +75 -0
- package/dist/cli/index.d.mts +16 -0
- package/dist/cli/index.mjs +156 -0
- package/dist/constants-DdXFXQtN.mjs +84 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-D2D5XXaV.mjs +559 -0
- package/dist/defineResource-PXzSJ15_.mjs +2197 -0
- package/dist/discovery/index.d.mts +46 -0
- package/dist/discovery/index.mjs +109 -0
- package/dist/docs/index.d.mts +162 -0
- package/dist/docs/index.mjs +74 -0
- package/dist/elevation-DGo5shaX.d.mts +87 -0
- package/dist/elevation-DSTbVvYj.mjs +113 -0
- package/dist/errorHandler-C3GY3_ow.mjs +108 -0
- package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
- package/dist/errors-DAWRdiYP.d.mts +124 -0
- package/dist/errors-DBANPbGr.mjs +211 -0
- package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
- package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
- package/dist/events/index.d.mts +53 -0
- package/dist/events/index.mjs +51 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +177 -0
- package/dist/events/transports/redis.d.mts +76 -0
- package/dist/events/transports/redis.mjs +124 -0
- package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
- package/dist/factory/index.d.mts +63 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
- package/dist/fields-Bi_AVKSo.d.mts +109 -0
- package/dist/fields-CTd_CrKr.mjs +114 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +96 -0
- package/dist/idempotency/index.mjs +319 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +114 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +103 -0
- package/dist/index.d.mts +260 -0
- package/dist/index.mjs +104 -0
- package/dist/integrations/event-gateway.d.mts +46 -0
- package/dist/integrations/event-gateway.mjs +43 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +103 -0
- package/dist/integrations/jobs.mjs +123 -0
- package/dist/integrations/streamline.d.mts +60 -0
- package/dist/integrations/streamline.mjs +125 -0
- package/dist/integrations/websocket.d.mts +82 -0
- package/dist/integrations/websocket.mjs +288 -0
- package/dist/interface-CSNjltAc.d.mts +77 -0
- package/dist/interface-DTbsvIWe.d.mts +54 -0
- package/dist/interface-e9XfSsUV.d.mts +1097 -0
- package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
- package/dist/keys-DhqDRxv3.mjs +42 -0
- package/dist/logger-ByrvQWZO.mjs +78 -0
- package/dist/memory-B2v7KrCB.mjs +143 -0
- package/dist/migrations/index.d.mts +156 -0
- package/dist/migrations/index.mjs +260 -0
- package/dist/mongodb-ClykrfGo.d.mts +118 -0
- package/dist/mongodb-DNKEExbf.mjs +93 -0
- package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
- package/dist/openapi-9nB_kiuR.mjs +525 -0
- package/dist/org/index.d.mts +68 -0
- package/dist/org/index.mjs +513 -0
- package/dist/org/types.d.mts +82 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +278 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/plugins/index.d.mts +172 -0
- package/dist/plugins/index.mjs +522 -0
- package/dist/plugins/response-cache.d.mts +87 -0
- package/dist/plugins/response-cache.mjs +283 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +185 -0
- package/dist/pluralize-CM-jZg7p.mjs +86 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -170
- package/dist/policies/index.mjs +321 -0
- package/dist/presets/{index.d.ts → index.d.mts} +62 -131
- package/dist/presets/index.mjs +143 -0
- package/dist/presets/multiTenant.d.mts +24 -0
- package/dist/presets/multiTenant.mjs +113 -0
- package/dist/presets-BTeYbw7h.d.mts +57 -0
- package/dist/presets-CeFtfDR8.mjs +119 -0
- package/dist/prisma-C3iornoK.d.mts +274 -0
- package/dist/prisma-DJbMt3yf.mjs +627 -0
- package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
- package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
- package/dist/redis-UwjEp8Ea.d.mts +49 -0
- package/dist/redis-stream-CBg0upHI.d.mts +103 -0
- package/dist/registry/index.d.mts +11 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-xi6OKBL-.mjs +55 -0
- package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
- package/dist/schemas/index.d.mts +63 -0
- package/dist/schemas/index.mjs +82 -0
- package/dist/scope/index.d.mts +21 -0
- package/dist/scope/index.mjs +65 -0
- package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
- package/dist/sse-DkqQ1uxb.mjs +123 -0
- package/dist/testing/index.d.mts +907 -0
- package/dist/testing/index.mjs +1976 -0
- package/dist/tracing-8CEbhF0w.d.mts +70 -0
- package/dist/typeGuards-DwxA1t_L.mjs +9 -0
- package/dist/types/index.d.mts +946 -0
- package/dist/types/index.mjs +14 -0
- package/dist/types-B0dhNrnd.d.mts +445 -0
- package/dist/types-Beqn1Un7.mjs +38 -0
- package/dist/types-DelU6kln.mjs +25 -0
- package/dist/types-RLkFVgaw.d.mts +101 -0
- package/dist/utils/index.d.mts +747 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
|
|
2
|
+
import { t as requestContext } from "./requestContext-xi6OKBL-.mjs";
|
|
3
|
+
import fp from "fastify-plugin";
|
|
4
|
+
|
|
5
|
+
//#region src/events/EventTransport.ts
|
|
6
|
+
/**
|
|
7
|
+
* In-memory event transport (default)
|
|
8
|
+
* Events are delivered synchronously within the process.
|
|
9
|
+
* Not suitable for multi-instance deployments.
|
|
10
|
+
*/
|
|
11
|
+
var MemoryEventTransport = class {
|
|
12
|
+
name = "memory";
|
|
13
|
+
handlers = /* @__PURE__ */ new Map();
|
|
14
|
+
logger;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.logger = options?.logger ?? console;
|
|
17
|
+
}
|
|
18
|
+
async publish(event) {
|
|
19
|
+
const exactHandlers = this.handlers.get(event.type) ?? /* @__PURE__ */ new Set();
|
|
20
|
+
const wildcardHandlers = this.handlers.get("*") ?? /* @__PURE__ */ new Set();
|
|
21
|
+
const patternHandlers = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const [pattern, handlers] of this.handlers.entries()) if (pattern.endsWith(".*")) {
|
|
23
|
+
const prefix = pattern.slice(0, -2);
|
|
24
|
+
if (event.type.startsWith(prefix + ".")) handlers.forEach((h) => patternHandlers.add(h));
|
|
25
|
+
}
|
|
26
|
+
const allHandlers = new Set([
|
|
27
|
+
...exactHandlers,
|
|
28
|
+
...wildcardHandlers,
|
|
29
|
+
...patternHandlers
|
|
30
|
+
]);
|
|
31
|
+
for (const handler of allHandlers) try {
|
|
32
|
+
await handler(event);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
this.logger.error(`[EventTransport] Handler error for ${event.type}:`, err);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async subscribe(pattern, handler) {
|
|
38
|
+
if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
|
|
39
|
+
this.handlers.get(pattern).add(handler);
|
|
40
|
+
return () => {
|
|
41
|
+
this.handlers.get(pattern)?.delete(handler);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async close() {
|
|
45
|
+
this.handlers.clear();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create a domain event with auto-generated metadata
|
|
50
|
+
*/
|
|
51
|
+
function createEvent(type, payload, meta) {
|
|
52
|
+
return {
|
|
53
|
+
type,
|
|
54
|
+
payload,
|
|
55
|
+
meta: {
|
|
56
|
+
id: crypto.randomUUID(),
|
|
57
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
58
|
+
...meta
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/events/retry.ts
|
|
65
|
+
/**
|
|
66
|
+
* Wrap an event handler with retry logic and dead letter support.
|
|
67
|
+
*
|
|
68
|
+
* On failure, retries with exponential backoff (with jitter).
|
|
69
|
+
* After all retries exhausted, calls `onDead` callback if provided.
|
|
70
|
+
*/
|
|
71
|
+
function withRetry(handler, options = {}) {
|
|
72
|
+
const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, onDead, name, logger = console } = options;
|
|
73
|
+
const label = name ?? handler.name ?? "anonymous";
|
|
74
|
+
return async (event) => {
|
|
75
|
+
const errors = [];
|
|
76
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
77
|
+
await handler(event);
|
|
78
|
+
return;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
81
|
+
errors.push(error);
|
|
82
|
+
if (attempt < maxRetries) {
|
|
83
|
+
const baseDelay = Math.min(backoffMs * 2 ** attempt, maxBackoffMs);
|
|
84
|
+
const delay = baseDelay + jitter * baseDelay * Math.random();
|
|
85
|
+
logger.warn(`[Arc Events] Handler '${label}' failed for ${event.type} (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${Math.round(delay)}ms: ${error.message}`);
|
|
86
|
+
await sleep(delay);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
logger.error(`[Arc Events] Handler '${label}' permanently failed for ${event.type} after ${maxRetries + 1} attempts. ${errors.length} errors.`);
|
|
90
|
+
if (onDead) try {
|
|
91
|
+
await onDead(event, errors);
|
|
92
|
+
} catch (dlqErr) {
|
|
93
|
+
logger.error("[Arc Events] Dead letter callback failed:", dlqErr);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* import { withRetry, createDeadLetterPublisher } from '@classytic/arc/events';
|
|
103
|
+
*
|
|
104
|
+
* const toDlq = createDeadLetterPublisher(fastify.events);
|
|
105
|
+
*
|
|
106
|
+
* await fastify.events.subscribe('order.created', withRetry(handler, {
|
|
107
|
+
* maxRetries: 3,
|
|
108
|
+
* onDead: toDlq,
|
|
109
|
+
* }));
|
|
110
|
+
*
|
|
111
|
+
* // Monitor dead letters
|
|
112
|
+
* await fastify.events.subscribe('$deadLetter', async (event) => {
|
|
113
|
+
* console.error('Dead letter:', event.payload);
|
|
114
|
+
* await alertOps(event.payload);
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
function createDeadLetterPublisher(events) {
|
|
119
|
+
return async (event, errors) => {
|
|
120
|
+
await events.publish("$deadLetter", {
|
|
121
|
+
originalEvent: event,
|
|
122
|
+
errors: errors.map((e) => ({
|
|
123
|
+
message: e.message,
|
|
124
|
+
stack: e.stack
|
|
125
|
+
})),
|
|
126
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function sleep(ms) {
|
|
131
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/events/eventPlugin.ts
|
|
136
|
+
/**
|
|
137
|
+
* Event Plugin
|
|
138
|
+
*
|
|
139
|
+
* Integrates event transport with Fastify.
|
|
140
|
+
* Defaults to in-memory transport; configure durable transport for production.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* // Development (in-memory)
|
|
144
|
+
* await fastify.register(eventPlugin);
|
|
145
|
+
*
|
|
146
|
+
* // Production (Redis)
|
|
147
|
+
* await fastify.register(eventPlugin, {
|
|
148
|
+
* transport: new RedisEventTransport({ url: process.env.REDIS_URL }),
|
|
149
|
+
* });
|
|
150
|
+
*/
|
|
151
|
+
var eventPlugin_exports = /* @__PURE__ */ __exportAll({
|
|
152
|
+
default: () => eventPlugin_default,
|
|
153
|
+
eventPlugin: () => eventPlugin
|
|
154
|
+
});
|
|
155
|
+
const eventPlugin = async (fastify, opts = {}) => {
|
|
156
|
+
const { transport = new MemoryEventTransport(), logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError } = opts;
|
|
157
|
+
fastify.decorate("events", {
|
|
158
|
+
publish: async (type, payload, meta) => {
|
|
159
|
+
const store = requestContext.get();
|
|
160
|
+
const event = createEvent(type, payload, {
|
|
161
|
+
...store?.requestId && !meta?.correlationId ? { correlationId: store.requestId } : {},
|
|
162
|
+
...meta
|
|
163
|
+
});
|
|
164
|
+
if (logEvents) fastify.log?.info?.({
|
|
165
|
+
eventType: type,
|
|
166
|
+
eventId: event.meta.id,
|
|
167
|
+
correlationId: event.meta.correlationId
|
|
168
|
+
}, "Publishing event");
|
|
169
|
+
try {
|
|
170
|
+
if (wal) await wal.save(event);
|
|
171
|
+
await transport.publish(event);
|
|
172
|
+
if (wal?.acknowledge) await wal.acknowledge(event.meta.id);
|
|
173
|
+
onPublish?.(event);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
fastify.log?.error?.({
|
|
176
|
+
transport: transport.name,
|
|
177
|
+
eventType: type,
|
|
178
|
+
error
|
|
179
|
+
}, "[Arc Events] Failed to publish event");
|
|
180
|
+
onPublishError?.(event, error);
|
|
181
|
+
if (!failOpen) throw error;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
subscribe: async (pattern, handler) => {
|
|
185
|
+
let wrappedHandler = handler;
|
|
186
|
+
if (retryOpts && pattern !== "$deadLetter") wrappedHandler = withRetry(handler, {
|
|
187
|
+
...retryOpts,
|
|
188
|
+
onDead: dlqOpts?.store ?? createDeadLetterPublisher(fastify.events),
|
|
189
|
+
logger: fastify.log
|
|
190
|
+
});
|
|
191
|
+
if (logEvents) fastify.log?.info?.({
|
|
192
|
+
pattern,
|
|
193
|
+
retry: !!retryOpts
|
|
194
|
+
}, "Subscribing to events");
|
|
195
|
+
try {
|
|
196
|
+
return await transport.subscribe(pattern, wrappedHandler);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
fastify.log?.error?.({
|
|
199
|
+
transport: transport.name,
|
|
200
|
+
pattern,
|
|
201
|
+
error
|
|
202
|
+
}, "[Arc Events] Failed to subscribe to events");
|
|
203
|
+
if (!failOpen) throw error;
|
|
204
|
+
return () => {};
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
transportName: transport.name
|
|
208
|
+
});
|
|
209
|
+
fastify.addHook("onClose", async () => {
|
|
210
|
+
try {
|
|
211
|
+
await transport.close?.();
|
|
212
|
+
} catch (error) {
|
|
213
|
+
fastify.log?.warn?.({
|
|
214
|
+
transport: transport.name,
|
|
215
|
+
error
|
|
216
|
+
}, "[Arc Events] Transport close failed");
|
|
217
|
+
if (!failOpen) throw error;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
if (transport.name === "memory") fastify.log?.warn?.("[Arc Events] Using in-memory transport. Events will not persist or scale across instances. For production, configure a durable transport (Redis, RabbitMQ, etc.)");
|
|
221
|
+
else fastify.log?.debug?.(`[Arc Events] Using ${transport.name} transport`);
|
|
222
|
+
};
|
|
223
|
+
var eventPlugin_default = fp(eventPlugin, {
|
|
224
|
+
name: "arc-events",
|
|
225
|
+
fastify: "5.x"
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
//#endregion
|
|
229
|
+
export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createEvent as o, createDeadLetterPublisher as r, eventPlugin as t };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-BkUDYZEb.mjs";
|
|
2
|
+
import { FastifyPluginAsync } from "fastify";
|
|
3
|
+
|
|
4
|
+
//#region src/events/retry.d.ts
|
|
5
|
+
interface RetryOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Max retry attempts (not counting the initial attempt).
|
|
8
|
+
* @default 3
|
|
9
|
+
*/
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Initial backoff delay in ms. Doubles on each retry (exponential backoff).
|
|
13
|
+
* @default 1000
|
|
14
|
+
*/
|
|
15
|
+
backoffMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Maximum backoff delay in ms (caps exponential growth).
|
|
18
|
+
* @default 30000
|
|
19
|
+
*/
|
|
20
|
+
maxBackoffMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Jitter factor (0-1). Adds randomness to prevent thundering herd.
|
|
23
|
+
* 0 = no jitter, 1 = full jitter (delay ∈ [0, calculated]).
|
|
24
|
+
* @default 0.1
|
|
25
|
+
*/
|
|
26
|
+
jitter?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Callback when all retries are exhausted. The event is "dead".
|
|
29
|
+
* Use this to publish to a `$deadLetter` channel, log, alert, etc.
|
|
30
|
+
*/
|
|
31
|
+
onDead?: (event: DomainEvent, errors: Error[]) => void | Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Optional name for logging/debugging.
|
|
34
|
+
*/
|
|
35
|
+
name?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Logger for retry warnings and error messages (default: console).
|
|
38
|
+
* Pass `fastify.log` to integrate with your application logger.
|
|
39
|
+
*/
|
|
40
|
+
logger?: EventLogger;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wrap an event handler with retry logic and dead letter support.
|
|
44
|
+
*
|
|
45
|
+
* On failure, retries with exponential backoff (with jitter).
|
|
46
|
+
* After all retries exhausted, calls `onDead` callback if provided.
|
|
47
|
+
*/
|
|
48
|
+
declare function withRetry(handler: EventHandler, options?: RetryOptions): EventHandler;
|
|
49
|
+
/**
|
|
50
|
+
* Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import { withRetry, createDeadLetterPublisher } from '@classytic/arc/events';
|
|
55
|
+
*
|
|
56
|
+
* const toDlq = createDeadLetterPublisher(fastify.events);
|
|
57
|
+
*
|
|
58
|
+
* await fastify.events.subscribe('order.created', withRetry(handler, {
|
|
59
|
+
* maxRetries: 3,
|
|
60
|
+
* onDead: toDlq,
|
|
61
|
+
* }));
|
|
62
|
+
*
|
|
63
|
+
* // Monitor dead letters
|
|
64
|
+
* await fastify.events.subscribe('$deadLetter', async (event) => {
|
|
65
|
+
* console.error('Dead letter:', event.payload);
|
|
66
|
+
* await alertOps(event.payload);
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function createDeadLetterPublisher(events: {
|
|
71
|
+
publish: <T>(type: string, payload: T, meta?: Record<string, unknown>) => Promise<void>;
|
|
72
|
+
}): (event: DomainEvent, errors: Error[]) => Promise<void>;
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/events/eventPlugin.d.ts
|
|
75
|
+
interface EventPluginOptions {
|
|
76
|
+
/** Event transport (default: MemoryEventTransport) */
|
|
77
|
+
transport?: EventTransport;
|
|
78
|
+
/** Enable event logging (default: false) */
|
|
79
|
+
logEvents?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Fail-open mode for runtime resilience (default: true).
|
|
82
|
+
* - true: publish/subscribe/close errors are logged and suppressed
|
|
83
|
+
* - false: errors are thrown to caller
|
|
84
|
+
*/
|
|
85
|
+
failOpen?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Write-Ahead Log (WAL) configuration for at-least-once delivery guarantees.
|
|
88
|
+
* If provided, events will be saved to the WAL *before* passing to the transport.
|
|
89
|
+
* After a successful publish, they are acknowledged.
|
|
90
|
+
*/
|
|
91
|
+
wal?: {
|
|
92
|
+
save: (event: DomainEvent) => Promise<void>;
|
|
93
|
+
acknowledge?: (eventId: string) => Promise<void>;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Auto-wrap all subscribed handlers with retry logic.
|
|
97
|
+
* When enabled, failed handler invocations are retried with exponential backoff.
|
|
98
|
+
*/
|
|
99
|
+
retry?: Pick<RetryOptions, "maxRetries" | "backoffMs" | "maxBackoffMs" | "jitter">;
|
|
100
|
+
/**
|
|
101
|
+
* Dead letter queue for events that exhaust all retries.
|
|
102
|
+
* Requires `retry` to be enabled. If `retry` is set but no custom `store`,
|
|
103
|
+
* failed events are published to the `$deadLetter` event type by default.
|
|
104
|
+
*/
|
|
105
|
+
deadLetterQueue?: {
|
|
106
|
+
/** Custom store function. If omitted, publishes to '$deadLetter' event type. */store?: (event: DomainEvent, errors: Error[]) => void | Promise<void>;
|
|
107
|
+
};
|
|
108
|
+
/** Callback after successful publish (for metrics/tracking) */
|
|
109
|
+
onPublish?: (event: DomainEvent) => void;
|
|
110
|
+
/** Callback on publish failure (for metrics/alerting) */
|
|
111
|
+
onPublishError?: (event: DomainEvent, error: Error) => void;
|
|
112
|
+
}
|
|
113
|
+
declare module "fastify" {
|
|
114
|
+
interface FastifyInstance {
|
|
115
|
+
events: {
|
|
116
|
+
/** Publish an event */publish: <T>(type: string, payload: T, meta?: Partial<DomainEvent["meta"]>) => Promise<void>; /** Subscribe to events */
|
|
117
|
+
subscribe: (pattern: string, handler: EventHandler) => Promise<() => void>; /** Get transport name */
|
|
118
|
+
transportName: string;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
declare const eventPlugin: FastifyPluginAsync<EventPluginOptions>;
|
|
123
|
+
//#endregion
|
|
124
|
+
export { withRetry as a, createDeadLetterPublisher as i, eventPlugin as n, RetryOptions as r, EventPluginOptions as t };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { a as MemoryEventTransport, i as EventTransport, n as EventHandler, o as MemoryEventTransportOptions, r as EventLogger, s as createEvent, t as DomainEvent } from "../EventTransport-BkUDYZEb.mjs";
|
|
2
|
+
import { a as withRetry, i as createDeadLetterPublisher, n as eventPlugin, r as RetryOptions, t as EventPluginOptions } from "../eventPlugin-H6wDDjGO.mjs";
|
|
3
|
+
import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
|
|
4
|
+
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-CBg0upHI.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/events/eventTypes.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Event Type Constants and Helpers
|
|
9
|
+
*
|
|
10
|
+
* Provides well-typed event names for CRUD operations and lifecycle events.
|
|
11
|
+
* Use these instead of hand-typing event strings to prevent typos.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
|
|
16
|
+
*
|
|
17
|
+
* // Subscribe to product creation events
|
|
18
|
+
* events.subscribe(crudEventType('product', 'created'), handler);
|
|
19
|
+
*
|
|
20
|
+
* // Subscribe to Arc lifecycle events
|
|
21
|
+
* events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
/** Suffixes for auto-emitted CRUD events */
|
|
25
|
+
declare const CRUD_EVENT_SUFFIXES: readonly ["created", "updated", "deleted"];
|
|
26
|
+
/** Type for CRUD event suffixes */
|
|
27
|
+
type CrudEventSuffix = typeof CRUD_EVENT_SUFFIXES[number];
|
|
28
|
+
/**
|
|
29
|
+
* Build a CRUD event type string.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* crudEventType('product', 'created') // 'product.created'
|
|
34
|
+
* crudEventType('order', 'deleted') // 'order.deleted'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function crudEventType(resource: string, suffix: CrudEventSuffix): string;
|
|
38
|
+
/** Arc framework lifecycle events — emitted automatically by the framework */
|
|
39
|
+
declare const ARC_LIFECYCLE_EVENTS: Readonly<{
|
|
40
|
+
/** Emitted when a resource plugin is registered */readonly RESOURCE_REGISTERED: "arc.resource.registered"; /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
|
|
41
|
+
readonly READY: "arc.ready";
|
|
42
|
+
}>;
|
|
43
|
+
/** Type for Arc lifecycle event names */
|
|
44
|
+
type ArcLifecycleEvent = typeof ARC_LIFECYCLE_EVENTS[keyof typeof ARC_LIFECYCLE_EVENTS];
|
|
45
|
+
/** Cache-specific event types for observability and external triggers */
|
|
46
|
+
declare const CACHE_EVENTS: Readonly<{
|
|
47
|
+
/** Emitted when a resource's cache version is bumped */readonly VERSION_BUMPED: "arc.cache.version.bumped"; /** Emitted when a tag version is bumped */
|
|
48
|
+
readonly TAG_VERSION_BUMPED: "arc.cache.tag.bumped";
|
|
49
|
+
}>;
|
|
50
|
+
/** Type for cache event names */
|
|
51
|
+
type CacheEvent = typeof CACHE_EVENTS[keyof typeof CACHE_EVENTS];
|
|
52
|
+
//#endregion
|
|
53
|
+
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type DomainEvent, type EventHandler, type EventLogger, type EventPluginOptions, type EventTransport, MemoryEventTransport, type MemoryEventTransportOptions, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RetryOptions, createDeadLetterPublisher, createEvent, crudEventType, eventPlugin, withRetry };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { a as MemoryEventTransport, i as withRetry, o as createEvent, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-BEOvaDqo.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/events/eventTypes.ts
|
|
4
|
+
/**
|
|
5
|
+
* Event Type Constants and Helpers
|
|
6
|
+
*
|
|
7
|
+
* Provides well-typed event names for CRUD operations and lifecycle events.
|
|
8
|
+
* Use these instead of hand-typing event strings to prevent typos.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
|
|
13
|
+
*
|
|
14
|
+
* // Subscribe to product creation events
|
|
15
|
+
* events.subscribe(crudEventType('product', 'created'), handler);
|
|
16
|
+
*
|
|
17
|
+
* // Subscribe to Arc lifecycle events
|
|
18
|
+
* events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/** Suffixes for auto-emitted CRUD events */
|
|
22
|
+
const CRUD_EVENT_SUFFIXES = Object.freeze([
|
|
23
|
+
"created",
|
|
24
|
+
"updated",
|
|
25
|
+
"deleted"
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* Build a CRUD event type string.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* crudEventType('product', 'created') // 'product.created'
|
|
33
|
+
* crudEventType('order', 'deleted') // 'order.deleted'
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function crudEventType(resource, suffix) {
|
|
37
|
+
return `${resource}.${suffix}`;
|
|
38
|
+
}
|
|
39
|
+
/** Arc framework lifecycle events — emitted automatically by the framework */
|
|
40
|
+
const ARC_LIFECYCLE_EVENTS = Object.freeze({
|
|
41
|
+
RESOURCE_REGISTERED: "arc.resource.registered",
|
|
42
|
+
READY: "arc.ready"
|
|
43
|
+
});
|
|
44
|
+
/** Cache-specific event types for observability and external triggers */
|
|
45
|
+
const CACHE_EVENTS = Object.freeze({
|
|
46
|
+
VERSION_BUMPED: "arc.cache.version.bumped",
|
|
47
|
+
TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, MemoryEventTransport, createDeadLetterPublisher, createEvent, crudEventType, eventPlugin, withRetry };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//#region src/events/transports/redis-stream.ts
|
|
2
|
+
var RedisStreamTransport = class {
|
|
3
|
+
name = "redis-stream";
|
|
4
|
+
redis;
|
|
5
|
+
stream;
|
|
6
|
+
group;
|
|
7
|
+
consumer;
|
|
8
|
+
blockTimeMs;
|
|
9
|
+
batchSize;
|
|
10
|
+
maxRetries;
|
|
11
|
+
claimTimeoutMs;
|
|
12
|
+
deadLetterStream;
|
|
13
|
+
maxLen;
|
|
14
|
+
logger;
|
|
15
|
+
handlers = /* @__PURE__ */ new Map();
|
|
16
|
+
running = false;
|
|
17
|
+
pollPromise = null;
|
|
18
|
+
groupCreated = false;
|
|
19
|
+
constructor(redis, options = {}) {
|
|
20
|
+
this.redis = redis;
|
|
21
|
+
this.stream = options.stream ?? "arc:events";
|
|
22
|
+
this.group = options.group ?? "default";
|
|
23
|
+
this.consumer = options.consumer ?? `consumer-${crypto.randomUUID().slice(0, 8)}`;
|
|
24
|
+
this.blockTimeMs = options.blockTimeMs ?? 5e3;
|
|
25
|
+
this.batchSize = options.batchSize ?? 10;
|
|
26
|
+
this.maxRetries = options.maxRetries ?? 5;
|
|
27
|
+
this.claimTimeoutMs = options.claimTimeoutMs ?? 3e4;
|
|
28
|
+
this.deadLetterStream = options.deadLetterStream ?? "arc:events:dlq";
|
|
29
|
+
this.maxLen = options.maxLen ?? 1e4;
|
|
30
|
+
this.logger = options.logger ?? console;
|
|
31
|
+
}
|
|
32
|
+
async publish(event) {
|
|
33
|
+
const args = [
|
|
34
|
+
this.stream,
|
|
35
|
+
...this.maxLen > 0 ? [
|
|
36
|
+
"MAXLEN",
|
|
37
|
+
"~",
|
|
38
|
+
String(this.maxLen)
|
|
39
|
+
] : [],
|
|
40
|
+
"*",
|
|
41
|
+
"type",
|
|
42
|
+
event.type,
|
|
43
|
+
"data",
|
|
44
|
+
JSON.stringify(event)
|
|
45
|
+
];
|
|
46
|
+
await this.redis.xadd(...args);
|
|
47
|
+
}
|
|
48
|
+
async subscribe(pattern, handler) {
|
|
49
|
+
if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
|
|
50
|
+
this.handlers.get(pattern).add(handler);
|
|
51
|
+
if (!this.running) {
|
|
52
|
+
await this.ensureGroup();
|
|
53
|
+
this.running = true;
|
|
54
|
+
this.pollPromise = this.pollLoop();
|
|
55
|
+
}
|
|
56
|
+
return () => {
|
|
57
|
+
const set = this.handlers.get(pattern);
|
|
58
|
+
if (set) {
|
|
59
|
+
set.delete(handler);
|
|
60
|
+
if (set.size === 0) this.handlers.delete(pattern);
|
|
61
|
+
}
|
|
62
|
+
if (this.handlers.size === 0 && this.running) this.running = false;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async close() {
|
|
66
|
+
this.running = false;
|
|
67
|
+
this.handlers.clear();
|
|
68
|
+
if (this.pollPromise) {
|
|
69
|
+
await this.pollPromise;
|
|
70
|
+
this.pollPromise = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async ensureGroup() {
|
|
74
|
+
if (this.groupCreated) return;
|
|
75
|
+
try {
|
|
76
|
+
await this.redis.xgroup("CREATE", this.stream, this.group, "$", "MKSTREAM");
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err instanceof Error && !err.message.includes("BUSYGROUP")) throw err;
|
|
79
|
+
}
|
|
80
|
+
this.groupCreated = true;
|
|
81
|
+
}
|
|
82
|
+
async pollLoop() {
|
|
83
|
+
while (this.running) try {
|
|
84
|
+
await this.claimPending();
|
|
85
|
+
await this.readNewMessages();
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (this.running) {
|
|
88
|
+
this.logger.error("[RedisStreamTransport] Poll error:", err);
|
|
89
|
+
await this.sleep(1e3);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async readNewMessages() {
|
|
94
|
+
const result = await this.redis.xreadgroup("GROUP", this.group, this.consumer, "COUNT", this.batchSize, "BLOCK", this.blockTimeMs, "STREAMS", this.stream, ">");
|
|
95
|
+
if (!result) return;
|
|
96
|
+
for (const [, entries] of result) for (const [messageId, fields] of entries) await this.processEntry(messageId, fields);
|
|
97
|
+
}
|
|
98
|
+
async claimPending() {
|
|
99
|
+
try {
|
|
100
|
+
const pending = await this.redis.xpending(this.stream, this.group, "-", "+", 10);
|
|
101
|
+
if (!pending || pending.length === 0) return;
|
|
102
|
+
const staleIds = [];
|
|
103
|
+
const overRetryIds = [];
|
|
104
|
+
for (const entry of pending) {
|
|
105
|
+
const [id, , idleTime, deliveryCount] = entry;
|
|
106
|
+
if (idleTime > this.claimTimeoutMs) if (deliveryCount >= this.maxRetries) overRetryIds.push(id);
|
|
107
|
+
else staleIds.push(id);
|
|
108
|
+
}
|
|
109
|
+
if (overRetryIds.length > 0) await this.moveToDlq(overRetryIds);
|
|
110
|
+
if (staleIds.length > 0) {
|
|
111
|
+
const claimed = await this.redis.xclaim(this.stream, this.group, this.consumer, this.claimTimeoutMs, ...staleIds);
|
|
112
|
+
for (const [messageId, fields] of claimed) await this.processEntry(messageId, fields);
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
async processEntry(messageId, fields) {
|
|
117
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
118
|
+
for (let i = 0; i < fields.length; i += 2) fieldMap.set(fields[i], fields[i + 1]);
|
|
119
|
+
const eventType = fieldMap.get("type");
|
|
120
|
+
const rawData = fieldMap.get("data");
|
|
121
|
+
if (!eventType || !rawData) {
|
|
122
|
+
await this.redis.xack(this.stream, this.group, messageId);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
let event;
|
|
126
|
+
try {
|
|
127
|
+
event = JSON.parse(rawData, (key, value) => {
|
|
128
|
+
if (key === "timestamp" && typeof value === "string") return new Date(value);
|
|
129
|
+
return value;
|
|
130
|
+
});
|
|
131
|
+
} catch {
|
|
132
|
+
await this.redis.xack(this.stream, this.group, messageId);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const matchingHandlers = this.getMatchingHandlers(event.type);
|
|
136
|
+
let allSucceeded = true;
|
|
137
|
+
for (const handler of matchingHandlers) try {
|
|
138
|
+
await handler(event);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
allSucceeded = false;
|
|
141
|
+
this.logger.error(`[RedisStreamTransport] Handler error for ${event.type}:`, err);
|
|
142
|
+
}
|
|
143
|
+
if (allSucceeded) await this.redis.xack(this.stream, this.group, messageId);
|
|
144
|
+
}
|
|
145
|
+
getMatchingHandlers(eventType) {
|
|
146
|
+
const matched = [];
|
|
147
|
+
for (const [pattern, handlers] of this.handlers) if (this.matchesPattern(pattern, eventType)) for (const h of handlers) matched.push(h);
|
|
148
|
+
return matched;
|
|
149
|
+
}
|
|
150
|
+
matchesPattern(pattern, eventType) {
|
|
151
|
+
if (pattern === "*") return true;
|
|
152
|
+
if (pattern === eventType) return true;
|
|
153
|
+
if (pattern.endsWith(".*")) {
|
|
154
|
+
const prefix = pattern.slice(0, -2);
|
|
155
|
+
return eventType.startsWith(prefix + ".");
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
async moveToDlq(ids) {
|
|
160
|
+
if (this.deadLetterStream === false) {
|
|
161
|
+
for (const id of ids) await this.redis.xack(this.stream, this.group, id);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
for (const id of ids) try {
|
|
165
|
+
await this.redis.xadd(this.deadLetterStream, "*", "originalStream", this.stream, "originalId", id, "group", this.group, "failedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
166
|
+
await this.redis.xack(this.stream, this.group, id);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
this.logger.error(`[RedisStreamTransport] DLQ write failed for ${id}:`, err);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
sleep(ms) {
|
|
172
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
export { RedisStreamTransport };
|