@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.
- package/README.md +116 -5
- package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-Vu2yc56T.mjs} +188 -102
- package/dist/EventTransport-CqZ8FyM_.d.mts +293 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/audit/index.d.mts +100 -11
- package/dist/audit/index.mjs +71 -18
- package/dist/auth/index.d.mts +15 -7
- package/dist/auth/index.mjs +13 -6
- package/dist/{betterAuthOpenApi-C5lDyRH2.mjs → betterAuthOpenApi--rdY15Ld.mjs} +1 -1
- package/dist/cache/index.d.mts +71 -1
- package/dist/cache/index.mjs +96 -3
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -5
- package/dist/{core-DKSwNSXf.mjs → core-DNncu0xF.mjs} +1 -1
- package/dist/{createActionRouter-Df1BuawX.mjs → createActionRouter-DH1YFL9m.mjs} +3 -3
- package/dist/{createApp-BOYjBgdI.mjs → createApp-CBJUJKGP.mjs} +6 -5
- package/dist/{defineResource-Bb_Bdhtw.mjs → defineResource-C__jkwvs.mjs} +22 -57
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/dynamic/index.d.mts +2 -2
- package/dist/dynamic/index.mjs +3 -3
- package/dist/{elevation-BBGFjzIP.mjs → elevation-DxQ6ACbt.mjs} +20 -6
- package/dist/{errorHandler-mzqk4cGl.mjs → errorHandler-CZDW4EXS.mjs} +59 -7
- package/dist/{errorHandler-CdZDavNH.d.mts → errorHandler-DixGcttC.d.mts} +37 -2
- package/dist/{eventPlugin-CVxlE6De.d.mts → eventPlugin-BxvaCIZF.d.mts} +14 -2
- package/dist/{eventPlugin-D91S2YF4.mjs → eventPlugin-Dl7MoVWH.mjs} +83 -5
- package/dist/events/index.d.mts +147 -36
- package/dist/events/index.mjs +338 -101
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{fields-DC4So2M2.d.mts → fields-BC7zcmI9.d.mts} +15 -3
- package/dist/{fields-ipsbIRPK.mjs → fields-CU6FlaDV.mjs} +18 -5
- package/dist/filesUpload-q8oHt--L.mjs +377 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +28 -4
- package/dist/idempotency/index.mjs +111 -2
- package/dist/idempotency/redis.d.mts +2 -2
- package/dist/idempotency/redis.mjs +134 -13
- package/dist/{index-CSkeivBx.d.mts → index-C-xjcA6F.d.mts} +2 -2
- package/dist/{index-CpTSDqmD.d.mts → index-Cibkchnx.d.mts} +5 -136
- package/dist/{index-BgmMdpm8.d.mts → index-CtGKT0lf.d.mts} +1 -1
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +8 -8
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/jobs.d.mts +25 -3
- package/dist/integrations/jobs.mjs +63 -4
- package/dist/integrations/mcp/index.d.mts +26 -8
- package/dist/integrations/mcp/index.mjs +96 -17
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +5 -0
- package/dist/integrations/webhooks.mjs +6 -0
- package/dist/{interface-BVuMfeVv.d.mts → interface-YrWsmKqE.d.mts} +324 -194
- package/dist/{openapi-CYCuekCn.mjs → openapi-CXuTG1M9.mjs} +3 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-CH4cNwJi.mjs → permissions-oNZawnkR.mjs} +1 -1
- package/dist/plugins/index.d.mts +6 -6
- package/dist/plugins/index.mjs +4 -4
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/policies/index.d.mts +26 -33
- package/dist/presets/filesUpload.d.mts +71 -0
- package/dist/presets/filesUpload.mjs +2 -0
- package/dist/presets/index.d.mts +4 -2
- package/dist/presets/index.mjs +4 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +91 -0
- package/dist/presets/search.mjs +150 -0
- package/dist/{presets-C2xgzW6x.mjs → presets-hM4WhNWY.mjs} +1 -1
- package/dist/{queryCachePlugin-D0iIVhW_.mjs → queryCachePlugin-DbUVroUG.mjs} +2 -2
- package/dist/redis-MXLp1oOf.d.mts +115 -0
- package/dist/{redis-stream-D54N5oXs.d.mts → redis-stream-Bz-4q96t.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/{resourceToTools-O_HwWXFa.mjs → resourceToTools-C3cWymnW.mjs} +65 -48
- package/dist/rpc/index.mjs +1 -1
- package/dist/{schemaConverter-OxfCshus.mjs → schemaConverter-BxFDdtXu.mjs} +25 -9
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +1 -1
- package/dist/storage-BwGQXUpd.d.mts +146 -0
- package/dist/store-helpers-DFiZl5TL.mjs +57 -0
- package/dist/testing/index.d.mts +7 -15
- package/dist/testing/index.mjs +23 -76
- package/dist/testing/storageContract.d.mts +26 -0
- package/dist/testing/storageContract.mjs +216 -0
- package/dist/types/index.d.mts +5 -5
- package/dist/types/storage.d.mts +2 -0
- package/dist/types/storage.mjs +1 -0
- package/dist/{types-CcG4avic.d.mts → types-CoSzA-s-.d.mts} +1 -1
- package/dist/{types-Bg2X42_m.d.mts → types-CunEX4UX.d.mts} +7 -5
- package/dist/{types-CVC4HOKi.d.mts → types-DZi1aYhm.d.mts} +1 -1
- package/dist/utils/index.d.mts +26 -8
- package/dist/utils/index.mjs +6 -6
- package/dist/{utils-yYT3HDXt.mjs → utils-B7FuRr9w.mjs} +1 -1
- package/package.json +23 -11
- package/skills/arc/SKILL.md +92 -14
- package/skills/arc/references/auth.md +94 -0
- package/skills/arc/references/events.md +229 -12
- package/skills/arc/references/mcp.md +4 -17
- package/skills/arc/references/multi-tenancy.md +43 -0
- package/skills/arc/references/production.md +34 -19
- package/dist/EventTransport-CinyO7zQ.d.mts +0 -135
- package/dist/audit/mongodb.d.mts +0 -2
- package/dist/audit/mongodb.mjs +0 -2
- package/dist/idempotency/mongodb.d.mts +0 -2
- package/dist/idempotency/mongodb.mjs +0 -123
- package/dist/mongodb-B5O6xaW1.mjs +0 -90
- package/dist/mongodb-B8U2xaLj.d.mts +0 -127
- package/dist/mongodb-X7LbEjTN.d.mts +0 -80
- package/dist/redis-z3sFr1UP.d.mts +0 -49
- /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-bqGpo9ML.mjs} +0 -0
- /package/dist/{circuitBreaker-cmi5XDv5.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
- /package/dist/{elevation-s5ykdNHr.d.mts → elevation-B6S5csVA.d.mts} +0 -0
- /package/dist/{errors-Bmn3eZT6.d.mts → errors-BI8kEKsO.d.mts} +0 -0
- /package/dist/{errors-BF2bIOIS.mjs → errors-CqWnSqM-.mjs} +0 -0
- /package/dist/{memory-Cp7_cAko.mjs → memory-BFAYkf8H.mjs} +0 -0
- /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
- /package/dist/{queryParser-CgCtsjti.mjs → queryParser-Cs-6SHQK.mjs} +0 -0
- /package/dist/{requestContext-DYvHl113.mjs → requestContext-DYtmNpm5.mjs} +0 -0
- /package/dist/{tracing-DxjKk7eW.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
- /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-
|
|
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-
|
|
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 };
|
package/dist/audit/index.d.mts
CHANGED
|
@@ -1,19 +1,108 @@
|
|
|
1
|
-
import {
|
|
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
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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,
|
|
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 };
|
package/dist/audit/index.mjs
CHANGED
|
@@ -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,
|
|
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 = [
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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:
|
|
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) {
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as PermissionCheck } from "../types-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
128
|
+
optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply$1) => Promise<void>;
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
/**
|
package/dist/auth/index.mjs
CHANGED
|
@@ -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-
|
|
3
|
-
import { h as requireTeamMembership, l as requireOrgMembership, u as requireOrgRole } from "../permissions-
|
|
4
|
-
import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi
|
|
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
|
|
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-
|
|
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
|
/**
|