@classytic/arc 2.23.0 → 2.28.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 (147) hide show
  1. package/README.md +2 -0
  2. package/dist/{BaseController-BQ8KNvWC.mjs → BaseController-B65zbz9m.mjs} +94 -23
  3. package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
  4. package/dist/actionPermissions-CAphcmUG.mjs +24 -0
  5. package/dist/{applyPermissionResult-Tw4iXtlf.mjs → applyPermissionResult-BnyVE9eD.mjs} +10 -0
  6. package/dist/audit/index.d.mts +32 -3
  7. package/dist/audit/index.mjs +7 -4
  8. package/dist/auth/index.d.mts +2 -2
  9. package/dist/auth/index.mjs +263 -259
  10. package/dist/{buildHandler-D_2ULWxZ.mjs → buildHandler-Dtwd8fdz.mjs} +1 -1
  11. package/dist/cache/index.d.mts +41 -3
  12. package/dist/cache/index.mjs +33 -4
  13. package/dist/cleanup/index.d.mts +313 -0
  14. package/dist/cleanup/index.mjs +1267 -0
  15. package/dist/cli/commands/describe.d.mts +1 -1
  16. package/dist/cli/commands/docs.mjs +1 -1
  17. package/dist/cli/commands/generate.mjs +1 -1
  18. package/dist/cli/commands/init.mjs +1 -1
  19. package/dist/cli/index.mjs +2 -2
  20. package/dist/context/index.mjs +2 -64
  21. package/dist/core/index.d.mts +3 -3
  22. package/dist/core/index.mjs +5 -4
  23. package/dist/core-BGa8TXJR.mjs +221 -0
  24. package/dist/{createActionRouter-BuOv-m4Y.mjs → createActionRouter-C-N7z2EL.mjs} +3 -2
  25. package/dist/{createAggregationRouter-CgJv_BU_.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
  26. package/dist/{createApp-CjOeWJ9_.mjs → createApp-Du5G54TM.mjs} +187 -96
  27. package/dist/{core-DQ6rDTyH.mjs → defineResource-2zvVVpxg.mjs} +297 -230
  28. package/dist/docs/index.d.mts +1 -1
  29. package/dist/docs/index.mjs +2 -2
  30. package/dist/{elevation-COpYKt2h.mjs → elevation-CIz1GjGm.mjs} +4 -3
  31. package/dist/{eventPlugin-A6EbthXR.d.mts → eventPlugin-Bg3cP2_u.d.mts} +38 -3
  32. package/dist/{eventPlugin-DJG2D7sy.mjs → eventPlugin-DrQ_FesK.mjs} +22 -4
  33. package/dist/events/index.d.mts +83 -314
  34. package/dist/events/index.mjs +183 -95
  35. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  36. package/dist/events/transports/redis-stream-entry.mjs +24 -2
  37. package/dist/factory/index.d.mts +5 -5
  38. package/dist/factory/index.mjs +30 -8
  39. package/dist/{fastify-augmentation-g_f9omSc.d.mts → fastify-augmentation-OzYGIiU1.d.mts} +26 -2
  40. package/dist/{generate-usRSdQmP.mjs → generate-D0wZupBg.mjs} +1 -1
  41. package/dist/headers-BvlQSDMB.mjs +45 -0
  42. package/dist/health-CB_m4S32.mjs +377 -0
  43. package/dist/hooks/index.d.mts +1 -1
  44. package/dist/idempotency/index.mjs +27 -3
  45. package/dist/{index-wMoWBLkn.d.mts → index-BMnbzvtO.d.mts} +3 -3
  46. package/dist/{integrations/jobs.d.mts → index-CG4yqMDB.d.mts} +82 -2
  47. package/dist/index-CZY4EGga.d.mts +107 -0
  48. package/dist/{index-CC5ruDGh.d.mts → index-CjVg-Epg.d.mts} +37 -5
  49. package/dist/{index-BBsvQt7U.d.mts → index-DPRH0Cme.d.mts} +36 -106
  50. package/dist/index.d.mts +6 -6
  51. package/dist/index.mjs +7 -6
  52. package/dist/{init-CjO4p_q1.mjs → init-tlRSt667.mjs} +206 -208
  53. package/dist/integrations/event-gateway.d.mts +1 -1
  54. package/dist/integrations/event-gateway.mjs +1 -1
  55. package/dist/integrations/index.d.mts +3 -3
  56. package/dist/integrations/jobs/index.d.mts +2 -0
  57. package/dist/integrations/{jobs.mjs → jobs/index.mjs} +110 -67
  58. package/dist/integrations/mcp/index.d.mts +4 -4
  59. package/dist/integrations/mcp/index.mjs +2 -2
  60. package/dist/integrations/mcp/testing.d.mts +1 -1
  61. package/dist/integrations/mcp/testing.mjs +1 -1
  62. package/dist/integrations/webhooks.d.mts +37 -30
  63. package/dist/integrations/webhooks.mjs +51 -62
  64. package/dist/integrations/websocket-redis.d.mts +1 -1
  65. package/dist/integrations/websocket.d.mts +1 -1
  66. package/dist/integrations/websocket.mjs +41 -14
  67. package/dist/{interface-C1_WLTMK.d.mts → interface-BGZCzOGL.d.mts} +24 -31
  68. package/dist/{mcpPlugin-BtzIL_kM.mjs → mcpPlugin-Cs6nRGCt.mjs} +12 -10
  69. package/dist/{memory-CLXA2VeJ.mjs → memory-CmnzXE5Z.mjs} +29 -0
  70. package/dist/middleware/index.d.mts +17 -1
  71. package/dist/middleware/index.mjs +1 -1
  72. package/dist/migrations/index.d.mts +94 -44
  73. package/dist/migrations/index.mjs +196 -44
  74. package/dist/module-_-M7C5ES.mjs +29 -0
  75. package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
  76. package/dist/{openapi-jSDx-VDv.mjs → openapi-BLV4OMXb.mjs} +3 -3
  77. package/dist/permissions/index.d.mts +2 -2
  78. package/dist/permissions/index.mjs +3 -3
  79. package/dist/{permissions-tbxFHxjZ.mjs → permissions-BrtiBKGs.mjs} +229 -229
  80. package/dist/pipeline/index.d.mts +1 -1
  81. package/dist/{actionPermissions-lszoJle1.mjs → pipeline-BGRnV-To.mjs} +4 -27
  82. package/dist/plugins/index.d.mts +6 -77
  83. package/dist/plugins/index.mjs +11 -95
  84. package/dist/plugins/response-cache.mjs +1 -0
  85. package/dist/plugins/tracing-entry.mjs +1 -1
  86. package/dist/{index-Ba10fdKp.d.mts → preloadResources-CM4uUaFv.d.mts} +51 -50
  87. package/dist/preloadResources-DSY5HGpS.mjs +45 -0
  88. package/dist/presets/filesUpload.d.mts +3 -3
  89. package/dist/presets/filesUpload.mjs +3 -3
  90. package/dist/presets/index.d.mts +2 -1
  91. package/dist/presets/index.mjs +1 -1
  92. package/dist/presets/multiTenant.d.mts +1 -1
  93. package/dist/presets/multiTenant.mjs +1 -1
  94. package/dist/presets/search.d.mts +2 -2
  95. package/dist/presets/search.mjs +1 -1
  96. package/dist/{presets-BOe4Ugkl.mjs → presets-DwFDVKP0.mjs} +2 -2
  97. package/dist/{queryCachePlugin-DzqLN904.mjs → queryCachePlugin-DSGuSVLW.mjs} +39 -8
  98. package/dist/{queryCachePlugin-CfYWq-Ak.d.mts → queryCachePlugin-br4otiqF.d.mts} +2 -2
  99. package/dist/{registerRoute-CWO6mdYe.mjs → rateLimit-DrED-C4v.mjs} +2 -109
  100. package/dist/{realtime-I8OrfnMR.mjs → realtime-DEuDu9io.mjs} +3 -3
  101. package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
  102. package/dist/registerRoute-DTp18Ah_.mjs +108 -0
  103. package/dist/registry/index.d.mts +2 -2
  104. package/dist/registry/index.mjs +1 -1
  105. package/dist/{registry-vcOCtPij.mjs → registry-9km_w1aP.mjs} +15 -0
  106. package/dist/renewingLease-3cuQH7h8.mjs +44 -0
  107. package/dist/{requestContext-BZKOr13H.mjs → requestContext-MrlJwECf.mjs} +2 -2
  108. package/dist/schedules-CjOd2MG9.mjs +113 -0
  109. package/dist/schemas/index.d.mts +67 -67
  110. package/dist/schemas/index.mjs +2 -3
  111. package/dist/scim/index.mjs +1 -2
  112. package/dist/scope/index.d.mts +2 -2
  113. package/dist/scope/index.mjs +4 -4
  114. package/dist/{scope-LONQ3mfZ.mjs → scope-DfaPntpo.mjs} +4 -5
  115. package/dist/{sse-0u4P4-OI.mjs → sse-BQ1w0dfl.mjs} +1 -1
  116. package/dist/testing/cleanupStoreContract.d.mts +10 -0
  117. package/dist/testing/cleanupStoreContract.mjs +375 -0
  118. package/dist/testing/index.d.mts +5 -3
  119. package/dist/testing/index.mjs +4 -2
  120. package/dist/testing/outboxStoreContract.d.mts +13 -0
  121. package/dist/testing/outboxStoreContract.mjs +247 -0
  122. package/dist/transactionContext-PNdoyLLm.mjs +64 -0
  123. package/dist/types/index.d.mts +4 -4
  124. package/dist/{types-bHblZ9YN.mjs → types-CSyCxl2V.mjs} +38 -7
  125. package/dist/types-C_ZZRcD6.d.mts +450 -0
  126. package/dist/{module-B4u10iOJ.d.mts → types-Cu6vVJmh.d.mts} +196 -60
  127. package/dist/{types-DH0GupI7.d.mts → types-DjC-KUCz.d.mts} +118 -20
  128. package/dist/{types-Csxga7d3.d.mts → types-Dmw-6Qeo.d.mts} +96 -4
  129. package/dist/{types-5OuqeTDX.d.mts → types-QFGc3Cjv.d.mts} +1 -1
  130. package/dist/usage/index.d.mts +3 -52
  131. package/dist/usage/index.mjs +0 -0
  132. package/dist/userHelpers-Cg_y3fBz.mjs +29 -0
  133. package/dist/utils/index.d.mts +58 -85
  134. package/dist/utils/index.mjs +4 -2
  135. package/dist/{utils-CZyeRXTh.mjs → utils-taUVD4p3.mjs} +47 -337
  136. package/dist/{validate-C2mGgSXg.mjs → validate-Bk44F2yT.mjs} +1 -1
  137. package/dist/{versioning-D6PvCwpq.d.mts → versioning-DXgY5BUm.d.mts} +11 -29
  138. package/dist/{versioning-Dk-71iIz.mjs → versioning-gKWyryT3.mjs} +8 -1
  139. package/dist/{websocket-uuWqM6UY.d.mts → websocket-_Pd0CgR0.d.mts} +15 -1
  140. package/package.json +541 -513
  141. package/skills/arc/references/api-reference.md +3 -1
  142. package/skills/arc/references/integrations.md +20 -11
  143. package/dist/QueryCache-Z1B38koh.d.mts +0 -40
  144. package/dist/betterAuthOpenApi-CV1lxpRX.mjs +0 -2
  145. package/dist/health-ScyjIVo5.mjs +0 -202
  146. package/dist/requestId-5BhTVzBv.mjs +0 -113
  147. package/dist/types-XTApa8b6.mjs +0 -24
@@ -59,6 +59,20 @@ interface RedisStreamTransportOptions {
59
59
  * @default 10
60
60
  */
61
61
  batchSize?: number;
62
+ /**
63
+ * Bounded concurrency for processing a batch's ENTRIES (default: 1 —
64
+ * strict stream order, the safe default). With one slow handler,
65
+ * throughput per consumer is ~`1 / total handler latency`; raising this
66
+ * processes up to N entries of a batch concurrently.
67
+ *
68
+ * Trade-off: entries within a batch may complete OUT OF ORDER — only
69
+ * raise it when handlers are order-independent (idempotent, keyed
70
+ * upserts). Handlers on this transport should be short and I/O-bound
71
+ * regardless; heavy work belongs in jobs. Matching handlers for a single
72
+ * event always run sequentially.
73
+ * @default 1
74
+ */
75
+ processingConcurrency?: number;
62
76
  /**
63
77
  * Max delivery attempts before moving to dead letter stream.
64
78
  * @default 5
@@ -133,6 +147,7 @@ declare class RedisStreamTransport implements EventTransport {
133
147
  private consumer;
134
148
  private blockTimeMs;
135
149
  private batchSize;
150
+ private processingConcurrency;
136
151
  private maxRetries;
137
152
  private claimTimeoutMs;
138
153
  private deadLetterStream;
@@ -207,6 +222,13 @@ declare class RedisStreamTransport implements EventTransport {
207
222
  private ensureGroup;
208
223
  private pollLoop;
209
224
  private readNewMessages;
225
+ /**
226
+ * Process a batch's entries with bounded concurrency
227
+ * (`processingConcurrency`, default 1 = strict order). Worker-pool over a
228
+ * shared index — at most N entries in flight, each entry's own handlers
229
+ * still sequential inside `processEntry`.
230
+ */
231
+ private processEntries;
210
232
  private claimPending;
211
233
  private processEntry;
212
234
  private getMatchingHandlers;
@@ -0,0 +1,108 @@
1
+ import { f as createError } from "./errors-DkO8Nk6-.mjs";
2
+ import { n as evaluateAndApplyPermission } from "./applyPermissionResult-BnyVE9eD.mjs";
3
+ //#region src/core/middlewares/permissions.ts
4
+ /**
5
+ * Build a PermissionContext from a Fastify request. Extracted so the CRUD
6
+ * permission middleware and the dynamic action-permission check use the same
7
+ * field layout — divergence here silently broke policy filters for actions.
8
+ */
9
+ function buildPermissionContext(req, opts) {
10
+ const reqWithExtras = req;
11
+ const params = req.params;
12
+ return {
13
+ user: reqWithExtras.user ?? null,
14
+ request: req,
15
+ resource: opts.resource,
16
+ action: opts.action,
17
+ resourceId: opts.resourceId ?? params?.id,
18
+ params,
19
+ data: opts.data ?? req.body
20
+ };
21
+ }
22
+ /**
23
+ * Static per-route CRUD permission gate. The permission and action are known
24
+ * at route-registration time, so the gate is a plain preHandler.
25
+ *
26
+ * Actions use the dynamic counterpart `buildActionPermissionMw` — their
27
+ * permission is resolved from `body.action` at request time.
28
+ */
29
+ function buildCrudPermissionMw(permissionCheck, resourceName, action) {
30
+ if (!permissionCheck) return null;
31
+ return async (req, reply) => {
32
+ await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
33
+ resource: resourceName,
34
+ action
35
+ }), req, reply);
36
+ };
37
+ }
38
+ /**
39
+ * Dynamic per-action permission gate for the action router.
40
+ *
41
+ * Resolves the permission from `body.action` at request time and runs
42
+ * `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
43
+ * `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
44
+ * (idempotency) and `routeGuards` run. Previously this check lived inside
45
+ * the main action handler, which meant idempotency recorded unauthorized
46
+ * requests and route guards saw unfiltered scope — the very divergence
47
+ * routerShared exists to prevent.
48
+ *
49
+ * Also acts as a defensive fallback for invalid action names — the
50
+ * `oneOf` body schema normally rejects these at AJV validation, but
51
+ * hosts that disable schema validation still get a 400 here.
52
+ */
53
+ function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
54
+ const enumSet = new Set(actionEnum);
55
+ const validActions = [...actionEnum];
56
+ return async (req, reply) => {
57
+ const body = req.body ?? {};
58
+ const action = body.action;
59
+ if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
60
+ const permissionCheck = actionPermissions[action] ?? globalAuth;
61
+ if (!permissionCheck) return;
62
+ const { action: _discard, ...data } = body;
63
+ const params = req.params;
64
+ await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
65
+ resource: resourceName,
66
+ action,
67
+ resourceId: params?.id,
68
+ data
69
+ }), req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
70
+ };
71
+ }
72
+ //#endregion
73
+ //#region src/core/middlewares/registerRoute.ts
74
+ /**
75
+ * Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
76
+ *
77
+ * Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
78
+ * overlaps before Fastify ever runs, but a few cases still slip through to
79
+ * Fastify itself: two presets contributing the same `routes` entry, hosts
80
+ * mounting two resources at the same prefix, custom routes that share a
81
+ * URL with another plugin. Fastify's default message ("Method 'GET'
82
+ * already declared for route '/'") gives no hint at the resource-shaped
83
+ * fix — `disabledRoutes: ['list']` — so wrap it with one.
84
+ */
85
+ function tryRegisterRoute(fastify, opts, context) {
86
+ try {
87
+ fastify.route(opts);
88
+ } catch (err) {
89
+ const fastifyErr = err;
90
+ if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
91
+ const method = opts.method;
92
+ const url = opts.url;
93
+ const opHint = context.op ? ` (op: ${context.op})` : "";
94
+ const fixHint = context.op && [
95
+ "list",
96
+ "get",
97
+ "create",
98
+ "update",
99
+ "delete"
100
+ ].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
101
+ const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
102
+ enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
103
+ enhanced.cause = err;
104
+ throw enhanced;
105
+ }
106
+ }
107
+ //#endregion
108
+ export { buildActionPermissionMw as n, buildCrudPermissionMw as r, tryRegisterRoute as t };
@@ -1,5 +1,5 @@
1
- import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-BBsvQt7U.mjs";
2
- import "../fastify-augmentation-g_f9omSc.mjs";
1
+ import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-DPRH0Cme.mjs";
2
+ import "../fastify-augmentation-OzYGIiU1.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
  import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
