@classytic/arc 2.31.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 (194) hide show
  1. package/README.md +1 -1
  2. package/bin/arc.js +58 -17
  3. package/dist/{BaseController-De7GqHck.mjs → BaseController-CaQC5EXX.mjs} +298 -35
  4. package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
  5. package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
  6. package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
  7. package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-DoALUlOO.mjs} +18 -15
  8. package/dist/{actionPermissions-BE5NwaQk.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 +44 -11
  14. package/dist/auth/redis-session.mjs +1 -1
  15. package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
  16. package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
  17. package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-DLE9xX-V.mjs} +6 -5
  18. package/dist/cache/index.mjs +4 -4
  19. package/dist/{caching-BFHA6Scf.mjs → caching-BcGmvnHh.mjs} +2 -2
  20. package/dist/cleanup/index.d.mts +52 -18
  21. package/dist/cleanup/index.mjs +281 -32
  22. package/dist/cli/commands/describe.d.mts +1 -1
  23. package/dist/cli/commands/describe.mjs +4 -6
  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-D3Dv4LAV.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-BGpIxINN.mjs → core-dxhcHhUD.mjs} +3 -3
  35. package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DVpoA0do.mjs} +14 -11
  36. package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B_B48JZ-.mjs} +10 -8
  37. package/dist/{createApp-Df89BEni.mjs → createApp-CLGegqiG.mjs} +287 -62
  38. package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
  39. package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-DR9vsUYy.mjs} +318 -35
  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-D2l1mh4g.mjs → elevation-B0tlY8y1.mjs} +4 -4
  44. package/dist/encryption/index.mjs +5 -4
  45. package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
  46. package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
  47. package/dist/{errors-Cj42AntO.mjs → errors-Cd_yy7aW.mjs} +1 -1
  48. package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
  49. package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dar8qRaj.mjs} +68 -11
  50. package/dist/events/index.d.mts +18 -297
  51. package/dist/events/index.mjs +7 -758
  52. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  53. package/dist/events/transports/redis-stream-entry.mjs +14 -10
  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 +131 -8
  58. package/dist/fastify-augmentation-DVsTAy6X.d.mts +179 -0
  59. package/dist/{fields-CB5Cuzk9.mjs → fields-CojeUkS4.mjs} +6 -11
  60. package/dist/{generate-EcA6ui4i.mjs → generate-DiB0nHPR.mjs} +2 -2
  61. package/dist/{headers-3zvE0rM3.mjs → headers-CveNO9gR.mjs} +2 -2
  62. package/dist/{health-DjZYjM7H.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 +95 -11
  67. package/dist/idempotency/redis.mjs +1 -1
  68. package/dist/{index-CEJNZFtX.d.mts → index-B-DvUzsr.d.mts} +82 -3
  69. package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
  70. package/dist/{index-Bc2ox8_U.d.mts → index-Brin0ODU.d.mts} +4 -4
  71. package/dist/{index-CFThn1K9.d.mts → index-C5NnMEKF.d.mts} +70 -4
  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-BEiNZBsJ.mjs → init-mdJ-v4Xp.mjs} +24 -16
  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 +100 -98
  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 +38 -35
  92. package/dist/{interface-DYzM0q-u.mjs → interface-DznJrm9R.mjs} +1 -1
  93. package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
  94. package/dist/{keys-C0_pa1tY.mjs → keys-WeKvY1dE.mjs} +1 -1
  95. package/dist/{loadResources-yawi9tAE.mjs → loadResources-Bj-aSsXi.mjs} +4 -3
  96. package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
  97. package/dist/logger/index.mjs +1 -1
  98. package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-aJg5TqHi.mjs} +76 -36
  99. package/dist/{memory-DR6wpa9F.mjs → memory-qYSB7rMQ.mjs} +5 -5
  100. package/dist/{metrics-CUdw_wLB.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 +3 -3
  104. package/dist/{module-0pFJA6YW.mjs → module-CwKsgZXp.mjs} +1 -1
  105. package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-4w-S3-PU.mjs} +3 -3
  106. package/dist/{openapi-B2L4js1v.mjs → openapi-BN5cQaO2.mjs} +6 -6
  107. package/dist/outbox-admin/index.d.mts +85 -0
  108. package/dist/outbox-admin/index.mjs +115 -0
  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 +3 -3
  112. package/dist/permissions/index.mjs +6 -6
  113. package/dist/{permissions-L--QDAir.mjs → permissions-BAfwgBxc.mjs} +120 -8
  114. package/dist/{pipe-DRDNsI2T.mjs → pipe-BzzoEhHj.mjs} +3 -5
  115. package/dist/pipeline/index.d.mts +1 -1
  116. package/dist/pipeline/index.mjs +2 -2
  117. package/dist/{pipeline-UPQFatit.mjs → pipeline-q69DZzgG.mjs} +17 -15
  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-O0FZgESl.mjs → pluralize-B7xf8qZF.mjs} +1 -1
  123. package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
  124. package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
  125. package/dist/presets/filesUpload.d.mts +2 -2
  126. package/dist/presets/filesUpload.mjs +6 -6
  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-C4-g4_4P.mjs → presets-D-h0uWtP.mjs} +6 -5
  134. package/dist/projection-BKM0Ui-s.mjs +42 -0
  135. package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +50 -8
  136. package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
  137. package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
  138. package/dist/{realtime-C5NoxWFY.mjs → realtime-CTkv3O59.mjs} +10 -10
  139. package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
  140. package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-DNnd3vkz.mjs} +10 -8
  141. package/dist/registry/index.d.mts +3 -3
  142. package/dist/registry/index.mjs +3 -3
  143. package/dist/{registry-4213U4zb.mjs → registry-CLxCR5LF.mjs} +2 -2
  144. package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
  145. package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
  146. package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
  147. package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-uYE35JxI.mjs} +1 -1
  148. package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
  149. package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
  150. package/dist/{schedules-DdtlICTf.mjs → schedules-CAcZgia0.mjs} +3 -3
  151. package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
  152. package/dist/{schemaIR-Bm2enbnh.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-DHx5Fous.mjs → scope-BaWcqBvr.mjs} +4 -4
  157. package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
  158. package/dist/{sse-CUTo9y0X.mjs → sse-Cf0jUHsh.mjs} +7 -6
  159. package/dist/{sseStream-B_5XcVVd.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
  160. package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-B88p99Z2.mjs} +1 -1
  161. package/dist/{streaming-PSZwi8JV.mjs → streaming-DsT1B5zJ.mjs} +1 -1
  162. package/dist/{subscribe-helpers-C0Tb2aUp.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 +12 -6
  166. package/dist/testing/index.d.mts +52 -13
  167. package/dist/testing/index.mjs +110 -40
  168. package/dist/testing/outboxStoreContract.d.mts +11 -2
  169. package/dist/testing/outboxStoreContract.mjs +110 -21
  170. package/dist/testing/storageContract.mjs +11 -6
  171. package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
  172. package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-jjKJDN13.mjs} +1 -1
  173. package/dist/types/index.d.mts +3 -3
  174. package/dist/types/index.mjs +1 -1
  175. package/dist/types/storage.mjs +1 -1
  176. package/dist/{types-CBgvkp8O.d.mts → types-BcTumAz_.d.mts} +128 -18
  177. package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
  178. package/dist/{types-Cys0ko9Z.mjs → types-CoqIRrkY.mjs} +4 -4
  179. package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
  180. package/dist/usage/index.d.mts +1 -1
  181. package/dist/usage/index.mjs +0 -0
  182. package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
  183. package/dist/utils/index.d.mts +57 -5
  184. package/dist/utils/index.mjs +12 -11
  185. package/dist/{utils-vL8BrDE2.mjs → utils-h1V_sT-t.mjs} +38 -7
  186. package/dist/{validate-_KbifpHF.mjs → validate-CdQlz52F.mjs} +2 -2
  187. package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
  188. package/dist/{versioning-CHDTor3P.mjs → versioning-rZbyLVD-.mjs} +4 -4
  189. package/package.json +31 -20
  190. package/skills/arc/SKILL.md +96 -4
  191. package/skills/arc/references/api-reference.md +19 -0
  192. package/skills/arc/references/production.md +2 -2
  193. package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
  194. package/dist/projection-BHw6xlOQ.mjs +0 -18
