@classytic/arc 2.8.4 → 2.8.5

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 (123) hide show
  1. package/README.md +28 -0
  2. package/dist/adapters/index.d.mts +2 -2
  3. package/dist/audit/index.d.mts +1 -1
  4. package/dist/audit/index.mjs +1 -1
  5. package/dist/audit/mongodb.d.mts +1 -1
  6. package/dist/audit/mongodb.mjs +1 -1
  7. package/dist/auth/index.d.mts +4 -4
  8. package/dist/auth/index.mjs +2 -2
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/{betterAuthOpenApi-C5lDyRH2.mjs → betterAuthOpenApi-BuUcUEJq.mjs} +1 -1
  11. package/dist/cache/index.d.mts +73 -3
  12. package/dist/cache/index.mjs +95 -2
  13. package/dist/cli/commands/docs.mjs +2 -2
  14. package/dist/cli/commands/generate.mjs +1 -1
  15. package/dist/cli/commands/introspect.mjs +1 -1
  16. package/dist/core/index.d.mts +2 -2
  17. package/dist/core/index.mjs +3 -3
  18. package/dist/{core-DKSwNSXf.mjs → core-F0QoWBt2.mjs} +1 -1
  19. package/dist/{createActionRouter-Df1BuawX.mjs → createActionRouter-BORM8f17.mjs} +1 -1
  20. package/dist/{createApp-BOYjBgdI.mjs → createApp-B1EY8zxa.mjs} +11 -11
  21. package/dist/{defineResource-Bb_Bdhtw.mjs → defineResource-tcgySDo1.mjs} +2 -2
  22. package/dist/docs/index.d.mts +2 -2
  23. package/dist/docs/index.mjs +1 -1
  24. package/dist/dynamic/index.d.mts +2 -2
  25. package/dist/dynamic/index.mjs +1 -1
  26. package/dist/{elevation-BBGFjzIP.mjs → elevation-DtFxrG0s.mjs} +1 -1
  27. package/dist/{errorHandler-CdZDavNH.d.mts → errorHandler-Bah5JhBd.d.mts} +1 -1
  28. package/dist/{eventPlugin-CVxlE6De.d.mts → eventPlugin-D9DKB2zM.d.mts} +1 -1
  29. package/dist/events/index.d.mts +3 -3
  30. package/dist/events/index.mjs +1 -1
  31. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  32. package/dist/events/transports/redis.d.mts +1 -1
  33. package/dist/factory/index.d.mts +1 -1
  34. package/dist/factory/index.mjs +2 -2
  35. package/dist/filesUpload-C7r7HIeA.mjs +319 -0
  36. package/dist/hooks/index.d.mts +1 -1
  37. package/dist/hooks/index.mjs +1 -1
  38. package/dist/idempotency/index.d.mts +3 -3
  39. package/dist/idempotency/mongodb.d.mts +1 -1
  40. package/dist/idempotency/redis.d.mts +2 -2
  41. package/dist/idempotency/redis.mjs +134 -13
  42. package/dist/{index-CSkeivBx.d.mts → index-BLXBmWud.d.mts} +3 -3
  43. package/dist/{index-BgmMdpm8.d.mts → index-C1meYuDn.d.mts} +1 -1
  44. package/dist/{index-CpTSDqmD.d.mts → index-DtDzOBn8.d.mts} +3 -3
  45. package/dist/index.d.mts +7 -7
  46. package/dist/index.mjs +4 -4
  47. package/dist/integrations/event-gateway.d.mts +1 -1
  48. package/dist/integrations/event-gateway.mjs +1 -1
  49. package/dist/integrations/index.d.mts +1 -1
  50. package/dist/integrations/jobs.d.mts +25 -3
  51. package/dist/integrations/jobs.mjs +63 -4
  52. package/dist/integrations/mcp/index.d.mts +2 -2
  53. package/dist/integrations/mcp/index.mjs +1 -1
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/{interface-BVuMfeVv.d.mts → interface-CMRutPfe.d.mts} +38 -16
  57. package/dist/{mongodb-B8U2xaLj.d.mts → mongodb-BsP-WbhN.d.mts} +1 -1
  58. package/dist/{mongodb-X7LbEjTN.d.mts → mongodb-CTcp0hQZ.d.mts} +1 -1
  59. package/dist/{openapi-CYCuekCn.mjs → openapi-CbKUJY_m.mjs} +3 -3
  60. package/dist/org/index.d.mts +2 -2
  61. package/dist/permissions/index.d.mts +3 -3
  62. package/dist/plugins/index.d.mts +4 -4
  63. package/dist/plugins/index.mjs +8 -8
  64. package/dist/plugins/tracing-entry.d.mts +1 -1
  65. package/dist/plugins/tracing-entry.mjs +1 -1
  66. package/dist/policies/index.d.mts +1 -1
  67. package/dist/presets/filesUpload.d.mts +49 -0
  68. package/dist/presets/filesUpload.mjs +2 -0
  69. package/dist/presets/index.d.mts +3 -2
  70. package/dist/presets/index.mjs +2 -1
  71. package/dist/presets/multiTenant.d.mts +1 -1
  72. package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-BJJGBTlu.d.mts} +1 -1
  73. package/dist/redis-BM00zaPB.d.mts +115 -0
  74. package/dist/{redis-stream-D54N5oXs.d.mts → redis-stream-CrsfUmPt.d.mts} +1 -1
  75. package/dist/registry/index.d.mts +1 -1
  76. package/dist/registry/index.mjs +2 -2
  77. package/dist/{resourceToTools-O_HwWXFa.mjs → resourceToTools-8s-EsCCe.mjs} +1 -1
  78. package/dist/rpc/index.d.mts +1 -1
  79. package/dist/{schemaConverter-OxfCshus.mjs → schemaConverter-Y7nCYaLJ.mjs} +24 -8
  80. package/dist/scope/index.d.mts +2 -2
  81. package/dist/scope/index.mjs +1 -1
  82. package/dist/{sse-CJpt7LGI.mjs → sse-Ad7ypl9e.mjs} +1 -1
  83. package/dist/storage-Dfzt4VTl.d.mts +146 -0
  84. package/dist/testing/index.d.mts +4 -3
  85. package/dist/testing/index.mjs +3 -2
  86. package/dist/testing/storageContract.d.mts +26 -0
  87. package/dist/testing/storageContract.mjs +216 -0
  88. package/dist/types/index.d.mts +4 -4
  89. package/dist/types/storage.d.mts +2 -0
  90. package/dist/types/storage.mjs +1 -0
  91. package/dist/{types-CcG4avic.d.mts → types-BsbNMEDR.d.mts} +1 -1
  92. package/dist/{types-Bg2X42_m.d.mts → types-Ch9pTQbf.d.mts} +9 -9
  93. package/dist/{types-CVC4HOKi.d.mts → types-DZi1aYhm.d.mts} +1 -1
  94. package/dist/utils/index.d.mts +26 -8
  95. package/dist/utils/index.mjs +1 -1
  96. package/package.json +16 -1
  97. package/skills/arc/references/events.md +29 -0
  98. package/dist/redis-z3sFr1UP.d.mts +0 -49
  99. /package/dist/{EventTransport-CinyO7zQ.d.mts → EventTransport-BXja8NOc.d.mts} +0 -0
  100. /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-HprTmvVY.mjs} +0 -0
  101. /package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-C6uXlWe3.mjs} +0 -0
  102. /package/dist/{caching-CjybdRwx.mjs → caching-IMuYVjTL.mjs} +0 -0
  103. /package/dist/{circuitBreaker-CvXkjfrW.d.mts → circuitBreaker-dTtG-UyS.d.mts} +0 -0
  104. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-B6S5csVA.d.mts} +0 -0
  105. /package/dist/{errorHandler-mzqk4cGl.mjs → errorHandler-f869_8PQ.mjs} +0 -0
  106. /package/dist/{errors-Bmn3eZT6.d.mts → errors-Ck2h67pm.d.mts} +0 -0
  107. /package/dist/{eventPlugin-D91S2YF4.mjs → eventPlugin-CDjVTM82.mjs} +0 -0
  108. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BnkYrNzp.d.mts} +0 -0
  109. /package/dist/{fields-DC4So2M2.d.mts → fields-DpZQa_Q3.d.mts} +0 -0
  110. /package/dist/{interface-DplgQO2e.d.mts → interface-4y979v99.d.mts} +0 -0
  111. /package/dist/{interface-B-pe8fhj.d.mts → interface-DfLGcus7.d.mts} +0 -0
  112. /package/dist/{loadResources-Bksk8ydA.mjs → loadResources-PWd0OCpV.mjs} +0 -0
  113. /package/dist/{logger-CDjpjySd.mjs → logger-D1YrIImS.mjs} +0 -0
  114. /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  115. /package/dist/{mongodb-B5O6xaW1.mjs → mongodb-Utc5k_-0.mjs} +0 -0
  116. /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
  117. /package/dist/{queryCachePlugin-D0iIVhW_.mjs → queryCachePlugin-BH-fidlv.mjs} +0 -0
  118. /package/dist/{registry-B0Wl7uVV.mjs → registry-BiTKT1Dg.mjs} +0 -0
  119. /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-CxkYGT81.mjs} +0 -0
  120. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-DDCmiNIo.d.mts} +0 -0
  121. /package/dist/{tracing-DxjKk7eW.d.mts → tracing-DdN2-wHJ.d.mts} +0 -0
  122. /package/dist/{types-C72d3NDn.d.mts → types-BD85MlEK.d.mts} +0 -0
  123. /package/dist/{versioning-Cm8qoFDg.mjs → versioning-CDugduqI.mjs} +0 -0
