@classytic/arc 2.8.5 → 2.9.1

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 (140) hide show
  1. package/README.md +88 -5
  2. package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-Vu2yc56T.mjs} +188 -102
  3. package/dist/EventTransport-CqZ8FyM_.d.mts +293 -0
  4. package/dist/adapters/index.d.mts +2 -2
  5. package/dist/audit/index.d.mts +100 -11
  6. package/dist/audit/index.mjs +71 -18
  7. package/dist/auth/index.d.mts +16 -8
  8. package/dist/auth/index.mjs +13 -6
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi--rdY15Ld.mjs} +1 -1
  11. package/dist/cache/index.d.mts +2 -2
  12. package/dist/cache/index.mjs +2 -2
  13. package/dist/cli/commands/docs.mjs +2 -2
  14. package/dist/cli/commands/introspect.mjs +1 -1
  15. package/dist/core/index.d.mts +3 -3
  16. package/dist/core/index.mjs +4 -5
  17. package/dist/{core-F0QoWBt2.mjs → core-DNncu0xF.mjs} +1 -1
  18. package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-DH1YFL9m.mjs} +3 -3
  19. package/dist/{createApp-B1EY8zxa.mjs → createApp-CBJUJKGP.mjs} +13 -12
  20. package/dist/{defineResource-tcgySDo1.mjs → defineResource-C__jkwvs.mjs} +22 -57
  21. package/dist/docs/index.d.mts +2 -2
  22. package/dist/docs/index.mjs +1 -1
  23. package/dist/dynamic/index.d.mts +1 -1
  24. package/dist/dynamic/index.mjs +3 -3
  25. package/dist/{elevation-DtFxrG0s.mjs → elevation-DxQ6ACbt.mjs} +21 -7
  26. package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-CZDW4EXS.mjs} +59 -7
  27. package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DixGcttC.d.mts} +37 -2
  28. package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-BxvaCIZF.d.mts} +14 -2
  29. package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-Dl7MoVWH.mjs} +83 -5
  30. package/dist/events/index.d.mts +147 -36
  31. package/dist/events/index.mjs +338 -101
  32. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  33. package/dist/events/transports/redis.d.mts +1 -1
  34. package/dist/factory/index.d.mts +1 -1
  35. package/dist/factory/index.mjs +2 -2
  36. package/dist/{fields-DpZQa_Q3.d.mts → fields-BC7zcmI9.d.mts} +15 -3
  37. package/dist/{fields-ipsbIRPK.mjs → fields-CU6FlaDV.mjs} +18 -5
  38. package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-q8oHt--L.mjs} +65 -7
  39. package/dist/hooks/index.d.mts +1 -1
  40. package/dist/hooks/index.mjs +1 -1
  41. package/dist/idempotency/index.d.mts +29 -5
  42. package/dist/idempotency/index.mjs +111 -2
  43. package/dist/idempotency/redis.d.mts +1 -1
  44. package/dist/{index-BLXBmWud.d.mts → index-C-xjcA6F.d.mts} +1 -1
  45. package/dist/{index-DtDzOBn8.d.mts → index-Cibkchnx.d.mts} +3 -134
  46. package/dist/{index-C1meYuDn.d.mts → index-CtGKT0lf.d.mts} +1 -1
  47. package/dist/index.d.mts +7 -7
  48. package/dist/index.mjs +9 -9
  49. package/dist/integrations/event-gateway.d.mts +1 -1
  50. package/dist/integrations/event-gateway.mjs +1 -1
  51. package/dist/integrations/index.d.mts +1 -1
  52. package/dist/integrations/mcp/index.d.mts +26 -8
  53. package/dist/integrations/mcp/index.mjs +96 -17
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/integrations/webhooks.d.mts +5 -0
  57. package/dist/integrations/webhooks.mjs +6 -0
  58. package/dist/{interface-CMRutPfe.d.mts → interface-YrWsmKqE.d.mts} +287 -179
  59. package/dist/{openapi-CbKUJY_m.mjs → openapi-CXuTG1M9.mjs} +2 -2
  60. package/dist/org/index.d.mts +1 -1
  61. package/dist/permissions/index.d.mts +2 -2
  62. package/dist/permissions/index.mjs +3 -3
  63. package/dist/{permissions-CH4cNwJi.mjs → permissions-oNZawnkR.mjs} +1 -1
  64. package/dist/plugins/index.d.mts +7 -7
  65. package/dist/plugins/index.mjs +11 -11
  66. package/dist/plugins/response-cache.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/policies/index.d.mts +25 -32
  70. package/dist/presets/filesUpload.d.mts +26 -4
  71. package/dist/presets/filesUpload.mjs +1 -1
  72. package/dist/presets/index.d.mts +3 -2
  73. package/dist/presets/index.mjs +4 -3
  74. package/dist/presets/multiTenant.d.mts +1 -1
  75. package/dist/presets/multiTenant.mjs +1 -1
  76. package/dist/presets/search.d.mts +91 -0
  77. package/dist/presets/search.mjs +150 -0
  78. package/dist/{presets-C2xgzW6x.mjs → presets-hM4WhNWY.mjs} +1 -1
  79. package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-CnTZZTC5.d.mts} +1 -1
  80. package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DbUVroUG.mjs} +2 -2
  81. package/dist/{redis-BM00zaPB.d.mts → redis-MXLp1oOf.d.mts} +1 -1
  82. package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-Bz-4q96t.d.mts} +1 -1
  83. package/dist/registry/index.d.mts +1 -1
  84. package/dist/registry/index.mjs +2 -2
  85. package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-C3cWymnW.mjs} +64 -47
  86. package/dist/rpc/index.d.mts +1 -1
  87. package/dist/rpc/index.mjs +1 -1
  88. package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
  89. package/dist/scope/index.mjs +1 -1
  90. package/dist/{sse-Ad7ypl9e.mjs → sse-CJpt7LGI.mjs} +1 -1
  91. package/dist/store-helpers-DFiZl5TL.mjs +57 -0
  92. package/dist/testing/index.d.mts +5 -14
  93. package/dist/testing/index.mjs +21 -75
  94. package/dist/testing/storageContract.d.mts +1 -1
  95. package/dist/types/index.d.mts +2 -2
  96. package/dist/types/storage.d.mts +1 -1
  97. package/dist/{types-BsbNMEDR.d.mts → types-CoSzA-s-.d.mts} +1 -1
  98. package/dist/{types-Ch9pTQbf.d.mts → types-CunEX4UX.d.mts} +10 -8
  99. package/dist/utils/index.d.mts +4 -4
  100. package/dist/utils/index.mjs +6 -6
  101. package/dist/{utils-yYT3HDXt.mjs → utils-B7FuRr9w.mjs} +1 -1
  102. package/package.json +8 -11
  103. package/skills/arc/SKILL.md +92 -14
  104. package/skills/arc/references/auth.md +94 -0
  105. package/skills/arc/references/events.md +200 -12
  106. package/skills/arc/references/mcp.md +4 -17
  107. package/skills/arc/references/multi-tenancy.md +43 -0
  108. package/skills/arc/references/production.md +34 -19
  109. package/dist/EventTransport-BXja8NOc.d.mts +0 -135
  110. package/dist/audit/mongodb.d.mts +0 -2
  111. package/dist/audit/mongodb.mjs +0 -2
  112. package/dist/idempotency/mongodb.d.mts +0 -2
  113. package/dist/idempotency/mongodb.mjs +0 -123
  114. package/dist/mongodb-BsP-WbhN.d.mts +0 -127
  115. package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
  116. package/dist/mongodb-Utc5k_-0.mjs +0 -90
  117. /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BjFu7zf1.mjs} +0 -0
  118. /package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-Dq3_zBQP.mjs} +0 -0
  119. /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-bqGpo9ML.mjs} +0 -0
  120. /package/dist/{caching-IMuYVjTL.mjs → caching-CjybdRwx.mjs} +0 -0
  121. /package/dist/{circuitBreaker-dTtG-UyS.d.mts → circuitBreaker-CvXkjfrW.d.mts} +0 -0
  122. /package/dist/{circuitBreaker-cmi5XDv5.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
  123. /package/dist/{errors-Ck2h67pm.d.mts → errors-BI8kEKsO.d.mts} +0 -0
  124. /package/dist/{errors-BF2bIOIS.mjs → errors-CqWnSqM-.mjs} +0 -0
  125. /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
  126. /package/dist/{interface-DfLGcus7.d.mts → interface-B-pe8fhj.d.mts} +0 -0
  127. /package/dist/{interface-4y979v99.d.mts → interface-DplgQO2e.d.mts} +0 -0
  128. /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-Bksk8ydA.mjs} +0 -0
  129. /package/dist/{logger-D1YrIImS.mjs → logger-CDjpjySd.mjs} +0 -0
  130. /package/dist/{memory-Cp7_cAko.mjs → memory-BFAYkf8H.mjs} +0 -0
  131. /package/dist/{metrics-B-PU4-Yu.mjs → metrics-TuOmguhi.mjs} +0 -0
  132. /package/dist/{queryParser-CgCtsjti.mjs → queryParser-Cs-6SHQK.mjs} +0 -0
  133. /package/dist/{registry-BiTKT1Dg.mjs → registry-B0Wl7uVV.mjs} +0 -0
  134. /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
  135. /package/dist/{requestContext-DYvHl113.mjs → requestContext-DYtmNpm5.mjs} +0 -0
  136. /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
  137. /package/dist/{storage-Dfzt4VTl.d.mts → storage-BwGQXUpd.d.mts} +0 -0
  138. /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
  139. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
  140. /package/dist/{versioning-CDugduqI.mjs → versioning-Cm8qoFDg.mjs} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { t as requestContext } from "./requestContext-DYvHl113.mjs";
