@classytic/arc 2.11.4 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +16 -12
  2. package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
  3. package/dist/EventTransport-CT_52aWU.d.mts +34 -0
  4. package/dist/EventTransport-DLWoUMHy.mjs +103 -0
  5. package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/audit.d.mts +199 -0
  9. package/dist/auth/audit.mjs +288 -0
  10. package/dist/auth/index.d.mts +3 -3
  11. package/dist/auth/index.mjs +117 -191
  12. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  13. package/dist/buildHandler-olo-gt94.mjs +610 -0
  14. package/dist/cache/index.mjs +3 -3
  15. package/dist/cli/commands/describe.d.mts +89 -13
  16. package/dist/cli/commands/describe.mjs +56 -2
  17. package/dist/cli/commands/docs.mjs +2 -2
  18. package/dist/cli/commands/generate.mjs +147 -48
  19. package/dist/cli/commands/init.d.mts +13 -0
  20. package/dist/cli/commands/init.mjs +130 -87
  21. package/dist/cli/commands/introspect.mjs +8 -1
  22. package/dist/context/index.mjs +1 -1
  23. package/dist/core/index.d.mts +3 -3
  24. package/dist/core/index.mjs +5 -5
  25. package/dist/core-DECn6zaU.mjs +1399 -0
  26. package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
  27. package/dist/createAggregationRouter-CRIBv4sC.mjs +114 -0
  28. package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
  29. package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
  30. package/dist/docs/index.d.mts +24 -11
  31. package/dist/docs/index.mjs +2 -2
  32. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  33. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  34. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  35. package/dist/errors-j4aJm1Wg.mjs +184 -0
  36. package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
  37. package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
  38. package/dist/events/index.d.mts +6 -6
  39. package/dist/events/index.mjs +11 -35
  40. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  41. package/dist/events/transports/redis.d.mts +1 -1
  42. package/dist/factory/index.d.mts +2 -2
  43. package/dist/factory/index.mjs +2 -2
  44. package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
  45. package/dist/hooks/index.d.mts +1 -1
  46. package/dist/hooks/index.mjs +1 -1
  47. package/dist/idempotency/index.d.mts +1 -1
  48. package/dist/idempotency/index.mjs +1 -20
  49. package/dist/idempotency/redis.mjs +1 -1
  50. package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
  51. package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
  52. package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
  53. package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  54. package/dist/index.d.mts +6 -7
  55. package/dist/index.mjs +9 -10
  56. package/dist/integrations/event-gateway.d.mts +1 -1
  57. package/dist/integrations/event-gateway.mjs +1 -1
  58. package/dist/integrations/index.d.mts +1 -1
  59. package/dist/integrations/mcp/index.d.mts +2 -2
  60. package/dist/integrations/mcp/index.mjs +1 -1
  61. package/dist/integrations/mcp/testing.d.mts +1 -1
  62. package/dist/integrations/mcp/testing.mjs +1 -1
  63. package/dist/integrations/streamline.d.mts +60 -11
  64. package/dist/integrations/streamline.mjs +75 -85
  65. package/dist/integrations/websocket.mjs +2 -8
  66. package/dist/middleware/index.d.mts +1 -1
  67. package/dist/middleware/index.mjs +2 -2
  68. package/dist/migrations/index.d.mts +23 -3
  69. package/dist/migrations/index.mjs +0 -7
  70. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  71. package/dist/openapi-noXno2CV.mjs +968 -0
  72. package/dist/org/index.d.mts +2 -2
  73. package/dist/org/index.mjs +1 -1
  74. package/dist/permissions/index.d.mts +3 -3
  75. package/dist/permissions/index.mjs +3 -3
  76. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  77. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  78. package/dist/pipeline/index.d.mts +1 -1
  79. package/dist/pipeline/index.mjs +1 -1
  80. package/dist/plugins/index.d.mts +16 -31
  81. package/dist/plugins/index.mjs +33 -13
  82. package/dist/plugins/response-cache.mjs +1 -1
  83. package/dist/plugins/tracing-entry.mjs +1 -1
  84. package/dist/presets/filesUpload.d.mts +4 -4
  85. package/dist/presets/filesUpload.mjs +6 -9
  86. package/dist/presets/index.d.mts +1 -1
  87. package/dist/presets/index.mjs +1 -1
  88. package/dist/presets/multiTenant.d.mts +1 -1
  89. package/dist/presets/multiTenant.mjs +2 -2
  90. package/dist/presets/search.d.mts +2 -2
  91. package/dist/presets/search.mjs +6 -8
  92. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  93. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  94. package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
  95. package/dist/registry/index.d.mts +1 -1
  96. package/dist/registry/index.mjs +2 -2
  97. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  98. package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-DLL32us3.mjs} +224 -71
  99. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
  100. package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.mjs} +1 -1
  101. package/dist/schemas/index.d.mts +100 -30
  102. package/dist/schemas/index.mjs +86 -29
  103. package/dist/scim/index.d.mts +264 -0
  104. package/dist/scim/index.mjs +963 -0
  105. package/dist/scope/index.d.mts +3 -3
  106. package/dist/scope/index.mjs +4 -4
  107. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  108. package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  109. package/dist/testing/index.d.mts +2 -8
  110. package/dist/testing/index.mjs +16 -24
  111. package/dist/types/index.d.mts +4 -4
  112. package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
  113. package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
  114. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  115. package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
  116. package/dist/utils/index.d.mts +2 -2
  117. package/dist/utils/index.mjs +5 -5
  118. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  119. package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  120. package/package.json +24 -34
  121. package/skills/arc/SKILL.md +147 -51
  122. package/skills/arc/references/agent-auth.md +238 -0
  123. package/skills/arc/references/api-reference.md +187 -0
  124. package/skills/arc/references/auth.md +354 -7
  125. package/skills/arc/references/enterprise-auth.md +94 -0
  126. package/skills/arc/references/events.md +8 -6
  127. package/skills/arc/references/mcp.md +2 -2
  128. package/skills/arc/references/multi-tenancy.md +11 -2
  129. package/skills/arc/references/production.md +10 -9
  130. package/skills/arc/references/scim.md +247 -0
  131. package/skills/arc/references/testing.md +1 -1
  132. package/skills/arc-code-review/SKILL.md +141 -0
  133. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  134. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  135. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  136. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  137. package/skills/arc-code-review/references/scaffolding.md +230 -0
  138. package/skills/arc-code-review/references/severity.md +127 -0
  139. package/dist/EventTransport-BFQjw9pB.mjs +0 -133
  140. package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
  141. package/dist/adapters/index.d.mts +0 -3
  142. package/dist/adapters/index.mjs +0 -2
  143. package/dist/adapters-DUUiiimH.mjs +0 -964
  144. package/dist/auth/mongoose.d.mts +0 -191
  145. package/dist/auth/mongoose.mjs +0 -73
  146. package/dist/core-CbcQRIch.mjs +0 -1054
  147. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  148. package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
  149. package/dist/errors-D5c-5BJL.mjs +0 -232
  150. package/dist/index-Rg8axYPz.d.mts +0 -370
  151. package/dist/openapi-D7G1V7ex.mjs +0 -557
  152. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  153. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  154. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  155. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  156. /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  157. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  158. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  159. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  160. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  161. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
  162. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  163. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  164. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  165. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  166. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  167. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