5
5
  //#region src/registry/assertNoTenantData.d.ts
@@ -1,3 +1,3 @@
1
- import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-vcOCtPij.mjs";
1
+ import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-9km_w1aP.mjs";
2
2
  import { t as ResourceRegistry } from "../ResourceRegistry-y9Hnypus.mjs";
3
3
  export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
@@ -79,6 +79,9 @@ async function assertNoTenantData(registry, options) {
79
79
  *
80
80
  * - `skip` strategy: returns `path: 'skipped'` without touching the repo.
81
81
  * - `purgeByField` present: routes through it (chunked, progress, abort).
82
+ * - `purgeByField` absent + `requireChunked`: returns `path: 'unsupported'`
83
+ * (`arc.purge.chunked_required`) — authoritative cleanup never runs the
84
+ * unbounded fallback.
82
85
  * - `purgeByField` absent + hard strategy: legacy `deleteMany` fallback.
83
86
  * - `purgeByField` absent + non-hard strategy: returns `path: 'unsupported'`
84
87
  * with a clear error — soft/anonymize require the new primitive.
@@ -111,6 +114,18 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
111
114
  ...result.error ? { error: result.error } : {}
112
115
  };
113
116
  }
117
+ if (options.requireChunked) return {
118
+ resource: resourceName,
119
+ tenantField,
120
+ strategy,
121
+ processed: 0,
122
+ ok: false,
123
+ path: "unsupported",
124
+ error: {
125
+ code: "arc.purge.chunked_required",
126
+ message: `Resource '${resourceName}' requires chunked purge for authoritative cleanup, but its adapter repository does not implement \`purgeByField\`. The legacy unbounded \`deleteMany\` fallback is disabled here (no chunking / progress / abort). Upgrade the adapter (@classytic/mongokit ≥ 3.13.4 / @classytic/sqlitekit ≥ 0.3.4).`
127
+ }
128
+ };
114
129
  if (strategy.type !== "hard") return {
115
130
  resource: resourceName,
116
131
  tenantField,
@@ -0,0 +1,44 @@
1
+ //#region src/lock/renewingLease.ts
2
+ /**
3
+ * Start renewing an ALREADY-ACQUIRED lease. The caller performed the
4
+ * initial `tryAcquire` (its contention policy differs per consumer); this
5
+ * keeps the lease alive until `stop()`.
6
+ */
7
+ function startRenewingLease(options) {
8
+ const { lock, name, holderId, leaseMs, onLost, onError } = options;
9
+ const renewEveryMs = options.renewEveryMs ?? Math.max(10, Math.floor(leaseMs / 2));
10
+ let stopped = false;
11
+ let lost = false;
12
+ let timer;
13
+ let inFlight;
14
+ const scheduleNext = () => {
15
+ if (stopped) return;
16
+ timer = setTimeout(() => {
17
+ inFlight = Promise.resolve(lock.tryAcquire(name, holderId, leaseMs)).then((renewed) => {
18
+ if (!renewed && !lost) {
19
+ lost = true;
20
+ onLost?.();
21
+ }
22
+ }).catch((error) => {
23
+ onError?.(error);
24
+ }).finally(() => {
25
+ inFlight = void 0;
26
+ scheduleNext();
27
+ });
28
+ }, renewEveryMs);
29
+ timer.unref?.();
30
+ };
31
+ scheduleNext();
32
+ return {
33
+ get lost() {
34
+ return lost;
35
+ },
36
+ async stop() {
37
+ stopped = true;
38
+ if (timer) clearTimeout(timer);
39
+ await inFlight;
40
+ }
41
+ };
42
+ }
43
+ //#endregion
44
+ export { startRenewingLease as t };
@@ -85,8 +85,8 @@ function getTraceHeaders() {
85
85
  const store = requestContext.get();
86
86
  if (!store) return void 0;
87
87
  const headers = {};
88
- if (store.traceparent) headers["traceparent"] = store.traceparent;
89
- if (store.tracestate) headers["tracestate"] = store.tracestate;
88
+ if (store.traceparent) headers.traceparent = store.traceparent;
89
+ if (store.tracestate) headers.tracestate = store.tracestate;
90
90
  return Object.keys(headers).length > 0 ? headers : void 0;
91
91
  }
92
92
  //#endregion
@@ -0,0 +1,113 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
+ import { t as startRenewingLease } from "./renewingLease-3cuQH7h8.mjs";
3
+ import fp from "fastify-plugin";
4
+ //#region src/plugins/schedules.ts
5
+ var schedules_exports = /* @__PURE__ */ __exportAll({
6
+ default: () => schedules_default,
7
+ schedulesPlugin: () => schedulesPlugin
8
+ });
9
+ const MIN_SAFE_INTERVAL_MS = 1e3;
10
+ const MAX_DEFAULT_LEASE_MS = 3e5;
11
+ const schedulesPlugin = async (fastify, opts) => {
12
+ const { schedules, lock, enabled = true } = opts;
13
+ const holderId = opts.holderId ?? `arc-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
14
+ const stats = /* @__PURE__ */ new Map();
15
+ fastify.decorate("getScheduleStats", () => [...stats.values()].map((s) => ({ ...s })));
16
+ if (!enabled) {
17
+ fastify.log.debug("schedulesPlugin disabled — registered as no-op");
18
+ return;
19
+ }
20
+ const seen = /* @__PURE__ */ new Set();
21
+ for (const s of schedules) {
22
+ if (!s.name.trim()) throw new TypeError("[arc-schedules] every schedule needs a name");
23
+ if (seen.has(s.name)) throw new TypeError(`[arc-schedules] duplicate schedule name "${s.name}"`);
24
+ seen.add(s.name);
25
+ if (!Number.isFinite(s.every) || s.every <= 0) throw new TypeError(`[arc-schedules] "${s.name}": \`every\` must be a positive number of ms`);
26
+ if (typeof s.handler !== "function") throw new TypeError(`[arc-schedules] "${s.name}": \`handler\` must be a function`);
27
+ if (s.jitterMs !== void 0 && (!Number.isFinite(s.jitterMs) || s.jitterMs < 0)) throw new TypeError(`[arc-schedules] "${s.name}": \`jitterMs\` must be a non-negative number of ms`);
28
+ if (s.leaseMs !== void 0 && (!Number.isFinite(s.leaseMs) || s.leaseMs <= 0)) throw new TypeError(`[arc-schedules] "${s.name}": \`leaseMs\` must be a positive number of ms`);
29
+ if (s.every < MIN_SAFE_INTERVAL_MS) fastify.log.warn(`[arc-schedules] "${s.name}" runs every ${s.every}ms — sub-second schedules are almost always a bug in production (DB pressure, lock churn). Intended for tests only.`);
30
+ }
31
+ const timers = /* @__PURE__ */ new Map();
32
+ const inFlight = /* @__PURE__ */ new Map();
33
+ let closing = false;
34
+ async function tick(s) {
35
+ const stat = stats.get(s.name);
36
+ if (!stat) return;
37
+ let lease;
38
+ try {
39
+ if (lock) {
40
+ const lockName = `arc:schedule:${s.name}`;
41
+ const leaseMs = s.leaseMs ?? Math.min(Math.floor(s.every * .9), MAX_DEFAULT_LEASE_MS);
42
+ if (!await lock.tryAcquire(lockName, holderId, leaseMs)) {
43
+ stat.skippedByLock++;
44
+ return;
45
+ }
46
+ lease = startRenewingLease({
47
+ lock,
48
+ name: lockName,
49
+ holderId,
50
+ leaseMs,
51
+ onLost: () => fastify.log.warn({ schedule: s.name }, "[arc-schedules] lease renewal lost — another holder took the lock; this run may now overlap. Handlers should be idempotent."),
52
+ onError: (err) => fastify.log.warn({
53
+ err,
54
+ schedule: s.name
55
+ }, "[arc-schedules] lease renewal errored (lease may lapse mid-run)")
56
+ });
57
+ }
58
+ stat.runs++;
59
+ stat.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
60
+ await s.handler(fastify);
61
+ stat.lastError = null;
62
+ } catch (err) {
63
+ stat.failures++;
64
+ stat.lastError = err instanceof Error ? err.message : String(err);
65
+ fastify.log.error({
66
+ err,
67
+ schedule: s.name
68
+ }, "[arc-schedules] schedule run failed");
69
+ } finally {
70
+ await lease?.stop();
71
+ }
72
+ }
73
+ function scheduleNext(s, delay) {
74
+ if (closing) return;
75
+ const timer = setTimeout(() => {
76
+ const run = tick(s).finally(() => {
77
+ inFlight.delete(s.name);
78
+ scheduleNext(s, s.every);
79
+ });
80
+ inFlight.set(s.name, run);
81
+ }, delay);
82
+ timer.unref?.();
83
+ timers.set(s.name, timer);
84
+ }
85
+ fastify.addHook("onReady", async () => {
86
+ for (const s of schedules) {
87
+ if (s.enabled === false) continue;
88
+ stats.set(s.name, {
89
+ name: s.name,
90
+ runs: 0,
91
+ failures: 0,
92
+ skippedByLock: 0,
93
+ lastRunAt: null,
94
+ lastError: null
95
+ });
96
+ const jitter = !s.runOnStart && s.jitterMs ? Math.floor(Math.random() * s.jitterMs) : 0;
97
+ scheduleNext(s, s.runOnStart ? 0 : s.every + jitter);
98
+ }
99
+ if (stats.size > 0) fastify.log.debug(`[arc-schedules] ${stats.size} schedule(s) armed${lock ? ` (leader-safe, holder ${holderId})` : ""}`);
100
+ });
101
+ fastify.addHook("onClose", async () => {
102
+ closing = true;
103
+ for (const timer of timers.values()) clearTimeout(timer);
104
+ timers.clear();
105
+ await Promise.allSettled([...inFlight.values()]);
106
+ });
107
+ };
108
+ var schedules_default = fp(schedulesPlugin, {
109
+ name: "arc-schedules",
110
+ fastify: "5.x"
111
+ });
112
+ //#endregion
113
+ export { schedules_default as n, schedules_exports as r, schedulesPlugin as t };
@@ -1,6 +1,6 @@
1
1
  import { ErrorContract, ErrorDetail } from "@classytic/repo-core/errors";
