@classytic/arc 2.8.4 → 2.9.1

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 (130) hide show
  1. package/README.md +116 -5
  2. package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-Vu2yc56T.mjs} +188 -102
  3. package/dist/EventTransport-CqZ8FyM_.d.mts +293 -0
  4. package/dist/adapters/index.d.mts +2 -2
  5. package/dist/audit/index.d.mts +100 -11
  6. package/dist/audit/index.mjs +71 -18
  7. package/dist/auth/index.d.mts +15 -7
  8. package/dist/auth/index.mjs +13 -6
  9. package/dist/{betterAuthOpenApi-C5lDyRH2.mjs → betterAuthOpenApi--rdY15Ld.mjs} +1 -1
  10. package/dist/cache/index.d.mts +71 -1
  11. package/dist/cache/index.mjs +96 -3
  12. package/dist/cli/commands/docs.mjs +1 -1
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/core/index.d.mts +3 -3
  15. package/dist/core/index.mjs +4 -5
  16. package/dist/{core-DKSwNSXf.mjs → core-DNncu0xF.mjs} +1 -1
  17. package/dist/{createActionRouter-Df1BuawX.mjs → createActionRouter-DH1YFL9m.mjs} +3 -3
  18. package/dist/{createApp-BOYjBgdI.mjs → createApp-CBJUJKGP.mjs} +6 -5
  19. package/dist/{defineResource-Bb_Bdhtw.mjs → defineResource-C__jkwvs.mjs} +22 -57
  20. package/dist/docs/index.d.mts +1 -1
  21. package/dist/docs/index.mjs +1 -1
  22. package/dist/dynamic/index.d.mts +2 -2
  23. package/dist/dynamic/index.mjs +3 -3
  24. package/dist/{elevation-BBGFjzIP.mjs → elevation-DxQ6ACbt.mjs} +20 -6
  25. package/dist/{errorHandler-mzqk4cGl.mjs → errorHandler-CZDW4EXS.mjs} +59 -7
  26. package/dist/{errorHandler-CdZDavNH.d.mts → errorHandler-DixGcttC.d.mts} +37 -2
  27. package/dist/{eventPlugin-CVxlE6De.d.mts → eventPlugin-BxvaCIZF.d.mts} +14 -2
  28. package/dist/{eventPlugin-D91S2YF4.mjs → eventPlugin-Dl7MoVWH.mjs} +83 -5
  29. package/dist/events/index.d.mts +147 -36
  30. package/dist/events/index.mjs +338 -101
  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 +1 -1
  35. package/dist/{fields-DC4So2M2.d.mts → fields-BC7zcmI9.d.mts} +15 -3
  36. package/dist/{fields-ipsbIRPK.mjs → fields-CU6FlaDV.mjs} +18 -5
  37. package/dist/filesUpload-q8oHt--L.mjs +377 -0
  38. package/dist/hooks/index.d.mts +1 -1
  39. package/dist/idempotency/index.d.mts +28 -4
  40. package/dist/idempotency/index.mjs +111 -2
  41. package/dist/idempotency/redis.d.mts +2 -2
  42. package/dist/idempotency/redis.mjs +134 -13
  43. package/dist/{index-CSkeivBx.d.mts → index-C-xjcA6F.d.mts} +2 -2
  44. package/dist/{index-CpTSDqmD.d.mts → index-Cibkchnx.d.mts} +5 -136
  45. package/dist/{index-BgmMdpm8.d.mts → index-CtGKT0lf.d.mts} +1 -1
  46. package/dist/index.d.mts +8 -8
  47. package/dist/index.mjs +8 -8
  48. package/dist/integrations/event-gateway.d.mts +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 +26 -8
  53. package/dist/integrations/mcp/index.mjs +96 -17
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/integrations/webhooks.d.mts +5 -0
  57. package/dist/integrations/webhooks.mjs +6 -0
  58. package/dist/{interface-BVuMfeVv.d.mts → interface-YrWsmKqE.d.mts} +324 -194
  59. package/dist/{openapi-CYCuekCn.mjs → openapi-CXuTG1M9.mjs} +3 -3
  60. package/dist/org/index.d.mts +2 -2
  61. package/dist/permissions/index.d.mts +3 -3
  62. package/dist/permissions/index.mjs +3 -3
  63. package/dist/{permissions-CH4cNwJi.mjs → permissions-oNZawnkR.mjs} +1 -1
  64. package/dist/plugins/index.d.mts +6 -6
  65. package/dist/plugins/index.mjs +4 -4
  66. package/dist/plugins/response-cache.mjs +1 -1
  67. package/dist/plugins/tracing-entry.d.mts +1 -1
  68. package/dist/plugins/tracing-entry.mjs +1 -1
  69. package/dist/policies/index.d.mts +26 -33
  70. package/dist/presets/filesUpload.d.mts +71 -0
  71. package/dist/presets/filesUpload.mjs +2 -0
  72. package/dist/presets/index.d.mts +4 -2
  73. package/dist/presets/index.mjs +4 -2
  74. package/dist/presets/multiTenant.d.mts +1 -1
  75. package/dist/presets/multiTenant.mjs +1 -1
  76. package/dist/presets/search.d.mts +91 -0
  77. package/dist/presets/search.mjs +150 -0
  78. package/dist/{presets-C2xgzW6x.mjs → presets-hM4WhNWY.mjs} +1 -1
  79. package/dist/{queryCachePlugin-D0iIVhW_.mjs → queryCachePlugin-DbUVroUG.mjs} +2 -2
  80. package/dist/redis-MXLp1oOf.d.mts +115 -0
  81. package/dist/{redis-stream-D54N5oXs.d.mts → redis-stream-Bz-4q96t.d.mts} +1 -1
  82. package/dist/registry/index.d.mts +1 -1
  83. package/dist/{resourceToTools-O_HwWXFa.mjs → resourceToTools-C3cWymnW.mjs} +65 -48
  84. package/dist/rpc/index.mjs +1 -1
  85. package/dist/{schemaConverter-OxfCshus.mjs → schemaConverter-BxFDdtXu.mjs} +25 -9
  86. package/dist/scope/index.d.mts +2 -2
  87. package/dist/scope/index.mjs +1 -1
  88. package/dist/storage-BwGQXUpd.d.mts +146 -0
  89. package/dist/store-helpers-DFiZl5TL.mjs +57 -0
  90. package/dist/testing/index.d.mts +7 -15
  91. package/dist/testing/index.mjs +23 -76
  92. package/dist/testing/storageContract.d.mts +26 -0
  93. package/dist/testing/storageContract.mjs +216 -0
  94. package/dist/types/index.d.mts +5 -5
  95. package/dist/types/storage.d.mts +2 -0
  96. package/dist/types/storage.mjs +1 -0
  97. package/dist/{types-CcG4avic.d.mts → types-CoSzA-s-.d.mts} +1 -1
  98. package/dist/{types-Bg2X42_m.d.mts → types-CunEX4UX.d.mts} +7 -5
  99. package/dist/{types-CVC4HOKi.d.mts → types-DZi1aYhm.d.mts} +1 -1
  100. package/dist/utils/index.d.mts +26 -8
  101. package/dist/utils/index.mjs +6 -6
  102. package/dist/{utils-yYT3HDXt.mjs → utils-B7FuRr9w.mjs} +1 -1
  103. package/package.json +23 -11
  104. package/skills/arc/SKILL.md +92 -14
  105. package/skills/arc/references/auth.md +94 -0
  106. package/skills/arc/references/events.md +229 -12
  107. package/skills/arc/references/mcp.md +4 -17
  108. package/skills/arc/references/multi-tenancy.md +43 -0
  109. package/skills/arc/references/production.md +34 -19
  110. package/dist/EventTransport-CinyO7zQ.d.mts +0 -135
  111. package/dist/audit/mongodb.d.mts +0 -2
  112. package/dist/audit/mongodb.mjs +0 -2
  113. package/dist/idempotency/mongodb.d.mts +0 -2
  114. package/dist/idempotency/mongodb.mjs +0 -123
  115. package/dist/mongodb-B5O6xaW1.mjs +0 -90
  116. package/dist/mongodb-B8U2xaLj.d.mts +0 -127
  117. package/dist/mongodb-X7LbEjTN.d.mts +0 -80
  118. package/dist/redis-z3sFr1UP.d.mts +0 -49
  119. /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-bqGpo9ML.mjs} +0 -0
  120. /package/dist/{circuitBreaker-cmi5XDv5.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
  121. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-B6S5csVA.d.mts} +0 -0
  122. /package/dist/{errors-Bmn3eZT6.d.mts → errors-BI8kEKsO.d.mts} +0 -0
  123. /package/dist/{errors-BF2bIOIS.mjs → errors-CqWnSqM-.mjs} +0 -0
  124. /package/dist/{memory-Cp7_cAko.mjs → memory-BFAYkf8H.mjs} +0 -0
  125. /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
  126. /package/dist/{queryParser-CgCtsjti.mjs → queryParser-Cs-6SHQK.mjs} +0 -0
  127. /package/dist/{requestContext-DYvHl113.mjs → requestContext-DYtmNpm5.mjs} +0 -0
  128. /package/dist/{tracing-DxjKk7eW.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
  129. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
  130. /package/dist/{types-C72d3NDn.d.mts → types-BD85MlEK.d.mts} +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,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-BVuMfeVv.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-BgmMdpm8.mjs";
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-YrWsmKqE.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-CtGKT0lf.mjs";
3
3
  export { AdapterFactory, DataAdapter, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createMongooseAdapter, createPrismaAdapter };
@@ -1,19 +1,108 @@
1
- import { a as AuditContext, c as AuditStore, i as AuditAction, l as AuditStoreOptions, n as MongoAuditStoreOptions, o as AuditEntry, r as MongoConnection, s as AuditQueryOptions, u as createAuditEntry } from "../mongodb-B8U2xaLj.mjs";
1
+ import { o as RepositoryLike } from "../interface-YrWsmKqE.mjs";
2
+ import { i as UserBase } from "../types-DZi1aYhm.mjs";
2
3
  import { FastifyPluginAsync } from "fastify";
3
4
 
5
+ //#region src/audit/stores/interface.d.ts
6
+ type AuditAction = "create" | "update" | "delete" | "restore" | "custom";
7
+ interface AuditEntry {
8
+ /** Unique audit log ID */
9
+ id: string;
10
+ /** Resource name (e.g., 'product', 'user') */
11
+ resource: string;
12
+ /** Document/entity ID */
13
+ documentId: string;
14
+ /** Action performed */
15
+ action: AuditAction;
16
+ /** User who performed the action */
17
+ userId?: string;
18
+ /** Organization context */
19
+ organizationId?: string;
20
+ /** Previous state (for updates) */
21
+ before?: Record<string, unknown>;
22
+ /** New state (for creates/updates) */
23
+ after?: Record<string, unknown>;
24
+ /** Changed fields (for updates) */
25
+ changes?: string[];
26
+ /** Request ID for tracing */
27
+ requestId?: string;
28
+ /** IP address */
29
+ ipAddress?: string;
30
+ /** User agent */
31
+ userAgent?: string;
32
+ /** Custom metadata */
33
+ metadata?: Record<string, unknown>;
34
+ /** When the action occurred */
35
+ timestamp: Date;
36
+ }
37
+ interface AuditContext {
38
+ user?: UserBase;
39
+ organizationId?: string;
40
+ requestId?: string;
41
+ ipAddress?: string;
42
+ userAgent?: string;
43
+ /** HTTP method + route pattern (e.g., 'PATCH /api/products/:id') */
44
+ endpoint?: string;
45
+ /** Request duration in milliseconds */
46
+ duration?: number;
47
+ }
48
+ interface AuditStoreOptions {
49
+ /** Store name for logging */
50
+ name: string;
51
+ }
52
+ /**
53
+ * Abstract audit store interface
54
+ */
55
+ interface AuditStore {
56
+ /** Store name */
57
+ readonly name: string;
58
+ /** Log an audit entry */
59
+ log(entry: AuditEntry): Promise<void>;
60
+ /** Query audit logs (optional - not all stores support querying) */
61
+ query?(options: AuditQueryOptions): Promise<AuditEntry[]>;
62
+ /** Close/cleanup (optional) */
63
+ close?(): Promise<void>;
64
+ }
65
+ interface AuditQueryOptions {
66
+ resource?: string;
67
+ documentId?: string;
68
+ userId?: string;
69
+ organizationId?: string;
70
+ action?: AuditAction | AuditAction[];
71
+ from?: Date;
72
+ to?: Date;
73
+ limit?: number;
74
+ offset?: number;
75
+ }
76
+ /**
77
+ * Create audit entry from context
78
+ */
79
+ declare function createAuditEntry(resource: string, documentId: string, action: AuditAction, context: AuditContext, data?: {
80
+ before?: Record<string, unknown>;
81
+ after?: Record<string, unknown>;
82
+ metadata?: Record<string, unknown>;
83
+ }): AuditEntry;
84
+ //#endregion
4
85
  //#region src/audit/auditPlugin.d.ts
5
86
  interface AuditPluginOptions {
6
87
  /** Enable audit logging (default: false) */
7
88
  enabled?: boolean;
8
- /** Storage backends to use */
9
- stores?: ("memory" | "mongodb")[];
10
- /** MongoDB connection (required if using mongodb store) */
11
- mongoConnection?: MongoConnection;
12
- /** MongoDB collection name (default: 'audit_logs') */
13
- mongoCollection?: string;
14
- /** TTL in days for MongoDB (default: 90) */
15
- ttlDays?: number;
16
- /** Custom stores (advanced) */
89
+ /**
90
+ * Repository managing the audit collection. Arc consumes it **directly**
91
+ * no wrapping, no aliases, no proxy classes. Pass any object that
92
+ * implements arc's `RepositoryLike` (mongokit's `Repository`, prismakit's
93
+ * repo, a custom implementation). Arc calls `repository.create(entry)` to
94
+ * log and `repository.findAll(filter, options)` to query.
95
+ *
96
+ * If neither `repository` nor `customStores` is provided, falls back to
97
+ * `MemoryAuditStore` (intended for dev / tests only).
98
+ */
99
+ repository?: RepositoryLike;
100
+ /**
101
+ * Custom audit stores — for backends that aren't repositories (Kafka, S3,
102
+ * OpenTelemetry exporter, etc.). Each must implement the `AuditStore`
103
+ * interface. `repository` and `customStores` compose: entries get logged
104
+ * to every store.
105
+ */
17
106
  customStores?: AuditStore[];
18
107
  /**
19
108
  * Automatically audit CRUD operations via the hook system (default: true when enabled).
@@ -101,4 +190,4 @@ declare class MemoryAuditStore implements AuditStore {
101
190
  clear(): void;
102
191
  }
103
192
  //#endregion
104
- export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions, type MongoAuditStoreOptions, _default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
193
+ export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions, _default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
@@ -1,5 +1,69 @@
1
- import { t as MongoAuditStore } from "../mongodb-B5O6xaW1.mjs";
2
1
  import fp from "fastify-plugin";
2
+ //#region src/audit/repository-audit-adapter.ts
3
+ function repositoryAsAuditStore(repository) {
4
+ return {
5
+ name: "repository",
6
+ async log(entry) {
7
+ const doc = {
8
+ _id: entry.id,
9
+ id: entry.id,
10
+ resource: entry.resource,
11
+ documentId: entry.documentId,
12
+ action: entry.action,
13
+ userId: entry.userId,
14
+ organizationId: entry.organizationId,
15
+ before: entry.before,
16
+ after: entry.after,
17
+ changes: entry.changes,
18
+ requestId: entry.requestId,
19
+ ipAddress: entry.ipAddress,
20
+ userAgent: entry.userAgent,
21
+ metadata: entry.metadata,
22
+ timestamp: entry.timestamp
23
+ };
24
+ await repository.create(doc);
25
+ },
26
+ async query(opts = {}) {
27
+ if (!repository.findAll) throw new Error("auditPlugin: repository.findAll is required for query(). mongokit ≥3.6 implements it; other kits should match.");
28
+ const filter = {};
29
+ if (opts.resource) filter.resource = opts.resource;
30
+ if (opts.documentId) filter.documentId = opts.documentId;
31
+ if (opts.userId) filter.userId = opts.userId;
32
+ if (opts.organizationId) filter.organizationId = opts.organizationId;
33
+ if (opts.action) {
34
+ const actions = Array.isArray(opts.action) ? opts.action : [opts.action];
35
+ filter.action = actions.length === 1 ? actions[0] : { $in: actions };
36
+ }
37
+ if (opts.from || opts.to) {
38
+ const range = {};
39
+ if (opts.from) range.$gte = opts.from;
40
+ if (opts.to) range.$lte = opts.to;
41
+ filter.timestamp = range;
42
+ }
43
+ return (await repository.findAll(filter, {
44
+ sort: { timestamp: -1 },
45
+ skip: opts.offset ?? 0,
46
+ limit: opts.limit ?? 100
47
+ })).map((d) => ({
48
+ id: String(d._id ?? d.id ?? ""),
49
+ resource: d.resource ?? "",
50
+ documentId: d.documentId ?? "",
51
+ action: d.action ?? "create",
52
+ userId: d.userId,
53
+ organizationId: d.organizationId,
54
+ before: d.before,
55
+ after: d.after,
56
+ changes: d.changes,
57
+ requestId: d.requestId,
58
+ ipAddress: d.ipAddress,
59
+ userAgent: d.userAgent,
60
+ metadata: d.metadata,
61
+ timestamp: d.timestamp ?? /* @__PURE__ */ new Date()
62
+ }));
63
+ }
64
+ };
65
+ }
66
+ //#endregion
3
67
  //#region src/audit/stores/interface.ts
