@classytic/arc 2.34.0 → 2.36.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 (182) hide show
  1. package/dist/{BaseController-CbkuhNI2.mjs → BaseController-CTJKm6Cb.mjs} +95 -14
  2. package/dist/{EventTransport-tq4CEB-q.mjs → EventTransport-D23Uvhg4.mjs} +1 -1
  3. package/dist/{HookSystem-DqXkMXkB.mjs → HookSystem-DgwxNZTx.mjs} +6 -4
  4. package/dist/{ResourceRegistry-DoALUlOO.mjs → ResourceRegistry-DNuYVbmv.mjs} +2 -2
  5. package/dist/{pipeline-q69DZzgG.mjs → actionPermissions-D97DniUQ.mjs} +30 -7
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +2 -2
  8. package/dist/auth/audit.mjs +1 -1
  9. package/dist/auth/index.d.mts +2 -2
  10. package/dist/auth/index.mjs +6 -6
  11. package/dist/auth/redis-session.mjs +1 -1
  12. package/dist/{authorizationDecision-CMMLWmuZ.mjs → authorizationDecision-EsiY3nUu.mjs} +2 -2
  13. package/dist/{base-Csjc2oF-.d.mts → base-C-NL2fTi.d.mts} +16 -1
  14. package/dist/{buildHandler-BrdYrG_1.mjs → buildHandler-Dohd2JuY.mjs} +4 -4
  15. package/dist/cache/index.mjs +4 -4
  16. package/dist/{caching-BcGmvnHh.mjs → caching-CP3GLJ7U.mjs} +2 -2
  17. package/dist/cleanup/index.d.mts +2 -2
  18. package/dist/cleanup/index.mjs +4 -4
  19. package/dist/cli/commands/describe.d.mts +1 -1
  20. package/dist/cli/commands/describe.mjs +3 -3
  21. package/dist/cli/commands/docs.mjs +3 -3
  22. package/dist/cli/commands/doctor.mjs +1 -1
  23. package/dist/cli/commands/generate.mjs +2 -2
  24. package/dist/cli/commands/init.mjs +2 -2
  25. package/dist/cli/commands/introspect.mjs +2 -2
  26. package/dist/cli/index.mjs +3 -3
  27. package/dist/{constants-BxCoxmiN.mjs → constants-DUoKAGYD.mjs} +1 -1
  28. package/dist/context/index.d.mts +16 -1
  29. package/dist/context/index.mjs +64 -4
  30. package/dist/core/index.d.mts +2 -2
  31. package/dist/core/index.mjs +7 -8
  32. package/dist/{core-BYKa8wHu.mjs → core-DTXsiNTJ.mjs} +3 -3
  33. package/dist/{createActionRouter-D6T0jWhS.mjs → createActionRouter-CZz6JO93.mjs} +27 -64
  34. package/dist/{createAggregationRouter-QLV2PrWP.mjs → createAggregationRouter-DKrGv_EC.mjs} +7 -7
  35. package/dist/defineAggregation-CpQfcM8R.mjs +25 -0
  36. package/dist/{defineResource-ZhuLonLm.mjs → defineResource-DWzjyxb2.mjs} +62 -66
  37. package/dist/discovery/index.mjs +1 -1
  38. package/dist/docs/index.d.mts +1 -1
  39. package/dist/docs/index.mjs +3 -3
  40. package/dist/{elevation-B0tlY8y1.mjs → elevation-D_cmo80j.mjs} +4 -4
  41. package/dist/encryption/index.mjs +3 -3
  42. package/dist/{errorHandler-C7xBqHsI.mjs → errorHandler-2RT0hLSI.mjs} +3 -3
  43. package/dist/{errors-Cd_yy7aW.mjs → errors-DDKni6Tz.mjs} +1 -1
  44. package/dist/{eventPlugin-DSNEZWfV.d.mts → eventPlugin-B47sfCJ8.d.mts} +1 -1
  45. package/dist/{eventPlugin-Dar8qRaj.mjs → eventPlugin-c3YwGzz9.mjs} +5 -5
  46. package/dist/events/index.d.mts +2 -2
  47. package/dist/events/index.mjs +5 -5
  48. package/dist/events/transports/redis-stream-entry.mjs +1 -1
  49. package/dist/events/transports/redis.mjs +1 -1
  50. package/dist/factory/index.d.mts +5 -5
  51. package/dist/factory/index.mjs +5 -6
  52. package/dist/{fastify-augmentation-DVsTAy6X.d.mts → fastify-augmentation-Bk_9PoiV.d.mts} +3 -3
  53. package/dist/{fields-CojeUkS4.mjs → fields-DdP225wt.mjs} +1 -1
  54. package/dist/{generate-DiB0nHPR.mjs → generate-B1bKdcV9.mjs} +2 -2
  55. package/dist/{headers-CveNO9gR.mjs → headers-DKslnhey.mjs} +2 -2
  56. package/dist/{health-CasG2A28.mjs → health-k6NODS8f.mjs} +1 -1
  57. package/dist/hooks/index.d.mts +1 -1
  58. package/dist/hooks/index.mjs +2 -2
  59. package/dist/idempotency/index.mjs +22 -56
  60. package/dist/idempotency/redis.mjs +1 -1
  61. package/dist/{index-B-DvUzsr.d.mts → index-BtYoMip1.d.mts} +21 -3
  62. package/dist/{index-BcR2bFZQ.d.mts → index-C01wWGJt.d.mts} +33 -105
  63. package/dist/{index-C5NnMEKF.d.mts → index-C50BUxKs.d.mts} +1 -1
  64. package/dist/{index-CFAJEWCX.d.mts → index-RdpLP71b.d.mts} +7 -30
  65. package/dist/{index-ugr1tQlW.d.mts → index-eyg5WEcy.d.mts} +1 -1
  66. package/dist/index.d.mts +5 -5
  67. package/dist/index.mjs +11 -12
  68. package/dist/{init-mdJ-v4Xp.mjs → init-LuGxIbQ_.mjs} +1 -1
  69. package/dist/integrations/event-gateway.mjs +2 -2
  70. package/dist/integrations/index.d.mts +2 -2
  71. package/dist/integrations/index.mjs +1 -1
  72. package/dist/integrations/jobs/index.d.mts +1 -1
  73. package/dist/integrations/jobs/index.mjs +2 -2
  74. package/dist/integrations/mcp/index.d.mts +4 -4
  75. package/dist/integrations/mcp/index.mjs +2 -2
  76. package/dist/integrations/mcp/testing.d.mts +1 -1
  77. package/dist/integrations/mcp/testing.mjs +3 -3
  78. package/dist/integrations/streamline.mjs +3 -3
  79. package/dist/integrations/webhooks.d.mts +4 -2
  80. package/dist/integrations/webhooks.mjs +28 -27
  81. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  82. package/dist/integrations/websocket-redis.mjs +1 -1
  83. package/dist/integrations/websocket.mjs +1 -1
  84. package/dist/{interface-DznJrm9R.mjs → interface-B7awDx3b.mjs} +1 -1
  85. package/dist/{jsonBody-CfGH-GxO.mjs → jsonBody-CiHih3ZU.mjs} +2 -2
  86. package/dist/{keys-WeKvY1dE.mjs → keys-BcNl12HN.mjs} +1 -1
  87. package/dist/{loadResources-Bj-aSsXi.mjs → loadResources-DGlfRQc9.mjs} +2 -2
  88. package/dist/{loadResourcesFromEntry-DwaxNcE2.mjs → loadResourcesFromEntry-VYPGSas8.mjs} +2 -2
  89. package/dist/logger/index.d.mts +23 -4
  90. package/dist/logger/index.mjs +50 -7
  91. package/dist/{mcpPlugin-DX3PHD7K.mjs → mcpPlugin-NxSlc-Ks.mjs} +15 -16
  92. package/dist/{memory-qYSB7rMQ.mjs → memory-BU-C-MV3.mjs} +2 -2
  93. package/dist/{metrics-D7uchLjN.mjs → metrics-7_1GbJpl.mjs} +2 -2
  94. package/dist/middleware/index.d.mts +1 -1
  95. package/dist/middleware/index.mjs +3 -3
  96. package/dist/migrations/index.mjs +7 -4
  97. package/dist/{module-CwKsgZXp.mjs → module-CQNHzE7g.mjs} +1 -1
  98. package/dist/{multipartBody-4w-S3-PU.mjs → multipartBody-Bi5yqW0M.mjs} +1 -1
  99. package/dist/{openapi-CkV5mQ53.mjs → openapi-RpNbPBL-.mjs} +6 -6
  100. package/dist/outbox-admin/index.d.mts +2 -2
  101. package/dist/outbox-admin/index.mjs +4 -4
  102. package/dist/{outbox-module-D1QfHqO4.mjs → outbox-module-ClcFrq98.mjs} +6 -4
  103. package/dist/{outbox-module-C1Bs_FXF.d.mts → outbox-module-b1TyDGGi.d.mts} +24 -1
  104. package/dist/permissions/index.d.mts +2 -2
  105. package/dist/permissions/index.mjs +5 -5
  106. package/dist/{permissions-CP47jdi7.mjs → permissions-DgPrZsJh.mjs} +36 -9
  107. package/dist/{pipe-BzzoEhHj.mjs → pipe-Cjy1UiXX.mjs} +2 -2
  108. package/dist/pipeline/index.d.mts +1 -1
  109. package/dist/pipeline/index.mjs +2 -2
  110. package/dist/plugins/index.d.mts +92 -7
  111. package/dist/plugins/index.mjs +168 -17
  112. package/dist/plugins/response-cache.mjs +2 -2
  113. package/dist/plugins/tracing-entry.mjs +2 -2
  114. package/dist/{pluralize-B7xf8qZF.mjs → pluralize-B7m0dPm8.mjs} +1 -1
  115. package/dist/{preloadResources-g07ldtzh.d.mts → preloadResources-BuutdWgr.d.mts} +54 -270
  116. package/dist/{createApp-zR-DEesr.mjs → preloadResources-CjnW4iIX.mjs} +170 -34
  117. package/dist/presets/filesUpload.d.mts +3 -3
  118. package/dist/presets/filesUpload.mjs +5 -5
  119. package/dist/presets/index.d.mts +2 -2
  120. package/dist/presets/index.mjs +2 -2
  121. package/dist/presets/multiTenant.d.mts +1 -1
  122. package/dist/presets/multiTenant.mjs +3 -3
  123. package/dist/presets/search.d.mts +9 -47
  124. package/dist/presets/search.mjs +2 -2
  125. package/dist/{presets-BAPijK_A.mjs → presets-CsGg8B58.mjs} +4 -4
  126. package/dist/{projection-BKM0Ui-s.mjs → projection-koISNyQo.mjs} +2 -2
  127. package/dist/{queryCachePlugin-DhsZ0ZIc.mjs → queryCachePlugin-DHueQT9O.mjs} +5 -5
  128. package/dist/{rateLimitKey-XngqVNEV.mjs → rateLimitKey-Bma8VXDO.mjs} +1 -1
  129. package/dist/{realtime-G1TC6Yzc.mjs → realtime-BBoC91Bz.mjs} +9 -9
  130. package/dist/{rateLimit-D4-ASwsI.mjs → registerRoute-BTgORTlR.mjs} +114 -3
  131. package/dist/registry/index.d.mts +2 -2
  132. package/dist/registry/index.mjs +3 -3
  133. package/dist/{registry-CLxCR5LF.mjs → registry-BzvRCYS6.mjs} +15 -56
  134. package/dist/{renewingLease-BnWtbA2U.mjs → renewingLease-CgIGh0Ab.mjs} +14 -2
  135. package/dist/{replyHelpers-BSBVCwL3.mjs → replyHelpers-BN16KPHr.mjs} +2 -2
  136. package/dist/{requestContext-uYE35JxI.mjs → requestContext-B93dmpf0.mjs} +1 -1
  137. package/dist/{rolldown-runtime-DNg-KNCk.mjs → rolldown-runtime-JJWfp8rp.mjs} +1 -1
  138. package/dist/{runtimeCapabilities-Dm88DaTW.mjs → runtimeCapabilities-jK99Ciw_.mjs} +1 -1
  139. package/dist/{schedules-CAcZgia0.mjs → schedules-CKKEL21P.mjs} +6 -4
  140. package/dist/{schemaConverter-XH1RZRyu.mjs → schemaConverter-gg0_aUPF.mjs} +19 -32
  141. package/dist/{schemaIR-CDvclAew.mjs → schemaIR-CngYvpCF.mjs} +2 -2
  142. package/dist/schemas/index.mjs +1 -1
  143. package/dist/scim/index.mjs +2 -2
  144. package/dist/scope/index.d.mts +1 -1
  145. package/dist/scope/index.mjs +5 -5
  146. package/dist/{scope-BaWcqBvr.mjs → scope-B1RsHdjF.mjs} +4 -4
  147. package/dist/simpleEqualityMatcher-_9NAMHUD.mjs +43 -0
  148. package/dist/{sse-Cf0jUHsh.mjs → sse-Bxt03Hk5.mjs} +5 -5
  149. package/dist/{sseStream-Bvpi-UXQ.mjs → sseStream-BMHTLlhR.mjs} +2 -2
  150. package/dist/{store-helpers-B88p99Z2.mjs → store-helpers-Brt8OxWZ.mjs} +1 -1
  151. package/dist/{streaming-DsT1B5zJ.mjs → streaming-CiTJW8kZ.mjs} +1 -1
  152. package/dist/{subscribe-helpers-CLIOHsOK.mjs → subscribe-helpers-B4fNHRNc.mjs} +21 -49
  153. package/dist/sync/changelog.mjs +1 -1
  154. package/dist/testing/cleanupStoreContract.mjs +1 -1
  155. package/dist/testing/index.d.mts +6 -6
  156. package/dist/testing/index.mjs +10 -11
  157. package/dist/testing/outboxStoreContract.mjs +1 -1
  158. package/dist/testing/storageContract.mjs +1 -1
  159. package/dist/{transactionContext-BYtWnwAS.mjs → transactionContext-D2HwJM2z.mjs} +1 -1
  160. package/dist/{typeGuards-jjKJDN13.mjs → typeGuards-IE1laMDf.mjs} +1 -1
  161. package/dist/types/index.d.mts +3 -3
  162. package/dist/types/index.mjs +1 -1
  163. package/dist/types/storage.mjs +1 -1
  164. package/dist/{types-CoqIRrkY.mjs → types-B8lwJv9Q.mjs} +10 -53
  165. package/dist/{types-BfC83KsP.d.mts → types-BXnTOsXP.d.mts} +1 -1
  166. package/dist/{types-BcTumAz_.d.mts → types-DDD5RKTu.d.mts} +30 -87
  167. package/dist/{types-ChuShpCP.d.mts → types-Ddl9OjmE.d.mts} +18 -121
  168. package/dist/usage/index.d.mts +2 -2
  169. package/dist/usage/index.mjs +0 -0
  170. package/dist/{userHelpers-CWF5pbZD.mjs → userHelpers-xQAi5bPF.mjs} +1 -1
  171. package/dist/utils/index.d.mts +16 -42
  172. package/dist/utils/index.mjs +11 -11
  173. package/dist/{utils-h1V_sT-t.mjs → utils-DgNFsJ2c.mjs} +3 -3
  174. package/dist/{validate-CdQlz52F.mjs → validate-DI3L5V9w.mjs} +2 -2
  175. package/dist/{versioning-rZbyLVD-.mjs → versioning-COpAyfpZ.mjs} +3 -3
  176. package/package.json +8 -8
  177. package/dist/actionPermissions-CUeCmpUd.mjs +0 -25
  178. package/dist/defineAggregation-Nv-DwfOf.mjs +0 -48
  179. package/dist/preloadResources-BkyqeTDz.mjs +0 -46
  180. package/dist/registerRoute-Ba8v0PCa.mjs +0 -113
  181. package/dist/repositoryFilter-BANpz6BX.mjs +0 -78
  182. package/dist/simpleEqualityMatcher-Se8VrsaP.mjs +0 -69
