@classytic/arc 2.15.4 → 2.16.1

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 (160) hide show
  1. package/README.md +1 -0
  2. package/bin/arc.js +12 -0
  3. package/dist/{BaseController-dx3m2J8V.mjs → BaseController-Vk8I7C0x.mjs} +61 -19
  4. package/dist/{HookSystem-Iiebom92.mjs → HookSystem-Cmf7-Etp.mjs} +8 -4
  5. package/dist/{QueryCache-D41bfdBB.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
  6. package/dist/{ResourceRegistry-CTERg_2x.mjs → ResourceRegistry-CzSzVK1m.mjs} +55 -9
  7. package/dist/audit/index.d.mts +1 -1
  8. package/dist/audit/index.mjs +4 -2
  9. package/dist/auth/index.d.mts +4 -4
  10. package/dist/auth/index.mjs +4 -4
  11. package/dist/auth/redis-session.d.mts +1 -1
  12. package/dist/{betterAuthOpenApi--M_i87dQ.mjs → betterAuthOpenApi-ClWxaceA.mjs} +10 -6
  13. package/dist/buildHandler-C_SkBNF3.mjs +302 -0
  14. package/dist/cache/index.d.mts +3 -3
  15. package/dist/cache/index.mjs +3 -3
  16. package/dist/{caching-SM8gghN6.mjs → caching-8MfrrUYU.mjs} +1 -1
  17. package/dist/cli/commands/describe.d.mts +35 -1
  18. package/dist/cli/commands/describe.mjs +52 -12
  19. package/dist/cli/commands/docs.d.mts +1 -4
  20. package/dist/cli/commands/docs.mjs +4 -16
  21. package/dist/cli/commands/generate.d.mts +2 -20
  22. package/dist/cli/commands/generate.mjs +1 -546
  23. package/dist/cli/commands/init.d.mts +2 -40
  24. package/dist/cli/commands/init.mjs +1 -3045
  25. package/dist/cli/commands/introspect.mjs +53 -64
  26. package/dist/cli/index.d.mts +2 -2
  27. package/dist/cli/index.mjs +2 -2
  28. package/dist/{constants-Cxde4rpC.mjs → constants-TrJVIJl0.mjs} +7 -0
  29. package/dist/core/index.d.mts +3 -3
  30. package/dist/core/index.mjs +5 -5
  31. package/dist/{core-CvmOqEms.mjs → core-wKeGXn38.mjs} +222 -44
  32. package/dist/createActionRouter-eiOFtnC3.mjs +288 -0
  33. package/dist/{createAggregationRouter-B0bPDf5b.mjs → createAggregationRouter-C4unhBnV.mjs} +3 -2
  34. package/dist/{createApp-PFegs47-.mjs → createApp-DdPBtmzp.mjs} +16 -14
  35. package/dist/{defineEvent-D5h7EvAx.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
  36. package/dist/docs/index.d.mts +2 -2
  37. package/dist/docs/index.mjs +1 -1
  38. package/dist/{errorHandler-Bk-AGhkU.mjs → errorHandler-DNPzqaTT.mjs} +17 -14
  39. package/dist/errors-4ZloYFvm.d.mts +91 -0
  40. package/dist/{eventPlugin-CaKTYkYM.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
  41. package/dist/{eventPlugin-qXpqTebY.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
  42. package/dist/events/index.d.mts +3 -3
  43. package/dist/events/index.mjs +5 -5
  44. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  45. package/dist/events/transports/redis.d.mts +1 -1
  46. package/dist/factory/index.d.mts +2 -2
  47. package/dist/factory/index.mjs +4 -3
  48. package/dist/fastify-augmentation-CJm0vh-l.d.mts +42 -0
  49. package/dist/{fields-COhcH3fk.d.mts → fields-DqZLp5d6.d.mts} +1 -1
  50. package/dist/generate-CcM24hoq.mjs +654 -0
  51. package/dist/generate-cRnsafMS.d.mts +38 -0
  52. package/dist/hooks/index.d.mts +1 -1
  53. package/dist/hooks/index.mjs +1 -1
  54. package/dist/idempotency/index.d.mts +2 -2
  55. package/dist/idempotency/index.mjs +1 -1
  56. package/dist/idempotency/redis.d.mts +1 -1
  57. package/dist/{index-BTqLEvhu.d.mts → index-3L-adFJO.d.mts} +12 -12
  58. package/dist/{index-BstGxcc3.d.mts → index-CC_pgZjY.d.mts} +88 -17
  59. package/dist/{index-BswOSJCE.d.mts → index-DBDgy6Hw.d.mts} +430 -19
  60. package/dist/index.d.mts +6 -6
  61. package/dist/index.mjs +7 -8
  62. package/dist/init-D-vrTZ1G.d.mts +71 -0
  63. package/dist/init-DYzfniuX.mjs +3098 -0
  64. package/dist/integrations/event-gateway.d.mts +2 -2
  65. package/dist/integrations/event-gateway.mjs +1 -1
  66. package/dist/integrations/index.d.mts +2 -2
  67. package/dist/integrations/jobs.mjs +3 -3
  68. package/dist/integrations/mcp/index.d.mts +239 -7
  69. package/dist/integrations/mcp/index.mjs +2 -528
  70. package/dist/integrations/mcp/testing.d.mts +2 -2
  71. package/dist/integrations/mcp/testing.mjs +6 -10
  72. package/dist/integrations/streamline.mjs +26 -1
  73. package/dist/integrations/websocket-redis.d.mts +1 -1
  74. package/dist/integrations/websocket.d.mts +1 -1
  75. package/dist/integrations/websocket.mjs +1 -0
  76. package/dist/loadResourcesFromEntry-DCjYMgfC.mjs +51 -0
  77. package/dist/{resourceToTools-tFYUNmM0.mjs → mcpPlugin-Dbgz9_Rj.mjs} +1021 -318
  78. package/dist/{memory-UBydS5ku.mjs → memory-QOLe11D5.mjs} +2 -0
  79. package/dist/middleware/index.d.mts +1 -1
  80. package/dist/middleware/index.mjs +1 -1
  81. package/dist/{openapi-BHXhoX8O.mjs → openapi-BRnd3pT7.mjs} +82 -44
  82. package/dist/permissions/index.d.mts +2 -2
  83. package/dist/permissions/index.mjs +1 -1
  84. package/dist/{permissions-ohQyv50e.mjs → permissions-CTxMrreC.mjs} +2 -2
  85. package/dist/{pipe-Zr0KXjQe.mjs → pipe-DiCyvyPN.mjs} +1 -0
  86. package/dist/pipeline/index.d.mts +1 -1
  87. package/dist/pipeline/index.mjs +1 -1
  88. package/dist/plugins/index.d.mts +5 -59
  89. package/dist/plugins/index.mjs +10 -10
  90. package/dist/plugins/response-cache.mjs +5 -5
  91. package/dist/plugins/tracing-entry.d.mts +1 -1
  92. package/dist/plugins/tracing-entry.mjs +1 -1
  93. package/dist/{pluralize-DQgqgifU.mjs → pluralize-B9M8xvy-.mjs} +2 -1
  94. package/dist/preloadResources-BOXzPhKU.mjs +45 -0
  95. package/dist/presets/filesUpload.d.mts +4 -4
  96. package/dist/presets/filesUpload.mjs +2 -2
  97. package/dist/presets/index.d.mts +1 -1
  98. package/dist/presets/index.mjs +1 -1
  99. package/dist/presets/multiTenant.d.mts +1 -1
  100. package/dist/presets/multiTenant.mjs +4 -3
  101. package/dist/presets/search.d.mts +2 -2
  102. package/dist/presets/search.mjs +1 -1
  103. package/dist/{presets-BbkjdPeH.mjs → presets-C9BE6WaZ.mjs} +2 -2
  104. package/dist/{queryCachePlugin-m1XsgAIJ.mjs → queryCachePlugin-B4XMSSe7.mjs} +2 -2
  105. package/dist/{queryCachePlugin-CqMdLI2-.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
  106. package/dist/{redis-DiMkdHEl.d.mts → redis-cpqVb85w.d.mts} +1 -1
  107. package/dist/{redis-stream-D6HzR1Z_.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
  108. package/dist/registry/index.d.mts +341 -2
  109. package/dist/registry/index.mjs +3 -3
  110. package/dist/registry-BrlHkrRS.mjs +582 -0
  111. package/dist/{routerShared-DrOa-26E.mjs → routerShared-ClV4cCaa.mjs} +40 -16
  112. package/dist/scope/index.d.mts +3 -3
  113. package/dist/scope/index.mjs +3 -3
  114. package/dist/{sse-Bz-5ZeTt.mjs → sse-jh5OqFjR.mjs} +1 -1
  115. package/dist/testing/index.d.mts +2 -25
  116. package/dist/testing/index.mjs +17 -51
  117. package/dist/testing/storageContract.d.mts +1 -1
  118. package/dist/types/index.d.mts +5 -5
  119. package/dist/types/storage.d.mts +1 -1
  120. package/dist/{types-BQsjgQzS.d.mts → types-BD0BCdf6.d.mts} +106 -12
  121. package/dist/{types-C_s5moIu.mjs → types-Bi0r0vjG.mjs} +53 -1
  122. package/dist/{types-CTYvcwHe.d.mts → types-CfapW2t8.d.mts} +42 -1
  123. package/dist/{types-DrBaUwyV.d.mts → types-Dw--SyTq.d.mts} +57 -11
  124. package/dist/utils/index.d.mts +1287 -2
  125. package/dist/utils/index.mjs +1 -1
  126. package/dist/{utils-_h9B3c57.mjs → utils-DPXZnZUx.mjs} +158 -41
  127. package/dist/{buildHandler-CcFOpJLh.mjs → validate-DFIP2VrM.mjs} +73 -302
  128. package/dist/{versioning-hmkPcDlX.d.mts → versioning-5H4930tj.d.mts} +1 -1
  129. package/package.json +22 -29
  130. package/skills/arc/SKILL.md +300 -706
  131. package/skills/arc/references/auth.md +19 -7
  132. package/skills/arc-code-review/SKILL.md +1 -1
  133. package/skills/arc-code-review/references/arc-cheatsheet.md +100 -322
  134. package/dist/createActionRouter-S3MLVYot.mjs +0 -220
  135. package/dist/index-bRjYu21O.d.mts +0 -1320
  136. package/dist/org/index.d.mts +0 -66
  137. package/dist/org/index.mjs +0 -486
  138. package/dist/org/types.d.mts +0 -82
  139. package/dist/org/types.mjs +0 -1
  140. package/dist/registry-I-ogLgL9.mjs +0 -46
  141. /package/dist/{EventTransport-CT_52aWU.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
  142. /package/dist/{EventTransport-DLWoUMHy.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
  143. /package/dist/{actionPermissions-CyUkQu6O.mjs → actionPermissions-Bjmvn7Eb.mjs} +0 -0
  144. /package/dist/{elevation-DgoeTyfX.mjs → elevation-Dci0AYLT.mjs} +0 -0
  145. /package/dist/{elevation-BXOWoGCF.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
  146. /package/dist/{errorHandler-DFr45ZG4.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
  147. /package/dist/{externalPaths-BD5nw6St.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
  148. /package/dist/{interface-beEtJyWM.d.mts → interface-BVkZsvEe.d.mts} +0 -0
  149. /package/dist/{interface-DfLGcus7.d.mts → interface-DoE6UoC2.d.mts} +0 -0
  150. /package/dist/{keys-CGcCbNyu.mjs → keys-DopsCuyQ.mjs} +0 -0
  151. /package/dist/{loadResources-DBMQg_Aj.mjs → loadResources-fol6JuvW.mjs} +0 -0
  152. /package/dist/{metrics-Qnvwc-LQ.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  153. /package/dist/{replyHelpers-CK-FNO8E.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
  154. /package/dist/{schemaIR-lYhC2gE5.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
  155. /package/dist/{sessionManager-C4Le_UB3.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
  156. /package/dist/{storage-Dfzt4VTl.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
  157. /package/dist/{store-helpers-BkIN9-vu.mjs → store-helpers-5JgaxieH.mjs} +0 -0
  158. /package/dist/{tracing-QJVprktp.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
  159. /package/dist/{versioning-BUrT5aP4.mjs → versioning-CuIwUQZ2.mjs} +0 -0
  160. /package/dist/{websocket-ChC2rqe1.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
@@ -108,6 +108,7 @@ var MemoryCacheStore = class {
108
108
  const oldestKey = this.cache.keys().next().value;
109
109
  if (!oldestKey) break;
110
110
  const entry = this.cache.get(oldestKey);
111
+ if (!entry) break;
111
112
  this.removeEntry(oldestKey, entry);
112
113
  this._evictions++;
113
114
  }
@@ -118,6 +119,7 @@ var MemoryCacheStore = class {
118
119
  const oldestKey = this.cache.keys().next().value;
119
120
  if (!oldestKey) break;
120
121
  const entry = this.cache.get(oldestKey);
122
+ if (!entry) break;
121
123
  this.removeEntry(oldestKey, entry);
122
124
  this._evictions++;
123
125
  if (this.currentBytes <= target) break;
@@ -1,4 +1,4 @@
1
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-BswOSJCE.mjs";
1
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-DBDgy6Hw.mjs";
2
2
  import { RouteHandlerMethod } from "fastify";
3
3
 
4
4
  //#region src/middleware/middleware.d.ts
@@ -1,4 +1,4 @@
1
- import { t as CRUD_OPERATIONS } from "../constants-Cxde4rpC.mjs";
1
+ import { t as CRUD_OPERATIONS } from "../constants-TrJVIJl0.mjs";
2
2
  import { t as multipartBody } from "../multipartBody-BOvVSVCD.mjs";
3
3
  //#region src/middleware/middleware.ts
4
4
  /**
@@ -1,7 +1,8 @@
1
1
  import { t as getUserRoles } from "./types-D57iXYb8.mjs";
2
2
  import { n as convertRouteSchema } from "./schemaConverter-De34B1ZG.mjs";
3
- import { t as resolveActionPermission } from "./actionPermissions-CyUkQu6O.mjs";
4
- import { t as buildActionBodySchema } from "./createActionRouter-S3MLVYot.mjs";
3
+ import { t as pluralize } from "./pluralize-B9M8xvy-.mjs";
4
+ import { t as resolveActionPermission } from "./actionPermissions-Bjmvn7Eb.mjs";
5
+ import { t as buildActionBodySchema } from "./createActionRouter-eiOFtnC3.mjs";
5
6
  import fp from "fastify-plugin";
6
7
  //#region src/docs/openapi/canonical-schemas.ts
7
8
  /**
@@ -383,9 +384,11 @@ function createOperation(resource, operation, summary, extras, requiresAuthOverr
383
384
  if (!s.operations) return true;
384
385
  return s.operations.includes(operation);
385
386
  });
387
+ const summaryNoun = (resource.displayName || resource.name).toLowerCase();
388
+ const finalNoun = operation === "list" ? pluralize(summaryNoun) : summaryNoun;
386
389
  const op = {
387
390
  tags: [resource.tag || "Resource"],
388
- summary: `${summary} ${(resource.displayName || resource.name).toLowerCase()}`,
391
+ summary: `${summary} ${finalNoun}`,
389
392
  operationId: `${resource.name}_${operation}`,
390
393
  ...descParts.length > 0 && { description: descParts.join("\n\n") },
391
394
  ...requiresAuth && { security: [{ bearerAuth: [] }, ...additionalSecurity] },
@@ -509,49 +512,59 @@ function extractPathParams(path) {
509
512
  //#endregion
510
513
  //#region src/docs/openapi/action-paths.ts
511
514
  /**
512
- * Action endpoint emitter — `POST /:resource/:id/action`.
515
+ * Action endpoint emitter — `POST /:resource/:id/action` and/or
516
+ * `POST /:resource/action`.
513
517
  *
514
- * Generates a single dispatch endpoint per resource that lists every
515
- * declared action via the `action` discriminant. Body schema is built
516
- * via the SAME `buildActionBodySchema` runtime uses, so docs and
517
- * validation stay in sync (one source of truth for the action envelope
518
- * shape).
518
+ * Generates a dispatch endpoint per mount point. Each mount has its own
519
+ * `oneOf` body schema containing only the actions that live at that
520
+ * mount — id-bound actions under `/:id/action`, id-less actions
521
+ * (`id: false`) under `/action`. Body schemas come from the SAME
522
+ * `buildActionBodySchema` the runtime uses, so docs and validation stay
523
+ * in sync.
519
524
  *
520
525
  * NOTE: action **response** shape varies per action — the dispatcher
521
526
  * returns whatever the handler returned. We can't statically type the
522
- * response without the handler exposing its return type, and most
523
- * handlers return either the mutated resource document or a kit-defined
524
- * envelope. We declare the `200` body schema as an empty object (`{}`)
525
- * which `@hey-api/openapi-ts` and friends compile to `unknown` — that's
526
- * the most accurate thing we can say without lying to consumers about
527
- * shape. Per-action shape is documented in the `description` field;
528
- * future work could let resource authors declare a per-action
529
- * `responseSchema`.
527
+ * response without the handler exposing its return type, so the `200`
528
+ * body schema is `{}` (codegen → `unknown`). Per-action shape lives in
529
+ * the operation `description`.
530
530
  */
531
531
  /**
532
- * Append the action-dispatch path (`POST /:basePath/:id/action`) when
533
- * the resource declares any `actions`.
532
+ * Append action-dispatch paths to `paths` for the resource. Emits one
533
+ * path per non-empty mount point — id-bound and/or id-less.
534
534
  */
535
535
  function appendActionPaths(paths, resource, basePath, additionalSecurity) {
536
536
  if (!resource.actions || resource.actions.length === 0) return;
537
- const actionPath = toOpenApiPath(`${basePath}/:id/action`);
538
- const actionEnum = resource.actions.map((a) => a.name);
537
+ const idBound = [];
538
+ const idLess = [];
539
+ for (const a of resource.actions) (a.id === false ? idLess : idBound).push(a);
540
+ if (idBound.length > 0) emitMount(paths, resource, basePath, additionalSecurity, idBound, true);
541
+ if (idLess.length > 0) emitMount(paths, resource, basePath, additionalSecurity, idLess, false);
542
+ }
543
+ /**
544
+ * Emit one OpenAPI path for a single mount point's action subset.
545
+ * Centralises the shared rendering (body schema, description list,
546
+ * auth detection, response wiring) so id-bound and id-less mounts can't
547
+ * drift in shape.
548
+ */
549
+ function emitMount(paths, resource, basePath, additionalSecurity, subsetActions, hasId) {
550
+ const actionPath = toOpenApiPath(`${basePath}${hasId ? "/:id/action" : "/action"}`);
551
+ const actionEnum = subsetActions.map((a) => a.name);
539
552
  const actionSchemas = {};
540
- for (const a of resource.actions) if (a.schema) actionSchemas[a.name] = a.schema;
553
+ for (const a of subsetActions) if (a.schema) actionSchemas[a.name] = a.schema;
541
554
  const bodySchema = buildActionBodySchema(actionEnum, actionSchemas);
542
555
  const descLines = [
543
- "Unified action endpoint for state transitions.",
556
+ hasId ? "Unified action endpoint for state transitions on an existing entity." : "Resource-root action endpoint (no `:id` — operates on the collection or creates new entities).",
544
557
  "",
545
558
  "**Available actions:**"
546
559
  ];
547
- for (const a of resource.actions) {
560
+ for (const a of subsetActions) {
548
561
  const roles = a.permissions?._roles;
549
562
  const roleStr = roles?.length ? ` — requires: ${roles.join(" or ")}` : "";
550
563
  const descStr = a.description ? ` — ${a.description}` : "";
551
564
  descLines.push(`- \`${a.name}\`${roleStr}${descStr}`);
552
565
  }
553
566
  descLines.push("", "Response shape depends on the action handler — typically the mutated resource document or a kit-defined result envelope. See the per-action description above.");
554
- const anyAuthRequired = resource.actions.some((a) => {
567
+ const anyAuthRequired = subsetActions.some((a) => {
555
568
  const effective = resolveActionPermission({
556
569
  action: { permissions: a.permissions },
557
570
  resourcePermissions: resource.permissions,
@@ -561,13 +574,13 @@ function appendActionPaths(paths, resource, basePath, additionalSecurity) {
561
574
  });
562
575
  if (!paths[actionPath]) paths[actionPath] = {};
563
576
  paths[actionPath].post = createOperation(resource, "action", `Perform action (${actionEnum.join(" / ")})`, {
564
- parameters: [{
577
+ ...hasId ? { parameters: [{
565
578
  name: "id",
566
579
  in: "path",
567
580
  required: true,
568
581
  schema: { type: "string" },
569
582
  description: "Resource ID"
570
- }],
583
+ }] } : {},
571
584
  description: descLines.join("\n"),
572
585
  requestBody: {
573
586
  required: true,
@@ -579,7 +592,7 @@ function appendActionPaths(paths, resource, basePath, additionalSecurity) {
579
592
  content: { "application/json": { schema: {} } }
580
593
  },
581
594
  "400": errorResponse("Invalid action or missing required fields"),
582
- "404": errorResponse("Resource not found")
595
+ ...hasId ? { "404": errorResponse("Resource not found") } : {}
583
596
  }
584
597
  }, anyAuthRequired, additionalSecurity);
585
598
  }
@@ -634,13 +647,7 @@ function appendAggregationPaths(paths, resource, basePath, additionalSecurity) {
634
647
  if (!paths[path]) paths[path] = {};
635
648
  paths[path].get = createOperation(resource, `aggregation.${agg.name}`, agg.summary ?? `Aggregation: ${agg.name}`, {
636
649
  description: descLines.join("\n"),
637
- parameters: [{
638
- name: "querystring",
639
- in: "query",
640
- required: false,
641
- schema: querystring,
642
- description: "Filter narrowing — composes with base filter + tenant scope."
643
- }],
650
+ parameters: buildAggregationParameters(agg, querystring),
644
651
  responses: {
645
652
  "200": {
646
653
  description: "Aggregation result",
@@ -688,6 +695,37 @@ function normalizeGroupByForOpenApi(groupBy) {
688
695
  if (!groupBy) return [];
689
696
  return typeof groupBy === "string" ? [groupBy] : groupBy;
690
697
  }
698
+ /**
699
+ * Build the OpenAPI `parameters` array for an aggregation route.
700
+ *
701
+ * Always includes the filter-narrowing `querystring` slot. When the
702
+ * aggregation declares URL-driven paging (`defaultLimit`), adds an
703
+ * explicit `?limit` integer parameter with the declared default +
704
+ * maximum. Auto-generated FE clients pick the constraint up; humans
705
+ * reading the docs see the cap without reading source.
706
+ */
707
+ function buildAggregationParameters(agg, querystring) {
708
+ const params = [{
709
+ name: "querystring",
710
+ in: "query",
711
+ required: false,
712
+ schema: querystring,
713
+ description: "Filter narrowing — composes with base filter + tenant scope."
714
+ }];
715
+ if (agg.defaultLimit !== void 0) params.push({
716
+ name: "limit",
717
+ in: "query",
718
+ required: false,
719
+ schema: {
720
+ type: "integer",
721
+ minimum: 1,
722
+ default: agg.defaultLimit,
723
+ maximum: agg.maxLimit ?? 1e3
724
+ },
725
+ description: `Result row cap. Default ${agg.defaultLimit}; values above ${agg.maxLimit ?? 1e3} are capped silently. Invalid values fall back to the default.`
726
+ });
727
+ return params;
728
+ }
691
729
  function setNestedSchemaProp(target, path, leaf) {
692
730
  if (path.length === 1) {
693
731
  target[path[0]] = leaf;
@@ -837,7 +875,9 @@ function appendCustomRoutePaths(paths, resource, basePath, additionalSecurity) {
837
875
  }
838
876
  if (routeSchema?.response) {
839
877
  const responseSchemas = routeSchema.response;
840
- for (const [statusCode, schema] of Object.entries(responseSchemas)) extras.responses[statusCode] = {
878
+ const responses = extras.responses ?? {};
879
+ extras.responses = responses;
880
+ for (const [statusCode, schema] of Object.entries(responseSchemas)) responses[statusCode] = {
841
881
  description: schema.description || `Response ${statusCode}`,
842
882
  content: { "application/json": { schema } }
843
883
  };
@@ -921,14 +961,12 @@ function buildOpenApiSpec(resources, options = {}, externalPaths) {
921
961
  for (const tag of ext.tags) if (!tags.find((t) => t.name === tag.name)) tags.push(tag);
922
962
  }
923
963
  }
924
- const externalSecuritySchemes = externalPaths?.reduce((acc, ext) => ({
925
- ...acc,
926
- ...ext.securitySchemes
927
- }), {}) ?? {};
928
- const externalSchemas = externalPaths?.reduce((acc, ext) => ({
929
- ...acc,
930
- ...ext.schemas
931
- }), {}) ?? {};
964
+ const externalSecuritySchemes = {};
965
+ const externalSchemas = {};
966
+ for (const ext of externalPaths ?? []) {
967
+ if (ext.securitySchemes) Object.assign(externalSecuritySchemes, ext.securitySchemes);
968
+ if (ext.schemas) Object.assign(externalSchemas, ext.schemas);
969
+ }
932
970
  return {
933
971
  openapi: "3.0.3",
934
972
  info: {
@@ -1,3 +1,3 @@
1
- import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-COhcH3fk.mjs";
2
- import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-BTqLEvhu.mjs";
1
+ import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-DqZLp5d6.mjs";
2
+ import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-3L-adFJO.mjs";
3
3
  export { ConnectEventsOptions, DynamicPermissionMatrix, DynamicPermissionMatrixConfig, FieldPermission, FieldPermissionMap, FieldPermissionType, PermissionCheck, PermissionContext, PermissionEventBus, PermissionResult, RequireAgentScopeOptions, RequireMandateOptions, RoleHierarchy, UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -1,3 +1,3 @@
1
- import { A as roles, C as allowPublic, D as requireAuth, E as not, F as applyPermissionResult, L as normalizePermissionResult, M as requireAgentScope, N as requireDPoP, O as requireOwnership, P as requireMandate, S as allOf, T as denyAll, _ as requireOrgMembership, a as presets_exports, b as requireServiceScope, c as readOnly, d as applyFieldWritePermissions, f as fields, g as requireOrgInScope, h as createOrgPermissions, i as ownerWithAdminBypass, j as when, k as requireRoles, l as createRoleHierarchy, m as createDynamicPermissionMatrix, n as authenticated, o as publicRead, p as resolveEffectiveRoles, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as applyFieldReadPermissions, v as requireOrgRole, w as anyOf, x as requireTeamMembership, y as requireScopeContext } from "../permissions-ohQyv50e.mjs";
1
+ import { A as roles, C as allowPublic, D as requireAuth, E as not, F as applyPermissionResult, L as normalizePermissionResult, M as requireAgentScope, N as requireDPoP, O as requireOwnership, P as requireMandate, S as allOf, T as denyAll, _ as requireOrgMembership, a as presets_exports, b as requireServiceScope, c as readOnly, d as applyFieldWritePermissions, f as fields, g as requireOrgInScope, h as createOrgPermissions, i as ownerWithAdminBypass, j as when, k as requireRoles, l as createRoleHierarchy, m as createDynamicPermissionMatrix, n as authenticated, o as publicRead, p as resolveEffectiveRoles, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as applyFieldReadPermissions, v as requireOrgRole, w as anyOf, x as requireTeamMembership, y as requireScopeContext } from "../permissions-CTxMrreC.mjs";
2
2
  import { n as normalizeRoles, t as getUserRoles } from "../types-D57iXYb8.mjs";
3
3
  export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { S as isService, _ as hasOrgAccess, a as getDPoPJkt, b as isMember, d as getScopeContext, f as getScopeContextMap, h as getUserId, m as getTeamId, o as getMandate, p as getServiceScopes, u as getRequestScope, x as isOrgInScope, y as isElevated } from "./types-C_s5moIu.mjs";
2
+ import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-Bi0r0vjG.mjs";
3
3
  import { t as getUserRoles } from "./types-D57iXYb8.mjs";
4
- import { t as MemoryCacheStore } from "./memory-UBydS5ku.mjs";
4
+ import { t as MemoryCacheStore } from "./memory-QOLe11D5.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  //#region src/permissions/applyPermissionResult.ts
7
7
  /**
@@ -53,6 +53,7 @@ async function executePipeline(steps, ctx, handler, operation) {
53
53
  let chain = () => handler(currentCtx);
54
54
  for (let i = interceptors.length - 1; i >= 0; i--) {
55
55
  const interceptor = interceptors[i];
56
+ if (!interceptor) continue;
56
57
  const next = chain;
57
58
  chain = () => interceptor.handler(currentCtx, next);
58
59
  }
@@ -1,4 +1,4 @@
1
- import { At as Guard, Ft as PipelineContext, It as PipelineStep, Lt as Transform, Mt as NextFunction, Nt as OperationFilter, Pt as PipelineConfig, _t as IControllerResponse, jt as Interceptor } from "../index-BswOSJCE.mjs";
1
+ import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-DBDgy6Hw.mjs";
2
2
 
3
3
  //#region src/pipeline/guard.d.ts
4
4
  interface GuardOptions {
@@ -1,4 +1,4 @@
1
- import { n as pipe, t as executePipeline } from "../pipe-Zr0KXjQe.mjs";
1
+ import { n as pipe, t as executePipeline } from "../pipe-DiCyvyPN.mjs";
2
2
  //#region src/pipeline/guard.ts
3
3
  /**
4
4
  * Create a named guard.
@@ -1,66 +1,12 @@
1
- import { On as HookSystem, Q as MiddlewareConfig, Wt as AnyRecord, ft as RouteSchemaOptions, lt as RouteDefinition, tt as PresetHook, z as ResourceRegistry } from "../index-BswOSJCE.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
3
- import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-hmkPcDlX.mjs";
4
- import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-DFr45ZG4.mjs";
5
- import { t as TracingOptions } from "../tracing-QJVprktp.mjs";
1
+ import { Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook } from "../index-DBDgy6Hw.mjs";
2
+ import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-CJm0vh-l.mjs";
3
+ import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-5H4930tj.mjs";
4
+ import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-DuqTqeqn.mjs";
5
+ import { t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
6
6
  import { PaginatedResult } from "@classytic/repo-core/pagination";
7
7
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
8
8
  import * as _$node_stream0 from "node:stream";
9
9
 
10
- //#region src/core/arcCorePlugin.d.ts
11
- interface ArcCorePluginOptions {
12
- /** Enable event emission for CRUD operations (requires eventPlugin) */
13
- emitEvents?: boolean;
14
- /** Hook system instance (for testing/custom setup) */
15
- hookSystem?: HookSystem;
16
- /** Resource registry instance (for testing/custom setup) */
17
- registry?: ResourceRegistry;
18
- }
19
- interface PluginMeta {
20
- name: string;
21
- version?: string;
22
- options?: Record<string, unknown>;
23
- registeredAt: string;
24
- }
25
- interface ArcCore {
26
- /** Instance-scoped hook system */
27
- hooks: HookSystem;
28
- /** Instance-scoped resource registry */
29
- registry: ResourceRegistry;
30
- /** Whether event emission is enabled */
31
- emitEvents: boolean;
32
- /** External OpenAPI paths contributed by auth adapters or third-party integrations */
33
- externalOpenApiPaths: ExternalOpenApiPaths[];
34
- /** Registered plugins for introspection */
35
- plugins: Map<string, PluginMeta>;
36
- }
37
- declare module "fastify" {
38
- interface FastifyInstance {
39
- /**
40
- * Arc core decorator. Optional because:
41
- *
42
- * 1. Consumers who import any `@classytic/arc/*` subpath get this module
43
- * augmentation merged into every `FastifyInstance` they see — even
44
- * instances on apps that never register {@link arcCorePlugin}. Making
45
- * it required would force those apps to assert or guard every
46
- * property access.
47
- * 2. Hosts that extend `FastifyInstance` to narrow `arc` to their own
48
- * type (`interface X extends FastifyInstance { arc?: MyArc }`) were
49
- * previously blocked because non-optional `arc: ArcCore` conflicts
50
- * with the re-declaration. An optional field lets hosts re-declare
51
- * with a compatible subtype without fighting TS.
52
- *
53
- * Inside Arc's own code, any call site that runs *after* `arcCorePlugin`
54
- * has registered treats this as non-null — see the call sites in
55
- * `factory/registerAuth.ts` and `factory/createApp.ts` which assert
56
- * with `fastify.arc!` or narrow explicitly.
57
- */
58
- arc?: ArcCore;
59
- }
60
- }
61
- declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
62
- declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
63
- //#endregion
64
10
  //#region src/plugins/createPlugin.d.ts
65
11
  interface PluginResourceResult {
66
12
  /** Additional routes to add to the resource (v2.8 `RouteDefinition` shape) */
@@ -1,15 +1,15 @@
1
- import { p as MUTATION_OPERATIONS } from "../constants-Cxde4rpC.mjs";
2
- import { c as getOrgId } from "../types-C_s5moIu.mjs";
1
+ import { p as MUTATION_OPERATIONS } from "../constants-TrJVIJl0.mjs";
2
+ import { c as getOrgId } from "../types-Bi0r0vjG.mjs";
3
3
  import { t as requestContext } from "../requestContext-SSaaTgW8.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-BzkXkvVv.mjs";
5
- import { t as HookSystem } from "../HookSystem-Iiebom92.mjs";
6
- import { t as ResourceRegistry } from "../ResourceRegistry-CTERg_2x.mjs";
7
- import { n as caching_default, t as cachingPlugin } from "../caching-SM8gghN6.mjs";
8
- import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-Bk-AGhkU.mjs";
9
- import { n as metrics_default, t as metricsPlugin } from "../metrics-Qnvwc-LQ.mjs";
10
- import { t as replyHelpersPlugin } from "../replyHelpers-CK-FNO8E.mjs";
11
- import { n as sse_default, t as ssePlugin } from "../sse-Bz-5ZeTt.mjs";
12
- import { n as versioning_default, t as versioningPlugin } from "../versioning-BUrT5aP4.mjs";
5
+ import { t as HookSystem } from "../HookSystem-Cmf7-Etp.mjs";
6
+ import { t as ResourceRegistry } from "../ResourceRegistry-CzSzVK1m.mjs";
7
+ import { n as caching_default, t as cachingPlugin } from "../caching-8MfrrUYU.mjs";
8
+ import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-DNPzqaTT.mjs";
9
+ import { n as metrics_default, t as metricsPlugin } from "../metrics-B-PU4-Yu.mjs";
10
+ import { t as replyHelpersPlugin } from "../replyHelpers-boS5Rx26.mjs";
11
+ import { n as sse_default, t as ssePlugin } from "../sse-jh5OqFjR.mjs";
12
+ import { n as versioning_default, t as versioningPlugin } from "../versioning-CuIwUQZ2.mjs";
13
13
  import { randomUUID } from "node:crypto";
14
14
  import fp from "fastify-plugin";
15
15
  //#region src/core/arcCorePlugin.ts
@@ -48,7 +48,7 @@ var LRUCache = class {
48
48
  let count = 0;
49
49
  for (const key of this.cache.keys()) {
50
50
  const colonIdx = key.indexOf(":");
51
- if ((colonIdx >= 0 ? key.slice(colonIdx + 1) : key).split("?")[0].startsWith(prefix)) {
51
+ if (((colonIdx >= 0 ? key.slice(colonIdx + 1) : key).split("?")[0] ?? "").startsWith(prefix)) {
52
52
  this.cache.delete(key);
53
53
  count++;
54
54
  }
@@ -60,7 +60,7 @@ var LRUCache = class {
60
60
  isPrefixLocked(key) {
61
61
  if (this.invalidatedPrefixes.size === 0) return false;
62
62
  const colonIdx = key.indexOf(":");
63
- const pathOnly = (colonIdx >= 0 ? key.slice(colonIdx + 1) : key).split("?")[0];
63
+ const pathOnly = (colonIdx >= 0 ? key.slice(colonIdx + 1) : key).split("?")[0] ?? "";
64
64
  const now = Date.now();
65
65
  for (const [prefix, expiresAt] of this.invalidatedPrefixes.entries()) if (now > expiresAt) this.invalidatedPrefixes.delete(prefix);
66
66
  else if (pathOnly.startsWith(prefix)) return true;
@@ -96,7 +96,7 @@ const responseCachePluginImpl = async (fastify, opts = {}) => {
96
96
  const invalidateMethods = new Set(invalidateOn.map((m) => m.toUpperCase()));
97
97
  /** Find TTL for a given URL path (seconds) */
98
98
  function getTTL(url) {
99
- const path = url.split("?")[0];
99
+ const path = url.split("?")[0] ?? url;
100
100
  for (const rule of rules) if (path.startsWith(rule.match)) return rule.ttl;
101
101
  return defaultTTL;
102
102
  }
@@ -123,7 +123,7 @@ const responseCachePluginImpl = async (fastify, opts = {}) => {
123
123
  if (!invalidateMethods.has(request.method.toUpperCase())) return;
124
124
  const statusCode = reply.statusCode;
125
125
  if (statusCode < 200 || statusCode >= 300) return;
126
- const path = request.url.split("?")[0];
126
+ const path = request.url.split("?")[0] ?? request.url;
127
127
  const segments = path.split("/").filter(Boolean);
128
128
  const lastSegment = segments[segments.length - 1];
129
129
  if (segments.length >= 2 && lastSegment != null && /^[0-9a-f]{8,}$|^\d+$/.test(lastSegment)) {
@@ -189,7 +189,7 @@ const responseCachePluginImpl = async (fastify, opts = {}) => {
189
189
  const parts = event.type.split(".");
190
190
  if (parts.length !== 2) return;
191
191
  const [resource, action] = parts;
192
- if (!resource || ![
192
+ if (!resource || !action || ![
193
193
  "created",
194
194
  "updated",
195
195
  "deleted"
@@ -1,2 +1,2 @@
1
- import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-QJVprktp.mjs";
1
+ import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
2
2
  export { type TracingOptions, createSpan, isTracingAvailable, traced, _default as tracingPlugin };
@@ -58,7 +58,7 @@ try {
58
58
  function createTracerProvider(options) {
59
59
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
60
60
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
61
- const resolvedVersion = serviceVersion ?? "2.15.4";
61
+ const resolvedVersion = serviceVersion ?? "2.16.1";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -58,7 +58,8 @@ function pluralize(word) {
58
58
  if (UNCOUNTABLES.has(lower)) return word;
59
59
  if (IRREGULARS[lower]) {
60
60
  const plural = IRREGULARS[lower];
61
- return word[0] === word[0]?.toUpperCase() ? plural.charAt(0).toUpperCase() + plural.slice(1) : plural;
61
+ const firstChar = word.charAt(0);
62
+ return firstChar === firstChar.toUpperCase() ? plural.charAt(0).toUpperCase() + plural.slice(1) : plural;
62
63
  }
63
64
  if (lower.endsWith("fe")) return `${word.slice(0, -2)}ves`;
64
65
  if (lower.endsWith("f") && !lower.endsWith("ff") && !lower.endsWith("roof") && !lower.endsWith("chief") && !lower.endsWith("belief")) return `${word.slice(0, -1)}ves`;
@@ -0,0 +1,45 @@
1
+ //#region src/testing/preloadResources.ts
2
+ /**
3
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
4
+ *
5
+ * Accepts either:
6
+ * - `{ import: 'default' }` form: values are the resource directly
7
+ * - default form: values are the full module — picks first export with `toPlugin()`
8
+ *
9
+ * Throws if any module doesn't yield a valid `ResourceLike`.
10
+ */
11
+ function preloadResources(globResult) {
12
+ const resources = [];
13
+ for (const [path, value] of Object.entries(globResult)) {
14
+ const resource = pickResource(value);
15
+ if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
16
+ resources.push(resource);
17
+ }
18
+ return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
19
+ }
20
+ /**
21
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
22
+ *
23
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
24
+ * cannot be evaluated at import time of the preload file.
25
+ */
26
+ async function preloadResourcesAsync(globResult) {
27
+ return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
28
+ const resource = pickResource(await loader());
29
+ if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
30
+ return resource;
31
+ }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
32
+ }
33
+ function pickResource(value) {
34
+ if (!value || typeof value !== "object") return void 0;
35
+ if (typeof value.toPlugin === "function") return value;
36
+ const mod = value;
37
+ const candidates = [
38
+ mod.default,
39
+ mod.resource,
40
+ ...Object.values(mod)
41
+ ];
42
+ for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
43
+ }
44
+ //#endregion
45
+ export { preloadResourcesAsync as n, preloadResources as t };
@@ -1,7 +1,7 @@
1
- import { nt as PresetResult } from "../index-BswOSJCE.mjs";
2
- import { i as RequestScope } from "../types-CTYvcwHe.mjs";
3
- import { c as PermissionCheck } from "../fields-COhcH3fk.mjs";
4
- import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-Dfzt4VTl.mjs";
1
+ import { rt as PresetResult } from "../index-DBDgy6Hw.mjs";
2
+ import { i as RequestScope } from "../types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
4
+ import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-HcAgP5Ni.mjs";
5
5
 
6
6
  //#region src/presets/filesUpload.d.ts
7
7
  interface FilesUploadPresetRoutes {
@@ -1,6 +1,6 @@
1
1
  import { i as NotFoundError, u as ValidationError } from "../errors-j4aJm1Wg.mjs";
2
- import { c as getOrgId, h as getUserId } from "../types-C_s5moIu.mjs";
3
- import { C as allowPublic, D as requireAuth } from "../permissions-ohQyv50e.mjs";
2
+ import { c as getOrgId, g as getUserId } from "../types-Bi0r0vjG.mjs";
3
+ import { C as allowPublic, D as requireAuth } from "../permissions-CTxMrreC.mjs";
4
4
  import { t as multipartBody } from "../multipartBody-BOvVSVCD.mjs";
5
5
  //#region src/presets/filesUpload.ts
6
6
  const DEFAULT_FIELD_NAME = "file";
@@ -1,4 +1,4 @@
1
- import { Wt as AnyRecord, _t as IControllerResponse, at as ResourceConfig, d as PaginationResult, nt as PresetResult, vt as IRequestContext } from "../index-BswOSJCE.mjs";
1
+ import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-DBDgy6Hw.mjs";
2
2
  import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
3
3
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
4
4
  import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
@@ -1,5 +1,5 @@
1
1
  import { multiTenantPreset } from "./multiTenant.mjs";
2
- 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-BbkjdPeH.mjs";
2
+ 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-C9BE6WaZ.mjs";
3
3
  import { filesUploadPreset } from "./filesUpload.mjs";
4
4
  import { searchPreset } from "./search.mjs";
5
5
  export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,4 +1,4 @@
1
- import { J as CrudRouteKey, nt as PresetResult } from "../index-BswOSJCE.mjs";
1
+ import { J as CrudRouteKey, rt as PresetResult } from "../index-DBDgy6Hw.mjs";
2
2
 
3
3
  //#region src/presets/multiTenant.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import "../constants-Cxde4rpC.mjs";
2
- import { _ as hasOrgAccess, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, y as isElevated } from "../types-C_s5moIu.mjs";
1
+ import "../constants-TrJVIJl0.mjs";
2
+ import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, v as hasOrgAccess } from "../types-Bi0r0vjG.mjs";
3
3
  //#region src/presets/multiTenant.ts
4
4
  /**
5
5
  * Resolve a single TenantFieldSpec against the current scope.
@@ -186,7 +186,8 @@ function multiTenantPreset(options = {}) {
186
186
  create: [tenantInjection],
187
187
  update: [getFilter("update"), tenantInjection],
188
188
  delete: [getFilter("delete")],
189
- aggregations: [strictTenantFilter]
189
+ aggregations: [strictTenantFilter],
190
+ tenantScope: [strictTenantFilter, tenantInjection]
190
191
  }
191
192
  };
192
193
  }
@@ -1,5 +1,5 @@
1
- import { lt as RouteDefinition, nt as PresetResult, pt as ControllerHandler, ut as RouteMcpConfig } from "../index-BswOSJCE.mjs";
2
- import { c as PermissionCheck } from "../fields-COhcH3fk.mjs";
1
+ import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-DBDgy6Hw.mjs";
2
+ import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
3
3
 
4
4
  //#region src/presets/search.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { C as allowPublic, D as requireAuth } from "../permissions-ohQyv50e.mjs";
1
+ import { C as allowPublic, D as requireAuth } from "../permissions-CTxMrreC.mjs";
2
2
  //#region src/presets/search.ts
3
3
  const BUILTINS = [
4
4
  {
@@ -1,5 +1,5 @@
1
- import { n as PUBLIC_SCOPE, y as isElevated } from "./types-C_s5moIu.mjs";
2
- import { C as allowPublic, D as requireAuth, k as requireRoles } from "./permissions-ohQyv50e.mjs";
1
+ import { b as isElevated, n as PUBLIC_SCOPE } from "./types-Bi0r0vjG.mjs";
2
+ import { C as allowPublic, D as requireAuth, k as requireRoles } from "./permissions-CTxMrreC.mjs";
3
3
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
4
4
  //#region src/presets/ownedByUser.ts
5
5
  /**
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { t as MemoryCacheStore } from "./memory-UBydS5ku.mjs";
2
+ import { t as MemoryCacheStore } from "./memory-QOLe11D5.mjs";
3
3
  import { t as hasEvents } from "./typeGuards-BzkXkvVv.mjs";
4
- import { i as versionKey, r as tagVersionKey } from "./keys-CGcCbNyu.mjs";
4
+ import { i as versionKey, r as tagVersionKey } from "./keys-DopsCuyQ.mjs";
5
5
  import fp from "fastify-plugin";
6
6
  //#region src/cache/QueryCache.ts
7
7
  var QueryCache = class {