@classytic/arc 2.41.0 → 2.43.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 (230) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
  3. package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
  6. package/dist/actionPermissions-BotX1YOa.mjs +776 -0
  7. package/dist/audit/index.d.mts +43 -5
  8. package/dist/audit/index.mjs +48 -26
  9. package/dist/auth/audit.mjs +1 -1
  10. package/dist/auth/index.d.mts +43 -13
  11. package/dist/auth/index.mjs +130 -155
  12. package/dist/auth/redis-session.mjs +1 -1
  13. package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
  14. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  15. package/dist/boundedMap-BULzfXYU.mjs +57 -0
  16. package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
  17. package/dist/cache/index.d.mts +22 -34
  18. package/dist/cache/index.mjs +4 -4
  19. package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
  20. package/dist/claimed-B10RTfkM.mjs +8 -0
  21. package/dist/cleanup/index.d.mts +6 -10
  22. package/dist/cleanup/index.mjs +27 -73
  23. package/dist/cli/commands/describe.d.mts +4 -2
  24. package/dist/cli/commands/describe.mjs +7 -4
  25. package/dist/cli/commands/docs.mjs +3 -3
  26. package/dist/cli/commands/doctor.mjs +1 -1
  27. package/dist/cli/commands/generate.mjs +2 -2
  28. package/dist/cli/commands/init.mjs +2 -2
  29. package/dist/cli/commands/introspect.mjs +2 -2
  30. package/dist/cli/index.mjs +3 -3
  31. package/dist/{constants-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
  32. package/dist/context/index.d.mts +2 -5
  33. package/dist/context/index.mjs +18 -38
  34. package/dist/core/index.d.mts +3 -3
  35. package/dist/core/index.mjs +8 -8
  36. package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
  37. package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
  38. package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
  39. package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
  40. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
  41. package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
  42. package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
  43. package/dist/discovery/index.mjs +1 -1
  44. package/dist/docs/index.d.mts +10 -4
  45. package/dist/docs/index.mjs +4 -4
  46. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  47. package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
  48. package/dist/encryption/index.mjs +3 -3
  49. package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
  50. package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
  51. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  52. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  53. package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
  54. package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
  55. package/dist/events/index.d.mts +33 -8
  56. package/dist/events/index.mjs +49 -53
  57. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  58. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  59. package/dist/events/transports/redis.d.mts +1 -1
  60. package/dist/events/transports/redis.mjs +2 -2
  61. package/dist/factory/index.d.mts +32 -17
  62. package/dist/factory/index.mjs +213 -32
  63. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
  64. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  65. package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
  66. package/dist/filter-merge-IpGywvym.mjs +99 -0
  67. package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
  68. package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
  69. package/dist/health-D-Ft6JM6.mjs +22 -0
  70. package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
  71. package/dist/hooks/index.d.mts +2 -2
  72. package/dist/hooks/index.mjs +2 -2
  73. package/dist/idempotency/index.d.mts +31 -7
  74. package/dist/idempotency/index.mjs +155 -98
  75. package/dist/idempotency/redis.d.mts +1 -1
  76. package/dist/idempotency/redis.mjs +48 -11
  77. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  78. package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
  79. package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
  80. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  81. package/dist/index-JdbZAIsm.d.mts +510 -0
  82. package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
  83. package/dist/index.d.mts +8 -8
  84. package/dist/index.mjs +11 -11
  85. package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
  86. package/dist/integrations/event-gateway.d.mts +8 -3
  87. package/dist/integrations/event-gateway.mjs +4 -4
  88. package/dist/integrations/index.d.mts +3 -3
  89. package/dist/integrations/index.mjs +1 -1
  90. package/dist/integrations/jobs/index.d.mts +1 -1
  91. package/dist/integrations/jobs/index.mjs +18 -5
  92. package/dist/integrations/mcp/index.d.mts +72 -59
  93. package/dist/integrations/mcp/index.mjs +19 -4
  94. package/dist/integrations/mcp/testing.d.mts +2 -2
  95. package/dist/integrations/mcp/testing.mjs +2 -2
  96. package/dist/integrations/streamline.d.mts +78 -136
  97. package/dist/integrations/streamline.mjs +116 -54
  98. package/dist/integrations/webhooks.d.mts +60 -120
  99. package/dist/integrations/webhooks.mjs +36 -80
  100. package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
  101. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  102. package/dist/integrations/websocket-redis.d.mts +1 -1
  103. package/dist/integrations/websocket-redis.mjs +1 -1
  104. package/dist/integrations/websocket.d.mts +3 -3
  105. package/dist/integrations/websocket.mjs +2 -2
  106. package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
  107. package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
  108. package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
  109. package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
  110. package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
  111. package/dist/logger/index.d.mts +1 -8
  112. package/dist/logger/index.mjs +3 -153
  113. package/dist/logger-DXjfIqjV.mjs +160 -0
  114. package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
  115. package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
  116. package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
  117. package/dist/middleware/index.d.mts +2 -2
  118. package/dist/middleware/index.mjs +3 -3
  119. package/dist/migrations/index.mjs +2 -2
  120. package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
  121. package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
  122. package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
  123. package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
  124. package/dist/outbox-admin/index.d.mts +3 -3
  125. package/dist/outbox-admin/index.mjs +6 -18
  126. package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
  127. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  128. package/dist/permissions/index.d.mts +5 -4
  129. package/dist/permissions/index.mjs +8 -6
  130. package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
  131. package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
  132. package/dist/pipeline/index.d.mts +2 -2
  133. package/dist/pipeline/index.mjs +2 -2
  134. package/dist/plan-FVR6LP59.mjs +565 -0
  135. package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
  136. package/dist/plugins/index.d.mts +14 -11
  137. package/dist/plugins/index.mjs +44 -22
  138. package/dist/plugins/response-cache.mjs +8 -14
  139. package/dist/plugins/tracing-entry.mjs +2 -2
  140. package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
  141. package/dist/presets/filesUpload.d.mts +2 -2
  142. package/dist/presets/filesUpload.mjs +5 -5
  143. package/dist/presets/index.d.mts +26 -31
  144. package/dist/presets/index.mjs +4 -4
  145. package/dist/presets/multiTenant.d.mts +2 -107
  146. package/dist/presets/multiTenant.mjs +22 -162
  147. package/dist/presets/search.d.mts +2 -2
  148. package/dist/presets/search.mjs +2 -2
  149. package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
  150. package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
  151. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  152. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  153. package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
  154. package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
  155. package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
  156. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  157. package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
  158. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  159. package/dist/registry/index.d.mts +9 -4
  160. package/dist/registry/index.mjs +4 -4
  161. package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
  162. package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
  163. package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
  164. package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
  165. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  166. package/dist/requirement-KDdoRcOP.mjs +35 -0
  167. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  168. package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
  169. package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
  170. package/dist/routeValidator-D36SW8qt.mjs +70 -0
  171. package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
  172. package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
  173. package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
  174. package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
  175. package/dist/schemas/index.d.mts +10 -0
  176. package/dist/schemas/index.mjs +8 -3
  177. package/dist/scim/index.mjs +2 -2
  178. package/dist/scope/index.d.mts +2 -2
  179. package/dist/scope/index.mjs +6 -6
  180. package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
  181. package/dist/shutdown-MqA81z_P.mjs +59 -0
  182. package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
  183. package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
  184. package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
  185. package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
  186. package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
  187. package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
  188. package/dist/tenancy--ed9eH_z.mjs +43 -0
  189. package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
  190. package/dist/testing/cleanupStoreContract.mjs +1 -1
  191. package/dist/testing/index.d.mts +193 -175
  192. package/dist/testing/index.mjs +286 -212
  193. package/dist/testing/outboxStoreContract.mjs +137 -2
  194. package/dist/testing/requiredPorts.d.mts +17 -0
  195. package/dist/testing/requiredPorts.mjs +65 -0
  196. package/dist/testing/storageContract.mjs +1 -1
  197. package/dist/timing-qczZ0YC-.mjs +46 -0
  198. package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
  199. package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
  200. package/dist/types/index.d.mts +4 -4
  201. package/dist/types/index.mjs +1 -1
  202. package/dist/types/storage.mjs +1 -1
  203. package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
  204. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  205. package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
  206. package/dist/usage/index.d.mts +2 -3
  207. package/dist/usage/index.mjs +5 -5
  208. package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
  209. package/dist/utils/index.d.mts +84 -223
  210. package/dist/utils/index.mjs +13 -12
  211. package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
  212. package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
  213. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  214. package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
  215. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  216. package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
  217. package/package.json +578 -574
  218. package/skills/arc/SKILL.md +13 -4
  219. package/skills/arc/references/auth.md +8 -5
  220. package/skills/arc/references/integrations.md +1 -1
  221. package/skills/arc/references/mcp.md +8 -6
  222. package/skills/arc/references/multi-tenancy.md +19 -14
  223. package/skills/arc/references/production.md +27 -10
  224. package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
  225. package/dist/index-CFfLKMH4.d.mts +0 -290
  226. package/dist/interface-BIpgzbZl.mjs +0 -17
  227. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  228. package/dist/registerRoute-DRsSHPSM.mjs +0 -364
  229. package/dist/sync/changelog.d.mts +0 -3
  230. package/dist/sync/changelog.mjs +0 -3
@@ -0,0 +1,180 @@
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as createTenantKeyGenerator } from "./rateLimitKey-C4O-kPm0.mjs";
3
+ //#region src/permissions/resourceRateLimit.ts
4
+ function decision(allowed, max, remaining, ttlSeconds) {
5
+ const reset = Math.max(1, ttlSeconds);
6
+ return {
7
+ allowed,
8
+ retryAfterSeconds: reset,
9
+ headers: {
10
+ "x-ratelimit-limit": max,
11
+ "x-ratelimit-remaining": remaining,
12
+ "x-ratelimit-reset": reset,
13
+ ...!allowed ? { "retry-after": reset } : {}
14
+ }
15
+ };
16
+ }
17
+ /** Policy namespacing must reach the store key, not merely the local limiter cache. */
18
+ function policyIdentity(resource, policy) {
19
+ if (!Number.isSafeInteger(policy.max) || policy.max <= 0) throw new Error("rateLimit.max must be a positive safe integer.");
20
+ return JSON.stringify([
21
+ "arc-resource",
22
+ resource,
23
+ policy.bucket ?? "resource",
24
+ policy.max,
25
+ resourceRateLimitWindowMs(policy.timeWindow)
26
+ ]);
27
+ }
28
+ const RESOURCE_RATE_LIMITER = Symbol.for("arc.resourceRateLimiter");
29
+ const tenantKey = createTenantKeyGenerator();
30
+ function resourceRateLimitKey(resource, context) {
31
+ const actor = tenantKey({
32
+ scope: context.scope,
33
+ ip: context.ip ?? "anonymous"
34
+ });
35
+ const dimension = context.scope && context.scope.kind !== "public" && "organizationId" in context.scope && context.scope.organizationId ? "tenant" : context.scope?.kind ?? "public";
36
+ return JSON.stringify([
37
+ resource,
38
+ dimension,
39
+ actor
40
+ ]);
41
+ }
42
+ const RATE_LIMITING_DISABLED = Symbol.for("arc.rateLimitingDisabled");
43
+ /**
44
+ * The host said `rateLimit: false`. A resource's declared limit is then a recorded no-op, and
45
+ * the resources are named once at ready. Absent this mark, a declared limit with no limiter
46
+ * refuses to boot: nobody decided, so nothing would enforce it.
47
+ */
48
+ function markRateLimitingDisabled(app) {
49
+ if (Reflect.get(app, RATE_LIMITING_DISABLED)) return;
50
+ const unenforced = /* @__PURE__ */ new Set();
51
+ Reflect.set(app, RATE_LIMITING_DISABLED, unenforced);
52
+ app.addHook("onReady", async () => {
53
+ if (unenforced.size === 0) return;
54
+ app.log.info({ resources: [...unenforced] }, `[arc] rate limiting is disabled for this app: declared rateLimit not enforced on ${[...unenforced].join(", ")}`);
55
+ });
56
+ }
57
+ /** Record a resource whose declared limit the host disabled; `false` when the host never decided. */
58
+ function recordUnenforcedRateLimit(app, resource) {
59
+ let owner = app;
60
+ while (owner) {
61
+ const unenforced = Reflect.get(owner, RATE_LIMITING_DISABLED);
62
+ if (unenforced instanceof Set) {
63
+ unenforced.add(resource);
64
+ return true;
65
+ }
66
+ owner = Object.getPrototypeOf(owner);
67
+ }
68
+ return false;
69
+ }
70
+ /** Uses createRateLimit's store factory, preserving Redis/custom-store configuration. */
71
+ function resolveResourceRateLimiter(app, durability = "memory") {
72
+ if (!app.hasDecorator("createRateLimit")) return void 0;
73
+ let owner = app;
74
+ while (!Object.hasOwn(owner, "createRateLimit")) {
75
+ const parent = Object.getPrototypeOf(owner);
76
+ if (!parent || typeof parent !== "object" || !("createRateLimit" in parent)) break;
77
+ owner = parent;
78
+ }
79
+ const existing = Reflect.get(owner, RESOURCE_RATE_LIMITER);
80
+ if (existing) return existing;
81
+ const limiters = /* @__PURE__ */ new Map();
82
+ const limiter = {
83
+ durability,
84
+ async consume(resource, policy, context) {
85
+ const identity = policyIdentity(resource, policy);
86
+ let consume = limiters.get(identity);
87
+ if (!consume) {
88
+ consume = owner.createRateLimit({
89
+ max: policy.max,
90
+ timeWindow: resourceRateLimitWindowMs(policy.timeWindow),
91
+ allowList: [],
92
+ skipOnError: false,
93
+ keyGenerator: (req) => req.ip
94
+ });
95
+ limiters.set(identity, consume);
96
+ }
97
+ const req = {
98
+ ip: JSON.stringify([identity, resourceRateLimitKey(resource, context)]),
99
+ routeOptions: { config: {} }
100
+ };
101
+ const result = await consume(req);
102
+ return result.isAllowed ? decision(true, policy.max, policy.max, Math.ceil(resourceRateLimitWindowMs(policy.timeWindow) / 1e3)) : decision(!result.isExceeded, result.max, result.remaining, result.ttlInSeconds);
103
+ }
104
+ };
105
+ Reflect.set(owner, RESOURCE_RATE_LIMITER, limiter);
106
+ return limiter;
107
+ }
108
+ /** Standalone tools may use memory; wired distributed apps use the host's shared store. */
109
+ function createMemoryResourceRateLimiter(capacity = 1e4) {
110
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) throw new Error("rateLimit memory capacity must be a positive safe integer.");
111
+ const windows = /* @__PURE__ */ new Map();
112
+ return {
113
+ durability: "memory",
114
+ async consume(resource, policy, context) {
115
+ const now = Date.now();
116
+ const windowMs = resourceRateLimitWindowMs(policy.timeWindow);
117
+ const key = JSON.stringify([policyIdentity(resource, policy), resourceRateLimitKey(resource, context)]);
118
+ let slot = windows.get(key);
119
+ if (!slot || slot.expires <= now) {
120
+ if (windows.size >= capacity) {
121
+ for (const [entry, window] of windows) if (window.expires <= now) windows.delete(entry);
122
+ if (!windows.has(key) && windows.size >= capacity) {
123
+ let retryMs = Number.POSITIVE_INFINITY;
124
+ for (const entry of windows.values()) retryMs = Math.min(retryMs, entry.expires - now);
125
+ return decision(false, policy.max, 0, Math.ceil(retryMs / 1e3));
126
+ }
127
+ }
128
+ slot = {
129
+ expires: now + windowMs,
130
+ count: 0
131
+ };
132
+ windows.set(key, slot);
133
+ }
134
+ slot.count++;
135
+ return decision(slot.count <= policy.max, policy.max, Math.max(0, policy.max - slot.count), Math.ceil((slot.expires - now) / 1e3));
136
+ }
137
+ };
138
+ }
139
+ function resourceRateLimitWindowMs(window) {
140
+ const match = /^\s*((?:\d+(?:\.\d+)?|\.\d+))\s*(ms|milliseconds?|msecs?|s|secs?|seconds?|m|mins?|minutes?|h|hrs?|hours?|d|days?|w|wks?|weeks?|y|yrs?|years?)?\s*$/i.exec(window);
141
+ const value = Number(match?.[1]) * ({
142
+ ms: 1,
143
+ millisecond: 1,
144
+ milliseconds: 1,
145
+ msec: 1,
146
+ msecs: 1,
147
+ s: 1e3,
148
+ sec: 1e3,
149
+ secs: 1e3,
150
+ second: 1e3,
151
+ seconds: 1e3,
152
+ m: 6e4,
153
+ min: 6e4,
154
+ mins: 6e4,
155
+ minute: 6e4,
156
+ minutes: 6e4,
157
+ h: 36e5,
158
+ hr: 36e5,
159
+ hrs: 36e5,
160
+ hour: 36e5,
161
+ hours: 36e5,
162
+ d: 864e5,
163
+ day: 864e5,
164
+ days: 864e5,
165
+ w: 6048e5,
166
+ wk: 6048e5,
167
+ wks: 6048e5,
168
+ week: 6048e5,
169
+ weeks: 6048e5,
170
+ y: 315576e5,
171
+ yr: 315576e5,
172
+ yrs: 315576e5,
173
+ year: 315576e5,
174
+ years: 315576e5
175
+ }[match?.[2]?.toLowerCase() ?? "ms"] ?? 0);
176
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`rateLimit.timeWindow '${window}' must be a positive duration in milliseconds.`);
177
+ return value;
178
+ }
179
+ //#endregion
180
+ export { resourceRateLimitWindowMs as a, resolveResourceRateLimiter as i, markRateLimitingDisabled as n, recordUnenforcedRateLimit as r, createMemoryResourceRateLimiter as t };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  import "node:module";
3
3
  //#region \0rolldown/runtime.js
