@classytic/arc 2.22.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 (147) hide show
  1. package/dist/{BaseController-C6FlRWxF.mjs → BaseController--M8EMzt0.mjs} +92 -22
  2. package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
  3. package/dist/{ResourceRegistry-7RnrEWNP.mjs → ResourceRegistry-y9Hnypus.mjs} +1 -0
  4. package/dist/actionPermissions-CAphcmUG.mjs +24 -0
  5. package/dist/applyPermissionResult-Tw4iXtlf.mjs +264 -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 +264 -260
  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/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/describe.mjs +1 -1
  15. package/dist/cli/commands/docs.mjs +2 -2
  16. package/dist/cli/commands/generate.mjs +1 -1
  17. package/dist/cli/commands/init.mjs +1 -1
  18. package/dist/cli/commands/introspect.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 +4 -4
  23. package/dist/{core-b6fgFgnd.mjs → core-EUNu0u07.mjs} +298 -12
  24. package/dist/{createActionRouter-h1cEEaEX.mjs → createActionRouter-B6GvAuAX.mjs} +4 -3
  25. package/dist/{createAggregationRouter-B1C0kvTL.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
  26. package/dist/docs/index.d.mts +1 -1
  27. package/dist/docs/index.mjs +2 -2
  28. package/dist/{elevation-COpYKt2h.mjs → elevation-CIz1GjGm.mjs} +4 -3
  29. package/dist/{errorHandler-BTzXSwIg.mjs → errorHandler-Bq2OeEQL.mjs} +10 -7
  30. package/dist/{eventPlugin-A6EbthXR.d.mts → eventPlugin-Bg3cP2_u.d.mts} +38 -3
  31. package/dist/{eventPlugin-DJG2D7sy.mjs → eventPlugin-DrQ_FesK.mjs} +22 -4
  32. package/dist/events/index.d.mts +57 -314
  33. package/dist/events/index.mjs +168 -93
  34. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  35. package/dist/events/transports/redis-stream-entry.mjs +24 -2
  36. package/dist/factory/index.d.mts +21 -6
  37. package/dist/factory/index.mjs +58 -18
  38. package/dist/{fastify-augmentation-9_fuetB4.d.mts → fastify-augmentation-dPNBVeIj.d.mts} +25 -2
  39. package/dist/{generate-usRSdQmP.mjs → generate-D0wZupBg.mjs} +1 -1
  40. package/dist/headers-BvlQSDMB.mjs +45 -0
  41. package/dist/health-CB_m4S32.mjs +377 -0
  42. package/dist/hooks/index.d.mts +1 -1
  43. package/dist/idempotency/index.d.mts +7 -0
  44. package/dist/idempotency/index.mjs +32 -20
  45. package/dist/{index-ogIHvOjo.d.mts → index-BMnbzvtO.d.mts} +70 -4
  46. package/dist/{integrations/jobs.d.mts → index-CG4yqMDB.d.mts} +82 -2
  47. package/dist/{index-DbtgCOCj.d.mts → index-DPRH0Cme.d.mts} +46 -94
  48. package/dist/{module-B4u10iOJ.d.mts → index-KUoxjIq1.d.mts} +263 -23
  49. package/dist/{index-D8MKYGDa.d.mts → index-f4PTrk3B.d.mts} +37 -5
  50. package/dist/index.d.mts +6 -6
  51. package/dist/index.mjs +6 -5
  52. package/dist/{init-DleK3p9f.mjs → init-tlRSt667.mjs} +206 -205
  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 +68 -5
  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 +149 -22
  63. package/dist/integrations/webhooks.mjs +186 -42
  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/interface-n5hpKg1d.mjs +16 -0
  69. package/dist/{loadResourcesFromEntry-OMCvIEE7.mjs → loadResourcesFromEntry-kvGT6EcO.mjs} +1 -1
  70. package/dist/{mcpPlugin-ByA2tf6D.mjs → mcpPlugin-Cb7olw0j.mjs} +316 -45
  71. package/dist/{memory-CLXA2VeJ.mjs → memory-CmnzXE5Z.mjs} +29 -0
  72. package/dist/middleware/index.d.mts +17 -1
  73. package/dist/middleware/index.mjs +1 -1
  74. package/dist/migrations/index.d.mts +94 -44
  75. package/dist/migrations/index.mjs +196 -44
  76. package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
  77. package/dist/{openapi-BRpMZD7C.mjs → openapi-DrLtog_B.mjs} +4 -4
  78. package/dist/permissions/index.d.mts +3 -3
  79. package/dist/permissions/index.mjs +4 -3
  80. package/dist/{permissions-BJMuKMl7.mjs → permissions-BrtiBKGs.mjs} +305 -492
  81. package/dist/pipeline/index.d.mts +1 -1
  82. package/dist/{actionPermissions-ze70xXJi.mjs → pipeline-BN5G05rd.mjs} +4 -27
  83. package/dist/plugins/index.d.mts +7 -78
  84. package/dist/plugins/index.mjs +14 -97
  85. package/dist/plugins/response-cache.mjs +1 -0
  86. package/dist/plugins/tracing-entry.mjs +1 -1
  87. package/dist/{index-BEu4wjbL.d.mts → preloadResources-0oe-JSSS.d.mts} +59 -50
  88. package/dist/{createApp-Ci7yG5gv.mjs → preloadResources-DfyngIN9.mjs} +240 -68
  89. package/dist/presets/filesUpload.d.mts +3 -3
  90. package/dist/presets/filesUpload.mjs +3 -3
  91. package/dist/presets/index.d.mts +2 -1
  92. package/dist/presets/index.mjs +1 -1
  93. package/dist/presets/multiTenant.d.mts +1 -1
  94. package/dist/presets/multiTenant.mjs +1 -1
  95. package/dist/presets/search.d.mts +2 -2
  96. package/dist/presets/search.mjs +1 -1
  97. package/dist/{presets-gw0ciIUD.mjs → presets-DwFDVKP0.mjs} +2 -2
  98. package/dist/{queryCachePlugin-DzqLN904.mjs → queryCachePlugin-DSGuSVLW.mjs} +39 -8
  99. package/dist/{queryCachePlugin-CfYWq-Ak.d.mts → queryCachePlugin-br4otiqF.d.mts} +2 -2
  100. package/dist/{registerRoute-xHc42fwB.mjs → rateLimit-DrED-C4v.mjs} +2 -109
  101. package/dist/realtime-Cuw_deA-.mjs +375 -0
  102. package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
  103. package/dist/registerRoute-v0Wb6Plt.mjs +108 -0
  104. package/dist/registry/index.d.mts +2 -2
  105. package/dist/registry/index.mjs +1 -1
  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/{schemaConverter-Ds5reSGs.mjs → schemaConverter-DJvodiHj.mjs} +4 -1
  110. package/dist/{schemaIR-B-6SPe7g.mjs → schemaIR-Ca7ySNPx.mjs} +1 -1
  111. package/dist/schemas/index.d.mts +67 -67
  112. package/dist/schemas/index.mjs +2 -3
  113. package/dist/scim/index.mjs +1 -2
  114. package/dist/scope/index.d.mts +2 -2
  115. package/dist/scope/index.mjs +4 -4
  116. package/dist/scope-DfaPntpo.mjs +36 -0
  117. package/dist/simpleEqualityMatcher-Cg05HPsn.mjs +68 -0
  118. package/dist/{sse-CfUjwjDk.mjs → sse-BQ1w0dfl.mjs} +8 -31
  119. package/dist/sseStream-BQ7Qaynp.mjs +57 -0
  120. package/dist/testing/index.d.mts +4 -3
  121. package/dist/testing/index.mjs +2 -2
  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-DH0GupI7.d.mts → types-DjC-KUCz.d.mts} +118 -20
  126. package/dist/{types-Csxga7d3.d.mts → types-Dmw-6Qeo.d.mts} +96 -4
  127. package/dist/{types-Bc42CUyk.d.mts → types-QFGc3Cjv.d.mts} +37 -2
  128. package/dist/usage/index.d.mts +3 -52
  129. package/dist/usage/index.mjs +0 -0
  130. package/dist/userHelpers-Cg_y3fBz.mjs +29 -0
  131. package/dist/utils/index.d.mts +90 -57
  132. package/dist/utils/index.mjs +7 -3
  133. package/dist/{utils-FhJ88TJE.mjs → utils-taUVD4p3.mjs} +47 -385
  134. package/dist/{validate-C2mGgSXg.mjs → validate-Bk44F2yT.mjs} +1 -1
  135. package/dist/{versioning-DLwklPlG.d.mts → versioning-DXgY5BUm.d.mts} +52 -31
  136. package/dist/{versioning-Dk-71iIz.mjs → versioning-gKWyryT3.mjs} +8 -1
  137. package/dist/{websocket-uuWqM6UY.d.mts → websocket-_Pd0CgR0.d.mts} +15 -1
  138. package/package.json +526 -453
  139. package/skills/arc/references/api-reference.md +3 -1
  140. package/skills/arc/references/integrations.md +20 -11
  141. package/skills/arc/references/production.md +734 -696
  142. package/dist/QueryCache-Z1B38koh.d.mts +0 -40
  143. package/dist/betterAuthOpenApi-CV1lxpRX.mjs +0 -2
  144. package/dist/health-ScyjIVo5.mjs +0 -202
  145. package/dist/requestId-5BhTVzBv.mjs +0 -113
  146. package/dist/scope-k15RXfcq.mjs +0 -64
  147. package/dist/types-XTApa8b6.mjs +0 -24
