@classytic/arc 2.38.0 → 2.41.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 (196) hide show
  1. package/dist/{BaseController-CYaDvmOB.mjs → BaseController-DYJRZ0EJ.mjs} +114 -24
  2. package/dist/{EventTransport-Cx60_s-6.d.mts → EventTransport-CbUKDzZZ.d.mts} +33 -0
  3. package/dist/{EventTransport-Gq7JWbWw.mjs → EventTransport-n7qO5Cz3.mjs} +22 -2
  4. package/dist/{HookSystem-C0ikzqQf.mjs → HookSystem-CxvZBo77.mjs} +2 -2
  5. package/dist/{ResourceRegistry-DPcX--zk.mjs → ResourceRegistry-Cu7uTJp9.mjs} +2 -2
  6. package/dist/{actionPermissions-jlFZ1it4.mjs → actionPermissions-DPwPWPOV.mjs} +7 -6
  7. package/dist/audit/index.d.mts +2 -2
  8. package/dist/audit/index.mjs +10 -3
  9. package/dist/auth/audit.mjs +1 -1
  10. package/dist/auth/index.d.mts +2 -2
  11. package/dist/auth/index.mjs +12 -6
  12. package/dist/auth/redis-session.mjs +1 -1
  13. package/dist/{authorizationDecision-BP8GHtjc.mjs → authorizationDecision-2YY_Kaj1.mjs} +2 -2
  14. package/dist/{base-C-NL2fTi.d.mts → base-CxrV1LmG.d.mts} +10 -0
  15. package/dist/{buildHandler-DjXOqIvX.mjs → buildHandler-CUhjGHGZ.mjs} +4 -4
  16. package/dist/cache/index.d.mts +4 -2
  17. package/dist/cache/index.mjs +4 -4
  18. package/dist/{caching-a3ZEcJJs.mjs → caching-DsWDjIxx.mjs} +2 -2
  19. package/dist/cleanup/index.d.mts +2 -2
  20. package/dist/cleanup/index.mjs +21 -5
  21. package/dist/cli/commands/describe.d.mts +1 -1
  22. package/dist/cli/commands/describe.mjs +3 -3
  23. package/dist/cli/commands/docs.mjs +3 -3
  24. package/dist/cli/commands/doctor.mjs +1 -1
  25. package/dist/cli/commands/generate.mjs +2 -2
  26. package/dist/cli/commands/init.mjs +2 -2
  27. package/dist/cli/commands/introspect.mjs +2 -2
  28. package/dist/cli/index.mjs +3 -3
  29. package/dist/{constants-BONghE1Y.mjs → constants-BxLgNFF7.mjs} +1 -1
  30. package/dist/context/index.d.mts +34 -6
  31. package/dist/context/index.mjs +42 -24
  32. package/dist/core/index.d.mts +2 -2
  33. package/dist/core/index.mjs +7 -7
  34. package/dist/{core-8She7FgF.mjs → core-iNGqYerO.mjs} +3 -3
  35. package/dist/{createActionRouter-Bqi6Bhhx.mjs → createActionRouter-BBlbPs3k.mjs} +7 -7
  36. package/dist/{createAggregationRouter-BsYO7ktd.mjs → createAggregationRouter-Dv5SuK2x.mjs} +7 -7
  37. package/dist/createMcpServer-CbDWkZ1T.d.mts +48 -0
  38. package/dist/{defineAggregation-0KBvqEEC.mjs → defineAggregation-UyeurGkp.mjs} +1 -1
  39. package/dist/{defineResource-BMdyRelW.mjs → defineResource-DzGT-2LP.mjs} +20 -22
  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-C6woysGC.mjs → elevation-BF-vhGxY.mjs} +4 -4
  44. package/dist/encryption/index.mjs +3 -3
  45. package/dist/{errorHandler-DI5AN2b5.d.mts → errorHandler--4rSMMvq.d.mts} +7 -1
  46. package/dist/{errorHandler-BdmIWPba.mjs → errorHandler-CIMkCOWm.mjs} +3 -3
  47. package/dist/{errors-2ExmOgIJ.mjs → errors-C_YhELhe.mjs} +1 -1
  48. package/dist/{eventPlugin-DFvHa4Nf.d.mts → eventPlugin-B52abJ8W.d.mts} +1 -1
  49. package/dist/{eventPlugin-DJwpGMzx.mjs → eventPlugin-CmXaPWLv.mjs} +5 -5
  50. package/dist/events/index.d.mts +4 -4
  51. package/dist/events/index.mjs +5 -5
  52. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  53. package/dist/events/transports/redis-stream-entry.mjs +34 -24
  54. package/dist/events/transports/redis.d.mts +1 -1
  55. package/dist/events/transports/redis.mjs +11 -8
  56. package/dist/factory/index.d.mts +5 -5
  57. package/dist/factory/index.mjs +5 -5
  58. package/dist/{fastify-augmentation-jYA2oHyL.d.mts → fastify-augmentation-Cq2nHVaW.d.mts} +3 -3
  59. package/dist/{fields-Cha2QCKH.mjs → fields-BTyPLHK0.mjs} +1 -1
  60. package/dist/{generate-BUP4Oydg.mjs → generate-D_YmiCNf.mjs} +4 -3
  61. package/dist/{headers-DeXNifVj.mjs → headers-CNECDE6q.mjs} +2 -2
  62. package/dist/{health-Bh0N_FLk.mjs → health-CxQzbJmr.mjs} +10 -2
  63. package/dist/hooks/index.d.mts +1 -1
  64. package/dist/hooks/index.mjs +2 -2
  65. package/dist/idempotency/index.d.mts +1 -1
  66. package/dist/idempotency/index.mjs +4 -4
  67. package/dist/idempotency/redis.d.mts +1 -1
  68. package/dist/idempotency/redis.mjs +1 -1
  69. package/dist/{index-BtYoMip1.d.mts → index-BHdZD8zK.d.mts} +10 -2
  70. package/dist/{index-D2uIUSQk.d.mts → index-CFfLKMH4.d.mts} +2 -2
  71. package/dist/{index-eyg5WEcy.d.mts → index-CbJiYrfv.d.mts} +1 -1
  72. package/dist/{index-BRoDtjek.d.mts → index-D4u76eQW.d.mts} +3 -3
  73. package/dist/{index-B78WZom8.d.mts → index-DHbyPo5j.d.mts} +52 -12
  74. package/dist/index.d.mts +5 -5
  75. package/dist/index.mjs +11 -11
  76. package/dist/{init-Dg_Am2o_.mjs → init-Y2MlSN9n.mjs} +9 -9
  77. package/dist/integrations/event-gateway.d.mts +1 -1
  78. package/dist/integrations/event-gateway.mjs +2 -2
  79. package/dist/integrations/index.d.mts +2 -2
  80. package/dist/integrations/index.mjs +1 -1
  81. package/dist/integrations/jobs/index.d.mts +1 -1
  82. package/dist/integrations/jobs/index.mjs +2 -2
  83. package/dist/integrations/mcp/index.d.mts +4 -47
  84. package/dist/integrations/mcp/index.mjs +2 -2
  85. package/dist/integrations/mcp/testing.d.mts +22 -2
  86. package/dist/integrations/mcp/testing.mjs +55 -33
  87. package/dist/integrations/streamline.mjs +3 -3
  88. package/dist/integrations/webhooks.d.mts +2 -2
  89. package/dist/integrations/webhooks.mjs +3 -3
  90. package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
  91. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  92. package/dist/integrations/websocket-redis.mjs +1 -1
  93. package/dist/integrations/websocket.mjs +24 -6
  94. package/dist/{interface-DYPVscCU.mjs → interface-BIpgzbZl.mjs} +1 -1
  95. package/dist/{jsonBody-DVui0uJl.mjs → jsonBody-Cq9KYl2Z.mjs} +2 -2
  96. package/dist/{keys-BSuxGS-B.mjs → keys-Cmv4cBRa.mjs} +1 -1
  97. package/dist/{loadResources-CNd1DWnK.mjs → loadResources-CblF7xt2.mjs} +2 -2
  98. package/dist/{loadResourcesFromEntry-DNj8cpiY.mjs → loadResourcesFromEntry-99fIMC3p.mjs} +2 -2
  99. package/dist/logger/index.mjs +1 -1
  100. package/dist/{mcpPlugin-KWsezMx7.mjs → mcpPlugin-xF3Gw-HW.mjs} +75 -31
  101. package/dist/{memory-BiPVaCPg.mjs → memory-CpDZ9NPw.mjs} +2 -2
  102. package/dist/{metrics-CsWaIVTa.mjs → metrics-BaKnq8ag.mjs} +2 -2
  103. package/dist/middleware/index.d.mts +1 -1
  104. package/dist/middleware/index.mjs +3 -3
  105. package/dist/migrations/index.mjs +2 -2
  106. package/dist/{module-BQXHxMEk.mjs → module-BkQEesNQ.mjs} +1 -1
  107. package/dist/{multipartBody-u6VrS_iH.mjs → multipartBody-C92aJ8am.mjs} +1 -1
  108. package/dist/{openapi-Dxc8RLy_.mjs → openapi-C3LFRzPq.mjs} +6 -6
  109. package/dist/outbox-admin/index.d.mts +2 -2
  110. package/dist/outbox-admin/index.mjs +4 -4
  111. package/dist/{outbox-module-CQXu_O6o.d.mts → outbox-module-BHdUnuuK.d.mts} +15 -2
  112. package/dist/{outbox-module-DyIqA5vo.mjs → outbox-module-dsCyLxXH.mjs} +43 -21
  113. package/dist/permissions/index.d.mts +2 -2
  114. package/dist/permissions/index.mjs +5 -5
  115. package/dist/{permissions-H7Zcb82Z.mjs → permissions-DiHXy8Ck.mjs} +12 -6
  116. package/dist/{pipe-SunhEaZ6.mjs → pipe-CBVmUJ-j.mjs} +2 -2
  117. package/dist/pipeline/index.d.mts +1 -1
  118. package/dist/pipeline/index.mjs +2 -2
  119. package/dist/plugins/index.d.mts +7 -37
  120. package/dist/plugins/index.mjs +34 -22
  121. package/dist/plugins/response-cache.mjs +9 -2
  122. package/dist/plugins/tracing-entry.mjs +2 -2
  123. package/dist/{pluralize-Da7IT_qJ.mjs → pluralize-2_U6QZQg.mjs} +1 -1
  124. package/dist/{preloadResources-DlhH81py.mjs → preloadResources-Bbp4QJ8V.mjs} +82 -26
  125. package/dist/{preloadResources-BbBkNkqK.d.mts → preloadResources-CjS2OfpU.d.mts} +14 -9
  126. package/dist/presets/filesUpload.d.mts +2 -2
  127. package/dist/presets/filesUpload.mjs +5 -5
  128. package/dist/presets/index.d.mts +2 -2
  129. package/dist/presets/index.mjs +2 -2
  130. package/dist/presets/multiTenant.d.mts +38 -1
  131. package/dist/presets/multiTenant.mjs +24 -6
  132. package/dist/presets/search.d.mts +2 -2
  133. package/dist/presets/search.mjs +2 -2
  134. package/dist/{presets-CNyfaxGL.mjs → presets-BdlugYTc.mjs} +4 -4
  135. package/dist/{projection-DSfEoooA.mjs → projection-Uvgp-EEj.mjs} +2 -2
  136. package/dist/{queryCachePlugin-oTsmVyEQ.mjs → queryCachePlugin-B5F09bYr.mjs} +11 -5
  137. package/dist/rateLimitKey-CK-EU2L8.mjs +41 -0
  138. package/dist/{realtime-CPmjjgqd.mjs → realtime-FnnD0n3Z.mjs} +9 -9
  139. package/dist/{redis-CduhKsJi.d.mts → redis-Bzi1y3F9.d.mts} +10 -2
  140. package/dist/{redis-stream-eh0eOtjh.d.mts → redis-stream-BPKscsTB.d.mts} +12 -7
  141. package/dist/{registerRoute-XvcI_3sh.mjs → registerRoute-DRsSHPSM.mjs} +6 -6
  142. package/dist/registry/index.d.mts +2 -2
  143. package/dist/registry/index.mjs +3 -3
  144. package/dist/{registry-UFEJQN8R.mjs → registry-D3y8VEJ1.mjs} +2 -2
  145. package/dist/{renewingLease-DuRGVOMe.mjs → renewingLease-B3VYh5aU.mjs} +1 -1
  146. package/dist/{replyHelpers-BHebarf0.mjs → replyHelpers-DPHsdw-5.mjs} +2 -2
  147. package/dist/{requestContext-DpmntRZy.mjs → requestContext-cSVKnYPk.mjs} +1 -1
  148. package/dist/{rolldown-runtime-Add2L50I.mjs → rolldown-runtime-DjDuyglE.mjs} +1 -1
  149. package/dist/{runtimeCapabilities-D793Pxci.mjs → runtimeCapabilities-DSA_dH1_.mjs} +31 -5
  150. package/dist/{schedules-DthM8Qag.mjs → schedules-BktJb3Y3.mjs} +8 -4
  151. package/dist/{schemaConverter-Db-MMsH6.mjs → schemaConverter-BLCSmNbr.mjs} +1 -1
  152. package/dist/{schemaIR-CIXShch7.mjs → schemaIR-K16Bb5BX.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-D03sk17n.mjs → scope-jSxuBTFM.mjs} +4 -4
  157. package/dist/{simpleEqualityMatcher-B7qVjQBv.mjs → simpleEqualityMatcher-BiOnyflP.mjs} +1 -1
  158. package/dist/{sse-D4MYqkic.mjs → sse-CH7Slxc1.mjs} +5 -5
  159. package/dist/{sseStream-BuMNPR6r.mjs → sseStream-CN-Lh1Tu.mjs} +2 -2
  160. package/dist/{store-helpers-CIF4RG8s.mjs → store-helpers-9DX9-1j_.mjs} +1 -1
  161. package/dist/{streaming-DkpW1HwJ.mjs → streaming-dvppnxsr.mjs} +1 -1
  162. package/dist/{subscribe-helpers-CBZOi6pk.mjs → subscribe-helpers-AVYPgqTl.mjs} +2 -2
  163. package/dist/sync/changelog.mjs +1 -1
  164. package/dist/testing/cleanupStoreContract.mjs +1 -1
  165. package/dist/testing/index.d.mts +5 -5
  166. package/dist/testing/index.mjs +10 -10
  167. package/dist/testing/outboxStoreContract.mjs +14 -3
  168. package/dist/testing/storageContract.mjs +1 -1
  169. package/dist/{transactionContext-DMmvcfdo.mjs → transactionContext-DCWMI6hV.mjs} +1 -1
  170. package/dist/{typeGuards-DHTnEs5X.mjs → typeGuards-D7tHFdH6.mjs} +1 -1
  171. package/dist/types/index.d.mts +2 -2
  172. package/dist/types/index.mjs +1 -1
  173. package/dist/types/storage.mjs +1 -1
  174. package/dist/{types-BqLyyH0o.mjs → types-CDy3-zw1.mjs} +4 -4
  175. package/dist/{types-LQFwC37d.d.mts → types-CWQn9-IC.d.mts} +46 -1
  176. package/dist/{types-BRU-Kd3t.d.mts → types-DfVDvkdP.d.mts} +20 -2
  177. package/dist/usage/index.d.mts +58 -2
  178. package/dist/usage/index.mjs +0 -0
  179. package/dist/{userHelpers-CXySQFRg.mjs → userHelpers-DDMOZ_Pm.mjs} +1 -1
  180. package/dist/utils/index.d.mts +110 -8
  181. package/dist/utils/index.mjs +12 -12
  182. package/dist/{utils-BbGG8XNT.mjs → utils-B-zj_b__.mjs} +61 -12
  183. package/dist/{validate-CidSeUrx.mjs → validate-R0G_JrnK.mjs} +2 -2
  184. package/dist/{versioning-B843jOYX.d.mts → versioning-D6JIK8Tk.d.mts} +58 -2
  185. package/dist/{versioning-C2r0eOZ4.mjs → versioning-DuVWcu-F.mjs} +3 -3
  186. package/dist/workScope-BhsWde4k.mjs +41 -0
  187. package/package.json +574 -565
  188. package/skills/arc/SKILL.md +8 -4
  189. package/skills/arc/references/mcp.md +20 -0
  190. package/skills/arc/references/production.md +94 -9
  191. package/skills/arc/references/testing.md +0 -12
  192. package/skills/arc-code-review/references/anti-patterns.md +1 -1
  193. package/skills/arc-code-review/references/migration-recipes.md +0 -60
  194. package/skills/arc-code-review/references/mongokit-migration.md +0 -27
  195. package/skills/arc-module-publishing/references/package-manifest.md +3 -1
  196. package/dist/rateLimitKey-CewdR-m6.mjs +0 -15