4
4
  var __defProp = Object.defineProperty;
@@ -0,0 +1,70 @@
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { AjvCompiler } from "@fastify/ajv-compiler";
3
+ //#region src/core/routeValidator.ts
4
+ /**
5
+ * Arc's request validator — the AJV settings every route arc builds is validated with, on ANY host.
6
+ *
7
+ * Fastify's stock AJV runs `removeAdditional: true`, which turns `additionalProperties: false`
8
+ * into a silent STRIP: an unknown key in a strict action body or filter querystring reaches the
9
+ * handler as if it were never sent, and a dropped filter is a WIDER read. Arc's routes therefore
10
+ * carry their own `validatorCompiler` unless the instance was built by `createApp`, whose root
11
+ * AJV already runs these options (plus the host's `ajv.keywords`).
12
+ * Pinned by `tests/core/route-validator-bare-host.test.ts`.
13
+ */
14
+ /** The AJV options `createApp` configures and arc's own routes are compiled with. */
15
+ function arcAjvCustomOptions(extraKeywords = []) {
16
+ return {
17
+ coerceTypes: true,
18
+ useDefaults: true,
19
+ removeAdditional: false,
20
+ strictTypes: false,
21
+ keywords: ["example", ...extraKeywords]
22
+ };
23
+ }
24
+ /** Mark an instance whose ROOT AJV was configured with `arcAjvCustomOptions` (createApp only). */
25
+ function markArcRootValidator(fastify) {
26
+ fastify.decorate("arcRootValidator", true);
27
+ }
28
+ /** One pool for the process: `@fastify/ajv-compiler` caches an AJV per (shared schemas, options). */
29
+ const buildFromPool = AjvCompiler();
30
+ /**
31
+ * The `validatorCompiler` an arc-built route registers with — `undefined` under `createApp`
32
+ * (the root already validates this way). Schemas added with `fastify.addSchema` stay
33
+ * `$ref`-able: the shared pool is read when Fastify compiles the route.
34
+ */
35
+ function arcRouteValidatorCompiler(fastify) {
36
+ if (fastify.arcRootValidator === true) return void 0;
37
+ return ({ schema, httpPart }) => {
38
+ return buildFromPool(sharedSchemas(fastify), { customOptions: arcAjvCustomOptions() })({ schema: httpPart === "headers" ? lowerCaseHeaderSchema(schema) : schema });
39
+ };
40
+ }
41
+ /** The instance's `addSchema` pool, in the shape AJV accepts (a schema is an object or boolean). */
42
+ function sharedSchemas(fastify) {
43
+ const out = {};
44
+ for (const [id, schema] of Object.entries(fastify.getSchemas())) if (isSchemaValue(schema)) out[id] = schema;
45
+ return out;
46
+ }
47
+ function isSchemaValue(value) {
48
+ return typeof value === "boolean" || typeof value === "object" && value !== null;
49
+ }
50
+ /**
51
+ * Fastify lower-cases a headers schema's keys before compiling it with its OWN compiler and
52
+ * skips that for a custom one — so a route-level compiler must do it, or `Authorization` in a
53
+ * schema never matches the `authorization` header Node delivers.
54
+ */
55
+ function lowerCaseHeaderSchema(schema) {
56
+ if (!isPlainObject(schema)) return schema;
57
+ const out = { ...schema };
58
+ if (Array.isArray(schema.required)) out.required = schema.required.map((h) => typeof h === "string" ? h.toLowerCase() : h);
59
+ if (isPlainObject(schema.properties)) {
60
+ const properties = {};
61
+ for (const [key, value] of Object.entries(schema.properties)) properties[key.toLowerCase()] = value;
62
+ out.properties = properties;
63
+ }
64
+ return out;
65
+ }
66
+ function isPlainObject(value) {
67
+ return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
68
+ }
69
+ //#endregion
70
+ export { arcRouteValidatorCompiler as n, markArcRootValidator as r, arcAjvCustomOptions as t };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/runtimeCapabilities.ts
3
3
  /**
4
4
  * `Symbol.for` so declarations from two arc copies in one dependency graph
@@ -1,7 +1,8 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
3
- import { t as runWorkScope } from "./workScope-BhsWde4k.mjs";
4
- import { t as startRenewingLease } from "./renewingLease-B3VYh5aU.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
3
+ import { a as runShutdown, i as runAdmittedWork, r as registerShutdownParticipant } from "./shutdown-MqA81z_P.mjs";
4
+ import { t as runWorkScope } from "./workScope-zpPyge4t.mjs";
5
+ import { t as startRenewingLease } from "./renewingLease-qODa7KJQ.mjs";
5
6
  import fp from "fastify-plugin";
6
7
  //#region src/plugins/schedules.ts
7
8
  var schedules_exports = /* @__PURE__ */ __exportAll({
@@ -61,10 +62,10 @@ const schedulesPlugin = async (fastify, opts) => {
61
62
  }
62
63
  stat.runs++;
63
64
  stat.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
64
- await runWorkScope({
65
+ await runAdmittedWork(() => runWorkScope({
65
66
  kind: "job",
66
67
  id: s.name
67
- }, () => s.handler(fastify));
68
+ }, () => s.handler(fastify)));
68
69
  stat.lastError = null;
69
70
  } catch (err) {
70
71
  stat.failures++;
@@ -105,37 +106,45 @@ const schedulesPlugin = async (fastify, opts) => {
105
106
  }
106
107
  if (stats.size > 0) fastify.log.debug(`[arc-schedules] ${stats.size} schedule(s) armed${lock ? ` (leader-safe, holder ${holderId})` : ""}`);
107
108
  });