@@ -1,5 +1,8 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { n as requestContext } from "./requestContext-uYE35JxI.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { n as evaluateAndApplyPermission } from "./authorizationDecision-EsiY3nUu.mjs";
3
+ import { f as createError } from "./errors-DDKni6Tz.mjs";
4
+ import { u as getRequestScope } from "./types-B8lwJv9Q.mjs";
5
+ import { n as requestContext } from "./requestContext-B93dmpf0.mjs";
3
6
  //#region src/core/middlewares/arcDecorator.ts
4
7
  /**
5
8
  * Build the `arcDecorator` preHandler for a resource.
@@ -178,6 +181,79 @@ function describeValue(v) {
178
181
  return `${typeof v} (${JSON.stringify(v).slice(0, 80)})`;
179
182
  }
180
183
  //#endregion
184
+ //#region src/core/middlewares/permissions.ts
185
+ /**
186
+ * Build a PermissionContext from a Fastify request. Extracted so the CRUD
187
+ * permission middleware and the dynamic action-permission check use the same
188
+ * field layout — divergence here silently broke policy filters for actions.
189
+ */
190
+ function buildPermissionContext(req, opts) {
191
+ const reqWithExtras = req;
192
+ const params = req.params;
193
+ return {
194
+ user: reqWithExtras.user ?? null,
195
+ scope: getRequestScope(req),
196
+ request: req,
197
+ resource: opts.resource,
198
+ action: opts.action,
199
+ resourceId: opts.resourceId ?? params?.id,
200
+ params,
201
+ data: opts.data ?? req.body
202
+ };
203
+ }
204
+ /**
205
+ * Static per-route CRUD permission gate. The permission and action are known
206
+ * at route-registration time, so the gate is a plain preHandler.
207
+ *
208
+ * Actions use the dynamic counterpart `buildActionPermissionMw` — their
209
+ * permission is resolved from `body.action` at request time.
210
+ */
211
+ function buildCrudPermissionMw(permissionCheck, resourceName, action) {
212
+ if (!permissionCheck) return null;
213
+ return async (req, reply) => {
214
+ const context = buildPermissionContext(req, {
215
+ resource: resourceName,
216
+ action
217
+ });
218
+ await evaluateAndApplyPermission(permissionCheck, context, req, reply);
219
+ };
220
+ }
221
+ /**
222
+ * Dynamic per-action permission gate for the action router.
223
+ *
224
+ * Resolves the permission from `body.action` at request time and runs
225
+ * `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
226
+ * `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
227
+ * (idempotency) and `routeGuards` run. Previously this check lived inside
228
+ * the main action handler, which meant idempotency recorded unauthorized
229
+ * requests and route guards saw unfiltered scope — the very divergence
230
+ * routerShared exists to prevent.
231
+ *
232
+ * Also acts as a defensive fallback for invalid action names — the
233
+ * `oneOf` body schema normally rejects these at AJV validation, but
234
+ * hosts that disable schema validation still get a 400 here.
235
+ */
236
+ function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
237
+ const enumSet = new Set(actionEnum);
238
+ const validActions = [...actionEnum];
239
+ return async (req, reply) => {
240
+ const body = req.body ?? {};
241
+ const action = body.action;
242
+ if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
243
+ const permissionCheck = actionPermissions[action] ?? globalAuth;
244
+ if (!permissionCheck) return;
245
+ const { action: _discard, ...data } = body;
246
+ const params = req.params;
247
+ const context = buildPermissionContext(req, {
248
+ resource: resourceName,
249
+ action,
250
+ resourceId: params?.id,
251
+ data
252
+ });
253
+ await evaluateAndApplyPermission(permissionCheck, context, req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
254
+ };
255
+ }
256
+ //#endregion
181
257
  //#region src/core/middlewares/pluginMw.ts