@@ -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 };
@@ -100,7 +100,10 @@ function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET, mode = "warn") {
100
100
  if (isZodSchema(input)) {
101
101
  if (!_toJSONSchema) return unconvertible(mode, "A Zod schema was passed but zod v4 is not installed (or not yet loaded). Install zod >=4 — arc converts via zod's native z.toJSONSchema().");
102
102
  try {
103
- const converted = _toJSONSchema(input, { target });
103
+ const converted = _toJSONSchema(input, {
104
+ target,
105
+ unrepresentable: mode === "throw" ? "throw" : "any"
106
+ });
104
107
  if ("$schema" in converted) delete converted.$schema;
105
108
  return converted;
106
109
  } catch (cause) {
@@ -1,4 +1,4 @@
1
- import { o as toJsonSchema } from "./schemaConverter-Ds5reSGs.mjs";
1
+ import { o as toJsonSchema } from "./schemaConverter-DJvodiHj.mjs";
2
2
  //#region src/core/schemaIR.ts
3
3
  /**
4
4
  * Schema IR — one canonical representation, two adapters.
@@ -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-k15RXfcq.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 };
@@ -0,0 +1,36 @@
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";
4
+ //#region src/scope/resolveOrgFromHeader.ts
5
+ /**
6
+ * Create a preHandler hook that resolves org scope from a header.
7
+ * Must run AFTER authentication so `request.user` is populated.
8
+ * If the header is present and user is a member, sets `request.scope` to `member`.
9
+ * If the header is absent, scope stays as-is (typically `authenticated`).
10
+ */
11
+ function resolveOrgFromHeader(options) {
12
+ const { header = "x-organization-id", resolveMembership } = options;
13
+ return async (request, _reply) => {
14
+ const orgId = requireSingleHeaderValue(request.headers, header);
15
+ if (!orgId) return;
16
+ const scope = request.scope;
17
+ if (!scope || scope.kind === "public") throw new UnauthorizedError("Authentication required for organization access");
18
+ if (scope.kind === "elevated") return;
19
+ const user = request.user;
20
+ if (!user) throw new UnauthorizedError("Authentication required for organization access");
21
+ const userId = String(user.id ?? user._id ?? "");
22
+ if (!userId) throw new UnauthorizedError("User identity required for organization access");
23
+ const membership = await resolveMembership(userId, orgId);
24
+ if (!membership) throw new OrgAccessDeniedError("Not a member of this organization");
25
+ const carriedRoles = "userRoles" in scope && Array.isArray(scope.userRoles) ? scope.userRoles : void 0;
26
+ request.scope = {
27
+ kind: "member",
28
+ userId: userId || void 0,
29
+ userRoles: carriedRoles?.length ? carriedRoles : getUserRoles(user),
30
+ organizationId: orgId,
31
+ orgRoles: membership.roles
32
+ };
33
+ };
34
+ }
35
+ //#endregion
36
+ export { resolveOrgFromHeader as t };
@@ -0,0 +1,68 @@
1
+ //#region src/utils/simpleEqualityMatcher.ts
2
+ /**
3
+ * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
4
+ * matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
5
+ *
6
+ * **What it does:** for each `[key, expected]` in the filter, compares
7
+ * `item[key]` to `expected` via string coercion (so Mongo `ObjectId` values
8
+ * match their string representation) and returns `true` only if every
9
+ * filter entry matches. Array item values are matched implicitly (contains).
10
+ *
11
+ * **What it does NOT do:**
12
+ * - No `$eq` / `$ne` / `$in` / `$nin` / `$gt` / `$lt` / `$regex` / `$exists`
13
+ * - No `$and` / `$or`
14
+ * - No dot-path traversal (`"owner.id"`)
15
+ * - No schema-specific coercion
16
+ *
17
+ * **Why it exists:** 95%+ of arc's `_policyFilters` are produced by built-in
18
+ * permission helpers and are shaped like `{ ownerId: "u1" }` or
19
+ * `{ organizationId: "org_x" }` — flat equality. For that common shape,
20
+ * this helper is a safe, tested, 15-line defense-in-depth matcher that
21
+ * hosts using minimal repos (no `getOne(compoundFilter)` DB path) can opt
22
+ * into without arc shipping a full Mongo-syntax engine.
23
+ *
24
+ * **When to use:**
25
+ * - Your adapter/repo doesn't natively filter on `getOne(compoundFilter)`
26
+ * - Your `_policyFilters` are flat equality (from arc's built-in permission helpers)
27
+ * - You want defense-in-depth on `validateItemAccess` / `fetchDetailed`'s `getById` fallback
28
+ *
29
+ * **When NOT to use:**
30
+ * - Your `_policyFilters` use operators (`$in`, `$ne`, etc.) — supply a
31
+ * native matcher (mongokit's repo does the filter at the DB layer; for
32
+ * custom repos, wrap the kit's own predicate engine).
33
+ * - You're a mongokit / sqlitekit / Prisma user — the DB-level filter
34
+ * applied by `getOne(compoundFilter)` already covers this.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { simpleEqualityMatcher } from '@classytic/arc/utils';
39
+ *
40
+ * // On a custom adapter
41
+ * const adapter: DataAdapter = {
42
+ * repository,
43
+ * type: 'custom',
44
+ * name: 'in-memory',
45
+ * matchesFilter: simpleEqualityMatcher,
46
+ * };
47
+ *
48
+ * // Or directly on BaseController for ad-hoc controllers
49
+ * new BaseController(repo, { matchesFilter: simpleEqualityMatcher });
50
+ * ```
51
+ */
52
+ function simpleEqualityMatcher(item, filters) {
53
+ if (!item || typeof item !== "object") return false;
54
+ const obj = item;
55
+ for (const [key, expected] of Object.entries(filters)) {
56
+ if (expected && typeof expected === "object" && !Array.isArray(expected) && Object.getPrototypeOf(expected) === Object.prototype && Object.keys(expected).some((k) => k.startsWith("$"))) return false;
57
+ const actual = obj[key];
58
+ if (Array.isArray(actual)) {
59
+ const expectedStr = String(expected);
60
+ if (!actual.some((v) => String(v) === expectedStr)) return false;
61
+ continue;
62
+ }
63
+ if (String(actual) !== String(expected)) return false;
64
+ }
65
+ return true;
66
+ }
67
+ //#endregion
68
+ export { simpleEqualityMatcher as t };
@@ -1,7 +1,8 @@
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";
4
- import { a as promoteStreamTokenToHeader, n as forwardedStreamHeaders } from "./streaming-BQcW6_Kq.mjs";
3
+ import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-CSyCxl2V.mjs";
4
+ import { a as promoteStreamTokenToHeader } from "./streaming-BQcW6_Kq.mjs";
5
+ import { t as openSseStream } from "./sseStream-BQ7Qaynp.mjs";
5
6
  import fp from "fastify-plugin";
6
7
  //#region src/plugins/sse.ts
7
8
  var sse_exports = /* @__PURE__ */ __exportAll({
@@ -29,16 +30,8 @@ const ssePlugin = async (fastify, opts = {}) => {
29
30
  } }
30
31
  },
31
32
  handler: async (request, reply) => {
32
- reply.hijack();
33
- reply.raw.writeHead(200, {
34
- "content-type": "text/event-stream",
35
- "cache-control": "no-cache",
36
- connection: "keep-alive",
37
- "x-accel-buffering": "no",
38
- ...forwardedStreamHeaders(reply)
39
- });
40
- reply.raw.flushHeaders();
41
- const unsubscribers = [];
33
+ const stream = openSseStream(request, reply, { heartbeatMs: heartbeat });
34
+ stream.onCleanup(() => activeConnections.delete(stream.close));
42
35
  const requestOrgId = getOrgId(request.scope ?? PUBLIC_SCOPE);
43
36
  const dropOrgEvents = orgScoped && !requestOrgId;
44
37
  for (const pattern of patterns) {
@@ -57,27 +50,11 @@ const ssePlugin = async (fastify, opts = {}) => {
57
50
  timestamp: event.meta.timestamp
58
51
  }
59
52
  });
60
- if (!reply.raw.write(`event: ${event.type}\ndata: ${data}\n\n`)) {
61
- request.raw.destroy(/* @__PURE__ */ new Error("SSE connection terminated: slow client backpressure"));
62
- cleanup();
63
- }
53
+ stream.write(event.type, data);
64
54
  });
65
- unsubscribers.push(unsub);
55
+ stream.onCleanup(unsub);
66
56
  }
67
- const heartbeatTimer = setInterval(() => {
68
- if (!reply.raw.write(": heartbeat\n\n")) {
69
- request.raw.destroy(/* @__PURE__ */ new Error("SSE connection terminated: heartbeat backpressure"));
70
- cleanup();
71
- }
72
- }, heartbeat);
73
- const cleanup = () => {
74
- clearInterval(heartbeatTimer);
75
- for (const unsub of unsubscribers) unsub();
76
- if (!reply.raw.writableEnded) reply.raw.end();
77
- activeConnections.delete(cleanup);
78
- };
79
- activeConnections.add(cleanup);
80
- request.raw.on("close", cleanup);
57
+ activeConnections.add(stream.close);
81
58
  }
82
59
  };
83
60
  if (requireAuth) {