@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.
- package/README.md +50 -38
- package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-CbKKIflT.mjs} +193 -143
- package/dist/EventTransport-CUw5NNWe.d.mts +293 -0
- package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.mjs +2 -2
- package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
- package/dist/audit/index.d.mts +135 -11
- package/dist/audit/index.mjs +107 -20
- package/dist/auth/index.d.mts +17 -9
- package/dist/auth/index.mjs +14 -7
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +1 -1
- package/dist/cache/index.d.mts +17 -15
- package/dist/cache/index.mjs +15 -14
- package/dist/{caching-IMuYVjTL.mjs → caching-CBpK_SCM.mjs} +8 -3
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -6
- package/dist/{defineResource-tcgySDo1.mjs → core-CcR01lup.mjs} +58 -61
- package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-Bp_5c_2b.mjs} +3 -3
- package/dist/{createApp-B1EY8zxa.mjs → createApp-BuvPma24.mjs} +15 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DtFxrG0s.mjs → elevation-C7hgL_aI.mjs} +22 -8
- package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-Bb49BvPD.mjs} +59 -7
- package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DRQ3EqfL.d.mts} +37 -2
- package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-CxWgpd6K.d.mts} +14 -2
- package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-DCUjuiQT.mjs} +83 -5
- package/dist/events/index.d.mts +150 -36
- package/dist/events/index.mjs +355 -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 +2 -2
- package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
- package/dist/{fields-ipsbIRPK.mjs → fields-bxkeltzz.mjs} +18 -5
- package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-t21LS-py.mjs} +65 -7
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +32 -5
- package/dist/idempotency/index.mjs +119 -12
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-DtDzOBn8.d.mts → index-8qw4y6ff.d.mts} +4 -135
- package/dist/{index-BLXBmWud.d.mts → index-ChIw3776.d.mts} +283 -408
- package/dist/{interface-CMRutPfe.d.mts → index-Cl0uoKd5.d.mts} +1758 -2506
- package/dist/{index-C1meYuDn.d.mts → index-DStwgFUK.d.mts} +81 -7
- package/dist/index.d.mts +7 -8
- package/dist/index.mjs +11 -12
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- 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-D218ikEo.d.mts +77 -0
- package/dist/{memory-Cp7_cAko.mjs → memory-B5Amv9A1.mjs} +23 -8
- package/dist/{openapi-CbKUJY_m.mjs → openapi-B5F8AddX.mjs} +3 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -4
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-CH4cNwJi.mjs → permissions-Dk6mshja.mjs} +315 -397
- package/dist/plugins/index.d.mts +7 -7
- package/dist/plugins/index.mjs +14 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +27 -5
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +3 -2
- package/dist/presets/index.mjs +4 -3
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +178 -0
- package/dist/presets/search.mjs +150 -0
- package/dist/{presets-C2xgzW6x.mjs → presets-fLJVXdVn.mjs} +1 -1
- package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
- package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DQCEfJis.mjs} +9 -9
- package/dist/{queryParser-CgCtsjti.mjs → queryParser-DBqBB6AC.mjs} +1 -1
- package/dist/{redis-BM00zaPB.d.mts → redis-DqyeggCa.d.mts} +1 -1
- package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-BElv3xPT.mjs} +65 -48
- package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-Ad7ypl9e.mjs → sse-yBCgOLGu.mjs} +1 -1
- package/dist/store-helpers-ZCSMJJAX.mjs +57 -0
- package/dist/testing/index.d.mts +9 -17
- package/dist/testing/index.mjs +27 -83
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -31
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-BsbNMEDR.d.mts → types-Btdda02s.d.mts} +1 -1
- package/dist/{types-Ch9pTQbf.d.mts → types-Co8k3NyS.d.mts} +11 -9
- package/dist/types-Csi3FLfq.mjs +27 -0
- package/dist/utils/index.d.mts +208 -4
- package/dist/utils/index.mjs +5 -6
- package/dist/{utils-yYT3HDXt.mjs → utils-B2fNOD_i.mjs} +285 -2
- package/dist/{versioning-CDugduqI.mjs → versioning-C2U_bLY0.mjs} +3 -5
- package/package.json +20 -26
- package/skills/arc/SKILL.md +97 -23
- package/skills/arc/references/auth.md +94 -0
- package/skills/arc/references/events.md +200 -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 -60
- package/dist/EventTransport-BXja8NOc.d.mts +0 -135
- package/dist/audit/mongodb.d.mts +0 -2
- package/dist/audit/mongodb.mjs +0 -2
- package/dist/circuitBreaker-cmi5XDv5.mjs +0 -284
- package/dist/circuitBreaker-dTtG-UyS.d.mts +0 -206
- package/dist/core-F0QoWBt2.mjs +0 -34
- package/dist/dynamic/index.d.mts +0 -93
- package/dist/dynamic/index.mjs +0 -122
- package/dist/fields-DpZQa_Q3.d.mts +0 -109
- package/dist/idempotency/mongodb.d.mts +0 -2
- package/dist/idempotency/mongodb.mjs +0 -123
- package/dist/interface-4y979v99.d.mts +0 -54
- package/dist/mongodb-BsP-WbhN.d.mts +0 -127
- package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
- package/dist/mongodb-Utc5k_-0.mjs +0 -90
- package/dist/policies/index.d.mts +0 -432
- package/dist/policies/index.mjs +0 -318
- package/dist/rpc/index.d.mts +0 -90
- package/dist/rpc/index.mjs +0 -248
- /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
- /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
- /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
- /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
- /package/dist/{errors-Ck2h67pm.d.mts → errors-CCSsMpXE.d.mts} +0 -0
- /package/dist/{errors-BF2bIOIS.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
- /package/dist/{interface-DfLGcus7.d.mts → interface-CSbZdv_3.d.mts} +0 -0
- /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-BAzJItAJ.mjs} +0 -0
- /package/dist/{logger-D1YrIImS.mjs → logger-DLg8-Ueg.mjs} +0 -0
- /package/dist/{metrics-B-PU4-Yu.mjs → metrics-DuhiSEZI.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{registry-BiTKT1Dg.mjs → registry-B3lRFBWo.mjs} +0 -0
- /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
- /package/dist/{requestContext-DYvHl113.mjs → requestContext-xHIKedG6.mjs} +0 -0
- /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
- /package/dist/{storage-Dfzt4VTl.d.mts → storage-CVk_SEn2.d.mts} +0 -0
- /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-65B51Dw3.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
- /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
|
@@ -116,22 +116,73 @@ const unsub = await fastify.events.subscribe('order.created', handler);
|
|
|
116
116
|
unsub();
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
## Event Structure
|
|
119
|
+
## Event Structure (v2.9)
|
|
120
120
|
|
|
121
121
|
```typescript
|
|
122
|
+
interface EventMeta {
|
|
123
|
+
id: string; // UUID v4 (fresh per emit; a retry gets a new id)
|
|
124
|
+
timestamp: Date;
|
|
125
|
+
schemaVersion?: number; // bump on payload breaking change
|
|
126
|
+
correlationId?: string; // stable across causal chain
|
|
127
|
+
causationId?: string; // direct parent event id
|
|
128
|
+
partitionKey?: string; // ordering hint (Kafka/Kinesis/Streams)
|
|
129
|
+
source?: string; // originating service/package ('commerce', 'billing')
|
|
130
|
+
idempotencyKey?: string; // cross-transport dedupe hint — stable per operation
|
|
131
|
+
resource?: string;
|
|
132
|
+
resourceId?: string;
|
|
133
|
+
userId?: string;
|
|
134
|
+
organizationId?: string;
|
|
135
|
+
aggregate?: { type: string; id: string }; // DDD aggregate marker
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
interface DomainEvent<T> {
|
|
123
|
-
type: string;
|
|
139
|
+
type: string; // e.g., 'order.created'
|
|
124
140
|
payload: T;
|
|
125
|
-
meta:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
meta: EventMeta;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Arc is source of truth** — `@classytic/primitives/events` mirrors these shapes. Downstream packages import from primitives; arc owns evolution.
|
|
146
|
+
|
|
147
|
+
### DDD aggregate narrowing
|
|
148
|
+
|
|
149
|
+
`aggregate.type` is `string` in arc's base contract so it stays framework-neutral. Domain packages narrow it to a closed union via interface extension:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// @classytic/cart
|
|
153
|
+
type CartAggregateType = 'cart' | 'cart-item';
|
|
154
|
+
|
|
155
|
+
interface CartEventMeta extends EventMeta {
|
|
156
|
+
aggregate?: { type: CartAggregateType; id: string };
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Unlike `correlationId` / `causationId`, `aggregate` is **not inherited** by `createChildEvent`. Child events usually belong to a different aggregate (e.g. an `order.placed` event emitted by the order aggregate spawns `inventory.reserved` owned by the inventory aggregate). Each event names its own aggregate explicitly.
|
|
161
|
+
|
|
162
|
+
### Causation chains
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { createEvent, createChildEvent } from '@classytic/arc/events';
|
|
166
|
+
|
|
167
|
+
const placed = createEvent('order.placed', { orderId: 'o1' }, {
|
|
168
|
+
correlationId: req.id, userId: user.id,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Downstream handler emits child — causation linked, correlation inherited:
|
|
172
|
+
const reserved = createChildEvent(placed, 'inventory.reserved', { sku: 'a' });
|
|
173
|
+
// reserved.meta.causationId === placed.meta.id
|
|
174
|
+
// reserved.meta.correlationId === placed.meta.correlationId
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Dead-letter contract
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import type { DeadLetteredEvent, EventTransport } from '@classytic/arc/events';
|
|
181
|
+
|
|
182
|
+
class KafkaTransport implements EventTransport {
|
|
183
|
+
async deadLetter(dlq: DeadLetteredEvent) {
|
|
184
|
+
await producer.send({ topic: `${dlq.event.type}.DLQ`, messages: [{ value: JSON.stringify(dlq) }] });
|
|
185
|
+
}
|
|
135
186
|
}
|
|
136
187
|
```
|
|
137
188
|
|
|
@@ -299,3 +350,140 @@ const retriedHandler = withRetry(async (event) => {
|
|
|
299
350
|
|
|
300
351
|
await fastify.events.subscribe('order.created', retriedHandler);
|
|
301
352
|
```
|
|
353
|
+
|
|
354
|
+
### Auto-route exhausted events to transport.deadLetter() (v2.9)
|
|
355
|
+
|
|
356
|
+
For transports with a native DLQ (Kafka DLQ topic, SQS DLQ queue, etc.), pass
|
|
357
|
+
`transport` to skip custom `$deadLetter` plumbing:
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
await fastify.events.subscribe(
|
|
361
|
+
'order.created',
|
|
362
|
+
withRetry(handler, {
|
|
363
|
+
maxRetries: 3,
|
|
364
|
+
transport: fastify.events.transport, // any EventTransport with deadLetter()
|
|
365
|
+
name: 'emailProcessor', // populates DeadLetteredEvent.handlerName
|
|
366
|
+
}),
|
|
367
|
+
);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
On exhaustion, a typed `DeadLetteredEvent` envelope (original event + error +
|
|
371
|
+
attempts + first/last failure timestamps) is handed to `transport.deadLetter()`.
|
|
372
|
+
`onDead` still works — both fire when both are configured.
|
|
373
|
+
|
|
374
|
+
## Transactional Outbox (v2.9)
|
|
375
|
+
|
|
376
|
+
**Why the outbox exists:** you write a row + publish an event in the same user
|
|
377
|
+
request. If the transport (Redis/Kafka) is down at that moment, the row
|
|
378
|
+
commits but the event vanishes — silent data divergence. The outbox persists
|
|
379
|
+
the event in the **same DB transaction** as the row, then a background relayer
|
|
380
|
+
guarantees at-least-once delivery to the transport. Multi-worker claim +
|
|
381
|
+
retry/DLQ policy + dedupe make it scale.
|
|
382
|
+
|
|
383
|
+
`EventOutbox` now offers centralised retry/DLQ and typed DLQ query:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
import { EventOutbox, MemoryOutboxStore, exponentialBackoff } from '@classytic/arc/events';
|
|
387
|
+
|
|
388
|
+
const outbox = new EventOutbox({
|
|
389
|
+
store: new MemoryOutboxStore(), // swap for durable store in prod
|
|
390
|
+
transport: fastify.events.transport,
|
|
391
|
+
|
|
392
|
+
// Centralised retry/DLQ — no more hand-rolled exponentialBackoff at every fail site
|
|
393
|
+
failurePolicy: ({ attempts, error }) => {
|
|
394
|
+
if (attempts >= 5) return { deadLetter: true };
|
|
395
|
+
return { retryAt: exponentialBackoff({ attempt: attempts }) };
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// meta.idempotencyKey auto-maps to OutboxWriteOptions.dedupeKey — duplicate
|
|
400
|
+
// saves with the same key are silently absorbed.
|
|
401
|
+
await outbox.store(
|
|
402
|
+
createEvent('order.placed', payload, { idempotencyKey: `order:${id}:placed` }),
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
// Rich per-batch outcome — deadLettered is new in v2.9
|
|
406
|
+
const result = await outbox.relayBatch();
|
|
407
|
+
// { relayed, attempted, publishFailed, ackFailed, ownershipMismatches,
|
|
408
|
+
// malformed, failHookErrors, deadLettered, usedPublishMany }
|
|
409
|
+
|
|
410
|
+
// Read DLQ state as typed DeadLetteredEvent[]
|
|
411
|
+
const dlq = await outbox.getDeadLettered(100);
|
|
412
|
+
for (const envelope of dlq) {
|
|
413
|
+
await alertOps(envelope); // event, error, attempts, firstFailedAt, lastFailedAt
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Store capability tiers:**
|
|
418
|
+
|
|
419
|
+
| Method | Required | What you lose without it |
|
|
420
|
+
|---|---|---|
|
|
421
|
+
| `save`, `getPending`, `acknowledge` | ✅ | — |
|
|
422
|
+
| `claimPending` | — | Multi-worker relay safety |
|
|
423
|
+
| `fail` | — | Retry / DLQ / per-event failure reporting |
|
|
424
|
+
| `getDeadLettered` | — | `outbox.getDeadLettered()` returns `[]` |
|
|
425
|
+
| `purge` | — | App owns retention (TTL index, cron DELETE, etc.) |
|
|
426
|
+
|
|
427
|
+
`MemoryOutboxStore` implements all capabilities — use it as a reference when
|
|
428
|
+
writing a durable store for Postgres / DynamoDB / your DB of choice.
|
|
429
|
+
|
|
430
|
+
### Durable store — pass a `RepositoryLike`
|
|
431
|
+
|
|
432
|
+
Arc adapts any `Repository` (mongokit / prismakit / your own kit) to the
|
|
433
|
+
`OutboxStore` contract — no dedicated subpath, no store class to
|
|
434
|
+
instantiate:
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
import mongoose from 'mongoose';
|
|
438
|
+
import { Repository } from '@classytic/mongokit';
|
|
439
|
+
import { EventOutbox, exponentialBackoff, createEvent } from '@classytic/arc/events';
|
|
440
|
+
|
|
441
|
+
const OutboxModel = mongoose.model('ArcOutbox', OutboxSchema, 'arc_outbox_events');
|
|
442
|
+
|
|
443
|
+
const outbox = new EventOutbox({
|
|
444
|
+
repository: new Repository(OutboxModel),
|
|
445
|
+
transport: redisTransport,
|
|
446
|
+
failurePolicy: ({ attempts }) =>
|
|
447
|
+
attempts >= 5 ? { deadLetter: true } : { retryAt: exponentialBackoff({ attempt: attempts }) },
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Persist event in the same DB transaction as the row
|
|
451
|
+
await mongoose.connection.transaction(async (session) => {
|
|
452
|
+
await Order.create([orderDoc], { session });
|
|
453
|
+
await outbox.store(
|
|
454
|
+
createEvent('order.placed', { orderId }, { idempotencyKey: `order:${orderId}:placed` }),
|
|
455
|
+
{ session },
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Background relayer
|
|
460
|
+
setInterval(async () => {
|
|
461
|
+
const r = await outbox.relayBatch();
|
|
462
|
+
metrics.gauge('outbox.deadLettered', r.deadLettered);
|
|
463
|
+
}, 1000);
|
|
464
|
+
|
|
465
|
+
// Ops: read dead-letter envelopes for alerting / replay
|
|
466
|
+
const dlq = await outbox.getDeadLettered(100);
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**What you get:**
|
|
470
|
+
|
|
471
|
+
- **Atomic multi-worker claim** — arc's adapter uses `findOneAndUpdate`
|
|
472
|
+
on `{ status: 'pending', visibleAt ≤ now, lease free }`. Two racing
|
|
473
|
+
relayers never see the same event; expired leases auto-recover.
|
|
474
|
+
- **Session threading** — `outbox.store(event, { session })` flows
|
|
475
|
+
through `Repository.create(doc, { session })` so the event commits
|
|
476
|
+
with your business write.
|
|
477
|
+
- **Dedupe** — `meta.idempotencyKey` maps to `dedupeKey`; your kit's
|
|
478
|
+
unique index (or equivalent) enforces idempotency.
|
|
479
|
+
- **DLQ** — `getDeadLettered(limit)` returns typed `DeadLetteredEvent[]`.
|
|
480
|
+
`RelayResult.deadLettered` counts per-batch transitions.
|
|
481
|
+
- **Purge** — `outbox.purge(olderThanMs)` deletes delivered rows; define
|
|
482
|
+
retention via a TTL index (`deliveredAt`), a cron, or a scheduler —
|
|
483
|
+
your kit's choice.
|
|
484
|
+
|
|
485
|
+
You own the schema and indexes. Recommended shape:
|
|
486
|
+
`{ eventId (unique), type, payload, meta, status, attempts, leaseOwner,
|
|
487
|
+
leaseExpiresAt, visibleAt, dedupeKey (unique sparse), lastError,
|
|
488
|
+
createdAt, deliveredAt }` with indexes on `{ status, visibleAt }` and
|
|
489
|
+
`{ deliveredAt }` (TTL).
|
|
@@ -537,7 +537,7 @@ Use to verify the MCP server is alive before configuring Claude CLI.
|
|
|
537
537
|
|
|
538
538
|
### ArcRequest — Typed Fastify Request
|
|
539
539
|
|
|
540
|
-
For `
|
|
540
|
+
For `raw: true` routes, use `ArcRequest` instead of `(req as any).user`:
|
|
541
541
|
|
|
542
542
|
```typescript
|
|
543
543
|
import type { ArcRequest } from '@classytic/arc';
|
|
@@ -589,28 +589,15 @@ throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { bal
|
|
|
589
589
|
// Arc's error handler auto-maps statusCode to HTTP response
|
|
590
590
|
```
|
|
591
591
|
|
|
592
|
-
### onRegister — Resource Lifecycle Hook
|
|
593
|
-
|
|
594
|
-
Called during plugin registration with the scoped Fastify instance:
|
|
595
|
-
|
|
596
|
-
```typescript
|
|
597
|
-
defineResource({
|
|
598
|
-
name: 'notification',
|
|
599
|
-
onRegister: (fastify) => {
|
|
600
|
-
setSseManager(fastify.sseManager);
|
|
601
|
-
},
|
|
602
|
-
})
|
|
603
|
-
```
|
|
604
|
-
|
|
605
592
|
### preAuth — Pre-Auth Handlers for SSE/WebSocket
|
|
606
593
|
|
|
607
594
|
Run before auth middleware. Use for promoting `?token=` to `Authorization` header (EventSource can't set headers):
|
|
608
595
|
|
|
609
596
|
```typescript
|
|
610
|
-
|
|
597
|
+
routes: [{
|
|
611
598
|
method: 'GET',
|
|
612
599
|
path: '/stream',
|
|
613
|
-
|
|
600
|
+
raw: true,
|
|
614
601
|
permissions: requireAuth(),
|
|
615
602
|
preAuth: [(req) => {
|
|
616
603
|
const token = req.query?.token;
|
|
@@ -625,7 +612,7 @@ additionalRoutes: [{
|
|
|
625
612
|
Auto-sets SSE headers and bypasses Arc's response wrapper:
|
|
626
613
|
|
|
627
614
|
```typescript
|
|
628
|
-
|
|
615
|
+
routes: [{
|
|
629
616
|
method: 'POST',
|
|
630
617
|
path: '/stream',
|
|
631
618
|
streamResponse: true, // SSE headers + no { success, data } wrapper
|
|
@@ -178,6 +178,49 @@ Arc does NOT auto-derive scope from `request.user.organizationId` — that's a f
|
|
|
178
178
|
2. **`elevated` with no `organizationId` bypasses tenant filtering.** This is intentional (admin sees everything), but it means you can't use `kind: 'elevated'` for normal per-org access.
|
|
179
179
|
3. **`systemManaged` is your seatbelt for create/update.** Mark tenant fields (`companyId`, `organizationId`) as `systemManaged` in `fieldRules` so `BodySanitizer` strips any client-supplied value. The tenant field is then injected from the scope at write time — not from the request body.
|
|
180
180
|
4. **Rate-limit keys respect all 5 scope kinds.** The built-in `createTenantKeyGenerator` uses `organizationId` for member/service/elevated and falls back to `userId`/IP for authenticated/public.
|
|
181
|
+
5. **multiTenant preset injects org on UPDATE (v2.9).** Body-supplied `organizationId` is overwritten with the caller's scope — closes the tenant-hop vector where a member could PATCH their own doc into another tenant. Elevated scope with no org still bypasses (admin cross-tenant).
|
|
182
|
+
|
|
183
|
+
## Mongokit tenant-context helper (optional)
|
|
184
|
+
|
|
185
|
+
If your adapter is mongokit ≥3.7, you can wire mongokit's `createTenantContext()` to propagate the org id through `AsyncLocalStorage` — useful when domain code outside arc routes needs the current tenant without threading `req` everywhere:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { createTenantContext, multiTenantPlugin, Repository } from '@classytic/mongokit';
|
|
189
|
+
import { getOrgId } from '@classytic/arc/scope';
|
|
190
|
+
|
|
191
|
+
const tenantContext = createTenantContext();
|
|
192
|
+
const repo = new Repository(Model, [
|
|
193
|
+
multiTenantPlugin({ tenantField: 'organizationId', context: tenantContext }),
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
// Install scope → ALS bridge once in your app:
|
|
197
|
+
fastify.addHook('preHandler', (req, _reply, done) => {
|
|
198
|
+
const scope = (req.metadata as { _scope?: unknown } | undefined)?._scope;
|
|
199
|
+
const orgId = scope ? getOrgId(scope as never) : undefined;
|
|
200
|
+
if (orgId) tenantContext.run(orgId, done);
|
|
201
|
+
else done();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Now any domain code can read it:
|
|
205
|
+
import { getTenantId } from './tenantContext.js';
|
|
206
|
+
await someService.doThing({ orgId: getTenantId() });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Arc doesn't bundle this — it's mongokit-specific. Arc's scope helpers (`getOrgId`, `getOrgContext`) remain the source of truth inside the request cycle; `createTenantContext()` is a complement for code that lives outside it.
|
|
210
|
+
|
|
211
|
+
## Plugin-order safety (mongokit ≥3.7)
|
|
212
|
+
|
|
213
|
+
Mongokit's `Repository` constructor accepts `pluginOrderChecks: 'warn' | 'throw' | 'off'` (default `'warn'`). Pass `'throw'` in production to catch foot-guns — e.g. installing `softDeletePlugin` AFTER `batchOperationsPlugin` silently bypasses soft-delete on `deleteMany`:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
new Repository(Model, [
|
|
217
|
+
multiTenantPlugin({...}), // must precede cache + batch-ops
|
|
218
|
+
softDeletePlugin(), // must precede batch-ops
|
|
219
|
+
batchOperationsPlugin(),
|
|
220
|
+
], {}, { pluginOrderChecks: 'throw' });
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Arc doesn't surface this option — configure it directly on the mongokit `Repository` you hand to `defineResource({ adapter: createMongooseAdapter({ repository }) })`.
|
|
181
224
|
|
|
182
225
|
## Helpers reference
|
|
183
226
|
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Health checks, audit trail, idempotency, tracing, SSE, caching, graceful shutdown.
|
|
4
4
|
|
|
5
|
+
## v2.9 Security Defaults
|
|
6
|
+
|
|
7
|
+
- **Field-write perms reject by default** — `defineResource({ fields, onFieldWriteDenied })`. Default `'reject'` → 403 with denied field list. Legacy silent-strip: `onFieldWriteDenied: 'strip'`.
|
|
8
|
+
- **Elevation always emits `arc.scope.elevated`** — subscribe via `fastify.events.subscribe('arc.scope.elevated', handler)` for audit. `onElevation` callback still supported.
|
|
9
|
+
- **multiTenant injects org on UPDATE** — closes cross-tenant hop vector. Body-supplied `organizationId` overwritten with caller's scope.
|
|
10
|
+
- **`verifySignature(body, secret, sig)` throws TypeError** if body isn't string/Buffer — register `@fastify/raw-body` before webhook routes; pass `req.rawBody`.
|
|
11
|
+
- **Upload `sanitizeFilename` (preset)** — strict by default; `false` / `'*'` / custom fn to relax per adapter needs.
|
|
12
|
+
- **Idempotency `namespace`** — fold an env key into the fingerprint when multiple deployments share a Redis (prod + canary, api + jobs).
|
|
13
|
+
|
|
5
14
|
## Health Plugin
|
|
6
15
|
|
|
7
16
|
Kubernetes-ready liveness/readiness probes:
|
|
@@ -56,22 +65,21 @@ await fastify.register(gracefulShutdownPlugin, {
|
|
|
56
65
|
|
|
57
66
|
## Audit Plugin
|
|
58
67
|
|
|
59
|
-
Change tracking
|
|
68
|
+
Change tracking. Pass a `RepositoryLike` for persistence, or omit for in-memory dev:
|
|
60
69
|
|
|
61
70
|
```typescript
|
|
62
71
|
import { auditPlugin } from '@classytic/arc/audit';
|
|
72
|
+
import { Repository } from '@classytic/mongokit';
|
|
63
73
|
|
|
64
|
-
// Development
|
|
65
|
-
await fastify.register(auditPlugin, { enabled: true
|
|
74
|
+
// Development (in-memory)
|
|
75
|
+
await fastify.register(auditPlugin, { enabled: true });
|
|
66
76
|
|
|
67
|
-
// Production
|
|
77
|
+
// Production — any kit (mongokit / prismakit / custom)
|
|
68
78
|
await fastify.register(auditPlugin, {
|
|
69
79
|
enabled: true,
|
|
70
|
-
|
|
71
|
-
mongoConnection: mongoose.connection,
|
|
72
|
-
mongoCollection: 'audit_logs',
|
|
73
|
-
ttlDays: 90, // Auto-cleanup via TTL index
|
|
80
|
+
repository: new Repository(AuditModel),
|
|
74
81
|
});
|
|
82
|
+
// TTL / retention owned by your DB (TTL index on `timestamp`, cron DELETE, etc.)
|
|
75
83
|
|
|
76
84
|
// Usage
|
|
77
85
|
await fastify.audit.create('product', product._id, product, request.auditContext);
|
|
@@ -137,16 +145,18 @@ fetch('/api/orders', {
|
|
|
137
145
|
**Storage backends:**
|
|
138
146
|
|
|
139
147
|
```typescript
|
|
140
|
-
// Memory (default, dev)
|
|
148
|
+
// Memory (default, dev) — omit both `repository` and `store`
|
|
141
149
|
import { MemoryIdempotencyStore } from '@classytic/arc/idempotency';
|
|
142
150
|
|
|
143
151
|
// Redis (production, multi-instance)
|
|
144
152
|
import { RedisIdempotencyStore } from '@classytic/arc/idempotency/redis';
|
|
145
153
|
store: new RedisIdempotencyStore({ client: redis, prefix: 'idem:', ttlMs: 86400000 })
|
|
146
154
|
|
|
147
|
-
//
|
|
148
|
-
import {
|
|
149
|
-
|
|
155
|
+
// DB-backed via RepositoryLike (mongokit / prismakit / custom)
|
|
156
|
+
import { Repository, methodRegistryPlugin, batchOperationsPlugin, mongoOperationsPlugin } from '@classytic/mongokit';
|
|
157
|
+
repository: new Repository(IdempotencyModel, [
|
|
158
|
+
methodRegistryPlugin(), batchOperationsPlugin(), mongoOperationsPlugin(),
|
|
159
|
+
])
|
|
150
160
|
```
|
|
151
161
|
|
|
152
162
|
**IdempotencyStore interface:**
|
|
@@ -356,28 +366,6 @@ const runner = new MigrationRunner(mongoose.connection.db);
|
|
|
356
366
|
await runner.up([v2]);
|
|
357
367
|
```
|
|
358
368
|
|
|
359
|
-
## Policies
|
|
360
|
-
|
|
361
|
-
Query-level authorization — modify queries based on user:
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
import { createAccessControlPolicy } from '@classytic/arc/policies';
|
|
365
|
-
|
|
366
|
-
const editorPolicy = createAccessControlPolicy({
|
|
367
|
-
statements: [
|
|
368
|
-
{ resource: 'product', action: ['create', 'update'] },
|
|
369
|
-
{ resource: 'order', action: ['read'] },
|
|
370
|
-
],
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
defineResource({
|
|
374
|
-
permissions: {
|
|
375
|
-
create: editorPolicy,
|
|
376
|
-
update: editorPolicy,
|
|
377
|
-
},
|
|
378
|
-
});
|
|
379
|
-
```
|
|
380
|
-
|
|
381
369
|
## OpenAPI & External Paths
|
|
382
370
|
|
|
383
371
|
Arc auto-generates OpenAPI 3.0 specs from resource definitions. External integrations (auth adapters, custom routes) inject their paths via `ExternalOpenApiPaths`.
|
|
@@ -532,39 +520,25 @@ Transactional outbox pattern — at-least-once delivery even if transport is dow
|
|
|
532
520
|
|
|
533
521
|
```typescript
|
|
534
522
|
import { EventOutbox, MemoryOutboxStore } from '@classytic/arc/events';
|
|
523
|
+
import { Repository } from '@classytic/mongokit';
|
|
524
|
+
|
|
525
|
+
// Dev
|
|
526
|
+
const outbox = new EventOutbox({ store: new MemoryOutboxStore(), transport: redisTransport });
|
|
535
527
|
|
|
528
|
+
// Production — any RepositoryLike
|
|
536
529
|
const outbox = new EventOutbox({
|
|
537
|
-
|
|
530
|
+
repository: new Repository(OutboxModel),
|
|
538
531
|
transport: redisTransport,
|
|
539
532
|
});
|
|
540
533
|
|
|
541
|
-
// In business logic (same DB transaction)
|
|
542
|
-
await outbox.store(event);
|
|
534
|
+
// In business logic (same DB transaction via `{ session }`)
|
|
535
|
+
await outbox.store(event, { session });
|
|
543
536
|
|
|
544
537
|
// Relay cron (runs every few seconds)
|
|
545
538
|
const relayed = await outbox.relay(); // publishes pending → transport
|
|
546
539
|
```
|
|
547
540
|
|
|
548
|
-
**OutboxStore interface**: `save(event)`, `getPending(limit)`, `acknowledge(eventId)
|
|
549
|
-
|
|
550
|
-
## RPC Service Client — Schema Versioning
|
|
551
|
-
|
|
552
|
-
The service client supports a `schemaVersion` option for contract compatibility between services:
|
|
553
|
-
|
|
554
|
-
```typescript
|
|
555
|
-
import { createServiceClient } from '@classytic/arc/rpc';
|
|
556
|
-
|
|
557
|
-
const catalog = createServiceClient({
|
|
558
|
-
baseUrl: 'http://catalog:3000',
|
|
559
|
-
schemaVersion: '1.2.0', // sent as x-arc-schema-version header
|
|
560
|
-
correlationId: () => request.id,
|
|
561
|
-
retry: { maxRetries: 2 },
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const products = await catalog.resource('product').list();
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
Receiving services can check `request.headers['x-arc-schema-version']` to detect version mismatches.
|
|
541
|
+
**OutboxStore interface**: `save(event)`, `getPending(limit)`, `acknowledge(eventId)` (+ optional `claimPending`, `fail`, `getDeadLettered`, `purge`). When you pass `repository`, arc adapts it internally.
|
|
568
542
|
|
|
569
543
|
## Bulk Operations Preset
|
|
570
544
|
|
|
@@ -655,16 +629,16 @@ await withCompensation('checkout', steps, { orderId }, {
|
|
|
655
629
|
});
|
|
656
630
|
```
|
|
657
631
|
|
|
658
|
-
**In
|
|
632
|
+
**In a custom route with Arc auth:**
|
|
659
633
|
|
|
660
634
|
```typescript
|
|
661
635
|
defineResource({
|
|
662
636
|
name: 'order',
|
|
663
|
-
|
|
637
|
+
routes: [{
|
|
664
638
|
method: 'POST',
|
|
665
639
|
path: '/:id/checkout',
|
|
666
640
|
permissions: requireAuth(),
|
|
667
|
-
|
|
641
|
+
raw: true,
|
|
668
642
|
handler: async (request, reply) => {
|
|
669
643
|
const result = await withCompensation('checkout', steps, { orderId: request.params.id });
|
|
670
644
|
if (!result.success) return reply.code(422).send({ error: result.error });
|
|
@@ -1,135 +0,0 @@
|
|
|
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
|
-
interface DomainEvent<T = unknown> {
|
|
21
|
-
/** Event type (e.g., 'product.created', 'order.shipped') */
|
|
22
|
-
type: string;
|
|
23
|
-
/** Event payload */
|
|
24
|
-
payload: T;
|
|
25
|
-
/** Event metadata */
|
|
26
|
-
meta: {
|
|
27
|
-
/** Unique event ID */id: string; /** Event timestamp */
|
|
28
|
-
timestamp: Date; /** Source resource */
|
|
29
|
-
resource?: string; /** Resource ID */
|
|
30
|
-
resourceId?: string; /** User who triggered the event */
|
|
31
|
-
userId?: string; /** Organization context */
|
|
32
|
-
organizationId?: string; /** Correlation ID for tracing */
|
|
33
|
-
correlationId?: string;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
type EventHandler<T = unknown> = (event: DomainEvent<T>) => void | Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Minimal logger interface for event transports.
|
|
39
|
-
* Compatible with `console`, `pino`, `fastify.log`, and any custom logger.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```typescript
|
|
43
|
-
* // Use Fastify's logger
|
|
44
|
-
* new MemoryEventTransport({ logger: fastify.log });
|
|
45
|
-
*
|
|
46
|
-
* // Use a custom logger
|
|
47
|
-
* new MemoryEventTransport({ logger: { warn: myWarn, error: myError } });
|
|
48
|
-
*
|
|
49
|
-
* // Default: console (no logger option needed)
|
|
50
|
-
* new MemoryEventTransport();
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
interface EventLogger {
|
|
54
|
-
warn(message: string, ...args: unknown[]): void;
|
|
55
|
-
error(message: string, ...args: unknown[]): void;
|
|
56
|
-
}
|
|
57
|
-
interface EventTransport {
|
|
58
|
-
/** Transport name for logging */
|
|
59
|
-
readonly name: string;
|
|
60
|
-
/**
|
|
61
|
-
* Publish an event to the transport
|
|
62
|
-
*/
|
|
63
|
-
publish(event: DomainEvent): Promise<void>;
|
|
64
|
-
/**
|
|
65
|
-
* Publish a batch of events to the transport (optional, v2.8.1+).
|
|
66
|
-
*
|
|
67
|
-
* Transports that can efficiently batch (Kafka producer, Redis pipeline,
|
|
68
|
-
* RabbitMQ publisher confirms, SQS send-message-batch) should implement
|
|
69
|
-
* this. {@link import('./outbox.js').EventOutbox.relay} auto-detects and
|
|
70
|
-
* uses it for much higher throughput than per-event publishing.
|
|
71
|
-
*
|
|
72
|
-
* **Contract**: the returned `PublishManyResult` must describe the
|
|
73
|
-
* per-event outcome so the caller can acknowledge successes and fail the
|
|
74
|
-
* rest. Partial success is allowed — the transport reports it per event.
|
|
75
|
-
*
|
|
76
|
-
* If not implemented, `EventOutbox.relay` falls back to calling
|
|
77
|
-
* {@link publish} once per event.
|
|
78
|
-
*
|
|
79
|
-
* @param events - Events to publish (in order)
|
|
80
|
-
* @returns Per-event outcome map keyed by `event.meta.id`
|
|
81
|
-
*/
|
|
82
|
-
publishMany?(events: readonly DomainEvent[]): Promise<PublishManyResult>;
|
|
83
|
-
/**
|
|
84
|
-
* Subscribe to events matching a pattern
|
|
85
|
-
* @param pattern - Event type pattern (e.g., 'product.*', '*')
|
|
86
|
-
* @param handler - Handler function
|
|
87
|
-
* @returns Unsubscribe function
|
|
88
|
-
*/
|
|
89
|
-
subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
|
|
90
|
-
/**
|
|
91
|
-
* Close transport connections
|
|
92
|
-
*/
|
|
93
|
-
close?(): Promise<void>;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Per-event outcome returned by {@link EventTransport.publishMany}.
|
|
97
|
-
*
|
|
98
|
-
* The key is `event.meta.id`; the value is `null` for success or an `Error`
|
|
99
|
-
* for per-event failure. Transports MUST include an entry for every event
|
|
100
|
-
* in the input batch.
|
|
101
|
-
*/
|
|
102
|
-
type PublishManyResult = ReadonlyMap<string, Error | null>;
|
|
103
|
-
interface MemoryEventTransportOptions {
|
|
104
|
-
/** Logger for error/warning messages (default: console) */
|
|
105
|
-
logger?: EventLogger;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* In-memory event transport (default)
|
|
109
|
-
* Events are delivered synchronously within the process.
|
|
110
|
-
* Not suitable for multi-instance deployments.
|
|
111
|
-
*/
|
|
112
|
-
declare class MemoryEventTransport implements EventTransport {
|
|
113
|
-
readonly name = "memory";
|
|
114
|
-
private handlers;
|
|
115
|
-
private logger;
|
|
116
|
-
constructor(options?: MemoryEventTransportOptions);
|
|
117
|
-
publish(event: DomainEvent): Promise<void>;
|
|
118
|
-
/**
|
|
119
|
-
* Reference `publishMany` implementation — delegates to `publish()` in order.
|
|
120
|
-
*
|
|
121
|
-
* Production transports (Kafka, Redis pipeline, SQS batch) should override
|
|
122
|
-
* this with a single batched network call. Memory transport has nothing to
|
|
123
|
-
* batch, so we just loop — the loop still returns a proper result map so
|
|
124
|
-
* `EventOutbox.relay` can exercise the batched code path in tests.
|
|
125
|
-
*/
|
|
126
|
-
publishMany(events: readonly DomainEvent[]): Promise<PublishManyResult>;
|
|
127
|
-
subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
|
|
128
|
-
close(): Promise<void>;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Create a domain event with auto-generated metadata
|
|
132
|
-
*/
|
|
133
|
-
declare function createEvent<T>(type: string, payload: T, meta?: Partial<DomainEvent["meta"]>): DomainEvent<T>;
|
|
134
|
-
//#endregion
|
|
135
|
-
export { MemoryEventTransport as a, createEvent as c, EventTransport as i, EventHandler as n, MemoryEventTransportOptions as o, EventLogger as r, PublishManyResult as s, DomainEvent as t };
|
package/dist/audit/mongodb.d.mts
DELETED
package/dist/audit/mongodb.mjs
DELETED