2
- import { FastifyPluginAsyncTypebox, FastifyPluginCallbackTypebox, TypeBoxTypeProvider, TypeBoxValidatorCompiler } from "@fastify/type-provider-typebox";
3
- import { Static, TObject, TSchema, TSchema as TSchema$1, Type } from "@sinclair/typebox";
2
+ import { Static, TObject, TSchema, TSchema as TSchema$1, Type, Type as Type$1 } from "typebox";
3
+ import { FastifyPluginAsyncTypebox, FastifyPluginCallbackTypebox, TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
4
4
  //#region src/schemas/index.d.ts
5
5
  /**
6
6
  * Paginated list response — full union of every wire shape `toCanonicalList`
@@ -16,74 +16,74 @@ import { Static, TObject, TSchema, TSchema as TSchema$1, Type } from "@sinclair/
16
16
  * variant when your endpoint never emits the others. HTTP status
17
17
  * discriminates success vs error — no `success` field.
18
18
  */
19
- declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
20
- method: import("@sinclair/typebox").TLiteral<"offset">;
21
- data: import("@sinclair/typebox").TArray<T>;
22
- page: import("@sinclair/typebox").TInteger;
23
- limit: import("@sinclair/typebox").TInteger;
24
- total: import("@sinclair/typebox").TInteger;
25
- pages: import("@sinclair/typebox").TInteger;
26
- hasNext: import("@sinclair/typebox").TBoolean;
27
- hasPrev: import("@sinclair/typebox").TBoolean;
28
- }>, import("@sinclair/typebox").TObject<{
29
- method: import("@sinclair/typebox").TLiteral<"keyset">;
30
- data: import("@sinclair/typebox").TArray<T>;
31
- limit: import("@sinclair/typebox").TInteger;
32
- hasMore: import("@sinclair/typebox").TBoolean;
33
- next: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNull]>;
34
- }>, import("@sinclair/typebox").TObject<{
35
- method: import("@sinclair/typebox").TLiteral<"aggregate">;
36
- data: import("@sinclair/typebox").TArray<T>;
37
- page: import("@sinclair/typebox").TInteger;
38
- limit: import("@sinclair/typebox").TInteger;
39
- total: import("@sinclair/typebox").TInteger;
40
- pages: import("@sinclair/typebox").TInteger;
41
- hasNext: import("@sinclair/typebox").TBoolean;
42
- hasPrev: import("@sinclair/typebox").TBoolean;
43
- }>, import("@sinclair/typebox").TObject<{
44
- data: import("@sinclair/typebox").TArray<T>;
19
+ declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TUnion<[Type$1.TObject<{
20
+ method: Type$1.TLiteral<"offset">;
21
+ data: Type$1.TArray<T>;
22
+ page: Type$1.TInteger;
23
+ limit: Type$1.TInteger;
24
+ total: Type$1.TInteger;
25
+ pages: Type$1.TInteger;
26
+ hasNext: Type$1.TBoolean;
27
+ hasPrev: Type$1.TBoolean;
28
+ }>, Type$1.TObject<{
29
+ method: Type$1.TLiteral<"keyset">;
30
+ data: Type$1.TArray<T>;
31
+ limit: Type$1.TInteger;
32
+ hasMore: Type$1.TBoolean;
33
+ next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
34
+ }>, Type$1.TObject<{
35
+ method: Type$1.TLiteral<"aggregate">;
36
+ data: Type$1.TArray<T>;
37
+ page: Type$1.TInteger;
38
+ limit: Type$1.TInteger;
39
+ total: Type$1.TInteger;
40
+ pages: Type$1.TInteger;
41
+ hasNext: Type$1.TBoolean;
42
+ hasPrev: Type$1.TBoolean;
43
+ }>, Type$1.TObject<{
44
+ data: Type$1.TArray<T>;
45
45
  }>]>;
46
46
  /** Offset variant — `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`. */
47
- declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T): import("@sinclair/typebox").TObject<{
48
- method: import("@sinclair/typebox").TLiteral<"offset">;
49
- data: import("@sinclair/typebox").TArray<T>;
50
- page: import("@sinclair/typebox").TInteger;
51
- limit: import("@sinclair/typebox").TInteger;
52
- total: import("@sinclair/typebox").TInteger;
53
- pages: import("@sinclair/typebox").TInteger;
54
- hasNext: import("@sinclair/typebox").TBoolean;
55
- hasPrev: import("@sinclair/typebox").TBoolean;
47
+ declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
48
+ method: Type$1.TLiteral<"offset">;
49
+ data: Type$1.TArray<T>;
50
+ page: Type$1.TInteger;
51
+ limit: Type$1.TInteger;
52
+ total: Type$1.TInteger;
53
+ pages: Type$1.TInteger;
54
+ hasNext: Type$1.TBoolean;
55
+ hasPrev: Type$1.TBoolean;
56
56
  }>;
