@classytic/arc 2.21.0 → 2.23.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 (115) hide show
  1. package/dist/{BaseController-BnLYgE9A.mjs → BaseController-BQ8KNvWC.mjs} +3 -2
  2. package/dist/{ResourceRegistry-7RnrEWNP.mjs → ResourceRegistry-y9Hnypus.mjs} +1 -0
  3. package/dist/{actionPermissions-ze70xXJi.mjs → actionPermissions-lszoJle1.mjs} +1 -1
  4. package/dist/applyPermissionResult-Tw4iXtlf.mjs +264 -0
  5. package/dist/audit/index.d.mts +2 -2
  6. package/dist/auth/index.d.mts +30 -2
  7. package/dist/auth/index.mjs +6 -3
  8. package/dist/cli/commands/describe.d.mts +1 -1
  9. package/dist/cli/commands/describe.mjs +1 -1
  10. package/dist/cli/commands/docs.mjs +2 -2
  11. package/dist/cli/commands/init.mjs +1 -1
  12. package/dist/cli/commands/introspect.mjs +1 -1
  13. package/dist/cli/index.mjs +1 -1
  14. package/dist/core/index.d.mts +2 -2
  15. package/dist/core/index.mjs +3 -3
  16. package/dist/{core-DZ6Xe1jR.mjs → core-DQ6rDTyH.mjs} +126 -68
  17. package/dist/{createActionRouter-BWMDW-dL.mjs → createActionRouter-BuOv-m4Y.mjs} +10 -6
  18. package/dist/{createAggregationRouter-l5mNdYrZ.mjs → createAggregationRouter-CgJv_BU_.mjs} +3 -3
  19. package/dist/{createApp-Bi61XxqU.mjs → createApp-CjOeWJ9_.mjs} +252 -57
  20. package/dist/docs/index.d.mts +1 -1
  21. package/dist/docs/index.mjs +1 -1
  22. package/dist/encryption/index.mjs +9 -5
  23. package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-Bq2OeEQL.mjs} +34 -10
  24. package/dist/{errorHandler-B0e-bq-O.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -0
  25. package/dist/events/index.mjs +5 -2
  26. package/dist/factory/index.d.mts +46 -9
  27. package/dist/factory/index.mjs +64 -13
  28. package/dist/{fastify-augmentation-DafigGQp.d.mts → fastify-augmentation-g_f9omSc.d.mts} +2 -2
  29. package/dist/fields-D3lhgzhD.d.mts +121 -0
  30. package/dist/health-ScyjIVo5.mjs +202 -0
  31. package/dist/hooks/index.d.mts +1 -1
  32. package/dist/idempotency/index.d.mts +7 -0
  33. package/dist/idempotency/index.mjs +7 -17
  34. package/dist/{index-Dtrj6XZk.d.mts → index-BBsvQt7U.d.mts} +1310 -1226
  35. package/dist/{types-DpxoNX5v.d.mts → index-Ba10fdKp.d.mts} +196 -66
  36. package/dist/{index-B5N-NaHs.d.mts → index-CC5ruDGh.d.mts} +3 -3
  37. package/dist/{index-B5PoNbfu.d.mts → index-wMoWBLkn.d.mts} +68 -2
  38. package/dist/index.d.mts +6 -5
  39. package/dist/index.mjs +6 -5
  40. package/dist/{init-0YhHkXGk.mjs → init-CjO4p_q1.mjs} +123 -4
  41. package/dist/integrations/event-gateway.mjs +1 -1
  42. package/dist/integrations/index.d.mts +1 -1
  43. package/dist/integrations/mcp/index.d.mts +67 -4
  44. package/dist/integrations/mcp/index.mjs +1 -1
  45. package/dist/integrations/mcp/testing.d.mts +1 -1
  46. package/dist/integrations/mcp/testing.mjs +1 -1
  47. package/dist/integrations/webhooks.d.mts +131 -7
  48. package/dist/integrations/webhooks.mjs +169 -10
  49. package/dist/interface-n5hpKg1d.mjs +16 -0
  50. package/dist/jsonBody-BlzAoGCe.mjs +43 -0
  51. package/dist/{loadResourcesFromEntry-OMCvIEE7.mjs → loadResourcesFromEntry-kvGT6EcO.mjs} +1 -1
  52. package/dist/logger/index.d.mts +26 -1
  53. package/dist/logger/index.mjs +39 -1
  54. package/dist/{mcpPlugin-B9XjjNVG.mjs → mcpPlugin-BtzIL_kM.mjs} +312 -39
  55. package/dist/middleware/index.d.mts +1 -1
  56. package/dist/{module-WeuU5jfd.d.mts → module-B4u10iOJ.d.mts} +26 -1
  57. package/dist/{openapi-Kyod_di6.mjs → openapi-jSDx-VDv.mjs} +3 -3
  58. package/dist/permissions/index.d.mts +4 -3
  59. package/dist/permissions/index.mjs +3 -2
  60. package/dist/{permissions-BJMuKMl7.mjs → permissions-tbxFHxjZ.mjs} +77 -264
  61. package/dist/pipeline/index.d.mts +1 -1
  62. package/dist/plugins/index.d.mts +24 -7
  63. package/dist/plugins/index.mjs +14 -282
  64. package/dist/plugins/response-cache.mjs +1 -0
  65. package/dist/plugins/tracing-entry.mjs +7 -4
  66. package/dist/presets/filesUpload.d.mts +2 -2
  67. package/dist/presets/filesUpload.mjs +1 -1
  68. package/dist/presets/index.d.mts +1 -1
  69. package/dist/presets/index.mjs +1 -1
  70. package/dist/presets/multiTenant.d.mts +1 -1
  71. package/dist/presets/search.d.mts +2 -2
  72. package/dist/presets/search.mjs +1 -1
  73. package/dist/{presets-gw0ciIUD.mjs → presets-BOe4Ugkl.mjs} +1 -1
  74. package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
  75. package/dist/realtime-I8OrfnMR.mjs +375 -0
  76. package/dist/{registerRoute-oGAl0oA8.mjs → registerRoute-CWO6mdYe.mjs} +38 -18
  77. package/dist/registry/index.d.mts +2 -2
  78. package/dist/registry/index.mjs +1 -1
  79. package/dist/requestId-5BhTVzBv.mjs +113 -0
  80. package/dist/schemaConverter-DJvodiHj.mjs +171 -0
  81. package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-Ca7ySNPx.mjs} +2 -2
  82. package/dist/scim/index.mjs +6 -7
  83. package/dist/scope/index.mjs +2 -76
  84. package/dist/scope-LONQ3mfZ.mjs +37 -0
  85. package/dist/simpleEqualityMatcher-Cg05HPsn.mjs +68 -0
  86. package/dist/{sse-CfUjwjDk.mjs → sse-0u4P4-OI.mjs} +7 -30
  87. package/dist/sseStream-BQ7Qaynp.mjs +57 -0
  88. package/dist/testing/index.d.mts +83 -3
  89. package/dist/testing/index.mjs +70 -3
  90. package/dist/types/index.d.mts +2 -2
  91. package/dist/{types-DRj-Ut4n.d.mts → types-5OuqeTDX.d.mts} +37 -2
  92. package/dist/{fields-BGqrAa_u.d.mts → types-Csxga7d3.d.mts} +1 -121
  93. package/dist/usage/index.d.mts +142 -0
  94. package/dist/usage/index.mjs +0 -0
  95. package/dist/utils/index.d.mts +101 -19
  96. package/dist/utils/index.mjs +5 -3
  97. package/dist/{utils-CO8a3B_1.mjs → utils-CZyeRXTh.mjs} +80 -82
  98. package/dist/{versioning-DQ4mCvg0.d.mts → versioning-D6PvCwpq.d.mts} +43 -9
  99. package/llms.txt +39 -0
  100. package/package.json +513 -447
  101. package/skills/arc/SKILL.md +14 -8
  102. package/skills/arc/references/api-reference.md +34 -0
  103. package/skills/arc/references/auth.md +21 -4
  104. package/skills/arc/references/mcp.md +1 -1
  105. package/skills/arc/references/production.md +734 -654
  106. package/skills/arc-code-review/SKILL.md +1 -1
  107. package/skills/arc-code-review/references/anti-patterns.md +71 -18
  108. package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
  109. package/skills/arc-code-review/references/migration-recipes.md +7 -7
  110. package/skills/arc-code-review/references/mongokit-migration.md +4 -3
  111. package/skills/arc-module-publishing/SKILL.md +2 -1
  112. package/skills/arc-module-publishing/references/authoring-modules.md +55 -0
  113. package/skills/arc-module-publishing/references/package-manifest.md +4 -2
  114. package/dist/preloadResources-CHw3_oA_.mjs +0 -45
  115. package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
