@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
@@ -1,6 +1,6 @@
1
- import { C as requireClientId, E as requireUserId, S as isService, T as requireTeamId, _ as hasOrgAccess, a as getDPoPJkt, b as isMember, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserRoles, h as getUserId, 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 isAuthenticated, w as requireOrgId, x as isOrgInScope, y as isElevated } from "../types-C_s5moIu.mjs";
1
+ import { C as isService, D as requireUserId, E as requireTeamId, 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-Bi0r0vjG.mjs";
2
2
  import { n as normalizeRoles } from "../types-D57iXYb8.mjs";
3
- import { n as elevation_default, t as elevationPlugin } from "../elevation-DgoeTyfX.mjs";
3
+ import { n as elevation_default, t as elevationPlugin } from "../elevation-Dci0AYLT.mjs";
4
4
  //#region src/scope/rateLimitKey.ts
5
5
  function createTenantKeyGenerator(opts) {
6
6
  if (opts?.strategy) return opts.strategy;
@@ -76,4 +76,4 @@ function resolveOrgFromHeader(options) {
76
76
  };
77
77
  }
78
78
  //#endregion
79
- 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, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader };
79
+ 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 };
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-C_s5moIu.mjs";
3
+ import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-Bi0r0vjG.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/plugins/sse.ts
6
6
  var sse_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
- import { V as ResourceDefinition, Wt as AnyRecord } from "../index-BswOSJCE.mjs";
2
- import { d as ResourceLike, r as CreateAppOptions } from "../types-DrBaUwyV.mjs";
1
+ import { Kt as AnyRecord, V as ResourceDefinition } from "../index-DBDgy6Hw.mjs";
2
+ import { d as preloadResourcesAsync, r as CreateAppOptions, u as preloadResources } from "../types-Dw--SyTq.mjs";
3
3
  import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
4
4
  import { FastifyInstance, FastifyServerOptions } from "fastify";
5
5
  import { Mock } from "vitest";
@@ -464,29 +464,6 @@ declare function createTestTimer(): {
464
464
  reset: () => void;
465
465
  };
466
466
  //#endregion
467
- //#region src/testing/preloadResources.d.ts
468
- /** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
469
- type EagerGlobResult = Record<string, unknown>;
470
- /** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
471
- type LazyGlobResult = Record<string, () => Promise<unknown>>;
472
- /**
473
- * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
474
- *
475
- * Accepts either:
476
- * - `{ import: 'default' }` form: values are the resource directly
477
- * - default form: values are the full module — picks first export with `toPlugin()`
478
- *
479
- * Throws if any module doesn't yield a valid `ResourceLike`.
480
- */
481
- declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
482
- /**
483
- * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
484
- *
485
- * Use this when resources depend on prior bootstrap (e.g., engine init) and
486
- * cannot be evaluated at import time of the preload file.
487
- */
488
- declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
489
- //#endregion
490
467
  //#region src/testing/testApp.d.ts
491
468
  type DbMode = "in-memory" | {
492
469
  uri: string;
@@ -1,4 +1,5 @@
1
- import { t as CRUD_OPERATIONS } from "../constants-Cxde4rpC.mjs";
1
+ import { t as CRUD_OPERATIONS } from "../constants-TrJVIJl0.mjs";
2
+ import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-BOXzPhKU.mjs";
2
3
  import { runStorageContract } from "./storageContract.mjs";
3
4
  import Fastify from "fastify";
4
5
  import { afterAll, describe, expect, it, vi } from "vitest";
@@ -415,7 +416,10 @@ async function setupBetterAuthTestApp(input) {
415
416
  user: u
416
417
  };
417
418
  }
418
- const creatorRec = signedUp[creators[0].key];
419
+ const creator = creators[0];
420
+ if (!creator) throw new Error("[arc-testing] setupBetterAuthTestApp: no `isCreator: true` user supplied");
421
+ const creatorRec = signedUp[creator.key];
422
+ if (!creatorRec) throw new Error(`[arc-testing] setupBetterAuthTestApp: creator '${creator.key}' missing from signed-up roster`);
419
423
  const orgRes = await helpers.createOrg(app, creatorRec.token, org);
420
424
  if (orgRes.statusCode >= 400 || !orgRes.orgId) throw new Error(`[arc-testing] setupBetterAuthTestApp: createOrg failed. statusCode=${orgRes.statusCode}, orgId=${orgRes.orgId ? "ok" : "missing"}, body=${JSON.stringify(orgRes.body).slice(0, 300)}`);
421
425
  const orgId = orgRes.orgId;
