@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
@@ -1,40 +0,0 @@
1
- import { r as CacheStore } from "./interface-C1_WLTMK.mjs";
2
- //#region src/cache/QueryCache.d.ts
3
- /** Metadata wrapper stored in CacheStore */
4
- interface CacheEnvelope<T = unknown> {
5
- data: T;
6
- createdAt: number;
7
- staleAfter: number;
8
- expiresAt: number;
9
- tags: string[];
10
- }
11
- interface QueryCacheConfig {
12
- /** Seconds data is "fresh" (no revalidation). Default: 0 */
13
- staleTime?: number;
14
- /** Seconds stale data stays cached (SWR window). Default: 60 */
15
- gcTime?: number;
16
- /** Tags for group invalidation */
17
- tags?: string[];
18
- }
19
- type CacheStatus = "fresh" | "stale" | "miss";
20
- interface CacheResult<T> {
21
- data: T;
22
- status: CacheStatus;
23
- }
24
- declare class QueryCache {
25
- private readonly store;
26
- constructor(store: CacheStore);
27
- get<T>(key: string): Promise<CacheResult<T>>;
28
- set<T>(key: string, data: T, config: QueryCacheConfig): Promise<void>;
29
- invalidate(key: string): Promise<void>;
30
- /** Get current version for a resource (defaults to 0 if not set) */
31
- getResourceVersion(resource: string): Promise<number>;
32
- /** Bump resource version — orphans all cached queries for this resource */
33
- bumpResourceVersion(resource: string): Promise<void>;
34
- /** Get current version for a tag */
35
- getTagVersion(tag: string): Promise<number>;
36
- /** Bump tag version — orphans all cached queries tagged with this tag */
37
- bumpTagVersion(tag: string): Promise<void>;
38
- }
39
- //#endregion
40
- export { QueryCacheConfig as a, QueryCache as i, CacheResult as n, CacheStatus as r, CacheEnvelope as t };
@@ -1,2 +0,0 @@
1
- import { extractBetterAuthOpenApi } from "./auth/index.mjs";
2
- export { extractBetterAuthOpenApi };
@@ -1,202 +0,0 @@
1
- import fp from "fastify-plugin";
2
- //#region src/plugins/health.ts
3
- function createHttpMetrics() {
4
- return {
5
- requestsTotal: {},
6
- requestDurations: [],
7
- _ringIndex: 0,
8
- startTime: Date.now()
9
- };
10
- }
11
- const healthPlugin = async (fastify, opts = {}) => {
12
- const { prefix = "/_health", checks = [], metrics = false, metricsCollector, version, collectHttpMetrics = metrics } = opts;
13
- const httpMetrics = createHttpMetrics();
14
- fastify.get(prefix, { schema: {
15
- tags: ["Health"],
16
- summary: "Liveness probe (alias for /live)",
17
- description: "Returns 200 if the process is alive",
18
- response: { 200: {
19
- type: "object",
20
- properties: {
21
- status: {
22
- type: "string",
23
- enum: ["ok"]
24
- },
25
- timestamp: { type: "string" },
26
- version: { type: "string" }
27
- }
28
- } }
29
- } }, async () => ({
30
- status: "ok",
31
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
32
- ...version ? { version } : {}
33
- }));
34
- fastify.get(`${prefix}/live`, { schema: {
35
- tags: ["Health"],
36
- summary: "Liveness probe",
37
- description: "Returns 200 if the process is alive",
38
- response: { 200: {
39
- type: "object",
40
- properties: {
41
- status: {
42
- type: "string",
43
- enum: ["ok"]
44
- },
45
- timestamp: { type: "string" },
46
- version: { type: "string" }
47
- }
48
- } }
49
- } }, async () => {
50
- return {
51
- status: "ok",
52
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
53
- ...version ? { version } : {}
54
- };
55
- });
56
- fastify.get(`${prefix}/ready`, { schema: {
57
- tags: ["Health"],
58
- summary: "Readiness probe",
59
- description: "Returns 200 if all dependencies are healthy",
60
- response: {
61
- 200: {
62
- type: "object",
63
- properties: {
64
- status: {
65
- type: "string",
66
- enum: ["ready", "not_ready"]
67
- },
68
- timestamp: { type: "string" },
69
- checks: {
70
- type: "array",
71
- items: {
72
- type: "object",
73
- properties: {
74
- name: { type: "string" },
75
- healthy: { type: "boolean" },
76
- duration: { type: "number" },
77
- error: { type: "string" }
78
- }
79
- }
80
- }
81
- }
82
- },
83
- 503: {
84
- type: "object",
85
- properties: {
86
- status: {
87
- type: "string",
88
- enum: ["not_ready"]
89
- },
90
- timestamp: { type: "string" },
91
- checks: { type: "array" }
92
- }
93
- }
94
- }
95
- } }, async (_, reply) => {
96
- const results = await runChecks(checks);
97
- const criticalFailed = results.some((r) => !r.healthy && (checks.find((c) => c.name === r.name)?.critical ?? true));
98
- const response = {
99
- status: criticalFailed ? "not_ready" : "ready",
100
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
101
- checks: results
102
- };
103
- if (criticalFailed) reply.code(503);
104
- return response;
105
- });
106
- if (metrics) fastify.get(`${prefix}/metrics`, async (_, reply) => {
107
- reply.type("text/plain; charset=utf-8");
108
- if (metricsCollector) return await metricsCollector();
109
- const uptime = process.uptime();
110
- const memory = process.memoryUsage();
111
- const cpu = process.cpuUsage();
112
- const lines = [
113
- "# HELP process_uptime_seconds Process uptime in seconds",
114
- "# TYPE process_uptime_seconds gauge",
115
- `process_uptime_seconds ${uptime.toFixed(2)}`,
116
- "",
117
- "# HELP process_memory_heap_bytes Heap memory usage in bytes",
118
- "# TYPE process_memory_heap_bytes gauge",
119
- `process_memory_heap_bytes{type="used"} ${memory.heapUsed}`,
120
- `process_memory_heap_bytes{type="total"} ${memory.heapTotal}`,
121
- "",
122
- "# HELP process_memory_rss_bytes RSS memory in bytes",
123
- "# TYPE process_memory_rss_bytes gauge",
124
- `process_memory_rss_bytes ${memory.rss}`,
125
- "",
126
- "# HELP process_memory_external_bytes External memory in bytes",
127
- "# TYPE process_memory_external_bytes gauge",
128
- `process_memory_external_bytes ${memory.external}`,
129
- "",
130
- "# HELP process_cpu_user_microseconds User CPU time in microseconds",
131
- "# TYPE process_cpu_user_microseconds counter",
132
- `process_cpu_user_microseconds ${cpu.user}`,
133
- "",
134
- "# HELP process_cpu_system_microseconds System CPU time in microseconds",
135
- "# TYPE process_cpu_system_microseconds counter",
136
- `process_cpu_system_microseconds ${cpu.system}`,
137
- ""
138
- ];
139
- if (collectHttpMetrics && Object.keys(httpMetrics.requestsTotal).length > 0) {
140
- lines.push("# HELP http_requests_total Total HTTP requests by status code", "# TYPE http_requests_total counter");
141
- for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) lines.push(`http_requests_total{status="${status}"} ${count}`);
142
- lines.push("");
143
- if (httpMetrics.requestDurations.length > 0) {
144
- const sorted = [...httpMetrics.requestDurations].sort((a, b) => a - b);
145
- const p50 = sorted[Math.floor(sorted.length * .5)] || 0;
146
- const p95 = sorted[Math.floor(sorted.length * .95)] || 0;
147
- const p99 = sorted[Math.floor(sorted.length * .99)] || 0;
148
- const sum = sorted.reduce((a, b) => a + b, 0);
149
- lines.push("# HELP http_request_duration_milliseconds HTTP request duration", "# TYPE http_request_duration_milliseconds summary", `http_request_duration_milliseconds{quantile="0.5"} ${p50.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.95"} ${p95.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.99"} ${p99.toFixed(2)}`, `http_request_duration_milliseconds_sum ${sum.toFixed(2)}`, `http_request_duration_milliseconds_count ${sorted.length}`, "");
150
- }
151
- }
152
- return lines.join("\n");
153
- });
154
- if (collectHttpMetrics) fastify.addHook("onResponse", async (_request, reply) => {
155
- const duration = reply.elapsedTime;
156
- const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
157
- httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
158
- if (httpMetrics.requestDurations.length < 1e4) httpMetrics.requestDurations.push(duration);
159
- else httpMetrics.requestDurations[httpMetrics._ringIndex % 1e4] = duration;
160
- httpMetrics._ringIndex = httpMetrics._ringIndex + 1;
161
- });
162
- fastify.log?.debug?.(`Health plugin registered at ${prefix} (alias), ${prefix}/live, ${prefix}/ready${metrics ? `, ${prefix}/metrics` : ""}`);
163
- };
164
- /**
165
- * Run all health checks with timeout
166
- */
167
- async function runChecks(checks) {
168
- const results = [];
169
- for (const check of checks) {
170
- const start = Date.now();
171
- const timeout = check.timeout ?? 5e3;
172
- let timer;
173
- try {
174
- const checkPromise = Promise.resolve(check.check());
175
- const timeoutPromise = new Promise((_, reject) => {
176
- timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Health check timeout")), timeout);
177
- });
178
- const healthy = await Promise.race([checkPromise, timeoutPromise]);
179
- results.push({
180
- name: check.name,
181
- healthy: Boolean(healthy),
182
- duration: Date.now() - start
183
- });
184
- } catch (err) {
185
- results.push({
186
- name: check.name,
187
- healthy: false,
188
- duration: Date.now() - start,
189
- error: err.message
190
- });
191
- } finally {
192
- if (timer) clearTimeout(timer);
193
- }
194
- }
195
- return results;
196
- }
197
- var health_default = fp(healthPlugin, {
198
- name: "arc-health",
199
- fastify: "5.x"
200
- });
201
- //#endregion
202
- export { health_default as n, healthPlugin as t };
@@ -1,113 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import fp from "fastify-plugin";
3
- //#region src/plugins/requestId.ts
4
- /**
5
- * Request ID Plugin
6
- *
7
- * Propagates request IDs for distributed tracing.
8
- * - Accepts incoming x-request-id header (sanitized)
9
- * - Generates UUID if not provided
10
- * - Echoes the id on the response header + W3C trace-context propagation
11
- *
12
- * ID RESOLUTION LIVES AT THE SERVER LEVEL, NOT HERE. Fastify binds
13
- * `request.log = logger.child({ reqId: request.id })` at request
14
- * construction — before any hook runs — so an id assigned inside a hook
15
- * never reaches the logs. `createApp` therefore wires
16
- * {@link createRequestIdGenerator} into Fastify's `genReqId` option, and
17
- * this plugin simply adopts `request.id`. Standalone registration on a
18
- * bare Fastify instance falls back to the legacy in-hook resolution
19
- * (header echo / UUID), with the documented limitation that
20
- * `request.log`'s `reqId` binding predates the overwrite — pass
21
- * `genReqId: createRequestIdGenerator()` to `Fastify()` to fix that.
22
- *
23
- * @example
24
- * import { requestIdPlugin, createRequestIdGenerator } from '@classytic/arc';
25
- *
26
- * const fastify = Fastify({ genReqId: createRequestIdGenerator() });
27
- * await fastify.register(requestIdPlugin);
28
- *
29
- * fastify.get('/', async (request) => {
30
- * request.log.info('traced'); // carries the same reqId as the header
31
- * return { id: request.id };
32
- * });
33
- */
34
- /**
35
- * Sanitize an incoming request-id header value: max 128 chars, alphanumeric
36
- * plus dashes/underscores/dots/colons only. Returns `undefined` for anything
37
- * else (crafted values could pollute logs or response headers). Array values
38
- * (repeated headers) are rejected outright — a client sending multiple
39
- * request-id headers is not tracing in good faith.
40
- */
41
- function sanitizeRequestId(value) {
42
- if (typeof value !== "string") return void 0;
43
- const trimmed = value.trim();
44
- return trimmed.length > 0 && trimmed.length <= 128 && /^[\w.:-]+$/.test(trimmed) ? trimmed : void 0;
45
- }
46
- /**
47
- * Build a Fastify `genReqId` function: adopt a sanitized incoming request-id
48
- * header, or generate one. Wire this into the `Fastify()` constructor so
49
- * `request.id`, `request.log`'s `reqId` binding, and the echoed response
50
- * header all agree — `createApp` does this automatically.
51
- */
52
- function createRequestIdGenerator(opts = {}) {
53
- const { header = "x-request-id", generator = randomUUID } = opts;
54
- const headerName = header.toLowerCase();
55
- return (req) => sanitizeRequestId(req.headers[headerName]) ?? generator();
56
- }
57
- /** Fastify's default request ids are `req-<base36 counter>`. */
58
- const FASTIFY_DEFAULT_ID = /^req-[0-9a-z]+$/;
59
- /** W3C traceparent format: `version-traceid-parentid-flags` */
60
- const TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
61
- /** W3C spec: version ff is reserved and invalid; trace/parent IDs must not be all zeros */
62
- const TRACEID_ZEROS = "0".repeat(32);
63
- const PARENTID_ZEROS = "0".repeat(16);
64
- /** tracestate: printable ASCII, <= 512 bytes */
65
- const TRACESTATE_MAX = 512;
66
- function isValidTraceparent(tp) {
67
- const m = TRACEPARENT_RE.exec(tp);
68
- if (!m) return false;
69
- const version = m[1] ?? "";
70
- const traceId = m[2] ?? "";
71
- const parentId = m[3] ?? "";
72
- if (version.toLowerCase() === "ff") return false;
73
- if (traceId.toLowerCase() === TRACEID_ZEROS) return false;
74
- if (parentId.toLowerCase() === PARENTID_ZEROS) return false;
75
- return true;
76
- }
77
- const requestIdPlugin = async (fastify, opts = {}) => {
78
- const { header = "x-request-id", generator = randomUUID, setResponseHeader = true, propagateTraceContext = true } = opts;
79
- if (!fastify.hasRequestDecorator("requestId")) fastify.decorateRequest("requestId", "");
80
- if (!fastify.hasRequestDecorator("traceContext")) fastify.decorateRequest("traceContext", void 0);
81
- fastify.addHook("onRequest", async (request, reply) => {
82
- const incoming = sanitizeRequestId(request.headers[header]);
83
- let requestId = String(request.id);
84
- if (incoming && incoming !== requestId) requestId = incoming;
85
- else if (!incoming && FASTIFY_DEFAULT_ID.test(requestId)) requestId = generator();
86
- if (requestId !== request.id) request.id = requestId;
87
- request.requestId = requestId;
88
- if (setResponseHeader) reply.header(header, requestId);
89
- if (propagateTraceContext) {
90
- const rawTraceparent = request.headers["traceparent"];
91
- const rawTracestate = request.headers["tracestate"];
92
- if (typeof rawTraceparent === "string") {
93
- const tp = rawTraceparent.trim();
94
- if (isValidTraceparent(tp)) {
95
- const ts = typeof rawTracestate === "string" && rawTracestate.length > 0 && rawTracestate.length <= TRACESTATE_MAX ? rawTracestate : void 0;
96
- request.traceContext = {
97
- traceparent: tp,
98
- ...ts ? { tracestate: ts } : {}
99
- };
100
- reply.header("traceparent", tp);
101
- if (ts) reply.header("tracestate", ts);
102
- }
103
- }
104
- }
105
- });
106
- fastify.log?.debug?.("Request ID plugin registered");
107
- };
108
- var requestId_default = fp(requestIdPlugin, {
109
- name: "arc-request-id",
110
- fastify: "5.x"
111
- });
112
- //#endregion
113
- export { sanitizeRequestId as i, requestIdPlugin as n, requestId_default as r, createRequestIdGenerator as t };
@@ -1,64 +0,0 @@
1
- import { n as normalizeRoles } from "./types-XTApa8b6.mjs";
2
- //#region src/scope/resolveOrgFromHeader.ts
3
- /**
4
- * Create a preHandler hook that resolves org scope from a header.
5
- * Must run AFTER authentication so `request.user` is populated.
6
- * If the header is present and user is a member, sets `request.scope` to `member`.
7
- * If the header is absent, scope stays as-is (typically `authenticated`).
8
- */
9
- function resolveOrgFromHeader(options) {
10
- const { header = "x-organization-id", resolveMembership } = options;
11
- return async (request, reply) => {
12
- const orgId = request.headers[header];
13
- if (!orgId) return;
14
- const scope = request.scope;
15
- if (!scope || scope.kind === "public") {
16
- reply.code(401).send({
17
- success: false,
18
- error: "Unauthorized",
19
- message: "Authentication required for organization access",
20
- code: "ORG_AUTH_REQUIRED"
21
- });
22
- return;
23
- }
24
- if (scope.kind === "elevated") return;
25
- const user = request.user;
26
- if (!user) {
27
- reply.code(401).send({
28
- success: false,
29
- error: "Unauthorized",
30
- message: "Authentication required for organization access",
31
- code: "ORG_AUTH_REQUIRED"
32
- });
33
- return;
34
- }
35
- const userId = String(user.id ?? user._id ?? "");
36
- if (!userId) {
37
- reply.code(401).send({
38
- success: false,
39
- error: "Unauthorized",
40
- message: "User identity required for organization access"
41
- });
42
- return;
43
- }
44
- const membership = await resolveMembership(userId, orgId);
45
- if (!membership) {
46
- reply.code(403).send({
47
- success: false,
48
- error: "Forbidden",
49
- message: "Not a member of this organization",
50
- code: "ORG_ACCESS_DENIED"
51
- });
52
- return;
53
- }
54
- request.scope = {
55
- kind: "member",
56
- userId: userId || void 0,
57
- userRoles: normalizeRoles(user.role),
58
- organizationId: orgId,
59
- orgRoles: membership.roles
60
- };
61
- };
62
- }
63
- //#endregion
64
- export { resolveOrgFromHeader as t };
@@ -1,24 +0,0 @@
1
- //#region src/permissions/types.ts
2
- /**
3
- * Extract normalized roles from a user object.
4
- *
5
- * Reads `user.role` which can be:
6
- * - A comma-separated string: `"superadmin,user"` (Better Auth admin plugin)
7
- * - A string array: `["admin", "user"]` (JWT / custom auth)
8
- * - A single string: `"admin"`
9
- */
10
- /**
11
- * Normalize a raw role value (string, comma-separated string, or array) into a string[].
12
- * Shared low-level helper used by both getUserRoles() and the Better Auth adapter.
13
- */
14
- function normalizeRoles(value) {
15
- if (Array.isArray(value)) return value.map((r) => String(r).trim()).filter(Boolean);
16
- if (typeof value === "string" && value.length > 0) return value.split(",").map((r) => r.trim()).filter(Boolean);
17
- return [];
18
- }
19
- function getUserRoles(user) {
20
- if (!user) return [];
21
- return normalizeRoles(user.role);
22
- }
23
- //#endregion
24
- export { normalizeRoles as n, getUserRoles as t };