182
258
  /**
183
259
  * Pick the request-lifecycle plugin middleware for an HTTP method:
@@ -246,4 +322,39 @@ function buildRateLimitConfig(rateLimit) {
246
322
  } };
247
323
  }
248
324
  //#endregion
249
- export { buildRouteHooks as a, buildAuthMiddleware as c, selectPluginMw as i, buildAuthMiddlewareForPermissions as l, buildRouteConfig as n, resolveRoutePreHandlers as o, resolveRouterPluginMw as r, routeHookOptions as s, buildRateLimitConfig as t, buildArcDecorator as u };
325
+ //#region src/core/middlewares/registerRoute.ts
326
+ /**
327
+ * Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
328
+ *
329
+ * Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
330
+ * overlaps before Fastify ever runs, but a few cases still slip through to
331
+ * Fastify itself: two presets contributing the same `routes` entry, hosts
332
+ * mounting two resources at the same prefix, custom routes that share a
333
+ * URL with another plugin. Fastify's default message ("Method 'GET'
334
+ * already declared for route '/'") gives no hint at the resource-shaped
335
+ * fix — `disabledRoutes: ['list']` — so wrap it with one.
336
+ */
337
+ function tryRegisterRoute(fastify, opts, context) {
338
+ try {
339
+ fastify.route(opts);
340
+ } catch (err) {
341
+ const fastifyErr = err;
342
+ if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
343
+ const method = opts.method;
344
+ const url = opts.url;
345
+ const opHint = context.op ? ` (op: ${context.op})` : "";
346
+ const fixHint = context.op && [
347
+ "list",
348
+ "get",
349
+ "create",
350
+ "update",
351
+ "delete"
352
+ ].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
353
+ const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
354
+ enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
355
+ enhanced.cause = err;
356
+ throw enhanced;
357
+ }
358
+ }
359
+ //#endregion
360
+ export { selectPluginMw as a, buildRouteHooks as c, buildAuthMiddleware as d, buildAuthMiddlewareForPermissions as f, resolveRouterPluginMw as i, resolveRoutePreHandlers as l, buildRateLimitConfig as n, buildActionPermissionMw as o, buildArcDecorator as p, buildRouteConfig as r, buildCrudPermissionMw as s, tryRegisterRoute as t, routeHookOptions as u };
@@ -1,6 +1,6 @@
1
1
 
