@classytic/arc 2.32.0 → 2.33.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 (192) hide show
  1. package/README.md +1 -1
  2. package/bin/arc.js +58 -17
  3. package/dist/{BaseController-Dk34dxhI.mjs → BaseController-CaQC5EXX.mjs} +293 -33
  4. package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
  5. package/dist/{EventTransport-BV10D0oG.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
  6. package/dist/{HookSystem-vimw5D7Z.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
  7. package/dist/{ResourceRegistry-BVr7PgA0.mjs → ResourceRegistry-DoALUlOO.mjs} +3 -2
  8. package/dist/{actionPermissions-Dti7ebyv.mjs → actionPermissions-CUeCmpUd.mjs} +1 -1
  9. package/dist/audit/index.d.mts +2 -2
  10. package/dist/audit/index.mjs +2 -2
  11. package/dist/auth/audit.mjs +1 -1
  12. package/dist/auth/index.d.mts +2 -2
  13. package/dist/auth/index.mjs +39 -7
  14. package/dist/auth/redis-session.mjs +1 -1
  15. package/dist/{authorizationDecision-__T3EJDA.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
  16. package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
  17. package/dist/{buildHandler-Dn7rzUJo.mjs → buildHandler-DLE9xX-V.mjs} +4 -4
  18. package/dist/cache/index.mjs +4 -4
  19. package/dist/{caching-DFJqHyFs.mjs → caching-BcGmvnHh.mjs} +2 -2
  20. package/dist/cleanup/index.d.mts +17 -5
  21. package/dist/cleanup/index.mjs +240 -7
  22. package/dist/cli/commands/describe.d.mts +1 -1
  23. package/dist/cli/commands/describe.mjs +3 -3
  24. package/dist/cli/commands/docs.mjs +3 -3
  25. package/dist/cli/commands/doctor.mjs +1 -1
  26. package/dist/cli/commands/generate.mjs +2 -2
  27. package/dist/cli/commands/init.mjs +2 -2
  28. package/dist/cli/commands/introspect.mjs +2 -2
  29. package/dist/cli/index.mjs +3 -3
  30. package/dist/{constants-J99LGTJI.mjs → constants-BxCoxmiN.mjs} +1 -1
  31. package/dist/context/index.mjs +3 -3
  32. package/dist/core/index.d.mts +2 -2
  33. package/dist/core/index.mjs +7 -7
  34. package/dist/{core-BwwmNSp7.mjs → core-dxhcHhUD.mjs} +3 -3
  35. package/dist/{createActionRouter-DkJZWo0m.mjs → createActionRouter-DVpoA0do.mjs} +8 -8
  36. package/dist/{createAggregationRouter-B1exOVD8.mjs → createAggregationRouter-B_B48JZ-.mjs} +7 -7
  37. package/dist/{createApp-Bv0m-Q6_.mjs → createApp-CLGegqiG.mjs} +128 -24
  38. package/dist/{defineAggregation-gX4No1gt.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
  39. package/dist/{defineResource-XFPPUu2c.mjs → defineResource-DR9vsUYy.mjs} +297 -23
  40. package/dist/discovery/index.mjs +1 -1
  41. package/dist/docs/index.d.mts +1 -1
  42. package/dist/docs/index.mjs +3 -3
  43. package/dist/{elevation-DNXXxgjl.mjs → elevation-B0tlY8y1.mjs} +4 -4
  44. package/dist/encryption/index.mjs +3 -3
  45. package/dist/{errorHandler-B2AZUJUj.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
  46. package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
  47. package/dist/{errors-Bh1KK5ps.mjs → errors-Cd_yy7aW.mjs} +1 -1
  48. package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
  49. package/dist/{eventPlugin-Dk_TyXeE.mjs → eventPlugin-Dar8qRaj.mjs} +65 -9
  50. package/dist/events/index.d.mts +5 -351
  51. package/dist/events/index.mjs +5 -780
  52. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  53. package/dist/events/transports/redis-stream-entry.mjs +1 -1
  54. package/dist/events/transports/redis.d.mts +1 -1
  55. package/dist/events/transports/redis.mjs +1 -1
  56. package/dist/factory/index.d.mts +20 -5
  57. package/dist/factory/index.mjs +7 -7
  58. package/dist/{fastify-augmentation-BppG5bMT.d.mts → fastify-augmentation-DVsTAy6X.d.mts} +3 -3
  59. package/dist/{fields-DEi65pGj.mjs → fields-CojeUkS4.mjs} +1 -1
  60. package/dist/{generate-CRSG0jTv.mjs → generate-DiB0nHPR.mjs} +2 -2
  61. package/dist/{headers-BXjQYbeC.mjs → headers-CveNO9gR.mjs} +2 -2
  62. package/dist/{health-5XnTa2uo.mjs → health-CasG2A28.mjs} +1 -1
  63. package/dist/hooks/index.d.mts +1 -1
  64. package/dist/hooks/index.mjs +2 -2
  65. package/dist/idempotency/index.d.mts +45 -1
  66. package/dist/idempotency/index.mjs +91 -9
  67. package/dist/idempotency/redis.mjs +1 -1
  68. package/dist/{index-zq6VrjaB.d.mts → index-B-DvUzsr.d.mts} +12 -2
  69. package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
  70. package/dist/{index-DXlHwvuE.d.mts → index-Brin0ODU.d.mts} +4 -4
  71. package/dist/{index-ZWhLTp_s.d.mts → index-C5NnMEKF.d.mts} +1 -1
  72. package/dist/{index-C-481Zf2.d.mts → index-ugr1tQlW.d.mts} +17 -1
  73. package/dist/index.d.mts +6 -6
  74. package/dist/index.mjs +11 -11
  75. package/dist/{init-BbfP8aky.mjs → init-mdJ-v4Xp.mjs} +18 -12
  76. package/dist/integrations/event-gateway.d.mts +1 -1
  77. package/dist/integrations/event-gateway.mjs +2 -2
  78. package/dist/integrations/index.d.mts +2 -2
  79. package/dist/integrations/index.mjs +1 -1
  80. package/dist/integrations/jobs/index.d.mts +1 -1
  81. package/dist/integrations/jobs/index.mjs +99 -97
  82. package/dist/integrations/mcp/index.d.mts +12 -3
  83. package/dist/integrations/mcp/index.mjs +2 -2
  84. package/dist/integrations/mcp/testing.d.mts +1 -1
  85. package/dist/integrations/mcp/testing.mjs +2 -2
  86. package/dist/integrations/streamline.mjs +3 -3
  87. package/dist/integrations/webhooks.d.mts +124 -1
  88. package/dist/integrations/webhooks.mjs +180 -2
  89. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  90. package/dist/integrations/websocket-redis.mjs +1 -1
  91. package/dist/integrations/websocket.mjs +1 -1
  92. package/dist/{interface-BP4IJCBO.mjs → interface-DznJrm9R.mjs} +1 -1
  93. package/dist/{jsonBody-DDSR2HFl.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
  94. package/dist/{keys-B-HBTGXl.mjs → keys-WeKvY1dE.mjs} +1 -1
  95. package/dist/{loadResources-DisEAmkm.mjs → loadResources-Bj-aSsXi.mjs} +2 -2
  96. package/dist/{loadResourcesFromEntry-D1MJp13f.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
  97. package/dist/logger/index.mjs +1 -1
  98. package/dist/{mcpPlugin-CpfVpKHE.mjs → mcpPlugin-aJg5TqHi.mjs} +60 -21
  99. package/dist/{memory-DsuWnj4T.mjs → memory-qYSB7rMQ.mjs} +2 -2
  100. package/dist/{metrics-DdQL8syd.mjs → metrics-D7uchLjN.mjs} +2 -2
  101. package/dist/middleware/index.d.mts +1 -1
  102. package/dist/middleware/index.mjs +3 -3
  103. package/dist/migrations/index.mjs +2 -2
  104. package/dist/{module-Br81am6m.mjs → module-CwKsgZXp.mjs} +1 -1
  105. package/dist/{multipartBody-BnkBLN7v.mjs → multipartBody-4w-S3-PU.mjs} +1 -1
  106. package/dist/{openapi-DIRzvxC0.mjs → openapi-BN5cQaO2.mjs} +6 -6
  107. package/dist/outbox-admin/index.d.mts +2 -2
  108. package/dist/outbox-admin/index.mjs +4 -4
  109. package/dist/outbox-module-CXB79bvt.mjs +839 -0
  110. package/dist/outbox-module-D-VFq37T.d.mts +368 -0
  111. package/dist/permissions/index.d.mts +2 -2
  112. package/dist/permissions/index.mjs +5 -5
  113. package/dist/{permissions-Dl_m0NCe.mjs → permissions-BAfwgBxc.mjs} +25 -6
  114. package/dist/{pipe-ELHCP3zj.mjs → pipe-BzzoEhHj.mjs} +2 -2
  115. package/dist/pipeline/index.d.mts +1 -1
  116. package/dist/pipeline/index.mjs +2 -2
  117. package/dist/{pipeline-CPA-jx0M.mjs → pipeline-q69DZzgG.mjs} +6 -6
  118. package/dist/plugins/index.d.mts +6 -6
  119. package/dist/plugins/index.mjs +16 -16
  120. package/dist/plugins/response-cache.mjs +2 -2
  121. package/dist/plugins/tracing-entry.mjs +2 -2
  122. package/dist/{pluralize-C2ZkKbsn.mjs → pluralize-B7xf8qZF.mjs} +1 -1
  123. package/dist/{preloadResources-CDs87fTw.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
  124. package/dist/{preloadResources-T1hmlTEZ.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
  125. package/dist/presets/filesUpload.d.mts +2 -2
  126. package/dist/presets/filesUpload.mjs +5 -5
  127. package/dist/presets/index.d.mts +2 -2
  128. package/dist/presets/index.mjs +2 -2
  129. package/dist/presets/multiTenant.d.mts +1 -1
  130. package/dist/presets/multiTenant.mjs +3 -3
  131. package/dist/presets/search.d.mts +2 -2
  132. package/dist/presets/search.mjs +2 -2
  133. package/dist/{presets-Dntd_pUO.mjs → presets-D-h0uWtP.mjs} +4 -4
  134. package/dist/projection-BKM0Ui-s.mjs +42 -0
  135. package/dist/{queryCachePlugin-Dhps-Ldt.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +49 -7
  136. package/dist/{rateLimit-C7mmAGs3.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
  137. package/dist/{rateLimitKey-CccEFBfk.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
  138. package/dist/{realtime-DVvVZZFz.mjs → realtime-CTkv3O59.mjs} +9 -9
  139. package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
  140. package/dist/{registerRoute-B_QrjYZS.mjs → registerRoute-DNnd3vkz.mjs} +4 -4
  141. package/dist/registry/index.d.mts +3 -3
  142. package/dist/registry/index.mjs +3 -3
  143. package/dist/{registry-DqUmzA9W.mjs → registry-CLxCR5LF.mjs} +2 -2
  144. package/dist/{renewingLease-BpP9wwj7.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
  145. package/dist/{replyHelpers-DS-P0b32.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
  146. package/dist/{repositoryFilter-DotlxG3L.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
  147. package/dist/{requestContext-B6vQF-sy.mjs → requestContext-uYE35JxI.mjs} +1 -1
  148. package/dist/{rolldown-runtime-BhflRrBs.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
  149. package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
  150. package/dist/{schedules-C2Q3uv2d.mjs → schedules-CAcZgia0.mjs} +3 -3
  151. package/dist/{schemaConverter-Crjvczi_.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
  152. package/dist/{schemaIR-CnyKK_1j.mjs → schemaIR-CDvclAew.mjs} +2 -2
  153. package/dist/schemas/index.mjs +1 -1
  154. package/dist/scim/index.mjs +2 -2
  155. package/dist/scope/index.mjs +5 -5
  156. package/dist/{scope-afz_Ieek.mjs → scope-BaWcqBvr.mjs} +4 -4
  157. package/dist/{simpleEqualityMatcher-C4KiMWUa.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
  158. package/dist/{sse-B9P4Y-UI.mjs → sse-Cf0jUHsh.mjs} +5 -5
  159. package/dist/{sseStream-B3zb_d1h.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
  160. package/dist/{store-helpers-DmEMHlPQ.mjs → store-helpers-B88p99Z2.mjs} +1 -1
  161. package/dist/{streaming-DPZdQmDz.mjs → streaming-DsT1B5zJ.mjs} +1 -1
  162. package/dist/{subscribe-helpers-sp5xTDVA.mjs → subscribe-helpers-CLIOHsOK.mjs} +2 -2
  163. package/dist/sync/changelog.mjs +1 -1
  164. package/dist/testing/cleanupStoreContract.d.mts +1 -1
  165. package/dist/testing/cleanupStoreContract.mjs +3 -1
  166. package/dist/testing/index.d.mts +24 -12
  167. package/dist/testing/index.mjs +24 -12
  168. package/dist/testing/outboxStoreContract.mjs +1 -1
  169. package/dist/testing/storageContract.mjs +1 -1
  170. package/dist/{transactionContext-BvLKvOIL.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
  171. package/dist/{typeGuards-B_5-hKjJ.mjs → typeGuards-jjKJDN13.mjs} +1 -1
  172. package/dist/types/index.d.mts +3 -3
  173. package/dist/types/index.mjs +1 -1
  174. package/dist/types/storage.mjs +1 -1
  175. package/dist/{types-CT5vu6M0.d.mts → types-BcTumAz_.d.mts} +41 -7
  176. package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
  177. package/dist/{types-DXYX7dIR.mjs → types-CoqIRrkY.mjs} +4 -4
  178. package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
  179. package/dist/usage/index.d.mts +1 -1
  180. package/dist/usage/index.mjs +0 -0
  181. package/dist/{userHelpers-BNsOXzOd.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
  182. package/dist/utils/index.d.mts +57 -5
  183. package/dist/utils/index.mjs +12 -11
  184. package/dist/{utils-BtOha_Sp.mjs → utils-h1V_sT-t.mjs} +38 -7
  185. package/dist/{validate-PMCPR9El.mjs → validate-CdQlz52F.mjs} +2 -2
  186. package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
  187. package/dist/{versioning-CKv2jz_U.mjs → versioning-rZbyLVD-.mjs} +3 -3
  188. package/package.json +17 -16
  189. package/skills/arc/SKILL.md +96 -4
  190. package/skills/arc/references/api-reference.md +19 -0
  191. package/skills/arc/references/production.md +2 -2
  192. package/dist/projection-CjHhM5W1.mjs +0 -18
@@ -1,5 +1,8 @@
1
1
 
2
+ import { a as EventTransport, n as DomainEvent } from "../EventTransport-BEVP50ka.mjs";
3
+ import { n as OutboxModuleOptions, r as createOutboxModule } from "../outbox-module-D-VFq37T.mjs";
2
4
  import { FastifyPluginAsync } from "fastify";
5
+ import { OutboxFailurePolicy, OutboxStore } from "@classytic/primitives/outbox";
3
6
  //#region src/integrations/webhooks.d.ts
4
7
  interface WebhookSubscription {
5
8
  /** Unique subscription ID */
@@ -86,6 +89,23 @@ interface WebhookPluginOptions {
86
89
  * framework.
87
90
  */
88
91
  validateUrl?: (url: URL, subscription: WebhookSubscription) => void | Promise<void>;
92
+ /**
93
+ * DURABLE delivery — the outbox→worker composition. When set, a matching
94
+ * event no longer POSTs inline: each (event × subscription) pair becomes ONE
95
+ * durable row in the given outbox store, and `createDurableWebhookModule`'s
96
+ * relay claims, delivers, retries with backoff, and dead-letters — all
97
+ * surviving crash and deploy, which the in-process `retry` cannot.
98
+ *
99
+ * The row id is deterministic (`<event id>:wh:<subscription id>`) and the
100
+ * save carries it as `dedupeKey`, so an at-least-once upstream (Redis
101
+ * Streams, an events outbox) redelivering the same event enqueues each
102
+ * delivery exactly once. Mutually exclusive with `retry` — the retry axis
103
+ * moves to the relay's failure policy.
104
+ */
105
+ durable?: {
106
+ /** Outbox store the delivery rows are written to — share it with the module. */
107
+ store: OutboxStore;
108
+ };
89
109
  }
90
110
  /** `WebhookSubscription` as returned by `list()` — secret withheld. */
91
111
  type WebhookSubscriptionPublic = Omit<WebhookSubscription, "secret">;
@@ -245,5 +265,108 @@ interface VerifySignatureOptions {
245
265
  declare function verifySignature(body: string | Buffer, secret: string, signature: string | undefined, options?: VerifySignatureOptions): boolean;
246
266
  declare const webhookPlugin: FastifyPluginAsync<WebhookPluginOptions>;
247
267
  declare const _default: FastifyPluginAsync<WebhookPluginOptions>;
268
+ /** Event type carried by durable webhook delivery rows. */
269
+ declare const WEBHOOK_DELIVERY_EVENT = "arc.webhook.delivery";
270
+ /** Payload of a {@link WEBHOOK_DELIVERY_EVENT} row. */
271
+ interface WebhookDeliveryPayload {
272
+ subscriptionId: string;
273
+ /** The original event, verbatim — what the receiver is signed and sent. */
274
+ event: {
275
+ type: string;
276
+ payload: unknown;
277
+ meta: DomainEvent["meta"];
278
+ };
279
+ }
280
+ /**
281
+ * A failed webhook POST, classified for the failure policy: `transient`
282
+ * failures (network error, timeout, 429, 5xx) are worth retrying; the rest
283
+ * (4xx rejection, 3xx under `redirect: "manual"`, URL-policy rejection,
284
+ * malformed row) are final for this payload and dead-letter immediately.
285
+ */
286
+ declare class WebhookDeliveryError extends Error {
287
+ readonly status?: number;
288
+ readonly transient: boolean;
289
+ constructor(message: string, options: {
290
+ status?: number;
291
+ transient: boolean;
292
+ cause?: unknown;
293
+ });
294
+ }
295
+ interface WebhookDeliveryTransportOptions {
296
+ /**
297
+ * Resolve a subscription (WITH secret) at delivery time — fresh URL and
298
+ * secret apply to every attempt, and an unregistered id is an acknowledged
299
+ * no-op (the delivery is moot, not an error).
300
+ */
301
+ getSubscription: (id: string) => WebhookSubscription | undefined | Promise<WebhookSubscription | undefined>;
302
+ /** Custom fetch (for testing). */
303
+ fetch?: typeof globalThis.fetch;
304
+ /** Per-POST timeout in ms (default: 10000). */
305
+ timeout?: number;
306
+ /**
307
+ * URL policy — same seam as {@link WebhookPluginOptions.validateUrl}, but
308
+ * enforced at DELIVERY time (covers rows enqueued before a policy existed
309
+ * and out-of-band store writes). When set, `redirect: "manual"` applies so
310
+ * an allowed URL can't 302 into a blocked target; a rejection is permanent.
311
+ */
312
+ validateUrl?: (url: URL, subscription: WebhookSubscription) => void | Promise<void>;
313
+ }
314
+ /**
315
+ * Publish-only `EventTransport` whose `publish()` IS the webhook POST —
316
+ * plugged into an outbox relay, delivery inherits the relay's guarantees.
317
+ * Throws {@link WebhookDeliveryError} on failure so the relay observes it
318
+ * (fail → policy → retryAt/deadLetter); returns normally on 2xx and on
319
+ * unregistered subscriptions (acknowledge).
320
+ *
321
+ * The wire contract is IDENTICAL to inline dispatch: v1 signing, and
322
+ * `x-arc-webhook-delivery` carries the ORIGINAL event's `meta.id` — stable
323
+ * across retries, so receiver-side dedup is unchanged.
324
+ */
325
+ declare function createWebhookDeliveryTransport(options: WebhookDeliveryTransportOptions): EventTransport;
326
+ interface WebhookDeliveryPolicyOptions {
327
+ /** Total attempts before dead-lettering (default: 8). */
328
+ maxAttempts?: number;
329
+ /** First retry delay in ms (default: 5000). */
330
+ baseMs?: number;
331
+ /** Backoff cap in ms (default: 900000 — 15 minutes). */
332
+ maxMs?: number;
333
+ }
334
+ /**
335
+ * Default failure policy for durable webhook delivery: permanent failures
336
+ * ({@link WebhookDeliveryError} with `transient: false`) dead-letter on the
337
+ * first attempt — repeating a rejected payload can't succeed; transient ones
338
+ * retry on exponential backoff until `maxAttempts`, then dead-letter.
339
+ */
340
+ declare function webhookDeliveryFailurePolicy(options?: WebhookDeliveryPolicyOptions): OutboxFailurePolicy;
341
+ interface DurableWebhookModuleOptions extends Omit<OutboxModuleOptions, "name" | "transport" | "failurePolicy">, Pick<WebhookDeliveryTransportOptions, "fetch" | "timeout" | "validateUrl"> {
342
+ /** Module name / `dependsOn` key. Default `'webhook-delivery'`. */
343
+ name?: string;
344
+ /** Retry/backoff tuning for the default failure policy. */
345
+ delivery?: WebhookDeliveryPolicyOptions;
346
+ /** Full policy override — replaces {@link webhookDeliveryFailurePolicy}. */
347
+ failurePolicy?: OutboxFailurePolicy;
348
+ }
349
+ /**
350
+ * Durable webhook delivery as a module: the outbox relay machinery pointed at
351
+ * webhook POSTs. Pair with `webhookPlugin`'s `durable` option sharing the
352
+ * same store:
353
+ *
354
+ * ```ts
355
+ * const store = repositoryAsOutboxStore(deliveryRepo);
356
+ * createApp({
357
+ * modules: [createDurableWebhookModule({ store })],
358
+ * plugins: async (f) => {
359
+ * await f.register(webhookPlugin, { durable: { store } });
360
+ * },
361
+ * });
362
+ * ```
363
+ *
364
+ * The relay arm is `kind: 'relay'`, so `role: 'relay'` deployments run
365
+ * delivery and `role: 'api'` deployments only enqueue. The delivering process
366
+ * must ALSO register `webhookPlugin` (with the shared subscription store) —
367
+ * subscriptions resolve through `fastify.webhooks` at delivery time, which is
368
+ * boot-checked, not discovered on the first row.
369
+ */
370
+ declare function createDurableWebhookModule(options: DurableWebhookModuleOptions): ReturnType<typeof createOutboxModule>;
248
371
  //#endregion
249
- export { VerifySignatureOptions, VerifyWebhookInput, VerifyWebhookResult, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WebhookSubscriptionPublic, _default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookPlugin };
372
+ export { DurableWebhookModuleOptions, VerifySignatureOptions, VerifyWebhookInput, VerifyWebhookResult, WEBHOOK_DELIVERY_EVENT, WebhookDeliveryError, WebhookDeliveryPayload, WebhookDeliveryPolicyOptions, WebhookDeliveryRecord, WebhookDeliveryTransportOptions, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WebhookSubscriptionPublic, createDurableWebhookModule, createWebhookDeliveryTransport, _default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookDeliveryFailurePolicy, webhookPlugin };
@@ -1,6 +1,9 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { n as declareRuntimeCapability } from "../runtimeCapabilities-Dm88DaTW.mjs";
3
+ import { i as exponentialBackoff, t as createOutboxModule } from "../outbox-module-CXB79bvt.mjs";
2
4
  import { createHmac, timingSafeEqual } from "node:crypto";
3
5
  import fp from "fastify-plugin";
6
+ import { createChildEvent } from "@classytic/primitives/events";
4
7
  //#region src/integrations/webhooks.ts
5
8
  /**
6
9
  * @classytic/arc — Webhook Outbound Integration
@@ -233,6 +236,11 @@ var MemoryWebhookStore = class {
233
236
  };
234
237
  const webhookPlugin = async (fastify, opts = {}) => {
235
238
  const store = opts.store ?? new MemoryWebhookStore();
239
+ declareRuntimeCapability(fastify, {
240
+ subsystem: "webhooks.store",
241
+ durability: opts.store ? "shared" : "memory",
242
+ detail: "default MemoryWebhookStore: subscriptions + delivery log are replica-local"
243
+ });
236
244
  const fetchFn = opts.fetch ?? globalThis.fetch;
237
245
  const timeout = opts.timeout ?? 1e4;
238
246
  const maxLogEntries = opts.maxLogEntries ?? 1e3;
@@ -241,6 +249,8 @@ const webhookPlugin = async (fastify, opts = {}) => {
241
249
  const retryAttempts = opts.retry?.attempts ?? 1;
242
250
  const retryBackoffMs = opts.retry?.backoffMs ?? 1e3;
243
251
  if (!Number.isInteger(retryAttempts) || retryAttempts < 1) throw new Error(`webhookPlugin: retry.attempts must be an integer >= 1 (got ${String(opts.retry?.attempts)})`);
252
+ if (opts.durable && opts.retry) throw new Error("webhookPlugin: `durable` and `retry` are mutually exclusive — durable delivery retries via the relay's failure policy (webhookDeliveryFailurePolicy), not in-process.");
253
+ const durableStore = opts.durable?.store;
244
254
  let subscriptions = [];
245
255
  const log = [];
246
256
  subscriptions = await store.getAll();
@@ -261,6 +271,21 @@ const webhookPlugin = async (fastify, opts = {}) => {
261
271
  async function dispatchEvent(event) {
262
272
  const matching = subscriptions.filter((s) => matchesPattern(s.events, event.type));
263
273
  if (matching.length === 0) return;
274
+ if (durableStore) {
275
+ for (const sub of matching) {
276
+ const deliveryId = `${event.meta.id}:wh:${sub.id}`;
277
+ const delivery = createChildEvent(event, WEBHOOK_DELIVERY_EVENT, {
278
+ subscriptionId: sub.id,
279
+ event: {
280
+ type: event.type,
281
+ payload: event.payload,
282
+ meta: event.meta
283
+ }
284
+ }, { id: deliveryId });
285
+ await durableStore.save(delivery, { dedupeKey: deliveryId });
286
+ }
287
+ return;
288
+ }
264
289
  const body = JSON.stringify({
265
290
  type: event.type,
266
291
  payload: event.payload,
@@ -361,5 +386,158 @@ var webhooks_default = fp(webhookPlugin, {
361
386
  fastify: "5.x",
362
387
  dependencies: ["arc-events"]
363
388
  });
389
+ /** Event type carried by durable webhook delivery rows. */
390
+ const WEBHOOK_DELIVERY_EVENT = "arc.webhook.delivery";
391
+ /**
392
+ * A failed webhook POST, classified for the failure policy: `transient`
393
+ * failures (network error, timeout, 429, 5xx) are worth retrying; the rest
394
+ * (4xx rejection, 3xx under `redirect: "manual"`, URL-policy rejection,
395
+ * malformed row) are final for this payload and dead-letter immediately.
396
+ */
397
+ var WebhookDeliveryError = class extends Error {
398
+ status;
399
+ transient;
400
+ constructor(message, options) {
401
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
402
+ this.name = "WebhookDeliveryError";
403
+ if (options.status !== void 0) this.status = options.status;
404
+ this.transient = options.transient;
405
+ }
406
+ };
407
+ /**
408
+ * Publish-only `EventTransport` whose `publish()` IS the webhook POST —
409
+ * plugged into an outbox relay, delivery inherits the relay's guarantees.
410
+ * Throws {@link WebhookDeliveryError} on failure so the relay observes it
411
+ * (fail → policy → retryAt/deadLetter); returns normally on 2xx and on
412
+ * unregistered subscriptions (acknowledge).
413
+ *
414
+ * The wire contract is IDENTICAL to inline dispatch: v1 signing, and
415
+ * `x-arc-webhook-delivery` carries the ORIGINAL event's `meta.id` — stable
416
+ * across retries, so receiver-side dedup is unchanged.
417
+ */
418
+ function createWebhookDeliveryTransport(options) {
419
+ const fetchFn = options.fetch ?? globalThis.fetch;
420
+ const timeout = options.timeout ?? 1e4;
421
+ return {
422
+ name: "webhook-delivery",
423
+ async publish(event) {
424
+ if (event.type !== "arc.webhook.delivery") throw new WebhookDeliveryError(`webhook-delivery transport received event type '${event.type}' — this store must contain only ${WEBHOOK_DELIVERY_EVENT} rows (wiring bug: wrong store or transport)`, { transient: false });
425
+ const payload = event.payload;
426
+ if (!payload?.subscriptionId || !payload.event?.meta?.id) throw new WebhookDeliveryError("webhook-delivery row is malformed: payload.subscriptionId and payload.event required", { transient: false });
427
+ const sub = await options.getSubscription(payload.subscriptionId);
428
+ if (!sub) return;
429
+ let url;
430
+ try {
431
+ url = new URL(sub.url);
432
+ if (options.validateUrl) await options.validateUrl(url, sub);
433
+ } catch (err) {
434
+ throw new WebhookDeliveryError(`webhook URL rejected for subscription '${sub.id}': ${err instanceof Error ? err.message : String(err)}`, {
435
+ transient: false,
436
+ cause: err
437
+ });
438
+ }
439
+ const body = JSON.stringify(payload.event);
440
+ const timestamp = Date.now();
441
+ const v1Signature = signWebhook(body, sub.secret, {
442
+ timestamp,
443
+ deliveryId: payload.event.meta.id
444
+ });
445
+ const ac = new AbortController();
446
+ const timer = setTimeout(() => ac.abort(), timeout);
447
+ let response;
448
+ try {
449
+ response = await fetchFn(sub.url, {
450
+ method: "POST",
451
+ headers: {
452
+ "content-type": "application/json",
453
+ "x-arc-webhook-signature": v1Signature,
454
+ "x-arc-webhook-timestamp": String(timestamp),
455
+ "x-arc-webhook-delivery": payload.event.meta.id
456
+ },
457
+ body,
458
+ signal: ac.signal,
459
+ ...options.validateUrl ? { redirect: "manual" } : {}
460
+ });
461
+ } catch (err) {
462
+ throw new WebhookDeliveryError(`webhook POST to subscription '${sub.id}' failed: ${err instanceof Error ? err.message : String(err)}`, {
463
+ transient: true,
464
+ cause: err
465
+ });
466
+ } finally {
467
+ clearTimeout(timer);
468
+ }
469
+ if (!response.ok) throw new WebhookDeliveryError(`webhook POST to subscription '${sub.id}' returned ${response.status}`, {
470
+ status: response.status,
471
+ transient: response.status === 429 || response.status >= 500
472
+ });
473
+ }
474
+ };
475
+ }
476
+ /**
477
+ * Default failure policy for durable webhook delivery: permanent failures
478
+ * ({@link WebhookDeliveryError} with `transient: false`) dead-letter on the
479
+ * first attempt — repeating a rejected payload can't succeed; transient ones
480
+ * retry on exponential backoff until `maxAttempts`, then dead-letter.
481
+ */
482
+ function webhookDeliveryFailurePolicy(options = {}) {
483
+ const maxAttempts = options.maxAttempts ?? 8;
484
+ const baseMs = options.baseMs ?? 5e3;
485
+ const maxMs = options.maxMs ?? 9e5;
486
+ return ({ error, attempts }) => {
487
+ if (error instanceof WebhookDeliveryError && !error.transient) return { deadLetter: true };
488
+ if (attempts >= maxAttempts) return { deadLetter: true };
489
+ return { retryAt: exponentialBackoff({
490
+ attempt: attempts,
491
+ baseMs,
492
+ maxMs
493
+ }) };
494
+ };
495
+ }
496
+ /**
497
+ * Durable webhook delivery as a module: the outbox relay machinery pointed at
498
+ * webhook POSTs. Pair with `webhookPlugin`'s `durable` option sharing the
499
+ * same store:
500
+ *
501
+ * ```ts
502
+ * const store = repositoryAsOutboxStore(deliveryRepo);
503
+ * createApp({
504
+ * modules: [createDurableWebhookModule({ store })],
505
+ * plugins: async (f) => {
506
+ * await f.register(webhookPlugin, { durable: { store } });
507
+ * },
508
+ * });
509
+ * ```
510
+ *
511
+ * The relay arm is `kind: 'relay'`, so `role: 'relay'` deployments run
512
+ * delivery and `role: 'api'` deployments only enqueue. The delivering process
513
+ * must ALSO register `webhookPlugin` (with the shared subscription store) —
514
+ * subscriptions resolve through `fastify.webhooks` at delivery time, which is
515
+ * boot-checked, not discovered on the first row.
516
+ */
517
+ function createDurableWebhookModule(options) {
518
+ const { name, delivery, failurePolicy, fetch, timeout, validateUrl, ...outboxOptions } = options;
519
+ return createOutboxModule({
520
+ ...outboxOptions,
521
+ name: name ?? "webhook-delivery",
522
+ transport: (fastify) => {
523
+ if (!fastify.hasDecorator("webhooks")) throw new Error(`Durable webhook module "${name ?? "webhook-delivery"}" requires webhookPlugin — subscriptions resolve through fastify.webhooks at delivery time. Register it in plugins() (with the SHARED subscription store in multi-process topologies).`);
524
+ return createWebhookDeliveryTransport({
525
+ getSubscription: (id) => {
526
+ const pub = fastify.webhooks.list().find((s) => s.id === id);
527
+ if (!pub) return void 0;
528
+ const secret = fastify.webhooks.getSecret(id);
529
+ return secret === void 0 ? void 0 : {
530
+ ...pub,
531
+ secret
532
+ };
533
+ },
534
+ ...fetch !== void 0 ? { fetch } : {},
535
+ ...timeout !== void 0 ? { timeout } : {},
536
+ ...validateUrl !== void 0 ? { validateUrl } : {}
537
+ });
538
+ },
539
+ failurePolicy: failurePolicy ?? webhookDeliveryFailurePolicy(delivery)
540
+ });
541
+ }
364
542
  //#endregion
365
- export { webhooks_default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookPlugin };
543
+ export { WEBHOOK_DELIVERY_EVENT, WebhookDeliveryError, createDurableWebhookModule, createWebhookDeliveryTransport, webhooks_default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookDeliveryFailurePolicy, webhookPlugin };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/integrations/websocket-pushref-redis.ts
3
3
  /**
4
4
  * Lua script for atomic claim. Runs server-side, single-roundtrip.
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/integrations/websocket-redis.ts
3
3
  /**
4
4
  * Redis Pub/Sub adapter for cross-instance WebSocket broadcast.
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  import { randomUUID } from "node:crypto";
3
3
  import sjson from "secure-json-parse";
4
4
  import fp from "fastify-plugin";
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/idempotency/stores/interface.ts
3
3
  /**
4
4
  * Helper to create a result object
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as ArcError } from "./errors-Bh1KK5ps.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as ArcError } from "./errors-Cd_yy7aW.mjs";
3
3
  import sjson from "secure-json-parse";
4
4
  //#region src/utils/jsonBody.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/cache/keys.ts
3
3
  /**
4
4
  * Cache Key Utilities
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-BhflRrBs.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
3
3
  import { arcLog } from "./logger/index.mjs";
4
4
  import { readdir } from "node:fs/promises";
5
5
  import { dirname, join, resolve } from "node:path";
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as ResourceRegistry } from "./ResourceRegistry-BVr7PgA0.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as ResourceRegistry } from "./ResourceRegistry-DoALUlOO.mjs";
3
3
  import { resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  //#region src/cli/utils/loadResourcesFromEntry.ts
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/logger/index.ts
3
3
  let globalOptions = {};
4
4
  /**
@@ -1,19 +1,19 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { r as evaluatePermissionDecision } from "./authorizationDecision-__T3EJDA.mjs";
3
- import { p as isArcError, t as ArcError } from "./errors-Bh1KK5ps.mjs";
4
- import { n as requireSingleHeaderValue } from "./headers-BXjQYbeC.mjs";
5
- import { b as isElevated, c as getOrgId, g as getUserId, x as isMember } from "./types-DXYX7dIR.mjs";
6
- import { t as BaseController } from "./BaseController-Dk34dxhI.mjs";
7
- import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-DEi65pGj.mjs";
8
- import { t as executePipeline } from "./pipe-ELHCP3zj.mjs";
9
- import { t as buildRequestScopeProjection } from "./projection-CjHhM5W1.mjs";
10
- import { r as resolvePipelineSteps } from "./pipeline-CPA-jx0M.mjs";
11
- import { n as validateAggregations } from "./validate-PMCPR9El.mjs";
12
- import { n as executeAggregation } from "./buildHandler-Dn7rzUJo.mjs";
13
- import { t as pluralize } from "./pluralize-C2ZkKbsn.mjs";
14
- import { t as resolveActionPermission } from "./actionPermissions-Dti7ebyv.mjs";
15
- import { t as createIdempotencyResult } from "./interface-BP4IJCBO.mjs";
16
- import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-CnyKK_1j.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { r as evaluatePermissionDecision } from "./authorizationDecision-B3mP_QTy.mjs";
3
+ import { p as isArcError, t as ArcError } from "./errors-Cd_yy7aW.mjs";
4
+ import { n as requireSingleHeaderValue } from "./headers-CveNO9gR.mjs";
5
+ import { b as isElevated, c as getOrgId, g as getUserId, x as isMember } from "./types-CoqIRrkY.mjs";
6
+ import { t as BaseController } from "./BaseController-CaQC5EXX.mjs";
7
+ import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CojeUkS4.mjs";
8
+ import { t as executePipeline } from "./pipe-BzzoEhHj.mjs";
9
+ import { t as buildRequestScopeProjection } from "./projection-BKM0Ui-s.mjs";
10
+ import { r as resolvePipelineSteps } from "./pipeline-q69DZzgG.mjs";
11
+ import { n as validateAggregations } from "./validate-CdQlz52F.mjs";
12
+ import { n as executeAggregation } from "./buildHandler-DLE9xX-V.mjs";
13
+ import { t as pluralize } from "./pluralize-B7xf8qZF.mjs";
14
+ import { t as resolveActionPermission } from "./actionPermissions-CUeCmpUd.mjs";
15
+ import { t as createIdempotencyResult } from "./interface-DznJrm9R.mjs";
16
+ import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-CDvclAew.mjs";
17
17
  import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
18
18
  import { createHash, randomUUID } from "node:crypto";
19
19
  import fp from "fastify-plugin";
@@ -332,18 +332,32 @@ function expandOperatorKeys(input) {
332
332
  */
333
333
  function buildScope(auth) {
334
334
  if (!auth) return { kind: "public" };
335
+ /**
336
+ * `context` (and `teamId`, member-only) are carried through, not dropped.
337
+ *
338
+ * A resolver could already return them — `McpAuthResult` has an index
339
+ * signature — and this function silently discarded both, so a resource
340
+ * gated by `requireScopeContext('branchId')` worked over HTTP and DENIED
341
+ * every MCP call, because the dimension it authorises against was never
342
+ * populated. Fail-closed, but it made the tool permanently unusable rather
343
+ * than wrong, which is why nothing caught it.
344
+ */
345
+ const context = normalizeScopeContext(auth.context);
335
346
  if (auth.clientId && auth.organizationId) return {
336
347
  kind: "service",
337
348
  clientId: auth.clientId,
338
349
  organizationId: auth.organizationId,
339
- scopes: auth.scopes
350
+ scopes: auth.scopes,
351
+ ...context ? { context } : {}
340
352
  };
341
353
  if (auth.organizationId) return {
342
354
  kind: "member",
343
355
  userId: auth.userId,
344
356
  userRoles: auth.roles ?? [],
345
357
  organizationId: auth.organizationId,
346
- orgRoles: auth.orgRoles ?? []
358
+ orgRoles: auth.orgRoles ?? [],
359
+ ...typeof auth.teamId === "string" ? { teamId: auth.teamId } : {},
360
+ ...context ? { context } : {}
347
361
  };
348
362
  return {
349
363
  kind: "authenticated",
@@ -351,6 +365,23 @@ function buildScope(auth) {
351
365
  userRoles: auth.roles ?? []
352
366
  };
353
367
  }
368
+ /**
369
+ * Accept a resolver's `context` only as a string→string map.
370
+ *
371
+ * The resolver is host code reached through an index signature, so the value
372
+ * is `unknown`. Coercing arbitrary values (numbers, objects, null) into
373
+ * authorisation dimensions would make `requireScopeContext('branchId')`
374
+ * compare against something that was never a branch id; non-string entries are
375
+ * dropped rather than stringified. Returns `undefined` for an empty result so
376
+ * a scope reports "carries no context" rather than "carries an empty one" —
377
+ * different facts to a caller.
378
+ */
379
+ function normalizeScopeContext(value) {
380
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
381
+ const out = {};
382
+ for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
383
+ return Object.keys(out).length > 0 ? Object.freeze(out) : void 0;
384
+ }
354
385
  //#endregion
355
386
  //#region src/integrations/mcp/tool-helpers.ts
356
387
  /**
@@ -849,9 +880,12 @@ function resolveCrudDescription(override, meta) {
849
880
  * MCP agent the same field-projection it already has on list.
850
881
  */
851
882
  const PROJECTION_FIELD = z.string().optional().describe("Comma-separated field list to project (e.g. 'name,price'). Prefix with '-' to exclude (e.g. '-description').");
883
+ /** Default page cap, used only when the resource declares none. */
884
+ const DEFAULT_MCP_MAX_LIMIT = 100;
885
+ /** `limit` is per-resource — see `maxLimit` on the options. */
886
+ const limitField = (maxLimit) => z.number().int().min(1).max(maxLimit).optional().describe(`Items per page (max ${maxLimit})`);
852
887
  const PAGINATION_SHAPE = {
853
888
  page: z.number().int().min(1).optional().describe("Page number (1-based). Ignored when `cursor` is set."),
854
- limit: z.number().int().min(1).max(100).optional().describe("Items per page (max 100)"),
855
889
  cursor: z.string().optional().describe("Opaque keyset cursor from a previous page's `next`. Pass it to fetch the following page; when set, `page` is ignored (cursor/keyset pagination — stable under concurrent inserts)."),
856
890
  sort: z.string().optional().describe("Sort field, prefix with - for descending"),
857
891
  search: z.string().optional().describe("Full-text search query"),
@@ -939,9 +973,12 @@ function opDescription(op, fieldName) {
939
973
  }
940
974
  /** Build list/query shape with filterable fields, operators, and pagination */
941
975
  function buildListShape(fieldRules, options) {
942
- const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators } = options;
976
+ const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators, maxLimit = DEFAULT_MCP_MAX_LIMIT } = options;
943
977
  const allHidden = /* @__PURE__ */ new Set([...hiddenFields, ...extraHideFields]);
944
- const shape = { ...PAGINATION_SHAPE };
978
+ const shape = {
979
+ ...PAGINATION_SHAPE,
980
+ limit: limitField(maxLimit)
981
+ };
945
982
  shape._count = z.boolean().optional().describe("Return only { count } of records matching the filters — no documents fetched. Cheapest way to answer 'how many'.");
946
983
  shape._exists = z.boolean().optional().describe("Return only whether ANY record matches the filters (boolean existence check).");
947
984
  shape._distinct = z.string().optional().describe("Return the distinct values of this field across records matching the filters.");
@@ -1826,6 +1863,7 @@ function resourceToTools(resource, config = {}) {
1826
1863
  const filterableFields = resource.schemaOptions?.filterableFields ?? resource.queryParser?.allowedFilterFields;
1827
1864
  const sortableFields = resource.queryParser?.allowedSortFields;
1828
1865
  const allowedOperators = resource.queryParser?.allowedOperators;
1866
+ const maxLimit = resource.queryParser?.maxLimit;
1829
1867
  const hasSoftDelete = resource._appliedPresets?.includes("softDelete") ?? false;
1830
1868
  const tools = [];
1831
1869
  const prefix = config.toolNamePrefix;
@@ -1846,6 +1884,7 @@ function resourceToTools(resource, config = {}) {
1846
1884
  extraHideFields: config.hideFields,
1847
1885
  filterableFields,
1848
1886
  allowedOperators,
1887
+ maxLimit,
1849
1888
  adapterBodies
1850
1889
  });
1851
1890
  if (wiring?.idempotencyStore && op !== "list" && op !== "get") inputSchema._idempotencyKey = z.string().optional().describe("Optional idempotency key — retrying with the same key and input replays the first successful result instead of re-executing.");
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-BhflRrBs.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
3
3
  //#region src/cache/memory.ts
4
4
  var memory_exports = /* @__PURE__ */ __exportAll({ MemoryCacheStore: () => MemoryCacheStore });
5
5
  /**
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-BhflRrBs.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/metrics.ts
5
5
  var metrics_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
1
 
2
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-CpzxqePp.mjs";
2
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-BcR2bFZQ.mjs";
3
3
  import { RouteHandlerMethod } from "fastify";
4
4
  //#region src/middleware/middleware.d.ts
5
5
  interface NamedMiddleware {
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as CRUD_OPERATIONS } from "../constants-J99LGTJI.mjs";
3
- import { t as multipartBody } from "../multipartBody-BnkBLN7v.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "../constants-BxCoxmiN.mjs";
3
+ import { t as multipartBody } from "../multipartBody-4w-S3-PU.mjs";
4
4
  //#region src/middleware/middleware.ts
5
5
  /**
6
6
  * Named Middleware — Priority-based, conditional middleware execution.
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { t as startRenewingLease } from "../renewingLease-BpP9wwj7.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as startRenewingLease } from "../renewingLease-BnWtbA2U.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  //#region src/migrations/index.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/factory/module/index.ts
3
3
  /**
4
4
  * Identity helper for authoring a typed module — mirrors `defineResource`.
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  import sjson from "secure-json-parse";
3
3
  //#region src/middleware/multipartBody.ts
4
4
  const DEFAULT_MAX_FILE_SIZE = 10485760;
@@ -1,9 +1,9 @@
1
- /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
- import { n as getUserRoles } from "./userHelpers-BNsOXzOd.mjs";
3
- import { n as convertRouteSchema } from "./schemaConverter-Crjvczi_.mjs";
4
- import { t as pluralize } from "./pluralize-C2ZkKbsn.mjs";
5
- import { t as resolveActionPermission } from "./actionPermissions-Dti7ebyv.mjs";
6
- import { t as buildActionBodySchema } from "./createActionRouter-DkJZWo0m.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { n as getUserRoles } from "./userHelpers-CWF5pbZD.mjs";
3
+ import { n as convertRouteSchema } from "./schemaConverter-XH1RZRyu.mjs";
4
+ import { t as pluralize } from "./pluralize-B7xf8qZF.mjs";
5
+ import { t as resolveActionPermission } from "./actionPermissions-CUeCmpUd.mjs";
6
+ import { t as buildActionBodySchema } from "./createActionRouter-DVpoA0do.mjs";
7
7
  import fp from "fastify-plugin";
8
8
  //#region src/docs/openapi/canonical-schemas.ts
9
9
  /**