@classytic/arc 2.8.5 → 2.10.3

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 (155) hide show
  1. package/README.md +50 -38
  2. package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-CbKKIflT.mjs} +193 -143
  3. package/dist/EventTransport-CUw5NNWe.d.mts +293 -0
  4. package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
  5. package/dist/adapters/index.d.mts +3 -3
  6. package/dist/adapters/index.mjs +2 -2
  7. package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
  8. package/dist/audit/index.d.mts +135 -11
  9. package/dist/audit/index.mjs +107 -20
  10. package/dist/auth/index.d.mts +17 -9
  11. package/dist/auth/index.mjs +14 -7
  12. package/dist/auth/redis-session.d.mts +1 -1
  13. package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +1 -1
  14. package/dist/cache/index.d.mts +17 -15
  15. package/dist/cache/index.mjs +15 -14
  16. package/dist/{caching-IMuYVjTL.mjs → caching-CBpK_SCM.mjs} +8 -3
  17. package/dist/cli/commands/describe.mjs +1 -1
  18. package/dist/cli/commands/docs.mjs +2 -2
  19. package/dist/cli/commands/generate.mjs +1 -1
  20. package/dist/cli/commands/init.mjs +1 -1
  21. package/dist/cli/commands/introspect.mjs +1 -1
  22. package/dist/core/index.d.mts +3 -3
  23. package/dist/core/index.mjs +4 -6
  24. package/dist/{defineResource-tcgySDo1.mjs → core-CcR01lup.mjs} +58 -61
  25. package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-Bp_5c_2b.mjs} +3 -3
  26. package/dist/{createApp-B1EY8zxa.mjs → createApp-BuvPma24.mjs} +15 -14
  27. package/dist/docs/index.d.mts +2 -2
  28. package/dist/docs/index.mjs +2 -2
  29. package/dist/{elevation-DtFxrG0s.mjs → elevation-C7hgL_aI.mjs} +22 -8
  30. package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-Bb49BvPD.mjs} +59 -7
  31. package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DRQ3EqfL.d.mts} +37 -2
  32. package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-CxWgpd6K.d.mts} +14 -2
  33. package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-DCUjuiQT.mjs} +83 -5
  34. package/dist/events/index.d.mts +150 -36
  35. package/dist/events/index.mjs +355 -101
  36. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  37. package/dist/events/transports/redis.d.mts +1 -1
  38. package/dist/factory/index.d.mts +1 -1
  39. package/dist/factory/index.mjs +2 -2
  40. package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
  41. package/dist/{fields-ipsbIRPK.mjs → fields-bxkeltzz.mjs} +18 -5
  42. package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-t21LS-py.mjs} +65 -7
  43. package/dist/hooks/index.d.mts +1 -1
  44. package/dist/hooks/index.mjs +1 -1
  45. package/dist/idempotency/index.d.mts +32 -5
  46. package/dist/idempotency/index.mjs +119 -12
  47. package/dist/idempotency/redis.d.mts +1 -1
  48. package/dist/{index-DtDzOBn8.d.mts → index-8qw4y6ff.d.mts} +4 -135
  49. package/dist/{index-BLXBmWud.d.mts → index-ChIw3776.d.mts} +283 -408
  50. package/dist/{interface-CMRutPfe.d.mts → index-Cl0uoKd5.d.mts} +1758 -2506
  51. package/dist/{index-C1meYuDn.d.mts → index-DStwgFUK.d.mts} +81 -7
  52. package/dist/index.d.mts +7 -8
  53. package/dist/index.mjs +11 -12
  54. package/dist/integrations/event-gateway.d.mts +1 -1
  55. package/dist/integrations/event-gateway.mjs +1 -1
  56. package/dist/integrations/index.d.mts +1 -1
  57. package/dist/integrations/mcp/index.d.mts +26 -8
  58. package/dist/integrations/mcp/index.mjs +96 -17
  59. package/dist/integrations/mcp/testing.d.mts +1 -1
  60. package/dist/integrations/mcp/testing.mjs +1 -1
  61. package/dist/integrations/webhooks.d.mts +5 -0
  62. package/dist/integrations/webhooks.mjs +6 -0
  63. package/dist/interface-D218ikEo.d.mts +77 -0
  64. package/dist/{memory-Cp7_cAko.mjs → memory-B5Amv9A1.mjs} +23 -8
  65. package/dist/{openapi-CbKUJY_m.mjs → openapi-B5F8AddX.mjs} +3 -3
  66. package/dist/org/index.d.mts +2 -2
  67. package/dist/permissions/index.d.mts +3 -4
  68. package/dist/permissions/index.mjs +5 -5
  69. package/dist/{permissions-CH4cNwJi.mjs → permissions-Dk6mshja.mjs} +315 -397
  70. package/dist/plugins/index.d.mts +7 -7
  71. package/dist/plugins/index.mjs +14 -16
  72. package/dist/plugins/response-cache.mjs +2 -2
  73. package/dist/plugins/tracing-entry.d.mts +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/presets/filesUpload.d.mts +27 -5
  76. package/dist/presets/filesUpload.mjs +1 -1
  77. package/dist/presets/index.d.mts +3 -2
  78. package/dist/presets/index.mjs +4 -3
  79. package/dist/presets/multiTenant.d.mts +1 -1
  80. package/dist/presets/multiTenant.mjs +2 -2
  81. package/dist/presets/search.d.mts +178 -0
  82. package/dist/presets/search.mjs +150 -0
  83. package/dist/{presets-C2xgzW6x.mjs → presets-fLJVXdVn.mjs} +1 -1
  84. package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
  85. package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DQCEfJis.mjs} +9 -9
  86. package/dist/{queryParser-CgCtsjti.mjs → queryParser-DBqBB6AC.mjs} +1 -1
  87. package/dist/{redis-BM00zaPB.d.mts → redis-DqyeggCa.d.mts} +1 -1
  88. package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
  89. package/dist/registry/index.d.mts +1 -1
  90. package/dist/registry/index.mjs +2 -2
  91. package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-BElv3xPT.mjs} +65 -48
  92. package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
  93. package/dist/scope/index.d.mts +1 -1
  94. package/dist/scope/index.mjs +2 -2
  95. package/dist/{sse-Ad7ypl9e.mjs → sse-yBCgOLGu.mjs} +1 -1
  96. package/dist/store-helpers-ZCSMJJAX.mjs +57 -0
  97. package/dist/testing/index.d.mts +9 -17
  98. package/dist/testing/index.mjs +27 -83
  99. package/dist/testing/storageContract.d.mts +1 -1
  100. package/dist/types/index.d.mts +4 -4
  101. package/dist/types/index.mjs +1 -31
  102. package/dist/types/storage.d.mts +1 -1
  103. package/dist/{types-BsbNMEDR.d.mts → types-Btdda02s.d.mts} +1 -1
  104. package/dist/{types-Ch9pTQbf.d.mts → types-Co8k3NyS.d.mts} +11 -9
  105. package/dist/types-Csi3FLfq.mjs +27 -0
  106. package/dist/utils/index.d.mts +208 -4
  107. package/dist/utils/index.mjs +5 -6
  108. package/dist/{utils-yYT3HDXt.mjs → utils-B2fNOD_i.mjs} +285 -2
  109. package/dist/{versioning-CDugduqI.mjs → versioning-C2U_bLY0.mjs} +3 -5
  110. package/package.json +20 -26
  111. package/skills/arc/SKILL.md +97 -23
  112. package/skills/arc/references/auth.md +94 -0
  113. package/skills/arc/references/events.md +200 -12
  114. package/skills/arc/references/mcp.md +4 -17
  115. package/skills/arc/references/multi-tenancy.md +43 -0
  116. package/skills/arc/references/production.md +34 -60
  117. package/dist/EventTransport-BXja8NOc.d.mts +0 -135
  118. package/dist/audit/mongodb.d.mts +0 -2
  119. package/dist/audit/mongodb.mjs +0 -2
  120. package/dist/circuitBreaker-cmi5XDv5.mjs +0 -284
  121. package/dist/circuitBreaker-dTtG-UyS.d.mts +0 -206
  122. package/dist/core-F0QoWBt2.mjs +0 -34
  123. package/dist/dynamic/index.d.mts +0 -93
  124. package/dist/dynamic/index.mjs +0 -122
  125. package/dist/fields-DpZQa_Q3.d.mts +0 -109
  126. package/dist/idempotency/mongodb.d.mts +0 -2
  127. package/dist/idempotency/mongodb.mjs +0 -123
  128. package/dist/interface-4y979v99.d.mts +0 -54
  129. package/dist/mongodb-BsP-WbhN.d.mts +0 -127
  130. package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
  131. package/dist/mongodb-Utc5k_-0.mjs +0 -90
  132. package/dist/policies/index.d.mts +0 -432
  133. package/dist/policies/index.mjs +0 -318
  134. package/dist/rpc/index.d.mts +0 -90
  135. package/dist/rpc/index.mjs +0 -248
  136. /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
  137. /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  138. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  139. /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
  140. /package/dist/{errors-Ck2h67pm.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  141. /package/dist/{errors-BF2bIOIS.mjs → errors-D5c-5BJL.mjs} +0 -0
  142. /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
  143. /package/dist/{interface-DfLGcus7.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  144. /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-BAzJItAJ.mjs} +0 -0
  145. /package/dist/{logger-D1YrIImS.mjs → logger-DLg8-Ueg.mjs} +0 -0
  146. /package/dist/{metrics-B-PU4-Yu.mjs → metrics-DuhiSEZI.mjs} +0 -0
  147. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
  148. /package/dist/{registry-BiTKT1Dg.mjs → registry-B3lRFBWo.mjs} +0 -0
  149. /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
  150. /package/dist/{requestContext-DYvHl113.mjs → requestContext-xHIKedG6.mjs} +0 -0
  151. /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
  152. /package/dist/{storage-Dfzt4VTl.d.mts → storage-CVk_SEn2.d.mts} +0 -0
  153. /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  154. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
  155. /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
@@ -0,0 +1,293 @@
1
+ //#region src/events/EventTransport.d.ts
2
+ /**
3
+ * Event Transport Interface
4
+ *
5
+ * Defines contract for event delivery backends.
6
+ * Implement for durable transports (Redis, RabbitMQ, Kafka, etc.)
7
+ *
8
+ * @example
9
+ * // Redis Pub/Sub implementation
10
+ * class RedisEventTransport implements EventTransport {
11
+ * async publish(event) {
12
+ * await redis.publish(event.type, JSON.stringify(event));
13
+ * }
14
+ * async subscribe(pattern, handler) {
15
+ * redis.psubscribe(pattern);
16
+ * redis.on('pmessage', (p, channel, msg) => handler(JSON.parse(msg)));
17
+ * }
18
+ * }
19
+ */
20
+ /**
21
+ * Event metadata.
22
+ *
23
+ * Split out as a standalone interface so primitives / downstream packages can
24
+ * mirror it without re-declaring the DomainEvent wrapper. See events.ts in
25
+ * @classytic/primitives for the sibling shape.
26
+ */
27
+ interface EventMeta {
28
+ /** Unique event ID (UUID v4 recommended) */
29
+ id: string;
30
+ /** Event timestamp */
31
+ timestamp: Date;
32
+ /**
33
+ * Schema version for this event type. Default: `1`.
34
+ *
35
+ * Use when the payload shape evolves so handlers can branch on version
36
+ * during migration windows (`if (event.meta.schemaVersion === 2) ...`).
37
+ * Bump ONLY when the payload contract changes in a breaking way.
38
+ */
39
+ schemaVersion?: number;
40
+ /**
41
+ * Correlation ID — stays stable across an entire causal chain so a single
42
+ * user action can be traced through every downstream event. Spans service
43
+ * boundaries. Generated at the edge (HTTP request, CLI invocation) and
44
+ * inherited by every child event.
45
+ */
46
+ correlationId?: string;
47
+ /**
48
+ * Causation ID — the `meta.id` of the direct parent event that caused
49
+ * this one. Forms a linked-list of cause-and-effect within a correlation.
50
+ *
51
+ * Distinct from correlationId: correlation groups, causation chains.
52
+ * Use {@link createChildEvent} to populate this automatically.
53
+ */
54
+ causationId?: string;
55
+ /**
56
+ * Partition key hint for ordered transports (Kafka, Kinesis, Redis Streams
57
+ * consumer groups). Events with the same partitionKey are guaranteed to be
58
+ * delivered in publish order by transports that honour it.
59
+ *
60
+ * Defaults to `resourceId` if unset. Transports that don't support ordering
61
+ * (in-memory, simple pub/sub) ignore this field.
62
+ */
63
+ partitionKey?: string;
64
+ /** Source resource (e.g. 'order', 'transaction') */
65
+ resource?: string;
66
+ /** Resource identifier */
67
+ resourceId?: string;
68
+ /** User who triggered the event */
69
+ userId?: string;
70
+ /** Organization context */
71
+ organizationId?: string;
72
+ /**
73
+ * Originating service or package (e.g. `'commerce'`, `'billing'`, `'arc-core'`).
74
+ *
75
+ * In a multi-service deployment, consumers route / log / alert by `source`
76
+ * without parsing `type` prefixes. Arc itself never populates this — hosts
77
+ * set it once per emitter (`app.events.publish('order.placed', p, { source: 'commerce' })`).
78
+ * Inherited by {@link createChildEvent} so downstream events carry the same
79
+ * source unless overridden.
80
+ */
81
+ source?: string;
82
+ /**
83
+ * Idempotency key — stable hint that this event represents a specific
84
+ * operation exactly once. Consumers dedupe with `if (processed.has(meta.idempotencyKey)) return`.
85
+ *
86
+ * Survives every transport (Memory / Pub-Sub / Streams / Kafka) because it's
87
+ * part of the event, not a transport-side option. Distinct from `meta.id`
88
+ * (which is fresh per emit — a retry would produce a new id).
89
+ *
90
+ * Typical sources: HTTP `Idempotency-Key` header, outbox `dedupeKey`, or
91
+ * `{aggregate.type}:{aggregate.id}:{action}`. Inherited by child events.
92
+ */
93
+ idempotencyKey?: string;
94
+ /**
95
+ * DDD aggregate marker — the aggregate that owns this event's invariant.
96
+ *
97
+ * Use when routing events by aggregate, doing event-sourcing replay, or
98
+ * enforcing consistency boundaries. Distinct from `resource` / `resourceId`
99
+ * (HTTP-origin entity) because an event emitted *by* one REST resource can
100
+ * *belong to* a different aggregate (e.g. `POST /orders/:id/ship` emits
101
+ * `shipment.dispatched` owned by a shipment aggregate).
102
+ *
103
+ * Downstream packages narrow `aggregate.type` to their own string union via
104
+ * interface extension:
105
+ *
106
+ * ```ts
107
+ * type CartAggregateType = 'cart' | 'cart-item';
108
+ * interface CartEventMeta extends EventMeta {
109
+ * aggregate?: { type: CartAggregateType; id: string };
110
+ * }
111
+ * ```
112
+ *
113
+ * Not inherited by {@link createChildEvent} — child events typically belong
114
+ * to a different aggregate than their parent.
115
+ */
116
+ aggregate?: {
117
+ type: string;
118
+ id: string;
119
+ };
120
+ }
121
+ interface DomainEvent<T = unknown> {
122
+ /** Event type (e.g., 'product.created', 'order.shipped') */
123
+ type: string;
124
+ /** Event payload */
125
+ payload: T;
126
+ /** Event metadata */
127
+ meta: EventMeta;
128
+ }
129
+ type EventHandler<T = unknown> = (event: DomainEvent<T>) => void | Promise<void>;
130
+ /**
131
+ * A permanently-failed event routed to a dead-letter sink after retries
132
+ * have been exhausted. Mirrors the shape a caller would log, alert on, or
133
+ * replay from once the upstream issue is fixed.
134
+ */
135
+ interface DeadLetteredEvent<T = unknown> {
136
+ /** The original event */
137
+ event: DomainEvent<T>;
138
+ /** Serialised failure reason (message + optional machine code + stack) */
139
+ error: {
140
+ message: string;
141
+ code?: string;
142
+ stack?: string;
143
+ };
144
+ /** How many delivery attempts were made before giving up */
145
+ attempts: number;
146
+ /** First failure timestamp */
147
+ firstFailedAt: Date;
148
+ /** Last failure timestamp (immediately before dead-lettering) */
149
+ lastFailedAt: Date;
150
+ /** Optional handler / subscriber name that last failed (for debug) */
151
+ handlerName?: string;
152
+ }
153
+ /**
154
+ * Minimal logger interface for event transports.
155
+ * Compatible with `console`, `pino`, `fastify.log`, and any custom logger.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * // Use Fastify's logger
160
+ * new MemoryEventTransport({ logger: fastify.log });
161
+ *
162
+ * // Use a custom logger
163
+ * new MemoryEventTransport({ logger: { warn: myWarn, error: myError } });
164
+ *
165
+ * // Default: console (no logger option needed)
166
+ * new MemoryEventTransport();
167
+ * ```
168
+ */
169
+ interface EventLogger {
170
+ warn(message: string, ...args: unknown[]): void;
171
+ error(message: string, ...args: unknown[]): void;
172
+ }
173
+ interface EventTransport {
174
+ /** Transport name for logging */
175
+ readonly name: string;
176
+ /**
177
+ * Publish an event to the transport
178
+ */
179
+ publish(event: DomainEvent): Promise<void>;
180
+ /**
181
+ * Publish a batch of events to the transport (optional, v2.8.1+).
182
+ *
183
+ * Transports that can efficiently batch (Kafka producer, Redis pipeline,
184
+ * RabbitMQ publisher confirms, SQS send-message-batch) should implement
185
+ * this. {@link import('./outbox.js').EventOutbox.relay} auto-detects and
186
+ * uses it for much higher throughput than per-event publishing.
187
+ *
188
+ * **Contract**: the returned `PublishManyResult` must describe the
189
+ * per-event outcome so the caller can acknowledge successes and fail the
190
+ * rest. Partial success is allowed — the transport reports it per event.
191
+ *
192
+ * If not implemented, `EventOutbox.relay` falls back to calling
193
+ * {@link publish} once per event.
194
+ *
195
+ * @param events - Events to publish (in order)
196
+ * @returns Per-event outcome map keyed by `event.meta.id`
197
+ */
198
+ publishMany?(events: readonly DomainEvent[]): Promise<PublishManyResult>;
199
+ /**
200
+ * Subscribe to events matching a pattern
201
+ * @param pattern - Event type pattern (e.g., 'product.*', '*')
202
+ * @param handler - Handler function
203
+ * @returns Unsubscribe function
204
+ */
205
+ subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
206
+ /**
207
+ * Route a permanently-failed event to the transport's dead-letter sink
208
+ * (Kafka DLQ topic, SQS DLQ, Redis Stream `PEL` timeout handler, etc.).
209
+ *
210
+ * Called by {@link import('./outbox.js').EventOutbox} after exhausting
211
+ * retries. Transports that don't have a native DLQ can omit this —
212
+ * callers treat an absent `deadLetter` as "log and drop".
213
+ */
214
+ deadLetter?(dlq: DeadLetteredEvent): Promise<void>;
215
+ /**
216
+ * Close transport connections
217
+ */
218
+ close?(): Promise<void>;
219
+ }
220
+ /**
221
+ * Per-event outcome returned by {@link EventTransport.publishMany}.
222
+ *
223
+ * The key is `event.meta.id`; the value is `null` for success or an `Error`
224
+ * for per-event failure. Transports MUST include an entry for every event
225
+ * in the input batch.
226
+ */
227
+ type PublishManyResult = ReadonlyMap<string, Error | null>;
228
+ interface MemoryEventTransportOptions {
229
+ /** Logger for error/warning messages (default: console) */
230
+ logger?: EventLogger;
231
+ }
232
+ /**
233
+ * In-memory event transport (default)
234
+ * Events are delivered synchronously within the process.
235
+ * Not suitable for multi-instance deployments.
236
+ */
237
+ declare class MemoryEventTransport implements EventTransport {
238
+ readonly name = "memory";
239
+ private handlers;
240
+ private logger;
241
+ constructor(options?: MemoryEventTransportOptions);
242
+ publish(event: DomainEvent): Promise<void>;
243
+ /**
244
+ * Reference `publishMany` implementation — delegates to `publish()` in order.
245
+ *
246
+ * Production transports (Kafka, Redis pipeline, SQS batch) should override
247
+ * this with a single batched network call. Memory transport has nothing to
248
+ * batch, so we just loop — the loop still returns a proper result map so
249
+ * `EventOutbox.relay` can exercise the batched code path in tests.
250
+ */
251
+ publishMany(events: readonly DomainEvent[]): Promise<PublishManyResult>;
252
+ subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
253
+ close(): Promise<void>;
254
+ }
255
+ /**
256
+ * Create a domain event with auto-generated metadata.
257
+ *
258
+ * `id` and `timestamp` are filled in; everything else is caller-controlled.
259
+ * Set `schemaVersion` explicitly for any event type you plan to evolve.
260
+ */
261
+ declare function createEvent<T>(type: string, payload: T, meta?: Partial<EventMeta>): DomainEvent<T>;
262
+ /**
263
+ * Create a child event that chains causation from a parent event.
264
+ *
265
+ * Rules:
266
+ * - `causationId` is set to the parent's `id` (direct cause)
267
+ * - `correlationId` is inherited from the parent if set, else falls back
268
+ * to the parent's `id` (root correlation)
269
+ * - `userId` / `organizationId` are inherited when not overridden so the
270
+ * whole chain stays scoped to the originating principal/tenant
271
+ *
272
+ * Caller-supplied `meta` wins over inherited fields — pass `{ userId: newActor }`
273
+ * to override when a subsystem acts on behalf of a different principal.
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const orderPlaced = createEvent('order.placed', { orderId: 'o1' }, {
278
+ * correlationId: req.id, userId: user.id,
279
+ * });
280
+ * await events.publish(orderPlaced);
281
+ *
282
+ * // Downstream handler emits a child event:
283
+ * const reserved = createChildEvent(orderPlaced, 'inventory.reserved', {
284
+ * orderId: 'o1', skus: ['sku-1', 'sku-2'],
285
+ * });
286
+ * // reserved.meta.causationId === orderPlaced.meta.id
287
+ * // reserved.meta.correlationId === orderPlaced.meta.correlationId
288
+ * // reserved.meta.userId === user.id (inherited)
289
+ * ```
290
+ */
291
+ declare function createChildEvent<T>(parent: DomainEvent, type: string, payload: T, meta?: Partial<EventMeta>): DomainEvent<T>;
292
+ //#endregion
293
+ export { EventMeta as a, MemoryEventTransportOptions as c, createEvent as d, EventLogger as i, PublishManyResult as l, DomainEvent as n, EventTransport as o, EventHandler as r, MemoryEventTransport as s, DeadLetteredEvent as t, createChildEvent as u };
@@ -1,4 +1,4 @@
1
- import { t as CRUD_OPERATIONS } from "./constants-Cxde4rpC.mjs";
1
+ import { t as CRUD_OPERATIONS } from "./constants-BhY1OHoH.mjs";
2
2
  //#region src/registry/ResourceRegistry.ts
3
3
  /**
4
4
  * Resource Registry
@@ -1,3 +1,3 @@
1
- import { a as RelationMetadata, c as ValidationResult, i as FieldMetadata, o as RepositoryLike, r as DataAdapter, s as SchemaMetadata, t as AdapterFactory } from "../interface-CMRutPfe.mjs";
2
- import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../index-C1meYuDn.mjs";
3
- export { AdapterFactory, DataAdapter, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createMongooseAdapter, createPrismaAdapter };
1
+ import { At as SchemaMetadata, Dt as FieldMetadata, Et as DataAdapter, Ot as RelationMetadata, jt as ValidationResult, kt as RepositoryLike, wt as AdapterFactory } from "../index-Cl0uoKd5.mjs";
2
+ import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, d as DrizzleAdapterOptions, f as createDrizzleAdapter, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter, u as DrizzleAdapter } from "../index-DStwgFUK.mjs";
3
+ export { AdapterFactory, DataAdapter, DrizzleAdapter, DrizzleAdapterOptions, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
@@ -1,2 +1,2 @@
1
- import { a as createMongooseAdapter, i as MongooseAdapter, n as PrismaQueryParser, r as createPrismaAdapter, t as PrismaAdapter } from "../adapters-BBqAVvPK.mjs";
2
- export { MongooseAdapter, PrismaAdapter, PrismaQueryParser, createMongooseAdapter, createPrismaAdapter };
1
+ import { a as createMongooseAdapter, i as MongooseAdapter, n as PrismaQueryParser, o as DrizzleAdapter, r as createPrismaAdapter, s as createDrizzleAdapter, t as PrismaAdapter } from "../adapters-BXY4i-hw.mjs";
2
+ export { DrizzleAdapter, MongooseAdapter, PrismaAdapter, PrismaQueryParser, createDrizzleAdapter, createMongooseAdapter, createPrismaAdapter };
@@ -1,4 +1,44 @@
1
- import { h as SYSTEM_FIELDS, m as RESERVED_QUERY_PARAMS } from "./constants-Cxde4rpC.mjs";
1
+ import { h as SYSTEM_FIELDS, m as RESERVED_QUERY_PARAMS } from "./constants-BhY1OHoH.mjs";
2
+ //#region src/adapters/field-rule-helpers.ts
3
+ /**
4
+ * Merge constraint-style `fieldRules` into an `OpenApiSchemas` bag in place.
5
+ *
6
+ * Operates on the three schema slots that carry property maps — `createBody`,
7
+ * `updateBody`, `response`. `listQuery` and `params` are skipped (their
8
+ * constraint vocabulary is owned by the kit's query parser).
9
+ *
10
+ * Existing constraints on a property always win — the merge only fills in
11
+ * gaps. Adapters that already walk `fieldRules` during base-schema assembly
12
+ * can call this helper for free (the checks are no-ops when constraints
13
+ * already exist).
14
+ */
15
+ function mergeFieldRuleConstraints(schemas, schemaOptions) {
16
+ if (!schemas || typeof schemas !== "object") return;
17
+ const rules = schemaOptions?.fieldRules;
18
+ if (!rules || Object.keys(rules).length === 0) return;
19
+ for (const slot of [
20
+ "createBody",
21
+ "updateBody",
22
+ "response"
23
+ ]) {
24
+ const slotSchema = schemas[slot];
25
+ if (!slotSchema || typeof slotSchema !== "object") continue;
26
+ const properties = slotSchema.properties;
27
+ if (!properties) continue;
28
+ for (const [field, rule] of Object.entries(rules)) {
29
+ const prop = properties[field];
30
+ if (!prop || typeof prop !== "object") continue;
31
+ if (rule.minLength != null && prop.minLength == null) prop.minLength = rule.minLength;
32
+ if (rule.maxLength != null && prop.maxLength == null) prop.maxLength = rule.maxLength;
33
+ if (rule.min != null && prop.minimum == null) prop.minimum = rule.min;
34
+ if (rule.max != null && prop.maximum == null) prop.maximum = rule.max;
35
+ if (rule.pattern != null && prop.pattern == null) prop.pattern = rule.pattern;
36
+ if (rule.enum != null && prop.enum == null) prop.enum = rule.enum;
37
+ if (rule.description != null && prop.description == null) prop.description = rule.description;
38
+ }
39
+ }
40
+ }
41
+ //#endregion
2
42
  //#region src/adapters/types.ts
3
43
  /**
4
44
  * Check if value is a Mongoose model
@@ -13,6 +53,168 @@ function isRepository(value) {
13
53
  return typeof value === "object" && value !== null && "getAll" in value && "getById" in value && "create" in value && "update" in value && "delete" in value;
14
54
  }
15
55
  //#endregion
56
+ //#region src/adapters/drizzle.ts
57
+ const DRIZZLE_COLUMNS_SYMBOL = Symbol.for("drizzle:Columns");
58
+ function getColumns(table) {
59
+ const cols = table[DRIZZLE_COLUMNS_SYMBOL];
60
+ if (!cols || typeof cols !== "object") return {};
61
+ return cols;
62
+ }
63
+ function columnToJsonSchema(column) {
64
+ const { dataType, columnType, enumValues, length } = column;
65
+ if (dataType === "date") return {
66
+ type: "string",
67
+ format: "date-time"
68
+ };
69
+ if (dataType === "boolean") return { type: "boolean" };
70
+ if (dataType === "json") return {
71
+ type: "object",
72
+ additionalProperties: true
73
+ };
74
+ if (dataType === "buffer") return {
75
+ type: "string",
76
+ contentEncoding: "base64"
77
+ };
78
+ if (dataType === "number" || dataType === "bigint") return { type: columnType === "SQLiteInteger" ? "integer" : "number" };
79
+ if (dataType === "string") {
80
+ const result = { type: "string" };
81
+ if (Array.isArray(enumValues) && enumValues.length > 0) result.enum = [...enumValues];
82
+ if (typeof length === "number" && length > 0) result.maxLength = length;
83
+ return result;
84
+ }
85
+ return {};
86
+ }
87
+ function columnToFieldMetadata(column) {
88
+ const { dataType, enumValues } = column;
89
+ const meta = {
90
+ type: (dataType && {
91
+ number: "number",
92
+ bigint: "number",
93
+ string: "string",
94
+ date: "date",
95
+ boolean: "boolean",
96
+ json: "object",
97
+ buffer: "object"
98
+ }[dataType]) ?? (enumValues?.length ? "enum" : "object"),
99
+ required: !!column.notNull && !column.hasDefault
100
+ };
101
+ if (enumValues?.length) meta.enum = [...enumValues];
102
+ if (typeof column.length === "number") meta.maxLength = column.length;
103
+ return meta;
104
+ }
105
+ var DrizzleAdapter = class {
106
+ type = "drizzle";
107
+ name;
108
+ table;
109
+ repository;
110
+ schemaGenerator;
111
+ constructor(options) {
112
+ if (!options.table || typeof options.table !== "object") throw new TypeError("DrizzleAdapter: Invalid table. Expected a Drizzle table created with sqliteTable / pgTable / mysqlTable.");
113
+ if (!isRepository(options.repository)) throw new TypeError("DrizzleAdapter: Invalid repository. Expected an object implementing MinimalRepo (getAll / getById / create / update / delete).");
114
+ this.table = options.table;
115
+ this.repository = options.repository;
116
+ this.schemaGenerator = options.schemaGenerator;
117
+ this.name = options.name ?? "DrizzleAdapter";
118
+ }
119
+ /**
120
+ * Introspect Drizzle columns into arc's schema metadata shape.
121
+ */
122
+ getSchemaMetadata() {
123
+ const columns = getColumns(this.table);
124
+ const fields = {};
125
+ const indexes = [];
126
+ for (const [name, column] of Object.entries(columns)) {
127
+ fields[name] = columnToFieldMetadata(column);
128
+ if (column.primary) indexes.push({
129
+ fields: [name],
130
+ unique: true
131
+ });
132
+ }
133
+ return {
134
+ name: this.name,
135
+ fields,
136
+ ...indexes.length > 0 ? { indexes } : {}
137
+ };
138
+ }
139
+ /**
140
+ * Generate OpenAPI schemas. Delegates to the user-provided
141
+ * `schemaGenerator` when available (strongly recommended — that's where
142
+ * field rules, omit lists, and param-type narrowing live). The built-in
143
+ * fallback emits a permissive entity + CRUD body shape so routes still
144
+ * register when no generator is provided.
145
+ *
146
+ * After the kit generator runs, arc merges constraint-style field rules
147
+ * (`minLength`, `maxLength`, `min`, `max`, `pattern`, `enum`, `description`)
148
+ * into the resulting property schemas so sqlitekit / pgkit behave
149
+ * identically to mongoose here — rule-driven AJV constraints apply
150
+ * regardless of backend.
151
+ */
152
+ generateSchemas(schemaOptions, context) {
153
+ try {
154
+ if (this.schemaGenerator) {
155
+ const generated = this.schemaGenerator(this.table, schemaOptions, context);
156
+ mergeFieldRuleConstraints(generated, schemaOptions);
157
+ return generated;
158
+ }
159
+ const columns = getColumns(this.table);
160
+ if (Object.keys(columns).length === 0) return null;
161
+ const entityProperties = {};
162
+ const inputProperties = {};
163
+ const inputRequired = [];
164
+ const updateProperties = {};
165
+ const fieldRules = schemaOptions?.fieldRules ?? {};
166
+ const readonlySet = new Set(schemaOptions?.readonlyFields ?? []);
167
+ const optionalSet = new Set(schemaOptions?.optionalFields ?? []);
168
+ const blocked = new Set([
169
+ ...Object.entries(fieldRules).filter(([, rules]) => rules.systemManaged || rules.hidden).map(([field]) => field),
170
+ ...schemaOptions?.excludeFields ?? [],
171
+ ...schemaOptions?.hiddenFields ?? []
172
+ ]);
173
+ for (const [fieldName, column] of Object.entries(columns)) {
174
+ const schema = columnToJsonSchema(column);
175
+ entityProperties[fieldName] = schema;
176
+ if (blocked.has(fieldName)) continue;
177
+ if (column.primary && column.columnType === "SQLiteInteger") continue;
178
+ if (!readonlySet.has(fieldName)) {
179
+ inputProperties[fieldName] = schema;
180
+ if (!!column.notNull && !column.hasDefault && !optionalSet.has(fieldName)) inputRequired.push(fieldName);
181
+ updateProperties[fieldName] = schema;
182
+ }
183
+ }
184
+ return {
185
+ createBody: {
186
+ type: "object",
187
+ properties: inputProperties,
188
+ required: inputRequired.length > 0 ? inputRequired : void 0,
189
+ additionalProperties: true
190
+ },
191
+ updateBody: {
192
+ type: "object",
193
+ properties: updateProperties,
194
+ additionalProperties: true
195
+ },
196
+ response: {
197
+ type: "object",
198
+ properties: entityProperties,
199
+ additionalProperties: true
200
+ }
201
+ };
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ async healthCheck() {
207
+ return typeof this.repository.getAll === "function";
208
+ }
209
+ };
210
+ /**
211
+ * Factory — preferred construction style for symmetry with
212
+ * `createMongooseAdapter` / `createPrismaAdapter`.
213
+ */
214
+ function createDrizzleAdapter(options) {
215
+ return new DrizzleAdapter(options);
216
+ }
217
+ //#endregion
16
218
  //#region src/adapters/mongoose.ts
17
219
  /**
18
220
  * Mongoose data adapter with proper type safety
@@ -27,7 +229,7 @@ var MongooseAdapter = class {
27
229
  schemaGenerator;
28
230
  constructor(options) {
29
231
  if (!isMongooseModel(options.model)) throw new TypeError("MongooseAdapter: Invalid model. Expected Mongoose Model instance.\nUsage: createMongooseAdapter({ model: YourModel, repository: yourRepo })");
30
- if (!isRepository(options.repository)) throw new TypeError("MongooseAdapter: Invalid repository. Expected CrudRepository instance.\nUsage: createMongooseAdapter({ model: YourModel, repository: yourRepo })");
232
+ if (!isRepository(options.repository)) throw new TypeError("MongooseAdapter: Invalid repository. Expected StandardRepo instance.\nUsage: createMongooseAdapter({ model: YourModel, repository: yourRepo })");
31
233
  this.model = options.model;
32
234
  this.repository = options.repository;
33
235
  this.schemaGenerator = options.schemaGenerator;
@@ -73,7 +275,11 @@ var MongooseAdapter = class {
73
275
  */
74
276
  generateSchemas(schemaOptions, context) {
75
277
  try {
76
- if (this.schemaGenerator) return this.schemaGenerator(this.model, schemaOptions, context);
278
+ if (this.schemaGenerator) {
279
+ const generated = this.schemaGenerator(this.model, schemaOptions, context);
280
+ mergeFieldRuleConstraints(generated, schemaOptions);
281
+ return generated;
282
+ }
77
283
  const paths = this.model.schema.paths;
78
284
  const properties = {};
79
285
  const required = [];
@@ -242,43 +448,6 @@ function createMongooseAdapter(modelOrOptions, repository) {
242
448
  //#endregion
243
449
  //#region src/adapters/prisma.ts
244
450
  /**
245
- * Prisma Adapter - PostgreSQL/MySQL/SQLite Implementation
246
- *
247
- * @experimental This adapter is implemented but has no integration tests yet.
248
- * Use in production at your own risk. The Mongoose adapter is the recommended
249
- * and battle-tested path.
250
- *
251
- * Bridges Prisma Client with Arc's DataAdapter interface.
252
- * Supports Prisma 5+ with all database providers.
253
- *
254
- * Implemented features:
255
- * - Schema generation (OpenAPI docs from DMMF)
256
- * - Health checks (database connectivity)
257
- * - Query parsing (URL params → Prisma where/orderBy)
258
- * - Policy filter translation
259
- * - Soft delete preset support
260
- *
261
- * Known gaps:
262
- * - No integration test coverage
263
- * - Multi-tenant isolation relies on caller-provided policyFilters (no auto-enforcement)
264
- *
265
- * @example
266
- * ```typescript
267
- * import { PrismaClient, Prisma } from '@prisma/client';
268
- * import { createPrismaAdapter, PrismaQueryParser } from '@classytic/arc/adapters';
269
- *
270
- * const prisma = new PrismaClient();
271
- *
272
- * const userAdapter = createPrismaAdapter({
273
- * client: prisma,
274
- * modelName: 'user',
275
- * repository: new UserRepository(prisma),
276
- * dmmf: Prisma.dmmf, // For schema generation
277
- * queryParser: new PrismaQueryParser(), // Optional: custom parser
278
- * });
279
- * ```
280
- */
281
- /**
282
451
  * Prisma Query Parser - Converts URL parameters to Prisma query format
283
452
  *
284
453
  * Translates Arc's query format to Prisma's where/orderBy/take/skip structure.
@@ -723,4 +892,4 @@ function createPrismaAdapter(options) {
723
892
  return new PrismaAdapter(options);
724
893
  }
725
894
  //#endregion
726
- export { createMongooseAdapter as a, MongooseAdapter as i, PrismaQueryParser as n, createPrismaAdapter as r, PrismaAdapter as t };
895
+ export { createMongooseAdapter as a, MongooseAdapter as i, PrismaQueryParser as n, DrizzleAdapter as o, createPrismaAdapter as r, createDrizzleAdapter as s, PrismaAdapter as t };