4
68
  /**
5
69
  * Create audit entry from context
@@ -96,28 +160,17 @@ var MemoryAuditStore = class {
96
160
  //#endregion
97
161
  //#region src/audit/auditPlugin.ts
98
162
  const auditPlugin = async (fastify, opts = {}) => {
99
- const { enabled = false, stores: storeTypes = ["memory"], mongoConnection, mongoCollection = "audit_logs", ttlDays = 90, customStores = [] } = opts;
163
+ const { enabled = false, repository, customStores = [] } = opts;
100
164
  if (!enabled) {
101
165
  fastify.decorate("audit", createNoopLogger());
102
166
  fastify.decorateRequest("auditContext", void 0);
103
167
  fastify.log?.debug?.("Audit plugin disabled");
104
168
  return;
105
169
  }
106
- const stores = [...customStores];
107
- for (const type of storeTypes) switch (type) {
108
- case "memory":
109
- stores.push(new MemoryAuditStore());
110
- break;
111
- case "mongodb":
112
- if (!mongoConnection) throw new Error("Audit: mongoConnection required for mongodb store");
113
- stores.push(new MongoAuditStore({
114
- connection: mongoConnection,
115
- collection: mongoCollection,
116
- ttlDays
117
- }));
118
- break;
119
- }
120
- if (stores.length === 0) throw new Error("Audit: at least one store must be configured");
170
+ const stores = [];
171
+ if (repository) stores.push(repositoryAsAuditStore(repository));
172
+ stores.push(...customStores);
173
+ if (stores.length === 0) stores.push(new MemoryAuditStore());
121
174
  async function logToStores(entry) {
122
175
  await Promise.all(stores.map((store) => store.log(entry)));
123
176
  }
@@ -232,7 +285,7 @@ const auditPlugin = async (fastify, opts = {}) => {
232
285
  }, "Auto-audit hooks registered");
233
286
  });
234
287
  }
235
- fastify.log?.debug?.({ stores: storeTypes }, "Audit plugin enabled");
288
+ fastify.log?.debug?.({ stores: stores.map((s) => s.name) }, "Audit plugin enabled");
236
289
  };
237
290
  /** Extract document ID from a result */
