@classytic/arc 2.41.1 → 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-DSqD_Xsg.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
  3. package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-MGpXoxHZ.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-DawKGoXb.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-CvxKOc0m.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-DbCBxlgw.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-D5Ubn1fe.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-5lazhva9.mjs → core-Cb0LjccA.mjs} +5 -4
  37. package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
  38. package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
  39. package/dist/{preloadResources-f3boenVZ.mjs → createApp-D-953ZAq.mjs} +299 -390
  40. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
  41. package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
  42. package/dist/{defineResource-DxkMTjRn.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-CkvG0LG2.mjs → elevation-DG8SrLN-.mjs} +41 -12
  48. package/dist/encryption/index.mjs +3 -3
  49. package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-BnfDviL1.mjs} +26 -5
  50. package/dist/{errors-BH7YU_9O.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-zZY-unhr.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
  54. package/dist/{subscribe-helpers-DatDkcuG.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-BHrkoF5y.mjs → fields-ToPYpMqS.mjs} +1 -1
  66. package/dist/filter-merge-IpGywvym.mjs +99 -0
  67. package/dist/{generate-BLRf2m3y.mjs → generate-DRTkLlIl.mjs} +2 -2
  68. package/dist/{headers-BIXWKrzQ.mjs → headers-BbkIF6vA.mjs} +2 -2
  69. package/dist/health-D-Ft6JM6.mjs +22 -0
  70. package/dist/{health-ZRHeDHXH.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-CpLkjU18.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-ELR9lfeU.mjs → jsonBody-B4YVGcql.mjs} +2 -2
  108. package/dist/{keys-cpZCtcIE.mjs → keys-B2KtgMLJ.mjs} +7 -18
  109. package/dist/{loadResources-BPfWiI5s.mjs → loadResources-Dn4lalw2.mjs} +3 -3
  110. package/dist/{loadResourcesFromEntry-ixtfL3J2.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-BX5PujI0.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
  115. package/dist/{memory-Iotz0cCx.mjs → memory-9Cp7611C.mjs} +36 -34
  116. package/dist/{metrics-NMsmpRmY.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-DpLJG2_N.mjs → module-BSvOQC7E.mjs} +8 -8
  121. package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
  122. package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
  123. package/dist/{openapi-CmFf3yq_.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-D2z5WkDY.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-D8TBH44E.mjs → permissions-Cm0uYHvd.mjs} +149 -33
  131. package/dist/{pipe-FMWNheSO.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-CYEwjyjN.mjs → plugin-BccET1tL.mjs} +1256 -345
  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-CXWZIzTv.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-ZJSPgmT2.mjs → presets-Cpvj6fGO.mjs} +99 -107
  150. package/dist/{projection-BzIgzoKZ.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-DZHPOm9p.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
  154. package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
  155. package/dist/{realtime-8tIQWZz4.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-DNkgGQcK.mjs → registry-Bcz9G1bh.mjs} +10 -8
  162. package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
  163. package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
  164. package/dist/{requestContext-cDkAUr-P.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-eysyzFAF.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
  170. package/dist/routeValidator-D36SW8qt.mjs +70 -0
  171. package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
  172. package/dist/{schedules-j6uXXAeJ.mjs → schedules-C-OpmHBq.mjs} +46 -37
  173. package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BORek3O-.mjs} +2 -2
  174. package/dist/{schemaIR-CTvPDoPB.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-BUi1xV1I.mjs → scope-DQthe88b.mjs} +4 -4
  181. package/dist/shutdown-MqA81z_P.mjs +59 -0
  182. package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
  183. package/dist/{sse-BeqVKQKu.mjs → sse-CYzQBVFe.mjs} +11 -15
  184. package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-B5_Afiqn.mjs} +2 -2
  185. package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
  186. package/dist/{streaming-IcRFjWQU.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-CPbdlnVw.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
  199. package/dist/{typeGuards-BLjPgVBm.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-CgenB1gv.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-ChRvkuSZ.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-CxcctrAh.mjs → utils-ec1oTo1n.mjs} +315 -404
  212. package/dist/{validate-DrYWnOzI.mjs → validate-9yzHC1e2.mjs} +6 -14
  213. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  214. package/dist/{versioning-DKkT4hNJ.mjs → versioning-fhhWHV74.mjs} +4 -4
  215. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  216. package/dist/{workScope-CxKqoIFn.mjs → workScope-zpPyge4t.mjs} +2 -2
  217. package/package.json +16 -12
  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-BoPGQ5-4.mjs +0 -350
  225. package/dist/index-CFfLKMH4.d.mts +0 -290
  226. package/dist/interface-YzIbF7Ii.mjs +0 -17
  227. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  228. package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
  229. package/dist/sync/changelog.d.mts +0 -3
  230. package/dist/sync/changelog.mjs +0 -3
@@ -1,165 +1,27 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import "../constants-D5Ubn1fe.mjs";
3
- import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, v as hasOrgAccess } from "../types-CgenB1gv.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import "../constants-Cu7Mx7_U.mjs";
3
+ import { t as ArcError } from "../errors-CtrywMHP.mjs";
4
+ import { u as getRequestScope } from "../types-Bk9NOgOC.mjs";
5
+ import { i as tagTenantPreparation, t as applyTenantPreparation } from "../tenantPreparation-BKon6Yox.mjs";
4
6
  //#region src/presets/multiTenant.ts
5
- /**
6
- * Resolve a single TenantFieldSpec against the current scope.
7
- * Returns `undefined` if the source value isn't present on the scope.
8
- */
9
- function resolveSpec(scope, spec) {
10
- if ("contextKey" in spec) return getScopeContext(scope, spec.contextKey);
11
- if (spec.type === "org") return getOrgId(scope);
12
- if (spec.type === "team") return getTeamId(scope);
13
- }
14
- /**
15
- * Stash the resolved tenant field map on the request so `BaseController`
16
- * can forward it to the repository layer as top-level options. Needed by
17
- * plugin-scoped repos (mongokit's `multiTenantPlugin`) that read tenant
18
- * from `context.<field>` rather than from filter/query/data stamping.
19
- */
20
- function stashTenantFields(request, resolved) {
21
- if (Object.keys(resolved).length === 0) return;
22
- const target = request;
23
- target._tenantFields = {
24
- ...target._tenantFields ?? {},
25
- ...resolved
26
- };
27
- }
28
- /** Resolve every spec — returns the partial map of fields that have a value. */
29
- function resolveAll(scope, specs) {
30
- const resolved = {};
31
- const missing = [];
32
- for (const spec of specs) {
33
- const value = resolveSpec(scope, spec);
34
- if (value !== void 0) resolved[spec.field] = value;
35
- else missing.push(spec.field);
36
- }
37
- return {
38
- resolved,
39
- missing
7
+ function createTenantMiddleware(specs, mode) {
8
+ const preparation = {
9
+ specs,
10
+ mode
40
11
  };
41
- }
42
- /**
43
- * Create tenant filter middleware (strict).
44
- * Walks the configured tenant fields and applies all of them in lockstep.
45
- * Fails closed if any non-elevated caller is missing a required dimension.
46
- */
47
- function createTenantFilter(specs) {
48
- return async (request, reply) => {
49
- const scope = getRequestScope(request);
50
- if (isElevated(scope)) {
51
- const { resolved } = resolveAll(scope, specs);
52
- if (Object.keys(resolved).length > 0) {
53
- request._policyFilters = {
54
- ...request._policyFilters ?? {},
55
- ...resolved
56
- };
57
- stashTenantFields(request, resolved);
58
- }
59
- return;
60
- }
61
- if (hasOrgAccess(scope)) {
62
- const { resolved, missing } = resolveAll(scope, specs);
63
- if (missing.length === 0) {
64
- request._policyFilters = {
65
- ...request._policyFilters ?? {},
66
- ...resolved
67
- };
68
- stashTenantFields(request, resolved);
69
- return;
70
- }
71
- reply.code(403).send({
12
+ const middleware = async (request, reply) => {
13
+ try {
14
+ applyTenantPreparation(request, request.body, getRequestScope(request), preparation);
15
+ } catch (error) {
16
+ if (!(error instanceof ArcError)) throw error;
17
+ reply.code(error.status).send({
72
18
  success: false,
73
- error: "Forbidden",
74
- message: `Tenant context incomplete — missing: ${missing.join(", ")}`
19
+ error: error.status === 401 ? "Unauthorized" : "Forbidden",
20
+ message: error.message
75
21
  });
76
- return;
77
22
  }
78
- if (scope.kind === "public") {
79
- reply.code(401).send({
80
- success: false,
81
- error: "Unauthorized",
82
- message: "Authentication required for multi-tenant resources"
83
- });
84
- return;
85
- }
86
- reply.code(403).send({
87
- success: false,
88
- error: "Forbidden",
89
- message: "Organization context required for this operation"
90
- });
91
- };
92
- }
93
- /**
94
- * Create flexible tenant filter middleware (allowPublic).
95
- * Same policy as the strict variant for authenticated callers, but
96
- * allows public/unauthenticated requests through without filtering.
97
- */
98
- function createFlexibleTenantFilter(specs) {
99
- return async (request, reply) => {
100
- const scope = getRequestScope(request);
101
- if (isElevated(scope)) {
102
- const { resolved } = resolveAll(scope, specs);
103
- if (Object.keys(resolved).length > 0) {
104
- request._policyFilters = {
105
- ...request._policyFilters ?? {},
106
- ...resolved
107
- };
108
- stashTenantFields(request, resolved);
109
- }
110
- return;
111
- }
112
- if (hasOrgAccess(scope)) {
113
- const { resolved, missing } = resolveAll(scope, specs);
114
- if (missing.length === 0) {
115
- request._policyFilters = {
116
- ...request._policyFilters ?? {},
117
- ...resolved
118
- };
119
- stashTenantFields(request, resolved);
120
- return;
121
- }
122
- reply.code(403).send({
123
- success: false,
124
- error: "Forbidden",
125
- message: `Tenant context incomplete — missing: ${missing.join(", ")}`
126
- });
127
- return;
128
- }
129
- };
130
- }
131
- /**
132
- * Create tenant injection middleware.
133
- * Walks the configured tenant fields and writes each into the request body.
134
- * Fails closed if any required dimension is missing for non-elevated callers.
135
- *
136
- * Also stashes the resolved fields on `request._tenantFields` so
137
- * `BaseController.tenantRepoOptions()` can forward them to the repo layer
138
- * as top-level options — needed by plugin-scoped repos like mongokit's
139
- * `multiTenantPlugin`, which reads tenant from `context.<field>` rather
140
- * than from `data.<field>`. Without this forwarding, multi-field preset
141
- * writes (update/delete) work only when the plugin's `allowDataInjection`
142
- * fallback covers the operation's policy key, which is write-only.
143
- */
144
- function createTenantInjection(specs) {
145
- return async (request, reply) => {
146
- const scope = getRequestScope(request);
147
- if (isElevated(scope) && !getOrgId(scope)) return;
148
- const { resolved, missing } = resolveAll(scope, specs);
149
- if (missing.length > 0) {
150
- reply.code(403).send({
151
- success: false,
152
- error: "Forbidden",
153
- message: `Tenant context incomplete — missing: ${missing.join(", ")}`
154
- });
155
- return;
156
- }
157
- if (request.body) Object.assign(request.body, resolved);
158
- request._tenantFields = {
159
- ...request._tenantFields ?? {},
160
- ...resolved
161
- };
162
23
  };
24
+ return tagTenantPreparation(middleware, preparation);
163
25
  }
164
26
  function multiTenantPreset(options = {}) {
165
27
  const { tenantField, tenantFields, allowPublic = [], crossTenant = [] } = options;
@@ -169,9 +31,9 @@ function multiTenantPreset(options = {}) {
169
31
  type: "org"
170
32
  }];
171
33
  if (specs.length === 0) throw new Error("multiTenantPreset: `tenantFields` must contain at least one entry");
172
- const strictTenantFilter = createTenantFilter(specs);
173
- const flexibleTenantFilter = createFlexibleTenantFilter(specs);
174
- const tenantInjection = createTenantInjection(specs);
34
+ const strictTenantFilter = createTenantMiddleware(specs, "strict");
35
+ const flexibleTenantFilter = createTenantMiddleware(specs, "flexible");
36
+ const tenantInjection = createTenantMiddleware(specs, "inject");
175
37
  const getFilter = (route) => allowPublic.includes(route) ? flexibleTenantFilter : strictTenantFilter;
176
38
  /**
177
39
  * A cross-tenant read MARKS the request rather than simply skipping the filter.
@@ -187,9 +49,7 @@ function multiTenantPreset(options = {}) {
187
49
  * repository layer needs no change of its own — kits already accept `bypassTenant`, which is the
188
50
  * same door elevated cross-tenant reads have always used.
189
51
  */
190
- const markCrossTenantRead = async (request) => {
191
- request._crossTenantRead = true;
192
- };
52
+ const markCrossTenantRead = createTenantMiddleware(specs, "crossTenant");
193
53
  const readMiddleware = (route) => crossTenant.includes(route) ? [markCrossTenantRead] : [getFilter(route)];
194
54
  const fieldRules = {};
195
55
  for (const spec of specs) fieldRules[spec.field] = {
@@ -1,6 +1,6 @@
1
1
 
2
- import { l as PermissionCheck } from "../base-CxrV1LmG.mjs";
3
- import { At as RouteDefinition, Y as PresetResult, jn as ControllerHandler, jt as RouteMcpConfig } from "../index-DHbyPo5j.mjs";
2
+ import { l as PermissionCheck } from "../base-CZyixEQR.mjs";
3
+ import { Dt as RouteMcpConfig, Et as RouteDefinition, Mn as ControllerHandler, W as PresetResult } from "../index-mqaqZNpe.mjs";
4
4
  //#region src/presets/search.d.ts
5
5
  /**
6
6
  * Search Preset — backend-agnostic search / vector / embed routes
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { B as requireAuth, F as allowPublic } from "../permissions-D8TBH44E.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { B as requireAuth, F as allowPublic } from "../permissions-Cm0uYHvd.mjs";
3
3
  //#region src/presets/search.ts
4
4
  const BUILTINS = [
5
5
  {
@@ -1,102 +1,8 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { l as UnauthorizedError } from "./errors-BH7YU_9O.mjs";
3
- import { b as isElevated, n as PUBLIC_SCOPE } from "./types-CgenB1gv.mjs";
4
- import { B as requireAuth, F as allowPublic, U as requireRoles } from "./permissions-D8TBH44E.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { l as UnauthorizedError } from "./errors-CtrywMHP.mjs";
3
+ import { b as isElevated, n as PUBLIC_SCOPE } from "./types-Bk9NOgOC.mjs";
4
+ import { B as requireAuth, F as allowPublic, U as requireRoles } from "./permissions-Cm0uYHvd.mjs";
5
5
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
6
- //#region src/presets/ownedByUser.ts
7
- /**
8
- * Create ownership check middleware.
9
- *
10
- * FAIL-CLOSED: ownership is meaningless without an authenticated identity, so a
11
- * request with no user (or a user with no id) is DENIED here rather than sliding
12
- * through with no ownership check installed. Elevated scope (platform admin)
13
- * bypasses the check by design.
14
- */
15
- function createOwnershipCheck(ownerField, missingOwner) {
16
- return async (request, _reply) => {
17
- const scope = request.scope ?? PUBLIC_SCOPE;
18
- if (isElevated(scope)) return;
19
- const user = request.user;
20
- if (!user) throw new UnauthorizedError("Authentication required for this operation");
21
- const userWithId = user;
22
- const userId = userWithId._id ?? userWithId.id;
23
- if (!userId) throw new UnauthorizedError("Authenticated identity has no usable id");
24
- request._ownershipCheck = {
25
- field: ownerField,
26
- userId,
27
- missingOwner
28
- };
29
- };
30
- }
31
- function ownedByUserPreset(options = {}) {
32
- const { ownerField = "userId", missingOwner = "deny" } = options;
33
- const ownershipMiddleware = createOwnershipCheck(ownerField, missingOwner);
34
- return {
35
- name: "ownedByUser",
36
- permissions: {
37
- update: requireAuth(),
38
- delete: requireAuth()
39
- },
40
- middlewares: {
41
- update: [ownershipMiddleware],
42
- delete: [ownershipMiddleware]
43
- }
44
- };
45
- }
46
- //#endregion
47
- //#region src/presets/slugLookup.ts
48
- /**
49
- * Slug Lookup Preset
50
- *
51
- * Adds a route to get resource by slug.
52
- */
53
- function slugLookupPreset(options = {}) {
54
- const { slugField = "slug" } = options;
55
- return {
56
- name: "slugLookup",
57
- routes: (permissions) => [{
58
- method: "GET",
59
- path: `/slug/:${slugField}`,
60
- handler: "getBySlug",
61
- summary: "Get by slug",
62
- permissions: permissions.get ?? allowPublic(),
63
- operation: "getBySlug"
64
- }],
65
- controllerOptions: { slugField }
66
- };
67
- }
68
- //#endregion
69
- //#region src/presets/softDelete.ts
70
- /**
71
- * Soft Delete Preset
72
- *
73
- * Adds routes for listing deleted items and restoring them.
74
- * The actual soft-delete behavior (deletedAt field, query filtering)
75
- * is handled by the repository/adapter layer (e.g., MongoKit's softDelete plugin).
76
- */
77
- function softDeletePreset() {
78
- return {
79
- name: "softDelete",
80
- routes: (permissions) => [{
81
- method: "GET",
82
- path: "/deleted",
83
- handler: "getDeleted",
84
- summary: "Get soft-deleted items",
85
- permissions: permissions.list ?? requireRoles(["admin"]),
86
- operation: "listDeleted",
87
- _presetSource: "softDelete"
88
- }, {
89
- method: "POST",
90
- path: "/:id/restore",
91
- handler: "restore",
92
- summary: "Restore soft-deleted item",
93
- permissions: permissions.update ?? requireRoles(["admin"]),
94
- operation: "restore",
95
- _presetSource: "softDelete"
96
- }]
97
- };
98
- }
99
- //#endregion
100
6
  //#region src/presets/audited.ts
101
7
  /**
102
8
  * Audited preset - adds createdBy/updatedBy tracking
@@ -236,6 +142,100 @@ function bulkPreset(opts) {
236
142
  };
237
143
  }
238
144
  //#endregion
145
+ //#region src/presets/ownedByUser.ts
146
+ /**
147
+ * Create ownership check middleware.
148
+ *
149
+ * FAIL-CLOSED: ownership is meaningless without an authenticated identity, so a
150
+ * request with no user (or a user with no id) is DENIED here rather than sliding
151
+ * through with no ownership check installed. Elevated scope (platform admin)
152
+ * bypasses the check by design.
153
+ */
154
+ function createOwnershipCheck(ownerField, missingOwner) {
155
+ return async (request, _reply) => {
156
+ const scope = request.scope ?? PUBLIC_SCOPE;
157
+ if (isElevated(scope)) return;
158
+ const user = request.user;
159
+ if (!user) throw new UnauthorizedError("Authentication required for this operation");
160
+ const userWithId = user;
161
+ const userId = userWithId._id ?? userWithId.id;
162
+ if (!userId) throw new UnauthorizedError("Authenticated identity has no usable id");
163
+ request._ownershipCheck = {
164
+ field: ownerField,
165
+ userId,
166
+ missingOwner
167
+ };
168
+ };
169
+ }
170
+ function ownedByUserPreset(options = {}) {
171
+ const { ownerField = "userId", missingOwner = "deny" } = options;
172
+ const ownershipMiddleware = createOwnershipCheck(ownerField, missingOwner);
173
+ return {
174
+ name: "ownedByUser",
175
+ permissions: {
176
+ update: requireAuth(),
177
+ delete: requireAuth()
178
+ },
179
+ middlewares: {
180
+ update: [ownershipMiddleware],
181
+ delete: [ownershipMiddleware]
182
+ }
183
+ };
184
+ }
185
+ //#endregion
186
+ //#region src/presets/slugLookup.ts
187
+ /**
188
+ * Slug Lookup Preset
189
+ *
190
+ * Adds a route to get resource by slug.
191
+ */
192
+ function slugLookupPreset(options = {}) {
193
+ const { slugField = "slug" } = options;
194
+ return {
195
+ name: "slugLookup",
196
+ routes: (permissions) => [{
197
+ method: "GET",
198
+ path: `/slug/:${slugField}`,
199
+ handler: "getBySlug",
200
+ summary: "Get by slug",
201
+ permissions: permissions.get ?? allowPublic(),
202
+ operation: "getBySlug"
203
+ }],
204
+ controllerOptions: { slugField }
205
+ };
206
+ }
207
+ //#endregion
208
+ //#region src/presets/softDelete.ts
209
+ /**
210
+ * Soft Delete Preset
211
+ *
212
+ * Adds routes for listing deleted items and restoring them.
213
+ * The actual soft-delete behavior (deletedAt field, query filtering)
214
+ * is handled by the repository/adapter layer (e.g., MongoKit's softDelete plugin).
215
+ */
216
+ function softDeletePreset() {
217
+ return {
218
+ name: "softDelete",
219
+ routes: (permissions) => [{
220
+ method: "GET",
221
+ path: "/deleted",
222
+ handler: "getDeleted",
223
+ summary: "Get soft-deleted items",
224
+ permissions: permissions.list ?? requireRoles(["admin"]),
225
+ operation: "listDeleted",
226
+ _presetSource: "softDelete"
227
+ }, {
228
+ method: "POST",
229
+ path: "/:id/restore",
230
+ handler: "restore",
231
+ summary: "Restore soft-deleted item",
232
+ permissions: permissions.update ?? requireRoles(["admin"]),
233
+ operation: "restore",
234
+ _presetSource: "softDelete"
235
+ }]
236
+ };
237
+ }
238
+ //#endregion
239
239
  //#region src/presets/tree.ts
240
240
  /**
241
241
  * Tree Preset
@@ -266,14 +266,6 @@ function treePreset(options = {}) {
266
266
  }
267
267
  //#endregion
268
268
  //#region src/presets/index.ts
269
- /**
270
- * Convenience alias for multiTenantPreset with public list/get routes
271
- * Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
272
- */
273
- const flexibleMultiTenantPreset = (options = {}) => multiTenantPreset({
274
- ...options,
275
- allowPublic: ["list", "get"]
276
- });
277
269
  const presetRegistry = {
278
270
  softDelete: softDeletePreset,
279
271
  slugLookup: slugLookupPreset,
@@ -409,4 +401,4 @@ function mergePreset(config, preset) {
409
401
  return result;
410
402
  }
411
403
  //#endregion
412
- export { registerPreset as a, auditedPreset as c, ownedByUserPreset as d, getPreset as i, softDeletePreset as l, flexibleMultiTenantPreset as n, treePreset as o, getAvailablePresets as r, bulkPreset as s, applyPresets as t, slugLookupPreset as u };
404
+ export { treePreset as a, ownedByUserPreset as c, registerPreset as i, bulkPreset as l, getAvailablePresets as n, softDeletePreset as o, getPreset as r, slugLookupPreset as s, applyPresets as t, auditedPreset as u };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { c as getOrgId, f as getScopeContextMap, g as getUserId, m as getTeamId, x as isMember } from "./types-CgenB1gv.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { c as getOrgId, f as getScopeContextMap, g as getUserId, m as getTeamId, x as isMember } from "./types-Bk9NOgOC.mjs";
3
3
  //#region src/scope/projection.ts
4
4
  /**
5
5
  * Compute the request-scope projection. Returns `undefined` when no
@@ -435,4 +435,4 @@ declare class PushRefRegistry {
435
435
  private hydrateEnvelope;
436
436
  }
437
437
  //#endregion
438
- export { PushRefRegistry as a, SerializedEntry as c, Principal as i, EnvelopeWriter as l, FencedWriteOutcome as n, PushRefRegistryOptions as o, MemoryPushRefStore as r, PushRefStore as s, EnvelopeSnapshot as t };
438
+ export { PushRefRegistry as a, SerializedEntry as c, Principal as i, EnvelopeWriter as l, FencedWriteOutcome as n, PushRefRegistryOptions as o, MemoryPushRefStore as r, PushRefStore as s, EnvelopeSnapshot as t, DeadQueueEntry as u };
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { r as CacheStore } from "./interface-BOk3YnTz.mjs";
3
- import { i as QueryCache } from "./QueryCache-DFA-QrU9.mjs";
3
+ import { i as QueryCache } from "./QueryCache--5uAuYPW.mjs";
4
4
  import { FastifyPluginAsync } from "fastify";
5
5
  //#region src/cache/queryCachePlugin.d.ts
6
6
  interface QueryCachePluginOptions {
@@ -1,11 +1,15 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-eysyzFAF.mjs";
3
- import { i as versionKey, r as tagVersionKey } from "./keys-cpZCtcIE.mjs";
4
- import { n as declareRuntimeCapability } from "./runtimeCapabilities-CkhS6jn6.mjs";
5
- import { t as hasEvents } from "./typeGuards-BLjPgVBm.mjs";
6
- import { t as MemoryCacheStore } from "./memory-Iotz0cCx.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
3
+ import { i as versionKey, r as tagVersionKey } from "./keys-B2KtgMLJ.mjs";
4
+ import { n as declareRuntimeCapability } from "./runtimeCapabilities-0tccjRlj.mjs";
5
+ import { t as hasEvents } from "./typeGuards-CySF12G-.mjs";
6
+ import { t as MemoryCacheStore } from "./memory-9Cp7611C.mjs";
7
7
  import fp from "fastify-plugin";
8
8
  //#region src/cache/QueryCache.ts
9
+ const MISS = Object.freeze({
10
+ status: "miss",
11
+ data: void 0
12
+ });
9
13
  var QueryCache = class {
10
14
  store;
11
15
  constructor(store) {
@@ -13,17 +17,11 @@ var QueryCache = class {
13
17
  }
14
18
  async get(key) {
15
19
  const envelope = await this.store.get(key);
16
- if (!envelope?.createdAt) return {
17
- data: void 0,
18
- status: "miss"
19
- };
20
+ if (!envelope?.createdAt) return MISS;
20
21
  const now = Date.now();
21
22
  if (now >= envelope.expiresAt) {
22
23
  await this.store.delete(key);
23
- return {
24
- data: void 0,
25
- status: "miss"
26
- };
24
+ return MISS;
27
25
  }
28
26
  if (now < envelope.staleAfter) return {
29
27
  data: envelope.data,
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  //#region src/scope/rateLimitKey.ts
3
3
  /**
4
4
  * Marks a key generator as SCOPE-AWARE, so `buildRateLimitOpts` can place the rate-limit
@@ -1,13 +1,14 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-eysyzFAF.mjs";
3
- import { arcLog } from "./logger/index.mjs";
4
- import { n as evaluateAndApplyPermission } from "./authorizationDecision-DawKGoXb.mjs";
5
- import { n as getUserRoles } from "./userHelpers-ChRvkuSZ.mjs";
6
- import { c as getOrgId } from "./types-CgenB1gv.mjs";
7
- import { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-C9bzq2gO.mjs";
8
- import { t as applyFieldReadPermissions } from "./fields-BHrkoF5y.mjs";
9
- import { a as promoteStreamTokenToHeader } from "./streaming-IcRFjWQU.mjs";
10
- import { t as openSseStream } from "./sseStream-Ppzo-WUH.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 { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-auW8QjXN.mjs";
5
+ import { n as getUserRoles } from "./userHelpers-aklawRaK.mjs";
6
+ import { n as isEventVisible, t as eventAudienceOf } from "./tenancy--ed9eH_z.mjs";
7
+ import { t as applyFieldReadPermissions } from "./fields-ToPYpMqS.mjs";
8
+ import { a as promoteStreamTokenToHeader } from "./streaming-DjTQhZrN.mjs";
9
+ import { n as evaluateAndApplyPermission } from "./authorizationDecision-DQYrkl5f.mjs";
10
+ import { t as openSseStream } from "./sseStream-B5_Afiqn.mjs";
11
+ import { n as arcRouteValidatorCompiler } from "./routeValidator-D36SW8qt.mjs";
11
12
  import fp from "fastify-plugin";
12
13
  //#region src/plugins/realtime.ts
13
14
  var realtime_exports = /* @__PURE__ */ __exportAll({
@@ -123,10 +124,10 @@ function resolveRowMatcher(filters, adapter) {
123
124
  * the client adds it if absent.
124
125
  */
125
126
  function resolveDelivery(event, policy, operation) {
126
- if (policy.tenantScoped) {
127
- const eventOrgId = event.meta?.organizationId;
128
- if (eventOrgId && eventOrgId !== policy.subscriberOrgId) return { kind: "drop" };
129
- }
127
+ if (policy.tenantScoped && !isEventVisible(event.meta?.organizationId, {
128
+ ...policy.subscriberOrgId !== void 0 ? { orgId: policy.subscriberOrgId } : {},
129
+ platformView: policy.subscriberPlatformView === true
130
+ })) return { kind: "drop" };
130
131
  const doc = event.payload?.data;
131
132
  if (!(!!policy.filters && Object.keys(policy.filters).length > 0)) return {
132
133
  kind: "deliver",
@@ -202,9 +203,8 @@ const realtimePlugin = async (fastify, opts = {}) => {
202
203
  * when the caller should stop.
203
204
  */
204
205
  const authorizeResource = async (resourceName, entry, request, reply) => {
205
- const holder = request;
206
- const saved = holder._policyFilters;
207
- holder._policyFilters = void 0;
206
+ const saved = request._policyFilters;
207
+ request._policyFilters = void 0;
208
208
  try {
209
209
  const listCheck = entry.permissions?.list;
210
210
  if (typeof listCheck === "function") {
@@ -215,7 +215,7 @@ const realtimePlugin = async (fastify, opts = {}) => {
215
215
  action: "list"
216
216
  }, request, reply)) return null;
217
217
  }
218
- const filters = holder._policyFilters;
218
+ const filters = request._policyFilters;
219
219
  const resolution = resolveRowMatcher(filters, fastify.arc?.registry?.getAdapter(resourceName));
220
220
  if (!resolution.enforceable) {
221
221
  await reply.code(501).send({
@@ -226,18 +226,20 @@ const realtimePlugin = async (fastify, opts = {}) => {
226
226
  return null;
227
227
  }
228
228
  const scope = request.scope;
229
+ const audience = eventAudienceOf(scope);
229
230
  return {
230
231
  name: resourceName,
231
232
  policy: {
232
233
  tenantScoped: entry.tenantField !== false,
233
- subscriberOrgId: scope ? getOrgId(scope) : void 0,
234
+ subscriberOrgId: audience.orgId,
235
+ subscriberPlatformView: audience.platformView,
234
236
  filters,
235
237
  matcher: resolution.matcher
236
238
  },
237
239
  fields: entry.fields
238
240
  };
239
241
  } finally {
240
- holder._policyFilters = saved;
242
+ request._policyFilters = saved;
241
243
  }
242
244
  };
243
245
  /** Open the stream and wire every authorized subscription onto it. */
@@ -327,9 +329,11 @@ const realtimePlugin = async (fastify, opts = {}) => {
327
329
  }
328
330
  });
329
331
  const multiplexPath = path.replace(/\/:resource$/, "");
332
+ const validatorCompiler = arcRouteValidatorCompiler(fastify);
330
333
  fastify.route({
331
334
  method: "GET",
332
335
  url: multiplexPath,
336
+ ...validatorCompiler ? { validatorCompiler } : {},
333
337
  schema: {
334
338
  tags: ["Events"],
335
339
  summary: "Realtime multi-resource change feed",
@@ -384,4 +388,4 @@ var realtime_default = fp(realtimePlugin, {
384
388
  dependencies: ["arc-events"]
385
389
  });
386
390
  //#endregion
387
- export { realtime_default as n, realtime_exports as r, realtimePlugin as t };
391
+ export { realtime_exports as n, realtime_default as t };