@@ -1,9 +1,9 @@
1
1
 
2
- import { Ht as ServerAccessor, Rt as IControllerResponse, V as ResourceDefinition, zt as IRequestContext } from "../../index-CpzxqePp.mjs";
3
2
  import { i as RequestScope } from "../../types-ChuShpCP.mjs";
4
- import { r as PermissionCheck } from "../../types-B7XeLEgK.mjs";
3
+ import { l as PermissionCheck } from "../../base-Csjc2oF-.mjs";
4
+ import { Fn as IControllerResponse, In as IRequestContext, V as ResourceDefinition, zn as ServerAccessor } from "../../index-BcR2bFZQ.mjs";
5
5
  import { n as FieldPermissionMap } from "../../fields-I_EeRWND.mjs";
6
- import { _ as ToolDefinition, a as CrudDescriptionOverride, c as McpAuthResult, d as McpResourceConfig, f as PromptDefinition, g as ToolContext, h as ToolAnnotations, i as CrudDescriptionMeta, l as McpExecutionWiring, m as SessionEntry, n as CallToolResult, o as CrudOperation, p as PromptResult, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpPluginOptions } from "../../types-CwcUQ610.mjs";
6
+ import { _ as ToolDefinition, a as CrudDescriptionOverride, c as McpAuthResult, d as McpResourceConfig, f as PromptDefinition, g as ToolContext, h as ToolAnnotations, i as CrudDescriptionMeta, l as McpExecutionWiring, m as SessionEntry, n as CallToolResult, o as CrudOperation, p as PromptResult, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpPluginOptions } from "../../types-BfC83KsP.mjs";
7
7
  import { ErrorContract } from "@classytic/repo-core/errors";