57
57
  /** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
58
- declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): import("@sinclair/typebox").TObject<{
59
- method: import("@sinclair/typebox").TLiteral<"keyset">;
60
- data: import("@sinclair/typebox").TArray<T>;
61
- limit: import("@sinclair/typebox").TInteger;
62
- hasMore: import("@sinclair/typebox").TBoolean;
63
- next: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNull]>;
58
+ declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
59
+ method: Type$1.TLiteral<"keyset">;
60
+ data: Type$1.TArray<T>;
61
+ limit: Type$1.TInteger;
62
+ hasMore: Type$1.TBoolean;
63
+ next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
64
64
  }>;
65
65
  /** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
66
- declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T): import("@sinclair/typebox").TObject<{
67
- method: import("@sinclair/typebox").TLiteral<"aggregate">;
68
- data: import("@sinclair/typebox").TArray<T>;
69
- page: import("@sinclair/typebox").TInteger;
70
- limit: import("@sinclair/typebox").TInteger;
71
- total: import("@sinclair/typebox").TInteger;
72
- pages: import("@sinclair/typebox").TInteger;
73
- hasNext: import("@sinclair/typebox").TBoolean;
74
- hasPrev: import("@sinclair/typebox").TBoolean;
66
+ declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
67
+ method: Type$1.TLiteral<"aggregate">;
68
+ data: Type$1.TArray<T>;
69
+ page: Type$1.TInteger;
70
+ limit: Type$1.TInteger;
71
+ total: Type$1.TInteger;
72
+ pages: Type$1.TInteger;
73
+ hasNext: Type$1.TBoolean;
74
+ hasPrev: Type$1.TBoolean;
75
75
  }>;
76
76
  /** Bare variant — `{ data }`, no `method` discriminant. */