2
- import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-BcR2bFZQ.mjs";
3
- import "../fastify-augmentation-DVsTAy6X.mjs";
2
+ import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-C01wWGJt.mjs";
3
+ import "../fastify-augmentation-Bk_9PoiV.mjs";
4
4
  import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
5
5
  import { FastifyPluginAsync } from "fastify";
6
6
  //#region src/registry/assertNoTenantData.d.ts
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-CLxCR5LF.mjs";
3
- import { t as ResourceRegistry } from "../ResourceRegistry-DoALUlOO.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-BzvRCYS6.mjs";
3
+ import { t as ResourceRegistry } from "../ResourceRegistry-DNuYVbmv.mjs";
4
4
  export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as CRUD_OPERATIONS } from "./constants-BxCoxmiN.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "./constants-DUoKAGYD.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/registry/assertNoTenantData.ts
5
5
  /**
@@ -441,64 +441,23 @@ var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspec
441
441
  //#endregion
442
442
  //#region src/registry/manifest.ts
443
443
  /**
444
- * Resource manifest for frontend code generators.
444
+ * Resource metadata for frontend code generators — `{ name, prefix, idField,
445
+ * crudOps, actions[] }`, where each action carries its `mount` and
446
+ * `requiresId`.
445
447
  *
446
- * Every host that hand-rolls a `createCrudApi('foo')` helper on the frontend
447
- * ends up rewriting the same shim for declarative actions: `postAction(id,
448
- * name, body)` — and forgets to add a method for every NEW action declared
449
- * on the resource. The OpenAI-team report flagged this in fajr's
450
- * `invoices.js`, and now matches needs the same.
448
+ * A hand-rolled FE client needs a method per declarative action and drifts the
449
+ * moment a new one is declared. Arc owns the resource definition, so it can
450
+ * emit the list instead of the host maintaining a parallel one.
451
451
  *
452
- * Arc owns the resource definition, so arc can ship the metadata FE codegen
453
- * needs in one canonical shape:
452
+ * BE-side only — arc ships no FE runtime. The host decides whether this powers
453
+ * a fetch helper, a TanStack Query factory, an RPC client, or `.d.ts` codegen.
454
454
  *
455
- * {
456
- * name: 'invoice',
457
- * prefix: '/invoices',
458
- * idField: '_id',
459
- * crudOps: ['list', 'get', 'create', 'update', 'delete'],
460
- * actions: [
461
- * { name: 'recordPayment', mount: '/:id/action', requiresId: true, description: '...' },
462
- * { name: 'propose', mount: '/action', requiresId: false, description: '...' },
463
- * ],
464
- * }
465
- *
466
- * Hosts feed this into their FE generator (or a runtime `createActionsApi`
467
- * shim) and every action is wired automatically. New actions land on the FE
468
- * the same moment they're declared on the resource — no parallel list to
469
- * keep in sync.
470
- *
471
- * The helper is BE-side only (lives under `@classytic/arc/registry`) — arc
472
- * stays a backend framework and doesn't ship FE runtime code. The host
473
- * decides whether the manifest powers a fetch-based helper, a TanStack
474
- * Query factory, an RPC client, or static `.d.ts` generation.
475
- *
476
- * @example BE → emit JSON at build time / on a route
455
+ * @example
477
456
  * ```ts
478
- * // Build-time codegen
479
- * import { buildResourceManifest } from '@classytic/arc/registry';
480
- * import { invoiceResource } from './invoice.resource.js';
481
- * writeFileSync('./fe-gen/invoice.manifest.json',
482
- * JSON.stringify(buildResourceManifest(invoiceResource)));
483
- *
484
- * // Runtime introspection endpoint
485
- * fastify.get('/manifest/:name', async (req) => {
486
- * const r = arc.registry.get(req.params.name);
487
- * return r ? buildResourceManifestFromRegistry(r) : reply.code(404);
488
- * });
489
- * ```
490
- *
491
- * @example FE → generate fetch methods from the manifest
492
- * ```ts
493
- * import manifest from './fe-gen/invoice.manifest.json';
494
- * import { createActionsApi } from './my-fe-shim.js'; // host-owned
495
- *
496
- * export const invoicesApi = {
497
- * ...createCrudApi(manifest.prefix),
498
- * ...createActionsApi(manifest.prefix, manifest.actions),
499
- * // → invoicesApi.recordPayment(id, body)
500
- * // → invoicesApi.propose(body) // id-less
501
- * };
457
+ * // build time
458
+ * writeFileSync('invoice.manifest.json', JSON.stringify(buildResourceManifest(r)));
459
+ * // or at runtime, from the live registry
460
+ * buildResourceManifestFromRegistry(arc.registry.get(name));
502
461
  * ```
503
462
  */