@@ -1,6 +1,19 @@
1
1
  import { FastifyPluginAsync } from "fastify";
2
2
 
3
3
  //#region src/integrations/jobs.d.ts
4
+ /** Repeat schedule — cron pattern or fixed interval. Explicit timezone is required. */
5
+ interface JobRepeatOptions {
6
+ /** Cron pattern (e.g. '0 9 * * *' = every day 09:00). Mutually exclusive with `every`. */
7
+ pattern?: string;
8
+ /** Fixed interval in ms. Mutually exclusive with `pattern`. */
9
+ every?: number;
10
+ /** IANA timezone (e.g. 'UTC', 'America/New_York'). Required for `pattern` — prevents DST drift. */
11
+ tz?: string;
12
+ /** Stop repeating after this date. */
13
+ endDate?: Date | string | number;
14
+ /** Max total runs. */
15
+ limit?: number;
16
+ }
4
17
  interface JobDefinition<TData = unknown, TResult = unknown> {
5
18
  /** Unique job name */
6
19
  name: string;
@@ -22,8 +35,10 @@ interface JobDefinition<TData = unknown, TResult = unknown> {
22
35
  max: number;
23
36
  duration: number;
24
37
  };
25
- /** Dead letter queue name (default: '{name}:dead') */
38
+ /** Dead letter queue name (default: '{name}-dead') */
26
39
  deadLetterQueue?: string;
40
+ /** Repeat schedule — cron or interval. Requires explicit timezone for cron. */
41
+ repeat?: JobRepeatOptions;
27
42
  }