@@ -0,0 +1,184 @@
1
+ //#region src/utils/errors.ts
2
+ /**
3
+ * Base Arc Error. Implements the canonical `HttpError` contract — `status`
4
+ * mirrors `statusCode` and `meta` mirrors `details`, so consumers reading
5
+ * either name see the same value without adapter glue.
6
+ */
7
+ var ArcError = class extends Error {
8
+ name = "ArcError";
9
+ code;
10
+ statusCode;
11
+ details;
12
+ cause;
13
+ constructor(message, options = {}) {
14
+ super(message, options.cause ? { cause: options.cause } : void 0);
15
+ this.code = options.code ?? "arc.error";
16
+ this.statusCode = options.statusCode ?? 500;
17
+ this.details = options.details;
18
+ this.cause = options.cause;
19
+ if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
20
+ }
21
+ /** `HttpError.status` mirror — repo-core's `toErrorContract` reads this. */
22
+ get status() {
23
+ return this.statusCode;
24
+ }
25
+ /** `HttpError.meta` mirror — `details` under the canonical name. */
26
+ get meta() {
27
+ return this.details;
28
+ }
29
+ };
30
+ var NotFoundError = class extends ArcError {
31
+ constructor(resource, identifier) {
32
+ const message = identifier ? `${resource} with identifier '${identifier}' not found` : `${resource} not found`;
33
+ super(message, {
34
+ code: "arc.not_found",
35
+ statusCode: 404,
36
+ details: {
37
+ resource,
38
+ ...identifier ? { identifier } : {}
39
+ }
40
+ });
41
+ this.name = "NotFoundError";
42
+ }
43
+ };
44
+ var ValidationError = class extends ArcError {
45
+ errors;
46
+ constructor(message, errors = []) {
47
+ super(message, {
48
+ code: "arc.validation_error",
49
+ statusCode: 400,
50
+ details: { errors }
51
+ });
52
+ this.name = "ValidationError";
53
+ this.errors = errors;
54
+ }
55
+ };
56
+ var UnauthorizedError = class extends ArcError {
57
+ constructor(message = "Authentication required") {
58
+ super(message, {
59
+ code: "arc.unauthorized",
60
+ statusCode: 401
61
+ });
62
+ this.name = "UnauthorizedError";
63
+ }
64
+ };
65
+ var ForbiddenError = class extends ArcError {
66
+ constructor(message = "Access denied") {
67
+ super(message, {
68
+ code: "arc.forbidden",
69
+ statusCode: 403
70
+ });
71
+ this.name = "ForbiddenError";
72
+ }
73
+ };
74
+ var ConflictError = class extends ArcError {
75
+ constructor(message, field) {
76
+ super(message, {
77
+ code: "arc.conflict",
78
+ statusCode: 409,
79
+ ...field ? { details: { field } } : {}
80
+ });
81
+ this.name = "ConflictError";
82
+ }
83
+ };
84
+ var OrgRequiredError = class extends ArcError {
85
+ organizations;
86
+ constructor(message, organizations) {
87
+ super(message, {
88
+ code: "arc.org.selection_required",
89
+ statusCode: 403,
90
+ ...organizations ? { details: { organizations } } : {}
91
+ });
92
+ this.name = "OrgRequiredError";
93
+ this.organizations = organizations;
94
+ }
95
+ };
96
+ var OrgAccessDeniedError = class extends ArcError {
97
+ constructor(orgId) {
98
+ super("Organization access denied", {
99
+ code: "arc.org.access_denied",
100
+ statusCode: 403,
101
+ ...orgId ? { details: { organizationId: orgId } } : {}
102
+ });
103
+ this.name = "OrgAccessDeniedError";
104
+ }
105
+ };
106
+ var RateLimitError = class extends ArcError {
107
+ retryAfter;
108
+ constructor(message = "Too many requests", retryAfter) {
109
+ super(message, {
110
+ code: "arc.rate_limited",
111
+ statusCode: 429,
112
+ ...retryAfter ? { details: { retryAfter } } : {}
113
+ });
114
+ this.name = "RateLimitError";
115
+ this.retryAfter = retryAfter;
116
+ }
117
+ };
118
+ var ServiceUnavailableError = class extends ArcError {
119
+ constructor(message = "Service temporarily unavailable") {
120
+ super(message, {
121
+ code: "arc.service_unavailable",
122
+ statusCode: 503
123
+ });
124
+ this.name = "ServiceUnavailableError";
125
+ }
126
+ };
127
+ /**
128
+ * Status-code → canonical `arc.*` code mapping. Used by {@link createError}
129
+ * and the global error handler when no explicit code is supplied.
130
+ */
131
+ const STATUS_CODE_MAP = {
132
+ 400: "arc.bad_request",
133
+ 401: "arc.unauthorized",
134
+ 403: "arc.forbidden",
135
+ 404: "arc.not_found",
136
+ 409: "arc.conflict",
137
+ 422: "arc.unprocessable_entity",
138
+ 429: "arc.rate_limited",
139
+ 500: "arc.internal_error",
140
+ 502: "arc.bad_gateway",
141
+ 503: "arc.service_unavailable",
142
+ 504: "arc.gateway_timeout"
143
+ };
144
+ /** Status → canonical arc code, falling back to `arc.error`. */
145
+ function statusToArcCode(status) {
146
+ return STATUS_CODE_MAP[status] ?? "arc.error";
147
+ }
148
+ /**
149
+ * Quick `ArcError` constructor when the bundled subclasses don't fit.
150
+ *
151
+ * If `details.code` is a string, it's lifted to the top-level `error.code`
152
+ * (the canonical wire slot for business signals — `'ORG_CONTEXT_REQUIRED'`,
153
+ * `'ALL_FIELDS_STRIPPED'`, etc). The same code stays in `details` so
154
+ * in-process consumers reading `error.details.code` still work. Without
155
+ * this lift, business codes get buried in `details` and the wire envelope
156
+ * carries only the status-derived `arc.forbidden` / `arc.bad_request`
157
+ * fallback — `repo-core`'s `toErrorContract` doesn't surface free-form
158
+ * `details` objects (its `details[]` array is reserved for validation /
159
+ * duplicate-key items).
160
+ */
161
+ function createError(statusCode, message, details) {
162
+ return new ArcError(message, {
163
+ code: (typeof details?.code === "string" ? details.code : void 0) ?? statusToArcCode(statusCode),
164
+ statusCode,
165
+ ...details ? { details } : {}
166
+ });
167
+ }
168
+ /**
169
+ * Domain-error escape hatch. Use a hierarchical code that scopes the error
170
+ * to your package (`'commerce.cart.locked'`, `'payment.gateway.timeout'`).
171
+ */
172
+ function createDomainError(code, message, statusCode = 400, details) {
173
+ return new ArcError(message, {
174
+ code,
175
+ statusCode,
176
+ ...details ? { details } : {}
177
+ });
178
+ }
179
+ /** Type guard. */
180
+ function isArcError(error) {
181
+ return error instanceof ArcError;
182
+ }
183
+ //#endregion
184
+ export { OrgAccessDeniedError as a, ServiceUnavailableError as c, createDomainError as d, createError as f, NotFoundError as i, UnauthorizedError as l, statusToArcCode as m, ConflictError as n, OrgRequiredError as o, isArcError as p, ForbiddenError as r, RateLimitError as s, ArcError as t, ValidationError as u };
@@ -1,6 +1,8 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { t as requestContext } from "./requestContext-C5XeK3VA.mjs";
3
- import { r as createEvent, t as MemoryEventTransport } from "./EventTransport-BFQjw9pB.mjs";
2
+ import { arcLog } from "./logger/index.mjs";
3
+ import { d as createDomainError } from "./errors-j4aJm1Wg.mjs";
4
+ import { t as requestContext } from "./requestContext-SSaaTgW8.mjs";
5
+ import { n as createEvent, t as MemoryEventTransport } from "./EventTransport-DLWoUMHy.mjs";
4
6
  import fp from "fastify-plugin";