8
8
  import { FastifyPluginAsync } from "fastify";
9
9
  import { z } from "zod";
@@ -222,6 +222,15 @@ interface FieldRulesToZodOptions {
222
222
  filterableFields?: readonly string[];
223
223
  /** Allowed filter operators — generates `field[op]` entries in list mode (e.g., price[gt], price[lte]) */
224
224
  allowedOperators?: readonly string[];
225
+ /**
226
+ * The resource's page-size cap, for the `limit` field's `max` AND its description.
227
+ *
228
+ * Defaults to 100 only when the resource states nothing. Hardcoding 100 here made
229
+ * MCP a FOURTH independent cap: a resource configured for 1000 exposed a tool that
230
+ * rejected `limit: 500` outright and told the agent "max 100" — so the model was
231
+ * misinformed about the ceiling, not merely limited by it.
232
+ */
233
+ maxLimit?: number;
225
234
  }
226
235
  /** Single field rule entry from Arc's schemaOptions.fieldRules */
227
236
  interface FieldRuleEntry {
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-B_es0hiF.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-aJg5TqHi.mjs";
3
3
  //#region src/integrations/mcp/defineTool.ts
4
4
  /**
5
5
  * Define a type-safe MCP tool.
@@ -1,5 +1,5 @@
1
1
 
2
- import { c as McpAuthResult, u as McpPluginOptions } from "../../types-CwcUQ610.mjs";
2
+ import { c as McpAuthResult, u as McpPluginOptions } from "../../types-BfC83KsP.mjs";
3
3
  //#region src/integrations/mcp/testing.d.ts
4
4
  interface TestMcpClientOptions {
5
5
  /** MCP plugin options (resources, overrides, etc.) — same as mcpPlugin config */
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-B_es0hiF.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-aJg5TqHi.mjs";
3
3
  //#region src/integrations/mcp/testing.ts
4
4
  /**
5
5
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { f as createError, i as NotFoundError, r as ForbiddenError } from "../errors-Cj42AntO.mjs";
3
- import { a as promoteStreamTokenToHeader, n as forwardedStreamHeaders } from "../streaming-PSZwi8JV.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { f as createError, i as NotFoundError, r as ForbiddenError } from "../errors-Cd_yy7aW.mjs";
3
+ import { a as promoteStreamTokenToHeader, n as forwardedStreamHeaders } from "../streaming-DsT1B5zJ.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/integrations/streamline.ts
6
6
  /**
@@ -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.31.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.31.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.31.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.31.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";
@@ -334,19 +334,21 @@ var MemoryPushRefStore = class {
334
334
  }
335
335
  async claim(args) {
336
336
  const existing = this.entries.get(args.pushRef);
337
- if (existing) if (!existing.active && existing.expiresAt <= args.now) this.entries.delete(args.pushRef);
338
- else {
339
- if (existing.principal !== args.principal) return { outcome: "rejected" };
340
- const supersededActive = existing.active === true;
341
- existing.active = true;
342
- existing.expiresAt = args.now + args.ttlMs;
343
- existing.generation = (existing.generation ?? 0) + 1;
344
- this.entries.set(args.pushRef, existing);
345
- return {
346
- outcome: "resumed",
347
- entry: existing,
348
- supersededActive
349
- };
337
+ if (existing) {
338
+ if (!existing.active && existing.expiresAt <= args.now) this.entries.delete(args.pushRef);
339
+ else {
340
+ if (existing.principal !== args.principal) return { outcome: "rejected" };
341
+ const supersededActive = existing.active === true;
342
+ existing.active = true;
343
+ existing.expiresAt = args.now + args.ttlMs;
344
+ existing.generation = (existing.generation ?? 0) + 1;
345
+ this.entries.set(args.pushRef, existing);
346
+ return {
347
+ outcome: "resumed",
348
+ entry: existing,
349
+ supersededActive
350
+ };
351
+ }
350
352
  }
351
353
  if (this.entries.size >= args.maxEntries && !this.evictOldestInactive()) return { outcome: "rejected" };
352
354
  const fresh = args.mintFresh();
@@ -1053,9 +1055,7 @@ async function handleConnection(ctx, socket, request) {
1053
1055
  }));
1054
1056
  break;
1055
1057
  }
1056
- default:
1057
- await options.onMessage?.(client, msg);
1058
- break;
1058
+ default: await options.onMessage?.(client, msg);
1059
1059
  }
1060
1060
  });
1061
1061
  socket.on("close", async () => {
@@ -1137,10 +1137,12 @@ function registerStatsRoute(fastify, rooms, path, expose) {
1137
1137
  fastify.get(`${path}/stats`, async () => rooms.getStats());
1138
1138
  return;
1139
1139
  }
1140
- if (expose === "authenticated") if (fastify.hasDecorator("authenticate")) {
1141
- const authenticate = fastify.authenticate;
1142
- fastify.get(`${path}/stats`, { preHandler: authenticate }, async () => rooms.getStats());
1143
- } else fastify.log.warn("arc-websocket: exposeStats is \"authenticated\" but fastify.authenticate is not registered — stats endpoint skipped");
1140
+ if (expose === "authenticated") {
1141
+ if (fastify.hasDecorator("authenticate")) {
1142
+ const authenticate = fastify.authenticate;
1143
+ fastify.get(`${path}/stats`, { preHandler: authenticate }, async () => rooms.getStats());
1144
+ } else fastify.log.warn("arc-websocket: exposeStats is \"authenticated\" but fastify.authenticate is not registered — stats endpoint skipped");
1145
+ }
1144
1146
  }
1145
1147
  //#endregion
1146
1148
  //#region src/integrations/websocket/outbound-truncate.ts
@@ -1164,7 +1166,7 @@ function registerStatsRoute(fastify, rooms, path, expose) {
1164
1166
  * instance, eliminating module state.
1165
1167
  */
1166
1168
  /** 5 MiB default — same as n8n's `relayViaPubSub` ceiling. */
1167
- const DEFAULT_MAX_OUTBOUND_BYTES = 5 * 1024 * 1024;
1169
+ const DEFAULT_MAX_OUTBOUND_BYTES = 5242880;
1168
1170
  /**
1169
1171
  * Returns either the original payload (when within `max`) or a
1170
1172
  * placeholder JSON string (when oversize). The placeholder carries the
@@ -1207,7 +1209,7 @@ function createTruncator(max = DEFAULT_MAX_OUTBOUND_BYTES) {
1207
1209
  * other instances fan out to their local sockets.
1208
1210
  */
1209
1211
  /** Drop fan-out to a client whose outbound buffer exceeds this threshold. */
1210
- const WS_BACKPRESSURE_LIMIT = 256 * 1024;
1212
+ const WS_BACKPRESSURE_LIMIT = 262144;
1211
1213
  var RoomManager = class {
1212
1214
  rooms = /* @__PURE__ */ new Map();
1213
1215
  clients = /* @__PURE__ */ new Map();
@@ -1426,18 +1428,19 @@ var RoomManager = class {
1426
1428
  if (client.socket.readyState !== 1) return false;
1427
1429
  const queue = client.queue;
1428
1430
  if (queue) {
1429
- if (cls === "critical") if (client.envelope) try {
1430
- const parsed = JSON.parse(message);
1431
- queue.send(client.envelope.wrap(parsed));
1432
- if (this.registry) safeAsync(this.registry.persist(client.pushRef, client.generation), this.logger, "registry.persist", {
1433
- pushRef: client.pushRef,
1434
- generation: client.generation
1435
- });
1436
- } catch {
1437
- queue.send(message);
1438
- }
1439
- else queue.send(message);
1440
- else queue.sendRealtime(message, coalesceKey);
1431
+ if (cls === "critical") {
1432
+ if (client.envelope) try {
1433
+ const parsed = JSON.parse(message);
1434
+ queue.send(client.envelope.wrap(parsed));
1435
+ if (this.registry) safeAsync(this.registry.persist(client.pushRef, client.generation), this.logger, "registry.persist", {
1436
+ pushRef: client.pushRef,
1437
+ generation: client.generation
1438
+ });
1439
+ } catch {
1440
+ queue.send(message);
1441
+ }
1442
+ else queue.send(message);
1443
+ } else queue.sendRealtime(message, coalesceKey);
1441
1444
  return true;
1442
1445
  }
1443
1446
  if ((client.socket.bufferedAmount ?? 0) > WS_BACKPRESSURE_LIMIT) return false;
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.31.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.31.0 | MIT | https://classytic.com */
2
- import { t as ArcError } from "./errors-Cj42AntO.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.31.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.31.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.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";
@@ -61,7 +61,8 @@ var loadResources_exports = /* @__PURE__ */ __exportAll({ loadResources: () => l
61
61
  */
62
62
  async function loadResources(dir, options = {}) {
63
63
  const { suffix = ".resource", recursive = true, exclude, include, filter } = options;
64
- const absDir = resolve(dir.startsWith("file://") ? dirname(fileURLToPath(dir)) : dir);
64
+ const resolvedDir = dir.startsWith("file://") ? dirname(fileURLToPath(dir)) : dir;
65
+ const absDir = resolve(resolvedDir);
65
66
  const discovered = await collectFiles(absDir, new RegExp(`${escapeRegex(suffix)}\\.(ts|js|mts|mjs)$`), recursive);
66
67
  discovered.sort();
67
68
  const files = filter ? discovered.filter((f) => filter(f)) : discovered;
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as ResourceRegistry } from "./ResourceRegistry-qq0mn2s9.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.31.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
  /**