@classytic/arc 2.11.2 → 2.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +20 -21
  3. package/bin/arc.js +2 -2
  4. package/dist/{BaseController-JNV08qOT.mjs → BaseController-swXruJ2_.mjs} +2 -2
  5. package/dist/EventTransport-BFQjw9pB.mjs +133 -0
  6. package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
  7. package/dist/{actionPermissions-C8YYU92K.mjs → actionPermissions-sUUKDhtP.mjs} +4 -2
  8. package/dist/adapters/index.d.mts +3 -3
  9. package/dist/adapters/index.mjs +2 -2
  10. package/dist/{adapters-D0tT2Tyo.mjs → adapters-DUUiiimH.mjs} +17 -2
  11. package/dist/audit/index.d.mts +2 -2
  12. package/dist/auth/index.d.mts +4 -4
  13. package/dist/auth/index.mjs +1 -1
  14. package/dist/auth/redis-session.d.mts +1 -1
  15. package/dist/cache/index.d.mts +3 -3
  16. package/dist/cli/commands/docs.mjs +1 -1
  17. package/dist/cli/commands/generate.d.mts +0 -2
  18. package/dist/cli/commands/generate.mjs +16 -16
  19. package/dist/cli/commands/init.mjs +149 -65
  20. package/dist/context/index.mjs +1 -1
  21. package/dist/core/index.d.mts +2 -2
  22. package/dist/core/index.mjs +3 -3
  23. package/dist/{core-DXdSSFW-.mjs → core-CbcQRIch.mjs} +25 -8
  24. package/dist/{createActionRouter-BwaSM0No.mjs → createActionRouter-CIKOcNA7.mjs} +74 -14
  25. package/dist/{createApp-P1d6rjPy.mjs → createApp-C9bRrqlX.mjs} +4 -6
  26. package/dist/defineEvent-D1Ky9M1D.mjs +188 -0
  27. package/dist/docs/index.d.mts +2 -2
  28. package/dist/docs/index.mjs +1 -1
  29. package/dist/{eventPlugin--5HIkdPU.mjs → eventPlugin-Cts2-Tfj.mjs} +9 -135
  30. package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-DDJoNEPL.d.mts} +34 -7
  31. package/dist/events/index.d.mts +164 -5
  32. package/dist/events/index.mjs +138 -182
  33. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  34. package/dist/events/transports/redis-stream-entry.mjs +204 -31
  35. package/dist/events/transports/redis.d.mts +1 -1
  36. package/dist/factory/index.d.mts +1 -1
  37. package/dist/factory/index.mjs +1 -1
  38. package/dist/{fields-C8Y0XLAu.d.mts → fields-BRjxOAFp.d.mts} +1 -1
  39. package/dist/hooks/index.d.mts +1 -1
  40. package/dist/idempotency/index.d.mts +3 -3
  41. package/dist/idempotency/index.mjs +1 -1
  42. package/dist/idempotency/redis.d.mts +1 -1
  43. package/dist/{index-6u4_Gg6G.d.mts → index-CXXRbnf8.d.mts} +51 -5
  44. package/dist/{index-DdQ3O9Pg.d.mts → index-D9t1KNaB.d.mts} +2 -2
  45. package/dist/{index-BbMrcvGp.d.mts → index-Rg8axYPz.d.mts} +12 -4
  46. package/dist/{index-BdXnTPRj.d.mts → index-m8mOOlFW.d.mts} +3 -3
  47. package/dist/{index-BYCqHCVu.d.mts → index-rHjXmJar.d.mts} +3 -3
  48. package/dist/index.d.mts +7 -7
  49. package/dist/index.mjs +7 -7
  50. package/dist/integrations/event-gateway.d.mts +2 -2
  51. package/dist/integrations/index.d.mts +2 -2
  52. package/dist/integrations/mcp/index.d.mts +2 -2
  53. package/dist/integrations/mcp/index.mjs +1 -1
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/integrations/websocket-redis.d.mts +1 -1
  57. package/dist/integrations/websocket.d.mts +1 -1
  58. package/dist/middleware/index.d.mts +1 -1
  59. package/dist/{openapi-C0L9ar7m.mjs → openapi-D7G1V7ex.mjs} +2 -2
  60. package/dist/org/index.d.mts +2 -2
  61. package/dist/permissions/index.d.mts +2 -2
  62. package/dist/permissions/index.mjs +1 -1
  63. package/dist/{permissions-B4vU9L0Q.mjs → permissions-gd_aUWrR.mjs} +42 -0
  64. package/dist/pipeline/index.d.mts +1 -1
  65. package/dist/plugins/index.d.mts +5 -5
  66. package/dist/plugins/index.mjs +1 -1
  67. package/dist/plugins/tracing-entry.d.mts +1 -1
  68. package/dist/plugins/tracing-entry.mjs +1 -1
  69. package/dist/presets/filesUpload.d.mts +4 -4
  70. package/dist/presets/filesUpload.mjs +1 -1
  71. package/dist/presets/index.d.mts +1 -1
  72. package/dist/presets/index.mjs +1 -1
  73. package/dist/presets/multiTenant.d.mts +1 -1
  74. package/dist/presets/search.d.mts +2 -2
  75. package/dist/presets/search.mjs +1 -1
  76. package/dist/{presets-k604Lj99.mjs → presets-Z7P5w4gF.mjs} +1 -1
  77. package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
  78. package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
  79. package/dist/redis-stream-xTGxB2bm.d.mts +232 -0
  80. package/dist/registry/index.d.mts +1 -1
  81. package/dist/{requestContext-CfRkaxwf.mjs → requestContext-C5XeK3VA.mjs} +15 -0
  82. package/dist/{resourceToTools--okX6QBr.mjs → resourceToTools-CxNmI6xF.mjs} +7 -6
  83. package/dist/{routerShared-DeESFp4a.mjs → routerShared-BqLRb5l7.mjs} +60 -3
  84. package/dist/scope/index.d.mts +2 -2
  85. package/dist/testing/index.d.mts +2 -2
  86. package/dist/testing/index.mjs +1 -1
  87. package/dist/testing/storageContract.d.mts +1 -1
  88. package/dist/types/index.d.mts +4 -4
  89. package/dist/types/storage.d.mts +1 -1
  90. package/dist/{types-9beEMe25.d.mts → types-BQ9TJQNy.d.mts} +1 -1
  91. package/dist/{types-BH7dEGvU.d.mts → types-D7KpfiL1.d.mts} +10 -10
  92. package/dist/utils/index.d.mts +1 -1
  93. package/dist/utils/index.mjs +1 -1
  94. package/dist/{utils-D3Yxnrwr.mjs → utils-CcYTj09l.mjs} +1 -1
  95. package/dist/{versioning-M9lNLhO8.d.mts → versioning-DsglKfM_.d.mts} +1 -1
  96. package/package.json +3 -1
  97. package/skills/arc/SKILL.md +409 -769
  98. package/skills/arc/references/events.md +489 -489
  99. package/dist/redis-stream-CM8TXTix.d.mts +0 -110
  100. /package/dist/{EventTransport-CfVEGaEl.d.mts → EventTransport-CYNUXdCJ.d.mts} +0 -0
  101. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BQQXZ_VR.d.mts} +0 -0
  102. /package/dist/{errorHandler-Co3lnVmJ.d.mts → errorHandler-DEWmGWPz.d.mts} +0 -0
  103. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
  104. /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
  105. /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.d.mts} +0 -0
  106. /package/dist/{pluralize-BneOJkpi.mjs → pluralize-CWP6MB39.mjs} +0 -0
  107. /package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-Dy2p4MxS.mjs} +0 -0
  108. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
  109. /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
  110. /package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-Cp4uKC1U.mjs} +0 -0
  111. /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
  112. /package/dist/{types-tgR4Pt8F.d.mts → types-DDyTPc6y.d.mts} +0 -0
  113. /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
