@classytic/arc 2.31.0 → 2.33.0
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 +1 -1
- package/bin/arc.js +58 -17
- package/dist/{BaseController-De7GqHck.mjs → BaseController-CaQC5EXX.mjs} +298 -35
- package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
- package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
- package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
- package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-DoALUlOO.mjs} +18 -15
- package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-CUeCmpUd.mjs} +1 -1
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +44 -11
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
- package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
- package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-DLE9xX-V.mjs} +6 -5
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BFHA6Scf.mjs → caching-BcGmvnHh.mjs} +2 -2
- package/dist/cleanup/index.d.mts +52 -18
- package/dist/cleanup/index.mjs +281 -32
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +4 -6
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D3Dv4LAV.mjs → constants-BxCoxmiN.mjs} +1 -1
- package/dist/context/index.mjs +3 -3
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -7
- package/dist/{core-BGpIxINN.mjs → core-dxhcHhUD.mjs} +3 -3
- package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DVpoA0do.mjs} +14 -11
- package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B_B48JZ-.mjs} +10 -8
- package/dist/{createApp-Df89BEni.mjs → createApp-CLGegqiG.mjs} +287 -62
- package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
- package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-DR9vsUYy.mjs} +318 -35
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-D2l1mh4g.mjs → elevation-B0tlY8y1.mjs} +4 -4
- package/dist/encryption/index.mjs +5 -4
- package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
- package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
- package/dist/{errors-Cj42AntO.mjs → errors-Cd_yy7aW.mjs} +1 -1
- package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
- package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dar8qRaj.mjs} +68 -11
- package/dist/events/index.d.mts +18 -297
- package/dist/events/index.mjs +7 -758
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +14 -10
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +20 -5
- package/dist/factory/index.mjs +131 -8
- package/dist/fastify-augmentation-DVsTAy6X.d.mts +179 -0
- package/dist/{fields-CB5Cuzk9.mjs → fields-CojeUkS4.mjs} +6 -11
- package/dist/{generate-EcA6ui4i.mjs → generate-DiB0nHPR.mjs} +2 -2
- package/dist/{headers-3zvE0rM3.mjs → headers-CveNO9gR.mjs} +2 -2
- package/dist/{health-DjZYjM7H.mjs → health-CasG2A28.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +45 -1
- package/dist/idempotency/index.mjs +95 -11
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-CEJNZFtX.d.mts → index-B-DvUzsr.d.mts} +82 -3
- package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
- package/dist/{index-Bc2ox8_U.d.mts → index-Brin0ODU.d.mts} +4 -4
- package/dist/{index-CFThn1K9.d.mts → index-C5NnMEKF.d.mts} +70 -4
- package/dist/{index-C-481Zf2.d.mts → index-ugr1tQlW.d.mts} +17 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +11 -11
- package/dist/{init-BEiNZBsJ.mjs → init-mdJ-v4Xp.mjs} +24 -16
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +100 -98
- package/dist/integrations/mcp/index.d.mts +12 -3
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +124 -1
- package/dist/integrations/webhooks.mjs +180 -2
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +38 -35
- package/dist/{interface-DYzM0q-u.mjs → interface-DznJrm9R.mjs} +1 -1
- package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
- package/dist/{keys-C0_pa1tY.mjs → keys-WeKvY1dE.mjs} +1 -1
- package/dist/{loadResources-yawi9tAE.mjs → loadResources-Bj-aSsXi.mjs} +4 -3
- package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-aJg5TqHi.mjs} +76 -36
- package/dist/{memory-DR6wpa9F.mjs → memory-qYSB7rMQ.mjs} +5 -5
- package/dist/{metrics-CUdw_wLB.mjs → metrics-D7uchLjN.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +3 -3
- package/dist/{module-0pFJA6YW.mjs → module-CwKsgZXp.mjs} +1 -1
- package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-4w-S3-PU.mjs} +3 -3
- package/dist/{openapi-B2L4js1v.mjs → openapi-BN5cQaO2.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +85 -0
- package/dist/outbox-admin/index.mjs +115 -0
- package/dist/outbox-module-CXB79bvt.mjs +839 -0
- package/dist/outbox-module-D-VFq37T.d.mts +368 -0
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +6 -6
- package/dist/{permissions-L--QDAir.mjs → permissions-BAfwgBxc.mjs} +120 -8
- package/dist/{pipe-DRDNsI2T.mjs → pipe-BzzoEhHj.mjs} +3 -5
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/{pipeline-UPQFatit.mjs → pipeline-q69DZzgG.mjs} +17 -15
- package/dist/plugins/index.d.mts +6 -6
- package/dist/plugins/index.mjs +16 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-O0FZgESl.mjs → pluralize-B7xf8qZF.mjs} +1 -1
- package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
- package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +6 -6
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-C4-g4_4P.mjs → presets-D-h0uWtP.mjs} +6 -5
- package/dist/projection-BKM0Ui-s.mjs +42 -0
- package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +50 -8
- package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
- package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
- package/dist/{realtime-C5NoxWFY.mjs → realtime-CTkv3O59.mjs} +10 -10
- package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
- package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-DNnd3vkz.mjs} +10 -8
- package/dist/registry/index.d.mts +3 -3
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-4213U4zb.mjs → registry-CLxCR5LF.mjs} +2 -2
- package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
- package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
- package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
- package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-uYE35JxI.mjs} +1 -1
- package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
- package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
- package/dist/{schedules-DdtlICTf.mjs → schedules-CAcZgia0.mjs} +3 -3
- package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
- package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CDvclAew.mjs} +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.mjs +5 -5
- package/dist/{scope-DHx5Fous.mjs → scope-BaWcqBvr.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
- package/dist/{sse-CUTo9y0X.mjs → sse-Cf0jUHsh.mjs} +7 -6
- package/dist/{sseStream-B_5XcVVd.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
- package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-B88p99Z2.mjs} +1 -1
- package/dist/{streaming-PSZwi8JV.mjs → streaming-DsT1B5zJ.mjs} +1 -1
- package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-CLIOHsOK.mjs} +2 -2
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.d.mts +1 -1
- package/dist/testing/cleanupStoreContract.mjs +12 -6
- package/dist/testing/index.d.mts +52 -13
- package/dist/testing/index.mjs +110 -40
- package/dist/testing/outboxStoreContract.d.mts +11 -2
- package/dist/testing/outboxStoreContract.mjs +110 -21
- package/dist/testing/storageContract.mjs +11 -6
- package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
- package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-jjKJDN13.mjs} +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CBgvkp8O.d.mts → types-BcTumAz_.d.mts} +128 -18
- package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
- package/dist/{types-Cys0ko9Z.mjs → types-CoqIRrkY.mjs} +4 -4
- package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
- package/dist/usage/index.d.mts +1 -1
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
- package/dist/utils/index.d.mts +57 -5
- package/dist/utils/index.mjs +12 -11
- package/dist/{utils-vL8BrDE2.mjs → utils-h1V_sT-t.mjs} +38 -7
- package/dist/{validate-_KbifpHF.mjs → validate-CdQlz52F.mjs} +2 -2
- package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
- package/dist/{versioning-CHDTor3P.mjs → versioning-rZbyLVD-.mjs} +4 -4
- package/package.json +31 -20
- package/skills/arc/SKILL.md +96 -4
- package/skills/arc/references/api-reference.md +19 -0
- package/skills/arc/references/production.md +2 -2
- package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
- package/dist/projection-BHw6xlOQ.mjs +0 -18
|
@@ -1,9 +1,35 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
3
|
-
import { m as statusToArcCode, p as isArcError } from "./errors-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
|
|
3
|
+
import { m as statusToArcCode, p as isArcError } from "./errors-Cd_yy7aW.mjs";
|
|
4
4
|
import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
|
|
5
|
+
import { isProductionEnv } from "@classytic/primitives/environment";
|
|
5
6
|
import fp from "fastify-plugin";
|
|
6
7
|
//#region src/plugins/errorHandler.ts
|
|
8
|
+
/**
|
|
9
|
+
* Global error handler — converts every thrown error into the canonical
|
|
10
|
+
* `ErrorContract` wire shape from `@classytic/repo-core/errors`.
|
|
11
|
+
*
|
|
12
|
+
* Wire shape (no envelope; HTTP status discriminates success vs error):
|
|
13
|
+
* ```
|
|
14
|
+
* { code, message, status, details?, meta?, correlationId? }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Throw anywhere — controller, hook, middleware — and the response is
|
|
18
|
+
* automatically converted. `ArcError` instances and any `HttpError`-shaped
|
|
19
|
+
* throw (mongokit, sqlitekit, streamline, prismakit) flow through the
|
|
20
|
+
* canonical converter unchanged.
|
|
21
|
+
*
|
|
22
|
+
* Custom domain error classes register via `errorMappers` (instanceof) or
|
|
23
|
+
* `errorMap` (by name). Driver-specific duplicate-key violations
|
|
24
|
+
* (Mongo 11000, Prisma P2002, Postgres 23505, MySQL 1062, SQLite
|
|
25
|
+
* UNIQUE) collapse to a 409 with `code: 'arc.conflict'`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* await fastify.register(errorHandlerPlugin, {
|
|
29
|
+
* includeStack: process.env.NODE_ENV !== 'production',
|
|
30
|
+
* onError: (err) => Sentry.captureException(err),
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
7
33
|
var errorHandler_exports = /* @__PURE__ */ __exportAll({
|
|
8
34
|
defaultIsDuplicateKeyError: () => defaultIsDuplicateKeyError,
|
|
9
35
|
errorHandlerPlugin: () => errorHandlerPlugin
|
|
@@ -94,8 +120,19 @@ function mongooseValidationDetails(errors) {
|
|
|
94
120
|
}));
|
|
95
121
|
}
|
|
96
122
|
async function errorHandlerPluginFn(fastify, options = {}) {
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
/**
|
|
124
|
+
* The FALLBACK for a plugin registered directly (`fastify.register(errorHandlerPlugin)`), which
|
|
125
|
+
* has no access to `createApp`'s resolved `preset`. Composed through `createApp`, both switches
|
|
126
|
+
* arrive already derived from the preset and this default never applies — see
|
|
127
|
+
* `factory/registerAuth.ts#registerErrorHandler`.
|
|
128
|
+
*
|
|
129
|
+
* Via the shared classifier, NOT `process.env.NODE_ENV === "production"`. That comparison
|
|
130
|
+
* recognises only the long spelling, so a deployment using `NODE_ENV=prod` got BOTH switches
|
|
131
|
+
* true: stack traces AND the raw thrown message of a 500 returned to clients, in production,
|
|
132
|
+
* with nothing to indicate it.
|
|
133
|
+
*/
|
|
134
|
+
const productionEnv = isProductionEnv(process.env.NODE_ENV);
|
|
135
|
+
const { includeStack = !productionEnv, exposeInternalMessages = !productionEnv, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
|
|
99
136
|
fastify.setErrorHandler(async (error, request, reply) => {
|
|
100
137
|
if (onError) try {
|
|
101
138
|
await onError(error, request);
|
|
@@ -17,11 +17,22 @@ interface ErrorMapper<T extends Error = Error> {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
interface ErrorHandlerOptions {
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Include `meta.stack` on the wire.
|
|
22
|
+
*
|
|
23
|
+
* Under `createApp`, defaults from the resolved `preset` (falling back to the
|
|
24
|
+
* environment via `isProductionEnv`, which knows both `production` and `prod`).
|
|
25
|
+
* Registering this plugin DIRECTLY skips that, and the default is the
|
|
26
|
+
* environment alone.
|
|
27
|
+
*/
|
|
21
28
|
includeStack?: boolean;
|
|
22
29
|
/**
|
|
23
|
-
* Ship the raw `error.message` of UNCLASSIFIED 500s on the wire
|
|
24
|
-
*
|
|
30
|
+
* Ship the raw `error.message` of UNCLASSIFIED 500s on the wire.
|
|
31
|
+
*
|
|
32
|
+
* Same derivation as {@link includeStack}: the `preset` under `createApp`,
|
|
33
|
+
* otherwise the environment. Both are host keys, so stating one no longer
|
|
34
|
+
* silently resets the other — `registerErrorHandler` MERGES them over the
|
|
35
|
+
* derived pair instead of replacing it.
|
|
25
36
|
*
|
|
26
37
|
* Only the `arc.internal_error` fallback is affected — an unclassified
|
|
27
38
|
* throw carries whatever some driver/library put in the message (connection
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-
|
|
2
|
+
import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-BEVP50ka.mjs";
|
|
3
3
|
import { FastifyPluginAsync } from "fastify";
|
|
4
4
|
//#region src/events/defineEvent.d.ts
|
|
5
5
|
interface EventSchema {
|
|
@@ -227,6 +227,34 @@ declare function createDeadLetterPublisher(events: {
|
|
|
227
227
|
interface EventPluginOptions {
|
|
228
228
|
/** Event transport (default: MemoryEventTransport) */
|
|
229
229
|
transport?: EventTransport;
|
|
230
|
+
/**
|
|
231
|
+
* Declare that this deployment is ONE process on purpose — the in-memory
|
|
232
|
+
* transport is its intended configuration, not a forgotten default.
|
|
233
|
+
*
|
|
234
|
+
* A single-node host (a small SaaS on one VPS, an on-prem install, a
|
|
235
|
+
* personal deployment that cannot justify Redis) is a legitimate production
|
|
236
|
+
* topology: with one process there are no other instances to broadcast to,
|
|
237
|
+
* so the memory transport is not a compromise — it is exactly sufficient.
|
|
238
|
+
* Without this flag arc cannot tell that apart from the accidental default
|
|
239
|
+
* (a multi-replica deployment that forgot to configure Redis), so it warns
|
|
240
|
+
* on every boot. Setting `singleProcess: true` states the topology and
|
|
241
|
+
* downgrades the warn to a factual info line.
|
|
242
|
+
*
|
|
243
|
+
* What it does NOT change: in-process delivery semantics. Events still
|
|
244
|
+
* vanish with the process — subscribers that were offline missed them. For
|
|
245
|
+
* durable at-least-once delivery on a single node, pair this with the
|
|
246
|
+
* repository-backed outbox (`createOutboxModule` + `repositoryAsOutboxStore`):
|
|
247
|
+
* events are committed to YOUR database and relayed to the in-process
|
|
248
|
+
* subscribers, so a crash between write and delivery replays on restart —
|
|
249
|
+
* no Redis anywhere in that path.
|
|
250
|
+
*
|
|
251
|
+
* Scale-out later by swapping the transport (Redis Streams etc.) and
|
|
252
|
+
* removing this flag; handler code is transport-independent and unchanged.
|
|
253
|
+
*
|
|
254
|
+
* Ignored (with a warn) when a non-memory transport is configured — the
|
|
255
|
+
* declaration would be describing a topology the transport contradicts.
|
|
256
|
+
*/
|
|
257
|
+
singleProcess?: boolean;
|
|
230
258
|
/** Enable event logging (default: false) */
|
|
231
259
|
logEvents?: boolean;
|
|
232
260
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
|
|
3
3
|
import { arcLog } from "./logger/index.mjs";
|
|
4
|
-
import { d as createDomainError } from "./errors-
|
|
5
|
-
import { n as requestContext } from "./requestContext-
|
|
6
|
-
import { n as createEvent, t as
|
|
4
|
+
import { d as createDomainError } from "./errors-Cd_yy7aW.mjs";
|
|
5
|
+
import { n as requestContext } from "./requestContext-uYE35JxI.mjs";
|
|
6
|
+
import { n as MemoryEventTransport, r as createEvent, t as ARC_EVENT_TRANSPORT } from "./EventTransport-tq4CEB-q.mjs";
|
|
7
|
+
import { isProductionEnv } from "@classytic/primitives/environment";
|
|
7
8
|
import fp from "fastify-plugin";
|
|
8
9
|
//#region src/events/retry.ts
|
|
9
10
|
/**
|
|
@@ -116,14 +117,39 @@ function sleep(ms) {
|
|
|
116
117
|
}
|
|
117
118
|
//#endregion
|
|
118
119
|
//#region src/events/eventPlugin.ts
|
|
120
|
+
/**
|
|
121
|
+
* Event Plugin
|
|
122
|
+
*
|
|
123
|
+
* Integrates event transport with Fastify.
|
|
124
|
+
* Defaults to in-memory transport; configure durable transport for production.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Development (in-memory)
|
|
128
|
+
* await fastify.register(eventPlugin);
|
|
129
|
+
*
|
|
130
|
+
* // Production (Redis)
|
|
131
|
+
* await fastify.register(eventPlugin, {
|
|
132
|
+
* transport: new RedisEventTransport({ url: process.env.REDIS_URL }),
|
|
133
|
+
* });
|
|
134
|
+
*/
|
|
119
135
|
var eventPlugin_exports = /* @__PURE__ */ __exportAll({
|
|
120
136
|
default: () => eventPlugin_default,
|
|
121
137
|
eventPlugin: () => eventPlugin
|
|
122
138
|
});
|
|
123
139
|
const eventPlugin = async (fastify, opts = {}) => {
|
|
124
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Lifecycle ownership: arc closes ONLY the transport it built.
|
|
142
|
+
*
|
|
143
|
+
* A host-supplied transport (a Redis client wrapper, typically) may be
|
|
144
|
+
* shared with other apps in the process or outlive this one — closing it
|
|
145
|
+
* on this app's shutdown severs a connection arc does not own. Read
|
|
146
|
+
* BEFORE the destructure, because the default below is indistinguishable
|
|
147
|
+
* from a host value afterwards.
|
|
148
|
+
*/
|
|
149
|
+
const ownsTransport = opts.transport === void 0;
|
|
150
|
+
const { transport = new MemoryEventTransport(), singleProcess = false, logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError, registry, validateMode: rawValidateMode, warnOnDuplicate: rawWarnOnDuplicate } = opts;
|
|
125
151
|
const validateMode = rawValidateMode ?? (registry ? "warn" : "off");
|
|
126
|
-
const warnOnDuplicate = rawWarnOnDuplicate ?? process.env.NODE_ENV
|
|
152
|
+
const warnOnDuplicate = rawWarnOnDuplicate ?? !isProductionEnv(process.env.NODE_ENV);
|
|
127
153
|
const DUP_WINDOW_MS = 5e3;
|
|
128
154
|
const recentPublishes = /* @__PURE__ */ new Map();
|
|
129
155
|
const evictExpiredPublishes = (now) => {
|
|
@@ -139,10 +165,11 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
139
165
|
if (type.startsWith("$") && type !== "$deadLetter") throw new Error(`[Arc Events] Event type '${type}' uses reserved '$' prefix`);
|
|
140
166
|
if (type.length > 256) throw new Error("[Arc Events] Event type exceeds 256 characters");
|
|
141
167
|
const store = requestContext.get();
|
|
142
|
-
const
|
|
168
|
+
const enrichedMeta = {
|
|
143
169
|
...store?.requestId && !meta?.correlationId ? { correlationId: store.requestId } : {},
|
|
144
170
|
...meta
|
|
145
|
-
}
|
|
171
|
+
};
|
|
172
|
+
const event = createEvent(type, payload, enrichedMeta);
|
|
146
173
|
if (warnOnDuplicate && event.meta.correlationId) {
|
|
147
174
|
const now = Date.now();
|
|
148
175
|
evictExpiredPublishes(now);
|
|
@@ -219,7 +246,32 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
219
246
|
transportName: transport.name,
|
|
220
247
|
registry
|
|
221
248
|
});
|
|
249
|
+
/**
|
|
250
|
+
* The RAW transport, for machinery that must observe publish failure.
|
|
251
|
+
*
|
|
252
|
+
* `fastify.events` is a REQUEST-FACING FACADE, and two of its properties make
|
|
253
|
+
* it wrong for a relay to publish through:
|
|
254
|
+
*
|
|
255
|
+
* 1. Different signature — `publish(type, payload, meta?)` vs the
|
|
256
|
+
* transport's `publish(event)`. Handing it a `DomainEvent` makes the
|
|
257
|
+
* envelope the `type` argument, which fails the non-empty-string guard.
|
|
258
|
+
* Measured on the default `createOutboxModule()` path: subscribers saw
|
|
259
|
+
* NOTHING and every relay tick reported `publishFailed: 1`, so events
|
|
260
|
+
* retried to dead-letter and never arrived.
|
|
261
|
+
* 2. Fail-open — the facade catches publish errors and only rethrows when
|
|
262
|
+
* `failOpen: false`. That is correct for HTTP (a transport outage must
|
|
263
|
+
* not fail a user's request) and fatal for an outbox: a swallowed error
|
|
264
|
+
* reads as success, the row is acknowledged, and the durability the
|
|
265
|
+
* outbox exists to provide is gone.
|
|
266
|
+
*
|
|
267
|
+
* So the boundary is explicit: **the facade may fail open; the relay must
|
|
268
|
+
* see the truth.** Decorated under a `Symbol.for` key — not part of the
|
|
269
|
+
* public `EventsDecorator` surface, and registry-keyed so it still resolves
|
|
270
|
+
* if a host's graph ends up with two arc copies.
|
|
271
|
+
*/
|
|
272
|
+
fastify.decorate(ARC_EVENT_TRANSPORT, transport);
|
|
222
273
|
fastify.addHook("onClose", async () => {
|
|
274
|
+
if (!ownsTransport) return;
|
|
223
275
|
try {
|
|
224
276
|
await transport.close?.();
|
|
225
277
|
} catch (error) {
|
|
@@ -230,8 +282,13 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
230
282
|
if (!failOpen) throw error;
|
|
231
283
|
}
|
|
232
284
|
});
|
|
233
|
-
if (transport.name === "memory")
|
|
234
|
-
|
|
285
|
+
if (transport.name === "memory") {
|
|
286
|
+
if (singleProcess) fastify.log?.info?.("[Arc Events] In-memory transport, declared single-process. Events are delivered in-process only and do not survive a crash or restart; subscribers in other processes (if any ever exist) will not receive them. For durability on this single node, pair with the repository-backed outbox (createOutboxModule + repositoryAsOutboxStore) — no Redis required. That covers a crash BEFORE publish; to also retry a THROWING handler, construct the transport with `onHandlerError: 'throw'` (by default a handler error is logged and the outbox row is still acknowledged).");
|
|
287
|
+
else fastify.log?.warn?.("[Arc Events] Using in-memory transport. Events will not persist or scale across instances. If this deployment is intentionally a single process, declare it — `eventPlugin, { singleProcess: true }` — and this becomes a supported configuration instead of a warning. Otherwise configure a durable transport (Redis Streams, RabbitMQ, etc.)");
|
|
288
|
+
} else {
|
|
289
|
+
if (singleProcess) fastify.log?.warn?.(`[Arc Events] \`singleProcess: true\` is declared but the '${transport.name}' transport is configured — a cross-instance transport contradicts a single-process declaration. The flag is ignored; remove it or switch back to the memory transport.`);
|
|
290
|
+
fastify.log?.debug?.(`[Arc Events] Using ${transport.name} transport`);
|
|
291
|
+
}
|
|
235
292
|
};
|
|
236
293
|
var eventPlugin_default = fp(eventPlugin, {
|
|
237
294
|
name: "arc-events",
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
3
|
-
import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-
|
|
2
|
+
import { i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions } from "../EventTransport-BEVP50ka.mjs";
|
|
3
|
+
import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-DSNEZWfV.mjs";
|
|
4
|
+
import { a as EventOutbox, c as OutboxRelayErrorKind, i as MemoryOutboxStore, l as RelayResult, n as OutboxModuleOptions, o as EventOutboxOptions, r as createOutboxModule, s as OutboxRelayErrorHandler, t as OutboxModuleExports } from "../outbox-module-D-VFq37T.mjs";
|
|
4
5
|
import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
|
|
5
|
-
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-
|
|
6
|
-
import {
|
|
6
|
+
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-mwLWlfHG.mjs";
|
|
7
|
+
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
7
8
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
8
9
|
//#region src/events/eventTypes.d.ts
|
|
9
10
|
/**
|
|
@@ -56,44 +57,6 @@ declare const CACHE_EVENTS: Readonly<{
|
|
|
56
57
|
/** Type for cache event names */
|
|
57
58
|
type CacheEvent = (typeof CACHE_EVENTS)[keyof typeof CACHE_EVENTS];
|
|
58
59
|
//#endregion
|
|
59
|
-
//#region src/events/memory-outbox.d.ts
|
|
60
|
-
interface MemoryEntry {
|
|
61
|
-
event: DomainEvent;
|
|
62
|
-
status: "pending" | "delivered" | "dead_letter";
|
|
63
|
-
attempts: number;
|
|
64
|
-
visibleAt: number;
|
|
65
|
-
leaseOwner: string | null;
|
|
66
|
-
leaseExpiresAt: number;
|
|
67
|
-
deliveredAt: number | null;
|
|
68
|
-
firstFailedAt: number | null;
|
|
69
|
-
lastFailedAt: number | null;
|
|
70
|
-
lastError: OutboxErrorInfo | null;
|
|
71
|
-
dedupeKey?: string;
|
|
72
|
-
}
|
|
73
|
-
declare class MemoryOutboxStore implements OutboxStore {
|
|
74
|
-
private readonly entries;
|
|
75
|
-
private readonly seenDedupeKeys;
|
|
76
|
-
/**
|
|
77
|
-
* Drop every entry (including delivered / failed / dead-lettered rows) and
|
|
78
|
-
* the dedupe-key memory.
|
|
79
|
-
*
|
|
80
|
-
* For tests only — it is the in-memory analogue of truncating the table, and
|
|
81
|
-
* exists so a suite can isolate cases against ONE store instance (the
|
|
82
|
-
* `reset` hook of `runOutboxStoreContract`). Not part of `OutboxStore`; a
|
|
83
|
-
* production store must never expose this.
|
|
84
|
-
*/
|
|
85
|
-
clear(): void;
|
|
86
|
-
save(event: DomainEvent, options?: OutboxWriteOptions): Promise<void>;
|
|
87
|
-
getPending(limit: number): Promise<DomainEvent[]>;
|
|
88
|
-
claimPending(options?: OutboxClaimOptions): Promise<DomainEvent[]>;
|
|
89
|
-
acknowledge(eventId: string, options?: OutboxAcknowledgeOptions): Promise<void>;
|
|
90
|
-
fail(eventId: string, error: OutboxErrorInfo, options?: OutboxFailOptions): Promise<void>;
|
|
91
|
-
getDeadLettered(limit: number): Promise<DeadLetteredEvent[]>;
|
|
92
|
-
purge(olderThanMs: number): Promise<number>;
|
|
93
|
-
/** Test helper: inspect entry by id */
|
|
94
|
-
_getEntry(eventId: string): Readonly<MemoryEntry> | undefined;
|
|
95
|
-
}
|
|
96
|
-
//#endregion
|
|
97
60
|
//#region src/events/outbox/backoff.d.ts
|
|
98
61
|
/**
|
|
99
62
|
* Exponential backoff for outbox failure policies — pure date math shared
|
|
@@ -152,259 +115,6 @@ interface ExponentialBackoffOptions {
|
|
|
152
115
|
*/
|
|
153
116
|
declare function exponentialBackoff(options: ExponentialBackoffOptions): Date;
|
|
154
117
|
//#endregion
|
|
155
|
-
//#region src/events/outbox/relay.d.ts
|
|
156
|
-
/** Reason codes passed to {@link EventOutboxOptions.onError}. */
|
|
157
|
-
type OutboxRelayErrorKind = "publish_failed" | "acknowledge_failed" | "fail_failed" | "ownership_mismatch" | "malformed_event";
|
|
158
|
-
/**
|
|
159
|
-
* Rich per-batch outcome returned by {@link EventOutbox.relayBatch}.
|
|
160
|
-
*
|
|
161
|
-
* Useful for operational dashboards, alerting thresholds, and test assertions.
|
|
162
|
-
* The simpler {@link EventOutbox.relay} returns just the `relayed` count for
|
|
163
|
-
* backward compatibility.
|
|
164
|
-
*/
|
|
165
|
-
interface RelayResult {
|
|
166
|
-
/** Number of events successfully published AND acknowledged */
|
|
167
|
-
readonly relayed: number;
|
|
168
|
-
/** Number of events claimed and attempted in this batch */
|
|
169
|
-
readonly attempted: number;
|
|
170
|
-
/** Number of publish failures (transport rejected the event) */
|
|
171
|
-
readonly publishFailed: number;
|
|
172
|
-
/** Number of acknowledge failures after successful publish (at-least-once replay risk) */
|
|
173
|
-
readonly ackFailed: number;
|
|
174
|
-
/** Number of ownership mismatches (our lease expired mid-flight) */
|
|
175
|
-
readonly ownershipMismatches: number;
|
|
176
|
-
/** Number of malformed events encountered (aborts the batch) */
|
|
177
|
-
readonly malformed: number;
|
|
178
|
-
/** Number of fail() calls that themselves threw (store bugs / contention) */
|
|
179
|
-
readonly failHookErrors: number;
|
|
180
|
-
/**
|
|
181
|
-
* Number of events moved to dead-letter state this batch via the configured
|
|
182
|
-
* {@link OutboxFailurePolicy}. Zero when no policy is set or no failure
|
|
183
|
-
* tripped the `deadLetter` branch.
|
|
184
|
-
*/
|
|
185
|
-
readonly deadLettered: number;
|
|
186
|
-
/** Whether `publishMany` was used (true) or per-event `publish` (false) */
|
|
187
|
-
readonly usedPublishMany: boolean;
|
|
188
|
-
/**
|
|
189
|
-
* Wall-clock ms spent claiming this batch from the store. With the
|
|
190
|
-
* portable repository adapter this is ~2 DB round trips; a persistently
|
|
191
|
-
* high value under low batch sizes points at store latency, not volume.
|
|
192
|
-
*/
|
|
193
|
-
readonly claimMs?: number;
|
|
194
|
-
/** Wall-clock ms spent publishing + acknowledging the batch. */
|
|
195
|
-
readonly publishMs?: number;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Called by {@link EventOutbox.relay} when a non-fatal error occurs during
|
|
199
|
-
* a batch. Used for logging and metrics. Must not throw.
|
|
200
|
-
*/
|
|
201
|
-
type OutboxRelayErrorHandler = (info: {
|
|
202
|
-
readonly kind: OutboxRelayErrorKind;
|
|
203
|
-
readonly event?: DomainEvent;
|
|
204
|
-
readonly error: Error;
|
|
205
|
-
}) => void;
|
|
206
|
-
interface EventOutboxOptions {
|
|
207
|
-
/**
|
|
208
|
-
* Repository managing the outbox collection. Arc consumes it directly —
|
|
209
|
-
* no wrapper classes. Requires `create`, `getOne`, `findAll`,
|
|
210
|
-
* `deleteMany`, and `findOneAndUpdate` from `RepositoryLike` (mongokit
|
|
211
|
-
* ≥3.8 satisfies all of these). Takes precedence over `store` when both
|
|
212
|
-
* are passed.
|
|
213
|
-
*
|
|
214
|
-
* Use this for the common path where the outbox lives in your primary
|
|
215
|
-
* database. Use `store` for non-repository backends (memory / custom).
|
|
216
|
-
*/
|
|
217
|
-
readonly repository?: RepositoryLike;
|
|
218
|
-
/**
|
|
219
|
-
* Non-repository outbox store. Use when your backend isn't a repository
|
|
220
|
-
* (memory for tests, Kafka, DynamoDB, custom). Ignored if `repository`
|
|
221
|
-
* is also passed.
|
|
222
|
-
*/
|
|
223
|
-
readonly store?: OutboxStore;
|
|
224
|
-
/** Transport to relay events to (optional — can relay later) */
|
|
225
|
-
readonly transport?: EventTransport;
|
|
226
|
-
/** Max events per relay batch (default: 100) */
|
|
227
|
-
readonly batchSize?: number;
|
|
228
|
-
/**
|
|
229
|
-
* Unique identifier for this relay worker. Used when the store supports
|
|
230
|
-
* `claimPending`/`fail` to enforce lease ownership. Defaults to a random ID.
|
|
231
|
-
*/
|
|
232
|
-
readonly consumerId?: string;
|
|
233
|
-
/**
|
|
234
|
-
* Lease duration in ms for claimed events. Only used when the store
|
|
235
|
-
* supports `claimPending`. Default: 30 seconds.
|
|
236
|
-
*/
|
|
237
|
-
readonly leaseMs?: number;
|
|
238
|
-
/**
|
|
239
|
-
* Callback for non-fatal errors during relay: publish failures,
|
|
240
|
-
* ownership mismatches, ack/fail errors, malformed events. Use this for
|
|
241
|
-
* logging and metrics. Must not throw — exceptions are swallowed.
|
|
242
|
-
*/
|
|
243
|
-
readonly onError?: OutboxRelayErrorHandler;
|
|
244
|
-
/**
|
|
245
|
-
* Enable {@link EventTransport.publishMany} when the transport implements it.
|
|
246
|
-
* Default: `true`. Set to `false` to force per-event `publish()` — useful
|
|
247
|
-
* for transports where strict event-order observability matters more than
|
|
248
|
-
* throughput, or to debug batch-specific issues.
|
|
249
|
-
*/
|
|
250
|
-
readonly usePublishMany?: boolean;
|
|
251
|
-
/**
|
|
252
|
-
* Retry/DLQ decision policy. When set, {@link EventOutbox.relayBatch}
|
|
253
|
-
* invokes this on every failure and uses the returned options for
|
|
254
|
-
* `store.fail()`. Centralises the "after N fails, dead-letter" rule so
|
|
255
|
-
* apps don't recompute `exponentialBackoff` + escalation thresholds on
|
|
256
|
-
* every failure site.
|
|
257
|
-
*
|
|
258
|
-
* Without a policy, `fail()` is called with `{}` (immediate re-visibility
|
|
259
|
-
* on next poll) — legacy behaviour, unchanged.
|
|
260
|
-
*/
|
|
261
|
-
readonly failurePolicy?: OutboxFailurePolicy;
|
|
262
|
-
/**
|
|
263
|
-
* Unit-of-Work session provider (AsyncLocalStorage integration).
|
|
264
|
-
*
|
|
265
|
-
* When set, `store()` calls this to get the active DB session and uses
|
|
266
|
-
* it as `OutboxWriteOptions.session` — **only when the caller has not
|
|
267
|
-
* already passed an explicit `options.session`**. This eliminates the
|
|
268
|
-
* boilerplate of threading the transaction handle from the controller
|
|
269
|
-
* through every service layer down to the outbox call site.
|
|
270
|
-
*
|
|
271
|
-
* Wire up with arc's `transactionContext`:
|
|
272
|
-
* ```typescript
|
|
273
|
-
* import { transactionContext } from '@classytic/arc/context';
|
|
274
|
-
*
|
|
275
|
-
* const outbox = new EventOutbox({
|
|
276
|
-
* store: myOutboxStore,
|
|
277
|
-
* sessionProvider: () => transactionContext.get(),
|
|
278
|
-
* });
|
|
279
|
-
*
|
|
280
|
-
* // In your service layer — session is auto-picked up:
|
|
281
|
-
* await transactionContext.run(mongooseSession, async () => {
|
|
282
|
-
* await orders.insertOne(order, { session: mongooseSession });
|
|
283
|
-
* await outbox.store(orderCreatedEvent); // no session arg needed
|
|
284
|
-
* });
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
readonly sessionProvider?: () => unknown;
|
|
288
|
-
/**
|
|
289
|
-
* W3C Trace Context provider.
|
|
290
|
-
*
|
|
291
|
-
* When set, `store()` calls this and merges the returned headers into
|
|
292
|
-
* `OutboxWriteOptions.headers` — only when `traceparent` is not already
|
|
293
|
-
* present in the caller-supplied headers. Downstream relay workers can
|
|
294
|
-
* then forward the headers to the transport, preserving the distributed
|
|
295
|
-
* trace tree across service boundaries.
|
|
296
|
-
*
|
|
297
|
-
* Wire up with arc's `getTraceHeaders`:
|
|
298
|
-
* ```typescript
|
|
299
|
-
* import { getTraceHeaders } from '@classytic/arc/context';
|
|
300
|
-
*
|
|
301
|
-
* const outbox = new EventOutbox({
|
|
302
|
-
* store: myOutboxStore,
|
|
303
|
-
* traceContextProvider: getTraceHeaders,
|
|
304
|
-
* });
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
readonly traceContextProvider?: () => Record<string, string> | undefined;
|
|
308
|
-
}
|
|
309
|
-
declare class EventOutbox {
|
|
310
|
-
private readonly _store;
|
|
311
|
-
private readonly _transport?;
|
|
312
|
-
private readonly _batchSize;
|
|
313
|
-
private readonly _consumerId;
|
|
314
|
-
private readonly _leaseMs;
|
|
315
|
-
private readonly _onError?;
|
|
316
|
-
private readonly _usePublishMany;
|
|
317
|
-
private readonly _sessionProvider?;
|
|
318
|
-
private readonly _traceContextProvider?;
|
|
319
|
-
private readonly _failurePolicy?;
|
|
320
|
-
/**
|
|
321
|
-
* In-process attempt counter per event id. Accurate within this relay
|
|
322
|
-
* process; resets on restart. Populated as failures occur and cleared on
|
|
323
|
-
* successful ack or dead-letter transition; additionally hard-capped at
|
|
324
|
-
* {@link MAX_TRACKED_ATTEMPTS} (oldest-write evicted first) so events
|
|
325
|
-
* resolved by another relay worker can't accumulate forever. For durable
|
|
326
|
-
* authoritative counts, apps can query the store directly inside
|
|
327
|
-
* {@link OutboxFailurePolicy}.
|
|
328
|
-
*/
|
|
329
|
-
private readonly _attempts;
|
|
330
|
-
constructor(opts: EventOutboxOptions);
|
|
331
|
-
/** Unique consumer ID used for lease ownership when the store supports claims */
|
|
332
|
-
get consumerId(): string;
|
|
333
|
-
/**
|
|
334
|
-
* Store event in outbox.
|
|
335
|
-
*
|
|
336
|
-
* Validates that `event.type` and `event.meta.id` are present — throws
|
|
337
|
-
* {@link InvalidOutboxEventError} otherwise, so corrupt rows can never
|
|
338
|
-
* be persisted via this API.
|
|
339
|
-
*
|
|
340
|
-
* Pass `options.session` to participate in a host-managed DB transaction
|
|
341
|
-
* (store must support session-aware writes). Other options (`visibleAt`,
|
|
342
|
-
* `dedupeKey`, `partitionKey`, `headers`) are forwarded to stores that
|
|
343
|
-
* implement them and ignored otherwise.
|
|
344
|
-
*/
|
|
345
|
-
store(event: DomainEvent, options?: OutboxWriteOptions): Promise<void>;
|
|
346
|
-
private _reportError;
|
|
347
|
-
/**
|
|
348
|
-
* Relay pending events to transport and return the number of successful
|
|
349
|
-
* publish+acknowledge pairs.
|
|
350
|
-
*
|
|
351
|
-
* For richer observability (per-kind counts, publishMany detection, etc.)
|
|
352
|
-
* use {@link relayBatch} which returns a {@link RelayResult}. This method
|
|
353
|
-
* is the backward-compatible shortcut that returns just the count.
|
|
354
|
-
*
|
|
355
|
-
* @returns Number of successfully published AND acknowledged events
|
|
356
|
-
*/
|
|
357
|
-
relay(): Promise<number>;
|
|
358
|
-
/**
|
|
359
|
-
* Relay a batch of pending events to the transport and return a rich
|
|
360
|
-
* {@link RelayResult} describing the outcome of each event.
|
|
361
|
-
*
|
|
362
|
-
* Behavior summary:
|
|
363
|
-
*
|
|
364
|
-
* - **Claim path**: uses {@link OutboxStore.claimPending} when the store
|
|
365
|
-
* supports it (safe for multi-worker relay) or falls back to
|
|
366
|
-
* {@link OutboxStore.getPending} (single-worker only).
|
|
367
|
-
*
|
|
368
|
-
* - **Publish path**: if the transport implements
|
|
369
|
-
* {@link EventTransport.publishMany} and `usePublishMany` is not disabled,
|
|
370
|
-
* the entire batch is sent in one call. Otherwise each event is published
|
|
371
|
-
* individually. Either way, per-event outcomes are tracked.
|
|
372
|
-
*
|
|
373
|
-
* - **Failure path**: if the store implements `fail`, per-event failures
|
|
374
|
-
* are reported via `store.fail(...)` and the batch continues. Without
|
|
375
|
-
* `fail`, the batch stops on the first failure (legacy behavior).
|
|
376
|
-
*
|
|
377
|
-
* - **Malformed events**: events missing `type` or `meta.id` abort the
|
|
378
|
-
* batch — a well-behaved store must never return them (see
|
|
379
|
-
* {@link OutboxStore} semantics #6). The error is reported via `onError`.
|
|
380
|
-
*
|
|
381
|
-
* - **Ownership mismatches**: if `acknowledge`/`fail` throws
|
|
382
|
-
* {@link OutboxOwnershipError} (our lease expired and another worker
|
|
383
|
-
* claimed the event), the event is NOT counted as relayed. The other
|
|
384
|
-
* worker will re-publish — at-least-once semantics preserved.
|
|
385
|
-
*
|
|
386
|
-
* @returns Per-kind outcome counts for the batch
|
|
387
|
-
*/
|
|
388
|
-
relayBatch(): Promise<RelayResult>;
|
|
389
|
-
/**
|
|
390
|
-
* Fetch current dead-lettered events as typed {@link DeadLetteredEvent}
|
|
391
|
-
* envelopes. Delegates to {@link OutboxStore.getDeadLettered} — returns
|
|
392
|
-
* `[]` when the store doesn't implement it.
|
|
393
|
-
*
|
|
394
|
-
* Pairs with {@link OutboxFailurePolicy}: apps set a policy that routes to
|
|
395
|
-
* `deadLetter: true` after N attempts, then read back with this to alert,
|
|
396
|
-
* replay, or archive.
|
|
397
|
-
*/
|
|
398
|
-
getDeadLettered(limit?: number): Promise<DeadLetteredEvent[]>;
|
|
399
|
-
/**
|
|
400
|
-
* Purge old **delivered** events from the outbox store.
|
|
401
|
-
* Delegates to `store.purge()` if implemented; no-op otherwise.
|
|
402
|
-
* @param olderThanMs - Remove events delivered more than this many ms ago (default: 7 days)
|
|
403
|
-
* @returns Number of purged events, or 0 if store doesn't support purge
|
|
404
|
-
*/
|
|
405
|
-
purge(olderThanMs?: number): Promise<number>;
|
|
406
|
-
}
|
|
407
|
-
//#endregion
|
|
408
118
|
//#region src/events/repository-outbox-adapter.d.ts
|
|
409
119
|
interface RepositoryOutboxStoreOptions {
|
|
410
120
|
/**
|
|
@@ -422,7 +132,18 @@ interface RepositoryOutboxStoreOptions {
|
|
|
422
132
|
*/
|
|
423
133
|
readonly visibleAtField?: string;
|
|
424
134
|
}
|
|
425
|
-
|
|
135
|
+
/**
|
|
136
|
+
* What this adapter GUARANTEES, as opposed to what `OutboxStore` permits.
|
|
137
|
+
*
|
|
138
|
+
* Most of the contract is optional so a minimal or non-relational store can implement
|
|
139
|
+
* the floor and no more. This adapter implements all of it, and saying so in the type
|
|
140
|
+
* is not cosmetic: with a plain `OutboxStore` return every caller of `getDeadLettered`,
|
|
141
|
+
* `requeue` or `countByStatus` has to write `store.requeue?.(id)`, and the `?.` silently
|
|
142
|
+
* evaluates to `undefined` if the method ever disappears — an operator's replay that
|
|
143
|
+
* quietly does nothing and reports success.
|
|
144
|
+
*/
|
|
145
|
+
type RepositoryOutboxStore = OutboxStore & Required<Pick<OutboxStore, "fail" | "getDeadLettered" | "purge" | "requeue" | "countByStatus" | "oldestPendingAgeMs">>;
|
|
146
|
+
declare function repositoryAsOutboxStore(repository: RepositoryLike, options?: RepositoryOutboxStoreOptions): RepositoryOutboxStore;
|
|
426
147
|
//#endregion
|
|
427
148
|
//#region src/events/subscribe-helpers.d.ts
|
|
428
149
|
/**
|
|
@@ -583,4 +304,4 @@ interface FastifyEventBus {
|
|
|
583
304
|
};
|
|
584
305
|
}
|
|
585
306
|
//#endregion
|
|
586
|
-
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type PayloadOf, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RepositoryOutboxStoreOptions, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
|
|
307
|
+
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxModuleExports, type OutboxModuleOptions, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type PayloadOf, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RepositoryOutboxStore, type RepositoryOutboxStoreOptions, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
|