238
291
  function autoAuditExtractId(doc) {
@@ -1,8 +1,8 @@
1
- import { b as AuthPluginOptions, y as AuthHelpers } from "../interface-BVuMfeVv.mjs";
2
- import { t as PermissionCheck } from "../types-CVC4HOKi.mjs";
1
+ import { v as AuthHelpers, y as AuthPluginOptions } from "../interface-YrWsmKqE.mjs";
2
+ import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
3
3
  import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
4
4
  import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-D-oNWHz3.mjs";
5
- import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest as FastifyRequest$1 } from "fastify";
5
+ import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
6
6
 
7
7
  //#region src/auth/authPlugin.d.ts
8
8
  declare module "fastify" {
@@ -17,6 +17,14 @@ declare module "fastify" {
17
17
  auth: AuthHelpers;
18
18
  }
19
19
  }
20
+ /**
21
+ * Extract Bearer token from Authorization header.
22
+ *
23
+ * Exported for property-based test coverage — the contract is:
24
+ * - header must start with exactly `"Bearer "` (case-sensitive, one space)
25
+ * - everything after that prefix is returned verbatim (no trim, no parse)
26
+ * - missing header → `null`; any other shape → `null`
27
+ */
20
28
  declare const authPlugin: FastifyPluginAsync<AuthPluginOptions>;