77
- declare function ArcBareListResponse<T extends TSchema$1>(itemSchema: T): import("@sinclair/typebox").TObject<{
78
- data: import("@sinclair/typebox").TArray<T>;
77
+ declare function ArcBareListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
78
+ data: Type$1.TArray<T>;
79
79
  }>;
80
80
  /**
81
81
  * Delete response — `{ message, id?, soft? }` raw at the top level.
82
82
  */
83
- declare function ArcDeleteResponse(): import("@sinclair/typebox").TObject<{
84
- message: import("@sinclair/typebox").TString;
85
- id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
86
- soft: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
83
+ declare function ArcDeleteResponse(): Type$1.TObject<{
84
+ message: Type$1.TString;
85
+ id: Type$1.TOptional<Type$1.TString>;
86
+ soft: Type$1.TOptional<Type$1.TBoolean>;
87
87
  }>;
88
88
  /**
89
89
  * Single field-scoped error detail — `Type.Unsafe<ErrorDetail>` over the
@@ -95,7 +95,7 @@ declare function ArcDeleteResponse(): import("@sinclair/typebox").TObject<{
95
95
  * Exported standalone so hosts can embed it in custom 422 / 409 response
96
96
  * schemas.
97
97
  */
98
- declare function ArcErrorDetail(): import("@sinclair/typebox").TUnsafe<ErrorDetail>;
98
+ declare function ArcErrorDetail(): Type$1.TUnsafe<ErrorDetail>;
99
99
  /**
100
100
  * Error response schema — `Type.Unsafe<ErrorContract>` over the canonical
101
101
  * JSON Schema `errorContractSchema` from `@classytic/repo-core/errors`.
@@ -104,7 +104,7 @@ declare function ArcErrorDetail(): import("@sinclair/typebox").TUnsafe<ErrorDeta
104
104
  * (`errorContractSchema`) and this TypeBox helper cannot drift —
105
105
  * literally one source.
106
106
  */
107
- declare function ArcErrorResponse(): import("@sinclair/typebox").TUnsafe<ErrorContract>;
107
+ declare function ArcErrorResponse(): Type$1.TUnsafe<ErrorContract>;
108
108
  /**
109
109
  * Standard pagination + sorting + filtering query parameters.
110
110
  * Matches Arc's list endpoint conventions.
@@ -120,12 +120,12 @@ declare function ArcErrorResponse(): import("@sinclair/typebox").TUnsafe<ErrorCo
120
120
  * projection forms at the request-validation gate even though arc passes
121
121
  * them through unchanged.
122
122
  */
123
- declare function ArcPaginationQuery(): import("@sinclair/typebox").TObject<{
124
- page: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
125
- limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
126
- sort: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
127
- select: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>, import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<0>, import("@sinclair/typebox").TLiteral<1>]>>]>>;
128
- populate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
123
+ declare function ArcPaginationQuery(): Type$1.TObject<{
124
+ page: Type$1.TOptional<Type$1.TInteger>;
125
+ limit: Type$1.TOptional<Type$1.TInteger>;
126
+ sort: Type$1.TOptional<Type$1.TString>;
127
+ select: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TArray<Type$1.TString>, Type$1.TRecord<"^.*$", Type$1.TUnion<[Type$1.TLiteral<0>, Type$1.TLiteral<1>]>>]>>;
128
+ populate: Type$1.TOptional<Type$1.TAny>;
129
129
  }>;
130
130
  //#endregion
131
- export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, type FastifyPluginAsyncTypebox, type FastifyPluginCallbackTypebox, type Static, type TObject, type TSchema, Type, type TypeBoxTypeProvider, TypeBoxValidatorCompiler };
131
+ export { ArcAggregateListResponse, ArcBareListResponse, ArcDeleteResponse, ArcErrorDetail, ArcErrorResponse, ArcKeysetListResponse, ArcListResponse, ArcOffsetListResponse, ArcPaginationQuery, type FastifyPluginAsyncTypebox, type FastifyPluginCallbackTypebox, type Static, type TObject, type TSchema, Type, type TypeBoxTypeProvider };
@@ -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";