2
+ import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/events/EventTransport.ts
5
5
  /**
@@ -67,7 +67,10 @@ var MemoryEventTransport = class {
67
67
  }
68
68
  };
69
69
  /**
70
- * Create a domain event with auto-generated metadata
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.
71
74
  */
72
75
  function createEvent(type, payload, meta) {
73
76
  return {
@@ -80,6 +83,55 @@ function createEvent(type, payload, meta) {
80
83
  }
81
84
  };
82
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
+ }
83
135
  //#endregion
84
136
  //#region src/events/retry.ts
85
137
  /**
@@ -89,16 +141,21 @@ function createEvent(type, payload, meta) {
89
141
  * After all retries exhausted, calls `onDead` callback if provided.
90
142
  */
91
143
  function withRetry(handler, options = {}) {
92
- const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, onDead, name, logger = console } = options;
144
+ const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, transport, onDead, name, logger = console } = options;
93
145
  const label = name ?? handler.name ?? "anonymous";
94
146
  return async (event) => {
95
147
  const errors = [];
148
+ let firstFailedAt;
149
+ let lastFailedAt;
96
150
  for (let attempt = 0; attempt <= maxRetries; attempt++) try {
97
151
  await handler(event);
98
152
  return;
99
153
  } catch (err) {
100
154
  const error = err instanceof Error ? err : new Error(String(err));
155
+ const now = /* @__PURE__ */ new Date();
101
156
  errors.push(error);
157
+ if (firstFailedAt === void 0) firstFailedAt = now;
158
+ lastFailedAt = now;
102
159
  if (attempt < maxRetries) {
103
160
  const baseDelay = Math.min(backoffMs * 2 ** attempt, maxBackoffMs);
104
161
  const delay = baseDelay + jitter * baseDelay * Math.random();
@@ -106,7 +163,28 @@ function withRetry(handler, options = {}) {
106
163
  await sleep(delay);
107
164
  }
108
165
  }
109
- logger.error(`[Arc Events] Handler '${label}' permanently failed for ${event.type} after ${maxRetries + 1} attempts. ${errors.length} errors.`);
166
+ const attempts = maxRetries + 1;
167
+ logger.error(`[Arc Events] Handler '${label}' permanently failed for ${event.type} after ${attempts} attempts. ${errors.length} errors.`);
168
+ if (transport?.deadLetter) {
169
+ const lastError = errors[errors.length - 1];
170
+ const dlq = {
171
+ event,
172
+ error: {
173
+ message: lastError?.message ?? "unknown",
174
+ ...lastError && "code" in lastError && typeof lastError.code === "string" ? { code: lastError.code } : {},
175
+ ...lastError?.stack ? { stack: lastError.stack } : {}
176
+ },
177
+ attempts,
178
+ firstFailedAt: firstFailedAt ?? /* @__PURE__ */ new Date(),
179
+ lastFailedAt: lastFailedAt ?? /* @__PURE__ */ new Date(),
180
+ handlerName: label
181
+ };
182
+ try {
183
+ await transport.deadLetter(dlq);
184
+ } catch (dlqErr) {
185
+ logger.error("[Arc Events] transport.deadLetter() failed:", dlqErr);
186
+ }
187
+ }
110
188
  if (onDead) try {
111
189
  await onDead(event, errors);
112
190
  } catch (dlqErr) {
@@ -243,4 +321,4 @@ var eventPlugin_default = fp(eventPlugin, {
243
321
  fastify: "5.x"
244
322
  });
245
323
  //#endregion
246
- export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createEvent as o, createDeadLetterPublisher as r, eventPlugin as t };
324
+ export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createChildEvent as o, createDeadLetterPublisher as r, createEvent as s, eventPlugin as t };
@@ -1,7 +1,8 @@
1
- import { a as MemoryEventTransport, c as createEvent, i as EventTransport, n as EventHandler, o as MemoryEventTransportOptions, r as EventLogger, s as PublishManyResult, t as DomainEvent } from "../EventTransport-BXja8NOc.mjs";
2
- 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-D9DKB2zM.mjs";
1
+ import { o as RepositoryLike } from "../interface-YrWsmKqE.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-CqZ8FyM_.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-BxvaCIZF.mjs";
3
4
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
4
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-CrsfUmPt.mjs";
5
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-Bz-4q96t.mjs";
5
6
 
6
7
  //#region src/events/eventTypes.d.ts
7
8
  /**
@@ -50,6 +51,34 @@ declare const CACHE_EVENTS: Readonly<{
50
51
  /** Type for cache event names */
51
52
  type CacheEvent = (typeof CACHE_EVENTS)[keyof typeof CACHE_EVENTS];
52
53
  //#endregion
54
+ //#region src/events/memory-outbox.d.ts
55
+ interface MemoryEntry {
56
+ event: DomainEvent;
57
+ status: "pending" | "delivered" | "dead_letter";
58
+ attempts: number;
59
+ visibleAt: number;
60
+ leaseOwner: string | null;
61
+ leaseExpiresAt: number;
62
+ deliveredAt: number | null;
63
+ firstFailedAt: number | null;
64
+ lastFailedAt: number | null;
65
+ lastError: OutboxErrorInfo | null;
66
+ dedupeKey?: string;
67
+ }
68
+ declare class MemoryOutboxStore implements OutboxStore {
69
+ private readonly entries;
70
+ private readonly seenDedupeKeys;
71
+ save(event: DomainEvent, options?: OutboxWriteOptions): Promise<void>;
72
+ getPending(limit: number): Promise<DomainEvent[]>;
73
+ claimPending(options?: OutboxClaimOptions): Promise<DomainEvent[]>;
74
+ acknowledge(eventId: string, options?: OutboxAcknowledgeOptions): Promise<void>;
75
+ fail(eventId: string, error: OutboxErrorInfo, options?: OutboxFailOptions): Promise<void>;
76
+ getDeadLettered(limit: number): Promise<DeadLetteredEvent[]>;
77
+ purge(olderThanMs: number): Promise<number>;
78
+ /** Test helper: inspect entry by id */
79
+ _getEntry(eventId: string): Readonly<MemoryEntry> | undefined;
80
+ }
81
+ //#endregion
53
82
  //#region src/events/outbox.d.ts
54
83
  /**
55
84
  * **Terminology (v2.8.1+):**
@@ -119,6 +148,52 @@ interface OutboxErrorInfo {
119
148
  readonly message: string;
120
149
  readonly code?: string;
121
150
  }
151
+ /**
152
+ * Context passed to {@link OutboxFailurePolicy}.
153
+ */
154
+ interface OutboxFailureContext {
155
+ /** The event whose delivery just failed */
156
+ readonly event: DomainEvent;
157
+ /** The error returned by the transport / handler */
158
+ readonly error: Error;
159
+ /**
160
+ * Attempt count including this failure (1 on the first fail).
161
+ *
162
+ * Tracked by {@link EventOutbox} in-process — accurate within a single
163
+ * relay process, resets on restart. For durable attempt counts, query
164
+ * your store directly inside the policy.
165
+ */
166
+ readonly attempts: number;
167
+ }
168
+ /**
169
+ * Decision returned by {@link OutboxFailurePolicy} — controls how the
170
+ * relay calls `store.fail()` for a failed event.
171
+ */
172
+ interface OutboxFailureDecision {
173
+ /** Schedule retry for this time. Omit for immediate (next-poll) retry. */
174
+ readonly retryAt?: Date;
175
+ /**
176
+ * Move the event to dead-letter state (no further retries). When `true`,
177
+ * {@link RelayResult.deadLettered} is incremented.
178
+ */
179
+ readonly deadLetter?: boolean;
180
+ }
181
+ /**
182
+ * Centralised retry/DLQ policy evaluated by {@link EventOutbox.relayBatch}
183
+ * on every failure. Returns the options passed to `store.fail()`.
184
+ *
185
+ * Typical shape:
186
+ * ```typescript
187
+ * failurePolicy: ({ attempts, error }) => {
188
+ * if (attempts >= 5) return { deadLetter: true };
189
+ * return { retryAt: exponentialBackoff({ attempt: attempts }) };
190
+ * }
191
+ * ```
192
+ *
193
+ * Without a policy, the relay uses the legacy "immediate re-visibility" fail
194
+ * behaviour (each fail() is equivalent to `{}`).
195
+ */
196
+ type OutboxFailurePolicy = (ctx: OutboxFailureContext) => OutboxFailureDecision | Promise<OutboxFailureDecision>;
122
197
  /**
123
198
  * Thrown by a store when `acknowledge` / `fail` is called by a consumer that
124
199
  * does not own the event's current lease.
@@ -240,6 +315,22 @@ interface OutboxStore {
240
315
  * @throws {@link OutboxOwnershipError} on ownership mismatch
241
316
  */
242
317
  fail?(eventId: string, error: OutboxErrorInfo, options?: OutboxFailOptions): Promise<void>;
318
+ /**
319
+ * Return events currently in dead-letter state as typed
320
+ * {@link DeadLetteredEvent} envelopes.
321
+ *
322
+ * Closes the loop between {@link OutboxStore.fail} (with `deadLetter: true`)
323
+ * and {@link import('./EventTransport.js').DeadLetteredEvent} — apps get the
324
+ * same shape out of the outbox that {@link import('./retry.js').withRetry}
325
+ * delivers to transports. No hand-building DLQ envelopes.
326
+ *
327
+ * Implementations MUST populate `error`, `attempts`, and both
328
+ * `firstFailedAt` / `lastFailedAt` from whatever they tracked during `fail()`
329
+ * calls. `handlerName` is optional — stores that don't track it can omit.
330
+ *
331
+ * @param limit - Max envelopes to return
332
+ */
333
+ getDeadLettered?(limit: number): Promise<DeadLetteredEvent[]>;
243
334
  /**
244
335
  * Purge old **delivered** events (optional, DB-agnostic contract).
245
336
  *
@@ -285,6 +376,12 @@ interface RelayResult {
285
376
  readonly malformed: number;
286
377
  /** Number of fail() calls that themselves threw (store bugs / contention) */
287
378
  readonly failHookErrors: number;
379
+ /**
380
+ * Number of events moved to dead-letter state this batch via the configured
381
+ * {@link OutboxFailurePolicy}. Zero when no policy is set or no failure
382
+ * tripped the `deadLetter` branch.
383
+ */
384
+ readonly deadLettered: number;
288
385
  /** Whether `publishMany` was used (true) or per-event `publish` (false) */
289
386
  readonly usedPublishMany: boolean;
290
387
  }