21
29
  declare const _default: FastifyPluginAsync<AuthPluginOptions>;
22
30
  //#endregion
@@ -90,9 +98,9 @@ interface BetterAuthAdapterResult {
90
98
  /** Fastify plugin that registers catch-all auth routes */
91
99
  plugin: FastifyPluginAsync;
92
100
  /** Authenticate preHandler -- validates session via Better Auth */
93
- authenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
101
+ authenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
94
102
  /** Optional authenticate -- resolves session silently, continues as unauthenticated on failure */
95
- optionalAuthenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
103
+ optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
96
104
  /** Permission helpers bound to this auth adapter (available when orgContext is enabled) */
97
105
  permissions: {
98
106
  requireOrgRole: (...roles: string[]) => PermissionCheck;
@@ -109,7 +117,7 @@ declare module "fastify" {
109
117
  * Validates session by calling Better Auth's session endpoint internally.
110
118
  * Set by the Better Auth adapter plugin.
111
119
  */
112
- authenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
120
+ authenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
113
121
  /**
114
122
  * Optional authenticate middleware (Better Auth variant).
115
123
  * Tries to resolve session silently — populates request.user if valid,
@@ -117,7 +125,7 @@ declare module "fastify" {
117
125
  * Used on allowPublic() routes so downstream middleware can apply
118
126
  * org-scoped queries when a user IS authenticated.
119
127
  */
120
- optionalAuthenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
128
+ optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
121
129
  }
122
130
  }
123
131
  /**
@@ -1,7 +1,7 @@
1
1
  import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
- import { t as ArcError } from "../errors-BF2bIOIS.mjs";
3
- import { h as requireTeamMembership, l as requireOrgMembership, u as requireOrgRole } from "../permissions-CH4cNwJi.mjs";
4
- import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-C5lDyRH2.mjs";
2
+ import { t as ArcError } from "../errors-CqWnSqM-.mjs";
3
+ import { h as requireTeamMembership, l as requireOrgMembership, u as requireOrgRole } from "../permissions-oNZawnkR.mjs";
4
+ import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi--rdY15Ld.mjs";
5
5
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/auth/authPlugin.ts
@@ -21,7 +21,12 @@ function parseExpiresIn(input, defaultValue) {
21
21
  }[match[2]?.toLowerCase() ?? "s"] ?? 1);
22
22
  }
23
23
  /**
24
- * Extract Bearer token from Authorization header
24
+ * Extract Bearer token from Authorization header.
25
+ *
26
+ * Exported for property-based test coverage — the contract is:
27
+ * - header must start with exactly `"Bearer "` (case-sensitive, one space)
28
+ * - everything after that prefix is returned verbatim (no trim, no parse)
29
+ * - missing header → `null`; any other shape → `null`
25
30
  */
26
31
  function extractBearerToken(request) {
27
32
  const auth = request.headers.authorization;
@@ -29,7 +34,7 @@ function extractBearerToken(request) {
29
34
  return auth.slice(7);
30
35
  }
31
36
  const authPlugin = async (fastify, opts = {}) => {
32
- const { jwt: jwtConfig, authenticate: appAuthenticator, onFailure, userProperty = "user", exposeAuthErrors = false, tokenExtractor, isRevoked } = opts;
37
+ const { jwt: jwtConfig, authenticate: appAuthenticator, onFailure, userProperty = "user", exposeAuthErrors = false, tokenExtractor, isRevoked, strictTokenType = true } = opts;
33
38
  /** Extract token from request — uses custom extractor if provided, else Bearer header */
34
39
  const resolveToken = (request) => {
35
40
  if (tokenExtractor) return tokenExtractor(request);
@@ -84,6 +89,7 @@ const authPlugin = async (fastify, opts = {}) => {
84
89
  if (token) {
85
90
  const decoded = jwtContext.verify(token);
86
91
  if (decoded.type === "refresh") throw new Error("Refresh tokens cannot be used for authentication");
92
+ if (strictTokenType && decoded.type !== "access") throw new Error("Invalid token type: expected access token");
87
93
  user = decoded;
88
94
  }
89
95
  } else throw new Error("No authenticator configured. Provide auth.authenticate function or auth.jwt.secret.");
@@ -146,6 +152,7 @@ const authPlugin = async (fastify, opts = {}) => {
146
152
  if (token) {
147
153
  const decoded = jwtContext.verify(token);
148
154
  if (decoded.type === "refresh") return;
155
+ if (strictTokenType && decoded.type !== "access") return;
149
156
  user = decoded;
150
157
  }
151
158
  }
@@ -677,7 +684,7 @@ function createBetterAuthAdapter(options) {
677
684
  if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
678
685
  if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
679
686
  if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
680
- const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-C5lDyRH2.mjs").then((n) => n.t);
687
+ const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi--rdY15Ld.mjs").then((n) => n.t);
681
688
  extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
682
689
  basePath,
683
690
  userFields
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { a as toJsonSchema } from "./schemaConverter-OxfCshus.mjs";
2
+ import { a as toJsonSchema } from "./schemaConverter-BxFDdtXu.mjs";
3
3
  //#region src/auth/betterAuthOpenApi.ts
4
4
  var betterAuthOpenApi_exports = /* @__PURE__ */ __exportAll({ extractBetterAuthOpenApi: () => extractBetterAuthOpenApi });
5
5
  /**