504
463
  /**
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  //#region src/lock/renewingLease.ts
3
3
  /**
4
4
  * Start renewing an ALREADY-ACQUIRED lease. The caller performed the
@@ -8,14 +8,23 @@
8
8
  function startRenewingLease(options) {
9
9
  const { lock, name, holderId, leaseMs, onLost, onError } = options;
10
10
  const renewEveryMs = options.renewEveryMs ?? Math.max(10, Math.floor(leaseMs / 2));
11
+ const fenced = typeof lock.tryAcquireFenced === "function" && options.token !== void 0;
11
12
  let stopped = false;
12
13
  let lost = false;
14
+ let token = options.token;
13
15
  let timer;
14
16
  let inFlight;
15
17
  const scheduleNext = () => {
16
18
  if (stopped) return;
17
19
  timer = setTimeout(() => {
18
- inFlight = Promise.resolve(lock.tryAcquire(name, holderId, leaseMs)).then((renewed) => {
20
+ inFlight = (fenced ? Promise.resolve(lock.tryAcquireFenced?.(name, holderId, leaseMs)).then((result) => {
21
+ if (result === null || result === void 0) return false;
22
+ if (result.token !== token) {
23
+ token = result.token;
24
+ return false;
25
+ }
26
+ return true;
27
+ }) : Promise.resolve(lock.tryAcquire(name, holderId, leaseMs))).then((renewed) => {
19
28
  if (!renewed && !lost) {
20
29
  lost = true;
21
30
  onLost?.();
@@ -34,6 +43,9 @@ function startRenewingLease(options) {
34
43
  get lost() {
35
44
  return lost;
36
45
  },
46
+ get token() {
47
+ return token;
48
+ },
37
49
  async stop() {
38
50
  stopped = true;
39
51
  if (timer) clearTimeout(timer);
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-JJWfp8rp.mjs";
3
3
  import { toCanonicalList } from "@classytic/repo-core/pagination";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/plugins/replyHelpers.ts
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  //#region src/context/requestContext.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  import "node:module";
3
3
  //#region \0rolldown/runtime.js
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/runtimeCapabilities.ts
3
3
  /**
4
4
  * `Symbol.for` so declarations from two arc copies in one dependency graph
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
3
- import { t as startRenewingLease } from "./renewingLease-BnWtbA2U.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-JJWfp8rp.mjs";
3
+ import { t as startRenewingLease } from "./renewingLease-CgIGh0Ab.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/plugins/schedules.ts
6
6
  var schedules_exports = /* @__PURE__ */ __exportAll({
@@ -40,7 +40,8 @@ const schedulesPlugin = async (fastify, opts) => {
40
40
  if (lock) {
41
41
  const lockName = `arc:schedule:${s.name}`;
42
42
  const leaseMs = s.leaseMs ?? Math.min(Math.floor(s.every * .9), MAX_DEFAULT_LEASE_MS);
43
- if (!await lock.tryAcquire(lockName, holderId, leaseMs)) {
43
+ const fenced = await lock.tryAcquireFenced?.(lockName, holderId, leaseMs);
44
+ if (!(fenced !== void 0 ? fenced !== null : await lock.tryAcquire(lockName, holderId, leaseMs))) {
44
45
  stat.skippedByLock++;
45
46
  return;
46
47
  }
@@ -49,6 +50,7 @@ const schedulesPlugin = async (fastify, opts) => {
49
50
  name: lockName,
50
51
  holderId,
51
52
  leaseMs,
53
+ ...fenced ? { token: fenced.token } : {},
52
54
  onLost: () => fastify.log.warn({ schedule: s.name }, "[arc-schedules] lease renewal lost — another holder took the lock; this run may now overlap. Handlers should be idempotent."),
53
55
  onError: (err) => fastify.log.warn({
54
56
  err,
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  import { arcLog } from "./logger/index.mjs";
3
3
  //#region src/utils/schemaConverter.ts
4
4
  /**
@@ -14,44 +14,31 @@ import { arcLog } from "./logger/index.mjs";
14
14
  * boot with an actionable error; DOC paths warn and degrade — see
15
15
  * {@link UnconvertibleMode}. Hosts that never pass Zod pay nothing.
16
16
  *
17
- * ## Targets
17
+ * TARGET — `draft-7` for Fastify route schemas, `openapi-3.0` for docs. Fastify
18
+ * v5's AJV 8 is draft-07 and wants NUMERIC `exclusiveMinimum`; the
19
+ * `openapi-3.0` target emits the draft-04 boolean form, which AJV rejects at
20
+ * registration (`exclusiveMinimum must be number`). Using `draft-7` makes
21
+ * `.positive()` / `.gt()` work unmodified.
18
22
  *
19
- * Zod v4's `toJSONSchema` supports multiple output targets; arc picks per consumer:
20
- *
21
- * - **`draft-7`** (default) — for Fastify route schemas. Fastify v5 bundles AJV 8
22
- * configured for draft-07, which uses **numeric** `exclusiveMinimum`/`exclusiveMaximum`.
23
- * The `openapi-3.0` target emits the **boolean** form inherited from draft-04
24
- * (`exclusiveMinimum: true` alongside `minimum`), which AJV rejects at route
25
- * registration with `schema is invalid: data/properties/X/exclusiveMinimum must be number`.
26
- * Using `draft-7` fixes `.positive() / .negative() / .gt() / .lt()` out of the box.
27
- * - **`openapi-3.0`** — for OpenAPI doc generation (arc emits OpenAPI 3.0.3). Keeps
28
- * the boolean exclusive form that 3.0 tooling expects.
29
- *
30
- * ## Input vs output (`io`)
31
- *
32
- * A Zod schema has TWO shapes and `z.toJSONSchema()` emits one of them. Getting
33
- * this wrong is not cosmetic — it rejects legal traffic:
23
+ * IO DIRECTION — a Zod schema has two shapes and getting this wrong REJECTS
24
+ * LEGAL TRAFFIC:
34
25
  *
35
26
  * ```ts
36
27
  * z.object({ title: z.string(), status: z.string().default("draft") })
37
- * // io: "output" → required: ["title", "status"] ← a client omitting `status` is REJECTED
38
- * // io: "input" → required: ["title"] ← correct: the default fills it in
28
+ * // io: "output" → required: ["title", "status"] ← omitting `status` is rejected
29
+ * // io: "input" → required: ["title"] ← correct; the default fills it
39
30
  * ```
40
31
  *
41
- * The same applies to `.transform()`: in output mode the node is unrepresentable
42
- * and degrades to `{}` (no validation at all), while input mode emits the real
43
- * input type. So arc converts by DIRECTION:
44
- *
45
- * - **request** (`body`, `querystring`, `params`, `headers`) → `io: "input"` —
46
- * validates what the client SENDS, before defaults/transforms/coercion apply.
47
- * - **response** (and the `entity` doc shape) → `io: "output"` — describes what
48
- * the server RETURNS, after they apply.
32
+ * `.transform()` is worse in output mode: unrepresentable, so it degrades to
33
+ * `{}` — no validation at all. Arc therefore converts by direction: REQUESTS
34
+ * (`body`/`querystring`/`params`/`headers`) use `io: "input"`, validating what
35
+ * the client sends before defaults and transforms; RESPONSES (and the `entity`
36
+ * shape) use `io: "output"`, describing what the server returns.
49
37
  *
50
- * One deliberate consequence: a plain `z.object()` emits
51
- * `additionalProperties: false` in output mode but NOT in input mode, because
52
- * `z.object()` STRIPS unknown keys rather than rejecting them — so extra keys
53
- * are legal input. Use `z.strictObject()` when the wire contract must reject
54
- * them; it keeps `additionalProperties: false` in both modes.
38
+ * Consequence: plain `z.object()` emits `additionalProperties: false` only in
39
+ * output mode, because it STRIPS unknown keys rather than rejecting them — so
40
+ * extra keys are legal input. Use `z.strictObject()` when the wire contract
41
+ * must reject them; it holds in both modes.
55
42
  */
56
43
  const log = arcLog("schema");
57
44
  /** Default target for Fastify-consumed schemas (matches Fastify v5's default AJV draft). */
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { o as toJsonSchema } from "./schemaConverter-XH1RZRyu.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { o as toJsonSchema } from "./schemaConverter-gg0_aUPF.mjs";
3
3
  //#region src/core/schemaIR.ts
4
4
  /**
5
5
  * Schema IR — one canonical representation, two adapters.
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
3
3
  import { Type, Type as Type$1 } from "typebox";
4
4
  //#region src/schemas/index.ts
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as parseJsonBody } from "../jsonBody-CfGH-GxO.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as parseJsonBody } from "../jsonBody-CiHih3ZU.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/scim/errors.ts
5
5
  var ScimError = class extends Error {
@@ -1,5 +1,5 @@
1
1
 
2
- import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-ChuShpCP.mjs";
2
+ import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-Ddl9OjmE.mjs";
3
3
  import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-BXCfVcNG.mjs";
4
4
  import { FastifyReply, FastifyRequest } from "fastify";
5
5
  //#region src/scope/rateLimitKey.d.ts
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-CoqIRrkY.mjs";
3
- import { n as elevation_default, t as elevationPlugin } from "../elevation-B0tlY8y1.mjs";
4
- import { t as createTenantKeyGenerator } from "../rateLimitKey-XngqVNEV.mjs";
5
- import { t as resolveOrgFromHeader } from "../scope-BaWcqBvr.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-B8lwJv9Q.mjs";
3
+ import { n as elevation_default, t as elevationPlugin } from "../elevation-D_cmo80j.mjs";
4
+ import { t as createTenantKeyGenerator } from "../rateLimitKey-Bma8VXDO.mjs";
5
+ import { t as resolveOrgFromHeader } from "../scope-B1RsHdjF.mjs";
6
6
  export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin, elevationPlugin as elevationPluginFn, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-Cd_yy7aW.mjs";
3
- import { n as requireSingleHeaderValue } from "./headers-CveNO9gR.mjs";
4
- import { n as getUserRoles } from "./userHelpers-CWF5pbZD.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-DDKni6Tz.mjs";
3
+ import { n as requireSingleHeaderValue } from "./headers-DKslnhey.mjs";
4
+ import { n as getUserRoles } from "./userHelpers-xQAi5bPF.mjs";
5
5
  //#region src/scope/resolveOrgFromHeader.ts
6
6
  /**
7
7
  * Create a preHandler hook that resolves org scope from a header.
@@ -0,0 +1,43 @@
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ //#region src/utils/simpleEqualityMatcher.ts
3
+ /**
4
+ * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
5
+ * matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
6
+ *
7
+ * Compares `item[key]` to each filter value by string coercion, so a Mongo
8
+ * `ObjectId` matches its string form; array values match by contains. ALL
9
+ * entries must match.
10
+ *
11
+ * NOT an operator engine: no `$in`/`$ne`/`$gt`/`$regex`/`$exists`, no
12
+ * `$and`/`$or`, no dot paths, no schema coercion. Most `_policyFilters` from
13
+ * arc's built-in permission helpers are flat equality (`{ ownerId }`,
14
+ * `{ organizationId }`), and this covers exactly that — defense-in-depth for
15
+ * hosts on minimal repos with no `getOne(compoundFilter)` path.
16
+ *
17
+ * Do NOT use it with operator-shaped filters — supply a native matcher instead
18
+ * (a kit's predicate engine). Kit users need neither: the DB-level filter in
19
+ * `getOne(compoundFilter)` already applies.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const adapter: DataAdapter = { repository, type: 'custom', name: 'in-memory',
24
+ * matchesFilter: simpleEqualityMatcher };
25
+ * ```
26
+ */
27
+ function simpleEqualityMatcher(item, filters) {
28
+ if (!item || typeof item !== "object") return false;
29
+ const obj = item;
30
+ for (const [key, expected] of Object.entries(filters)) {
31
+ if (expected && typeof expected === "object" && !Array.isArray(expected) && Object.getPrototypeOf(expected) === Object.prototype && Object.keys(expected).some((k) => k.startsWith("$"))) return false;
32
+ const actual = obj[key];
33
+ if (Array.isArray(actual)) {
34
+ const expectedStr = String(expected);
35
+ if (!actual.some((v) => String(v) === expectedStr)) return false;
36
+ continue;
37
+ }
38
+ if (String(actual) !== String(expected)) return false;
39
+ }
40
+ return true;
41
+ }
42
+ //#endregion
43
+ export { simpleEqualityMatcher as t };
@@ -1,9 +1,9 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-JJWfp8rp.mjs";
3
3
  import { arcLog } from "./logger/index.mjs";
4
- import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-CoqIRrkY.mjs";
5
- import { a as promoteStreamTokenToHeader } from "./streaming-DsT1B5zJ.mjs";
6
- import { t as openSseStream } from "./sseStream-Bvpi-UXQ.mjs";
4
+ import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-B8lwJv9Q.mjs";
5
+ import { a as promoteStreamTokenToHeader } from "./streaming-CiTJW8kZ.mjs";
6
+ import { t as openSseStream } from "./sseStream-BMHTLlhR.mjs";
7
7
  import fp from "fastify-plugin";
8
8
  //#region src/plugins/sse.ts
9
9
  var sse_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { n as forwardedStreamHeaders } from "./streaming-DsT1B5zJ.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { n as forwardedStreamHeaders } from "./streaming-CiTJW8kZ.mjs";
3
3
  //#region src/utils/sseStream.ts
4
4
  /**
5
5
  * Take over the reply socket and open an SSE stream. Call ONLY after every
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/store-helpers.ts
3
3
  /**
4
4
  * Classify an error thrown by `getOne` / `getById` / `update` as a
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  import { Readable } from "node:stream";
3
3
  //#region src/utils/streaming.ts
4
4
  /**