@@ -298,8 +395,23 @@ type OutboxRelayErrorHandler = (info: {
298
395
  readonly error: Error;
299
396
  }) => void;
300
397
  interface EventOutboxOptions {
301
- /** Outbox store for persistence */
302
- readonly store: OutboxStore;
398
+ /**
399
+ * Repository managing the outbox collection. Arc consumes it directly —
400
+ * no wrapper classes. Requires `create`, `getOne`, `findAll`,
401
+ * `deleteMany`, and `findOneAndUpdate` from `RepositoryLike` (mongokit
402
+ * ≥3.8 satisfies all of these). Takes precedence over `store` when both
403
+ * are passed.
404
+ *
405
+ * Use this for the common path where the outbox lives in your primary
406
+ * database. Use `store` for non-repository backends (memory / custom).
407
+ */
408
+ readonly repository?: RepositoryLike;
409
+ /**
410
+ * Non-repository outbox store. Use when your backend isn't a repository
411
+ * (memory for tests, Kafka, DynamoDB, custom). Ignored if `repository`
412
+ * is also passed.
413
+ */
414
+ readonly store?: OutboxStore;
303
415
  /** Transport to relay events to (optional — can relay later) */
304
416
  readonly transport?: EventTransport;
305
417
  /** Max events per relay batch (default: 100) */
@@ -327,6 +439,17 @@ interface EventOutboxOptions {
327
439
  * throughput, or to debug batch-specific issues.
328
440
  */
329
441
  readonly usePublishMany?: boolean;
442
+ /**
443
+ * Retry/DLQ decision policy. When set, {@link EventOutbox.relayBatch}
444
+ * invokes this on every failure and uses the returned options for
445
+ * `store.fail()`. Centralises the "after N fails, dead-letter" rule so
446
+ * apps don't recompute `exponentialBackoff` + escalation thresholds on
447
+ * every failure site.
448
+ *
449
+ * Without a policy, `fail()` is called with `{}` (immediate re-visibility
450
+ * on next poll) — legacy behaviour, unchanged.
451
+ */
452
+ readonly failurePolicy?: OutboxFailurePolicy;
330
453
  }
331
454
  declare class EventOutbox {
332
455
  private readonly _store;
@@ -336,6 +459,14 @@ declare class EventOutbox {
336
459
  private readonly _leaseMs;
337
460
  private readonly _onError?;
338
461
  private readonly _usePublishMany;
462
+ private readonly _failurePolicy?;
463
+ /**
464
+ * In-process attempt counter per event id. Accurate within this relay
465
+ * process; resets on restart. Populated as failures occur and cleared on
466
+ * successful ack or dead-letter transition. For durable authoritative
467
+ * counts, apps can query the store directly inside {@link OutboxFailurePolicy}.
468
+ */
469
+ private readonly _attempts;
339
470
  constructor(opts: EventOutboxOptions);
340
471
  /** Unique consumer ID used for lease ownership when the store supports claims */
341
472
  get consumerId(): string;
@@ -395,6 +526,16 @@ declare class EventOutbox {
395
526
  * @returns Per-kind outcome counts for the batch
396
527
  */
397
528
  relayBatch(): Promise<RelayResult>;
529
+ /**
530
+ * Fetch current dead-lettered events as typed {@link DeadLetteredEvent}
531
+ * envelopes. Delegates to {@link OutboxStore.getDeadLettered} — returns
532
+ * `[]` when the store doesn't implement it.
533
+ *
534
+ * Pairs with {@link OutboxFailurePolicy}: apps set a policy that routes to
535
+ * `deadLetter: true` after N attempts, then read back with this to alert,
536
+ * replay, or archive.
537
+ */
538
+ getDeadLettered(limit?: number): Promise<DeadLetteredEvent[]>;
398
539
  /**
399
540
  * Purge old **delivered** events from the outbox store.
400
541
  * Delegates to `store.purge()` if implemented; no-op otherwise.
@@ -403,36 +544,6 @@ declare class EventOutbox {
403
544
  */
404
545
  purge(olderThanMs?: number): Promise<number>;
405
546
  }
406
- interface MemoryEntry {
407
- event: DomainEvent;
408
- status: "pending" | "delivered" | "dead_letter";
409
- attempts: number;
410
- visibleAt: number;
411
- leaseOwner: string | null;
412
- leaseExpiresAt: number;
413
- deliveredAt: number | null;
414
- lastError: OutboxErrorInfo | null;
415
- dedupeKey?: string;
416
- }
417
- /**
418
- * In-memory outbox store — reference implementation supporting the full
419
- * capability set (claim/lease, fail/retry, dedupe, visibleAt).
420
- *
421
- * For dev/testing only. Production deployments should use a durable store
422
- * backed by the app's database.
423
- */
424
- declare class MemoryOutboxStore implements OutboxStore {
425
- private readonly entries;
426
- private readonly seenDedupeKeys;
427
- save(event: DomainEvent, options?: OutboxWriteOptions): Promise<void>;
428
- getPending(limit: number): Promise<DomainEvent[]>;
429
- claimPending(options?: OutboxClaimOptions): Promise<DomainEvent[]>;
430
- acknowledge(eventId: string, options?: OutboxAcknowledgeOptions): Promise<void>;
431
- fail(eventId: string, error: OutboxErrorInfo, options?: OutboxFailOptions): Promise<void>;
432
- purge(olderThanMs: number): Promise<number>;
433
- /** Test helper: inspect entry by id */
434
- _getEntry(eventId: string): Readonly<MemoryEntry> | undefined;
435
- }
436
547
  /**
437
548
  * Options for {@link exponentialBackoff}.
438
549
  */
@@ -486,4 +597,4 @@ interface ExponentialBackoffOptions {
486
597
  */
487
598
  declare function exponentialBackoff(options: ExponentialBackoffOptions): Date;
488
599
  //#endregion
489
- export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type DomainEvent, type EventDefinitionInput, type EventDefinitionOutput, type EventHandler, type EventLogger, 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, 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, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, withRetry };
600
+ 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, withRetry };