@classytic/arc 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/{BaseController-BQ8KNvWC.mjs → BaseController--M8EMzt0.mjs} +90 -21
  2. package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
  3. package/dist/actionPermissions-CAphcmUG.mjs +24 -0
  4. package/dist/audit/index.d.mts +32 -3
  5. package/dist/audit/index.mjs +7 -4
  6. package/dist/auth/index.d.mts +2 -2
  7. package/dist/auth/index.mjs +263 -259
  8. package/dist/{buildHandler-D_2ULWxZ.mjs → buildHandler-Dtwd8fdz.mjs} +1 -1
  9. package/dist/cache/index.d.mts +41 -3
  10. package/dist/cache/index.mjs +33 -4
  11. package/dist/cli/commands/describe.d.mts +1 -1
  12. package/dist/cli/commands/docs.mjs +1 -1
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.mjs +1 -1
  15. package/dist/cli/index.mjs +2 -2
  16. package/dist/context/index.mjs +2 -64
  17. package/dist/core/index.d.mts +3 -3
  18. package/dist/core/index.mjs +4 -4
  19. package/dist/{core-DQ6rDTyH.mjs → core-EUNu0u07.mjs} +296 -11
  20. package/dist/{createActionRouter-BuOv-m4Y.mjs → createActionRouter-B6GvAuAX.mjs} +3 -2
  21. package/dist/{createAggregationRouter-CgJv_BU_.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
  22. package/dist/docs/index.d.mts +1 -1
  23. package/dist/docs/index.mjs +2 -2
  24. package/dist/{elevation-COpYKt2h.mjs → elevation-CIz1GjGm.mjs} +4 -3
  25. package/dist/{eventPlugin-A6EbthXR.d.mts → eventPlugin-Bg3cP2_u.d.mts} +38 -3
  26. package/dist/{eventPlugin-DJG2D7sy.mjs → eventPlugin-DrQ_FesK.mjs} +22 -4
  27. package/dist/events/index.d.mts +57 -314
  28. package/dist/events/index.mjs +163 -91
  29. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  30. package/dist/events/transports/redis-stream-entry.mjs +24 -2
  31. package/dist/factory/index.d.mts +4 -5
  32. package/dist/factory/index.mjs +28 -8
  33. package/dist/{fastify-augmentation-g_f9omSc.d.mts → fastify-augmentation-dPNBVeIj.d.mts} +25 -2
  34. package/dist/{generate-usRSdQmP.mjs → generate-D0wZupBg.mjs} +1 -1
  35. package/dist/headers-BvlQSDMB.mjs +45 -0
  36. package/dist/health-CB_m4S32.mjs +377 -0
  37. package/dist/hooks/index.d.mts +1 -1
  38. package/dist/idempotency/index.mjs +27 -3
  39. package/dist/{index-wMoWBLkn.d.mts → index-BMnbzvtO.d.mts} +3 -3
  40. package/dist/{integrations/jobs.d.mts → index-CG4yqMDB.d.mts} +82 -2
  41. package/dist/{index-BBsvQt7U.d.mts → index-DPRH0Cme.d.mts} +36 -106
  42. package/dist/{module-B4u10iOJ.d.mts → index-KUoxjIq1.d.mts} +263 -23
  43. package/dist/{index-CC5ruDGh.d.mts → index-f4PTrk3B.d.mts} +37 -5
  44. package/dist/index.d.mts +6 -6
  45. package/dist/index.mjs +5 -5
  46. package/dist/{init-CjO4p_q1.mjs → init-tlRSt667.mjs} +206 -208
  47. package/dist/integrations/event-gateway.d.mts +1 -1
  48. package/dist/integrations/event-gateway.mjs +1 -1
  49. package/dist/integrations/index.d.mts +3 -3
  50. package/dist/integrations/jobs/index.d.mts +2 -0
  51. package/dist/integrations/{jobs.mjs → jobs/index.mjs} +110 -67
  52. package/dist/integrations/mcp/index.d.mts +4 -4
  53. package/dist/integrations/mcp/index.mjs +2 -2
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/integrations/webhooks.d.mts +37 -30
  57. package/dist/integrations/webhooks.mjs +51 -62
  58. package/dist/integrations/websocket-redis.d.mts +1 -1
  59. package/dist/integrations/websocket.d.mts +1 -1
  60. package/dist/integrations/websocket.mjs +41 -14
  61. package/dist/{interface-C1_WLTMK.d.mts → interface-BGZCzOGL.d.mts} +24 -31
  62. package/dist/{mcpPlugin-BtzIL_kM.mjs → mcpPlugin-Cb7olw0j.mjs} +11 -9
  63. package/dist/{memory-CLXA2VeJ.mjs → memory-CmnzXE5Z.mjs} +29 -0
  64. package/dist/middleware/index.d.mts +17 -1
  65. package/dist/middleware/index.mjs +1 -1
  66. package/dist/migrations/index.d.mts +94 -44
  67. package/dist/migrations/index.mjs +196 -44
  68. package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
  69. package/dist/{openapi-jSDx-VDv.mjs → openapi-DrLtog_B.mjs} +3 -3
  70. package/dist/permissions/index.d.mts +2 -2
  71. package/dist/permissions/index.mjs +2 -2
  72. package/dist/{permissions-tbxFHxjZ.mjs → permissions-BrtiBKGs.mjs} +229 -229
  73. package/dist/pipeline/index.d.mts +1 -1
  74. package/dist/{actionPermissions-lszoJle1.mjs → pipeline-BN5G05rd.mjs} +3 -26
  75. package/dist/plugins/index.d.mts +6 -77
  76. package/dist/plugins/index.mjs +11 -95
  77. package/dist/plugins/response-cache.mjs +1 -0
  78. package/dist/plugins/tracing-entry.mjs +1 -1
  79. package/dist/{index-Ba10fdKp.d.mts → preloadResources-0oe-JSSS.d.mts} +50 -50
  80. package/dist/{createApp-CjOeWJ9_.mjs → preloadResources-DfyngIN9.mjs} +233 -70
  81. package/dist/presets/filesUpload.d.mts +3 -3
  82. package/dist/presets/filesUpload.mjs +3 -3
  83. package/dist/presets/index.d.mts +2 -1
  84. package/dist/presets/index.mjs +1 -1
  85. package/dist/presets/multiTenant.d.mts +1 -1
  86. package/dist/presets/multiTenant.mjs +1 -1
  87. package/dist/presets/search.d.mts +2 -2
  88. package/dist/presets/search.mjs +1 -1
  89. package/dist/{presets-BOe4Ugkl.mjs → presets-DwFDVKP0.mjs} +2 -2
  90. package/dist/{queryCachePlugin-DzqLN904.mjs → queryCachePlugin-DSGuSVLW.mjs} +39 -8
  91. package/dist/{queryCachePlugin-CfYWq-Ak.d.mts → queryCachePlugin-br4otiqF.d.mts} +2 -2
  92. package/dist/{registerRoute-CWO6mdYe.mjs → rateLimit-DrED-C4v.mjs} +2 -109
  93. package/dist/{realtime-I8OrfnMR.mjs → realtime-Cuw_deA-.mjs} +2 -2
  94. package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
  95. package/dist/registerRoute-v0Wb6Plt.mjs +108 -0
  96. package/dist/registry/index.d.mts +2 -2
  97. package/dist/renewingLease-3cuQH7h8.mjs +44 -0
  98. package/dist/{requestContext-BZKOr13H.mjs → requestContext-MrlJwECf.mjs} +2 -2
  99. package/dist/schedules-CjOd2MG9.mjs +113 -0
  100. package/dist/schemas/index.d.mts +67 -67
  101. package/dist/schemas/index.mjs +2 -3
  102. package/dist/scim/index.mjs +1 -2
  103. package/dist/scope/index.d.mts +2 -2
  104. package/dist/scope/index.mjs +4 -4
  105. package/dist/{scope-LONQ3mfZ.mjs → scope-DfaPntpo.mjs} +4 -5
  106. package/dist/{sse-0u4P4-OI.mjs → sse-BQ1w0dfl.mjs} +1 -1
  107. package/dist/testing/index.d.mts +4 -3
  108. package/dist/testing/index.mjs +2 -2
  109. package/dist/transactionContext-PNdoyLLm.mjs +64 -0
  110. package/dist/types/index.d.mts +4 -4
  111. package/dist/{types-bHblZ9YN.mjs → types-CSyCxl2V.mjs} +38 -7
  112. package/dist/{types-DH0GupI7.d.mts → types-DjC-KUCz.d.mts} +118 -20
  113. package/dist/{types-Csxga7d3.d.mts → types-Dmw-6Qeo.d.mts} +96 -4
  114. package/dist/{types-5OuqeTDX.d.mts → types-QFGc3Cjv.d.mts} +1 -1
  115. package/dist/usage/index.d.mts +3 -52
  116. package/dist/usage/index.mjs +0 -0
  117. package/dist/userHelpers-Cg_y3fBz.mjs +29 -0
  118. package/dist/utils/index.d.mts +58 -85
  119. package/dist/utils/index.mjs +4 -2
  120. package/dist/{utils-CZyeRXTh.mjs → utils-taUVD4p3.mjs} +47 -337
  121. package/dist/{validate-C2mGgSXg.mjs → validate-Bk44F2yT.mjs} +1 -1
  122. package/dist/{versioning-D6PvCwpq.d.mts → versioning-DXgY5BUm.d.mts} +11 -29
  123. package/dist/{versioning-Dk-71iIz.mjs → versioning-gKWyryT3.mjs} +8 -1
  124. package/dist/{websocket-uuWqM6UY.d.mts → websocket-_Pd0CgR0.d.mts} +15 -1
  125. package/package.json +526 -513
  126. package/skills/arc/references/api-reference.md +3 -1
  127. package/skills/arc/references/integrations.md +20 -11
  128. package/dist/QueryCache-Z1B38koh.d.mts +0 -40
  129. package/dist/betterAuthOpenApi-CV1lxpRX.mjs +0 -2
  130. package/dist/health-ScyjIVo5.mjs +0 -202
  131. package/dist/requestId-5BhTVzBv.mjs +0 -113
  132. package/dist/types-XTApa8b6.mjs +0 -24
@@ -1,6 +1,5 @@
1
1
  import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
2
- import { TypeBoxValidatorCompiler } from "@fastify/type-provider-typebox";
3
- import { Type, Type as Type$1 } from "@sinclair/typebox";
2
+ import { Type, Type as Type$1 } from "typebox";
4
3
  //#region src/schemas/index.ts
5
4
  /**
6
5
  * Paginated list response — full union of every wire shape `toCanonicalList`
@@ -134,4 +133,4 @@ function ArcPaginationQuery() {
134
133
  }, { additionalProperties: true });
135
134
  }
136
135
  //#endregion
137
- export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, Type, TypeBoxValidatorCompiler };
136
+ export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, Type };
@@ -533,8 +533,7 @@ var Parser = class {
533
533
  parseNot() {
534
534
  if (this.peek()?.kind === "op" && this.peek().value === "not") {
535
535
  this.consume();
536
- const next = this.peek();
537
- if (!next || next.kind !== "lparen") throw new ScimError(400, "invalidFilter", "Expected '(' after 'not'");
536
+ if (this.peek()?.kind !== "lparen") throw new ScimError(400, "invalidFilter", "Expected '(' after 'not'");
538
537
  this.consume();
539
538
  const child = this.parseOr();
540
539
  if (this.consume().kind !== "rparen") throw new ScimError(400, "invalidFilter", "Expected ')' after 'not(...)'");
@@ -1,4 +1,4 @@
1
- import { C as isMember, D as requireOrgId, E as requireClientId, O as requireTeamId, S as isElevated, T as isService, _ as getTenantFromRequest, a as getAncestorOrgIds, b as hasOrgAccess, c as getMandate, d as getOrgRoles, f as getRequestScope, g as getTeamId, h as getServiceScopes, i as RequestScope, k as requireUserId, l as getOrgContext, m as getScopeContextMap, n as Mandate, o as getClientId, p as getScopeContext, r as PUBLIC_SCOPE, s as getDPoPJkt, t as AUTHENTICATED_SCOPE, u as getOrgId, v as getUserId, w as isOrgInScope, x as isAuthenticated, y as getUserRoles } from "../types-DH0GupI7.mjs";
1
+ import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-DjC-KUCz.mjs";
2
2
  import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-C9s5hFP_.mjs";
3
3
  import { FastifyReply, FastifyRequest } from "fastify";
4
4
  //#region src/scope/rateLimitKey.d.ts
@@ -29,4 +29,4 @@ interface ResolveOrgFromHeaderOptions {
29
29
  */
30
30
  declare function resolveOrgFromHeader(options: ResolveOrgFromHeaderOptions): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
31
31
  //#endregion
32
- export { AUTHENTICATED_SCOPE, type ElevationEvent, type ElevationOptions, type Mandate, PUBLIC_SCOPE, type RateLimitKeyContext, type RequestScope, type ResolveOrgFromHeaderOptions, type TenantKeyGeneratorOptions, createTenantKeyGenerator, _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 };
32
+ export { AUTHENTICATED_SCOPE, type ElevationEvent, type ElevationOptions, type Mandate, PUBLIC_SCOPE, type RateLimitKeyContext, type RequestScope, type ResolveOrgFromHeaderOptions, type ScopeFirstCtx, type ScopeFirstCtxOptions, type ScopeFirstCtxRequest, type TenantKeyGeneratorOptions, createTenantKeyGenerator, _default as elevationPlugin, elevationPlugin as elevationPluginFn, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
@@ -1,5 +1,5 @@
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-bHblZ9YN.mjs";
2
- import { n as elevation_default, t as elevationPlugin } from "../elevation-COpYKt2h.mjs";
1
+ import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-CSyCxl2V.mjs";
2
+ import { n as elevation_default, t as elevationPlugin } from "../elevation-CIz1GjGm.mjs";
3
3
  import { t as createTenantKeyGenerator } from "../rateLimitKey-BoWRH3e7.mjs";
4
- import { t as resolveOrgFromHeader } from "../scope-LONQ3mfZ.mjs";
5
- 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 };
4
+ import { t as resolveOrgFromHeader } from "../scope-DfaPntpo.mjs";
5
+ export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin, elevationPlugin as elevationPluginFn, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
@@ -1,5 +1,6 @@
1
- import { a as OrgAccessDeniedError, l as UnauthorizedError, u as ValidationError } from "./errors-DkO8Nk6-.mjs";
2
- import { t as getUserRoles } from "./types-XTApa8b6.mjs";
1
+ import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-DkO8Nk6-.mjs";
2
+ import { n as requireSingleHeaderValue } from "./headers-BvlQSDMB.mjs";
3
+ import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
3
4
  //#region src/scope/resolveOrgFromHeader.ts
4
5
  /**
5
6
  * Create a preHandler hook that resolves org scope from a header.
@@ -10,9 +11,7 @@ import { t as getUserRoles } from "./types-XTApa8b6.mjs";
10
11
  function resolveOrgFromHeader(options) {
11
12
  const { header = "x-organization-id", resolveMembership } = options;
12
13
  return async (request, _reply) => {
13
- const raw = request.headers[header.toLowerCase()];
14
- if (Array.isArray(raw)) throw new ValidationError(`Duplicate '${header}' header`);
15
- const orgId = raw;
14
+ const orgId = requireSingleHeaderValue(request.headers, header);
16
15
  if (!orgId) return;
17
16
  const scope = request.scope;
18
17
  if (!scope || scope.kind === "public") throw new UnauthorizedError("Authentication required for organization access");
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-bHblZ9YN.mjs";
3
+ import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-CSyCxl2V.mjs";
4
4
  import { a as promoteStreamTokenToHeader } from "./streaming-BQcW6_Kq.mjs";
5
5
  import { t as openSseStream } from "./sseStream-BQ7Qaynp.mjs";
6
6
  import fp from "fastify-plugin";
@@ -1,6 +1,7 @@
1
- import { Jt as AnyRecord, V as ResourceDefinition } from "../index-BBsvQt7U.mjs";
2
- import { n as ArcModuleInput } from "../module-B4u10iOJ.mjs";
3
- import { d as preloadResourcesAsync, t as CreateAppOptions, u as preloadResources } from "../index-Ba10fdKp.mjs";
1
+ import { V as ResourceDefinition } from "../index-DPRH0Cme.mjs";
2
+ import { c as AnyRecord } from "../types-Dmw-6Qeo.mjs";
3
+ import { d as ArcModuleInput } from "../index-KUoxjIq1.mjs";
4
+ import { n as preloadResourcesAsync, r as CreateAppOptions, t as preloadResources } from "../preloadResources-0oe-JSSS.mjs";
4
5
  import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
5
6
  import { FastifyInstance, FastifyServerOptions } from "fastify";
6
7
  import { Mock } from "vitest";
@@ -1,5 +1,5 @@
1
1
  import { t as CRUD_OPERATIONS } from "../constants-DNre0wRg.mjs";
2
- import { d as getModuleExports, h as preloadResourcesAsync, m as preloadResources, n as createApp } from "../createApp-CjOeWJ9_.mjs";
2
+ import { i as createApp, n as preloadResourcesAsync, p as getModuleExports, t as preloadResources } from "../preloadResources-DfyngIN9.mjs";
3
3
  import { runStorageContract } from "./storageContract.mjs";
4
4
  import Fastify from "fastify";
5
5
  import { afterAll, describe, expect, it, vi } from "vitest";
@@ -1116,7 +1116,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1116
1116
  };
1117
1117
  }
1118
1118
  async function createTestApp(options = {}) {
1119
- const { createApp } = await import("../createApp-CjOeWJ9_.mjs").then((n) => n.r);
1119
+ const { createApp } = await import("../preloadResources-DfyngIN9.mjs").then((n) => n.a);
1120
1120
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1121
1121
  let dbHandle;
1122
1122
  let dbUri;
@@ -0,0 +1,64 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region src/context/transactionContext.ts
3
+ /**
4
+ * Transaction Context via AsyncLocalStorage
5
+ *
6
+ * Provides a Unit-of-Work (UoW) hook so any code in the call stack can
7
+ * retrieve the active database session/transaction without it being threaded
8
+ * through every function parameter.
9
+ *
10
+ * Arc is DB-agnostic — the session type is `unknown` so any backend
11
+ * (Mongoose session, Prisma transaction, pg client, Drizzle client) can
12
+ * participate. Kit adapters call `runInTransaction(session, fn)` to install
13
+ * the active context; the outbox's `store()` reads it automatically when no
14
+ * explicit `options.session` is passed.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // In a Mongoose-backed service (mongokit adapter)
19
+ * import { transactionContext } from '@classytic/arc/context';
20
+ *
21
+ * await mongoose.connection.withSession(async (session) => {
22
+ * await session.withTransaction(async () => {
23
+ * await transactionContext.run(session, async () => {
24
+ * await orders.insertOne(order, { session });
25
+ * // outbox.store() auto-picks up `session` — no manual threading
26
+ * await outbox.store(orderCreatedEvent);
27
+ * });
28
+ * });
29
+ * });
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // Opt-out: explicit session always wins over the ALS context
35
+ * await outbox.store(event, { session: explicitSession });
36
+ * ```
37
+ */
38
+ const storage = new AsyncLocalStorage();
39
+ /**
40
+ * Transaction context API.
41
+ *
42
+ * - `run(session, fn)` — install an active DB session for the duration of `fn`
43
+ * - `get()` — read the current session (undefined if outside a transaction scope)
44
+ */
45
+ const transactionContext = {
46
+ /**
47
+ * Run `fn` with `session` as the active DB transaction context.
48
+ * Any call to `get()` within `fn`'s async call stack returns `session`.
49
+ */
50
+ run(session, fn) {
51
+ return storage.run(session, fn);
52
+ },
53
+ /**
54
+ * Get the current DB session, or `undefined` if not inside a
55
+ * `transactionContext.run(...)` scope.
56
+ */
57
+ get() {
58
+ return storage.getStore();
59
+ },
60
+ /** The underlying AsyncLocalStorage instance. Exposed for advanced integrations. */
61
+ storage
62
+ };
63
+ //#endregion
64
+ export { transactionContext as t };
@@ -1,5 +1,5 @@
1
- import { $ as OpenApiSchemas, $t as UserLike, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as AuthenticatorContext, H as CrudController, Ht as AuthHelpers, I as MiddlewareHandler, In as BaseControllerOptions, It as FastifyHandler, J as PresetHook, Jt as AnyRecord, K as ResolvedTenantPurge, Kt as JwtContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Qt as ObjectId, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthPluginOptions, W as ResourcePermissions, Wt as Authenticator, X as CrudRouteKey, Xt as ArcRequest, Y as PresetResult, Yt as ApiResponse, Z as CrudSchemas, Zt as JWTPayload, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, en as UserOrganization, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as TokenPair, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-BBsvQt7U.mjs";
2
- import { i as RequestScope } from "../types-DH0GupI7.mjs";
3
- import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-Csxga7d3.mjs";
1
+ import { $ as OpenApiSchemas, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as AuthenticatorContext, H as CrudController, Ht as AuthHelpers, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, K as ResolvedTenantPurge, Kt as JwtContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthPluginOptions, W as ResourcePermissions, Wt as Authenticator, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kn as BaseControllerOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as TokenPair, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-DPRH0Cme.mjs";
2
+ import { i as RequestScope } from "../types-DjC-KUCz.mjs";
3
+ import { c as AnyRecord, d as ObjectId, f as UserLike, i as UserBase, l as ArcRequest, n as PermissionContext, p as UserOrganization, r as PermissionResult, t as PermissionCheck, u as JWTPayload } from "../types-Dmw-6Qeo.mjs";
4
4
  import { n as ElevationOptions, t as ElevationEvent } from "../elevation-C9s5hFP_.mjs";
5
- export type { 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, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
5
+ export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, 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, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
@@ -1,4 +1,6 @@
1
1
  import { l as UnauthorizedError, o as OrgRequiredError } from "./errors-DkO8Nk6-.mjs";
2
+ import { n as requireSingleHeaderValue } from "./headers-BvlQSDMB.mjs";
3
+ import { t as getUserId$1 } from "./userHelpers-Cg_y3fBz.mjs";
2
4
  //#region src/scope/types.ts
3
5
  /**
4
6
  * Request Scope — The One Standard
@@ -87,8 +89,7 @@ function getTenantFromRequest(req, options = {}) {
87
89
  };
88
90
  }
89
91
  const headerName = (options.header ?? "x-organization-id").toLowerCase();
90
- const raw = req.headers[headerName];
91
- const headerValue = Array.isArray(raw) ? raw[0] : raw;
92
+ const headerValue = requireSingleHeaderValue(req.headers, headerName);
92
93
  if (typeof headerValue === "string" && headerValue.length > 0) return {
93
94
  organizationId: headerValue,
94
95
  source: "header"
@@ -259,8 +260,13 @@ function isOrgInScope(scope, targetOrgId) {
259
260
  * ```
260
261
  */
261
262
  function getUserId(scope) {
262
- if (scope.kind === "public") return void 0;
263
- return scope.userId;
263
+ switch (scope.kind) {
264
+ case "authenticated":
265
+ case "member":
266
+ case "elevated": return scope.userId;
267
+ case "public":
268
+ case "service": return;
269
+ }
264
270
  }
265
271
  /**
266
272
  * Throwing variant of `getOrgId`. Returns the organization id when the
@@ -356,9 +362,12 @@ function getUserRoles(scope) {
356
362
  */
357
363
  function getOrgContext(request) {
358
364
  const scope = request.scope ?? { kind: "public" };
365
+ const requestedOrgId = requireSingleHeaderValue(request.headers, "x-organization-id");
366
+ const rawOrganizationId = request.user?.organizationId;
367
+ const userOrganizationId = rawOrganizationId === void 0 || rawOrganizationId === null ? void 0 : String(rawOrganizationId) || void 0;
359
368
  return {
360
- userId: getUserId(scope) ?? request.user?.id ?? request.user?._id,
361
- organizationId: getOrgId(scope) ?? request.user?.organizationId ?? request.headers?.["x-organization-id"],
369
+ userId: getUserId(scope) ?? getUserId$1(request.user),
370
+ organizationId: getOrgId(scope) ?? userOrganizationId ?? requestedOrgId,
362
371
  roles: getUserRoles(scope),
363
372
  orgRoles: getOrgRoles(scope)
364
373
  };
@@ -389,9 +398,31 @@ function getOrgContext(request) {
389
398
  function getRequestScope(request) {
390
399
  return request.scope ?? PUBLIC_SCOPE;
391
400
  }
401
+ function scopeFirstCtx(req, options = {}) {
402
+ let actorId;
403
+ let organizationId;
404
+ const s = req.scope;
405
+ if (s && "kind" in s) {
406
+ actorId = getUserId(s) ?? getClientId(s);
407
+ organizationId = getOrgId(s);
408
+ } else if (s) {
409
+ actorId = s.userId;
410
+ organizationId = s.organizationId;
411
+ }
412
+ actorId = actorId ?? options.fallbackActorId;
413
+ if (!organizationId && options.orgHeader !== false && req.headers) {
414
+ const headerName = (options.orgHeader ?? "x-organization-id").toLowerCase();
415
+ const headerValue = requireSingleHeaderValue(req.headers, headerName);
416
+ if (typeof headerValue === "string" && headerValue.length > 0) organizationId = headerValue;
417
+ }
418
+ return {
419
+ ...actorId !== void 0 ? { actorId } : {},
420
+ ...organizationId !== void 0 ? { organizationId } : {}
421
+ };
422
+ }
392
423
  /** Default public scope — used as initial decoration value */
393
424
  const PUBLIC_SCOPE = Object.freeze({ kind: "public" });
394
425
  /** Default authenticated scope — used when user is logged in but no org */
395
426
  const AUTHENTICATED_SCOPE = Object.freeze({ kind: "authenticated" });
396
427
  //#endregion
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 };
428
+ export { isService as C, requireUserId as D, requireTeamId as E, scopeFirstCtx as O, 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 };
@@ -1,24 +1,39 @@
1
- //#region src/scope/types.d.ts
1
+ //#region src/utils/headers.d.ts
2
2
  /**
3
- * Request Scope — The One Standard
4
- *
5
- * Discriminated union representing the access context of every request.
6
- * Replaces scattered orgScope/orgRoles/organizationId/bypassRoles.
7
- *
8
- * Set once by auth adapters, read everywhere by permissions/presets/guards.
3
+ * Canonical header typing + accessor.
4
+ *
5
+ * Node/Fastify header values are `string | string[] | undefined` — repeated
6
+ * headers arrive as arrays. `IRequestContext.headers` is DECLARED as
7
+ * `Record<string, string | undefined>` for ergonomic host code, but the HTTP
8
+ * adapter passes Fastify's headers object through unchanged, so `string[]`
9
+ * CAN appear at runtime behind the narrower type (the declared type is
10
+ * scheduled to widen to {@link ArcHeaders} in v3). Until then, read headers
11
+ * through {@link getHeader} instead of indexing — it's honest about arrays
12
+ * and deterministic about duplicates.
13
+ */
14
+ /** What a headers bag actually contains at runtime — HTTP or synthetic MCP. */
15
+ type ArcHeaders = Readonly<Record<string, string | readonly string[] | undefined>>;
16
+ /**
17
+ * Read a single header value. Lowercases the name (Node normalizes incoming
18
+ * header names to lowercase) and returns the FIRST value when the header
19
+ * was repeated.
20
+ *
21
+ * Duplicate policy: first-value is deterministic, but for security-sensitive
22
+ * headers (authorization material, forwarded-for chains, content-length) a
23
+ * duplicate usually signals smuggling or a broken proxy — consumers that
24
+ * care should check `Array.isArray(headers[name])` and REJECT instead of
25
+ * picking a winner.
26
+ */
27
+ declare function getHeader(headers: ArcHeaders | Record<string, string | undefined> | undefined, name: string): string | undefined;
28
+ /**
29
+ * Read an identity-bearing or protocol header and reject repeated values.
9
30
  *
10
- * @example
11
- * ```typescript
12
- * // In a permission check
13
- * const scope = request.scope;
14
- * if (isElevated(scope)) return true;
15
- * if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
16
- *
17
- * // Get user identity from scope
18
- * const userId = getUserId(scope);
19
- * const globalRoles = getUserRoles(scope);
20
- * ```
31
+ * Repeated security-sensitive headers are ambiguous at proxy boundaries and
32
+ * may indicate request smuggling. Callers must not silently choose a winner.
21
33
  */
34
+ declare function requireSingleHeaderValue(headers: ArcHeaders | Record<string, string | undefined> | undefined, name: string): string | undefined;
35
+ //#endregion
36
+ //#region src/scope/types.d.ts
22
37
  /**
23
38
  * A capability mandate — declarative, time-boxed, optionally cap-bounded
24
39
  * authorization for a single high-value action by an AI agent or service.
@@ -118,7 +133,9 @@ interface Mandate {
118
133
  * | elevated | Platform admin, explicit elevation |
119
134
  *
120
135
  * **Identity fields by kind:**
121
- * - `userId` / `userRoles` — available on `authenticated`, `member`, `elevated` (a real human)
136
+ * - `userId` — available on `authenticated`, `member`, `elevated` (a real human)
137
+ * - `userRoles` — available on `authenticated` and `member`; elevated scope
138
+ * bypasses role gates explicitly and therefore does not carry role claims
122
139
  * - `clientId` — available on `service` (a machine identity, NOT a user)
123
140
  * - `organizationId` — required on `member` and `service`, optional on `elevated`
124
141
  * - `orgRoles` — org-level roles, only on `member` (from membership records)
@@ -517,9 +534,90 @@ declare function getOrgContext(request: {
517
534
  declare function getRequestScope(request: {
518
535
  scope?: RequestScope;
519
536
  }): RequestScope;
537
+ /** Options for {@link scopeFirstCtx}. */
538
+ interface ScopeFirstCtxOptions {
539
+ /**
540
+ * Tenant-selection header consulted ONLY when the scope carries no org —
541
+ * the custom-auth/public bridge (same semantics as `getTenantFromRequest`).
542
+ * A validated scope org always wins; the header never overrides it.
543
+ * Default `'x-organization-id'`; pass `false` to disable the bridge and
544
+ * read strictly from scope.
545
+ */
546
+ orgHeader?: string | false;
547
+ /**
548
+ * Actor id substituted when the scope carries no user/client identity
549
+ * (e.g. `'system'` / `'anonymous'`). When provided, the returned `actorId`
550
+ * narrows to `string`; omit to receive `actorId: undefined` on
551
+ * unauthenticated requests and apply your own policy.
552
+ */
553
+ fallbackActorId?: string;
554
+ }
555
+ /** Return shape of {@link scopeFirstCtx}. */
556
+ interface ScopeFirstCtx {
557
+ actorId?: string;
558
+ organizationId?: string;
559
+ }
560
+ /**
561
+ * Request shape accepted by {@link scopeFirstCtx} — deliberately structural so
562
+ * ONE helper serves both surfaces:
563
+ * - a raw `FastifyRequest`, whose `scope` is the discriminated {@link RequestScope}
564
+ * - arc's `IRequestContext` (controller overrides, pipeline handlers, actions),
565
+ * whose `scope` is the FLAT projection (`{ organizationId?, userId?, orgRoles? }`
566
+ * lifted from `metadata._scope` — see `RequestScopeProjection`)
567
+ */
568
+ interface ScopeFirstCtxRequest {
569
+ scope?: RequestScope | {
570
+ organizationId?: string | undefined;
571
+ userId?: string | undefined;
572
+ orgRoles?: string[] | undefined;
573
+ } | undefined;
574
+ /** `ArcHeaders` — readonly, repeated headers may arrive as arrays. Fastify's `IncomingHttpHeaders` assigns cleanly. */
575
+ headers?: ArcHeaders | undefined;
576
+ }
577
+ /**
578
+ * The ONE shared actor/org derivation for resource handlers and module
579
+ * packages (`arc-*` fleet) — scope-FIRST, per the canonical precedence:
580
+ *
581
+ * 1. `request.scope` (arc's validated `RequestScope`, set by auth adapters):
582
+ * `userId` (human) or `clientId` (service) → `actorId`; `getOrgId(scope)`
583
+ * → `organizationId`.
584
+ * 2. The `x-organization-id` header — ONLY when the scope carries no org.
585
+ * This is the bridge for custom-auth/public routes where arc leaves scope
586
+ * unpopulated; it never overrides a validated scope.
587
+ *
588
+ * Replaces per-package hand-rolled clones of
589
+ * `req.user?._id ?? req.user?.id ?? headers['x-organization-id']` — that
590
+ * pattern re-implements (and drifts from) arc's canonical resolution. A
591
+ * package's `ctxOf(req)` should call this for the shared
592
+ * `{ actorId, organizationId }` and only ADD its kernel-specific fields
593
+ * (`actorKind` / `correlationId` / `roles` / `idempotencyKey`).
594
+ *
595
+ * Structural request shape — works on a raw `FastifyRequest` AND on arc's
596
+ * `IRequestContext` (see {@link ScopeFirstCtxRequest}: the discriminated
597
+ * `RequestScope` and the flat controller projection are both understood).
598
+ *
599
+ * @example
600
+ * ```typescript
601
+ * import { scopeFirstCtx } from '@classytic/arc/scope';
602
+ *
603
+ * // Guaranteed actor (kernel context requires one):
604
+ * const { actorId, organizationId } = scopeFirstCtx(req, { fallbackActorId: 'system' });
605
+ *
606
+ * // Optional actor (package applies its own policy):
607
+ * const ctx = scopeFirstCtx(req);
608
+ * if (ctx.actorId) audit.record({ actor: ctx.actorId });
609
+ * ```
610
+ */
611
+ declare function scopeFirstCtx(req: ScopeFirstCtxRequest, options: ScopeFirstCtxOptions & {
612
+ fallbackActorId: string;
613
+ }): {
614
+ actorId: string;
615
+ organizationId?: string;
616
+ };
617
+ declare function scopeFirstCtx(req: ScopeFirstCtxRequest, options?: ScopeFirstCtxOptions): ScopeFirstCtx;
520
618
  /** Default public scope — used as initial decoration value */
521
619
  declare const PUBLIC_SCOPE: Readonly<RequestScope>;
522
620
  /** Default authenticated scope — used when user is logged in but no org */
523
621
  declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
524
622
  //#endregion
525
- 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 };
623
+ export { requireOrgId as A, hasOrgAccess as C, isOrgInScope as D, isMember as E, getHeader as F, requireSingleHeaderValue as I, requireUserId as M, scopeFirstCtx as N, isService as O, ArcHeaders as P, getUserRoles as S, isElevated as T, getScopeContextMap as _, ScopeFirstCtx as a, getTenantFromRequest as b, getAncestorOrgIds as c, getMandate as d, getOrgContext as f, getScopeContext as g, getRequestScope as h, RequestScope as i, requireTeamId as j, requireClientId as k, getClientId as l, getOrgRoles as m, Mandate as n, ScopeFirstCtxOptions as o, getOrgId as p, PUBLIC_SCOPE as r, ScopeFirstCtxRequest as s, AUTHENTICATED_SCOPE as t, getDPoPJkt as u, getServiceScopes as v, isAuthenticated as w, getUserId as x, getTeamId as y };
@@ -1,5 +1,99 @@
1
- import { i as RequestScope } from "./types-DH0GupI7.mjs";
1
+ import { i as RequestScope } from "./types-DjC-KUCz.mjs";
2
2
  import { FastifyRequest } from "fastify";
3
+ //#region src/types/base.d.ts
4
+ declare module "fastify" {
5
+ interface FastifyRequest {
6
+ /** Request scope — set by auth adapter, read by permissions/presets/guards */
7
+ scope: RequestScope;
8
+ /**
9
+ * Current user — set by auth adapter (Better Auth, JWT, custom).
10
+ * `undefined` on public routes (`auth: false`) or unauthenticated requests.
11
+ * Guard with `if (request.user)` on routes that allow anonymous access.
12
+ *
13
+ * Kept as required (not `user?`) because `@fastify/jwt` declares it
14
+ * as required — declaration merges must have identical modifiers.
15
+ * The `| undefined` in the type achieves the same DX.
16
+ */
17
+ user: Record<string, unknown> | undefined;
18
+ /** Policy-injected query filters (e.g. ownership, org-scoping) */
19
+ _policyFilters?: Record<string, unknown>;
20
+ /** Field mask — fields to include/exclude in responses */
21
+ fieldMask?: {
22
+ include?: string[];
23
+ exclude?: string[];
24
+ };
25
+ /** Arbitrary policy metadata for downstream consumers */
26
+ policyMetadata?: Record<string, unknown>;
27
+ /** Document loaded by policy middleware for ownership checks */
28
+ document?: unknown;
29
+ /** Ownership check context (field name + user field) */
30
+ _ownershipCheck?: Record<string, unknown>;
31
+ }
32
+ }
33
+ type AnyRecord = Record<string, unknown>;
34
+ /** MongoDB ObjectId — accepts string or any object with a `toString()` (e.g. mongoose ObjectId). */
35
+ type ObjectId = string | {
36
+ toString(): string;
37
+ };
38
+ /**
39
+ * Flexible user type that accepts any object with id/_id properties.
40
+ * The actual user structure is defined by your app's auth system.
41
+ */
42
+ type UserLike = UserBase & {
43
+ /** User email (optional) */
44
+ email?: string;
45
+ };
46
+ interface UserOrganization {
47
+ userId: string;
48
+ organizationId: string;
49
+ [key: string]: unknown;
50
+ }
51
+ interface JWTPayload {
52
+ sub: string;
53
+ [key: string]: unknown;
54
+ }
55
+ /**
56
+ * Typed Fastify request with Arc decorations. Use in `raw: true` handlers
57
+ * instead of `(req as any).user`.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * import type { ArcRequest } from '@classytic/arc';
62
+ *
63
+ * handler: async (req: ArcRequest, reply) => {
64
+ * req.user?.id; // typed
65
+ * req.scope.organizationId; // typed (when member)
66
+ * req.signal; // AbortSignal (Fastify 5)
67
+ * }
68
+ * ```
69
+ */
70
+ type ArcRequest = FastifyRequest & {
71
+ scope: RequestScope;
72
+ user: Record<string, unknown> | undefined;
73
+ signal: AbortSignal;
74
+ };
75
+ //#endregion
76
+ //#region src/utils/userHelpers.d.ts
77
+ /**
78
+ * Extract a user ID from a user object. Accepts `id` or `_id` — returns
79
+ * `undefined` when neither is present. Used by arc's controllers to
80
+ * populate `createdBy` / `updatedBy` fields and for cache scoping.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import { getUserId } from '@classytic/arc/utils';
85
+ * const uid = getUserId(request.user);
86
+ * ```
87
+ */
88
+ declare function getUserId(user: UserLike | null | undefined): string | undefined;
89
+ /** Normalize a raw role value from Better Auth, JWT, or a custom identity provider. */
90
+ declare function normalizeRoles(value: unknown): string[];
91
+ /** Extract normalized roles from a user-like object. */
92
+ declare function getUserRoles(user: {
93
+ role?: unknown;
94
+ roles?: unknown;
95
+ } | null | undefined): string[];
96
+ //#endregion
3
97
  //#region src/permissions/types.d.ts
4
98
  /**
5
99
  * User base interface - minimal shape Arc expects
@@ -24,8 +118,6 @@ interface UserBase {
24
118
  * Normalize a raw role value (string, comma-separated string, or array) into a string[].
25
119
  * Shared low-level helper used by both getUserRoles() and the Better Auth adapter.
26
120
  */
27
- declare function normalizeRoles(value: unknown): string[];
28
- declare function getUserRoles(user: UserBase | null | undefined): string[];
29
121
  /**
30
122
  * Context passed to permission check functions
31
123
  */
@@ -195,4 +287,4 @@ interface PermissionCheckMeta {
195
287
  };
196
288
  }
197
289
  //#endregion
198
- export { getUserRoles as a, UserBase as i, PermissionContext as n, normalizeRoles as o, PermissionResult as r, PermissionCheck as t };
290
+ export { getUserId as a, AnyRecord as c, ObjectId as d, UserLike as f, UserBase as i, ArcRequest as l, PermissionContext as n, getUserRoles as o, UserOrganization as p, PermissionResult as r, normalizeRoles as s, PermissionCheck as t, JWTPayload as u };
@@ -1,4 +1,4 @@
1
- import { V as ResourceDefinition } from "./index-BBsvQt7U.mjs";
1
+ import { V as ResourceDefinition } from "./index-DPRH0Cme.mjs";
2
2
  import { z } from "zod";
3
3
  //#region src/integrations/mcp/types.d.ts
4
4
  /** Behavioral hints for MCP clients (tool annotations per MCP spec) */
@@ -1,5 +1,6 @@
1
- import { i as RequestScope } from "../types-DH0GupI7.mjs";
2
- import { n as PermissionContext, t as PermissionCheck } from "../types-Csxga7d3.mjs";
1
+ import { i as RequestScope } from "../types-DjC-KUCz.mjs";
2
+ import { n as PermissionContext, t as PermissionCheck } from "../types-Dmw-6Qeo.mjs";
3
+ import { UsageBucket, UsageStore, usagePeriod } from "@classytic/repo-core/usage";
3
4
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
4
5
  //#region src/usage/requireQuota.d.ts
5
6
  interface QuotaOptions {
@@ -28,56 +29,6 @@ interface QuotaOptions {
28
29
  }
29
30
  declare function requireQuota(options: QuotaOptions): PermissionCheck;
30
31
  //#endregion
31
- //#region src/usage/stores/interface.d.ts
32
- /**
33
- * Usage store contract — period-bucketed counters per actor.
34
- *
35
- * CANONICAL HOME: `@classytic/repo-core/usage` (>=0.10) owns this
36
- * contract for the ecosystem — kits implement it there without ever
37
- * depending on arc (`@classytic/mongokit/usage`, ...). This file is a
38
- * deliberate STRUCTURAL MIRROR of that shape — same trick as
39
- * `ScheduleLockLike` vs repo-core's `LockAdapter` — so arc's emitted
40
- * declarations carry no repo-core@>=0.10 subpath reference and the
41
- * peer floor stays honest. Any conforming adapter assigns directly.
42
- *
43
- * The deliberately tiny surface (one increment, one read) is what makes
44
- * every backend trivial: memory (dev/tests), Redis (`HINCRBY`), or any kit
45
- * (Mongo `$inc` upsert) implements it in a handful of lines. Aggregation
46
- * happens IN the store — arc never buffers counters in process memory
47
- * beyond the built-in memory store, so multi-replica deployments stay
48
- * correct as long as the store is shared.
49
- *
50
- * This is the HORIZONTAL usage layer (per-actor, per-period platform
51
- * accounting for quotas/plans/billing). Itemized vertical records — e.g.
52
- * `@classytic/arc-ai/usage`'s per-run AI token/cost tracking — keep their
53
- * own richer stores and can sink into this one (`kind: 'ai.tokens'`).
54
- */
55
- /** One counter cell: (actor, period, kind). */
56
- interface UsageBucket {
57
- /** Who consumed — org / user / client id, or an IP-derived fallback. */
58
- actor: string;
59
- /** Aggregation period, `YYYY-MM` (see `usagePeriod()`). */
60
- period: string;
61
- /** Namespaced counter, dot-separated: `api.requests`, `ai.tokens`, `storage.bytes`. */
62
- kind: string;
63
- }
64
- interface UsageStore {
65
- /** Store name for diagnostics (e.g. 'memory', 'redis', 'mongo'). */
66
- readonly name: string;
67
- /** Add `amount` to the bucket's counter, creating it at 0 first. MUST be atomic per bucket. */
68
- increment(bucket: UsageBucket, amount: number): Promise<void> | void;
69
- /** Every counter for an actor in a period: `{ 'api.requests': 40231, ... }`. */
70
- summary(actor: string, period: string): Promise<Record<string, number>>;
71
- /** Optional cleanup hook (connections, timers). */
72
- close?(): Promise<void>;
73
- }
74
- /**
75
- * Canonical period key for a date — calendar month, UTC: `2026-07`.
76
- * Monthly is the billing-native granularity; stores that want finer
77
- * windows can shard internally without changing the contract.
78
- */
79
- declare function usagePeriod(date?: Date): string;
80
- //#endregion
81
32
  //#region src/usage/stores/memory.d.ts
82
33
  declare class MemoryUsageStore implements UsageStore {
83
34
  #private;
Binary file