109
+ registerShutdownParticipant(fastify, {
110
+ name: "schedules",
111
+ stop: () => {
112
+ closing = true;
113
+ for (const timer of timers.values()) clearTimeout(timer);
114
+ timers.clear();
115
+ },
116
+ drain: async () => {
117
+ /**
118
+ * Let in-flight runs settle so shutdown never truncates a sweep mid-write —
119
+ * but BOUNDED, because a tick has no cancellation contract and its duration
120
+ * is not this plugin's to predict. An outbox relay whose subscriber retries
121
+ * on a backoff ladder holds one tick for minutes, and an unbounded await
122
+ * here contradicts the `unref` two functions up: pending scheduled work is
123
+ * already declared as something that must not hold the process.
124
+ *
125
+ * Waiting longer than the orchestrator's grace period buys nothing anyway —
126
+ * SIGKILL truncates the same write, without the log line naming what was
127
+ * still running.
128
+ */
129
+ const pending = [...inFlight.entries()];
130
+ if (pending.length === 0) return;
131
+ let timeout;
132
+ const expired = Symbol("drain-timeout");
133
+ const budget = new Promise((resolve) => {
134
+ timeout = setTimeout(() => resolve(expired), drainTimeoutMs);
135
+ timeout.unref?.();
136
+ });
137
+ const settled = Promise.allSettled(pending.map(([, run]) => run)).then(() => void 0);
138
+ const outcome = await Promise.race([settled, budget]);
139
+ if (timeout) clearTimeout(timeout);
140
+ if (outcome === expired) fastify.log.warn({
141
+ schedules: pending.map(([name]) => name),
142
+ drainTimeoutMs
143
+ }, "[arc-schedules] shutdown drain budget exceeded — abandoning in-flight run(s). Durable work (outbox rows, leased sweeps) is retried on the next boot; raise `drainTimeoutMs` only if a listed job is genuinely non-resumable.");
144
+ }
145
+ });
108
146
  fastify.addHook("onClose", async () => {
109
- closing = true;
110
- for (const timer of timers.values()) clearTimeout(timer);
111
- timers.clear();
112
- /**
113
- * Let in-flight runs settle so shutdown never truncates a sweep mid-write —
114
- * but BOUNDED, because a tick has no cancellation contract and its duration
115
- * is not this plugin's to predict. An outbox relay whose subscriber retries
116
- * on a backoff ladder holds one tick for minutes, and an unbounded await
117
- * here contradicts the `unref` two functions up: pending scheduled work is
118
- * already declared as something that must not hold the process.
119
- *
120
- * Waiting longer than the orchestrator's grace period buys nothing anyway —
121
- * SIGKILL truncates the same write, without the log line naming what was
122
- * still running.
123
- */
124
- const pending = [...inFlight.entries()];
125
- if (pending.length === 0) return;
126
- let timeout;
127
- const expired = Symbol("drain-timeout");
128
- const budget = new Promise((resolve) => {
129
- timeout = setTimeout(() => resolve(expired), drainTimeoutMs);
130
- timeout.unref?.();
131
- });
132
- const settled = Promise.allSettled(pending.map(([, run]) => run)).then(() => void 0);
133
- const outcome = await Promise.race([settled, budget]);
134
- if (timeout) clearTimeout(timeout);
135
- if (outcome === expired) fastify.log.warn({
136
- schedules: pending.map(([name]) => name),
137
- drainTimeoutMs
138
- }, "[arc-schedules] shutdown drain budget exceeded — abandoning in-flight run(s). Durable work (outbox rows, leased sweeps) is retried on the next boot; raise `drainTimeoutMs` only if a listed job is genuinely non-resumable.");
147
+ await runShutdown(fastify);
139
148
  });