@@ -1,20 +1,20 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { p as MUTATION_OPERATIONS } from "../constants-BONghE1Y.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import { p as MUTATION_OPERATIONS } from "../constants-BxLgNFF7.mjs";
3
3
  import { arcLog } from "../logger/index.mjs";
4
- import { c as getOrgId } from "../types-BqLyyH0o.mjs";
5
- import { n as requestContext } from "../requestContext-DpmntRZy.mjs";
6
- import { t as hasEvents } from "../typeGuards-DHTnEs5X.mjs";
7
- import { t as HookSystem } from "../HookSystem-C0ikzqQf.mjs";
8
- import { t as ResourceRegistry } from "../ResourceRegistry-DPcX--zk.mjs";
9
- import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-Bh0N_FLk.mjs";
10
- import { n as caching_default, t as cachingPlugin } from "../caching-a3ZEcJJs.mjs";
11
- import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-BdmIWPba.mjs";
12
- import { n as metrics_default, t as metricsPlugin } from "../metrics-CsWaIVTa.mjs";
13
- import { n as realtime_default, t as realtimePlugin } from "../realtime-CPmjjgqd.mjs";
14
- import { t as replyHelpersPlugin } from "../replyHelpers-BHebarf0.mjs";
15
- import { n as schedules_default, t as schedulesPlugin } from "../schedules-DthM8Qag.mjs";
16
- import { n as sse_default, t as ssePlugin } from "../sse-D4MYqkic.mjs";
17
- import { n as versioning_default, t as versioningPlugin } from "../versioning-C2r0eOZ4.mjs";
4
+ import { c as getOrgId } from "../types-CDy3-zw1.mjs";
5
+ import { n as requestContext } from "../requestContext-cSVKnYPk.mjs";
6
+ import { t as hasEvents } from "../typeGuards-D7tHFdH6.mjs";
7
+ import { t as HookSystem } from "../HookSystem-CxvZBo77.mjs";
8
+ import { t as ResourceRegistry } from "../ResourceRegistry-Cu7uTJp9.mjs";
9
+ import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-CxQzbJmr.mjs";
10
+ import { n as caching_default, t as cachingPlugin } from "../caching-DsWDjIxx.mjs";
11
+ import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-CIMkCOWm.mjs";
12
+ import { n as metrics_default, t as metricsPlugin } from "../metrics-BaKnq8ag.mjs";
13
+ import { n as realtime_default, t as realtimePlugin } from "../realtime-FnnD0n3Z.mjs";
14
+ import { t as replyHelpersPlugin } from "../replyHelpers-DPHsdw-5.mjs";
15
+ import { n as schedules_default, t as schedulesPlugin } from "../schedules-BktJb3Y3.mjs";
16
+ import { n as sse_default, t as ssePlugin } from "../sse-CH7Slxc1.mjs";
17
+ import { n as versioning_default, t as versioningPlugin } from "../versioning-DuVWcu-F.mjs";
18
18
  import fp from "fastify-plugin";