5
7
  //#region src/events/retry.ts
6
8
  /**
@@ -109,8 +111,18 @@ var eventPlugin_exports = /* @__PURE__ */ __exportAll({
109
111
  eventPlugin: () => eventPlugin
110
112
  });
111
113
  const eventPlugin = async (fastify, opts = {}) => {
112
- const { transport = new MemoryEventTransport(), logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError, registry, validateMode: rawValidateMode } = opts;
114
+ const { transport = new MemoryEventTransport(), logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError, registry, validateMode: rawValidateMode, warnOnDuplicate: rawWarnOnDuplicate } = opts;
113
115
  const validateMode = rawValidateMode ?? (registry ? "warn" : "off");
116
+ const warnOnDuplicate = rawWarnOnDuplicate ?? process.env.NODE_ENV !== "production";
117
+ const DUP_WINDOW_MS = 5e3;
118
+ const recentPublishes = warnOnDuplicate ? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map();
119
+ const evictExpiredPublishes = (now) => {
120
+ if (recentPublishes.size === 0) return;
121
+ for (const [key, timestamp] of recentPublishes) {
122
+ if (now - timestamp <= DUP_WINDOW_MS) break;
123
+ recentPublishes.delete(key);
124
+ }
125
+ };
114
126
  fastify.decorate("events", {
115
127
  publish: async (type, payload, meta) => {
116
128
  if (!type || typeof type !== "string") throw new Error("[Arc Events] Event type must be a non-empty string");
@@ -121,6 +133,15 @@ const eventPlugin = async (fastify, opts = {}) => {
121
133
  ...store?.requestId && !meta?.correlationId ? { correlationId: store.requestId } : {},
122
134
  ...meta
123
135
  });
136
+ if (warnOnDuplicate && event.meta.correlationId) {
137
+ const now = Date.now();
138
+ evictExpiredPublishes(now);
139
+ const dupKey = `${type}::${event.meta.correlationId}`;
140
+ const previous = recentPublishes.get(dupKey);
141
+ if (previous !== void 0 && now - previous <= DUP_WINDOW_MS) arcLog("events").warn(`Duplicate publish detected: event type "${type}" published twice within ${DUP_WINDOW_MS}ms with correlationId "${event.meta.correlationId}". Subscribers will fire twice for the same logical event. Common cause: a domain service holds both a publisher and a notification helper that also publishes to the same bus — pick one. Set \`arcPlugins: { events: { warnOnDuplicate: false } }\` to silence.`);
142
+ recentPublishes.delete(dupKey);
143
+ recentPublishes.set(dupKey, now);
144
+ }
124
145
  if (logEvents) fastify.log?.info?.({
125
146
  eventType: type,
126
147
  eventId: event.meta.id,
@@ -130,7 +151,10 @@ const eventPlugin = async (fastify, opts = {}) => {
130
151
  const result = registry.validate(type, payload, event.meta.schemaVersion);
131
152
  if (!result.valid) {
132
153
  const msg = `[Arc Events] Event '${type}' payload validation failed: ${result.errors?.join("; ")}`;
133
- if (validateMode === "reject") throw new Error(msg);
154
+ if (validateMode === "reject") throw createDomainError("arc.event.validation_error", msg, 400, {
155
+ event: type,
156
+ errors: result.errors
157
+ });
134
158
  fastify.log?.warn?.(msg);
135
159
  }
136
160
  }
@@ -1,4 +1,4 @@
1
- import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "./EventTransport-CYNUXdCJ.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-CT_52aWU.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/events/defineEvent.d.ts
@@ -275,6 +275,29 @@ interface EventPluginOptions {
275
275
  * - `'off'`: skip validation entirely (registry is only for introspection)
276
276
  */
277
277
  validateMode?: "warn" | "reject" | "off";
278
+ /**
279
+ * Dev-mode duplicate-publish detector (v2.12).
280
+ *
281
+ * When enabled, arc keeps a 5-second LRU on `(eventType, correlationId)`
282
+ * and emits an `arcLog("events").warn(...)` the second time a request
283
+ * publishes the same event with the same correlation id within the
284
+ * window. Catches the dual-publish trap where a domain service holds
285
+ * BOTH a publisher AND a notification helper that internally publishes
286
+ * to the same bus — every subscriber fires twice for one logical event.
287
+ *
288
+ * Defaults:
289
+ * - `undefined` → enabled when `process.env.NODE_ENV !== 'production'`.
290
+ * - `true` → always enabled (catches duplicates in prod too — overhead
291
+ * is one Map lookup per publish).
292
+ * - `false` → always disabled.
293
+ *
294
+ * When a duplicate is detected, arc logs once and **still publishes** —
295
+ * the detector is observability, not enforcement. Pair with the outbox
296
+ * for at-most-once delivery.
297
+ *
298
+ * Documented in `wiki/gotchas.md` (#20).
299
+ */
300
+ warnOnDuplicate?: boolean;
278
301
  }
279
302
  declare module "fastify" {
280
303
  interface FastifyInstance {
@@ -1,8 +1,8 @@
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";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions, t as DeadLetteredEvent } from "../EventTransport-CT_52aWU.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-qXpqTebY.mjs";
4
3
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
5
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-xTGxB2bm.mjs";
4
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-D6HzR1Z_.mjs";
5
+ import { RepositoryLike } from "@classytic/repo-core/adapter";
6
6
 
7
7
  //#region src/events/eventTypes.d.ts
8
8
  /**
@@ -620,7 +620,7 @@ declare function repositoryAsOutboxStore(repository: RepositoryLike): OutboxStor
620
620
  * ```
621
621
  */
622
622
  type PayloadOf<D> = D extends EventDefinitionOutput<infer T> ? T : never;
623
- interface WrapWithSchemaOptions<T> {
623
+ interface WrapWithSchemaOptions<_T> {
624
624
  /**
625
625
  * Custom validator. Overrides the built-in lookup. Use this to plug AJV /
626
626
  * Zod / TypeBox in. Same shape as `EventRegistryOptions.validate`.
@@ -759,4 +759,4 @@ interface FastifyEventBus {
759
759
  };
760
760
  }
761
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 };
762
+ export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, 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 RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
@@ -1,7 +1,7 @@
1
- import { n as createChildEvent, r as createEvent, t as MemoryEventTransport } from "../EventTransport-BFQjw9pB.mjs";
2
- import { n as defineEvent, t as createEventRegistry } from "../defineEvent-D1Ky9M1D.mjs";
3
- import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-Cts2-Tfj.mjs";
4
- import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-Cp4uKC1U.mjs";
1
+ import { t as MemoryEventTransport } from "../EventTransport-DLWoUMHy.mjs";
2
+ import { n as defineEvent, t as createEventRegistry } from "../defineEvent-D5h7EvAx.mjs";
3
+ import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-CaKTYkYM.mjs";
4
+ import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-BkIN9-vu.mjs";
5
5
  import { and, anyOf, eq, lte, ne, or } from "@classytic/repo-core/filter";
6
6
  import { update } from "@classytic/repo-core/update";
7
7
  //#region src/events/eventTypes.ts
@@ -170,30 +170,6 @@ var MemoryOutboxStore = class {
170
170
  };
171
171
  //#endregion
172
172
  //#region src/events/repository-outbox-adapter.ts
173
- /**
174
- * RepositoryLike → OutboxStore adapter.
175
- *
176
- * Maps the `OutboxStore` vocabulary (save / claimPending / acknowledge /
177
- * fail / getDeadLettered / purge) onto arc's own `RepositoryLike` primitives
178
- * (create / getOne / findAll / deleteMany / findOneAndUpdate). `EventOutbox`
179
- * wraps a passed repository with this helper when you use the
180
- * `{ repository }` option; the function is also re-exported from
181
- * `@classytic/arc/events` so consumers can build and decorate the store
182
- * manually (metrics, tracing, multi-transport fan-out).
183
- *
184
- * Portability: filters compose via `@classytic/repo-core/filter` and
185
- * updates via `@classytic/repo-core/update`. The primary-key column name
186
- * is read from `repository.idField` — mongokit defaults to `_id`,
187
- * sqlitekit / pgkit / prismakit to the schema's declared PK. The adapter
188
- * therefore runs on any kit that implements `StandardRepo.findOneAndUpdate`
189
- * + `getOne` + `getAll` + `deleteMany` + `create`.
190
- *
191
- * `fail()` uses a lease-gated read-then-write pair to preserve
192
- * `firstFailedAt` across retries without relying on Mongo's aggregation-
193
- * pipeline `$ifNull`. Leases guarantee single-writer during the failure
194
- * window (`claimPending` filters out non-owned rows), so the two calls are
195
- * safe under concurrent relayers.
196
- */
197
173
  const DEFAULT_LEASE_MS$1 = 3e4;
198
174
  const DEFAULT_CLAIM_LIMIT = 100;
199
175
  const DEFAULT_PURGE_BATCH = 500;
@@ -208,12 +184,12 @@ function repositoryAsOutboxStore(repository) {
208
184
  const r = repository;
209
185
  const idField = repository.idField ?? "_id";
210
186
  /**
211
- * Unwrap mongokit's pagination envelope ({ docs, total, ... }) — some
187
+ * Unwrap mongokit's pagination envelope ({ data, total, ... }) — some
212
188
  * kits may return a bare array when pagination is disabled. Handle both.
213
189
  */
214
190
  const unwrapDocs = (result) => {
215
191
  if (Array.isArray(result)) return result;
216
- return result?.docs ?? [];
192
+ return result?.data ?? [];
217
193
  };
218
194
  const isDuplicateKeyError = createIsDuplicateKeyError(repository);
219
195
  const safeGetOne = createSafeGetOne(repository);
@@ -532,7 +508,7 @@ var EventOutbox = class {
532
508
  const valid = [];
533
509
  let malformed = 0;
534
510
  for (const event of pending) {
535
- if (!event || !event.type || !event.meta?.id) {
511
+ if (!event?.type || !event.meta?.id) {
536
512
  this._reportError("malformed_event", new InvalidOutboxEventError("store returned event missing type or meta.id — batch aborted"), event);
537
513
  malformed++;
538
514
  break;
@@ -551,7 +527,7 @@ var EventOutbox = class {
551
527
  const canFail = typeof this._store.fail === "function";
552
528
  let publishOutcomes;
553
529
  if (canPublishMany && valid.length > 0) try {
554
- const result = await this._transport.publishMany(valid);
530
+ const result = await this._transport.publishMany?.(valid);
555
531
  publishOutcomes = new Map(result);
556
532
  } catch (batchErr) {
557
533
  publishOutcomes = /* @__PURE__ */ new Map();
@@ -596,7 +572,7 @@ var EventOutbox = class {
596
572
  this._reportError("fail_failed", policyErr, event);
597
573
  }
598
574
  try {
599
- await this._store.fail(event.meta.id, normalizeError(publishErr), failOpts);
575
+ await this._store.fail?.(event.meta.id, normalizeError(publishErr), failOpts);
600
576
  if (failOpts.deadLetter) {
601
577
  counts.deadLettered++;
602
578
  this._attempts.delete(event.meta.id);
@@ -740,7 +716,7 @@ function wrapWithSchema(definition, handler, options = {}) {
740
716
  if (validate && definition.schema) result = validate(definition.schema, event.payload);
741
717
  else if (registry) result = registry.validate(definition.name, event.payload, eventVersion);
742
718
  else if (definition.schema) {
743
- const { createEventRegistry } = await import("../defineEvent-D1Ky9M1D.mjs").then((n) => n.r);
719
+ const { createEventRegistry } = await import("../defineEvent-D5h7EvAx.mjs").then((n) => n.r);
744
720
  const adhoc = createEventRegistry();
745
721
  adhoc.register(definition);
746
722
  result = adhoc.validate(definition.name, event.payload);
@@ -834,4 +810,4 @@ async function subscribeWithBoundary(fastify, pattern, handler, options) {
834
810
  return fastify.events.subscribe(pattern, wrapWithBoundary(handler, options));
835
811
  }
836
812
  //#endregion
837
- export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, EventOutbox, InvalidOutboxEventError, MemoryEventTransport, MemoryOutboxStore, OutboxOwnershipError, createChildEvent, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
813
+ export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, EventOutbox, InvalidOutboxEventError, MemoryEventTransport, MemoryOutboxStore, OutboxOwnershipError, createDeadLetterPublisher, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
@@ -1,2 +1,2 @@
1
- import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-xTGxB2bm.mjs";
1
+ import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-D6HzR1Z_.mjs";
2
2
  export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
@@ -1,4 +1,4 @@
1
- import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "../../EventTransport-CYNUXdCJ.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-CT_52aWU.mjs";
2
2
 
3
3
  //#region src/events/transports/redis.d.ts
4
4
  interface RedisLike {
@@ -1,4 +1,4 @@
1
- import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as ResourceModule, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, p as loadResources, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-D7KpfiL1.mjs";
1
+ import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as ResourceModule, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, p as loadResources, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-BvqwCCSx.mjs";
2
2
  import { FastifyInstance } from "fastify";
3
3
 
4
4
  //#region src/factory/createApp.d.ts
@@ -14,7 +14,7 @@ import { FastifyInstance } from "fastify";
14
14
  * 4. Arc core (fastify.arc, events)
15
15
  * 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
16
16
  * 6. Auth (scope decoration, auth strategy, elevation, error handler)
17
- * 7. plugins() — user infra (DB, docs, webhooks)
17
+ * 7. plugins() — user infra (DB, data, webhooks)
18
18
  * 8. bootstrap[] — domain init (singletons, event handlers)
19
19
  * 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
20
20
  * 10. afterResources() — post-registration wiring
@@ -1,5 +1,5 @@
1
- import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-C9bRrqlX.mjs";
2
- import { t as loadResources } from "../loadResources-CPpkyKfM.mjs";
1
+ import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-XX2-N0Yd.mjs";
2
+ import { t as loadResources } from "../loadResources-DBMQg_Aj.mjs";
3
3
  //#region src/factory/edge.ts
4
4
  /**
5
5
  * Convert a Fastify app into a Web Standards fetch handler.
@@ -1,4 +1,4 @@
1
- import { r as RequestScope } from "./types-DDyTPc6y.mjs";
1
+ import { i as RequestScope } from "./types-CTYvcwHe.mjs";
2
2
  import { FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/permissions/types.d.ts
@@ -134,7 +134,7 @@ interface PermissionResult {
134
134
  type PermissionCheck<TDoc = Record<string, unknown>> = ((context: PermissionContext<TDoc>) => boolean | PermissionResult | Promise<boolean | PermissionResult>) & PermissionCheckMeta;
135
135
  /**
136
136
  * Optional metadata attached to permission check functions.
137
- * Used for OpenAPI docs, introspection, and route-level auth decisions.
137
+ * Used for OpenAPI data, introspection, and route-level auth decisions.
138
138
  *
139
139
  * Each helper from `permissions/index.ts` writes its own discriminating tag
140
140
  * so downstream tooling (OpenAPI generator, MCP resource builder, route
@@ -173,6 +173,27 @@ interface PermissionCheckMeta {
173
173
  * (e.g. from route params).
174
174
  */
175
175
  _orgInScopeTarget?: string | ((ctx: PermissionContext) => string | undefined);
176
+ /**
177
+ * Set by requireDPoP() — the inbound credential must be sender-constrained
178
+ * via DPoP (RFC 9449), with `scope.dpopJkt` set by the authenticate
179
+ * function after a successful proof verification.
180
+ */
181
+ _dpopRequired?: boolean;
182
+ /**
183
+ * Set by requireMandate() — the capability string the mandate on
184
+ * `scope.mandate` must authorize (e.g. `payment.charge`, `data.export`).
185
+ */
186
+ _mandateCapability?: string;
187
+ /**
188
+ * Set by requireAgentScope() — composite descriptor for AI-agent flows.
189
+ * Tools (audit, OpenAPI, MCP) can render the full agent-auth requirement
190
+ * in one read instead of unpacking three separate metadata fields.
191
+ */
192
+ _agentScope?: {
193
+ capability: string;
194
+ scopes?: readonly string[];
195
+ dpop: boolean;
196
+ };
176
197
  }
177
198
  //#endregion
178
199
  //#region src/permissions/fields.d.ts
@@ -1,2 +1,2 @@
1
- import { Cn as beforeUpdate, Sn as beforeDelete, Tn as defineHook, _n as HookSystemOptions, bn as afterUpdate, dn as HookContext, fn as HookHandler, gn as HookSystem, hn as HookRegistration, mn as HookPhase, pn as HookOperation, un as DefineHookOptions, vn as afterCreate, wn as createHookSystem, xn as beforeCreate, yn as afterDelete } from "../index-CXXRbnf8.mjs";
1
+ import { An as afterUpdate, Cn as HookOperation, Dn as HookSystemOptions, En as HookSystem, Fn as defineHook, Mn as beforeDelete, Nn as beforeUpdate, On as afterCreate, Pn as createHookSystem, Sn as HookHandler, Tn as HookRegistration, bn as DefineHookOptions, jn as beforeCreate, kn as afterDelete, wn as HookPhase, xn as HookContext } from "../index-BtW7qYwa.mjs";
2
2
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,2 +1,2 @@
1
- import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-CGsMd6oK.mjs";
1
+ import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-Iiebom92.mjs";
2
2
  export { HookSystem, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,7 +1,7 @@
1
- import { Mn as RepositoryLike } from "../index-CXXRbnf8.mjs";
2
1
  import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-DfLGcus7.mjs";
3
2
  import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-DiMkdHEl.mjs";
4
3
  import { FastifyPluginAsync } from "fastify";
4
+ import { RepositoryLike } from "@classytic/repo-core/adapter";
5
5
 
6
6
  //#region src/idempotency/idempotencyPlugin.d.ts
7
7
  interface IdempotencyPluginOptions {
@@ -1,28 +1,9 @@
1
- import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-Cp4uKC1U.mjs";
1
+ import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-BkIN9-vu.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  import { and, eq, exists, gt, lt, or, startsWith } from "@classytic/repo-core/filter";
5
5
  import { update } from "@classytic/repo-core/update";
6
6
  //#region src/idempotency/repository-idempotency-adapter.ts
7
- /**
8
- * RepositoryLike → IdempotencyStore adapter.
9
- *
10
- * Maps the idempotency store's verbs (get / set / tryLock / unlock / delete /
11
- * deleteByPrefix / findByPrefix) onto arc's canonical repository primitives
12
- * (`getOne` / `deleteMany` / `findOneAndUpdate`). `idempotencyPlugin` wraps
13
- * a passed repository with this helper when you use the `{ repository }`
14
- * option; the function is also re-exported from `@classytic/arc/idempotency`
15
- * so consumers can build and decorate the store (metrics, tracing, key
16
- * namespacing) before passing it via `store:`.
17
- *
18
- * Portability: filters compose via `@classytic/repo-core/filter` builders
19
- * (`and` / `or` / `eq` / `gt` / `lt` / `exists` / `startsWith`) and updates
20
- * via `@classytic/repo-core/update` (`update({ set, unset, setOnInsert })`).
21
- * Both IRs compile to Mongo operators on mongokit, SQL predicates on
22
- * sqlitekit / pgkit, and `WhereInput` / `update` on prismakit. The store
23
- * therefore runs identically on every backend that implements the
24
- * `StandardRepo.findOneAndUpdate` + `getOne` + `deleteMany` surface.
25
- */
26
7
  function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
27
8
  const missing = [];
28
9
  if (typeof repository.getOne !== "function") missing.push("getOne");
@@ -218,7 +218,7 @@ function upstashAsIdempotencyClient(client) {
218
218
  const keyCount = _numKeys;
219
219
  const keys = args.slice(0, keyCount).map(String);
220
220
  const rest = args.slice(keyCount);
221
- return client.eval(script, keys, rest);
221
+ return client.eval?.(script, keys, rest);
222
222
  } : void 0,
223
223
  async quit() {
224
224
  return "OK";