@@ -3,14 +3,16 @@ import { c as getOrgId } from "../types-bHblZ9YN.mjs";
3
3
  import { n as requestContext } from "../requestContext-BZKOr13H.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-C9KXUly6.mjs";
5
5
  import { t as HookSystem } from "../HookSystem-C1vB_Gc9.mjs";
6
- import { t as ResourceRegistry } from "../ResourceRegistry-7RnrEWNP.mjs";
6
+ import { t as ResourceRegistry } from "../ResourceRegistry-y9Hnypus.mjs";
7
+ import { i as sanitizeRequestId, n as requestIdPlugin, r as requestId_default, t as createRequestIdGenerator } from "../requestId-5BhTVzBv.mjs";
8
+ import { n as health_default, t as healthPlugin } from "../health-ScyjIVo5.mjs";
7
9
  import { n as caching_default, t as cachingPlugin } from "../caching-_NJeDd1_.mjs";
8
- import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-Dxf6P_kV.mjs";
10
+ import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-Bq2OeEQL.mjs";
9
11
  import { n as metrics_default, t as metricsPlugin } from "../metrics-fdxI5mn_.mjs";
12
+ import { n as realtime_default, t as realtimePlugin } from "../realtime-I8OrfnMR.mjs";
10
13
  import { t as replyHelpersPlugin } from "../replyHelpers-67tRawQZ.mjs";