19
19
  import { performance as performance$1 } from "node:perf_hooks";
20
20
  //#region src/core/arcCorePlugin.ts
@@ -35,12 +35,14 @@ const arcCorePlugin = async (fastify, opts = {}) => {
35
35
  for (const field of [
36
36
  "_policyFilters",
37
37
  "_ownershipCheck",
38
+ "_crossTenantRead",
38
39
  "fieldMask",
39
40
  "policyMetadata",
40
41
  "document"
41
42
  ]) if (!fastify.hasRequestDecorator(field)) fastify.decorateRequest(field, void 0);
42
43
  fastify.addHook("onRequest", (request, _reply, done) => {
43
44
  const store = {
45
+ kind: "request",
44
46
  requestId: request.id,
45
47
  startTime: performance.now()
46
48
  };
@@ -153,18 +155,20 @@ function createPlugin(name, definition) {
153
155
  //#endregion
154
156
  //#region src/plugins/gracefulShutdown.ts
155
157
  const gracefulShutdownPlugin = async (fastify, opts = {}) => {
156
- const { timeout = 3e4, onShutdown, signals = ["SIGTERM", "SIGINT"], logEvents = true, onForceExit = () => process.exit(1) } = opts;
157
- let isShuttingDown = false;
158
+ const { timeout = 3e4, onShutdown, signals = ["SIGTERM", "SIGINT"], logEvents = true, drainDelayMs = 0, onForceExit = () => process.exit(1) } = opts;
159
+ const state = { draining: false };
158
160
  const signalHandlers = /* @__PURE__ */ new Map();
159
161
  const shutdown = async (signal) => {
160
- if (isShuttingDown) {
162
+ if (state.draining) {
161
163
  if (logEvents) fastify.log?.warn?.({ signal }, "Shutdown already in progress, ignoring signal");
162
164
  return;
163
165
  }
164
- isShuttingDown = true;
166
+ state.draining = true;
167
+ state.since = /* @__PURE__ */ new Date();
165
168
  if (logEvents) fastify.log?.info?.({
166
169
  signal,
167
- timeout
170
+ timeout,
171
+ drainDelayMs
168
172
  }, "Shutdown signal received, starting graceful shutdown");
169
173
  const forceExitTimer = setTimeout(() => {
170
174
  if (logEvents) fastify.log?.error?.("Graceful shutdown timeout exceeded, forcing exit");
@@ -172,6 +176,10 @@ const gracefulShutdownPlugin = async (fastify, opts = {}) => {
172
176
  }, timeout);
173
177
  forceExitTimer.unref();
174
178
  try {
179
+ if (drainDelayMs > 0) {
180
+ if (logEvents) fastify.log?.info?.({ drainDelayMs }, "Draining — readiness failing, still serving");
181
+ await new Promise((resolve) => setTimeout(resolve, drainDelayMs));
182
+ }
175
183
  if (logEvents) fastify.log?.info?.("Closing server to new connections");
176
184
  await fastify.close();
177
185
  if (onShutdown) {
@@ -200,7 +208,11 @@ const gracefulShutdownPlugin = async (fastify, opts = {}) => {
200
208
  fastify.decorate("shutdown", async () => {
201
209
  await shutdown("MANUAL");
202
210
  });
203
- if (logEvents) fastify.log?.debug?.({ signals }, "Graceful shutdown plugin registered");
211
+ fastify.decorate("shutdownState", state);
212
+ if (logEvents) fastify.log?.debug?.({
213
+ signals,
214
+ drainDelayMs
215
+ }, "Graceful shutdown plugin registered");
204
216
  };
205
217
  var gracefulShutdown_default = fp(gracefulShutdownPlugin, {
206
218
  name: "arc-graceful-shutdown",
@@ -1,5 +1,6 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as hasEvents } from "../typeGuards-DHTnEs5X.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import { n as declareRuntimeCapability } from "../runtimeCapabilities-DSA_dH1_.mjs";
3
+ import { t as hasEvents } from "../typeGuards-D7tHFdH6.mjs";
3
4
  import fp from "fastify-plugin";
4
5
  //#region src/plugins/response-cache.ts
5
6
  /**
@@ -96,6 +97,12 @@ const responseCachePluginImpl = async (fastify, opts = {}) => {
96
97
  if (maxEntries > 5e3) fastify.log.warn(`[arc] responseCache maxEntries=${maxEntries}: prefix invalidation scans all entries synchronously on every mutating request — expect event-loop pauses at this size. This cache is an instance-local micro-cache; for large shared caching use the queryCache with a Redis store instead.`);
97
98
  const cache = new LRUCache(maxEntries);
98
99
  const invalidateMethods = new Set(invalidateOn.map((m) => m.toUpperCase()));
100
+ declareRuntimeCapability(fastify, {
101
+ subsystem: "http.response-cache",
102
+ durability: "memory",
103
+ accepted: true,
104
+ detail: "per-replica micro-cache; correctness comes from short TTLs, not shared state"
105
+ });
99
106
  if (!fastify.hasRequestDecorator("__arcCacheTTL")) fastify.decorateRequest("__arcCacheTTL", void 0);
100
107
  /** Find TTL for a given URL path (seconds) */
101
108
  function getTTL(url) {
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
2
  import { createRequire } from "node:module";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/tracing.ts
@@ -59,7 +59,7 @@ try {
59
59
  function createTracerProvider(options) {
60
60
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
61
61
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
62
- const resolvedVersion = serviceVersion ?? "2.38.0";
62
+ const resolvedVersion = serviceVersion ?? "2.41.0";
63
63
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
64
64
  const provider = new NodeTracerProvider({ resource: { attributes: {
65
65
  "service.name": serviceName,
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/pluralize.ts
3
3
  /**
4
4
  * Lightweight English pluralization for the Arc CLI.
@@ -1,12 +1,12 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-Add2L50I.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
3
3
  import { arcLog, configureArcLogger, createPinoWriter, enterArcLoggerScope, runWithArcLogger } from "./logger/index.mjs";
4
- import { n as PUBLIC_SCOPE } from "./types-BqLyyH0o.mjs";
5
- import { t as auditRuntimeCapabilities } from "./runtimeCapabilities-D793Pxci.mjs";
6
- import { t as createTenantKeyGenerator } from "./rateLimitKey-CewdR-m6.mjs";
7
- import { i as createRequestIdGenerator, r as mergeHealthChecks } from "./health-Bh0N_FLk.mjs";
8
- import { t as parseJsonBody } from "./jsonBody-DVui0uJl.mjs";
9
- import { r as wrapWithBoundary } from "./subscribe-helpers-CBZOi6pk.mjs";
4
+ import { n as PUBLIC_SCOPE } from "./types-CDy3-zw1.mjs";
5
+ import { r as initRuntimeCapabilityRegistry, t as auditRuntimeCapabilities } from "./runtimeCapabilities-DSA_dH1_.mjs";
6
+ import { n as isScopeAwareKeyGenerator, t as createTenantKeyGenerator } from "./rateLimitKey-CK-EU2L8.mjs";
7
+ import { i as createRequestIdGenerator, r as mergeHealthChecks } from "./health-CxQzbJmr.mjs";
8
+ import { t as parseJsonBody } from "./jsonBody-Cq9KYl2Z.mjs";
9
+ import { r as wrapWithBoundary } from "./subscribe-helpers-AVYPgqTl.mjs";
10
10
  import { isProductionEnv } from "@classytic/primitives/environment";
11
11
  import Fastify from "fastify";
12
12
  import qs from "qs";
@@ -851,7 +851,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
851
851
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
852
852
  trackPlugin("arc-core");
853
853
  if (config.arcPlugins?.events !== false) {
854
- const { default: eventPlugin } = await import("./eventPlugin-DJwpGMzx.mjs").then((n) => n.n);
854
+ const { default: eventPlugin } = await import("./eventPlugin-CmXaPWLv.mjs").then((n) => n.n);
855
855
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
856
856
  await fastify.register(eventPlugin, {
857
857
  /**
@@ -906,19 +906,20 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
906
906
  trackPlugin("arc-health", { checkCount: healthOpts.checks?.length ?? 0 });
907
907
  }
908
908
  if (config.arcPlugins?.gracefulShutdown !== false) {
909
- await fastify.register(gracefulShutdownPlugin);
910
- trackPlugin("arc-graceful-shutdown");
909
+ const shutdownOpts = typeof config.arcPlugins?.gracefulShutdown === "object" ? config.arcPlugins.gracefulShutdown : {};
910
+ await fastify.register(gracefulShutdownPlugin, shutdownOpts);
911
+ trackPlugin("arc-graceful-shutdown", shutdownOpts);
911
912
  }
912
913
  if (config.arcPlugins?.caching) {
913
- const { default: cachingPlugin } = await import("./caching-a3ZEcJJs.mjs").then((n) => n.r);
914
+ const { default: cachingPlugin } = await import("./caching-DsWDjIxx.mjs").then((n) => n.r);
914
915
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
915
916
  await fastify.register(cachingPlugin, opts);
916
917
  trackPlugin("arc-caching", opts);
917
918
  }
918
919
  if (config.arcPlugins?.queryCache) {
919
- const { queryCachePlugin } = await import("./queryCachePlugin-oTsmVyEQ.mjs").then((n) => n.n);
920
+ const { queryCachePlugin } = await import("./queryCachePlugin-B5F09bYr.mjs").then((n) => n.n);
920
921
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
921
- const store = config.stores?.queryCache ?? new (await (import("./memory-BiPVaCPg.mjs").then((n) => n.n))).MemoryCacheStore();
922
+ const store = config.stores?.queryCache ?? new (await (import("./memory-CpDZ9NPw.mjs").then((n) => n.n))).MemoryCacheStore();
922
923
  await fastify.register(queryCachePlugin, {
923
924
  store,
924
925
  ...opts
@@ -928,7 +929,7 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
928
929
  if (config.arcPlugins?.sse) {
929
930
  if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
930
931
  else {
931
- const { default: ssePlugin } = await import("./sse-D4MYqkic.mjs").then((n) => n.r);
932
+ const { default: ssePlugin } = await import("./sse-CH7Slxc1.mjs").then((n) => n.r);
932
933
  const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
933
934
  await fastify.register(ssePlugin, opts);
934
935
  trackPlugin("arc-sse", opts);
@@ -937,20 +938,20 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
937
938
  if (config.arcPlugins?.realtime) {
938
939
  if (config.arcPlugins?.events === false) fastify.log.warn("Realtime plugin requires events plugin (arcPlugins.events). Realtime disabled.");
939
940
  else {
940
- const { default: realtimePlugin } = await import("./realtime-CPmjjgqd.mjs").then((n) => n.r);
941
+ const { default: realtimePlugin } = await import("./realtime-FnnD0n3Z.mjs").then((n) => n.r);
941
942
  const opts = config.arcPlugins.realtime === true ? {} : config.arcPlugins.realtime;
942
943
  await fastify.register(realtimePlugin, opts);
943
944
  trackPlugin("arc-realtime", opts);
944
945
  }
945
946
  }
946
947
  if (config.arcPlugins?.metrics) {
947
- const { default: metricsPlugin } = await import("./metrics-CsWaIVTa.mjs").then((n) => n.r);
948
+ const { default: metricsPlugin } = await import("./metrics-BaKnq8ag.mjs").then((n) => n.r);
948
949
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
949
950
  await fastify.register(metricsPlugin, opts);
950
951
  trackPlugin("arc-metrics", opts);
951
952
  }
952
953
  if (config.arcPlugins?.versioning) {
953
- const { default: versioningPlugin } = await import("./versioning-C2r0eOZ4.mjs").then((n) => n.r);
954
+ const { default: versioningPlugin } = await import("./versioning-DuVWcu-F.mjs").then((n) => n.r);
954
955
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
955
956
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
956
957
  }
@@ -1027,7 +1028,7 @@ async function registerAuth(fastify, config, trackPlugin) {
1027
1028
  */
1028
1029
  async function registerElevation(fastify, config, trackPlugin) {
1029
1030
  if (!config.elevation) return;
1030
- const { elevationPlugin } = await import("./elevation-C6woysGC.mjs").then((n) => n.r);
1031
+ const { elevationPlugin } = await import("./elevation-BF-vhGxY.mjs").then((n) => n.r);
1031
1032
  await fastify.register(elevationPlugin, config.elevation);
1032
1033
  trackPlugin("arc-elevation", config.elevation);
1033
1034
  fastify.log.debug("Elevation plugin enabled");
@@ -1042,7 +1043,7 @@ async function registerElevation(fastify, config, trackPlugin) {
1042
1043
  */
1043
1044
  async function registerErrorHandler(fastify, config, trackPlugin, moduleErrorMappers) {
1044
1045
  if (config.errorHandler === false) return;
1045
- const { errorHandlerPlugin } = await import("./errorHandler-BdmIWPba.mjs").then((n) => n.r);
1046
+ const { errorHandlerPlugin } = await import("./errorHandler-CIMkCOWm.mjs").then((n) => n.r);
1046
1047
  /**
1047
1048
  * DISCLOSURE DEFAULTS MERGE — they used to be REPLACED, and that was two bugs.
1048
1049
  *
@@ -1266,7 +1267,7 @@ async function registerResources(fastify, config, preResolvedModules) {
1266
1267
  let discoveryPath;
1267
1268
  let discoveryYieldedZero = false;
1268
1269
  if (resolvedResources === void 0 && config.resourceDir) {
1269
- const { loadResources } = await import("./loadResources-CNd1DWnK.mjs").then((n) => n.n);
1270
+ const { loadResources } = await import("./loadResources-CblF7xt2.mjs").then((n) => n.n);
1270
1271
  const { resolve, dirname } = await import("node:path");
1271
1272
  const { fileURLToPath } = await import("node:url");
1272
1273
  const rawDir = config.resourceDir;
@@ -1392,7 +1393,7 @@ Resources supplied by "${m.name}": ${provided}.`);
1392
1393
  if (fastify.hasDecorator("getScheduleStats")) throw new Error("[arc] modules declare scheduledJobs but schedulesPlugin was already registered manually. Configure their runner through arcPlugins.schedules so Arc can compose one schedule table.");
1393
1394
  const frozenJobs = Object.freeze(scheduledJobs.map((job) => Object.freeze({ ...job })));
1394
1395
  const scheduleOptions = typeof config.arcPlugins?.schedules === "object" ? config.arcPlugins.schedules : {};
1395
- const { default: schedulesPlugin } = await import("./schedules-DthM8Qag.mjs").then((n) => n.r);
1396
+ const { default: schedulesPlugin } = await import("./schedules-BktJb3Y3.mjs").then((n) => n.r);
1396
1397
  await fastify.register(schedulesPlugin, {
1397
1398
  ...scheduleOptions,
1398
1399
  schedules: frozenJobs
@@ -1566,6 +1567,7 @@ function buildPlanOpts(rest, plan) {
1566
1567
  */
1567
1568
  function buildRateLimitOpts(input) {
1568
1569
  const { skipPaths, allowList, plan, ...bare } = input;
1570
+ applyScopeAwareHook(bare);
1569
1571
  const rest = plan ? buildPlanOpts(bare, plan) : bare;
1570
1572
  if (!skipPaths || skipPaths.length === 0) return allowList === void 0 ? rest : {
1571
1573
  ...rest,
@@ -1584,6 +1586,27 @@ function buildRateLimitOpts(input) {
1584
1586
  allowList: combined
1585
1587
  };
1586
1588
  }
1589
+ /**
1590
+ * A scope-keyed generator must run AFTER authentication, so arc places the hook itself.
1591
+ *
1592
+ * `@fastify/rate-limit` defaults to `onRequest`; arc authenticates in a `preHandler`. A
1593
+ * generator from `createTenantKeyGenerator` therefore saw `PUBLIC_SCOPE` and returned the
1594
+ * caller's IP for EVERY request — and behind a proxy with `trustProxy` unset that is one
1595
+ * address, so a whole deployment shared a single bucket. Nothing threw: the limiter ran,
1596
+ * the host's key function ran, and the answer was silently wrong.
1597
+ *
1598
+ * Defaulted rather than merely warned, because the correct value is knowable here and the
1599
+ * failure is unobservable in production. An explicit `onRequest` is REFUSED rather than
1600
+ * honoured — it cannot do what the caller is asking for.
1601
+ */
1602
+ function applyScopeAwareHook(bare) {
1603
+ if (!isScopeAwareKeyGenerator(bare.keyGenerator)) return;
1604
+ if (bare.hook === void 0) {
1605
+ bare.hook = "preHandler";
1606
+ return;
1607
+ }
1608
+ if (bare.hook === "onRequest") throw new Error("[arc] rateLimit.hook: 'onRequest' cannot be combined with a scope-aware keyGenerator (createTenantKeyGenerator). Authentication runs in a preHandler, so the generator would see PUBLIC_SCOPE and key every caller by IP — behind a proxy that is one bucket for the whole deployment. Use hook: 'preHandler' (arc's default for this generator), or supply a keyGenerator that derives identity without the scope.");
1609
+ }
1587
1610
  function compilePathMatcher(patterns) {
1588
1611
  const prefixes = [];
1589
1612
  const exact = /* @__PURE__ */ new Set();
@@ -1626,8 +1649,9 @@ async function registerSecurityPlugins(fastify, config) {
1626
1649
  timeWindow: "1 minute"
1627
1650
  });
1628
1651
  await fastify.register(rateLimit, rateLimitOpts);
1629
- if (!(typeof rateLimitOpts === "object" && "store" in rateLimitOpts)) {
1630
- if (config.runtime === "distributed") throw new Error("[Arc] runtime: 'distributed' with rate limiting requires a shared store.\nProvide rateLimit: { store: new RedisStore({ ... }) } or disable rate limiting: rateLimit: false");
1652
+ const rateLimitRecord = typeof rateLimitOpts === "object" && rateLimitOpts !== null ? rateLimitOpts : void 0;
1653
+ if (!(rateLimitRecord !== void 0 && (rateLimitRecord.store != null || rateLimitRecord.redis != null))) {
1654
+ if (config.runtime === "distributed") throw new Error("[Arc] runtime: 'distributed' with rate limiting requires a shared store.\nProvide rateLimit: { redis: ioredisClient } (or { store: CustomStore }) or disable rate limiting: rateLimit: false");
1631
1655
  else if (config.preset === "production") fastify.log.warn("Rate limiting is using in-memory store. In multi-instance deployments, each instance tracks limits independently. Configure a Redis store for distributed rate limiting.");
1632
1656
  }
1633
1657
  fastify.log.debug("Rate limiting enabled");
@@ -1738,6 +1762,17 @@ var createApp_exports = /* @__PURE__ */ __exportAll({
1738
1762
  createApp: () => createApp,
1739
1763
  resolveLoggerConfig: () => resolveLoggerConfig
1740
1764
  });
1765
+ /**
1766
+ * The fail-loud switches, and what each one costs while OFF.
1767
+ *
1768
+ * Every one is a SPECIFIC instruction defeated by a GENERAL default, which is
1769
+ * why they are reported together rather than one at a time.
1770
+ */
1771
+ const STRICT_ENV_FLAGS = [
1772
+ ["ARC_STRICT_PERMISSIONS", "An ungated write route warns instead of failing boot."],
1773
+ ["ARC_STRICT_QUERY_PARAMS", "An unknown filter key is DROPPED, which WIDENS the read."],
1774
+ ["ARC_STRICT_IMMUTABLE_WRITES", "A PATCH of an immutable field returns 200, unchanged."]
1775
+ ];
1741
1776
  const MEMORY_STORE_NAMES = /* @__PURE__ */ new Set(["memory", "memory-cache"]);
1742
1777
  /**
1743
1778
  * Default redact paths layered into Fastify's pino logger when the host
@@ -1757,6 +1792,7 @@ const DEFAULT_LOGGER_REDACT_PATHS = [
1757
1792
  "req.headers.authorization",
1758
1793
  "req.headers[\"x-api-key\"]",
1759
1794
  "req.headers[\"x-internal-api-key\"]",
1795
+ "req.headers[\"x-device-token\"]",
1760
1796
  "req.headers.cookie",
1761
1797
  "req.headers[\"set-cookie\"]",
1762
1798
  "res.headers[\"set-cookie\"]",
@@ -1828,7 +1864,10 @@ function validateDistributedRuntime(options) {
1828
1864
  });
1829
1865
  }
1830
1866
  const schedules = options.arcPlugins?.schedules;
1831
- if (schedules && typeof schedules === "object" && schedules.enabled !== false && !schedules.lock) deferredWarnings.push("runtime: 'distributed' — schedules configured without a `lock` adapter. EVERY replica will fire every schedule tick. Pass an ecosystem lock (e.g. createMongoLockAdapter) under arcPlugins.schedules.lock for single-firer leases.");
1867
+ if (schedules && typeof schedules === "object" && schedules.enabled !== false && !schedules.lock && !schedules.singleReplica) missing.push({
1868
+ key: "arcPlugins.schedules.lock",
1869
+ hint: "a LockAdapter for single-firer leases (e.g. createMongoLockAdapter) — or `arcPlugins.schedules.singleReplica: true` if exactly one replica arms schedules"
1870
+ });
1832
1871
  if (missing.length > 0) {
1833
1872
  const lines = missing.map((m) => ` • ${m.key.padEnd(20)} → ${m.hint}`).join("\n");
1834
1873
  throw new Error(`[Arc] runtime: 'distributed' requires durable (Redis/MongoDB) adapters, not memory.
@@ -1953,6 +1992,7 @@ async function buildApp(options, loggerScope, onInstance) {
1953
1992
  } }
1954
1993
  });
1955
1994
  onInstance(fastify);
1995
+ initRuntimeCapabilityRegistry(fastify);
1956
1996
  if (resolvedLogger !== false) {
1957
1997
  const writer = createPinoWriter(fastify.log);
1958
1998
  loggerScope.writer = writer;
@@ -1967,6 +2007,22 @@ async function buildApp(options, loggerScope, onInstance) {
1967
2007
  for (const warning of deferredWarnings) fastify.log.warn(warning);
1968
2008
  if (numericTrustProxy) fastify.log.warn(`[arc] trustProxy: ${String(config.trustProxy)} (a hop COUNT) trusts nothing on Fastify >= 5.12.1 — hop-count trust cannot validate the immediate peer, so it fails closed upstream to stop clients spoofing X-Forwarded-* with padded hops. \`request.ip\` is therefore the PROXY's address: rate-limit keys collapse onto one bucket and audit rows record the wrong client. Name the proxies instead — trustProxy: 'loopback', '10.0.0.0/8', a comma-separated list, or \`true\` if every upstream hop is genuinely trusted.`);
1969
2009
  if (inheritedTrustProxyDefault) fastify.log.warn("[arc] preset: 'production' now defaults to trustProxy: false (fail-closed; was true before 2.24). If this app runs behind a load balancer or reverse proxy, request.ip is currently the PROXY's address — rate-limit keys collapse onto one bucket and audit logs record the wrong client. Set trustProxy explicitly: a hop count (trustProxy: 1), a CIDR allow-list ('10.0.0.0/8'), a resolver function, or false if clients truly connect directly.");
2010
+ /**
2011
+ * `preset: 'production'` REPORTS the fail-loud switches it cannot set.
2012
+ *
2013
+ * All three are read where the object is CONSTRUCTED — `defineResource` at
2014
+ * define time, `QueryParser` and `BodySanitizer` in their constructors — and a
2015
+ * host imports its resources before it calls `createApp`. So setting
2016
+ * `process.env` here would be too late for everything already built: wired to
2017
+ * read, doing nothing. Reporting cannot fail that way.
2018
+ *
2019
+ * Set them in the process entrypoint, BEFORE the first resource import, with
2020
+ * `??=` so an operator's explicit `false` still wins.
2021
+ */
2022
+ if (config.preset === "production") {
2023
+ const off = STRICT_ENV_FLAGS.filter(([name]) => process.env[name] !== "true");
2024
+ if (off.length > 0) fastify.log.warn(`[arc] preset: 'production' with fail-loud OFF: ${off.map(([n]) => n).join(", ")}. ${off.map(([, what]) => what).join(" ")} Set them in the entrypoint BEFORE any resource import — they are read when resources, parsers and sanitizers are CONSTRUCTED, so setting them later is silently too late. Per-resource options still override.`);
2025
+ }
1970
2026
  fastify.removeContentTypeParser("application/json");
1971
2027
  fastify.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
1972
2028
  try {
@@ -1999,7 +2055,7 @@ async function buildApp(options, loggerScope, onInstance) {
1999
2055
  await registerErrorHandler(fastify, config, trackPlugin, moduleErrorMappers);
2000
2056
  await registerResources(fastify, config, resolvedModules);
2001
2057
  if (config.replyHelpers) {
2002
- const { replyHelpersPlugin } = await import("./replyHelpers-BHebarf0.mjs").then((n) => n.n);
2058
+ const { replyHelpersPlugin } = await import("./replyHelpers-DPHsdw-5.mjs").then((n) => n.n);
2003
2059
  await fastify.register(replyHelpersPlugin);
2004
2060
  }
2005
2061
  if (config.serializeBigInt) {
@@ -1,15 +1,15 @@
1
1
 
2
- import { It as Authenticator } from "./index-B78WZom8.mjs";
2
+ import { It as Authenticator } from "./index-DHbyPo5j.mjs";
3
3
  import { r as CacheStore } from "./interface-BOk3YnTz.mjs";
4
4
  import { n as ElevationOptions } from "./elevation-BXCfVcNG.mjs";
5
- import { a as EventTransport } from "./EventTransport-Cx60_s-6.mjs";
5
+ import { a as EventTransport } from "./EventTransport-CbUKDzZZ.mjs";
6
6
  import { t as ExternalOpenApiPaths } from "./externalPaths-CmGOxOtD.mjs";
7
7
  import { r as QueryCachePluginOptions } from "./queryCachePlugin-BzqxconG.mjs";
8
- import { t as ErrorHandlerOptions } from "./errorHandler-DI5AN2b5.mjs";
9
- import { l as ResourceLike, m as SchedulesPluginOptions, n as ArcModuleInput, v as HealthOptions } from "./types-BRU-Kd3t.mjs";
10
- import "./index-D2uIUSQk.mjs";
11
- import { t as EventPluginOptions } from "./eventPlugin-DFvHa4Nf.mjs";
12
- import { g as CachingOptions, o as MetricsOptions, p as SSEOptions, t as VersioningOptions, u as RealtimeOptions } from "./versioning-B843jOYX.mjs";
8
+ import { t as ErrorHandlerOptions } from "./errorHandler--4rSMMvq.mjs";
9
+ import { l as ResourceLike, m as SchedulesPluginOptions, n as ArcModuleInput, v as HealthOptions } from "./types-DfVDvkdP.mjs";
10
+ import "./index-CFfLKMH4.mjs";
11
+ import { t as EventPluginOptions } from "./eventPlugin-B52abJ8W.mjs";
12
+ import { b as GracefulShutdownOptions, g as CachingOptions, o as MetricsOptions, p as SSEOptions, t as VersioningOptions, u as RealtimeOptions } from "./versioning-D6JIK8Tk.mjs";
13
13
  import { r as IdempotencyStore } from "./interface-DEWNOMza.mjs";
14
14
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
15
15
  //#region src/factory/types/assets.d.ts
@@ -652,8 +652,13 @@ interface CreateAppOptions {
652
652
  * ```
653
653
  */
654
654
  health?: boolean | HealthOptions;
655
- /** Graceful shutdown handling (default: true) */
656
- gracefulShutdown?: boolean;
655
+ /**
656
+ * Graceful shutdown handling (default: true). Pass `GracefulShutdownOptions`
657
+ * to configure it inline — in particular `drainDelayMs`, the lame-duck
658
+ * window behind a load balancer (readiness fails first, the server keeps
659
+ * serving until the LB has deregistered the instance, then closes).
660
+ */
661
+ gracefulShutdown?: boolean | GracefulShutdownOptions;
657
662
  /** Emit events for CRUD operations (default: true) */
658
663
  emitEvents?: boolean;
659
664
  /**
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { i as RequestScope } from "../types-Ddl9OjmE.mjs";
3
- import { l as PermissionCheck } from "../base-C-NL2fTi.mjs";
4
- import { Y as PresetResult } from "../index-B78WZom8.mjs";
3
+ import { l as PermissionCheck } from "../base-CxrV1LmG.mjs";
4
+ import { Y as PresetResult } from "../index-DHbyPo5j.mjs";
5
5
  import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-Cx9Sz3Ou.mjs";
6
6
  //#region src/presets/filesUpload.d.ts
7
7
  interface FilesUploadPresetRoutes {
@@ -1,8 +1,8 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { i as NotFoundError, u as ValidationError } from "../errors-2ExmOgIJ.mjs";
3
- import { c as getOrgId, g as getUserId } from "../types-BqLyyH0o.mjs";
4
- import { B as requireAuth, F as allowPublic } from "../permissions-H7Zcb82Z.mjs";
5
- import { t as multipartBody } from "../multipartBody-u6VrS_iH.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import { i as NotFoundError, u as ValidationError } from "../errors-C_YhELhe.mjs";
3
+ import { c as getOrgId, g as getUserId } from "../types-CDy3-zw1.mjs";
4
+ import { B as requireAuth, F as allowPublic } from "../permissions-DiHXy8Ck.mjs";
5
+ import { t as multipartBody } from "../multipartBody-C92aJ8am.mjs";
6
6
  //#region src/presets/filesUpload.ts
7
7
  const DEFAULT_FIELD_NAME = "file";
8
8
  const DEFAULT_MAX_FILE_SIZE = 10485760;
@@ -1,6 +1,6 @@
1
1
 
2
- import { t as AnyRecord } from "../base-C-NL2fTi.mjs";
3
- import { Fn as IControllerResponse, In as IRequestContext, U as ResourceConfig, Y as PresetResult, d as PaginationResult } from "../index-B78WZom8.mjs";
2
+ import { t as AnyRecord } from "../base-CxrV1LmG.mjs";
3
+ import { Fn as IControllerResponse, In as IRequestContext, U as ResourceConfig, Y as PresetResult, d as PaginationResult } from "../index-DHbyPo5j.mjs";
4
4
  import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
5
5
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
6
6
  import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
2
  import { multiTenantPreset } from "./multiTenant.mjs";
3
- import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-CNyfaxGL.mjs";
3
+ import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-BdlugYTc.mjs";
4
4
  import { filesUploadPreset } from "./filesUpload.mjs";
5
5
  import { searchPreset } from "./search.mjs";
6
6
  export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,5 +1,5 @@
1
1
 
2
- import { X as CrudRouteKey, Y as PresetResult } from "../index-B78WZom8.mjs";
2
+ import { X as CrudRouteKey, Y as PresetResult } from "../index-DHbyPo5j.mjs";
3
3
  //#region src/presets/multiTenant.d.ts
4
4
  /**
5
5
  * One tenant dimension for multi-field filtering. Discriminated by source:
@@ -65,6 +65,43 @@ interface MultiTenantOptions {
65
65
  * multiTenantPreset({ allowPublic: ['list', 'get'] })
66
66
  */
67
67
  allowPublic?: CrudRouteKey[];
68
+ /**
69
+ * Reads that are NOT scoped to a tenant at all — a marketplace listing.
70
+ *
71
+ * `allowPublic` answers "may an anonymous caller reach this route?". That is a different
72
+ * question from "is this listing scoped to a tenant?", and until this option there was no way
73
+ * to answer the second one with "no". An `allowPublic` route still filters by the caller's
74
+ * organization whenever they have one, and a signed-in user of a multi-org product ALWAYS has
75
+ * one, because choosing an organization writes it onto the session. So a cross-tenant catalog
76
+ * silently became single-tenant the moment one of its own sellers browsed it: the same URL,
77
+ * with no header and no query parameter to explain the difference, returned a different world
78
+ * depending on who was asking.
79
+ *
80
+ * The only escape was to drop this preset from the resource entirely, which also drops tenant
81
+ * INJECTION on create/update — the thing that stops a member moving a document into another
82
+ * organization. Trading a write guarantee for a read behaviour is not a real choice.
83
+ *
84
+ * Listed routes get no tenant middleware: no filter, and nothing stashed for the repo layer.
85
+ * Everything else keeps its filter, so writes are unaffected.
86
+ *
87
+ * READS ONLY, by type. A cross-tenant `create` or `update` is not a listing, it is an escape
88
+ * from tenancy, and it should never be one option away by accident.
89
+ *
90
+ * THIS DOES NOT AUTHORIZE ANYTHING. Removing the tenant filter widens what the query can
91
+ * return, so the resource owes a row-level `policy` from its permission check saying what a
92
+ * stranger may see (`{ status: 'active' }`, `{ visibility: 'public' }`). Declaring this without
93
+ * one publishes every row in every tenant.
94
+ *
95
+ * @default []
96
+ * @example
97
+ * // A public catalog: anyone may browse every seller's LIVE products,
98
+ * // while create/update/delete stay pinned to the caller's own shop.
99
+ * multiTenantPreset({
100
+ * allowPublic: ['list', 'get'],
101
+ * crossTenant: ['list', 'get'],
102
+ * })
103
+ */
104
+ crossTenant?: readonly Extract<CrudRouteKey, "list" | "get">[];
68
105
  }
69
106
  declare function multiTenantPreset(options?: MultiTenantOptions): PresetResult;
70
107
  //#endregion
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import "../constants-BONghE1Y.mjs";
3
- import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, v as hasOrgAccess } from "../types-BqLyyH0o.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import "../constants-BxLgNFF7.mjs";
3
+ import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, v as hasOrgAccess } from "../types-CDy3-zw1.mjs";
4
4
  //#region src/presets/multiTenant.ts
5
5
  /**
6
6
  * Resolve a single TenantFieldSpec against the current scope.
@@ -162,7 +162,7 @@ function createTenantInjection(specs) {
162
162
  };
163
163
  }
164
164
  function multiTenantPreset(options = {}) {
165
- const { tenantField, tenantFields, allowPublic = [] } = options;
165
+ const { tenantField, tenantFields, allowPublic = [], crossTenant = [] } = options;
166
166
  if (tenantField !== void 0 && tenantFields !== void 0) throw new Error("multiTenantPreset: pass either `tenantField` (single-field) or `tenantFields` (multi-field), not both");
167
167
  const specs = tenantFields ?? [{
168
168
  field: tenantField ?? "organizationId",
@@ -173,6 +173,24 @@ function multiTenantPreset(options = {}) {
173
173
  const flexibleTenantFilter = createFlexibleTenantFilter(specs);
174
174
  const tenantInjection = createTenantInjection(specs);
175
175
  const getFilter = (route) => allowPublic.includes(route) ? flexibleTenantFilter : strictTenantFilter;
176
+ /**
177
+ * A cross-tenant read MARKS the request rather than simply skipping the filter.
178
+ *
179
+ * Skipping is not enough, because this middleware is only the first of four places that scope a
180
+ * read, and the other three each re-derive the organization from the request scope on their own:
181
+ * `QueryResolver` adds the tenant filter for `list`, `buildTenantRepoOptions` forwards it to the
182
+ * repository as tenant context, and `checkOrgScope` re-checks it post-fetch on `get`. Removing
183
+ * one of four changes nothing observable, which is exactly what made this look unfixable from the
184
+ * preset: the listing stayed single-tenant no matter what the preset decided.
185
+ *
186
+ * So the decision travels on the request instead, and each of those layers honours it. The
187
+ * repository layer needs no change of its own — kits already accept `bypassTenant`, which is the
188
+ * same door elevated cross-tenant reads have always used.
189
+ */
190
+ const markCrossTenantRead = async (request) => {
191
+ request._crossTenantRead = true;
192
+ };
193
+ const readMiddleware = (route) => crossTenant.includes(route) ? [markCrossTenantRead] : [getFilter(route)];
176
194
  const fieldRules = {};
177
195
  for (const spec of specs) fieldRules[spec.field] = {
178
196
  systemManaged: true,
@@ -182,8 +200,8 @@ function multiTenantPreset(options = {}) {
182
200
  name: "multiTenant",
183
201
  schemaOptions: { fieldRules },
184
202
  middlewares: {
185
- list: [getFilter("list")],
186
- get: [getFilter("get")],
203
+ list: readMiddleware("list"),
204
+ get: readMiddleware("get"),
187
205
  create: [tenantInjection],
188
206
  update: [getFilter("update"), tenantInjection],
189
207
  delete: [getFilter("delete")],
@@ -1,6 +1,6 @@
1
1
 
2
- import { l as PermissionCheck } from "../base-C-NL2fTi.mjs";
3
- import { At as RouteDefinition, Y as PresetResult, jn as ControllerHandler, jt as RouteMcpConfig } from "../index-B78WZom8.mjs";
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";
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.38.0 | MIT | https://classytic.com */
2
- import { B as requireAuth, F as allowPublic } from "../permissions-H7Zcb82Z.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import { B as requireAuth, F as allowPublic } from "../permissions-DiHXy8Ck.mjs";
3
3
  //#region src/presets/search.ts
4
4
  const BUILTINS = [
5
5
  {
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { l as UnauthorizedError } from "./errors-2ExmOgIJ.mjs";
3
- import { b as isElevated, n as PUBLIC_SCOPE } from "./types-BqLyyH0o.mjs";
4
- import { B as requireAuth, F as allowPublic, U as requireRoles } from "./permissions-H7Zcb82Z.mjs";
1
+ /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
+ import { l as UnauthorizedError } from "./errors-C_YhELhe.mjs";
3
+ import { b as isElevated, n as PUBLIC_SCOPE } from "./types-CDy3-zw1.mjs";
4
+ import { B as requireAuth, F as allowPublic, U as requireRoles } from "./permissions-DiHXy8Ck.mjs";
5
5
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
6
6
  //#region src/presets/ownedByUser.ts
7
7
  /**