28
43
  interface JobMeta {
29
44
  jobId: string;
@@ -41,6 +56,8 @@ interface JobDispatchOptions {
41
56
  removeOnComplete?: boolean | number;
42
57
  /** Remove job after failure */
43
58
  removeOnFail?: boolean | number;
59
+ /** One-shot repeat override at dispatch time. Usually prefer `JobDefinition.repeat`. */
60
+ repeat?: JobRepeatOptions;
44
61
  }
45
62
  interface JobsPluginOptions {
46
63
  /** Redis connection options (passed to BullMQ) */
@@ -97,7 +114,12 @@ interface QueueStats {
97
114
  * });
98
115
  */
99
116
  declare function defineJob<TData = unknown, TResult = unknown>(definition: JobDefinition<TData, TResult>): JobDefinition<TData, TResult>;
100
- /** Pluggable BullMQ job queue integration for Arc */
117
+ /**
118
+ * Pluggable BullMQ job queue integration for Arc.
119
+ *
120
+ * Wrapped with fastify-plugin so the `fastify.jobs` decorator is available
121
+ * in the outer scope (the documented `fastify.jobs.dispatch(...)` usage).
122
+ */
101
123
  declare const jobsPlugin: FastifyPluginAsync<JobsPluginOptions>;
102
124
  //#endregion
103
- export { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats, defineJob, jobsPlugin };
125
+ export { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobRepeatOptions, JobsPluginOptions, QueueStats, defineJob, jobsPlugin };
@@ -1,3 +1,4 @@
1
+ import fp from "fastify-plugin";
1
2
  //#region src/integrations/jobs.ts
2
3
  /**
3
4
  * Define a background job with typed data and configuration.
@@ -26,16 +27,41 @@ const jobsPluginImpl = async (fastify, options) => {
26
27
  } catch {
27
28
  throw new Error("@classytic/arc/integrations/jobs requires \"bullmq\" package.\nInstall it: npm install bullmq");
28
29
  }
30
+ if (connection && typeof connection === "object" && "options" in connection) {
31
+ if (connection.options?.maxRetriesPerRequest !== null) fastify.log.warn("[arc/jobs] BullMQ requires ioredis `maxRetriesPerRequest: null`. Pass `new Redis(url, { maxRetriesPerRequest: null, enableReadyCheck: false })` or workers will stall on transient Redis errors.");
32
+ }
29
33
  const queues = /* @__PURE__ */ new Map();
30
34
  const dlqQueues = /* @__PURE__ */ new Map();
31
35
  const workers = /* @__PURE__ */ new Map();
36
+ for (const job of jobs) {
37
+ if (!job.repeat) continue;
38
+ const { pattern, every, tz } = job.repeat;
39
+ if (pattern && every) throw new Error(`[arc/jobs] Job '${job.name}' sets both repeat.pattern and repeat.every — use one.`);
40
+ if (!pattern && every == null) throw new Error(`[arc/jobs] Job '${job.name}' has repeat config but no pattern or every.`);
41
+ if (pattern && !tz) throw new Error(`[arc/jobs] Job '${job.name}' uses a cron pattern but no timezone. Set repeat.tz (e.g. 'UTC' or 'America/New_York') to avoid DST drift.`);
42
+ }
32
43
  for (const job of jobs) {
33
44
  const queueName = job.name;
34
45
  const queue = new Queue(queueName, { connection });
35
46
  queues.set(queueName, queue);
47
+ if (job.repeat) {
48
+ const repeatOpts = {
49
+ ...job.repeat.pattern ? {
50
+ pattern: job.repeat.pattern,
51
+ tz: job.repeat.tz
52
+ } : { every: job.repeat.every },
53
+ ...job.repeat.endDate ? { endDate: job.repeat.endDate } : {},
54
+ ...job.repeat.limit != null ? { limit: job.repeat.limit } : {}
55
+ };
56
+ await queue.add(queueName, {}, {
57
+ repeat: repeatOpts,
58
+ removeOnComplete: defaults.removeOnComplete ?? 100,
59
+ removeOnFail: defaults.removeOnFail ?? 500
60
+ });
61
+ }
36
62
  let dlqQueue = null;
37
63
  if (job.deadLetterQueue != null) {
38
- const dlqName = job.deadLetterQueue || `${queueName}:dead`;
64
+ const dlqName = job.deadLetterQueue || `${queueName}-dead`;
39
65
  dlqQueue = new Queue(dlqName, { connection });
40
66
  dlqQueues.set(dlqName, dlqQueue);
41
67
  }
@@ -110,12 +136,35 @@ const jobsPluginImpl = async (fastify, options) => {
110
136
  }, `Failed to publish job.${queueName}.failed event`);
111
137
  }
112
138
  });
139
+ worker.on("stalled", async (jobId) => {
140
+ fastify.log.warn({
141
+ jobId,
142
+ queue: queueName
143
+ }, "Job stalled — worker may have crashed");
144
+ if (bridgeEvents && fastify.events?.publish) try {
145
+ await fastify.events.publish(`job.${queueName}.stalled`, { jobId });
146
+ } catch (err) {
147
+ fastify.log.warn({
148
+ err,
149
+ jobId
150
+ }, `Failed to publish job.${queueName}.stalled event`);
151
+ }
152
+ });
113
153
  workers.set(queueName, worker);
114
154
  }
155
+ const JOB_PAYLOAD_WARN_BYTES = 100 * 1024;
115
156
  const dispatcher = {
116
157
  async dispatch(name, data, opts = {}) {
117
158
  const queue = queues.get(name);
118
159
  if (!queue) throw new Error(`Job queue '${name}' not registered. Available: ${Array.from(queues.keys()).join(", ")}`);
160
+ try {
161
+ const serializedBytes = Buffer.byteLength(JSON.stringify(data) ?? "", "utf8");
162
+ if (serializedBytes > JOB_PAYLOAD_WARN_BYTES) fastify.log.warn({
163
+ queue: name,
164
+ bytes: serializedBytes,
165
+ limit: JOB_PAYLOAD_WARN_BYTES
166
+ }, `[arc/jobs] Large job payload — prefer passing IDs and reloading in the handler`);
167
+ } catch {}
119
168
  const jobDef = jobs.find((j) => j.name === name);
120
169
  return { jobId: (await queue.add(name, data, {
121
170
  delay: opts.delay,
@@ -127,7 +176,8 @@ const jobsPluginImpl = async (fastify, options) => {
127
176
  backoff: jobDef?.backoff ?? defaults.backoff ?? {
128
177
  type: "exponential",
129
178
  delay: 1e3
130
- }
179
+ },
180
+ repeat: jobDef?.repeat ?? opts.repeat
131
181
  })).id };
132
182
  },
133
183
  getQueue(name) {
@@ -148,6 +198,7 @@ const jobsPluginImpl = async (fastify, options) => {
148
198
  return stats;
149
199
  },
150
200
  async close() {
201
+ await Promise.all(Array.from(workers.values()).map((w) => w.pause().catch(() => {})));
151
202
  const closePromises = [];
152
203
  for (const worker of workers.values()) closePromises.push(worker.close());
153
204
  for (const queue of queues.values()) closePromises.push(queue.close());
@@ -166,7 +217,15 @@ const jobsPluginImpl = async (fastify, options) => {
166
217
  await dispatcher.close();
167
218
  });
168
219
  };
169
- /** Pluggable BullMQ job queue integration for Arc */
170
- const jobsPlugin = jobsPluginImpl;
220
+ /**
221
+ * Pluggable BullMQ job queue integration for Arc.
222
+ *
223
+ * Wrapped with fastify-plugin so the `fastify.jobs` decorator is available
224
+ * in the outer scope (the documented `fastify.jobs.dispatch(...)` usage).
225
+ */
226
+ const jobsPlugin = fp(jobsPluginImpl, {
227
+ name: "arc-jobs",
228
+ fastify: "5.x"
229
+ });
171
230
  //#endregion
172
231
  export { defineJob, jobsPlugin };
@@ -1,5 +1,5 @@
1
- import { qt as ResourceDefinition } from "../../interface-BVuMfeVv.mjs";
2
- import { a as McpAuthResolver, c as McpResourceConfig, d as SessionEntry, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler, u as PromptResult } from "../../types-CcG4avic.mjs";
1
+ import { qt as ResourceDefinition } from "../../interface-CMRutPfe.mjs";
2
+ import { a as McpAuthResolver, c as McpResourceConfig, d as SessionEntry, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler, u as PromptResult } from "../../types-BsbNMEDR.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
  import { z } from "zod";
5
5
 
@@ -1,4 +1,4 @@
1
- import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-O_HwWXFa.mjs";
1
+ import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-8s-EsCCe.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/integrations/mcp/defineTool.ts
@@ -1,4 +1,4 @@
1
- import { o as McpAuthResult, s as McpPluginOptions } from "../../types-CcG4avic.mjs";
1
+ import { o as McpAuthResult, s as McpPluginOptions } from "../../types-BsbNMEDR.mjs";
2
2
 
3
3
  //#region src/integrations/mcp/testing.d.ts
4
4
  interface TestMcpClientOptions {
@@ -1,4 +1,4 @@
1
- import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-O_HwWXFa.mjs";
1
+ import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-8s-EsCCe.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -1,6 +1,6 @@
1
- import { r as RequestScope } from "./types-C72d3NDn.mjs";
2
- import { n as FieldPermissionMap } from "./fields-DC4So2M2.mjs";
3
- import { i as UserBase, t as PermissionCheck } from "./types-CVC4HOKi.mjs";
1
+ import { r as RequestScope } from "./types-BD85MlEK.mjs";
2
+ import { n as FieldPermissionMap } from "./fields-DpZQa_Q3.mjs";
3
+ import { i as UserBase, t as PermissionCheck } from "./types-DZi1aYhm.mjs";
4
4
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
5
5
 
6
6
  //#region src/hooks/HookSystem.d.ts
@@ -2419,8 +2419,20 @@ interface RouteDefinition {
2419
2419
  readonly preAuth?: RouteHandlerMethod[];
2420
2420
  /** SSE streaming mode */
2421
2421
  readonly streamResponse?: boolean;
2422
- /** Fastify route schema */
2423
- readonly schema?: Record<string, unknown>;
2422
+ /**
2423
+ * Fastify route schema. Each slot (`body`, `querystring`, `params`, `headers`,
2424
+ * `response[status]`) accepts a plain JSON Schema object **or** a Zod v4 schema —
2425
+ * arc auto-converts via `convertRouteSchema` at registration time. Slot values
2426
+ * are typed `unknown` so class-based Zod schemas assign without casts.
2427
+ */
2428
+ readonly schema?: {
2429
+ body?: unknown;
2430
+ querystring?: unknown;
2431
+ params?: unknown;
2432
+ headers?: unknown;
2433
+ response?: Record<number | string, unknown>;
2434
+ [key: string]: unknown;
2435
+ };
2424
2436
  /**
2425
2437
  * MCP tool generation:
2426
2438
  * - omitted/true: auto-generate (non-raw routes only)
@@ -2452,8 +2464,11 @@ interface ActionDefinition {
2452
2464
  readonly handler: ActionHandlerFn;
2453
2465
  /** Per-action permission check (overrides resource-level actionPermissions) */
2454
2466
  readonly permissions?: PermissionCheck;
2455
- /** JSON Schema for action-specific body fields */
2456
- readonly schema?: Record<string, Record<string, unknown>>;
2467
+ /**
2468
+ * JSON Schema or Zod v4 schema for action-specific body fields.
2469
+ * Per-field values are typed `unknown` so Zod class instances assign without casts.
2470
+ */
2471
+ readonly schema?: Record<string, unknown>;
2457
2472
  /** Description for OpenAPI docs and MCP tool */
2458
2473
  readonly description?: string;
2459
2474
  /**
@@ -2506,47 +2521,54 @@ interface FieldRule {
2506
2521
  /**
2507
2522
  * CRUD Route Schemas (Fastify Native Format)
2508
2523
  *
2524
+ * Each slot accepts either a plain JSON Schema object **or** a Zod v4 schema —
2525
+ * arc's `convertRouteSchema` feature-detects at runtime. The slot values are
2526
+ * typed `unknown` (not `Record<string, unknown>`) so class-based Zod schemas
2527
+ * assign cleanly without `as unknown as Record<string, unknown>` casts.
2528
+ *
2509
2529
  * @example
2530
+ * ```ts
2510
2531
  * {
2511
2532
  * list: {
2512
2533
  * querystring: { type: 'object', properties: { page: { type: 'number' } } },
2513
- * response: { 200: { type: 'object', properties: { docs: { type: 'array' } } } }
2534
+ * response: { 200: z.object({ docs: z.array(EntitySchema) }) }
2514
2535
  * },
2515
2536
  * create: {
2516
- * body: { type: 'object', properties: { name: { type: 'string' } } },
2517
- * response: { 201: { type: 'object' } }
2537
+ * body: z.object({ name: z.string(), size: z.number().int().positive() }),
2538
+ * response: { 201: EntitySchema }
2518
2539
  * }
2519
2540
  * }
2541
+ * ```
2520
2542
  */
2521
2543
  interface CrudSchemas {
2522
2544
  /** GET / - List all resources */
2523
2545
  list?: {
2524
- querystring?: Record<string, unknown>;
2546
+ /** Plain JSON Schema or Zod schema (auto-converted). */querystring?: unknown; /** Map of HTTP status code → JSON Schema or Zod schema. */
2525
2547
  response?: Record<number, unknown>;
2526
2548
  [key: string]: unknown;
2527
2549
  };
2528
2550
  /** GET /:id - Get single resource */
2529
2551
  get?: {
2530
- params?: Record<string, unknown>;
2552
+ params?: unknown;
2531
2553
  response?: Record<number, unknown>;
2532
2554
  [key: string]: unknown;
2533
2555
  };
2534
2556
  /** POST / - Create resource */
2535
2557
  create?: {
2536
- body?: Record<string, unknown>;
2558
+ body?: unknown;
2537
2559
  response?: Record<number, unknown>;
2538
2560
  [key: string]: unknown;
2539
2561
  };
2540
2562
  /** PATCH /:id - Update resource */
2541
2563
  update?: {
2542
- params?: Record<string, unknown>;
2543
- body?: Record<string, unknown>;
2564
+ params?: unknown;
2565
+ body?: unknown;
2544
2566
  response?: Record<number, unknown>;
2545
2567
  [key: string]: unknown;
2546
2568
  };
2547
2569
  /** DELETE /:id - Delete resource */
2548
2570
  delete?: {
2549
- params?: Record<string, unknown>;
2571
+ params?: unknown;
2550
2572
  response?: Record<number, unknown>;
2551
2573
  [key: string]: unknown;
2552
2574
  };
@@ -1,4 +1,4 @@
1
- import { i as UserBase } from "./types-CVC4HOKi.mjs";
1
+ import { i as UserBase } from "./types-DZi1aYhm.mjs";
2
2
 
3
3
  //#region src/audit/stores/interface.d.ts
4
4
  type AuditAction = "create" | "update" | "delete" | "restore" | "custom";
@@ -1,4 +1,4 @@
1
- import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-B-pe8fhj.mjs";
1
+ import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-DfLGcus7.mjs";
2
2
 
3
3
  //#region src/idempotency/stores/mongodb.d.ts
4
4
  interface MongoConnection {
@@ -1,6 +1,6 @@
1
1
  import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
2
- import { n as convertRouteSchema } from "./schemaConverter-OxfCshus.mjs";
3
- import { t as buildActionBodySchema } from "./createActionRouter-Df1BuawX.mjs";
2
+ import { n as convertRouteSchema } from "./schemaConverter-Y7nCYaLJ.mjs";
3
+ import { t as buildActionBodySchema } from "./createActionRouter-BORM8f17.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/docs/openapi.ts
6
6
  const openApiPlugin = async (fastify, opts = {}) => {
@@ -288,7 +288,7 @@ function generateResourcePaths(resource, apiPrefix = "", additionalSecurity = []
288
288
  responses: { "200": { description: route.description || "Success" } }
289
289
  };
290
290
  const rawSchema = route.schema;
291
- const routeSchema = rawSchema ? convertRouteSchema(rawSchema) : void 0;
291
+ const routeSchema = rawSchema ? convertRouteSchema(rawSchema, "openapi-3.0") : void 0;
292
292
  if (routeSchema?.body && [
293
293
  "post",
294
294
  "put",
@@ -1,5 +1,5 @@
1
- import { Wt as RouteHandler } from "../interface-BVuMfeVv.mjs";
2
- import { i as UserBase } from "../types-CVC4HOKi.mjs";
1
+ import { Wt as RouteHandler } from "../interface-CMRutPfe.mjs";
2
+ import { i as UserBase } from "../types-DZi1aYhm.mjs";
3
3
  import { InvitationAdapter, InvitationDoc, MemberDoc, OrgAdapter, OrgDoc, OrgPermissionStatement, OrgRole, OrganizationPluginOptions } from "./types.mjs";
4
4
  import { FastifyPluginAsync, RouteHandlerMethod } from "fastify";
5
5
 
@@ -1,4 +1,4 @@
1
- import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission } from "../fields-DC4So2M2.mjs";
2
- import { a as getUserRoles, i as UserBase, n as PermissionContext, o as normalizeRoles, r as PermissionResult, t as PermissionCheck } from "../types-CVC4HOKi.mjs";
3
- import { A as RoleHierarchy, C as authenticated, D as publicRead, E as presets_d_exports, M as applyPermissionResult, N as normalizePermissionResult, O as publicReadAdminWrite, S as adminOnly, T as ownerWithAdminBypass, _ as requireScopeContext, a as allOf, b as roles, c as createDynamicPermissionMatrix, d as requireAuth, f as requireOrgInScope, g as requireRoles, h as requireOwnership, i as PermissionEventBus, j as createRoleHierarchy, k as readOnly, l as createOrgPermissions, m as requireOrgRole, n as DynamicPermissionMatrix, o as allowPublic, p as requireOrgMembership, r as DynamicPermissionMatrixConfig, s as anyOf, t as ConnectEventsOptions, u as denyAll, v as requireServiceScope, w as fullPublic, x as when, y as requireTeamMembership } from "../index-CSkeivBx.mjs";
1
+ import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission } from "../fields-DpZQa_Q3.mjs";
2
+ import { a as getUserRoles, i as UserBase, n as PermissionContext, o as normalizeRoles, r as PermissionResult, t as PermissionCheck } from "../types-DZi1aYhm.mjs";
3
+ import { A as RoleHierarchy, C as authenticated, D as publicRead, E as presets_d_exports, M as applyPermissionResult, N as normalizePermissionResult, O as publicReadAdminWrite, S as adminOnly, T as ownerWithAdminBypass, _ as requireScopeContext, a as allOf, b as roles, c as createDynamicPermissionMatrix, d as requireAuth, f as requireOrgInScope, g as requireRoles, h as requireOwnership, i as PermissionEventBus, j as createRoleHierarchy, k as readOnly, l as createOrgPermissions, m as requireOrgRole, n as DynamicPermissionMatrix, o as allowPublic, p as requireOrgMembership, r as DynamicPermissionMatrixConfig, s as anyOf, t as ConnectEventsOptions, u as denyAll, v as requireServiceScope, w as fullPublic, x as when, y as requireTeamMembership } from "../index-BLXBmWud.mjs";
4
4
  export { ConnectEventsOptions, DynamicPermissionMatrix, DynamicPermissionMatrixConfig, FieldPermission, FieldPermissionMap, FieldPermissionType, PermissionCheck, PermissionContext, PermissionEventBus, PermissionResult, RoleHierarchy, UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, ownerWithAdminBypass, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -1,7 +1,7 @@
1
- import { K as MiddlewareConfig, Kt as ResourceRegistry, Tn as HookSystem, et as PresetHook, m as AnyRecord, p as AdditionalRoute, vt as RouteSchemaOptions } from "../interface-BVuMfeVv.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
3
- import { _ as _default$1, a as _default$7, c as MetricsCollector, d as metricsPlugin, f as SSEOptions, g as CachingRule, h as CachingOptions, i as VersioningOptions, l as MetricsOptions, m as ssePlugin, n as ErrorMapper, o as versioningPlugin, p as _default$6, r as errorHandlerPlugin, s as MetricEntry, t as ErrorHandlerOptions, u as _default$4, v as cachingPlugin } from "../errorHandler-CdZDavNH.mjs";
4
- import { t as TracingOptions } from "../tracing-DxjKk7eW.mjs";
1
+ import { K as MiddlewareConfig, Kt as ResourceRegistry, Tn as HookSystem, et as PresetHook, m as AnyRecord, p as AdditionalRoute, vt as RouteSchemaOptions } from "../interface-CMRutPfe.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-BnkYrNzp.mjs";
3
+ import { _ as _default$1, a as _default$7, c as MetricsCollector, d as metricsPlugin, f as SSEOptions, g as CachingRule, h as CachingOptions, i as VersioningOptions, l as MetricsOptions, m as ssePlugin, n as ErrorMapper, o as versioningPlugin, p as _default$6, r as errorHandlerPlugin, s as MetricEntry, t as ErrorHandlerOptions, u as _default$4, v as cachingPlugin } from "../errorHandler-Bah5JhBd.mjs";
4
+ import { t as TracingOptions } from "../tracing-DdN2-wHJ.mjs";
5
5
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
6
6
  import * as _$node_stream0 from "node:stream";
7
7
 
@@ -2,14 +2,14 @@ import { p as MUTATION_OPERATIONS } from "../constants-Cxde4rpC.mjs";
2
2
  import { o as getOrgId } from "../types-AOD8fxIw.mjs";
3
3
  import { t as requestContext } from "../requestContext-DYvHl113.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
5
- import { t as HookSystem } from "../HookSystem-BjFu7zf1.mjs";
6
- import { t as ResourceRegistry } from "../ResourceRegistry-Dq3_zBQP.mjs";
7
- import { n as caching_default, t as cachingPlugin } from "../caching-CjybdRwx.mjs";
8
- import { t as errorHandlerPlugin } from "../errorHandler-mzqk4cGl.mjs";
9
- import { n as metrics_default, t as metricsPlugin } from "../metrics-TuOmguhi.mjs";
10
- import { t as replyHelpersPlugin } from "../replyHelpers-BLojtuvR.mjs";
11
- import { n as sse_default, t as ssePlugin } from "../sse-CJpt7LGI.mjs";
12
- import { n as versioning_default, t as versioningPlugin } from "../versioning-Cm8qoFDg.mjs";
5
+ import { t as HookSystem } from "../HookSystem-HprTmvVY.mjs";
6
+ import { t as ResourceRegistry } from "../ResourceRegistry-C6uXlWe3.mjs";
7
+ import { n as caching_default, t as cachingPlugin } from "../caching-IMuYVjTL.mjs";
8
+ import { t as errorHandlerPlugin } from "../errorHandler-f869_8PQ.mjs";
9
+ import { n as metrics_default, t as metricsPlugin } from "../metrics-B-PU4-Yu.mjs";
10
+ import { t as replyHelpersPlugin } from "../replyHelpers-CxkYGT81.mjs";
11
+ import { n as sse_default, t as ssePlugin } from "../sse-Ad7ypl9e.mjs";
12
+ import { n as versioning_default, t as versioningPlugin } from "../versioning-CDugduqI.mjs";
13
13
  import { randomUUID } from "node:crypto";
14
14
  import fp from "fastify-plugin";
15
15
  //#region src/core/arcCorePlugin.ts
@@ -1,2 +1,2 @@
1
- import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-DxjKk7eW.mjs";
1
+ import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-DdN2-wHJ.mjs";
2
2
  export { type TracingOptions, createSpan, isTracingAvailable, traced, _default as tracingPlugin };
@@ -44,7 +44,7 @@ try {
44
44
  function createTracerProvider(options) {
45
45
  if (!isAvailable) return null;
46
46
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
47
- const resolvedVersion = serviceVersion ?? "2.8.4";
47
+ const resolvedVersion = serviceVersion ?? "2.8.5";
48
48
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
49
49
  const provider = new NodeTracerProvider({ resource: { attributes: {
50
50
  "service.name": serviceName,
@@ -1,4 +1,4 @@
1
- import { t as PermissionCheck } from "../types-CVC4HOKi.mjs";
1
+ import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
2
2
  import { FastifyReply, FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/policies/PolicyInterface.d.ts
@@ -0,0 +1,49 @@
1
+ import { r as RequestScope } from "../types-BD85MlEK.mjs";
2
+ import { tt as PresetResult } from "../interface-CMRutPfe.mjs";
3
+ import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
4
+ import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-Dfzt4VTl.mjs";
5
+
6
+ //#region src/presets/filesUpload.d.ts
7
+ interface FilesUploadPresetRoutes {
8
+ upload?: boolean;
9
+ read?: boolean;
10
+ delete?: boolean;
11
+ }
12
+ interface FilesUploadPresetPermissions {
13
+ upload?: PermissionCheck;
14
+ read?: PermissionCheck;
15
+ delete?: PermissionCheck;
16
+ }
17
+ interface FilesUploadPresetOptions {
18
+ /** Any implementation of the `Storage` interface. App owns it. */
19
+ storage: Storage;
20
+ /** Multipart form field name. Default: `'file'`. */
21
+ fieldName?: string;
22
+ /** Max bytes per file. Forwarded to `multipartBody`. Default: 10 MB. */
23
+ maxFileSize?: number;
24
+ /** IANA MIME allow-list. Forwarded to `multipartBody`. Default: no filter. */
25
+ allowedMimeTypes?: string[];
26
+ /**
27
+ * Per-route permissions.
28
+ * Defaults: upload → `requireAuth()`, read → `allowPublic()`, delete → `requireAuth()`.
29
+ */
30
+ permissions?: FilesUploadPresetPermissions;
31
+ /** Opt out of individual routes. Default: all three enabled. */
32
+ includeRoutes?: FilesUploadPresetRoutes;
33
+ /**
34
+ * Map arc's `RequestScope` to `StorageContext.scope`.
35
+ * Default: `{ userId, organizationId }` extracted via `getUserId` / `getOrgId`.
36
+ * Adapters ignore keys they don't care about.
37
+ */
38
+ contextFrom?: (scope: RequestScope | undefined) => Record<string, unknown>;
39
+ }
40
+ /**
41
+ * Create a files-upload preset bound to a `Storage` adapter.
42
+ *
43
+ * The preset uses `raw: true` routes so binary responses bypass arc's JSON
44
+ * envelope. Upload still returns the standard `{ success: true, data }`
45
+ * envelope manually because the response is structured metadata, not bytes.
46
+ */
47
+ declare function filesUploadPreset(options: FilesUploadPresetOptions): PresetResult;
48
+ //#endregion
49
+ export { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, type Storage, type StorageContext, type StorageFile, type StorageReadRange, type StorageReadResult, type StorageUploadInput, filesUploadPreset };
@@ -0,0 +1,2 @@
1
+ import { t as filesUploadPreset } from "../filesUpload-C7r7HIeA.mjs";
2
+ export { filesUploadPreset };
@@ -1,4 +1,5 @@
1
- import { Ht as IControllerResponse, Ut as IRequestContext, m as AnyRecord, on as PaginationResult, tt as PresetResult, ut as ResourceConfig } from "../interface-BVuMfeVv.mjs";
1
+ import { Ht as IControllerResponse, Ut as IRequestContext, m as AnyRecord, on as PaginationResult, tt as PresetResult, ut as ResourceConfig } from "../interface-CMRutPfe.mjs";
2
+ import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
2
3
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
3
4
 
4
5
  //#region src/presets/ownedByUser.d.ts
@@ -271,4 +272,4 @@ type PresetInput = string | PresetResult | {
271
272
  */
272
273
  declare function applyPresets<TDoc = AnyRecord>(config: ResourceConfig<TDoc>, presets?: PresetInput[]): ResourceConfig<TDoc>;
273
274
  //#endregion
274
- export { type AuditedPresetOptions, type BulkOperation, type BulkPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TenantFieldSpec, type TreeOptions, applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
275
+ export { type AuditedPresetOptions, type BulkOperation, type BulkPresetOptions, type FilesUploadPresetOptions, type FilesUploadPresetPermissions, type FilesUploadPresetRoutes, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TenantFieldSpec, type TreeOptions, applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,3 +1,4 @@
1
1
  import { multiTenantPreset } from "./multiTenant.mjs";
2
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-C2xgzW6x.mjs";
3
- export { applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
3
+ import { t as filesUploadPreset } from "../filesUpload-C7r7HIeA.mjs";
4
+ export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,4 +1,4 @@
1
- import { E as CrudRouteKey, tt as PresetResult } from "../interface-BVuMfeVv.mjs";
1
+ import { E as CrudRouteKey, tt as PresetResult } from "../interface-CMRutPfe.mjs";
2
2
 
3
3
  //#region src/presets/multiTenant.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { i as CacheStore } from "./interface-DplgQO2e.mjs";
1
+ import { i as CacheStore } from "./interface-4y979v99.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/cache/QueryCache.d.ts
@@ -0,0 +1,115 @@
1
+ import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-DfLGcus7.mjs";
2
+
3
+ //#region src/idempotency/stores/redis.d.ts
4
+ interface RedisClient {
5
+ get(key: string): Promise<string | null>;
6
+ set(key: string, value: string, options?: {
7
+ EX?: number;
8
+ NX?: boolean;
9
+ }): Promise<string | null>;
10
+ del(key: string | string[]): Promise<number>;
11
+ exists(key: string | string[]): Promise<number>;
12
+ /** SCAN command — compatible with node-redis and ioredis varargs signatures. */
13
+ scan?(cursor: string | number, ...args: (string | number)[]): Promise<[string | number, string[]]>;
14
+ quit?(): Promise<string>;
15
+ disconnect?(): Promise<void>;
16
+ }
17
+ interface RedisIdempotencyStoreOptions {
18
+ /** Redis client instance */
19
+ client: RedisClient;
20
+ /** Key prefix (default: 'idem:') */
21
+ prefix?: string;
22
+ /** Lock key prefix (default: 'idem:lock:') */
23
+ lockPrefix?: string;
24
+ /** Default TTL in ms (default: 86400000 = 24 hours) */
25
+ ttlMs?: number;
26
+ }
27
+ declare class RedisIdempotencyStore implements IdempotencyStore {
28
+ readonly name = "redis";
29
+ private client;
30
+ private prefix;
31
+ private lockPrefix;
32
+ private ttlMs;
33
+ constructor(options: RedisIdempotencyStoreOptions);
34
+ private resultKey;
35
+ private lockKey;
36
+ get(key: string): Promise<IdempotencyResult | undefined>;
37
+ set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
38
+ tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
39
+ unlock(key: string, requestId: string): Promise<void>;
40
+ isLocked(key: string): Promise<boolean>;
41
+ delete(key: string): Promise<void>;
42
+ deleteByPrefix(prefix: string): Promise<number>;
43
+ findByPrefix(prefix: string): Promise<IdempotencyResult | undefined>;
44
+ /** Scan Redis keys matching a prefix pattern. Falls back to empty if SCAN unavailable. */
45
+ private scanByPrefix;
46
+ close(): Promise<void>;
47
+ }
48
+ /** Minimal ioredis shape we depend on — keeps this file peer-dep-free. */
49
+ interface IoredisLike {
50
+ get(key: string): Promise<string | null>;
51
+ set(...args: unknown[]): Promise<string | null>;
52
+ del(...keys: string[]): Promise<number>;
53
+ exists(...keys: string[]): Promise<number>;
54
+ scan(cursor: string | number, ...args: (string | number)[]): Promise<[string, string[]]>;
55
+ eval?(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;
56
+ quit?(): Promise<string>;
57
+ disconnect?(): void;
58
+ }
59
+ /**
60
+ * Wrap an ioredis instance as the arc idempotency `RedisClient`.
61
+ *
62
+ * Arc's idempotency store expects node-redis-v4 style option objects
63
+ * (`{ EX, NX }`). ioredis uses positional flags. This adapter lets users
64
+ * plug an ioredis instance in without writing the bridge themselves.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * import Redis from 'ioredis';
69
+ * import { RedisIdempotencyStore, ioredisAsIdempotencyClient }
70
+ * from '@classytic/arc/idempotency/redis';
71
+ *
72
+ * const redis = new Redis(process.env.REDIS_URL);
73
+ * const store = new RedisIdempotencyStore({
74
+ * client: ioredisAsIdempotencyClient(redis),
75
+ * });
76
+ * ```
77
+ */
78
+ declare function ioredisAsIdempotencyClient(client: IoredisLike): RedisClient & {
79
+ eval?: (script: string, numkeys: number, ...args: (string | number)[]) => Promise<unknown>;
80
+ };
81
+ /** Minimal `@upstash/redis` shape we depend on (REST client, edge-safe). */
82
+ interface UpstashRedisLike {
83
+ get(key: string): Promise<string | null | unknown>;
84
+ set(key: string, value: unknown, opts?: Record<string, unknown>): Promise<unknown>;
85
+ del(...keys: string[]): Promise<number>;
86
+ exists(...keys: string[]): Promise<number>;
87
+ scan(cursor: number | string, opts?: {
88
+ match?: string;
89
+ count?: number;
90
+ }): Promise<[number, string[]] | [string, string[]]>;
91
+ eval?(script: string, keys: string[], args: (string | number)[]): Promise<unknown>;
92
+ }
93
+ /**
94
+ * Wrap an `@upstash/redis` REST client as an idempotency `RedisClient`.
95
+ *
96
+ * Enables running arc's idempotency store on Cloudflare Workers, Vercel Edge
97
+ * and Deno Deploy — runtimes that don't support raw TCP (ioredis).
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * import { Redis } from '@upstash/redis';
102
+ * import { RedisIdempotencyStore, upstashAsIdempotencyClient }
103
+ * from '@classytic/arc/idempotency/redis';
104
+ *
105
+ * const redis = Redis.fromEnv();
106
+ * const store = new RedisIdempotencyStore({
107
+ * client: upstashAsIdempotencyClient(redis),
108
+ * });
109
+ * ```
110
+ */
111
+ declare function upstashAsIdempotencyClient(client: UpstashRedisLike): RedisClient & {
112
+ eval?: (script: string, numkeys: number, ...args: (string | number)[]) => Promise<unknown>;
113
+ };
114
+ //#endregion
115
+ export { UpstashRedisLike as a, RedisIdempotencyStoreOptions as i, RedisClient as n, ioredisAsIdempotencyClient as o, RedisIdempotencyStore as r, upstashAsIdempotencyClient as s, IoredisLike as t };