140
149
  };
141
150
  var schedules_default = fp(schedulesPlugin, {
@@ -143,4 +152,4 @@ var schedules_default = fp(schedulesPlugin, {
143
152
  fastify: "5.x"
144
153
  });
145
154
  //#endregion
146
- export { schedules_default as n, schedules_exports as r, schedulesPlugin as t };
155
+ export { schedules_exports as n, schedules_default as t };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { arcLog } from "./logger/index.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as arcLog } from "./logger-DXjfIqjV.mjs";
3
3
  //#region src/utils/schemaConverter.ts
4
4
  /**
5
5
  * Schema Converter — Detect-First, Convert-Only-When-Needed
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { o as toJsonSchema } from "./schemaConverter-BLCSmNbr.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { o as toJsonSchema } from "./schemaConverter-BORek3O-.mjs";
3
3
  //#region src/core/schemaIR.ts
4
4
  /**
5
5
  * Schema IR — one canonical representation, two adapters.
@@ -26,12 +26,17 @@ declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TUn
26
26
  pages: Type$1.TInteger;
27
27
  hasNext: Type$1.TBoolean;
28
28
  hasPrev: Type$1.TBoolean;
29
+ totalIsEstimate: Type$1.TOptional<Type$1.TBoolean>;
30
+ countedAt: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
29
31
  }>, Type$1.TObject<{
30
32
  method: Type$1.TLiteral<"keyset">;
31
33
  data: Type$1.TArray<T>;
32
34
  limit: Type$1.TInteger;
33
35
  hasMore: Type$1.TBoolean;
34
36
  next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
37
+ prev: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
38
+ hasPrev: Type$1.TOptional<Type$1.TBoolean>;
39
+ end: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
35
40
  }>, Type$1.TObject<{
36
41
  method: Type$1.TLiteral<"aggregate">;
37
42
  data: Type$1.TArray<T>;
@@ -54,6 +59,8 @@ declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T): Type
54
59
  pages: Type$1.TInteger;
55
60
  hasNext: Type$1.TBoolean;
56
61
  hasPrev: Type$1.TBoolean;
62
+ totalIsEstimate: Type$1.TOptional<Type$1.TBoolean>;
63
+ countedAt: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
57
64
  }>;
58
65
  /** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
59
66
  declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
@@ -62,6 +69,9 @@ declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): Type
62
69
  limit: Type$1.TInteger;
63
70
  hasMore: Type$1.TBoolean;
64
71
  next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
72
+ prev: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
73
+ hasPrev: Type$1.TOptional<Type$1.TBoolean>;
74
+ end: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
65
75
  }>;
66
76
  /** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
67
77
  declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
3
3
  import { Type, Type as Type$1 } from "typebox";
4
4
  //#region src/schemas/index.ts
@@ -34,7 +34,9 @@ function ArcOffsetListResponse(itemSchema) {
34
34
  total: Type$1.Integer(),
35
35
  pages: Type$1.Integer(),
36
36
  hasNext: Type$1.Boolean(),
37
- hasPrev: Type$1.Boolean()
37
+ hasPrev: Type$1.Boolean(),
38
+ totalIsEstimate: Type$1.Optional(Type$1.Boolean()),
39
+ countedAt: Type$1.Optional(Type$1.Union([Type$1.String({ format: "date-time" }), Type$1.Null()]))
38
40
  });
39
41
  }
40
42
  /** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
@@ -44,7 +46,10 @@ function ArcKeysetListResponse(itemSchema) {
44
46
  data: Type$1.Array(itemSchema),
45
47
  limit: Type$1.Integer(),
46
48
  hasMore: Type$1.Boolean(),
47
- next: Type$1.Union([Type$1.String(), Type$1.Null()])
49
+ next: Type$1.Union([Type$1.String(), Type$1.Null()]),
50
+ prev: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()])),
51
+ hasPrev: Type$1.Optional(Type$1.Boolean()),
52
+ end: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
48
53
  });
49
54
  }
50
55
  /** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { t as parseJsonBody } from "../jsonBody-Cq9KYl2Z.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as parseJsonBody } from "../jsonBody-B4YVGcql.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/scim/errors.ts
5
5
  var ScimError = class extends Error {
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-Ddl9OjmE.mjs";
3
- import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-BXCfVcNG.mjs";
3
+ import { n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-BW2J3tNj.mjs";
4
4
  import { FastifyReply, FastifyRequest } from "fastify";
5
5
  //#region src/scope/rateLimitKey.d.ts
6
6
  interface RateLimitKeyContext {
@@ -30,4 +30,4 @@ interface ResolveOrgFromHeaderOptions {
30
30
  */
31
31
  declare function resolveOrgFromHeader(options: ResolveOrgFromHeaderOptions): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
32
32
  //#endregion
33
- export { AUTHENTICATED_SCOPE, type ElevationEvent, type ElevationOptions, type Mandate, PUBLIC_SCOPE, type RateLimitKeyContext, type RequestScope, type ResolveOrgFromHeaderOptions, type ScopeFirstCtx, type ScopeFirstCtxOptions, type ScopeFirstCtxRequest, type TenantKeyGeneratorOptions, createTenantKeyGenerator, _default as elevationPlugin, elevationPlugin as elevationPluginFn, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
33
+ export { AUTHENTICATED_SCOPE, type ElevationEvent, type ElevationOptions, type Mandate, PUBLIC_SCOPE, type RateLimitKeyContext, type RequestScope, type ResolveOrgFromHeaderOptions, type ScopeFirstCtx, type ScopeFirstCtxOptions, type ScopeFirstCtxRequest, type TenantKeyGeneratorOptions, createTenantKeyGenerator, _default as elevationPlugin, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-CDy3-zw1.mjs";
3
- import { n as elevation_default, t as elevationPlugin } from "../elevation-BF-vhGxY.mjs";
4
- import { t as createTenantKeyGenerator } from "../rateLimitKey-CK-EU2L8.mjs";
5
- import { t as resolveOrgFromHeader } from "../scope-jSxuBTFM.mjs";
6
- export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin, elevationPlugin as elevationPluginFn, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-Bk9NOgOC.mjs";
3
+ import { t as createTenantKeyGenerator } from "../rateLimitKey-C4O-kPm0.mjs";
4
+ import { t as elevation_default } from "../elevation-DG8SrLN-.mjs";
5
+ import { t as resolveOrgFromHeader } from "../scope-DQthe88b.mjs";
6
+ export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-C_YhELhe.mjs";
3
- import { n as requireSingleHeaderValue } from "./headers-CNECDE6q.mjs";
4
- import { n as getUserRoles } from "./userHelpers-DDMOZ_Pm.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-CtrywMHP.mjs";
3
+ import { n as requireSingleHeaderValue } from "./headers-BbkIF6vA.mjs";
4
+ import { n as getUserRoles } from "./userHelpers-aklawRaK.mjs";
5
5
  //#region src/scope/resolveOrgFromHeader.ts
6
6
  /**
7
7
  * Create a preHandler hook that resolves org scope from a header.
@@ -0,0 +1,59 @@
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ //#region src/utils/shutdown.ts
4
+ /** Stop admissions across every runtime before draining work and disposing domain engines. */
5
+ const SHUTDOWN = Symbol.for("arc.shutdown");
6
+ const admittedWork = new AsyncLocalStorage();
7
+ /** Descendants of admitted work may finish their causal publications during shutdown. */
8
+ async function runAdmittedWork(work) {
9
+ if (isAdmittedWork()) return await work();
10
+ const scope = { active: true };
11
+ return admittedWork.run(scope, async () => {
12
+ try {
13
+ return await work();
14
+ } finally {
15
+ scope.active = false;
16
+ }
17
+ });
18
+ }
19
+ function isAdmittedWork() {
20
+ return admittedWork.getStore()?.active === true;
21
+ }
22
+ /** Seed on the root before plugins register, so encapsulated plugins share one coordinator. */
23
+ function initShutdownRegistry(fastify) {
24
+ const holder = fastify;
25
+ if (holder[SHUTDOWN]) return;
26
+ holder[SHUTDOWN] = { participants: [] };
27
+ fastify.addHook("onClose", async () => {
28
+ const errors = await runShutdown(fastify);
29
+ for (const err of errors) fastify.log.error({ err }, "[arc] runtime shutdown failed");
30
+ });
31
+ }
32
+ /** A participant's stop must prevent new work; drain waits for work already admitted. */
33
+ function registerShutdownParticipant(fastify, participant) {
34
+ initShutdownRegistry(fastify);
35
+ const registry = fastify[SHUTDOWN];
36
+ if (!registry) throw new Error("[arc] shutdown coordinator is unavailable");
37
+ if (registry.completion) throw new Error(`[arc] cannot register ${participant.name} during shutdown`);
38
+ registry.participants.push(participant);
39
+ }
40
+ /** Once-guarded, best effort; callers dispose engines only after this completes. */
41
+ function runShutdown(fastify) {
42
+ const registry = fastify[SHUTDOWN];
43
+ if (!registry) return Promise.resolve([]);
44
+ registry.completion ??= Promise.resolve().then(async () => {
45
+ const errors = [];
46
+ for (const phase of ["stop", "drain"]) {
47
+ const participants = phase === "drain" ? [...registry.participants.filter((p) => p.kind !== "consumer"), ...registry.participants.filter((p) => p.kind === "consumer")] : registry.participants;
48
+ for (const participant of participants) try {
49
+ await participant[phase]?.();
50
+ } catch (cause) {
51
+ errors.push(new Error(`[arc] ${participant.name} shutdown ${phase} failed`, { cause }));
52
+ }
53
+ }
54
+ return errors;
55
+ });
56
+ return registry.completion;
57
+ }
58
+ //#endregion
59
+ export { runShutdown as a, runAdmittedWork as i, isAdmittedWork as n, registerShutdownParticipant as r, initShutdownRegistry as t };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/simpleEqualityMatcher.ts
3
3
  /**
4
4
  * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
@@ -1,9 +1,10 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
3
- import { arcLog } from "./logger/index.mjs";
4
- import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-CDy3-zw1.mjs";
5
- import { a as promoteStreamTokenToHeader } from "./streaming-dvppnxsr.mjs";
6
- import { t as openSseStream } from "./sseStream-CN-Lh1Tu.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
3
+ import { t as arcLog } from "./logger-DXjfIqjV.mjs";
4
+ import { n as PUBLIC_SCOPE } from "./types-Bk9NOgOC.mjs";
5
+ import { n as isEventVisible, t as eventAudienceOf } from "./tenancy--ed9eH_z.mjs";
6
+ import { a as promoteStreamTokenToHeader } from "./streaming-DjTQhZrN.mjs";
7
+ import { t as openSseStream } from "./sseStream-B5_Afiqn.mjs";
7
8
  import fp from "fastify-plugin";
8
9
  //#region src/plugins/sse.ts
9
10
  var sse_exports = /* @__PURE__ */ __exportAll({
@@ -12,7 +13,7 @@ var sse_exports = /* @__PURE__ */ __exportAll({
12
13
  });
13
14
  const log = arcLog("sse");
14
15
  const ssePlugin = async (fastify, opts = {}) => {
15
- const { path = "/events/stream", requireAuth = true, patterns = ["*"], heartbeat = 3e4, orgScoped = false, filter: customFilter, tokenQueryParam = "token" } = opts;
16
+ const { path = "/events/stream", requireAuth = true, patterns = ["*"], heartbeat = 3e4, orgScoped = true, filter: customFilter, tokenQueryParam = "token" } = opts;
16
17
  if (!fastify.hasDecorator("events")) {
17
18
  log.warn("Events plugin (arc-events) not registered. SSE plugin will not function. Register eventPlugin before ssePlugin.");
18
19
  return;
@@ -34,15 +35,10 @@ const ssePlugin = async (fastify, opts = {}) => {
34
35
  const stream = openSseStream(request, reply, { heartbeatMs: heartbeat });
35
36
  stream.onCleanup(() => activeConnections.delete(stream.close));
36
37
  const scope = request.scope ?? PUBLIC_SCOPE;
37
- const requestOrgId = getOrgId(scope);
38
- const dropOrgEvents = orgScoped && !requestOrgId;
38
+ const audience = eventAudienceOf(scope);
39
39
  for (const pattern of patterns) {
40
40
  const unsub = await fastify.events.subscribe(pattern, async (event) => {
41
- if (orgScoped) {
42
- const eventOrgId = event.meta?.organizationId;
43
- if (dropOrgEvents && eventOrgId) return;
44
- if (requestOrgId && eventOrgId && eventOrgId !== requestOrgId) return;
45
- }
41
+ if (orgScoped && !isEventVisible(event.meta?.organizationId, audience)) return;
46
42
  if (customFilter && !customFilter(event, request)) return;
47
43
  const data = JSON.stringify({
48
44
  type: event.type,
@@ -90,4 +86,4 @@ var sse_default = fp(ssePlugin, {
90
86
  dependencies: ["arc-events"]
91
87
  });
92
88
  //#endregion
93
- export { sse_default as n, sse_exports as r, ssePlugin as t };
89
+ export { sse_exports as n, sse_default as t };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { n as forwardedStreamHeaders } from "./streaming-dvppnxsr.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { n as forwardedStreamHeaders } from "./streaming-DjTQhZrN.mjs";
3
3
  //#region src/utils/sseStream.ts
4
4
  /**
5
5
  * Take over the reply socket and open an SSE stream. Call ONLY after every
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/store-helpers.ts
3
3
  /**
4
4
  * Classify an error thrown by `getOne` / `getById` / `update` as a
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  import { Readable } from "node:stream";
3
3
  //#region src/utils/streaming.ts
4
4
  /**