@@ -0,0 +1,188 @@
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { r as createEvent } from "./EventTransport-BFQjw9pB.mjs";
3
+ //#region src/events/defineEvent.ts
4
+ /**
5
+ * defineEvent — Typed Event Definitions with Optional Schema Validation
6
+ *
7
+ * Provides:
8
+ * 1. defineEvent() — declare an event with name, schema, version, description
9
+ * 2. EventRegistry — catalog of all known events + payload validation
10
+ * 3. .create() helper — build DomainEvent with auto-generated metadata
11
+ *
12
+ * The built-in validator checks: object type, required fields, and top-level
13
+ * property types. It does NOT recurse into nested objects, validate arrays,
14
+ * enums, patterns, formats, or $ref. This is intentional — it's a lightweight
15
+ * guard, not a full JSON Schema engine.
16
+ *
17
+ * For full validation, pass a custom `validate` function to `createEventRegistry()`:
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import Ajv from 'ajv';
22
+ * const ajv = new Ajv();
23
+ *
24
+ * const registry = createEventRegistry({
25
+ * validate: (schema, payload) => {
26
+ * const valid = ajv.validate(schema, payload);
27
+ * return valid
28
+ * ? { valid: true }
29
+ * : { valid: false, errors: ajv.errorsText().split(', ') };
30
+ * },
31
+ * });
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { defineEvent, createEventRegistry } from '@classytic/arc/events';
37
+ *
38
+ * const OrderCreated = defineEvent({
39
+ * name: 'order.created',
40
+ * version: 1,
41
+ * schema: {
42
+ * type: 'object',
43
+ * properties: {
44
+ * orderId: { type: 'string' },
45
+ * total: { type: 'number' },
46
+ * },
47
+ * required: ['orderId', 'total'],
48
+ * },
49
+ * });
50
+ *
51
+ * // Type-safe event creation
52
+ * const event = OrderCreated.create({ orderId: 'o-1', total: 100 });
53
+ * await fastify.events.publish(event.type, event.payload, event.meta);
54
+ *
55
+ * // Registry for introspection + validation
56
+ * const registry = createEventRegistry();
57
+ * registry.register(OrderCreated);
58
+ * const result = registry.validate('order.created', payload);
59
+ * ```
60
+ */
61
+ var defineEvent_exports = /* @__PURE__ */ __exportAll({
62
+ createEventRegistry: () => createEventRegistry,
63
+ defineEvent: () => defineEvent
64
+ });
65
+ /**
66
+ * Define a typed event with optional schema validation.
67
+ *
68
+ * @example
69
+ * const OrderCreated = defineEvent({
70
+ * name: 'order.created',
71
+ * schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
72
+ * });
73
+ *
74
+ * const event = OrderCreated.create({ orderId: '123' });
75
+ */
76
+ function defineEvent(input) {
77
+ const { name, schema, version = 1, description } = input;
78
+ return {
79
+ name,
80
+ schema,
81
+ version,
82
+ description,
83
+ create(payload, meta) {
84
+ return createEvent(name, payload, {
85
+ schemaVersion: version,
86
+ ...meta
87
+ });
88
+ }
89
+ };
90
+ }
91
+ /**
92
+ * Create an event registry for cataloging and validating events.
93
+ *
94
+ * The registry is opt-in — unregistered events pass validation.
95
+ * This allows gradual adoption without breaking existing code.
96
+ *
97
+ * @param options.validate - Custom validator replacing the built-in minimal validator.
98
+ * The built-in validator only checks top-level object structure (type, required, property types).
99
+ * For nested objects, arrays, enums, patterns, or $ref, provide AJV or similar.
100
+ */
101
+ function createEventRegistry(options) {
102
+ const customValidator = options?.validate;
103
+ const definitions = /* @__PURE__ */ new Map();
104
+ function registryKey(name, version) {
105
+ return `${name}:v${version}`;
106
+ }
107
+ return {
108
+ register(definition) {
109
+ const key = registryKey(definition.name, definition.version);
110
+ if (definitions.has(key)) throw new Error(`Event '${definition.name}' v${definition.version} is already registered. Use a different version number for schema evolution.`);
111
+ definitions.set(key, definition);
112
+ },
113
+ get(name, version) {
114
+ if (version !== void 0) return definitions.get(registryKey(name, version));
115
+ let latest;
116
+ let latestVersion = -1;
117
+ for (const def of definitions.values()) if (def.name === name && def.version > latestVersion) {
118
+ latest = def;
119
+ latestVersion = def.version;
120
+ }
121
+ return latest;
122
+ },
123
+ catalog() {
124
+ return Array.from(definitions.values()).map((def) => ({
125
+ name: def.name,
126
+ version: def.version,
127
+ description: def.description,
128
+ schema: def.schema
129
+ }));
130
+ },
131
+ validate(name, payload, version) {
132
+ let target = version !== void 0 ? definitions.get(registryKey(name, version)) : void 0;
133
+ if (!target && version === void 0) {
134
+ let latestVersion = -1;
135
+ for (const candidate of definitions.values()) if (candidate.name === name && candidate.version > latestVersion) {
136
+ target = candidate;
137
+ latestVersion = candidate.version;
138
+ }
139
+ }
140
+ if (!target) return { valid: true };
141
+ if (!target.schema) return { valid: true };
142
+ return customValidator ? customValidator(target.schema, payload) : validatePayload(payload, target.schema);
143
+ }
144
+ };
145
+ }
146
+ /**
147
+ * Built-in minimal validator — lightweight guard, NOT a full JSON Schema engine.
148
+ *
149
+ * Checks:
150
+ * - payload is an object (not null, not array)
151
+ * - required fields are present
152
+ * - top-level property types match (string, number, boolean, array, object)
153
+ *
154
+ * Does NOT check:
155
+ * - nested object properties
156
+ * - array item types
157
+ * - enum, pattern, format, minLength, minimum, $ref
158
+ *
159
+ * For full validation, pass a custom `validate` function to `createEventRegistry()`.
160
+ */
161
+ function validatePayload(payload, schema) {
162
+ const errors = [];
163
+ if (schema.type === "object") {
164
+ if (payload === null || payload === void 0 || typeof payload !== "object" || Array.isArray(payload)) return {
165
+ valid: false,
166
+ errors: ["Payload must be an object"]
167
+ };
168
+ const record = payload;
169
+ if (schema.required) {
170
+ for (const field of schema.required) if (!(field in record) || record[field] === void 0) errors.push(`Missing required field: '${field}'`);
171
+ }
172
+ if (schema.properties) {
173
+ for (const [key, propSchema] of Object.entries(schema.properties)) if (key in record && record[key] !== void 0 && record[key] !== null) {
174
+ const expectedType = propSchema.type;
175
+ if (expectedType) {
176
+ const actualType = Array.isArray(record[key]) ? "array" : typeof record[key];
177
+ if (expectedType !== actualType) errors.push(`Field '${key}': expected ${expectedType}, got ${actualType}`);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return errors.length === 0 ? { valid: true } : {
183
+ valid: false,
184
+ errors
185
+ };
186
+ }
187
+ //#endregion
188
+ export { defineEvent as n, defineEvent_exports as r, createEventRegistry as t };
@@ -1,5 +1,5 @@
1
- import { p as RegistryEntry } from "../index-6u4_Gg6G.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
1
+ import { p as RegistryEntry } from "../index-CXXRbnf8.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/docs/openapi.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as getUserRoles } from "../types-DV9WDfeg.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-C0L9ar7m.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-D7G1V7ex.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,144 +1,18 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { t as requestContext } from "./requestContext-CfRkaxwf.mjs";
2
+ import { t as requestContext } from "./requestContext-C5XeK3VA.mjs";
3
+ import { r as createEvent, t as MemoryEventTransport } from "./EventTransport-BFQjw9pB.mjs";
3
4
  import fp from "fastify-plugin";
4
- //#region src/events/EventTransport.ts
5
- /**
6
- * In-memory event transport (default)
7
- * Events are delivered synchronously within the process.
8
- * Not suitable for multi-instance deployments.
9
- */
10
- var MemoryEventTransport = class {
11
- name = "memory";
12
- handlers = /* @__PURE__ */ new Map();
13
- logger;
14
- constructor(options) {
15
- this.logger = options?.logger ?? console;
16
- }
17
- async publish(event) {
18
- const exactHandlers = this.handlers.get(event.type) ?? /* @__PURE__ */ new Set();
19
- const wildcardHandlers = this.handlers.get("*") ?? /* @__PURE__ */ new Set();
20
- const patternHandlers = /* @__PURE__ */ new Set();
21
- for (const [pattern, handlers] of this.handlers.entries()) if (pattern.endsWith(".*")) {
22
- const prefix = pattern.slice(0, -2);
23
- if (event.type.startsWith(`${prefix}.`)) for (const h of handlers) patternHandlers.add(h);
24
- }
25
- const allHandlers = new Set([
26
- ...exactHandlers,
27
- ...wildcardHandlers,
28
- ...patternHandlers
29
- ]);
30
- for (const handler of allHandlers) try {
31
- await handler(event);
32
- } catch (err) {
33
- this.logger.error(`[EventTransport] Handler error for ${event.type}:`, err);
34
- }
35
- }
36
- /**
37
- * Reference `publishMany` implementation — delegates to `publish()` in order.
38
- *
39
- * Production transports (Kafka, Redis pipeline, SQS batch) should override
40
- * this with a single batched network call. Memory transport has nothing to
41
- * batch, so we just loop — the loop still returns a proper result map so
42
- * `EventOutbox.relay` can exercise the batched code path in tests.
43
- */
44
- async publishMany(events) {
45
- const results = /* @__PURE__ */ new Map();
46
- for (const event of events) try {
47
- await this.publish(event);
48
- results.set(event.meta.id, null);
49
- } catch (err) {
50
- results.set(event.meta.id, err instanceof Error ? err : new Error(String(err)));
51
- }
52
- return results;
53
- }
54
- async subscribe(pattern, handler) {
55
- if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
56
- this.handlers.get(pattern)?.add(handler);
57
- return () => {
58
- const set = this.handlers.get(pattern);
59
- if (set) {
60
- set.delete(handler);
61
- if (set.size === 0) this.handlers.delete(pattern);
62
- }
63
- };
64
- }
65
- async close() {
66
- this.handlers.clear();
67
- }
68
- };
69
- /**
70
- * Create a domain event with auto-generated metadata.
71
- *
72
- * `id` and `timestamp` are filled in; everything else is caller-controlled.
73
- * Set `schemaVersion` explicitly for any event type you plan to evolve.
74
- */
75
- function createEvent(type, payload, meta) {
76
- return {
77
- type,
78
- payload,
79
- meta: {
80
- id: crypto.randomUUID(),
81
- timestamp: /* @__PURE__ */ new Date(),
82
- ...meta
83
- }
84
- };
85
- }
86
- /**
87
- * Create a child event that chains causation from a parent event.
88
- *
89
- * Rules:
90
- * - `causationId` is set to the parent's `id` (direct cause)
91
- * - `correlationId` is inherited from the parent if set, else falls back
92
- * to the parent's `id` (root correlation)
93
- * - `userId` / `organizationId` are inherited when not overridden so the
94
- * whole chain stays scoped to the originating principal/tenant
95
- *
96
- * Caller-supplied `meta` wins over inherited fields — pass `{ userId: newActor }`
97
- * to override when a subsystem acts on behalf of a different principal.
98
- *
99
- * @example
100
- * ```typescript
101
- * const orderPlaced = createEvent('order.placed', { orderId: 'o1' }, {
102
- * correlationId: req.id, userId: user.id,
103
- * });
104
- * await events.publish(orderPlaced);
105
- *
106
- * // Downstream handler emits a child event:
107
- * const reserved = createChildEvent(orderPlaced, 'inventory.reserved', {
108
- * orderId: 'o1', skus: ['sku-1', 'sku-2'],
109
- * });
110
- * // reserved.meta.causationId === orderPlaced.meta.id
111
- * // reserved.meta.correlationId === orderPlaced.meta.correlationId
112
- * // reserved.meta.userId === user.id (inherited)
113
- * ```
114
- */
115
- function createChildEvent(parent, type, payload, meta) {
116
- const inherited = {
117
- correlationId: parent.meta.correlationId ?? parent.meta.id,
118
- causationId: parent.meta.id
119
- };
120
- if (parent.meta.userId !== void 0) inherited.userId = parent.meta.userId;
121
- if (parent.meta.organizationId !== void 0) inherited.organizationId = parent.meta.organizationId;
122
- if (parent.meta.source !== void 0) inherited.source = parent.meta.source;
123
- if (parent.meta.idempotencyKey !== void 0) inherited.idempotencyKey = parent.meta.idempotencyKey;
124
- return {
125
- type,
126
- payload,
127
- meta: {
128
- id: crypto.randomUUID(),
129
- timestamp: /* @__PURE__ */ new Date(),
130
- ...inherited,
131
- ...meta
132
- }
133
- };
134
- }
135
- //#endregion
136
5
  //#region src/events/retry.ts
137
6
  /**
138
7
  * Wrap an event handler with retry logic and dead letter support.
139
8
  *
140
9
  * On failure, retries with exponential backoff (with jitter).
141
10
  * After all retries exhausted, calls `onDead` callback if provided.
11
+ *
12
+ * Generic in the payload type `T` so composing with `wrapWithSchema<T>` /
13
+ * `subscribeWithSchema<T>` doesn't force a cast at the boundary — the inner
14
+ * `handler: EventHandler<T>` flows through to the returned wrapper. Defaults
15
+ * to `unknown` for raw `subscribe(pattern, withRetry(...))` call sites.
142
16
  */
143
17
  function withRetry(handler, options = {}) {
144
18
  const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, transport, onDead, name, logger = console } = options;
@@ -253,7 +127,7 @@ const eventPlugin = async (fastify, opts = {}) => {
253
127
  correlationId: event.meta.correlationId
254
128
  }, "Publishing event");
255
129
  if (registry && validateMode !== "off") {
256
- const result = registry.validate(type, payload);
130
+ const result = registry.validate(type, payload, event.meta.schemaVersion);
257
131
  if (!result.valid) {
258
132
  const msg = `[Arc Events] Event '${type}' payload validation failed: ${result.errors?.join("; ")}`;
259
133
  if (validateMode === "reject") throw new Error(msg);
@@ -321,4 +195,4 @@ var eventPlugin_default = fp(eventPlugin, {
321
195
  fastify: "5.x"
322
196
  });
323
197
  //#endregion
324
- export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createChildEvent as o, createDeadLetterPublisher as r, createEvent as s, eventPlugin as t };
198
+ export { withRetry as i, eventPlugin_exports as n, createDeadLetterPublisher as r, eventPlugin as t };
@@ -1,4 +1,4 @@
1
- import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "./EventTransport-CfVEGaEl.mjs";
1
+ import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "./EventTransport-CYNUXdCJ.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/events/defineEvent.d.ts
@@ -73,8 +73,15 @@ interface EventRegistry {
73
73
  description?: string;
74
74
  schema?: EventSchema;
75
75
  }>;
76
- /** Validate a payload against a registered event's schema */
77
- validate(name: string, payload: unknown): ValidationResult;
76
+ /**
77
+ * Validate a payload against a registered event's schema.
78
+ *
79
+ * @param version - Optional schema version. When set, validation runs
80
+ * against that exact version (use during migrations: producer A still
81
+ * on v1 must validate against v1's schema even if v2 is registered).
82
+ * When omitted, validates against the latest registered version.
83
+ */
84
+ validate(name: string, payload: unknown, version?: number): ValidationResult;
78
85
  }
79
86
  /**
80
87
  * Define a typed event with optional schema validation.
@@ -155,8 +162,13 @@ interface RetryOptions {
155
162
  *
156
163
  * On failure, retries with exponential backoff (with jitter).
157
164
  * After all retries exhausted, calls `onDead` callback if provided.
165
+ *
166
+ * Generic in the payload type `T` so composing with `wrapWithSchema<T>` /
167
+ * `subscribeWithSchema<T>` doesn't force a cast at the boundary — the inner
168
+ * `handler: EventHandler<T>` flows through to the returned wrapper. Defaults
169
+ * to `unknown` for raw `subscribe(pattern, withRetry(...))` call sites.
158
170
  */
159
- declare function withRetry(handler: EventHandler, options?: RetryOptions): EventHandler;
171
+ declare function withRetry<T = unknown>(handler: EventHandler<T>, options?: RetryOptions): EventHandler<T>;
160
172
  /**
161
173
  * Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
162
174
  *
@@ -203,9 +215,24 @@ interface EventPluginOptions {
203
215
  */
204
216
  failOpen?: boolean;
205
217
  /**
206
- * Write-Ahead Log (WAL) configuration for at-least-once delivery guarantees.
207
- * If provided, events will be saved to the WAL *before* passing to the transport.
208
- * After a successful publish, they are acknowledged.
218
+ * Low-level write-ahead hook called BEFORE the transport publish, with an
219
+ * optional acknowledge() called AFTER a successful publish.
220
+ *
221
+ * **Important**: this is NOT at-least-once delivery on its own. If
222
+ * `transport.publish()` throws after `wal.save()`, the saved row stays
223
+ * but arc does NOT relay it on next boot — there is no replay loop here.
224
+ * For at-least-once you must EITHER:
225
+ *
226
+ * 1. Run a relay loop yourself (read unacknowledged WAL rows on boot,
227
+ * republish, ack on success), or
228
+ * 2. Use `EventOutbox` ([./outbox.ts]) — `outbox.relay()` is the
229
+ * production-grade at-least-once primitive with claim/lease,
230
+ * retry/DLQ, multi-worker safety, and `repository`-backed durable
231
+ * storage. New code should prefer `EventOutbox` over `wal`.
232
+ *
233
+ * The `wal` slot is kept for hosts that want to integrate with custom
234
+ * write-ahead infrastructure (Kafka producer transactions, S3 batch
235
+ * archives, debug audit logs) without arc's outbox claim/lease semantics.
209
236
  */
210
237
  wal?: {
211
238
  save: (event: DomainEvent) => Promise<void>;
@@ -1,8 +1,8 @@
1
- import { jn as RepositoryLike } from "../index-6u4_Gg6G.mjs";
2
- import { a as EventMeta, c as MemoryEventTransportOptions, d as createEvent, i as EventLogger, l as PublishManyResult, n as DomainEvent, o as EventTransport, r as EventHandler, s as MemoryEventTransport, t as DeadLetteredEvent, u as createChildEvent } from "../EventTransport-CfVEGaEl.mjs";
3
- import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-CUNjYYRY.mjs";
1
+ import { Mn as RepositoryLike } from "../index-CXXRbnf8.mjs";
2
+ import { a as EventMeta, c as MemoryEventTransportOptions, d as createEvent, i as EventLogger, l as PublishManyResult, n as DomainEvent, o as EventTransport, r as EventHandler, s as MemoryEventTransport, t as DeadLetteredEvent, u as createChildEvent } from "../EventTransport-CYNUXdCJ.mjs";
3
+ import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-DDJoNEPL.mjs";
4
4
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
5
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-CM8TXTix.mjs";
5
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-xTGxB2bm.mjs";
6
6
 
7
7
  //#region src/events/eventTypes.d.ts
8
8
  /**
@@ -600,4 +600,163 @@ declare function exponentialBackoff(options: ExponentialBackoffOptions): Date;
600
600
  //#region src/events/repository-outbox-adapter.d.ts
601
601
  declare function repositoryAsOutboxStore(repository: RepositoryLike): OutboxStore;
602
602
  //#endregion
603
- export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type DeadLetteredEvent, type DomainEvent, type EventDefinitionInput, type EventDefinitionOutput, type EventHandler, type EventLogger, type EventMeta, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type EventTransport, type ExponentialBackoffOptions, InvalidOutboxEventError, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxAcknowledgeOptions, type OutboxClaimOptions, type OutboxErrorInfo, type OutboxFailOptions, type OutboxFailureContext, type OutboxFailureDecision, type OutboxFailurePolicy, OutboxOwnershipError, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type OutboxStore, type OutboxWriteOptions, type PublishManyResult, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RetryOptions, type ValidationResult, createChildEvent, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, withRetry };
603
+ //#region src/events/subscribe-helpers.d.ts
604
+ /**
605
+ * Extract the payload type from an `EventDefinitionOutput<T>`.
606
+ *
607
+ * `defineEvent<T>` already threads `T` through `.create(payload: T, ...)`, but
608
+ * there's no exposed way to recover `T` for use in handler signatures, factory
609
+ * helpers, or test fixtures. `PayloadOf<typeof OrderPaid>` closes the loop
610
+ * without forcing every host to define their own copy.
611
+ *
612
+ * @example
613
+ * ```ts
614
+ * const OrderPaid = defineEvent<{ orderId: string; total: number }>({
615
+ * name: 'order.paid',
616
+ * schema: { type: 'object', required: ['orderId', 'total'] },
617
+ * });
618
+ * type OrderPaidPayload = PayloadOf<typeof OrderPaid>;
619
+ * // ^? { orderId: string; total: number }
620
+ * ```
621
+ */
622
+ type PayloadOf<D> = D extends EventDefinitionOutput<infer T> ? T : never;
623
+ interface WrapWithSchemaOptions<T> {
624
+ /**
625
+ * Custom validator. Overrides the built-in lookup. Use this to plug AJV /
626
+ * Zod / TypeBox in. Same shape as `EventRegistryOptions.validate`.
627
+ *
628
+ * Resolution order:
629
+ * 1. `validate` (this option)
630
+ * 2. `registry.validate(definition.name, payload)` — uses whatever
631
+ * validator the registry was configured with
632
+ * 3. Built-in minimal validator (top-level `required` + property types)
633
+ */
634
+ validate?: CustomValidator;
635
+ /**
636
+ * Optional registry — when set and `validate` is omitted, validation routes
637
+ * through `registry.validate(definition.name, payload)`. Lets the subscriber
638
+ * use the same configured validator (AJV, custom) the publish side uses
639
+ * via `eventPlugin({ registry })`.
640
+ */
641
+ registry?: EventRegistry;
642
+ /**
643
+ * Called when payload validation fails. Default behaviour: log a warning
644
+ * with the event's id/type/errors and skip the handler (the event is NOT
645
+ * acknowledged as a failure, since the handler never ran — matches `withRetry`'s
646
+ * `onDead` semantics for terminal failures, but at the validation boundary).
647
+ *
648
+ * Receives the raw event (untyped — the payload is by definition not the
649
+ * declared shape) plus the validation errors array.
650
+ */
651
+ onInvalid?: (event: DomainEvent<unknown>, errors: string[]) => void | Promise<void>;
652
+ /**
653
+ * Logger for invalid-payload warnings. Pass `fastify.log` to integrate
654
+ * with the application logger. Default: `console`.
655
+ */
656
+ logger?: EventLogger;
657
+ /**
658
+ * Optional name for log output (otherwise the definition name is used).
659
+ */
660
+ name?: string;
661
+ }
662
+ /**
663
+ * Pure handler wrapper — returns a new `EventHandler` that validates
664
+ * `event.payload` against the definition's schema before invoking the handler.
665
+ *
666
+ * The returned handler's input is `DomainEvent<unknown>` (since the transport
667
+ * delivers untyped events) but the inner `handler` receives `DomainEvent<T>`.
668
+ * No cast at the call site.
669
+ *
670
+ * @example
671
+ * ```ts
672
+ * await fastify.events.subscribe(
673
+ * OrderPaid.name,
674
+ * wrapWithSchema(OrderPaid, async (event) => {
675
+ * // event.payload is typed via the registered schema — no cast.
676
+ * await postSalesEntry(event.payload.orderId, event.payload.total);
677
+ * }),
678
+ * );
679
+ * ```
680
+ */
681
+ declare function wrapWithSchema<T>(definition: EventDefinitionOutput<T>, handler: EventHandler<T>, options?: WrapWithSchemaOptions<T>): EventHandler<unknown>;
682
+ /**
683
+ * Convenience: validate + subscribe in one call. Equivalent to
684
+ * `fastify.events.subscribe(definition.name, wrapWithSchema(definition, handler, options))`.
685
+ *
686
+ * Returns the unsubscribe function from the underlying transport.
687
+ *
688
+ * @example
689
+ * ```ts
690
+ * await subscribeWithSchema(fastify, OrderPaid, async (event) => {
691
+ * await postSalesEntry(event.payload.orderId, event.payload.total);
692
+ * });
693
+ *
694
+ * // Compose with withRetry — schema validation runs FIRST, then retry on
695
+ * // handler failure. Invalid payloads skip without burning retry attempts.
696
+ * await subscribeWithSchema(
697
+ * fastify,
698
+ * OrderPaid,
699
+ * withRetry(handler, { maxRetries: 3 }),
700
+ * );
701
+ * ```
702
+ */
703
+ declare function subscribeWithSchema<T>(fastify: FastifyEventBus, definition: EventDefinitionOutput<T>, handler: EventHandler<T>, options?: WrapWithSchemaOptions<T>): Promise<() => void>;
704
+ interface WrapWithBoundaryOptions {
705
+ /**
706
+ * Called when the handler throws. Default behaviour: log the error with
707
+ * `{ err, event: event.type, eventId: event.meta.id }` and swallow.
708
+ *
709
+ * Use this to push metrics (`statsd.increment('handler.error', { type })`)
710
+ * or alert on specific event types.
711
+ */
712
+ onError?: (err: Error, event: DomainEvent) => void | Promise<void>;
713
+ /**
714
+ * Logger for handler errors. Pass `fastify.log` to integrate with the
715
+ * application logger. Default: `console`.
716
+ */
717
+ logger?: EventLogger;
718
+ /**
719
+ * Optional name for log output (otherwise the handler's `.name` is used).
720
+ */
721
+ name?: string;
722
+ }
723
+ /**
724
+ * Pure handler wrapper — returns a new `EventHandler` that catches handler
725
+ * exceptions and routes them to `onError` (or logs and swallows). For
726
+ * projection / cache-invalidation / fire-and-forget handlers where retry
727
+ * would just delay the next-event resync, and where one bad event must NOT
728
+ * stop processing of subsequent events.
729
+ *
730
+ * Lighter than `withRetry`: no exponential backoff, no DLQ. Composes with
731
+ * `withRetry` if you want both ("retry, then log if exhausted, never throw").
732
+ *
733
+ * @example
734
+ * ```ts
735
+ * await fastify.events.subscribe(
736
+ * 'product:variants.changed',
737
+ * wrapWithBoundary(async (event) => {
738
+ * cache.invalidate(event.payload.productId);
739
+ * }),
740
+ * );
741
+ * ```
742
+ */
743
+ declare function wrapWithBoundary(handler: EventHandler, options?: WrapWithBoundaryOptions): EventHandler;
744
+ /**
745
+ * Convenience: subscribe + error-boundary in one call. Equivalent to
746
+ * `fastify.events.subscribe(pattern, wrapWithBoundary(handler, options))`.
747
+ *
748
+ * Returns the unsubscribe function from the underlying transport.
749
+ */
750
+ declare function subscribeWithBoundary(fastify: FastifyEventBus, pattern: string, handler: EventHandler, options?: WrapWithBoundaryOptions): Promise<() => void>;
751
+ /**
752
+ * Structural type — accepts anything with the `events.subscribe` method,
753
+ * including `FastifyInstance` (declaration-merged in `eventPlugin.ts`) and
754
+ * test doubles. Avoids importing the full Fastify type in this module.
755
+ */
756
+ interface FastifyEventBus {
757
+ events: {
758
+ subscribe: (pattern: string, handler: EventHandler) => Promise<() => void>;
759
+ };
760
+ }
761
+ //#endregion
762
+ export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type DeadLetteredEvent, type DomainEvent, type EventDefinitionInput, type EventDefinitionOutput, type EventHandler, type EventLogger, type EventMeta, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type EventTransport, type ExponentialBackoffOptions, InvalidOutboxEventError, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxAcknowledgeOptions, type OutboxClaimOptions, type OutboxErrorInfo, type OutboxFailOptions, type OutboxFailureContext, type OutboxFailureDecision, type OutboxFailurePolicy, OutboxOwnershipError, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type OutboxStore, type OutboxWriteOptions, type PayloadOf, type PublishManyResult, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createChildEvent, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };