@classytic/arc 2.10.3 → 2.10.8

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 (135) hide show
  1. package/README.md +1 -1
  2. package/dist/{BaseController-CbKKIflT.mjs → BaseController-DVNKvoX4.mjs} +151 -131
  3. package/dist/actionPermissions-TUVR3uiZ.mjs +22 -0
  4. package/dist/adapters/index.d.mts +2 -2
  5. package/dist/audit/index.d.mts +2 -2
  6. package/dist/audit/index.mjs +15 -17
  7. package/dist/auth/index.d.mts +4 -4
  8. package/dist/auth/index.mjs +5 -5
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/cache/index.d.mts +2 -2
  11. package/dist/cache/index.mjs +3 -3
  12. package/dist/cli/commands/docs.mjs +2 -2
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.mjs +1 -1
  15. package/dist/cli/commands/introspect.mjs +1 -1
  16. package/dist/context/index.d.mts +58 -0
  17. package/dist/context/index.mjs +2 -0
  18. package/dist/core/index.d.mts +2 -2
  19. package/dist/core/index.mjs +2 -2
  20. package/dist/{core-CcR01lup.mjs → core-3MWJosCH.mjs} +139 -91
  21. package/dist/{createApp-BuvPma24.mjs → createApp-BwnEAO2h.mjs} +54 -20
  22. package/dist/docs/index.d.mts +2 -2
  23. package/dist/docs/index.mjs +2 -2
  24. package/dist/{elevation-C7hgL_aI.mjs → elevation-Dci0AYLT.mjs} +2 -2
  25. package/dist/errorHandler-2ii4RIYr.d.mts +114 -0
  26. package/dist/{errorHandler-Bb49BvPD.mjs → errorHandler-CSxe7KIM.mjs} +1 -1
  27. package/dist/{eventPlugin-DCUjuiQT.mjs → eventPlugin-ByU4Cv0e.mjs} +1 -1
  28. package/dist/{eventPlugin-CxWgpd6K.d.mts → eventPlugin-D1ThQ1Pp.d.mts} +1 -1
  29. package/dist/events/index.d.mts +4 -4
  30. package/dist/events/index.mjs +69 -51
  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/{fields-Lo1VUDpt.d.mts → fields-C8Y0XLAu.d.mts} +1 -1
  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/index.mjs +38 -27
  40. package/dist/idempotency/redis.d.mts +1 -1
  41. package/dist/{index-Cl0uoKd5.d.mts → index-BGbpGVyM.d.mts} +2362 -2155
  42. package/dist/{index-DStwgFUK.d.mts → index-BziRPS4H.d.mts} +1 -1
  43. package/dist/{index-ChIw3776.d.mts → index-C_Noptz-.d.mts} +3 -3
  44. package/dist/{index-8qw4y6ff.d.mts → index-EqQN6p0W.d.mts} +3 -3
  45. package/dist/index.d.mts +7 -219
  46. package/dist/index.mjs +8 -128
  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/mcp/index.d.mts +2 -2
  51. package/dist/integrations/mcp/index.mjs +1 -1
  52. package/dist/integrations/mcp/testing.d.mts +1 -1
  53. package/dist/integrations/mcp/testing.mjs +1 -1
  54. package/dist/logger/index.d.mts +81 -0
  55. package/dist/{logger-DLg8-Ueg.mjs → logger/index.mjs} +1 -6
  56. package/dist/middleware/index.d.mts +109 -0
  57. package/dist/middleware/index.mjs +70 -0
  58. package/dist/multipartBody-CUQGVlM_.mjs +123 -0
  59. package/dist/{openapi-B5F8AddX.mjs → openapi-DpNpqBmo.mjs} +9 -7
  60. package/dist/org/index.d.mts +2 -2
  61. package/dist/permissions/index.d.mts +2 -2
  62. package/dist/permissions/index.mjs +3 -3
  63. package/dist/{permissions-Dk6mshja.mjs → permissions-wkqRwicB.mjs} +2 -2
  64. package/dist/pipe-CGJxqDGx.mjs +62 -0
  65. package/dist/pipeline/index.d.mts +62 -0
  66. package/dist/pipeline/index.mjs +53 -0
  67. package/dist/plugins/index.d.mts +25 -5
  68. package/dist/plugins/index.mjs +9 -9
  69. package/dist/plugins/tracing-entry.d.mts +1 -1
  70. package/dist/plugins/tracing-entry.mjs +1 -1
  71. package/dist/presets/filesUpload.d.mts +4 -4
  72. package/dist/presets/filesUpload.mjs +255 -1
  73. package/dist/presets/index.d.mts +1 -1
  74. package/dist/presets/index.mjs +2 -2
  75. package/dist/presets/multiTenant.d.mts +1 -1
  76. package/dist/presets/multiTenant.mjs +42 -8
  77. package/dist/presets/search.d.mts +2 -2
  78. package/dist/presets/search.mjs +1 -1
  79. package/dist/{presets-fLJVXdVn.mjs → presets-CrwOvuXI.mjs} +1 -1
  80. package/dist/{queryCachePlugin-DQCEfJis.mjs → queryCachePlugin-ChLNZvFT.mjs} +2 -2
  81. package/dist/{queryCachePlugin-BKbWjgDG.d.mts → queryCachePlugin-Dumka73q.d.mts} +1 -1
  82. package/dist/{queryParser-DBqBB6AC.mjs → queryParser-NR__Qiju.mjs} +68 -1
  83. package/dist/{redis-DqyeggCa.d.mts → redis-MXLp1oOf.d.mts} +1 -1
  84. package/dist/{redis-stream-CakIQmwR.d.mts → redis-stream-bkO88VHx.d.mts} +1 -1
  85. package/dist/registry/index.d.mts +1 -1
  86. package/dist/registry/index.mjs +2 -2
  87. package/dist/{requestContext-xHIKedG6.mjs → requestContext-C38GskNt.mjs} +1 -1
  88. package/dist/{resourceToTools-BElv3xPT.mjs → resourceToTools-BhF3JV5p.mjs} +8 -3
  89. package/dist/scope/index.d.mts +2 -2
  90. package/dist/scope/index.mjs +2 -2
  91. package/dist/{sse-yBCgOLGu.mjs → sse-D8UeDwis.mjs} +1 -1
  92. package/dist/{store-helpers-ZCSMJJAX.mjs → store-helpers-DYYUQbQN.mjs} +4 -0
  93. package/dist/testing/index.d.mts +2 -2
  94. package/dist/testing/index.mjs +11 -2
  95. package/dist/testing/storageContract.d.mts +1 -1
  96. package/dist/types/index.d.mts +4 -4
  97. package/dist/types/index.mjs +1 -1
  98. package/dist/types/storage.d.mts +1 -1
  99. package/dist/{types-Btdda02s.d.mts → types-CVKBssX5.d.mts} +1 -1
  100. package/dist/{types-Co8k3NyS.d.mts → types-CVdgPXBW.d.mts} +22 -9
  101. package/dist/utils/index.d.mts +73 -3
  102. package/dist/utils/index.mjs +4 -4
  103. package/dist/{utils-B2fNOD_i.mjs → utils-LMwVidKy.mjs} +20 -2
  104. package/dist/versioning-CeUXHfjw.d.mts +117 -0
  105. package/package.json +22 -6
  106. package/skills/arc/SKILL.md +1 -1
  107. package/dist/errorHandler-DRQ3EqfL.d.mts +0 -218
  108. package/dist/filesUpload-t21LS-py.mjs +0 -377
  109. /package/dist/{EventTransport-CUw5NNWe.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
  110. /package/dist/{HookSystem-BNYKnrXF.mjs → HookSystem-BjFu7zf1.mjs} +0 -0
  111. /package/dist/{ResourceRegistry-BPd6NQDm.mjs → ResourceRegistry-CcN2LVrc.mjs} +0 -0
  112. /package/dist/{betterAuthOpenApi-BBRVhjQN.mjs → betterAuthOpenApi--rdY15Ld.mjs} +0 -0
  113. /package/dist/{caching-CBpK_SCM.mjs → caching-3h93rkJM.mjs} +0 -0
  114. /package/dist/{createActionRouter-Bp_5c_2b.mjs → createActionRouter-C8UUB3Px.mjs} +0 -0
  115. /package/dist/{elevation-C5SwtkAn.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
  116. /package/dist/{errors-CCSsMpXE.d.mts → errors-BI8kEKsO.d.mts} +0 -0
  117. /package/dist/{errors-D5c-5BJL.mjs → errors-BqdUDja_.mjs} +0 -0
  118. /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
  119. /package/dist/{fields-bxkeltzz.mjs → fields-CTMWOUDt.mjs} +0 -0
  120. /package/dist/{interface-CSbZdv_3.d.mts → interface-B-pe8fhj.d.mts} +0 -0
  121. /package/dist/{interface-D218ikEo.d.mts → interface-yhyb_pLY.d.mts} +0 -0
  122. /package/dist/{keys-qcD-TVJl.mjs → keys-nWQGUTu1.mjs} +0 -0
  123. /package/dist/{loadResources-BAzJItAJ.mjs → loadResources-Bksk8ydA.mjs} +0 -0
  124. /package/dist/{memory-B5Amv9A1.mjs → memory-DqI-449b.mjs} +0 -0
  125. /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
  126. /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
  127. /package/dist/{registry-B3lRFBWo.mjs → registry-B0Wl7uVV.mjs} +0 -0
  128. /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
  129. /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
  130. /package/dist/{storage-CVk_SEn2.d.mts → storage-BwGQXUpd.d.mts} +0 -0
  131. /package/dist/{tracing-65B51Dw3.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
  132. /package/dist/{types-Csi3FLfq.mjs → types-CDnTEpga.mjs} +0 -0
  133. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  134. /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
  135. /package/dist/{versioning-C2U_bLY0.mjs → versioning-B6mimogM.mjs} +0 -0
@@ -0,0 +1,114 @@
1
+ import { FastifyInstance, FastifyRequest } from "fastify";
2
+
3
+ //#region src/plugins/errorHandler.d.ts
4
+ /** Class-based error mapper — maps thrown error instances to HTTP responses */
5
+ interface ErrorMapper<T extends Error = Error> {
6
+ /**
7
+ * Error class to match. Checked at runtime via `instanceof` — the constructor
8
+ * arity/signature is not called by the plugin, so the signature is typed
9
+ * permissively to accept real-world error classes:
10
+ *
11
+ * - **Abstract classes** (e.g. base domain errors) — `abstract new` is accepted.
12
+ * - **Specific constructor signatures** (e.g. `new InvalidTransitionError(from, to, id?)`)
13
+ * — `any[]` avoids forcing consumers to widen to `unknown[]` or cast.
14
+ *
15
+ * What matters for dispatch is the `instanceof` check, not the ctor shape.
16
+ */
17
+ type: abstract new (...args: any[]) => T;
18
+ /** Convert the error to an HTTP response shape */
19
+ toResponse: (error: T) => {
20
+ status: number;
21
+ code?: string;
22
+ message?: string;
23
+ details?: Record<string, unknown>;
24
+ };
25
+ }
26
+ interface ErrorHandlerOptions {
27
+ /**
28
+ * Include stack trace in error responses (default: false in production)
29
+ */
30
+ includeStack?: boolean;
31
+ /**
32
+ * Custom error callback for logging to external services
33
+ */
34
+ onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
35
+ /**
36
+ * Map specific error types to custom responses (by error.name string)
37
+ */
38
+ errorMap?: Record<string, {
39
+ statusCode: number;
40
+ code: string;
41
+ message?: string;
42
+ }>;
43
+ /**
44
+ * Class-based error mappers — checked via `instanceof`, highest priority.
45
+ *
46
+ * Register your domain error classes once; Arc auto-catches and maps them
47
+ * in every handler. Handlers just `throw` — no try/catch needed.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * class AccountingError extends Error {
52
+ * constructor(message: string, public status: number, public code: string) {
53
+ * super(message);
54
+ * }
55
+ * }
56
+ *
57
+ * const app = await createApp({
58
+ * errorHandler: {
59
+ * errorMappers: [
60
+ * {
61
+ * type: AccountingError,
62
+ * toResponse: (err) => ({ status: err.status, code: err.code, message: err.message }),
63
+ * },
64
+ * ],
65
+ * },
66
+ * });
67
+ *
68
+ * // Now handlers just throw:
69
+ * handler: async (req) => {
70
+ * await ledger.post(id); // throws AccountingError → Arc maps to proper HTTP response
71
+ * }
72
+ * ```
73
+ */
74
+ errorMappers?: ErrorMapper[];
75
+ /**
76
+ * Classify an error as a duplicate-key / unique-constraint violation →
77
+ * mapped to `409 Conflict` with `code: "DUPLICATE_KEY"`.
78
+ *
79
+ * Mirrors `RepositoryLike.isDuplicateKeyError` for the Fastify layer: errors
80
+ * that escape a controller (custom routes, user hooks, raw driver calls)
81
+ * still land here, so the classifier is duplicated at the edge. Defaults
82
+ * cover MongoDB (`code 11000` / `codeName "DuplicateKey"`), Prisma
83
+ * (`code "P2002"`), and Postgres (`code "23505"`). Override to add other
84
+ * backends (DynamoDB `ConditionalCheckFailedException`, etc.) or to disable
85
+ * the built-in detection.
86
+ */
87
+ isDuplicateKeyError?: (err: unknown) => boolean;
88
+ }
89
+ /**
90
+ * Default duplicate-key detector covering the mainstream drivers arc sees
91
+ * most. Detection is strictly by known driver codes — never by message
92
+ * string matching — because false positives on dup-key silently mask real
93
+ * errors (WriteConflict, NotWritablePrimary, etc.) as 409s. For long-tail
94
+ * drivers (Neo4j, MSSQL, DynamoDB, custom kits), compose rather than
95
+ * replace:
96
+ *
97
+ * ```ts
98
+ * import { defaultIsDuplicateKeyError } from '@classytic/arc/plugins';
99
+ *
100
+ * errorHandler: {
101
+ * isDuplicateKeyError: (err) =>
102
+ * defaultIsDuplicateKeyError(err) || isNeo4jDupKey(err),
103
+ * }
104
+ * ```
105
+ *
106
+ * Drizzle apps get coverage transitively (Drizzle doesn't wrap driver
107
+ * errors — pg/mysql2/better-sqlite3 codes propagate as-is). Neon is
108
+ * Postgres-wire-compatible → `23505` covers `@neondatabase/serverless`.
109
+ */
110
+ declare function defaultIsDuplicateKeyError(err: unknown): boolean;
111
+ declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
112
+ declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
113
+ //#endregion
114
+ export { errorHandlerPlugin as i, ErrorMapper as n, defaultIsDuplicateKeyError as r, ErrorHandlerOptions as t };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { p as isArcError } from "./errors-D5c-5BJL.mjs";
2
+ import { p as isArcError } from "./errors-BqdUDja_.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/errorHandler.ts
5
5
  var errorHandler_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
2
+ import { t as requestContext } from "./requestContext-C38GskNt.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/events/EventTransport.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "./EventTransport-CUw5NNWe.mjs";
1
+ import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "./EventTransport-CfVEGaEl.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/events/defineEvent.d.ts
@@ -1,8 +1,8 @@
1
- import { kt as RepositoryLike } from "../index-Cl0uoKd5.mjs";
2
- import { a as EventMeta, c as MemoryEventTransportOptions, d as createEvent, i as EventLogger, l as PublishManyResult, n as DomainEvent, o as EventTransport, r as EventHandler, s as MemoryEventTransport, t as DeadLetteredEvent, u as createChildEvent } from "../EventTransport-CUw5NNWe.mjs";
3
- import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-CxWgpd6K.mjs";
1
+ import { _n as RepositoryLike } from "../index-BGbpGVyM.mjs";
2
+ import { a as EventMeta, c as MemoryEventTransportOptions, d as createEvent, i as EventLogger, l as PublishManyResult, n as DomainEvent, o as EventTransport, r as EventHandler, s as MemoryEventTransport, t as DeadLetteredEvent, u as createChildEvent } from "../EventTransport-CfVEGaEl.mjs";
3
+ import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-D1ThQ1Pp.mjs";
4
4
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
5
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-CakIQmwR.mjs";
5
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-bkO88VHx.mjs";
6
6
 
7
7
  //#region src/events/eventTypes.d.ts
8
8
  /**
@@ -1,5 +1,7 @@
1
- import { a as MemoryEventTransport, i as withRetry, o as createChildEvent, r as createDeadLetterPublisher, s as createEvent, t as eventPlugin } from "../eventPlugin-DCUjuiQT.mjs";
2
- import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-ZCSMJJAX.mjs";
1
+ import { a as MemoryEventTransport, i as withRetry, o as createChildEvent, r as createDeadLetterPublisher, s as createEvent, t as eventPlugin } from "../eventPlugin-ByU4Cv0e.mjs";
2
+ import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-DYYUQbQN.mjs";
3
+ import { and, anyOf, eq, lte, ne, or } from "@classytic/repo-core/filter";
4
+ import { update } from "@classytic/repo-core/update";
3
5
  //#region src/events/defineEvent.ts
4
6
  /**
5
7
  * defineEvent — Typed Event Definitions with Optional Schema Validation
@@ -339,6 +341,30 @@ var MemoryOutboxStore = class {
339
341
  };
340
342
  //#endregion
341
343
  //#region src/events/repository-outbox-adapter.ts
344
+ /**
345
+ * RepositoryLike → OutboxStore adapter.
346
+ *
347
+ * Maps the `OutboxStore` vocabulary (save / claimPending / acknowledge /
348
+ * fail / getDeadLettered / purge) onto arc's own `RepositoryLike` primitives
349
+ * (create / getOne / findAll / deleteMany / findOneAndUpdate). `EventOutbox`
350
+ * wraps a passed repository with this helper when you use the
351
+ * `{ repository }` option; the function is also re-exported from
352
+ * `@classytic/arc/events` so consumers can build and decorate the store
353
+ * manually (metrics, tracing, multi-transport fan-out).
354
+ *
355
+ * Portability: filters compose via `@classytic/repo-core/filter` and
356
+ * updates via `@classytic/repo-core/update`. The primary-key column name
357
+ * is read from `repository.idField` — mongokit defaults to `_id`,
358
+ * sqlitekit / pgkit / prismakit to the schema's declared PK. The adapter
359
+ * therefore runs on any kit that implements `StandardRepo.findOneAndUpdate`
360
+ * + `getOne` + `getAll` + `deleteMany` + `create`.
361
+ *
362
+ * `fail()` uses a lease-gated read-then-write pair to preserve
363
+ * `firstFailedAt` across retries without relying on Mongo's aggregation-
364
+ * pipeline `$ifNull`. Leases guarantee single-writer during the failure
365
+ * window (`claimPending` filters out non-owned rows), so the two calls are
366
+ * safe under concurrent relayers.
367
+ */
342
368
  const DEFAULT_LEASE_MS$1 = 3e4;
343
369
  const DEFAULT_CLAIM_LIMIT = 100;
344
370
  const DEFAULT_PURGE_BATCH = 500;
@@ -351,6 +377,7 @@ function repositoryAsOutboxStore(repository) {
351
377
  if (typeof repository.findOneAndUpdate !== "function") missing.push("findOneAndUpdate");
352
378
  if (missing.length > 0) throw new Error(`EventOutbox: repository is missing required methods: ${missing.join(", ")}. mongokit ≥3.10.2 satisfies all five; other kits must implement them to back the outbox.`);
353
379
  const r = repository;
380
+ const idField = repository.idField ?? "_id";
354
381
  /**
355
382
  * Unwrap mongokit's pagination envelope ({ docs, total, ... }) — some
356
383
  * kits may return a bare array when pagination is disabled. Handle both.
@@ -362,13 +389,20 @@ function repositoryAsOutboxStore(repository) {
362
389
  const isDuplicateKeyError = createIsDuplicateKeyError(repository);
363
390
  const safeGetOne = createSafeGetOne(repository);
364
391
  const isWellFormed = (event) => !!event && typeof event.type === "string" && !!event.meta?.id;
392
+ /**
393
+ * Filter matching every row that's eligible to be claimed by a relayer:
394
+ * status=pending, visible now, and either unleased or under an expired
395
+ * lease. Used by `getPending` and `claimPending` — defined once so the
396
+ * two code paths stay in lockstep.
397
+ */
398
+ const claimableFilter = (now) => and(eq("status", "pending"), lte("visibleAt", now), or(eq("leaseOwner", null), lte("leaseExpiresAt", now)));
365
399
  return {
366
400
  async save(event, options) {
367
401
  if (!event?.type || typeof event.type !== "string") throw new InvalidOutboxEventError("event.type is required");
368
402
  if (!event.meta?.id || typeof event.meta.id !== "string") throw new InvalidOutboxEventError("event.meta.id is required");
369
403
  const now = /* @__PURE__ */ new Date();
370
404
  const doc = {
371
- _id: event.meta.id,
405
+ [idField]: event.meta.id,
372
406
  event,
373
407
  type: event.type,
374
408
  status: "pending",
@@ -395,11 +429,7 @@ function repositoryAsOutboxStore(repository) {
395
429
  async getPending(limit) {
396
430
  const now = /* @__PURE__ */ new Date();
397
431
  return unwrapDocs(await r.getAll({
398
- filters: {
399
- status: "pending",
400
- visibleAt: { $lte: now },
401
- $or: [{ leaseOwner: null }, { leaseExpiresAt: { $lte: now } }]
402
- },
432
+ filters: claimableFilter(now),
403
433
  sort: { createdAt: 1 },
404
434
  page: 1,
405
435
  limit
@@ -409,23 +439,19 @@ function repositoryAsOutboxStore(repository) {
409
439
  const limit = options?.limit ?? DEFAULT_CLAIM_LIMIT;
410
440
  const leaseMs = options?.leaseMs ?? DEFAULT_LEASE_MS$1;
411
441
  const consumerId = options?.consumerId ?? "anonymous";
412
- const typeFilter = options?.types?.length ? { type: { $in: options.types } } : {};
442
+ const typeFilter = options?.types?.length ? anyOf("type", options.types) : null;
413
443
  const claimed = [];
414
444
  for (let i = 0; i < limit; i++) {
415
445
  const now = /* @__PURE__ */ new Date();
416
446
  const leaseExpiresAt = new Date(now.getTime() + leaseMs);
417
- const doc = await r.findOneAndUpdate({
418
- status: "pending",
419
- visibleAt: { $lte: now },
420
- $or: [{ leaseOwner: null }, { leaseExpiresAt: { $lte: now } }],
421
- ...typeFilter
422
- }, {
423
- $set: {
447
+ const filter = typeFilter ? and(claimableFilter(now), typeFilter) : claimableFilter(now);
448
+ const doc = await r.findOneAndUpdate(filter, update({
449
+ set: {
424
450
  leaseOwner: consumerId,
425
451
  leaseExpiresAt
426
452
  },
427
- $inc: { attempts: 1 }
428
- }, {
453
+ inc: { attempts: 1 }
454
+ }), {
429
455
  sort: { createdAt: 1 },
430
456
  returnDocument: "after"
431
457
  });
@@ -436,18 +462,15 @@ function repositoryAsOutboxStore(repository) {
436
462
  },
437
463
  async acknowledge(eventId, options) {
438
464
  const now = /* @__PURE__ */ new Date();
439
- const filter = {
440
- _id: eventId,
441
- status: { $ne: "delivered" }
442
- };
443
- if (options?.consumerId) filter.leaseOwner = options.consumerId;
444
- if (await r.findOneAndUpdate(filter, { $set: {
465
+ const baseFilter = and(eq(idField, eventId), ne("status", "delivered"));
466
+ const filter = options?.consumerId ? and(baseFilter, eq("leaseOwner", options.consumerId)) : baseFilter;
467
+ if (await r.findOneAndUpdate(filter, update({ set: {
445
468
  status: "delivered",
446
469
  deliveredAt: now,
447
470
  leaseOwner: null,
448
471
  leaseExpiresAt: null
449
- } }, { returnDocument: "after" })) return;
450
- const current = await safeGetOne({ _id: eventId });
472
+ } }), { returnDocument: "after" })) return;
473
+ const current = await safeGetOne(eq(idField, eventId));
451
474
  if (!current) return;
452
475
  if (current.status === "delivered") return;
453
476
  if (options?.consumerId && current.leaseOwner !== options.consumerId) throw new OutboxOwnershipError(eventId, options.consumerId, current.leaseOwner);
@@ -456,32 +479,30 @@ function repositoryAsOutboxStore(repository) {
456
479
  const now = /* @__PURE__ */ new Date();
457
480
  const targetStatus = options?.deadLetter ? "dead_letter" : "pending";
458
481
  const visibleAt = options?.retryAt ?? now;
459
- const filter = { _id: eventId };
460
- if (options?.consumerId) filter.leaseOwner = options.consumerId;
461
- const pipeline = [{ $set: {
482
+ const baseFilter = eq(idField, eventId);
483
+ const filter = options?.consumerId ? and(baseFilter, eq("leaseOwner", options.consumerId)) : baseFilter;
484
+ const current = await safeGetOne(baseFilter);
485
+ if (!current) return;
486
+ if (options?.consumerId && current.leaseOwner !== options.consumerId) throw new OutboxOwnershipError(eventId, options.consumerId, current.leaseOwner);
487
+ const errorInfo = error.code ? {
488
+ message: error.message,
489
+ code: error.code
490
+ } : { message: error.message };
491
+ const firstFailedAt = current.firstFailedAt ?? now;
492
+ await r.findOneAndUpdate(filter, update({ set: {
462
493
  status: targetStatus,
463
494
  visibleAt,
464
495
  leaseOwner: null,
465
496
  leaseExpiresAt: null,
466
497
  lastFailedAt: now,
467
- lastError: {
468
- message: error.message,
469
- ...error.code ? { code: error.code } : {}
470
- },
471
- firstFailedAt: { $ifNull: ["$firstFailedAt", now] }
472
- } }];
473
- if (await r.findOneAndUpdate(filter, pipeline, {
474
- returnDocument: "after",
475
- updatePipeline: true
476
- })) return;
477
- const current = await safeGetOne({ _id: eventId });
478
- if (!current) return;
479
- if (options?.consumerId && current.leaseOwner !== options.consumerId) throw new OutboxOwnershipError(eventId, options.consumerId, current.leaseOwner);
498
+ lastError: errorInfo,
499
+ firstFailedAt
500
+ } }), { returnDocument: "after" });
480
501
  },
481
502
  async getDeadLettered(limit) {
482
503
  return unwrapDocs(await r.getAll({
483
- filters: { status: "dead_letter" },
484
- sort: { _id: 1 },
504
+ filters: eq("status", "dead_letter"),
505
+ sort: { [idField]: 1 },
485
506
  page: 1,
486
507
  limit
487
508
  })).filter((d) => isWellFormed(d.event)).map((d) => ({
@@ -500,18 +521,15 @@ function repositoryAsOutboxStore(repository) {
500
521
  let totalDeleted = 0;
501
522
  for (;;) {
502
523
  const batch = unwrapDocs(await r.getAll({
503
- filters: {
504
- status: "delivered",
505
- deliveredAt: { $lte: cutoff }
506
- },
524
+ filters: and(eq("status", "delivered"), lte("deliveredAt", cutoff)),
507
525
  sort: { deliveredAt: 1 },
508
526
  page: 1,
509
527
  limit: DEFAULT_PURGE_BATCH,
510
- select: "_id"
528
+ select: idField
511
529
  }));
512
530
  if (batch.length === 0) break;
513
- const ids = batch.map((d) => d._id);
514
- const res = await r.deleteMany({ _id: { $in: ids } });
531
+ const ids = batch.map((d) => d[idField]);
532
+ const res = await r.deleteMany(anyOf(idField, ids));
515
533
  totalDeleted += res.deletedCount ?? 0;
516
534
  if (batch.length < DEFAULT_PURGE_BATCH) break;
517
535
  }
@@ -1,2 +1,2 @@
1
- import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-CakIQmwR.mjs";
1
+ import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-bkO88VHx.mjs";
2
2
  export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
@@ -1,4 +1,4 @@
1
- import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "../../EventTransport-CUw5NNWe.mjs";
1
+ import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "../../EventTransport-CfVEGaEl.mjs";
2
2
 
3
3
  //#region src/events/transports/redis.d.ts
4
4
  interface RedisLike {
@@ -1,4 +1,4 @@
1
- import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-Co8k3NyS.mjs";
1
+ import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-CVdgPXBW.mjs";
2
2
  import { FastifyInstance } from "fastify";
3
3
 
4
4
  //#region src/factory/createApp.d.ts
@@ -1,5 +1,5 @@
1
- import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-BuvPma24.mjs";
2
- import { t as loadResources } from "../loadResources-BAzJItAJ.mjs";
1
+ import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-BwnEAO2h.mjs";
2
+ import { t as loadResources } from "../loadResources-Bksk8ydA.mjs";
3
3
  //#region src/factory/edge.ts
4
4
  /**
5
5
  * Convert a Fastify app into a Web Standards fetch handler.
@@ -1,4 +1,4 @@
1
- import { r as RequestScope } from "./types-BD85MlEK.mjs";
1
+ import { r as RequestScope } from "./types-tgR4Pt8F.mjs";
2
2
  import { FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/permissions/types.d.ts
@@ -1,2 +1,2 @@
1
- import { $t as HookPhase, Qt as HookOperation, Xt as HookContext, Yt as DefineHookOptions, Zt as HookHandler, an as afterUpdate, cn as beforeUpdate, en as HookRegistration, in as afterDelete, ln as createHookSystem, nn as HookSystemOptions, on as beforeCreate, rn as afterCreate, sn as beforeDelete, tn as HookSystem, un as defineHook } from "../index-Cl0uoKd5.mjs";
1
+ import { $ as beforeCreate, G as HookOperation, H as DefineHookOptions, J as HookSystem, K as HookPhase, Q as afterUpdate, U as HookContext, W as HookHandler, X as afterCreate, Y as HookSystemOptions, Z as afterDelete, et as beforeDelete, nt as createHookSystem, q as HookRegistration, rt as defineHook, tt as beforeUpdate } from "../index-BGbpGVyM.mjs";
2
2
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,2 +1,2 @@
1
- import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-BNYKnrXF.mjs";
1
+ import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-BjFu7zf1.mjs";
2
2
  export { HookSystem, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,6 +1,6 @@
1
- import { kt as RepositoryLike } from "../index-Cl0uoKd5.mjs";
2
- import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-CSbZdv_3.mjs";
3
- import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-DqyeggCa.mjs";
1
+ import { _n as RepositoryLike } from "../index-BGbpGVyM.mjs";
2
+ import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-B-pe8fhj.mjs";
3
+ import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-MXLp1oOf.mjs";
4
4
  import { FastifyPluginAsync } from "fastify";
5
5
 
6
6
  //#region src/idempotency/idempotencyPlugin.d.ts
@@ -1,7 +1,28 @@
1
- import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-ZCSMJJAX.mjs";
1
+ import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-DYYUQbQN.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
+ import { and, eq, exists, gt, lt, or, startsWith } from "@classytic/repo-core/filter";
5
+ import { update } from "@classytic/repo-core/update";
4
6
  //#region src/idempotency/repository-idempotency-adapter.ts
7
+ /**
8
+ * RepositoryLike → IdempotencyStore adapter.
9
+ *
10
+ * Maps the idempotency store's verbs (get / set / tryLock / unlock / delete /
11
+ * deleteByPrefix / findByPrefix) onto arc's canonical repository primitives
12
+ * (`getOne` / `deleteMany` / `findOneAndUpdate`). `idempotencyPlugin` wraps
13
+ * a passed repository with this helper when you use the `{ repository }`
14
+ * option; the function is also re-exported from `@classytic/arc/idempotency`
15
+ * so consumers can build and decorate the store (metrics, tracing, key
16
+ * namespacing) before passing it via `store:`.
17
+ *
18
+ * Portability: filters compose via `@classytic/repo-core/filter` builders
19
+ * (`and` / `or` / `eq` / `gt` / `lt` / `exists` / `startsWith`) and updates
20
+ * via `@classytic/repo-core/update` (`update({ set, unset, setOnInsert })`).
21
+ * Both IRs compile to Mongo operators on mongokit, SQL predicates on
22
+ * sqlitekit / pgkit, and `WhereInput` / `update` on prismakit. The store
23
+ * therefore runs identically on every backend that implements the
24
+ * `StandardRepo.findOneAndUpdate` + `getOne` + `deleteMany` surface.
25
+ */
5
26
  function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
6
27
  const missing = [];
7
28
  if (typeof repository.getOne !== "function") missing.push("getOne");
@@ -9,13 +30,13 @@ function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
9
30
  if (typeof repository.findOneAndUpdate !== "function") missing.push("findOneAndUpdate");
10
31
  if (missing.length > 0) throw new Error(`idempotencyPlugin: repository is missing required methods: ${missing.join(", ")}. mongokit ≥3.8 satisfies these; other kits must implement them to back idempotency via a repository.`);
11
32
  const r = repository;
33
+ const idField = repository.idField ?? "_id";
12
34
  const isDuplicateKeyError = createIsDuplicateKeyError(repository);
13
35
  const safeGetOne = createSafeGetOne(repository);
14
- const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
36
  return {
16
37
  name: "repository",
17
38
  async get(key) {
18
- const doc = await safeGetOne({ _id: key });
39
+ const doc = await safeGetOne(eq(idField, key));
19
40
  if (!doc?.result) return void 0;
20
41
  if (new Date(doc.expiresAt) < /* @__PURE__ */ new Date()) return void 0;
21
42
  return {
@@ -28,8 +49,8 @@ function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
28
49
  };
29
50
  },
30
51
  async set(key, result) {
31
- await r.findOneAndUpdate({ _id: key }, {
32
- $set: {
52
+ await r.findOneAndUpdate(eq(idField, key), update({
53
+ set: {
33
54
  result: {
34
55
  statusCode: result.statusCode,
35
56
  headers: result.headers,
@@ -38,8 +59,8 @@ function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
38
59
  createdAt: result.createdAt,
39
60
  expiresAt: result.expiresAt
40
61
  },
41
- $unset: { lock: "" }
42
- }, {
62
+ unset: ["lock"]
63
+ }), {
43
64
  upsert: true,
44
65
  returnDocument: "after"
45
66
  });
@@ -49,19 +70,16 @@ function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
49
70
  const lockExpiresAt = new Date(now.getTime() + ttlMs);
50
71
  const docExpiresAt = new Date(now.getTime() + defaultTtlMs);
51
72
  try {
52
- const doc = await r.findOneAndUpdate({
53
- _id: key,
54
- $or: [{ lock: { $exists: false } }, { "lock.expiresAt": { $lt: now } }]
55
- }, {
56
- $set: { lock: {
73
+ const doc = await r.findOneAndUpdate(and(eq(idField, key), or(exists("lock", false), lt("lock.expiresAt", now))), update({
74
+ set: { lock: {
57
75
  requestId,
58
76
  expiresAt: lockExpiresAt
59
77
  } },
60
- $setOnInsert: {
78
+ setOnInsert: {
61
79
  createdAt: now,
62
80
  expiresAt: docExpiresAt
63
81
  }
64
- }, {
82
+ }), {
65
83
  upsert: true,
66
84
  returnDocument: "after"
67
85
  });
@@ -72,31 +90,24 @@ function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
72
90
  }
73
91
  },
74
92
  async unlock(key, requestId) {
75
- await r.findOneAndUpdate({
76
- _id: key,
77
- "lock.requestId": requestId
78
- }, { $unset: { lock: "" } });
93
+ await r.findOneAndUpdate(and(eq(idField, key), eq("lock.requestId", requestId)), update({ unset: ["lock"] }));
79
94
  },
80
95
  async isLocked(key) {
81
- const doc = await safeGetOne({ _id: key });
96
+ const doc = await safeGetOne(eq(idField, key));
82
97
  if (!doc?.lock) return false;
83
98
  return new Date(doc.lock.expiresAt) > /* @__PURE__ */ new Date();
84
99
  },
85
100
  async delete(key) {
86
- await r.deleteMany({ _id: key });
101
+ await r.deleteMany(eq(idField, key));
87
102
  },
88
103
  async deleteByPrefix(prefix) {
89
- return (await r.deleteMany({ _id: { $regex: `^${escapeRegex(prefix)}` } })).deletedCount ?? 0;
104
+ return (await r.deleteMany(startsWith(idField, prefix, "sensitive"))).deletedCount ?? 0;
90
105
  },
91
106
  async findByPrefix(prefix) {
92
- const doc = await safeGetOne({
93
- _id: { $regex: `^${escapeRegex(prefix)}` },
94
- result: { $exists: true },
95
- expiresAt: { $gt: /* @__PURE__ */ new Date() }
96
- });
107
+ const doc = await safeGetOne(and(startsWith(idField, prefix, "sensitive"), exists("result", true), gt("expiresAt", /* @__PURE__ */ new Date())));
97
108
  if (!doc?.result) return void 0;
98
109
  return {
99
- key: doc._id,
110
+ key: String(doc[idField] ?? prefix),
100
111
  statusCode: doc.result.statusCode,
101
112
  headers: doc.result.headers,
102
113
  body: doc.result.body,
@@ -1,2 +1,2 @@
1
- import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-DqyeggCa.mjs";
1
+ import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-MXLp1oOf.mjs";
2
2
  export { type IoredisLike, type RedisClient, RedisIdempotencyStore, type RedisIdempotencyStoreOptions, type UpstashRedisLike, ioredisAsIdempotencyClient, upstashAsIdempotencyClient };