@@ -423,6 +427,7 @@ async function setupBetterAuthTestApp(input) {
423
427
  if (u.isCreator === true) continue;
424
428
  if (!addMember) continue;
425
429
  const rec = signedUp[u.key];
430
+ if (!rec) continue;
426
431
  const res = await addMember({
427
432
  app,
428
433
  creatorToken: creatorRec.token,
@@ -486,6 +491,7 @@ function createTestFixtures() {
486
491
  async clear() {
487
492
  for (let i = tracked.length - 1; i >= 0; i--) {
488
493
  const entry = tracked[i];
494
+ if (!entry) continue;
489
495
  if (entry.destroy) await entry.destroy(entry.record).catch(() => {});
490
496
  }
491
497
  tracked.length = 0;
@@ -541,7 +547,9 @@ var HttpTestHarness = class {
541
547
  this.runValidation();
542
548
  }
543
549
  runCrud() {
544
- const { resource, enabledRoutes, updateMethods } = this;
550
+ const resource = this.resource;
551
+ const enabledRoutes = this.enabledRoutes;
552
+ const updateMethods = this.updateMethods;
545
553
  let createdId = null;
546
554
  describe(`${resource.displayName} HTTP CRUD`, () => {
547
555
  afterAll(async () => {
@@ -663,7 +671,9 @@ var HttpTestHarness = class {
663
671
  });
664
672
  }
665
673
  runPermissions() {
666
- const { resource, enabledRoutes, updateMethods } = this;
674
+ const resource = this.resource;
675
+ const enabledRoutes = this.enabledRoutes;
676
+ const updateMethods = this.updateMethods;
667
677
  const protectedOps = {
668
678
  list: opRequiresAuth(resource, "list"),
669
679
  get: opRequiresAuth(resource, "get"),
@@ -737,8 +747,8 @@ var HttpTestHarness = class {
737
747
  });
738
748
  }
739
749
  runValidation() {
740
- const { resource, enabledRoutes } = this;
741
- if (!enabledRoutes.has("create")) return;
750
+ const resource = this.resource;
751
+ if (!this.enabledRoutes.has("create")) return;
742
752
  describe(`${resource.displayName} HTTP Validation`, () => {
743
753
  it("POST with invalid payload should be rejected", async () => {
744
754
  const { app, fixtures } = this.getOptions();
@@ -961,50 +971,6 @@ function createTestTimer() {
961
971
  };
962
972
  }
963
973
  //#endregion
964
- //#region src/testing/preloadResources.ts
965
- /**
966
- * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
967
- *
968
- * Accepts either:
969
- * - `{ import: 'default' }` form: values are the resource directly
970
- * - default form: values are the full module — picks first export with `toPlugin()`
971
- *
972
- * Throws if any module doesn't yield a valid `ResourceLike`.
973
- */
974
- function preloadResources(globResult) {
975
- const resources = [];
976
- for (const [path, value] of Object.entries(globResult)) {
977
- const resource = pickResource(value);
978
- if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
979
- resources.push(resource);
980
- }
981
- return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
982
- }
983
- /**
984
- * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
985
- *
986
- * Use this when resources depend on prior bootstrap (e.g., engine init) and
987
- * cannot be evaluated at import time of the preload file.
988
- */
989
- async function preloadResourcesAsync(globResult) {
990
- return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
991
- const resource = pickResource(await loader());
992
- if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
993
- return resource;
994
- }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
995
- }
996
- function pickResource(value) {
997
- if (!value || typeof value !== "object") return void 0;
998
- if (typeof value.toPlugin === "function") return value;
999
- const mod = value;
1000
- const candidates = [
1001
- mod.default,
1002
- mod.resource,
1003
- ...Object.values(mod)
1004
- ];
1005
- for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
1006
- }
1007
- //#endregion
1008
974
  //#region src/testing/testApp.ts
1009
975
  /**
1010
976
  * createTestApp — test app factory for arc
@@ -1070,7 +1036,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1070
1036
  };
1071
1037
  }
1072
1038
  async function createTestApp(options = {}) {
1073
- const { createApp } = await import("../createApp-PFegs47-.mjs").then((n) => n.r);
1039
+ const { createApp } = await import("../createApp-DdPBtmzp.mjs").then((n) => n.r);
1074
1040
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1075
1041
  let dbHandle;
1076
1042
  let dbUri;
@@ -1,4 +1,4 @@
1
- import { t as Storage } from "../storage-Dfzt4VTl.mjs";
1
+ import { t as Storage } from "../storage-HcAgP5Ni.mjs";
2
2
 
3
3
  //#region src/testing/storageContract.d.ts
4
4
  interface StorageContractSetupResult {
@@ -1,5 +1,5 @@
1
- import { $ as OpenApiSchemas, A as RequestIdOptions, Bt as Authenticator, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, G as ActionsMap, Gt as ApiResponse, H as ActionDefinition, Ht as JwtContext, I as MiddlewareHandler, J as CrudRouteKey, Jt as ObjectId, K as ArcFieldRule, Kt as ArcRequest, L as RequestWithExtras, M as EventsDecorator, N as FastifyRequestExtras, O as HealthOptions, P as FastifyWithAuth, Q as MiddlewareConfig, Rt as AuthHelpers, S as QueryParserInterface, T as CrudRouterOptions, U as ActionEntry, Ut as TokenPair, Vt as AuthenticatorContext, W as ActionHandlerFn, Wt as AnyRecord, X as EventDefinition, Xt as UserOrganization, Y as CrudSchemas, Yt as UserLike, Z as FieldRule, _ as ControllerQueryOptions, _t as IControllerResponse, a as InferAdapterDoc, at as ResourceConfig, b as ParsedQuery, c as TypedController, ct as ResourcePermissions, d as PaginationResult, dt as RouteMethod, et as PresetFunction, f as IntrospectionData, ft as RouteSchemaOptions, g as ArcInternalMetadata, gt as IController, h as ResourceMetadata, ht as FastifyHandler, i as ValidationResult, it as ResourceCacheConfig, j as ArcDecorator, k as IntrospectionPluginOptions, l as TypedRepository, lt as RouteDefinition, m as RegistryStats, mt as ControllerLike, n as ConfigError, nt as PresetResult, o as InferDocType, on as BaseControllerOptions, ot as ResourceHookContext, p as RegistryEntry, pt as ControllerHandler, q as CrudController, qt as JWTPayload, r as ValidateOptions, rt as RateLimitConfig, s as InferResourceDoc, st as ResourceHooks, t as RouteHandlerMethod, tt as PresetHook, u as TypedResourceConfig, ut as RouteMcpConfig, v as LookupOption, vt as IRequestContext, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, yt as RouteHandler, zt as AuthPluginOptions } from "../index-BswOSJCE.mjs";
2
- import { i as RequestScope } from "../types-CTYvcwHe.mjs";
3
- import { c as PermissionCheck, d as UserBase, l as PermissionContext, u as PermissionResult } from "../fields-COhcH3fk.mjs";
4
- import { n as ElevationOptions, t as ElevationEvent } from "../elevation-BXOWoGCF.mjs";
5
- export { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ApiResponse, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
1
+ import { $ as OnTenantDeleteConfig, A as RequestIdOptions, Bt as AuthHelpers, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, G as ActionsMap, Gt as TokenPair, H as ActionDefinition, Ht as Authenticator, I as MiddlewareHandler, J as CrudRouteKey, Jt as ArcRequest, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, M as EventsDecorator, N as FastifyRequestExtras, O as HealthOptions, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, T as CrudRouterOptions, U as ActionEntry, Ut as AuthenticatorContext, Vt as AuthPluginOptions, W as ActionHandlerFn, Wt as JwtContext, X as EventDefinition, Xt as ObjectId, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, Zt as UserLike, _ as ControllerQueryOptions, _t as FastifyHandler, a as InferAdapterDoc, at as ResolvedTenantPurge, b as ParsedQuery, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, ct as ResourceHookContext, d as PaginationResult, dt as RouteDefinition, et as OpenApiSchemas, f as IntrospectionData, ft as RouteMcpConfig, g as ArcInternalMetadata, gt as ControllerLike, h as ResourceMetadata, ht as ControllerHandler, i as ValidationResult, it as RateLimitConfig, j as ArcDecorator, k as IntrospectionPluginOptions, l as TypedRepository, lt as ResourceHooks, m as RegistryStats, mt as RouteSchemaOptions, n as ConfigError, nt as PresetHook, o as InferDocType, ot as ResourceCacheConfig, p as RegistryEntry, pt as RouteMethod, q as CrudController, qt as ApiResponse, r as ValidateOptions, rt as PresetResult, s as InferResourceDoc, st as ResourceConfig, t as RouteHandlerMethod, tt as PresetFunction, u as TypedResourceConfig, ut as ResourcePermissions, v as LookupOption, vt as IController, w as ServiceContext, x as PopulateOption, xt as RouteHandler, y as OwnershipCheck, yt as IControllerResponse } from "../index-DBDgy6Hw.mjs";
2
+ import { i as RequestScope } from "../types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck, d as UserBase, l as PermissionContext, u as PermissionResult } from "../fields-DqZLp5d6.mjs";
4
+ import { n as ElevationOptions, t as ElevationEvent } from "../elevation-DvwtjDwu.mjs";
5
+ export { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ApiResponse, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
@@ -1,2 +1,2 @@
1
- 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 { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-HcAgP5Ni.mjs";
2
2
  export { Storage, StorageContext, StorageFile, StorageReadRange, StorageReadResult, StorageUploadInput };
@@ -1,4 +1,4 @@
1
- import { V as ResourceDefinition } from "./index-BswOSJCE.mjs";
1
+ import { V as ResourceDefinition } from "./index-DBDgy6Hw.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/integrations/mcp/types.d.ts
@@ -13,10 +13,30 @@ interface ToolAnnotations {
13
13
  /** Tool interacts with external systems beyond this server */
14
14
  openWorldHint?: boolean;
15
15
  }
16
- /** Context passed to tool handlers at invocation time */
17
- interface ToolContext {
18
- /** Session identity — null in no-auth mode */
19
- session: McpAuthResult | null;
16
+ /**
17
+ * Context passed to tool handlers at invocation time.
18
+ *
19
+ * The `TSession` parameter widens `session` to a host-specific shape — pass
20
+ * an interface that extends `McpAuthResult` (or omits it entirely for fully
21
+ * custom auth resolvers) to drop the `as any` casts at call sites:
22
+ *
23
+ * ```ts
24
+ * interface MySession extends McpAuthResult {
25
+ * userId: string; // required, not optional
26
+ * organizationId: string;
27
+ * tenantPlan: 'free' | 'pro';
28
+ * }
29
+ *
30
+ * defineTool<MySession>('upgrade', {
31
+ * handler: async (_input, ctx) => {
32
+ * ctx.session?.tenantPlan; // typed, no cast
33
+ * },
34
+ * });
35
+ * ```
36
+ */
37
+ interface ToolContext<TSession = McpAuthResult> {
38
+ /** Session identity — `null` in no-auth mode */
39
+ session: TSession | null;
20
40
  /** Log to MCP client (best-effort, non-blocking) */
21
41
  log: (level: "info" | "warning" | "error" | "debug", message: string) => Promise<void>;
22
42
  /** Raw MCP SDK extra context (for advanced use) */
@@ -59,8 +79,11 @@ interface CallToolResult {
59
79
  *
60
80
  * `inputSchema` is a flat Zod shape `{ name: z.string(), age: z.number() }` —
61
81
  * the SDK wraps it in z.object() internally. Do NOT pass z.object() here.
82
+ *
83
+ * The `TSession` parameter lets hosts widen the session shape on the
84
+ * handler — same generic the `defineTool()` builder accepts.
62
85
  */
63
- interface ToolDefinition {
86
+ interface ToolDefinition<TSession = McpAuthResult> {
64
87
  name: string;
65
88
  description: string;
66
89
  title?: string;
@@ -69,7 +92,7 @@ interface ToolDefinition {
69
92
  /** Flat Zod shape for structured output validation */
70
93
  outputSchema?: Record<string, z.ZodTypeAny>;
71
94
  annotations?: ToolAnnotations;
72
- handler: (input: Record<string, unknown>, ctx: ToolContext) => Promise<CallToolResult>;
95
+ handler: (input: Record<string, unknown>, ctx: ToolContext<TSession>) => Promise<CallToolResult>;
73
96
  }
74
97
  /** Output of definePrompt() — plain data, not yet registered */
75
98
  interface PromptDefinition {
@@ -93,12 +116,57 @@ interface PromptResult {
93
116
  };
94
117
  }>;
95
118
  }
119
+ /**
120
+ * Metadata threaded into a function-form description override so authors
121
+ * can produce a context-aware description without re-deriving filter /
122
+ * sort / operator lists from the resource definition by hand.
123
+ *
124
+ * Equivalent to the inputs `defaultCrudDescription()` consumes — the
125
+ * default description is also passed (`defaultDescription`) so authors
126
+ * can prepend or append to it without duplicating Arc's auto-derived
127
+ * filterable-fields blurb.
128
+ */
129
+ interface CrudDescriptionMeta {
130
+ readonly operation: CrudOperation;
131
+ /** Resource display name (e.g. `Posts`) — Arc derives this from `defineResource`. */
132
+ readonly displayName: string;
133
+ readonly softDelete: boolean;
134
+ /** Pre-rendered default description Arc would have used. */
135
+ readonly defaultDescription: string;
136
+ readonly filterableFields?: readonly string[];
137
+ readonly allowedOperators?: readonly string[];
138
+ readonly sortableFields?: readonly string[];
139
+ }
140
+ /**
141
+ * Override shape for a single CRUD tool description.
142
+ *
143
+ * - `string` — replace the default outright.
144
+ * - Function — receives the auto-rendered default plus the same metadata
145
+ * `defaultCrudDescription()` consumes, returns a final string. Use this
146
+ * to append context (`'Note: prefer isActive: true'`) without losing
147
+ * Arc's auto-derived filterable-fields blurb.
148
+ */
149
+ type CrudDescriptionOverride = string | ((meta: CrudDescriptionMeta) => string);
96
150
  /** Per-resource MCP configuration overrides */
97
151
  interface McpResourceConfig {
98
152
  /** Which CRUD operations to expose (default: all enabled on the resource) */
99
153
  operations?: CrudOperation[];
100
- /** Override tool descriptions per operation */
101
- descriptions?: Partial<Record<CrudOperation, string>>;
154
+ /**
155
+ * Override tool descriptions per operation.
156
+ *
157
+ * Each entry is either a plain replacement string OR a function
158
+ * `(meta) => string` that receives the auto-rendered default plus the
159
+ * filterable / sortable metadata Arc would have used. Use the function
160
+ * form to extend the default without losing the auto-derived blurb:
161
+ *
162
+ * ```ts
163
+ * descriptions: {
164
+ * list: ({ defaultDescription }) =>
165
+ * `${defaultDescription} Sort by createdAt desc for newest first.`,
166
+ * }
167
+ * ```
168
+ */
169
+ descriptions?: Partial<Record<CrudOperation, CrudDescriptionOverride>>;
102
170
  /** Fields to hide from MCP tool schemas (beyond schemaOptions.hiddenFields) */
103
171
  hideFields?: string[];
104
172
  /** Per-operation tool name overrides: `{ get: 'get_job_by_id' }` */
@@ -168,9 +236,35 @@ interface McpPluginOptions {
168
236
  serverVersion?: string;
169
237
  /** Instructions for the LLM — guidance on tool usage, constraints */
170
238
  instructions?: string;
171
- /** Resources to exclude by name (ignored if `include` is set) */
239
+ /**
240
+ * Resources to exclude by name (ignored if `expose`/`include` is set).
241
+ *
242
+ * Drift-prone for LLM-facing surfaces: a new `defineResource()` is exposed
243
+ * by default and you have to remember to add it here. Prefer `expose` when
244
+ * the principle of least authority matters.
245
+ */
172
246
  exclude?: string[];
173
- /** Resources to include by name — only these get MCP tools. Takes priority over `exclude`. */
247
+ /**
248
+ * Default-deny allowlist — only resources whose names appear here are
249
+ * surfaced to MCP. New `defineResource()` calls are auto-denied (the
250
+ * inverse of `exclude`), matching the principle-of-least-authority that
251
+ * LLM-driven tool surfaces want.
252
+ *
253
+ * Mutually exclusive with `include` (a deprecated alias) — passing both
254
+ * throws at registration. Mutually exclusive with `exclude`: the deny
255
+ * default would render the exclude list redundant; passing both throws
256
+ * with a hint to drop `exclude`.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * await app.register(mcpPlugin, { resources, expose: ['post'] });
261
+ * ```
262
+ */
263
+ expose?: string[];
264
+ /**
265
+ * @deprecated Use `expose` — same default-deny semantics with a clearer
266
+ * verb. Kept for backwards compatibility with pre-2.15.5 hosts.
267
+ */
174
268
  include?: string[];
175
269
  /** Tool name prefix: 'crm' → 'crm_list_products' */
176
270
  toolNamePrefix?: string;
@@ -288,4 +382,4 @@ interface CreateMcpServerConfig {
288
382
  /** CRUD operation type */
289
383
  type CrudOperation = "list" | "get" | "create" | "update" | "delete";
290
384
  //#endregion
291
- export { McpAuthResolver as a, McpResourceConfig as c, SessionEntry as d, ToolAnnotations as f, CrudOperation as i, PromptDefinition as l, ToolDefinition as m, CallToolResult as n, McpAuthResult as o, ToolContext as p, CreateMcpServerConfig as r, McpPluginOptions as s, BetterAuthHandler as t, PromptResult as u };
385
+ export { CrudDescriptionOverride as a, McpAuthResult as c, PromptDefinition as d, PromptResult as f, ToolDefinition as g, ToolContext as h, CrudDescriptionMeta as i, McpPluginOptions as l, ToolAnnotations as m, CallToolResult as n, CrudOperation as o, SessionEntry as p, CreateMcpServerConfig as r, McpAuthResolver as s, BetterAuthHandler as t, McpResourceConfig as u };
@@ -47,6 +47,58 @@ function getOrgId(scope) {
47
47
  if (scope.kind === "elevated") return scope.organizationId;
48
48
  }
49
49
  /**
50
+ * Resolve the tenant (organization) id from an incoming Fastify request,
51
+ * walking the canonical sources in order: `request.scope` → `x-organization-id`
52
+ * header. Returns `{ organizationId, source }` so callers can log or branch
53
+ * on which path won.
54
+ *
55
+ * Use in custom handlers that bypass arc's auto-CRUD path (worker routes,
56
+ * batch importers, cross-engine subscribers) and therefore don't get the
57
+ * tenant injected by `BaseCrudController`. Bridges arc's HTTP shape to
58
+ * the mongokit/kit-side tenant context (e.g. `createTenantContext().run`).
59
+ *
60
+ * Header fallback is intentional: matches `resolveOrgFromHeader` semantics
61
+ * for hosts that select an org via header instead of session/scope. Don't
62
+ * reach for this when `getOrgId(scope)` alone is enough — for standard
63
+ * authenticated routes the scope is already authoritative.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * import { getTenantFromRequest } from '@classytic/arc/scope';
68
+ * import { tenantContext } from './lib/mongokit.js';
69
+ *
70
+ * fastify.post('/statements/batch', async (req, reply) => {
71
+ * const { organizationId } = getTenantFromRequest(req);
72
+ * if (!organizationId) {
73
+ * return reply.code(400).send({ error: 'organizationId required' });
74
+ * }
75
+ * await tenantContext.run({ tenantId: organizationId }, async () => {
76
+ * await statementRepository.create(req.body);
77
+ * });
78
+ * });
79
+ * ```
80
+ */
81
+ function getTenantFromRequest(req, options = {}) {
82
+ if (req.scope) {
83
+ const id = getOrgId(req.scope);
84
+ if (id) return {
85
+ organizationId: id,
86
+ source: "scope"
87
+ };
88
+ }
89
+ const headerName = (options.header ?? "x-organization-id").toLowerCase();
90
+ const raw = req.headers[headerName];
91
+ const headerValue = Array.isArray(raw) ? raw[0] : raw;
92
+ if (typeof headerValue === "string" && headerValue.length > 0) return {
93
+ organizationId: headerValue,
94
+ source: "header"
95
+ };
96
+ return {
97
+ organizationId: void 0,
98
+ source: "none"
99
+ };
100
+ }
101
+ /**
50
102
  * Get stable client identity from a service scope.
51
103
  *
52
104
  * Returns the `clientId` for machine-to-machine auth (API keys, service accounts),
@@ -342,4 +394,4 @@ const PUBLIC_SCOPE = Object.freeze({ kind: "public" });
342
394
  /** Default authenticated scope — used when user is logged in but no org */
343
395
  const AUTHENTICATED_SCOPE = Object.freeze({ kind: "authenticated" });
344
396
  //#endregion
345
- export { requireClientId as C, requireUserId as E, isService as S, requireTeamId as T, hasOrgAccess as _, getDPoPJkt as a, isMember as b, getOrgId as c, getScopeContext as d, getScopeContextMap as f, getUserRoles as g, getUserId as h, getClientId as i, getOrgRoles as l, getTeamId as m, PUBLIC_SCOPE as n, getMandate as o, getServiceScopes as p, getAncestorOrgIds as r, getOrgContext as s, AUTHENTICATED_SCOPE as t, getRequestScope as u, isAuthenticated as v, requireOrgId as w, isOrgInScope as x, isElevated as y };
397
+ export { isService as C, requireUserId as D, requireTeamId as E, isOrgInScope as S, requireOrgId as T, getUserRoles as _, getDPoPJkt as a, isElevated as b, getOrgId as c, getScopeContext as d, getScopeContextMap as f, getUserId as g, getTenantFromRequest as h, getClientId as i, getOrgRoles as l, getTeamId as m, PUBLIC_SCOPE as n, getMandate as o, getServiceScopes as p, getAncestorOrgIds as r, getOrgContext as s, AUTHENTICATED_SCOPE as t, getRequestScope as u, hasOrgAccess as v, requireClientId as w, isMember as x, isAuthenticated as y };
@@ -229,6 +229,47 @@ declare function hasOrgAccess(scope: RequestScope): boolean;
229
229
  declare function isAuthenticated(scope: RequestScope): boolean;
230
230
  /** Get organizationId from scope (member, service, or elevated — undefined otherwise) */
231
231
  declare function getOrgId(scope: RequestScope): string | undefined;
232
+ /**
233
+ * Resolve the tenant (organization) id from an incoming Fastify request,
234
+ * walking the canonical sources in order: `request.scope` → `x-organization-id`
235
+ * header. Returns `{ organizationId, source }` so callers can log or branch
236
+ * on which path won.
237
+ *
238
+ * Use in custom handlers that bypass arc's auto-CRUD path (worker routes,
239
+ * batch importers, cross-engine subscribers) and therefore don't get the
240
+ * tenant injected by `BaseCrudController`. Bridges arc's HTTP shape to
241
+ * the mongokit/kit-side tenant context (e.g. `createTenantContext().run`).
242
+ *
243
+ * Header fallback is intentional: matches `resolveOrgFromHeader` semantics
244
+ * for hosts that select an org via header instead of session/scope. Don't
245
+ * reach for this when `getOrgId(scope)` alone is enough — for standard
246
+ * authenticated routes the scope is already authoritative.
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * import { getTenantFromRequest } from '@classytic/arc/scope';
251
+ * import { tenantContext } from './lib/mongokit.js';
252
+ *
253
+ * fastify.post('/statements/batch', async (req, reply) => {
254
+ * const { organizationId } = getTenantFromRequest(req);
255
+ * if (!organizationId) {
256
+ * return reply.code(400).send({ error: 'organizationId required' });
257
+ * }
258
+ * await tenantContext.run({ tenantId: organizationId }, async () => {
259
+ * await statementRepository.create(req.body);
260
+ * });
261
+ * });
262
+ * ```
263
+ */
264
+ declare function getTenantFromRequest(req: {
265
+ scope?: RequestScope;
266
+ headers: Record<string, string | string[] | undefined>;
267
+ }, options?: {
268
+ header?: string;
269
+ }): {
270
+ organizationId: string | undefined;
271
+ source: "scope" | "header" | "none";
272
+ };
232
273
  /**
233
274
  * Get stable client identity from a service scope.
234
275
  *
@@ -477,4 +518,4 @@ declare const PUBLIC_SCOPE: Readonly<RequestScope>;
477
518
  /** Default authenticated scope — used when user is logged in but no org */
478
519
  declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
479
520
  //#endregion
480
- export { isOrgInScope as C, requireTeamId as D, requireOrgId as E, requireUserId as O, isMember as S, requireClientId as T, getUserId as _, getAncestorOrgIds as a, isAuthenticated as b, getMandate as c, getOrgRoles as d, getRequestScope as f, getTeamId as g, getServiceScopes as h, RequestScope as i, getOrgContext as l, getScopeContextMap as m, Mandate as n, getClientId as o, getScopeContext as p, PUBLIC_SCOPE as r, getDPoPJkt as s, AUTHENTICATED_SCOPE as t, getOrgId as u, getUserRoles as v, isService as w, isElevated as x, hasOrgAccess as y };
521
+ export { isMember as C, requireOrgId as D, requireClientId as E, requireTeamId as O, isElevated as S, isService as T, getTenantFromRequest as _, getAncestorOrgIds as a, hasOrgAccess as b, getMandate as c, getOrgRoles as d, getRequestScope as f, getTeamId as g, getServiceScopes as h, RequestScope as i, requireUserId as k, getOrgContext as l, getScopeContextMap as m, Mandate as n, getClientId as o, getScopeContext as p, PUBLIC_SCOPE as r, getDPoPJkt as s, AUTHENTICATED_SCOPE as t, getOrgId as u, getUserId as v, isOrgInScope as w, isAuthenticated as x, getUserRoles as y };
@@ -1,13 +1,13 @@
1
- import { r as CacheStore } from "./interface-beEtJyWM.mjs";
2
- import { Bt as Authenticator } from "./index-BswOSJCE.mjs";
3
- import { n as ElevationOptions } from "./elevation-BXOWoGCF.mjs";
4
- import { a as EventTransport } from "./EventTransport-CT_52aWU.mjs";
5
- import { t as ExternalOpenApiPaths } from "./externalPaths-BD5nw6St.mjs";
6
- import { r as QueryCachePluginOptions } from "./queryCachePlugin-CqMdLI2-.mjs";
7
- import { t as EventPluginOptions } from "./eventPlugin-qXpqTebY.mjs";
8
- import { _ as HealthOptions, f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-hmkPcDlX.mjs";
9
- import { t as ErrorHandlerOptions } from "./errorHandler-DFr45ZG4.mjs";
10
- import { r as IdempotencyStore } from "./interface-DfLGcus7.mjs";
1
+ import { r as CacheStore } from "./interface-BVkZsvEe.mjs";
2
+ import { Ht as Authenticator } from "./index-DBDgy6Hw.mjs";
3
+ import { n as ElevationOptions } from "./elevation-DvwtjDwu.mjs";
4
+ import { a as EventTransport } from "./EventTransport-B1GFiYUy.mjs";
5
+ import { t as ExternalOpenApiPaths } from "./externalPaths-ClykOQqa.mjs";
6
+ import { r as QueryCachePluginOptions } from "./queryCachePlugin-Do4P_WBC.mjs";
7
+ import { t as EventPluginOptions } from "./eventPlugin-DjEx_J3j.mjs";
8
+ import { _ as HealthOptions, f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-5H4930tj.mjs";
9
+ import { t as ErrorHandlerOptions } from "./errorHandler-DuqTqeqn.mjs";
10
+ import { r as IdempotencyStore } from "./interface-DoE6UoC2.mjs";
11
11
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
12
12
 
13
13
  //#region src/factory/loadResources.d.ts
@@ -203,6 +203,29 @@ interface LoadResourcesOptions<TContext = unknown> {
203
203
  */
204
204
  declare function loadResources<TContext = unknown>(dir: string, options?: LoadResourcesOptions<TContext>): Promise<ResourceLike[]>;
205
205
  //#endregion
206
+ //#region src/testing/preloadResources.d.ts
207
+ /** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
208
+ type EagerGlobResult = Record<string, unknown>;
209
+ /** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
210
+ type LazyGlobResult = Record<string, () => Promise<unknown>>;
211
+ /**
212
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
213
+ *
214
+ * Accepts either:
215
+ * - `{ import: 'default' }` form: values are the resource directly
216
+ * - default form: values are the full module — picks first export with `toPlugin()`
217
+ *
218
+ * Throws if any module doesn't yield a valid `ResourceLike`.
219
+ */
220
+ declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
221
+ /**
222
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
223
+ *
224
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
225
+ * cannot be evaluated at import time of the preload file.
226
+ */
227
+ declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
228
+ //#endregion
206
229
  //#region src/factory/types.d.ts
207
230
  type CorsOptions = Record<string, unknown> & {
208
231
  origin?: unknown;
@@ -498,6 +521,29 @@ interface CreateAppOptions {
498
521
  * separately by `multipart.limits.fileSize`. (2.15.1)
499
522
  */
500
523
  bodyLimit?: number;
524
+ /**
525
+ * Maximum length of a single URL path parameter (in characters).
526
+ *
527
+ * 2.16 — Fastify's default is `100`, which silently 404s modern signed-
528
+ * token URLs: HMAC tracking tokens (~250 chars), JWT-in-URL, OAuth state
529
+ * parameters, password-reset / magic-link tokens, etc. Arc's default
530
+ * is **400** to match the everything-is-a-signed-token reality of
531
+ * production apps; hosts that need longer params can raise it further
532
+ * (`1024` for very long JWTs).
533
+ *
534
+ * Pass-through to Fastify's `maxParamLength` constructor option — see
535
+ * https://fastify.dev/docs/latest/Reference/Server/#maxparamlength.
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * // App expects long HMAC tokens in the URL path
540
+ * createApp({ preset: 'production', maxParamLength: 1024 });
541
+ *
542
+ * // Constrain tighter than arc's default for audit reasons
543
+ * createApp({ preset: 'production', maxParamLength: 256 });
544
+ * ```
545
+ */
546
+ maxParamLength?: number;
501
547
  /**
502
548
  * Auth configuration
503
549
  *
@@ -1018,4 +1064,4 @@ interface RawBodyOptions {
1018
1064
  runFirst?: boolean;
1019
1065
  }
1020
1066
  //#endregion
1021
- export { CustomPluginAuthOption as a, RawBodyOptions as c, ResourceLike as d, ResourceModule as f, CustomAuthenticatorOption as i, UnderPressureOptions as l, BetterAuthOption as n, JwtAuthOption as o, loadResources as p, CreateAppOptions as r, MultipartOptions as s, AuthOption as t, LoadResourcesOptions as u };
1067
+ export { CustomPluginAuthOption as a, RawBodyOptions as c, preloadResourcesAsync as d, LoadResourcesOptions as f, loadResources as h, CustomAuthenticatorOption as i, UnderPressureOptions as l, ResourceModule as m, BetterAuthOption as n, JwtAuthOption as o, ResourceLike as p, CreateAppOptions as r, MultipartOptions as s, AuthOption as t, preloadResources as u };