11
- import { n as sse_default, t as ssePlugin } from "../sse-CfUjwjDk.mjs";
14
+ import { n as sse_default, t as ssePlugin } from "../sse-0u4P4-OI.mjs";
12
15
  import { n as versioning_default, t as versioningPlugin } from "../versioning-Dk-71iIz.mjs";
13
- import { randomUUID } from "node:crypto";
14
16
  import fp from "fastify-plugin";
15
17
  //#region src/core/arcCorePlugin.ts
16
18
  const arcCorePlugin = async (fastify, opts = {}) => {
@@ -24,6 +26,13 @@ const arcCorePlugin = async (fastify, opts = {}) => {
24
26
  externalOpenApiPaths: [],
25
27
  plugins: /* @__PURE__ */ new Map()
26
28
  });
29
+ for (const field of [
30
+ "_policyFilters",
31
+ "_ownershipCheck",
32
+ "fieldMask",
33
+ "policyMetadata",
34
+ "document"
35
+ ]) if (!fastify.hasRequestDecorator(field)) fastify.decorateRequest(field, void 0);
27
36
  fastify.addHook("onRequest", (request, _reply, done) => {
28
37
  const store = {
29
38
  requestId: request.id,
@@ -192,283 +201,6 @@ var gracefulShutdown_default = fp(gracefulShutdownPlugin, {
192
201
  fastify: "5.x"
193
202
  });
194
203
  //#endregion
195
- //#region src/plugins/health.ts
196
- function createHttpMetrics() {
197
- return {
198
- requestsTotal: {},
199
- requestDurations: [],
200
- _ringIndex: 0,
201
- startTime: Date.now()
202
- };
203
- }
204
- const healthPlugin = async (fastify, opts = {}) => {
205
- const { prefix = "/_health", checks = [], metrics = false, metricsCollector, version, collectHttpMetrics = metrics } = opts;
206
- const httpMetrics = createHttpMetrics();
207
- fastify.get(prefix, { schema: {
208
- tags: ["Health"],
209
- summary: "Liveness probe (alias for /live)",
210
- description: "Returns 200 if the process is alive",
211
- response: { 200: {
212
- type: "object",
213
- properties: {
214
- status: {
215
- type: "string",
216
- enum: ["ok"]
217
- },
218
- timestamp: { type: "string" },
219
- version: { type: "string" }
220
- }
221
- } }
222
- } }, async () => ({
223
- status: "ok",
224
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
225
- ...version ? { version } : {}
226
- }));
227
- fastify.get(`${prefix}/live`, { schema: {
228
- tags: ["Health"],
229
- summary: "Liveness probe",
230
- description: "Returns 200 if the process is alive",
231
- response: { 200: {
232
- type: "object",
233
- properties: {
234
- status: {
235
- type: "string",
236
- enum: ["ok"]
237
- },
238
- timestamp: { type: "string" },
239
- version: { type: "string" }
240
- }
241
- } }
242
- } }, async () => {
243
- return {
244
- status: "ok",
245
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
246
- ...version ? { version } : {}
247
- };
248
- });
249
- fastify.get(`${prefix}/ready`, { schema: {
250
- tags: ["Health"],
251
- summary: "Readiness probe",
252
- description: "Returns 200 if all dependencies are healthy",
253
- response: {
254
- 200: {
255
- type: "object",
256
- properties: {
257
- status: {
258
- type: "string",
259
- enum: ["ready", "not_ready"]
260
- },
261
- timestamp: { type: "string" },
262
- checks: {
263
- type: "array",
264
- items: {
265
- type: "object",
266
- properties: {
267
- name: { type: "string" },
268
- healthy: { type: "boolean" },
269
- duration: { type: "number" },
270
- error: { type: "string" }
271
- }
272
- }
273
- }
274
- }
275
- },
276
- 503: {
277
- type: "object",
278
- properties: {
279
- status: {
280
- type: "string",
281
- enum: ["not_ready"]
282
- },
283
- timestamp: { type: "string" },
284
- checks: { type: "array" }
285
- }
286
- }
287
- }
288
- } }, async (_, reply) => {
289
- const results = await runChecks(checks);
290
- const criticalFailed = results.some((r) => !r.healthy && (checks.find((c) => c.name === r.name)?.critical ?? true));
291
- const response = {
292
- status: criticalFailed ? "not_ready" : "ready",
293
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
294
- checks: results
295
- };
296
- if (criticalFailed) reply.code(503);
297
- return response;
298
- });
299
- if (metrics) fastify.get(`${prefix}/metrics`, async (_, reply) => {
300
- reply.type("text/plain; charset=utf-8");
301
- if (metricsCollector) return await metricsCollector();
302
- const uptime = process.uptime();
303
- const memory = process.memoryUsage();
304
- const cpu = process.cpuUsage();
305
- const lines = [
306
- "# HELP process_uptime_seconds Process uptime in seconds",
307
- "# TYPE process_uptime_seconds gauge",
308
- `process_uptime_seconds ${uptime.toFixed(2)}`,
309
- "",
310
- "# HELP process_memory_heap_bytes Heap memory usage in bytes",
311
- "# TYPE process_memory_heap_bytes gauge",
312
- `process_memory_heap_bytes{type="used"} ${memory.heapUsed}`,
313
- `process_memory_heap_bytes{type="total"} ${memory.heapTotal}`,
314
- "",
315
- "# HELP process_memory_rss_bytes RSS memory in bytes",
316
- "# TYPE process_memory_rss_bytes gauge",
317
- `process_memory_rss_bytes ${memory.rss}`,
318
- "",
319
- "# HELP process_memory_external_bytes External memory in bytes",
320
- "# TYPE process_memory_external_bytes gauge",
321
- `process_memory_external_bytes ${memory.external}`,
322
- "",
323
- "# HELP process_cpu_user_microseconds User CPU time in microseconds",
324
- "# TYPE process_cpu_user_microseconds counter",
325
- `process_cpu_user_microseconds ${cpu.user}`,
326
- "",
327
- "# HELP process_cpu_system_microseconds System CPU time in microseconds",
328
- "# TYPE process_cpu_system_microseconds counter",
329
- `process_cpu_system_microseconds ${cpu.system}`,
330
- ""
331
- ];
332
- if (collectHttpMetrics && Object.keys(httpMetrics.requestsTotal).length > 0) {
333
- lines.push("# HELP http_requests_total Total HTTP requests by status code", "# TYPE http_requests_total counter");
334
- for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) lines.push(`http_requests_total{status="${status}"} ${count}`);
335
- lines.push("");
336
- if (httpMetrics.requestDurations.length > 0) {
337
- const sorted = [...httpMetrics.requestDurations].sort((a, b) => a - b);
338
- const p50 = sorted[Math.floor(sorted.length * .5)] || 0;
339
- const p95 = sorted[Math.floor(sorted.length * .95)] || 0;
340
- const p99 = sorted[Math.floor(sorted.length * .99)] || 0;
341
- const sum = sorted.reduce((a, b) => a + b, 0);
342
- 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}`, "");
343
- }
344
- }
345
- return lines.join("\n");
346
- });
347
- if (collectHttpMetrics) {
348
- fastify.addHook("onRequest", async (request) => {
349
- request._startTime = Date.now();
350
- });
351
- fastify.addHook("onResponse", async (request, reply) => {
352
- const duration = Date.now() - (request._startTime ?? Date.now());
353
- const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
354
- httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
355
- if (httpMetrics.requestDurations.length < 1e4) httpMetrics.requestDurations.push(duration);
356
- else httpMetrics.requestDurations[httpMetrics._ringIndex % 1e4] = duration;
357
- httpMetrics._ringIndex = httpMetrics._ringIndex + 1;
358
- });
359
- }
360
- fastify.log?.debug?.(`Health plugin registered at ${prefix} (alias), ${prefix}/live, ${prefix}/ready${metrics ? `, ${prefix}/metrics` : ""}`);
361
- };
362
- /**
363
- * Run all health checks with timeout
364
- */
365
- async function runChecks(checks) {
366
- const results = [];
367
- for (const check of checks) {
368
- const start = Date.now();
369
- const timeout = check.timeout ?? 5e3;
370
- let timer;
371
- try {
372
- const checkPromise = Promise.resolve(check.check());
373
- const timeoutPromise = new Promise((_, reject) => {
374
- timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Health check timeout")), timeout);
375
- });
376
- const healthy = await Promise.race([checkPromise, timeoutPromise]);
377
- results.push({
378
- name: check.name,
379
- healthy: Boolean(healthy),
380
- duration: Date.now() - start
381
- });
382
- } catch (err) {
383
- results.push({
384
- name: check.name,
385
- healthy: false,
386
- duration: Date.now() - start,
387
- error: err.message
388
- });
389
- } finally {
390
- if (timer) clearTimeout(timer);
391
- }
392
- }
393
- return results;
394
- }
395
- var health_default = fp(healthPlugin, {
396
- name: "arc-health",
397
- fastify: "5.x"
398
- });
399
- //#endregion
400
- //#region src/plugins/requestId.ts
401
- /**
402
- * Request ID Plugin
403
- *
404
- * Propagates request IDs for distributed tracing.
405
- * - Accepts incoming x-request-id header
406
- * - Generates UUID if not provided
407
- * - Attaches to request.id and response header
408
- *
409
- * @example
410
- * import { requestIdPlugin } from '@classytic/arc';
411
- *
412
- * await fastify.register(requestIdPlugin);
413
- *
414
- * // In handlers, access via request.id
415
- * fastify.get('/', async (request) => {
416
- * console.log(request.id); // UUID
417
- * });
418
- */
419
- /** W3C traceparent format: `version-traceid-parentid-flags` */
420
- const TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
421
- /** W3C spec: version ff is reserved and invalid; trace/parent IDs must not be all zeros */
422
- const TRACEID_ZEROS = "0".repeat(32);
423
- const PARENTID_ZEROS = "0".repeat(16);
424
- /** tracestate: printable ASCII, <= 512 bytes */
425
- const TRACESTATE_MAX = 512;
426
- function isValidTraceparent(tp) {
427
- const m = TRACEPARENT_RE.exec(tp);
428
- if (!m) return false;
429
- const version = m[1] ?? "";
430
- const traceId = m[2] ?? "";
431
- const parentId = m[3] ?? "";
432
- if (version.toLowerCase() === "ff") return false;
433
- if (traceId.toLowerCase() === TRACEID_ZEROS) return false;
434
- if (parentId.toLowerCase() === PARENTID_ZEROS) return false;
435
- return true;
436
- }
437
- const requestIdPlugin = async (fastify, opts = {}) => {
438
- const { header = "x-request-id", generator = randomUUID, setResponseHeader = true, propagateTraceContext = true } = opts;
439
- if (!fastify.hasRequestDecorator("requestId")) fastify.decorateRequest("requestId", "");
440
- if (!fastify.hasRequestDecorator("traceContext")) fastify.decorateRequest("traceContext", void 0);
441
- fastify.addHook("onRequest", async (request, reply) => {
442
- const incomingId = request.headers[header];
443
- const sanitized = typeof incomingId === "string" ? incomingId.trim() : "";
444
- const requestId = sanitized.length > 0 && sanitized.length <= 128 && /^[\w.:-]+$/.test(sanitized) ? sanitized : generator();
445
- request.id = requestId;
446
- request.requestId = requestId;
447
- if (setResponseHeader) reply.header(header, requestId);
448
- if (propagateTraceContext) {
449
- const rawTraceparent = request.headers["traceparent"];
450
- const rawTracestate = request.headers["tracestate"];
451
- if (typeof rawTraceparent === "string") {
452
- const tp = rawTraceparent.trim();
453
- if (isValidTraceparent(tp)) {
454
- const ts = typeof rawTracestate === "string" && rawTracestate.length > 0 && rawTracestate.length <= TRACESTATE_MAX ? rawTracestate : void 0;
455
- request.traceContext = {
456
- traceparent: tp,
457
- ...ts ? { tracestate: ts } : {}
458
- };
459
- reply.header("traceparent", tp);
460
- if (ts) reply.header("tracestate", ts);
461
- }
462
- }
463
- }
464
- });
465
- fastify.log?.debug?.("Request ID plugin registered");
466
- };
467
- var requestId_default = fp(requestIdPlugin, {
468
- name: "arc-request-id",
469
- fastify: "5.x"
470
- });
471
- //#endregion
472
204
  //#region src/plugins/schedules.ts
473
205
  const MIN_SAFE_INTERVAL_MS = 1e3;
474
206
  const MAX_DEFAULT_LEASE_MS = 3e5;
@@ -556,4 +288,4 @@ var schedules_default = fp(schedulesPlugin, {
556
288
  fastify: "5.x"
557
289
  });
558
290
  //#endregion
559
- export { arcCorePlugin_default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, caching_default as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, gracefulShutdown_default as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, health_default as healthPlugin, healthPlugin as healthPluginFn, metrics_default as metricsPlugin, metricsPlugin as metricsPluginFn, replyHelpersPlugin, requestId_default as requestIdPlugin, requestIdPlugin as requestIdPluginFn, schedules_default as schedulesPlugin, schedulesPlugin as schedulesPluginFn, sse_default as ssePlugin, ssePlugin as ssePluginFn, versioning_default as versioningPlugin, versioningPlugin as versioningPluginFn };
291
+ export { arcCorePlugin_default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, caching_default as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, createRequestIdGenerator, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, gracefulShutdown_default as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, health_default as healthPlugin, healthPlugin as healthPluginFn, metrics_default as metricsPlugin, metricsPlugin as metricsPluginFn, realtime_default as realtimePlugin, realtimePlugin as realtimePluginFn, replyHelpersPlugin, requestId_default as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sanitizeRequestId, schedules_default as schedulesPlugin, schedulesPlugin as schedulesPluginFn, sse_default as ssePlugin, ssePlugin as ssePluginFn, versioning_default as versioningPlugin, versioningPlugin as versioningPluginFn };
@@ -94,6 +94,7 @@ const responseCachePluginImpl = async (fastify, opts = {}) => {
94
94
  ], xCacheHeader = true, statsPath = null, keyFn } = opts;
95
95
  const cache = new LRUCache(maxEntries);
96
96
  const invalidateMethods = new Set(invalidateOn.map((m) => m.toUpperCase()));
97
+ if (!fastify.hasRequestDecorator("__arcCacheTTL")) fastify.decorateRequest("__arcCacheTTL", void 0);
97
98
  /** Find TTL for a given URL path (seconds) */
98
99
  function getTTL(url) {
99
100
  const path = url.split("?")[0] ?? url;
@@ -58,7 +58,7 @@ try {
58
58
  function createTracerProvider(options) {
59
59
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
60
60
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
61
- const resolvedVersion = serviceVersion ?? "2.21.0";
61
+ const resolvedVersion = serviceVersion ?? "2.23.0";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -92,8 +92,11 @@ async function tracingPlugin(fastify, options = {}) {
92
92
  }
93
93
  const tracer = otelTrace.getTracer(serviceName);
94
94
  fastify.decorateRequest("tracer", void 0);
95
- fastify.addHook("onRequest", async (request, _reply) => {
96
- if (Math.random() > sampleRate) return;
95
+ fastify.addHook("onRequest", (request, _reply, done) => {
96
+ if (Math.random() > sampleRate) {
97
+ done();
98
+ return;
99
+ }
97
100
  const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {
98
101
  kind: 1,
99
102
  attributes: {
@@ -109,7 +112,7 @@ async function tracingPlugin(fastify, options = {}) {
109
112
  tracer,
110
113
  currentSpan: span
111
114
  };
112
- otelContext.with(otelTrace.setSpan(otelContext.active(), span), () => {});
115
+ otelContext.with(otelTrace.setSpan(otelContext.active(), span), done);
113
116
  });
114
117
  fastify.addHook("onResponse", async (request, reply) => {
115
118
  if (!request.tracer?.currentSpan) return;
@@ -1,6 +1,6 @@
1
- import { rt as PresetResult } from "../index-Dtrj6XZk.mjs";
1
+ import { Y as PresetResult } from "../index-BBsvQt7U.mjs";
2
2
  import { i as RequestScope } from "../types-DH0GupI7.mjs";
3
- import { c as PermissionCheck } from "../fields-BGqrAa_u.mjs";
3
+ import { t as PermissionCheck } from "../types-Csxga7d3.mjs";
4
4
  import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-DLUZ04kK.mjs";
5
5
  //#region src/presets/filesUpload.d.ts
6
6
  interface FilesUploadPresetRoutes {
@@ -1,6 +1,6 @@
1
1
  import { i as NotFoundError, u as ValidationError } from "../errors-DkO8Nk6-.mjs";
2
2
  import { c as getOrgId, g as getUserId } from "../types-bHblZ9YN.mjs";
3
- import { b as allowPublic, w as requireAuth } from "../permissions-BJMuKMl7.mjs";
3
+ import { C as allowPublic, D as requireAuth } from "../permissions-tbxFHxjZ.mjs";
4
4
  import { t as multipartBody } from "../multipartBody-CHEQSZ6G.mjs";
5
5
  //#region src/presets/filesUpload.ts
6
6
  const DEFAULT_FIELD_NAME = "file";
@@ -1,4 +1,4 @@
1
- import { bt as IControllerResponse, d as PaginationResult, qt as AnyRecord, rt as PresetResult, st as ResourceConfig, xt as IRequestContext } from "../index-Dtrj6XZk.mjs";
1
+ import { Jt as AnyRecord, Rt as IControllerResponse, U as ResourceConfig, Y as PresetResult, d as PaginationResult, zt as IRequestContext } from "../index-BBsvQt7U.mjs";
2
2
  import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
3
3
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
4
4
  import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
@@ -1,5 +1,5 @@
1
1
  import { multiTenantPreset } from "./multiTenant.mjs";
2
- import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-gw0ciIUD.mjs";
2
+ import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-BOe4Ugkl.mjs";
3
3
  import { filesUploadPreset } from "./filesUpload.mjs";
4
4
  import { searchPreset } from "./search.mjs";
5
5
  export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,4 +1,4 @@
1
- import { J as CrudRouteKey, rt as PresetResult } from "../index-Dtrj6XZk.mjs";
1
+ import { X as CrudRouteKey, Y as PresetResult } from "../index-BBsvQt7U.mjs";
2
2
  //#region src/presets/multiTenant.d.ts
3
3
  /**
4
4
  * One tenant dimension for multi-field filtering. Discriminated by source:
@@ -1,5 +1,5 @@
1
- import { ft as RouteDefinition, gt as ControllerHandler, pt as RouteMcpConfig, rt as PresetResult } from "../index-Dtrj6XZk.mjs";
2
- import { c as PermissionCheck } from "../fields-BGqrAa_u.mjs";
1
+ import { At as RouteDefinition, Pt as ControllerHandler, Y as PresetResult, jt as RouteMcpConfig } from "../index-BBsvQt7U.mjs";
2
+ import { t as PermissionCheck } from "../types-Csxga7d3.mjs";
3
3
  //#region src/presets/search.d.ts
4
4
  /**
5
5
  * Search Preset — backend-agnostic search / vector / embed routes
@@ -1,4 +1,4 @@
1
- import { b as allowPublic, w as requireAuth } from "../permissions-BJMuKMl7.mjs";
1
+ import { C as allowPublic, D as requireAuth } from "../permissions-tbxFHxjZ.mjs";
2
2
  //#region src/presets/search.ts
3
3
  const BUILTINS = [
4
4
  {
@@ -1,6 +1,6 @@
1
1
  import { b as isElevated, n as PUBLIC_SCOPE } from "./types-bHblZ9YN.mjs";
2
- import { E as requireRoles, b as allowPublic, w as requireAuth } from "./permissions-BJMuKMl7.mjs";
3
2
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
3
+ import { C as allowPublic, D as requireAuth, k as requireRoles } from "./permissions-tbxFHxjZ.mjs";
4
4
  //#region src/presets/ownedByUser.ts
5
5
  /**
6
6
  * Create ownership check middleware.
@@ -0,0 +1,14 @@
1
+ //#region src/scope/rateLimitKey.ts
2
+ function createTenantKeyGenerator(opts) {
3
+ if (opts?.strategy) return opts.strategy;
4
+ return (ctx) => {
5
+ const scope = ctx.scope;
6
+ if (!scope || scope.kind === "public") return ctx.ip;
7
+ if (scope.kind === "member") return scope.organizationId;
8
+ if (scope.kind === "service") return scope.organizationId;
9
+ if (scope.kind === "elevated") return scope.organizationId ?? scope.userId ?? ctx.ip;
10
+ return scope.userId ?? ctx.ip;
11
+ };
12
+ }
13
+ //#endregion
14
+ export { createTenantKeyGenerator as t };