@classytic/arc 2.3.0 → 2.4.2
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 +187 -18
- package/bin/arc.js +11 -3
- package/dist/BaseController-CkM5dUh_.mjs +1031 -0
- package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
- package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
- package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
- package/dist/adapters/index.d.mts +3 -5
- package/dist/adapters/index.mjs +2 -3
- package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
- package/dist/audit/index.d.mts +4 -7
- package/dist/audit/index.mjs +2 -29
- package/dist/audit/mongodb.d.mts +1 -4
- package/dist/audit/mongodb.mjs +2 -3
- package/dist/auth/index.d.mts +7 -9
- package/dist/auth/index.mjs +65 -63
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +1 -2
- package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
- package/dist/cache/index.d.mts +23 -23
- package/dist/cache/index.mjs +4 -6
- package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
- package/dist/chunk-BpYLSNr0.mjs +14 -0
- package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
- package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
- package/dist/cli/commands/describe.mjs +24 -7
- package/dist/cli/commands/docs.mjs +6 -7
- package/dist/cli/commands/doctor.d.mts +10 -0
- package/dist/cli/commands/doctor.mjs +156 -0
- package/dist/cli/commands/generate.mjs +66 -17
- package/dist/cli/commands/init.mjs +315 -45
- package/dist/cli/commands/introspect.mjs +2 -4
- package/dist/cli/index.d.mts +1 -10
- package/dist/cli/index.mjs +4 -153
- package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
- package/dist/core/index.d.mts +3 -5
- package/dist/core/index.mjs +5 -4
- package/dist/core-C1XCMtqM.mjs +185 -0
- package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
- package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
- package/dist/discovery/index.mjs +37 -5
- package/dist/docs/index.d.mts +6 -9
- package/dist/docs/index.mjs +3 -21
- package/dist/dynamic/index.d.mts +93 -0
- package/dist/dynamic/index.mjs +122 -0
- package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
- package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
- package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
- package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
- package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
- package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
- package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
- package/dist/events/index.d.mts +72 -7
- package/dist/events/index.mjs +216 -4
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +19 -7
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +3 -4
- package/dist/factory/index.d.mts +23 -9
- package/dist/factory/index.mjs +48 -3
- package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
- package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
- package/dist/hooks/index.d.mts +1 -3
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +5 -5
- package/dist/idempotency/index.mjs +3 -7
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +4 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +2 -5
- package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
- package/dist/index-Diqcm14c.d.mts +369 -0
- package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
- package/dist/index.d.mts +100 -105
- package/dist/index.mjs +85 -58
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +8 -4
- package/dist/integrations/index.d.mts +4 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs.d.mts +2 -2
- package/dist/integrations/jobs.mjs +63 -14
- package/dist/integrations/mcp/index.d.mts +219 -0
- package/dist/integrations/mcp/index.mjs +572 -0
- package/dist/integrations/mcp/testing.d.mts +53 -0
- package/dist/integrations/mcp/testing.mjs +104 -0
- package/dist/integrations/streamline.mjs +39 -19
- package/dist/integrations/webhooks.d.mts +56 -0
- package/dist/integrations/webhooks.mjs +139 -0
- package/dist/integrations/websocket-redis.d.mts +46 -0
- package/dist/integrations/websocket-redis.mjs +50 -0
- package/dist/integrations/websocket.d.mts +68 -2
- package/dist/integrations/websocket.mjs +96 -13
- package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
- package/dist/interface-DGmPxakH.d.mts +2213 -0
- package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
- package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
- package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
- package/dist/metrics-Csh4nsvv.mjs +224 -0
- package/dist/migrations/index.d.mts +113 -44
- package/dist/migrations/index.mjs +84 -102
- package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
- package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
- package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
- package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
- package/dist/org/index.d.mts +12 -14
- package/dist/org/index.mjs +92 -119
- package/dist/org/types.d.mts +2 -2
- package/dist/org/types.mjs +1 -1
- package/dist/permissions/index.d.mts +4 -278
- package/dist/permissions/index.mjs +4 -579
- package/dist/permissions-CA5zg0yK.mjs +751 -0
- package/dist/plugins/index.d.mts +104 -107
- package/dist/plugins/index.mjs +203 -313
- package/dist/plugins/response-cache.mjs +4 -69
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +24 -11
- package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
- package/dist/policies/index.d.mts +2 -2
- package/dist/policies/index.mjs +80 -83
- package/dist/presets/index.d.mts +26 -19
- package/dist/presets/index.mjs +2 -142
- package/dist/presets/multiTenant.d.mts +1 -4
- package/dist/presets/multiTenant.mjs +4 -6
- package/dist/presets-C9QXJV1u.mjs +422 -0
- package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
- package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
- package/dist/queryParser-CgCtsjti.mjs +352 -0
- package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
- package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
- package/dist/registry/index.d.mts +1 -4
- package/dist/registry/index.mjs +3 -4
- package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
- package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
- package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
- package/dist/rpc/index.d.mts +90 -0
- package/dist/rpc/index.mjs +248 -0
- package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
- package/dist/schemas/index.d.mts +30 -30
- package/dist/schemas/index.mjs +2 -4
- package/dist/scope/index.d.mts +13 -2
- package/dist/scope/index.mjs +18 -5
- package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
- package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
- package/dist/testing/index.d.mts +551 -567
- package/dist/testing/index.mjs +1744 -1799
- package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
- package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
- package/dist/types/index.d.mts +4 -946
- package/dist/types/index.mjs +2 -4
- package/dist/types-BJmgxNbF.d.mts +275 -0
- package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
- package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
- package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
- package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
- package/dist/utils/index.d.mts +254 -351
- package/dist/utils/index.mjs +7 -6
- package/dist/utils-Dc0WhlIl.mjs +594 -0
- package/dist/versioning-BzfeHmhj.mjs +37 -0
- package/package.json +44 -10
- package/skills/arc/SKILL.md +518 -0
- package/skills/arc/references/auth.md +250 -0
- package/skills/arc/references/events.md +272 -0
- package/skills/arc/references/integrations.md +385 -0
- package/skills/arc/references/mcp.md +431 -0
- package/skills/arc/references/production.md +610 -0
- package/skills/arc/references/testing.md +183 -0
- package/dist/audited-CGdLiSlE.mjs +0 -140
- package/dist/chunk-C7Uep-_p.mjs +0 -20
- package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
- package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
- package/dist/interface-BtdYtQUA.d.mts +0 -1114
- package/dist/presets-BTeYbw7h.d.mts +0 -57
- package/dist/presets-CeFtfDR8.mjs +0 -119
- /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
- /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
- /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-
|
|
2
|
-
import { t as requestContext } from "./requestContext-
|
|
1
|
+
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
+
import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
|
-
|
|
5
4
|
//#region src/events/EventTransport.ts
|
|
6
5
|
/**
|
|
7
6
|
* In-memory event transport (default)
|
|
@@ -21,7 +20,7 @@ var MemoryEventTransport = class {
|
|
|
21
20
|
const patternHandlers = /* @__PURE__ */ new Set();
|
|
22
21
|
for (const [pattern, handlers] of this.handlers.entries()) if (pattern.endsWith(".*")) {
|
|
23
22
|
const prefix = pattern.slice(0, -2);
|
|
24
|
-
if (event.type.startsWith(prefix
|
|
23
|
+
if (event.type.startsWith(`${prefix}.`)) for (const h of handlers) patternHandlers.add(h);
|
|
25
24
|
}
|
|
26
25
|
const allHandlers = new Set([
|
|
27
26
|
...exactHandlers,
|
|
@@ -36,9 +35,13 @@ var MemoryEventTransport = class {
|
|
|
36
35
|
}
|
|
37
36
|
async subscribe(pattern, handler) {
|
|
38
37
|
if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
|
|
39
|
-
this.handlers.get(pattern)
|
|
38
|
+
this.handlers.get(pattern)?.add(handler);
|
|
40
39
|
return () => {
|
|
41
|
-
this.handlers.get(pattern)
|
|
40
|
+
const set = this.handlers.get(pattern);
|
|
41
|
+
if (set) {
|
|
42
|
+
set.delete(handler);
|
|
43
|
+
if (set.size === 0) this.handlers.delete(pattern);
|
|
44
|
+
}
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
async close() {
|
|
@@ -59,7 +62,6 @@ function createEvent(type, payload, meta) {
|
|
|
59
62
|
}
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
|
-
|
|
63
65
|
//#endregion
|
|
64
66
|
//#region src/events/retry.ts
|
|
65
67
|
/**
|
|
@@ -130,32 +132,20 @@ function createDeadLetterPublisher(events) {
|
|
|
130
132
|
function sleep(ms) {
|
|
131
133
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
132
134
|
}
|
|
133
|
-
|
|
134
135
|
//#endregion
|
|
135
136
|
//#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
137
|
var eventPlugin_exports = /* @__PURE__ */ __exportAll({
|
|
152
138
|
default: () => eventPlugin_default,
|
|
153
139
|
eventPlugin: () => eventPlugin
|
|
154
140
|
});
|
|
155
141
|
const eventPlugin = async (fastify, opts = {}) => {
|
|
156
|
-
const { transport = new MemoryEventTransport(), logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError } = opts;
|
|
142
|
+
const { transport = new MemoryEventTransport(), logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError, registry, validateMode: rawValidateMode } = opts;
|
|
143
|
+
const validateMode = rawValidateMode ?? (registry ? "warn" : "off");
|
|
157
144
|
fastify.decorate("events", {
|
|
158
145
|
publish: async (type, payload, meta) => {
|
|
146
|
+
if (!type || typeof type !== "string") throw new Error("[Arc Events] Event type must be a non-empty string");
|
|
147
|
+
if (type.startsWith("$") && type !== "$deadLetter") throw new Error(`[Arc Events] Event type '${type}' uses reserved '$' prefix`);
|
|
148
|
+
if (type.length > 256) throw new Error("[Arc Events] Event type exceeds 256 characters");
|
|
159
149
|
const store = requestContext.get();
|
|
160
150
|
const event = createEvent(type, payload, {
|
|
161
151
|
...store?.requestId && !meta?.correlationId ? { correlationId: store.requestId } : {},
|
|
@@ -166,6 +156,14 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
166
156
|
eventId: event.meta.id,
|
|
167
157
|
correlationId: event.meta.correlationId
|
|
168
158
|
}, "Publishing event");
|
|
159
|
+
if (registry && validateMode !== "off") {
|
|
160
|
+
const result = registry.validate(type, payload);
|
|
161
|
+
if (!result.valid) {
|
|
162
|
+
const msg = `[Arc Events] Event '${type}' payload validation failed: ${result.errors?.join("; ")}`;
|
|
163
|
+
if (validateMode === "reject") throw new Error(msg);
|
|
164
|
+
fastify.log?.warn?.(msg);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
169
167
|
try {
|
|
170
168
|
if (wal) await wal.save(event);
|
|
171
169
|
await transport.publish(event);
|
|
@@ -204,7 +202,8 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
204
202
|
return () => {};
|
|
205
203
|
}
|
|
206
204
|
},
|
|
207
|
-
transportName: transport.name
|
|
205
|
+
transportName: transport.name,
|
|
206
|
+
registry
|
|
208
207
|
});
|
|
209
208
|
fastify.addHook("onClose", async () => {
|
|
210
209
|
try {
|
|
@@ -224,6 +223,5 @@ var eventPlugin_default = fp(eventPlugin, {
|
|
|
224
223
|
name: "arc-events",
|
|
225
224
|
fastify: "5.x"
|
|
226
225
|
});
|
|
227
|
-
|
|
228
226
|
//#endregion
|
|
229
|
-
export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createEvent as o, createDeadLetterPublisher as r, eventPlugin as t };
|
|
227
|
+
export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createEvent as o, createDeadLetterPublisher as r, eventPlugin as t };
|
|
@@ -1,6 +1,77 @@
|
|
|
1
|
-
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-
|
|
1
|
+
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-wc5hSLik.mjs";
|
|
2
2
|
import { FastifyPluginAsync } from "fastify";
|
|
3
3
|
|
|
4
|
+
//#region src/events/defineEvent.d.ts
|
|
5
|
+
interface EventSchema {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties?: Record<string, {
|
|
8
|
+
type?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}>;
|
|
11
|
+
required?: string[];
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
interface EventDefinitionInput {
|
|
15
|
+
/** Event type name (e.g., 'order.created') */
|
|
16
|
+
name: string;
|
|
17
|
+
/** JSON Schema for payload validation */
|
|
18
|
+
schema?: EventSchema;
|
|
19
|
+
/** Event version for schema evolution (default: 1) */
|
|
20
|
+
version?: number;
|
|
21
|
+
/** Human-readable description */
|
|
22
|
+
description?: string;
|
|
23
|
+
}
|
|
24
|
+
interface EventDefinitionOutput<T = unknown> {
|
|
25
|
+
/** Event type name */
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/** JSON Schema for payload validation */
|
|
28
|
+
readonly schema?: EventSchema;
|
|
29
|
+
/** Event version */
|
|
30
|
+
readonly version: number;
|
|
31
|
+
/** Human-readable description */
|
|
32
|
+
readonly description?: string;
|
|
33
|
+
/** Create a DomainEvent with this type + auto-generated metadata */
|
|
34
|
+
create(payload: T, meta?: Partial<DomainEvent["meta"]>): DomainEvent<T>;
|
|
35
|
+
}
|
|
36
|
+
interface ValidationResult {
|
|
37
|
+
valid: boolean;
|
|
38
|
+
errors?: string[];
|
|
39
|
+
}
|
|
40
|
+
interface EventRegistry {
|
|
41
|
+
/** Register an event definition */
|
|
42
|
+
register(definition: EventDefinitionOutput): void;
|
|
43
|
+
/** Get event definition by name (latest version if no version specified) */
|
|
44
|
+
get(name: string, version?: number): EventDefinitionOutput | undefined;
|
|
45
|
+
/** Get full catalog of registered events */
|
|
46
|
+
catalog(): ReadonlyArray<{
|
|
47
|
+
name: string;
|
|
48
|
+
version: number;
|
|
49
|
+
description?: string;
|
|
50
|
+
schema?: EventSchema;
|
|
51
|
+
}>;
|
|
52
|
+
/** Validate a payload against a registered event's schema */
|
|
53
|
+
validate(name: string, payload: unknown): ValidationResult;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Define a typed event with optional schema validation.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const OrderCreated = defineEvent({
|
|
60
|
+
* name: 'order.created',
|
|
61
|
+
* schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* const event = OrderCreated.create({ orderId: '123' });
|
|
65
|
+
*/
|
|
66
|
+
declare function defineEvent<T = unknown>(input: EventDefinitionInput): EventDefinitionOutput<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Create an event registry for cataloging and validating events.
|
|
69
|
+
*
|
|
70
|
+
* The registry is opt-in — unregistered events pass validation.
|
|
71
|
+
* This allows gradual adoption without breaking existing code.
|
|
72
|
+
*/
|
|
73
|
+
declare function createEventRegistry(): EventRegistry;
|
|
74
|
+
//#endregion
|
|
4
75
|
//#region src/events/retry.d.ts
|
|
5
76
|
interface RetryOptions {
|
|
6
77
|
/**
|
|
@@ -79,8 +150,16 @@ interface EventPluginOptions {
|
|
|
79
150
|
logEvents?: boolean;
|
|
80
151
|
/**
|
|
81
152
|
* Fail-open mode for runtime resilience (default: true).
|
|
82
|
-
* - true: publish/subscribe/close errors are logged and suppressed
|
|
83
|
-
*
|
|
153
|
+
* - true: publish/subscribe/close errors are logged and suppressed — the
|
|
154
|
+
* request still succeeds even if event delivery fails. Safe for analytics
|
|
155
|
+
* and non-critical side effects.
|
|
156
|
+
* - false: errors are thrown to caller — use this for business-critical
|
|
157
|
+
* events where silent loss is unacceptable (e.g. billing, notifications).
|
|
158
|
+
*
|
|
159
|
+
* **Important:** With `failOpen: true` (default), a transport outage will
|
|
160
|
+
* silently drop events while requests continue succeeding. Pair with the
|
|
161
|
+
* `onPublishError` callback to monitor failures, or use `wal` for
|
|
162
|
+
* at-least-once delivery guarantees.
|
|
84
163
|
*/
|
|
85
164
|
failOpen?: boolean;
|
|
86
165
|
/**
|
|
@@ -109,16 +188,37 @@ interface EventPluginOptions {
|
|
|
109
188
|
onPublish?: (event: DomainEvent) => void;
|
|
110
189
|
/** Callback on publish failure (for metrics/alerting) */
|
|
111
190
|
onPublishError?: (event: DomainEvent, error: Error) => void;
|
|
191
|
+
/**
|
|
192
|
+
* Event registry for payload validation and introspection.
|
|
193
|
+
* When provided, payloads are validated against registered schemas on publish.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const registry = createEventRegistry();
|
|
198
|
+
* registry.register(defineEvent({ name: 'order.created', schema: { ... } }));
|
|
199
|
+
*
|
|
200
|
+
* await fastify.register(eventPlugin, { registry, validateMode: 'warn' });
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
registry?: EventRegistry;
|
|
204
|
+
/**
|
|
205
|
+
* How to handle schema validation failures on publish:
|
|
206
|
+
* - `'warn'` (default when registry is provided): log a warning, still publish
|
|
207
|
+
* - `'reject'`: throw an error, do NOT publish
|
|
208
|
+
* - `'off'`: skip validation entirely (registry is only for introspection)
|
|
209
|
+
*/
|
|
210
|
+
validateMode?: "warn" | "reject" | "off";
|
|
112
211
|
}
|
|
113
212
|
declare module "fastify" {
|
|
114
213
|
interface FastifyInstance {
|
|
115
214
|
events: {
|
|
116
215
|
/** Publish an event */publish: <T>(type: string, payload: T, meta?: Partial<DomainEvent["meta"]>) => Promise<void>; /** Subscribe to events */
|
|
117
216
|
subscribe: (pattern: string, handler: EventHandler) => Promise<() => void>; /** Get transport name */
|
|
118
|
-
transportName: string;
|
|
217
|
+
transportName: string; /** Event registry for introspection (undefined when no registry configured) */
|
|
218
|
+
registry?: EventRegistry;
|
|
119
219
|
};
|
|
120
220
|
}
|
|
121
221
|
}
|
|
122
222
|
declare const eventPlugin: FastifyPluginAsync<EventPluginOptions>;
|
|
123
223
|
//#endregion
|
|
124
|
-
export { withRetry as a, createDeadLetterPublisher as i, eventPlugin as n, RetryOptions as r, EventPluginOptions as t };
|
|
224
|
+
export { withRetry as a, EventRegistry as c, createEventRegistry as d, defineEvent as f, createDeadLetterPublisher as i, EventSchema as l, eventPlugin as n, EventDefinitionInput as o, RetryOptions as r, EventDefinitionOutput as s, EventPluginOptions as t, ValidationResult as u };
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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-
|
|
2
|
-
import { a as withRetry, i as createDeadLetterPublisher, n as eventPlugin, r as RetryOptions, t as EventPluginOptions } from "../eventPlugin-
|
|
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-wc5hSLik.mjs";
|
|
2
|
+
import { a as withRetry, c as EventRegistry, d as createEventRegistry, f as defineEvent, i as createDeadLetterPublisher, l as EventSchema, n as eventPlugin, o as EventDefinitionInput, r as RetryOptions, s as EventDefinitionOutput, t as EventPluginOptions, u as ValidationResult } from "../eventPlugin-iGrSEmwJ.mjs";
|
|
3
3
|
import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
|
|
4
|
-
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-
|
|
4
|
+
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-BW9UKLZM.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/events/eventTypes.d.ts
|
|
7
7
|
/**
|
|
@@ -24,7 +24,7 @@ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis
|
|
|
24
24
|
/** Suffixes for auto-emitted CRUD events */
|
|
25
25
|
declare const CRUD_EVENT_SUFFIXES: readonly ["created", "updated", "deleted"];
|
|
26
26
|
/** Type for CRUD event suffixes */
|
|
27
|
-
type CrudEventSuffix = typeof CRUD_EVENT_SUFFIXES[number];
|
|
27
|
+
type CrudEventSuffix = (typeof CRUD_EVENT_SUFFIXES)[number];
|
|
28
28
|
/**
|
|
29
29
|
* Build a CRUD event type string.
|
|
30
30
|
*
|
|
@@ -41,13 +41,78 @@ declare const ARC_LIFECYCLE_EVENTS: Readonly<{
|
|
|
41
41
|
readonly READY: "arc.ready";
|
|
42
42
|
}>;
|
|
43
43
|
/** Type for Arc lifecycle event names */
|
|
44
|
-
type ArcLifecycleEvent = typeof ARC_LIFECYCLE_EVENTS[keyof typeof ARC_LIFECYCLE_EVENTS];
|
|
44
|
+
type ArcLifecycleEvent = (typeof ARC_LIFECYCLE_EVENTS)[keyof typeof ARC_LIFECYCLE_EVENTS];
|
|
45
45
|
/** Cache-specific event types for observability and external triggers */
|
|
46
46
|
declare const CACHE_EVENTS: Readonly<{
|
|
47
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
48
|
readonly TAG_VERSION_BUMPED: "arc.cache.tag.bumped";
|
|
49
49
|
}>;
|
|
50
50
|
/** Type for cache event names */
|
|
51
|
-
type CacheEvent = typeof CACHE_EVENTS[keyof typeof CACHE_EVENTS];
|
|
51
|
+
type CacheEvent = (typeof CACHE_EVENTS)[keyof typeof CACHE_EVENTS];
|
|
52
52
|
//#endregion
|
|
53
|
-
|
|
53
|
+
//#region src/events/outbox.d.ts
|
|
54
|
+
interface OutboxStore {
|
|
55
|
+
/** Save event to outbox (called within business transaction) */
|
|
56
|
+
save(event: DomainEvent): Promise<void>;
|
|
57
|
+
/** Get pending (unrelayed) events, ordered FIFO */
|
|
58
|
+
getPending(limit: number): Promise<DomainEvent[]>;
|
|
59
|
+
/** Mark event as successfully relayed */
|
|
60
|
+
acknowledge(eventId: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Purge old acknowledged events (optional, DB-agnostic contract).
|
|
63
|
+
*
|
|
64
|
+
* Arc does **not** ship a concrete implementation — your store owns the
|
|
65
|
+
* cleanup strategy that fits your database:
|
|
66
|
+
*
|
|
67
|
+
* - **MongoDB:** TTL index on `acknowledgedAt` (automatic, zero-code)
|
|
68
|
+
* - **SQL:** Scheduled `DELETE FROM outbox WHERE acknowledged_at < :cutoff`
|
|
69
|
+
* - **Redis:** Key expiry (`EXPIRE`) on acknowledged entries
|
|
70
|
+
*
|
|
71
|
+
* Called by {@link EventOutbox.purge}. If not implemented, cleanup is
|
|
72
|
+
* entirely the app's responsibility via native DB tools.
|
|
73
|
+
*
|
|
74
|
+
* @param olderThanMs - Remove events acknowledged more than this many ms ago
|
|
75
|
+
* @returns Number of purged events
|
|
76
|
+
*/
|
|
77
|
+
purge?(olderThanMs: number): Promise<number>;
|
|
78
|
+
}
|
|
79
|
+
interface EventOutboxOptions {
|
|
80
|
+
/** Outbox store for persistence */
|
|
81
|
+
store: OutboxStore;
|
|
82
|
+
/** Transport to relay events to (optional — can relay later) */
|
|
83
|
+
transport?: EventTransport;
|
|
84
|
+
/** Max events per relay batch (default: 100) */
|
|
85
|
+
batchSize?: number;
|
|
86
|
+
}
|
|
87
|
+
declare class EventOutbox {
|
|
88
|
+
private readonly _store;
|
|
89
|
+
private readonly _transport?;
|
|
90
|
+
private readonly _batchSize;
|
|
91
|
+
constructor(opts: EventOutboxOptions);
|
|
92
|
+
/** Store event in outbox (call within your DB transaction) */
|
|
93
|
+
store(event: DomainEvent): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Relay pending events to transport.
|
|
96
|
+
*
|
|
97
|
+
* Processes events in FIFO order up to `batchSize`. Stops on the first
|
|
98
|
+
* transport failure — remaining events stay pending for the next relay call.
|
|
99
|
+
*
|
|
100
|
+
* @returns Number of successfully published events in this batch
|
|
101
|
+
*/
|
|
102
|
+
relay(): Promise<number>;
|
|
103
|
+
/**
|
|
104
|
+
* Purge old acknowledged events from the outbox store.
|
|
105
|
+
* Delegates to `store.purge()` if implemented; no-op otherwise.
|
|
106
|
+
* @param olderThanMs - Remove events acknowledged more than this many ms ago (default: 7 days)
|
|
107
|
+
* @returns Number of purged events, or 0 if store doesn't support purge
|
|
108
|
+
*/
|
|
109
|
+
purge(olderThanMs?: number): Promise<number>;
|
|
110
|
+
}
|
|
111
|
+
declare class MemoryOutboxStore implements OutboxStore {
|
|
112
|
+
private events;
|
|
113
|
+
save(event: DomainEvent): Promise<void>;
|
|
114
|
+
getPending(limit: number): Promise<DomainEvent[]>;
|
|
115
|
+
acknowledge(eventId: string): Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type DomainEvent, type EventDefinitionInput, type EventDefinitionOutput, type EventHandler, type EventLogger, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventSchema, type EventTransport, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxStore, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RetryOptions, type ValidationResult, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, withRetry };
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,5 +1,151 @@
|
|
|
1
|
-
import { a as MemoryEventTransport, i as withRetry, o as createEvent, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-
|
|
2
|
-
|
|
1
|
+
import { a as MemoryEventTransport, i as withRetry, o as createEvent, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-Ba00swHF.mjs";
|
|
2
|
+
//#region src/events/defineEvent.ts
|
|
3
|
+
/**
|
|
4
|
+
* defineEvent — Typed Event Definitions with Optional Schema Validation
|
|
5
|
+
*
|
|
6
|
+
* Provides:
|
|
7
|
+
* 1. defineEvent() — declare an event with name, schema, version, description
|
|
8
|
+
* 2. EventRegistry — catalog of all known events + payload validation
|
|
9
|
+
* 3. .create() helper — build DomainEvent with auto-generated metadata
|
|
10
|
+
*
|
|
11
|
+
* Schema validation uses a minimal JSON Schema subset (type, required, properties)
|
|
12
|
+
* to avoid pulling in AJV as a dependency. For full JSON Schema validation,
|
|
13
|
+
* users can provide their own validator via the registry.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { defineEvent, createEventRegistry } from '@classytic/arc/events';
|
|
18
|
+
*
|
|
19
|
+
* const OrderCreated = defineEvent({
|
|
20
|
+
* name: 'order.created',
|
|
21
|
+
* version: 1,
|
|
22
|
+
* schema: {
|
|
23
|
+
* type: 'object',
|
|
24
|
+
* properties: {
|
|
25
|
+
* orderId: { type: 'string' },
|
|
26
|
+
* total: { type: 'number' },
|
|
27
|
+
* },
|
|
28
|
+
* required: ['orderId', 'total'],
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Type-safe event creation
|
|
33
|
+
* const event = OrderCreated.create({ orderId: 'o-1', total: 100 });
|
|
34
|
+
* await fastify.events.publish(event.type, event.payload, event.meta);
|
|
35
|
+
*
|
|
36
|
+
* // Registry for introspection + validation
|
|
37
|
+
* const registry = createEventRegistry();
|
|
38
|
+
* registry.register(OrderCreated);
|
|
39
|
+
* const result = registry.validate('order.created', payload);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Define a typed event with optional schema validation.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const OrderCreated = defineEvent({
|
|
47
|
+
* name: 'order.created',
|
|
48
|
+
* schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* const event = OrderCreated.create({ orderId: '123' });
|
|
52
|
+
*/
|
|
53
|
+
function defineEvent(input) {
|
|
54
|
+
const { name, schema, version = 1, description } = input;
|
|
55
|
+
return {
|
|
56
|
+
name,
|
|
57
|
+
schema,
|
|
58
|
+
version,
|
|
59
|
+
description,
|
|
60
|
+
create(payload, meta) {
|
|
61
|
+
return createEvent(name, payload, meta);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create an event registry for cataloging and validating events.
|
|
67
|
+
*
|
|
68
|
+
* The registry is opt-in — unregistered events pass validation.
|
|
69
|
+
* This allows gradual adoption without breaking existing code.
|
|
70
|
+
*/
|
|
71
|
+
function createEventRegistry() {
|
|
72
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
73
|
+
function registryKey(name, version) {
|
|
74
|
+
return `${name}:v${version}`;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
register(definition) {
|
|
78
|
+
const key = registryKey(definition.name, definition.version);
|
|
79
|
+
if (definitions.has(key)) throw new Error(`Event '${definition.name}' v${definition.version} is already registered. Use a different version number for schema evolution.`);
|
|
80
|
+
definitions.set(key, definition);
|
|
81
|
+
},
|
|
82
|
+
get(name, version) {
|
|
83
|
+
if (version !== void 0) return definitions.get(registryKey(name, version));
|
|
84
|
+
let latest;
|
|
85
|
+
let latestVersion = -1;
|
|
86
|
+
for (const def of definitions.values()) if (def.name === name && def.version > latestVersion) {
|
|
87
|
+
latest = def;
|
|
88
|
+
latestVersion = def.version;
|
|
89
|
+
}
|
|
90
|
+
return latest;
|
|
91
|
+
},
|
|
92
|
+
catalog() {
|
|
93
|
+
return Array.from(definitions.values()).map((def) => ({
|
|
94
|
+
name: def.name,
|
|
95
|
+
version: def.version,
|
|
96
|
+
description: def.description,
|
|
97
|
+
schema: def.schema
|
|
98
|
+
}));
|
|
99
|
+
},
|
|
100
|
+
validate(name, payload) {
|
|
101
|
+
let latest;
|
|
102
|
+
let latestVersion = -1;
|
|
103
|
+
for (const def of definitions.values()) if (def.name === name && def.version > latestVersion) {
|
|
104
|
+
latest = def;
|
|
105
|
+
latestVersion = def.version;
|
|
106
|
+
}
|
|
107
|
+
if (!latest) return { valid: true };
|
|
108
|
+
if (!latest.schema) return { valid: true };
|
|
109
|
+
return validatePayload(payload, latest.schema);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Minimal JSON Schema validator — handles the common subset:
|
|
115
|
+
* - type: 'object'
|
|
116
|
+
* - required: string[]
|
|
117
|
+
* - properties with type checks
|
|
118
|
+
*
|
|
119
|
+
* For full JSON Schema validation (patterns, formats, $ref, etc.),
|
|
120
|
+
* use AJV directly or provide a custom validator.
|
|
121
|
+
*/
|
|
122
|
+
function validatePayload(payload, schema) {
|
|
123
|
+
const errors = [];
|
|
124
|
+
if (schema.type === "object") {
|
|
125
|
+
if (payload === null || payload === void 0 || typeof payload !== "object" || Array.isArray(payload)) return {
|
|
126
|
+
valid: false,
|
|
127
|
+
errors: ["Payload must be an object"]
|
|
128
|
+
};
|
|
129
|
+
const record = payload;
|
|
130
|
+
if (schema.required) {
|
|
131
|
+
for (const field of schema.required) if (!(field in record) || record[field] === void 0) errors.push(`Missing required field: '${field}'`);
|
|
132
|
+
}
|
|
133
|
+
if (schema.properties) {
|
|
134
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) if (key in record && record[key] !== void 0 && record[key] !== null) {
|
|
135
|
+
const expectedType = propSchema.type;
|
|
136
|
+
if (expectedType) {
|
|
137
|
+
const actualType = Array.isArray(record[key]) ? "array" : typeof record[key];
|
|
138
|
+
if (expectedType !== actualType) errors.push(`Field '${key}': expected ${expectedType}, got ${actualType}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return errors.length === 0 ? { valid: true } : {
|
|
144
|
+
valid: false,
|
|
145
|
+
errors
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
3
149
|
//#region src/events/eventTypes.ts
|
|
4
150
|
/**
|
|
5
151
|
* Event Type Constants and Helpers
|
|
@@ -46,6 +192,72 @@ const CACHE_EVENTS = Object.freeze({
|
|
|
46
192
|
VERSION_BUMPED: "arc.cache.version.bumped",
|
|
47
193
|
TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
|
|
48
194
|
});
|
|
49
|
-
|
|
50
195
|
//#endregion
|
|
51
|
-
|
|
196
|
+
//#region src/events/outbox.ts
|
|
197
|
+
/** Default outbox retention — acknowledged events older than this are eligible for purge */
|
|
198
|
+
const DEFAULT_OUTBOX_RETENTION_MS = 10080 * 60 * 1e3;
|
|
199
|
+
var EventOutbox = class {
|
|
200
|
+
_store;
|
|
201
|
+
_transport;
|
|
202
|
+
_batchSize;
|
|
203
|
+
constructor(opts) {
|
|
204
|
+
this._store = opts.store;
|
|
205
|
+
this._transport = opts.transport;
|
|
206
|
+
this._batchSize = opts.batchSize ?? 100;
|
|
207
|
+
}
|
|
208
|
+
/** Store event in outbox (call within your DB transaction) */
|
|
209
|
+
async store(event) {
|
|
210
|
+
await this._store.save(event);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Relay pending events to transport.
|
|
214
|
+
*
|
|
215
|
+
* Processes events in FIFO order up to `batchSize`. Stops on the first
|
|
216
|
+
* transport failure — remaining events stay pending for the next relay call.
|
|
217
|
+
*
|
|
218
|
+
* @returns Number of successfully published events in this batch
|
|
219
|
+
*/
|
|
220
|
+
async relay() {
|
|
221
|
+
if (!this._transport) return 0;
|
|
222
|
+
const pending = await this._store.getPending(this._batchSize);
|
|
223
|
+
let relayed = 0;
|
|
224
|
+
for (const event of pending) {
|
|
225
|
+
if (!event.type || !event.meta?.id) {
|
|
226
|
+
if (event.meta?.id) await this._store.acknowledge(event.meta.id);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
await this._transport.publish(event);
|
|
231
|
+
await this._store.acknowledge(event.meta.id);
|
|
232
|
+
relayed++;
|
|
233
|
+
} catch {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return relayed;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Purge old acknowledged events from the outbox store.
|
|
241
|
+
* Delegates to `store.purge()` if implemented; no-op otherwise.
|
|
242
|
+
* @param olderThanMs - Remove events acknowledged more than this many ms ago (default: 7 days)
|
|
243
|
+
* @returns Number of purged events, or 0 if store doesn't support purge
|
|
244
|
+
*/
|
|
245
|
+
async purge(olderThanMs = DEFAULT_OUTBOX_RETENTION_MS) {
|
|
246
|
+
if (!this._store.purge) return 0;
|
|
247
|
+
return this._store.purge(olderThanMs);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var MemoryOutboxStore = class {
|
|
251
|
+
events = [];
|
|
252
|
+
async save(event) {
|
|
253
|
+
this.events.push(event);
|
|
254
|
+
}
|
|
255
|
+
async getPending(limit) {
|
|
256
|
+
return this.events.slice(0, limit);
|
|
257
|
+
}
|
|
258
|
+
async acknowledge(eventId) {
|
|
259
|
+
this.events = this.events.filter((e) => e.meta.id !== eventId);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, EventOutbox, MemoryEventTransport, MemoryOutboxStore, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, withRetry };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-
|
|
1
|
+
import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-BW9UKLZM.mjs";
|
|
2
2
|
export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
|
|
@@ -11,6 +11,7 @@ var RedisStreamTransport = class {
|
|
|
11
11
|
claimTimeoutMs;
|
|
12
12
|
deadLetterStream;
|
|
13
13
|
maxLen;
|
|
14
|
+
maxPayloadBytes;
|
|
14
15
|
logger;
|
|
15
16
|
handlers = /* @__PURE__ */ new Map();
|
|
16
17
|
running = false;
|
|
@@ -27,9 +28,12 @@ var RedisStreamTransport = class {
|
|
|
27
28
|
this.claimTimeoutMs = options.claimTimeoutMs ?? 3e4;
|
|
28
29
|
this.deadLetterStream = options.deadLetterStream ?? "arc:events:dlq";
|
|
29
30
|
this.maxLen = options.maxLen ?? 1e4;
|
|
31
|
+
this.maxPayloadBytes = options.maxPayloadBytes ?? 1e6;
|
|
30
32
|
this.logger = options.logger ?? console;
|
|
31
33
|
}
|
|
32
34
|
async publish(event) {
|
|
35
|
+
const serialized = JSON.stringify(event);
|
|
36
|
+
if (serialized.length > this.maxPayloadBytes) throw new Error(`[RedisStreamTransport] Event payload (${serialized.length} bytes) exceeds limit (${this.maxPayloadBytes}). Consider breaking into smaller events or increasing maxPayloadBytes.`);
|
|
33
37
|
const args = [
|
|
34
38
|
this.stream,
|
|
35
39
|
...this.maxLen > 0 ? [
|
|
@@ -41,17 +45,20 @@ var RedisStreamTransport = class {
|
|
|
41
45
|
"type",
|
|
42
46
|
event.type,
|
|
43
47
|
"data",
|
|
44
|
-
|
|
48
|
+
serialized
|
|
45
49
|
];
|
|
46
50
|
await this.redis.xadd(...args);
|
|
47
51
|
}
|
|
48
52
|
async subscribe(pattern, handler) {
|
|
49
53
|
if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
|
|
50
|
-
this.handlers.get(pattern)
|
|
54
|
+
this.handlers.get(pattern)?.add(handler);
|
|
51
55
|
if (!this.running) {
|
|
52
56
|
await this.ensureGroup();
|
|
53
57
|
this.running = true;
|
|
54
|
-
this.pollPromise = this.pollLoop()
|
|
58
|
+
this.pollPromise = this.pollLoop().catch((err) => {
|
|
59
|
+
this.logger.error("[RedisStreamTransport] Poll loop crashed:", err);
|
|
60
|
+
this.running = false;
|
|
61
|
+
});
|
|
55
62
|
}
|
|
56
63
|
return () => {
|
|
57
64
|
const set = this.handlers.get(pattern);
|
|
@@ -124,10 +131,16 @@ var RedisStreamTransport = class {
|
|
|
124
131
|
}
|
|
125
132
|
let event;
|
|
126
133
|
try {
|
|
127
|
-
|
|
134
|
+
const parsed = JSON.parse(rawData, (key, value) => {
|
|
128
135
|
if (key === "timestamp" && typeof value === "string") return new Date(value);
|
|
129
136
|
return value;
|
|
130
137
|
});
|
|
138
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string" || !parsed.meta?.id) {
|
|
139
|
+
this.logger.warn("[RedisStreamTransport] Malformed event — missing type or meta.id, acking and skipping");
|
|
140
|
+
await this.redis.xack(this.stream, this.group, messageId);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
event = parsed;
|
|
131
144
|
} catch {
|
|
132
145
|
await this.redis.xack(this.stream, this.group, messageId);
|
|
133
146
|
return;
|
|
@@ -152,7 +165,7 @@ var RedisStreamTransport = class {
|
|
|
152
165
|
if (pattern === eventType) return true;
|
|
153
166
|
if (pattern.endsWith(".*")) {
|
|
154
167
|
const prefix = pattern.slice(0, -2);
|
|
155
|
-
return eventType.startsWith(prefix
|
|
168
|
+
return eventType.startsWith(`${prefix}.`);
|
|
156
169
|
}
|
|
157
170
|
return false;
|
|
158
171
|
}
|
|
@@ -172,6 +185,5 @@ var RedisStreamTransport = class {
|
|
|
172
185
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
173
186
|
}
|
|
174
187
|
};
|
|
175
|
-
|
|
176
188
|
//#endregion
|
|
177
|
-
export { RedisStreamTransport };
|
|
189
|
+
export { RedisStreamTransport };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "../../EventTransport-
|
|
1
|
+
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "../../EventTransport-wc5hSLik.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/events/transports/redis.d.ts
|
|
4
4
|
interface RedisLike {
|