@classytic/arc 2.41.0 → 2.43.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/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
- package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
- package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
- package/dist/actionPermissions-BotX1YOa.mjs +776 -0
- package/dist/audit/index.d.mts +43 -5
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +43 -13
- package/dist/auth/index.mjs +130 -155
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-BULzfXYU.mjs +57 -0
- package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
- package/dist/cache/index.d.mts +22 -34
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
- package/dist/claimed-B10RTfkM.mjs +8 -0
- package/dist/cleanup/index.d.mts +6 -10
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +4 -2
- package/dist/cli/commands/describe.mjs +7 -4
- 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-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
- package/dist/context/index.d.mts +2 -5
- package/dist/context/index.mjs +18 -38
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +8 -8
- package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
- package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
- package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
- package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
- package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
- package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +10 -4
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
- package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
- package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
- package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
- package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
- package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
- package/dist/events/index.d.mts +33 -8
- package/dist/events/index.mjs +49 -53
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +139 -157
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +32 -17
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
- package/dist/filter-merge-IpGywvym.mjs +99 -0
- package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
- package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
- package/dist/health-D-Ft6JM6.mjs +22 -0
- package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +31 -7
- package/dist/idempotency/index.mjs +155 -98
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +48 -11
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
- package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/index-JdbZAIsm.d.mts +510 -0
- package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +11 -11
- package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +8 -3
- package/dist/integrations/event-gateway.mjs +4 -4
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +18 -5
- package/dist/integrations/mcp/index.d.mts +72 -59
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +78 -136
- package/dist/integrations/streamline.mjs +116 -54
- package/dist/integrations/webhooks.d.mts +60 -120
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/integrations/websocket.mjs +2 -2
- package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
- package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
- package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
- package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
- package/dist/logger/index.d.mts +1 -8
- package/dist/logger/index.mjs +3 -153
- package/dist/logger-DXjfIqjV.mjs +160 -0
- package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
- package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
- package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
- package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
- package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
- package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +3 -3
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
- package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +8 -6
- package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
- package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
- package/dist/pipeline/index.d.mts +2 -2
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-FVR6LP59.mjs +565 -0
- package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
- package/dist/plugins/index.d.mts +14 -11
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +26 -31
- package/dist/presets/index.mjs +4 -4
- package/dist/presets/multiTenant.d.mts +2 -107
- package/dist/presets/multiTenant.mjs +22 -162
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
- package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
- package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
- package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +9 -4
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
- package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
- package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
- package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/requirement-KDdoRcOP.mjs +35 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
- package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
- package/dist/routeValidator-D36SW8qt.mjs +70 -0
- package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
- package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
- package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
- package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
- package/dist/schemas/index.d.mts +10 -0
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
- package/dist/shutdown-MqA81z_P.mjs +59 -0
- package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
- package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
- package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
- package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
- package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
- package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
- package/dist/tenancy--ed9eH_z.mjs +43 -0
- package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +193 -175
- package/dist/testing/index.mjs +286 -212
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +17 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-qczZ0YC-.mjs +46 -0
- package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
- package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
- package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
- package/dist/usage/index.d.mts +2 -3
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
- package/dist/utils/index.d.mts +84 -223
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
- package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
- package/package.json +578 -574
- package/skills/arc/SKILL.md +13 -4
- package/skills/arc/references/auth.md +8 -5
- package/skills/arc/references/integrations.md +1 -1
- package/skills/arc/references/mcp.md +8 -6
- package/skills/arc/references/multi-tenancy.md +19 -14
- package/skills/arc/references/production.md +27 -10
- package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-BIpgzbZl.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-DRsSHPSM.mjs +0 -364
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
3
|
-
import { arcLog } from "./logger
|
|
4
|
-
import { d as createDomainError } from "./errors-
|
|
5
|
-
import { n as requestContext } from "./requestContext-
|
|
6
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
|
|
3
|
+
import { t as arcLog } from "./logger-DXjfIqjV.mjs";
|
|
4
|
+
import { d as createDomainError } from "./errors-CtrywMHP.mjs";
|
|
5
|
+
import { n as requestContext } from "./requestContext-Dsu9P1bV.mjs";
|
|
6
|
+
import { i as runAdmittedWork, n as isAdmittedWork, r as registerShutdownParticipant } from "./shutdown-MqA81z_P.mjs";
|
|
7
|
+
import { n as MemoryEventTransport, r as createEvent, t as ARC_EVENT_TRANSPORT } from "./EventTransport-tzA7CAB4.mjs";
|
|
8
|
+
import { n as preserveSubscriberIdentity } from "./subscriberIdentity-CW39uA8G.mjs";
|
|
7
9
|
import { isProductionEnv } from "@classytic/primitives/environment";
|
|
8
10
|
import fp from "fastify-plugin";
|
|
11
|
+
import { NonDurablePublishError } from "@classytic/primitives/durable-events";
|
|
9
12
|
//#region src/events/retry.ts
|
|
10
13
|
/**
|
|
11
14
|
* Wrap an event handler with retry logic and dead letter support.
|
|
@@ -21,7 +24,7 @@ import fp from "fastify-plugin";
|
|
|
21
24
|
function withRetry(handler, options = {}) {
|
|
22
25
|
const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, transport, deadLetter, onDead, dlqFailureMode = "rethrow", name, logger = console } = options;
|
|
23
26
|
const label = name ?? handler.name ?? "anonymous";
|
|
24
|
-
return async (event) => {
|
|
27
|
+
return preserveSubscriberIdentity(handler, async (event) => {
|
|
25
28
|
const errors = [];
|
|
26
29
|
let firstFailedAt;
|
|
27
30
|
let lastFailedAt;
|
|
@@ -76,8 +79,9 @@ function withRetry(handler, options = {}) {
|
|
|
76
79
|
} catch (dlqErr) {
|
|
77
80
|
logger.error("[Arc Events] onDead observability callback failed:", dlqErr);
|
|
78
81
|
}
|
|
82
|
+
if (!transport?.deadLetter && !deadLetter && dlqFailureMode === "rethrow") throw new Error(`[Arc Events] Handler '${label}' exhausted retries for ${event.type} and no dead-letter sink is wired — rethrowing so the delivery stays owed (the outbox relay retries and dead-letters it) instead of acknowledging a lost event. Set dlqFailureMode: 'log-and-drop' on a handler whose loss is acceptable.`, { cause: errors[errors.length - 1] });
|
|
79
83
|
if (dlqPersistError && dlqFailureMode === "rethrow") throw new Error(`[Arc Events] Handler '${label}' exhausted retries for ${event.type} AND dead-letter persistence failed — rethrowing so the transport redelivers instead of acknowledging a lost event (set dlqFailureMode: 'log-and-drop' to accept loss).`, { cause: dlqPersistError });
|
|
80
|
-
};
|
|
84
|
+
});
|
|
81
85
|
}
|
|
82
86
|
/**
|
|
83
87
|
* Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
|
|
@@ -134,8 +138,23 @@ function sleep(ms) {
|
|
|
134
138
|
*/
|
|
135
139
|
var eventPlugin_exports = /* @__PURE__ */ __exportAll({
|
|
136
140
|
default: () => eventPlugin_default,
|
|
141
|
+
emitObserved: () => emitObserved,
|
|
137
142
|
eventPlugin: () => eventPlugin
|
|
138
143
|
});
|
|
144
|
+
const OBSERVERS = Symbol.for("arc.events.observers");
|
|
145
|
+
/** Notify `fastify.events.observe` listeners. Internal: the plugin and the outbox relay call it. */
|
|
146
|
+
function emitObserved(events, event) {
|
|
147
|
+
const listeners = events?.[OBSERVERS];
|
|
148
|
+
if (!listeners) return;
|
|
149
|
+
for (const listener of listeners) try {
|
|
150
|
+
listener(event);
|
|
151
|
+
} catch {}
|
|
152
|
+
}
|
|
153
|
+
/** A consumer that can register subscriptions now and start consuming later (`RedisStreamTransport`). */
|
|
154
|
+
function isHoldable(transport) {
|
|
155
|
+
const t = transport;
|
|
156
|
+
return typeof t.hold === "function" && typeof t.start === "function";
|
|
157
|
+
}
|
|
139
158
|
const eventPlugin = async (fastify, opts = {}) => {
|
|
140
159
|
/**
|
|
141
160
|
* Lifecycle ownership: arc closes ONLY the transport it built.
|
|
@@ -148,6 +167,18 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
148
167
|
*/
|
|
149
168
|
const ownsTransport = opts.transport === void 0;
|
|
150
169
|
const { transport = new MemoryEventTransport(opts.memoryTransport), singleProcess = false, logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError, registry, validateMode: rawValidateMode, warnOnDuplicate: rawWarnOnDuplicate } = opts;
|
|
170
|
+
/** A host telemetry callback — isolated like an observer, so it can never change delivery. */
|
|
171
|
+
const notify = (hook, run, eventType) => {
|
|
172
|
+
try {
|
|
173
|
+
run();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
fastify.log?.warn?.({
|
|
176
|
+
hook,
|
|
177
|
+
eventType,
|
|
178
|
+
error
|
|
179
|
+
}, "[Arc Events] publish telemetry callback threw — ignored");
|
|
180
|
+
}
|
|
181
|
+
};
|
|
151
182
|
const validateMode = rawValidateMode ?? (registry ? "warn" : "off");
|
|
152
183
|
const warnOnDuplicate = rawWarnOnDuplicate ?? !isProductionEnv(process.env.NODE_ENV);
|
|
153
184
|
const DUP_WINDOW_MS = 5e3;
|
|
@@ -159,7 +190,27 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
159
190
|
recentPublishes.delete(key);
|
|
160
191
|
}
|
|
161
192
|
};
|
|
193
|
+
const observers = /* @__PURE__ */ new Set();
|
|
194
|
+
const activeHandlers = /* @__PURE__ */ new Set();
|
|
195
|
+
let stopping = false;
|
|
196
|
+
registerShutdownParticipant(fastify, {
|
|
197
|
+
name: "events.handlers",
|
|
198
|
+
kind: "consumer",
|
|
199
|
+
stop: () => {
|
|
200
|
+
stopping = true;
|
|
201
|
+
},
|
|
202
|
+
drain: async () => {
|
|
203
|
+
while (activeHandlers.size) await Promise.allSettled([...activeHandlers]);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
162
206
|
fastify.decorate("events", {
|
|
207
|
+
[OBSERVERS]: observers,
|
|
208
|
+
observe: (listener) => {
|
|
209
|
+
observers.add(listener);
|
|
210
|
+
return () => {
|
|
211
|
+
observers.delete(listener);
|
|
212
|
+
};
|
|
213
|
+
},
|
|
163
214
|
publish: async (type, payload, meta) => {
|
|
164
215
|
if (!type || typeof type !== "string") throw new Error("[Arc Events] Event type must be a non-empty string");
|
|
165
216
|
if (type.startsWith("$") && type !== "$deadLetter") throw new Error(`[Arc Events] Event type '${type}' uses reserved '$' prefix`);
|
|
@@ -170,6 +221,7 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
170
221
|
...meta
|
|
171
222
|
};
|
|
172
223
|
const event = createEvent(type, payload, enrichedMeta);
|
|
224
|
+
if (registry?.get(type, event.meta.schemaVersion)?.durability === "durable") throw new NonDurablePublishError(type, "fastify.events writes no outbox row — save it in the unit of work that made it true (dispatchDomainEvent, or outbox.save in the session)");
|
|
173
225
|
if (warnOnDuplicate && event.meta.correlationId) {
|
|
174
226
|
const now = Date.now();
|
|
175
227
|
evictExpiredPublishes(now);
|
|
@@ -200,16 +252,18 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
200
252
|
if (wal && !isInternalEvent) await wal.save(event);
|
|
201
253
|
await transport.publish(event);
|
|
202
254
|
if (wal?.acknowledge && !isInternalEvent) await wal.acknowledge(event.meta.id);
|
|
203
|
-
onPublish?.(event);
|
|
204
255
|
} catch (error) {
|
|
205
256
|
fastify.log?.error?.({
|
|
206
257
|
transport: transport.name,
|
|
207
258
|
eventType: type,
|
|
208
259
|
error
|
|
209
260
|
}, "[Arc Events] Failed to publish event");
|
|
210
|
-
onPublishError?.(event, error);
|
|
261
|
+
notify("onPublishError", () => onPublishError?.(event, error), type);
|
|
211
262
|
if (!failOpen) throw error;
|
|
263
|
+
return;
|
|
212
264
|
}
|
|
265
|
+
notify("onPublish", () => onPublish?.(event), type);
|
|
266
|
+
emitObserved(fastify.events, event);
|
|
213
267
|
},
|
|
214
268
|
subscribe: async (pattern, handler) => {
|
|
215
269
|
let wrappedHandler = handler;
|
|
@@ -222,17 +276,18 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
222
276
|
pattern,
|
|
223
277
|
retry: !!retryOpts
|
|
224
278
|
}, "Subscribing to events");
|
|
225
|
-
if (typeof transport.subscribe !== "function") {
|
|
226
|
-
const error = /* @__PURE__ */ new Error(`[Arc Events] transport '${transport.name}' is publish-only (no subscribe) — cannot register a handler for '${pattern}'. Use MemoryEventTransport / RedisEventTransport, or a transport that implements subscribe().`);
|
|
227
|
-
fastify.log?.error?.({
|
|
228
|
-
transport: transport.name,
|
|
229
|
-
pattern
|
|
230
|
-
}, error.message);
|
|
231
|
-
if (!failOpen) throw error;
|
|
232
|
-
return () => {};
|
|
233
|
-
}
|
|
279
|
+
if (typeof transport.subscribe !== "function") throw new Error(`[Arc Events] transport '${transport.name}' is publish-only (no subscribe) — cannot register a handler for '${pattern}'. Use MemoryEventTransport / RedisEventTransport, or a transport that implements subscribe().`);
|
|
234
280
|
try {
|
|
235
|
-
|
|
281
|
+
const dispatch = wrappedHandler;
|
|
282
|
+
const tracked = (event) => {
|
|
283
|
+
if (stopping && !isAdmittedWork()) return Promise.reject(/* @__PURE__ */ new Error("[Arc Events] consumer is shutting down"));
|
|
284
|
+
const pending = runAdmittedWork(() => dispatch(event));
|
|
285
|
+
activeHandlers.add(pending);
|
|
286
|
+
return pending.finally(() => {
|
|
287
|
+
activeHandlers.delete(pending);
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
return await transport.subscribe(pattern, preserveSubscriberIdentity(handler, tracked));
|
|
236
291
|
} catch (error) {
|
|
237
292
|
fastify.log?.error?.({
|
|
238
293
|
transport: transport.name,
|
|
@@ -270,6 +325,12 @@ const eventPlugin = async (fastify, opts = {}) => {
|
|
|
270
325
|
* if a host's graph ends up with two arc copies.
|
|
271
326
|
*/
|
|
272
327
|
fastify.decorate(ARC_EVENT_TRANSPORT, transport);
|
|
328
|
+
if (isHoldable(transport)) {
|
|
329
|
+
transport.hold();
|
|
330
|
+
fastify.addHook("onReady", async () => {
|
|
331
|
+
await transport.start();
|
|
332
|
+
});
|
|
333
|
+
}
|
|
273
334
|
fastify.addHook("onClose", async () => {
|
|
274
335
|
if (!ownsTransport) return;
|
|
275
336
|
try {
|
|
@@ -295,4 +356,4 @@ var eventPlugin_default = fp(eventPlugin, {
|
|
|
295
356
|
fastify: "5.x"
|
|
296
357
|
});
|
|
297
358
|
//#endregion
|
|
298
|
-
export { withRetry as i,
|
|
359
|
+
export { withRetry as a, createDeadLetterPublisher as i, eventPlugin as n, eventPlugin_exports as r, emitObserved as t };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { r as createEvent } from "./EventTransport-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as createEvent } from "./EventTransport-tzA7CAB4.mjs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { durableEventMeta } from "@classytic/primitives/durable-events";
|
|
3
5
|
//#region src/events/defineEvent.ts
|
|
4
6
|
/**
|
|
5
7
|
* Typed event declarations, a registry for introspection, and `.create()` to
|
|
@@ -24,25 +26,30 @@ import { r as createEvent } from "./EventTransport-n7qO5Cz3.mjs";
|
|
|
24
26
|
* await fastify.events.publish(event.type, event.payload, event.meta);
|
|
25
27
|
* ```
|
|
26
28
|
*/
|
|
27
|
-
/**
|
|
28
|
-
* Define a typed event with optional schema validation.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* const OrderCreated = defineEvent({
|
|
32
|
-
* name: 'order.created',
|
|
33
|
-
* schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
|
|
34
|
-
* });
|
|
35
|
-
*
|
|
36
|
-
* const event = OrderCreated.create({ orderId: '123' });
|
|
37
|
-
*/
|
|
38
29
|
function defineEvent(input) {
|
|
39
|
-
const { name,
|
|
30
|
+
const { name, payload, version = 1, description } = input;
|
|
31
|
+
const durable = input.durability === "durable" ? input : void 0;
|
|
32
|
+
if (durable && typeof durable.occurrence !== "function") throw new Error(`[arc] durable event "${name}" must declare \`occurrence(payload)\`.`);
|
|
33
|
+
if (payload && input.schema) throw new Error(`[arc] event "${name}" declares both \`payload\` and \`schema\` — declare the payload once.`);
|
|
40
34
|
return {
|
|
41
35
|
name,
|
|
42
|
-
schema,
|
|
36
|
+
schema: payload ? payloadToJsonSchema(name, payload) : input.schema,
|
|
37
|
+
...payload ? { payloadSchema: payload } : {},
|
|
43
38
|
version,
|
|
44
39
|
description,
|
|
40
|
+
...input.durability !== void 0 ? { durability: input.durability } : {},
|
|
41
|
+
...durable ? { occurrence: (p) => durable.occurrence(p) } : {},
|
|
42
|
+
...durable?.produces ? { produces: durable.produces } : {},
|
|
45
43
|
create(payload, meta) {
|
|
44
|
+
if (durable) {
|
|
45
|
+
const fixed = durableEventMeta(name, version, durable.occurrence(payload));
|
|
46
|
+
if (meta?.id !== void 0 && meta.id !== fixed.id) throw new Error(`[arc] durable event "${name}": meta.id "${meta.id}" contradicts its occurrence id "${fixed.id}".`);
|
|
47
|
+
return createEvent(name, payload, {
|
|
48
|
+
schemaVersion: version,
|
|
49
|
+
...meta,
|
|
50
|
+
...fixed
|
|
51
|
+
});
|
|
52
|
+
}
|
|
46
53
|
return createEvent(name, payload, {
|
|
47
54
|
schemaVersion: version,
|
|
48
55
|
...meta
|
|
@@ -51,6 +58,24 @@ function defineEvent(input) {
|
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
60
|
/**
|
|
61
|
+
* The payload's JSON Schema, via zod's own converter — loaded only here, so an app that never
|
|
62
|
+
* declares a zod payload never loads zod. A payload JSON cannot carry fails AT DEFINITION.
|
|
63
|
+
*/
|
|
64
|
+
function payloadToJsonSchema(name, payload) {
|
|
65
|
+
let zod;
|
|
66
|
+
try {
|
|
67
|
+
zod = createRequire(import.meta.url)("zod");
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(`[arc] event "${name}" declares a zod \`payload\` but zod is not installed.`);
|
|
70
|
+
}
|
|
71
|
+
if (typeof zod.toJSONSchema !== "function") throw new Error(`[arc] event "${name}": \`payload\` needs zod v4 (\`z.toJSONSchema\`).`);
|
|
72
|
+
try {
|
|
73
|
+
return zod.toJSONSchema(payload);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
throw new Error(`[arc] event "${name}": its payload cannot travel as JSON — ${err.message}. An event crosses a transport as JSON: send dates as ISO strings (\`z.string()\`).`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
54
79
|
* Create an event registry for cataloging and validating events.
|
|
55
80
|
*
|
|
56
81
|
* The registry is opt-in — unregistered events pass validation.
|
|
@@ -87,7 +112,8 @@ function createEventRegistry(options) {
|
|
|
87
112
|
name: def.name,
|
|
88
113
|
version: def.version,
|
|
89
114
|
description: def.description,
|
|
90
|
-
schema: def.schema
|
|
115
|
+
schema: def.schema,
|
|
116
|
+
...def.durability !== void 0 ? { durability: def.durability } : {}
|
|
91
117
|
}));
|
|
92
118
|
},
|
|
93
119
|
validate(name, payload, version) {
|
|
@@ -100,6 +126,13 @@ function createEventRegistry(options) {
|
|
|
100
126
|
}
|
|
101
127
|
}
|
|
102
128
|
if (!target) return { valid: true };
|
|
129
|
+
if (target.payloadSchema) {
|
|
130
|
+
const parsed = target.payloadSchema.safeParse(payload);
|
|
131
|
+
return parsed.success ? { valid: true } : {
|
|
132
|
+
valid: false,
|
|
133
|
+
errors: (parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".") || "(payload)"}: ${i.message}`)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
103
136
|
if (!target.schema) return { valid: true };
|
|
104
137
|
return customValidator ? customValidator(target.schema, payload) : validatePayload(payload, target.schema);
|
|
105
138
|
}
|
|
@@ -258,6 +291,29 @@ async function subscribeWithSchema(fastify, definition, handler, options) {
|
|
|
258
291
|
return fastify.events.subscribe(definition.name, wrapWithSchema(definition, handler, options));
|
|
259
292
|
}
|
|
260
293
|
/**
|
|
294
|
+
* Wrap a handler so anything it throws names it: `[<name>] <message>`, the original as `cause`
|
|
295
|
+
* and its string `code` kept. A transport's aggregate error — and so an outbox row's `lastError`
|
|
296
|
+
* — then says WHICH subscriber failed. A message already carrying the label is left as is.
|
|
297
|
+
*/
|
|
298
|
+
function wrapWithAttribution(handler, name) {
|
|
299
|
+
const label = `[${name}]`;
|
|
300
|
+
return async (event) => {
|
|
301
|
+
try {
|
|
302
|
+
await handler(event);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
305
|
+
if (message.startsWith(label)) throw err;
|
|
306
|
+
const attributed = new Error(`${label} ${message}`, { cause: err });
|
|
307
|
+
if (err instanceof Error) {
|
|
308
|
+
attributed.name = err.name;
|
|
309
|
+
const code = Reflect.get(err, "code");
|
|
310
|
+
if (typeof code === "string") Reflect.set(attributed, "code", code);
|
|
311
|
+
}
|
|
312
|
+
throw attributed;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
261
317
|
* Pure handler wrapper — returns a new `EventHandler` that catches handler
|
|
262
318
|
* exceptions and routes them to `onError` (or logs and swallows). For
|
|
263
319
|
* projection / cache-invalidation / fire-and-forget handlers where retry
|
|
@@ -309,4 +365,82 @@ async function subscribeWithBoundary(fastify, pattern, handler, options) {
|
|
|
309
365
|
return fastify.events.subscribe(pattern, wrapWithBoundary(handler, options));
|
|
310
366
|
}
|
|
311
367
|
//#endregion
|
|
312
|
-
|
|
368
|
+
//#region src/events/eventTypes.ts
|
|
369
|
+
/**
|
|
370
|
+
* Event Type Constants and Helpers
|
|
371
|
+
*
|
|
372
|
+
* Provides well-typed event names for CRUD operations and lifecycle events.
|
|
373
|
+
* Use these instead of hand-typing event strings to prevent typos.
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
|
|
378
|
+
*
|
|
379
|
+
* // Subscribe to product creation events
|
|
380
|
+
* events.subscribe(crudEventType('product', 'created'), handler);
|
|
381
|
+
*
|
|
382
|
+
* // Subscribe to Arc lifecycle events
|
|
383
|
+
* events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
/** Suffixes for auto-emitted CRUD events */
|
|
387
|
+
const CRUD_EVENT_SUFFIXES = Object.freeze([
|
|
388
|
+
"created",
|
|
389
|
+
"updated",
|
|
390
|
+
"deleted"
|
|
391
|
+
]);
|
|
392
|
+
/**
|
|
393
|
+
* Build a CRUD event type string.
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```typescript
|
|
397
|
+
* crudEventType('product', 'created') // 'product.created'
|
|
398
|
+
* crudEventType('order', 'deleted') // 'order.deleted'
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
function crudEventType(resource, suffix) {
|
|
402
|
+
return `${resource}.${suffix}`;
|
|
403
|
+
}
|
|
404
|
+
/** Arc framework lifecycle events — emitted automatically by the framework */
|
|
405
|
+
const ARC_LIFECYCLE_EVENTS = Object.freeze({
|
|
406
|
+
/** Emitted when a resource plugin is registered */
|
|
407
|
+
RESOURCE_REGISTERED: "arc.resource.registered",
|
|
408
|
+
/** Emitted when Arc is fully ready (all resources registered, onReady fired) */
|
|
409
|
+
READY: "arc.ready"
|
|
410
|
+
});
|
|
411
|
+
/** Cache-specific event types for observability and external triggers */
|
|
412
|
+
const CACHE_EVENTS = Object.freeze({
|
|
413
|
+
/** Emitted when a resource's cache version is bumped */
|
|
414
|
+
VERSION_BUMPED: "arc.cache.version.bumped",
|
|
415
|
+
/** Emitted when a tag version is bumped */
|
|
416
|
+
TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
|
|
417
|
+
});
|
|
418
|
+
/**
|
|
419
|
+
* The payload arc publishes for every auto-CRUD event (`<resource>.created|updated|deleted`).
|
|
420
|
+
* `data` is the resource document as the route returned it.
|
|
421
|
+
*/
|
|
422
|
+
const CRUD_EVENT_SCHEMA = Object.freeze({
|
|
423
|
+
type: "object",
|
|
424
|
+
properties: {
|
|
425
|
+
resource: { type: "string" },
|
|
426
|
+
operation: {
|
|
427
|
+
type: "string",
|
|
428
|
+
enum: [
|
|
429
|
+
"create",
|
|
430
|
+
"update",
|
|
431
|
+
"delete"
|
|
432
|
+
]
|
|
433
|
+
},
|
|
434
|
+
data: {},
|
|
435
|
+
userId: {},
|
|
436
|
+
organizationId: { type: "string" },
|
|
437
|
+
timestamp: { type: "string" }
|
|
438
|
+
},
|
|
439
|
+
required: [
|
|
440
|
+
"resource",
|
|
441
|
+
"operation",
|
|
442
|
+
"timestamp"
|
|
443
|
+
]
|
|
444
|
+
});
|
|
445
|
+
//#endregion
|
|
446
|
+
export { crudEventType as a, wrapWithAttribution as c, createEventRegistry as d, defineEvent as f, CRUD_EVENT_SUFFIXES as i, wrapWithBoundary as l, CACHE_EVENTS as n, subscribeWithBoundary as o, CRUD_EVENT_SCHEMA as r, subscribeWithSchema as s, ARC_LIFECYCLE_EVENTS as t, wrapWithSchema as u };
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
import { i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions } from "../EventTransport-
|
|
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-
|
|
4
|
-
import { a as
|
|
2
|
+
import { i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions } from "../EventTransport-CiicKz2Z.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-BtmA4-3P.mjs";
|
|
4
|
+
import { a as MemoryOutboxStore, c as OutboxRelayErrorHandler, d as DEFAULT_RELAY_LAG_UNHEALTHY_MS, f as OutboxHealth, i as createOutboxModule, l as OutboxRelayErrorKind, m as readOutboxHealth, n as OutboxModuleExports, o as EventOutbox, p as OutboxHealthStore, r as OutboxModuleOptions, s as EventOutboxOptions, t as OutboxDrainResult, u as RelayResult } from "../outbox-module-gZCHoE4H.mjs";
|
|
5
5
|
import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
|
|
6
|
-
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-
|
|
6
|
+
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-BMk-bpQS.mjs";
|
|
7
|
+
import { IdempotencyClaimStore } from "@classytic/repo-core/idempotency";
|
|
7
8
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
8
9
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
9
10
|
//#region src/events/eventTypes.d.ts
|
|
@@ -57,6 +58,26 @@ declare const CACHE_EVENTS: Readonly<{
|
|
|
57
58
|
/** Type for cache event names */
|
|
58
59
|
type CacheEvent = (typeof CACHE_EVENTS)[keyof typeof CACHE_EVENTS];
|
|
59
60
|
//#endregion
|
|
61
|
+
//#region src/events/inbox.d.ts
|
|
62
|
+
interface InboxOptions {
|
|
63
|
+
/** The claims store — ONE per deployment, shared with command idempotency. */
|
|
64
|
+
readonly store: IdempotencyClaimStore;
|
|
65
|
+
/** Must exceed the handler's worst-case run time. Default: the store's lease default. */
|
|
66
|
+
readonly leaseMs?: number;
|
|
67
|
+
/** Commit or roll back ALL work, including claim completion; external effects need their own key. */
|
|
68
|
+
readonly transaction?: <T>(work: (session: unknown) => Promise<T>) => Promise<T>;
|
|
69
|
+
}
|
|
70
|
+
interface InboxHandlerContext {
|
|
71
|
+
/** Use this session on every database write when a transaction is configured. */
|
|
72
|
+
readonly session?: unknown;
|
|
73
|
+
/** Stable effect deduplication key for external calls or nontransactional handlers. */
|
|
74
|
+
readonly eventId: string;
|
|
75
|
+
}
|
|
76
|
+
type InboxHandler<T> = (event: DomainEvent<T>, context: InboxHandlerContext) => Promise<void>;
|
|
77
|
+
/** The claim operation every inbox row is filed under. */
|
|
78
|
+
declare const EVENT_INBOX_OPERATION = "event-inbox";
|
|
79
|
+
declare function withInbox<T = unknown>(name: string, handler: InboxHandler<T>, options: InboxOptions): EventHandler<T>;
|
|
80
|
+
//#endregion
|
|
60
81
|
//#region src/events/outbox/backoff.d.ts
|
|
61
82
|
/**
|
|
62
83
|
* Exponential backoff for outbox failure policies — pure date math shared
|
|
@@ -107,9 +128,9 @@ interface ExponentialBackoffOptions {
|
|
|
107
128
|
* @example Tuning for a faster transport
|
|
108
129
|
* ```typescript
|
|
109
130
|
* exponentialBackoff({ attempt: 3, baseMs: 250, maxMs: 10_000, jitter: 0.3 });
|
|
110
|
-
* // attempt=1 →
|
|
111
|
-
* // attempt=2 →
|
|
112
|
-
* // attempt=3 →
|
|
131
|
+
* // attempt=1 → 250–325ms (+0–30%: jitter only ever DELAYS, never schedules early)
|
|
132
|
+
* // attempt=2 → 500–650ms
|
|
133
|
+
* // attempt=3 → 1000–1300ms
|
|
113
134
|
* // attempt=10 → capped at 10_000ms
|
|
114
135
|
* ```
|
|
115
136
|
*/
|
|
@@ -304,4 +325,8 @@ interface FastifyEventBus {
|
|
|
304
325
|
};
|
|
305
326
|
}
|
|
306
327
|
//#endregion
|
|
307
|
-
|
|
328
|
+
//#region src/events/subscriberIdentity.d.ts
|
|
329
|
+
/** Assign a deployment-stable identity for durable per-handler progress; never use an index. */
|
|
330
|
+
declare function withSubscriberId<T>(id: string, handler: EventHandler<T>): EventHandler<T>;
|
|
331
|
+
//#endregion
|
|
332
|
+
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, DEFAULT_RELAY_LAG_UNHEALTHY_MS, EVENT_INBOX_OPERATION, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, type FastifyEventBus, type InboxHandler, type InboxHandlerContext, type InboxOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxDrainResult, type OutboxHealth, type OutboxHealthStore, 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, readOutboxHealth, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withInbox, withRetry, withSubscriberId, wrapWithBoundary, wrapWithSchema };
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,58 +1,54 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { n as MemoryEventTransport } from "../EventTransport-
|
|
3
|
-
import { a as createEventRegistry, i as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as MemoryEventTransport } from "../EventTransport-tzA7CAB4.mjs";
|
|
3
|
+
import { a as crudEventType, d as createEventRegistry, f as defineEvent, i as CRUD_EVENT_SUFFIXES, l as wrapWithBoundary, n as CACHE_EVENTS, o as subscribeWithBoundary, s as subscribeWithSchema, t as ARC_LIFECYCLE_EVENTS, u as wrapWithSchema } from "../eventTypes-Cdhm6ZlV.mjs";
|
|
4
|
+
import { r as withSubscriberId } from "../subscriberIdentity-CW39uA8G.mjs";
|
|
5
|
+
import { a as withRetry, i as createDeadLetterPublisher, n as eventPlugin } from "../eventPlugin-Q6BhzHzs.mjs";
|
|
6
|
+
import { r as readOutboxHealth, t as DEFAULT_RELAY_LAG_UNHEALTHY_MS } from "../health-D-Ft6JM6.mjs";
|
|
7
|
+
import { i as exponentialBackoff, n as EventOutbox, r as repositoryAsOutboxStore, t as createOutboxModule } from "../outbox-module-Bjgzl6tg.mjs";
|
|
8
|
+
import { fingerprintRequest, runIdempotent } from "@classytic/repo-core/idempotency";
|
|
6
9
|
import { MemoryOutboxStore } from "@classytic/primitives/memory-outbox";
|
|
7
|
-
//#region src/events/
|
|
10
|
+
//#region src/events/inbox.ts
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
|
|
17
|
-
*
|
|
18
|
-
* // Subscribe to product creation events
|
|
19
|
-
* events.subscribe(crudEventType('product', 'created'), handler);
|
|
20
|
-
*
|
|
21
|
-
* // Subscribe to Arc lifecycle events
|
|
22
|
-
* events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
|
|
23
|
-
* ```
|
|
12
|
+
* Per-handler completion deduplication. Without a shared transaction, a crash between an
|
|
13
|
+
* effect and completion can repeat the effect: handlers must dedupe effects by event id.
|
|
14
|
+
* With `transaction`, the handler's DB effect and claim completion share one session; the
|
|
15
|
+
* host must provide a transaction-aware claim store on the SAME database connection.
|
|
24
16
|
*/
|
|
25
|
-
/**
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
17
|
+
/** The claim operation every inbox row is filed under. */
|
|
18
|
+
const EVENT_INBOX_OPERATION = "event-inbox";
|
|
19
|
+
function withInbox(name, handler, options) {
|
|
20
|
+
if (typeof name !== "string" || name.length === 0) throw new Error("[Arc Events] withInbox: a handler name is required — it is the inbox key.");
|
|
21
|
+
return withSubscriberId(name, async (event) => {
|
|
22
|
+
const execute = async (session) => {
|
|
23
|
+
const run = await runIdempotent({
|
|
24
|
+
store: options.store,
|
|
25
|
+
identity: {
|
|
26
|
+
operation: EVENT_INBOX_OPERATION,
|
|
27
|
+
key: `${name}:${event.meta.id}`
|
|
28
|
+
},
|
|
29
|
+
requestFingerprint: fingerprintRequest({
|
|
30
|
+
type: event.type,
|
|
31
|
+
payload: event.payload
|
|
32
|
+
}),
|
|
33
|
+
...options.leaseMs !== void 0 ? { leaseMs: options.leaseMs } : {},
|
|
34
|
+
...session !== void 0 ? { storeOptions: { session } } : {},
|
|
35
|
+
execute: async () => {
|
|
36
|
+
await handler(event, {
|
|
37
|
+
session,
|
|
38
|
+
eventId: event.meta.id
|
|
39
|
+
});
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (run.outcome === "in_flight") throw new Error(`[Arc Events] Handler '${name}' for ${event.type} ${event.meta.id} is in flight elsewhere — retry later.`);
|
|
44
|
+
if (run.outcome === "exhausted") throw new Error(`[Arc Events] Handler '${name}' for ${event.type} ${event.meta.id} exhausted its takeovers: ${run.lastError ?? "unknown"}.`);
|
|
45
|
+
};
|
|
46
|
+
if (options.transaction) await options.transaction(async (session) => {
|
|
47
|
+
if (session === void 0 || session === null) throw new Error("[Arc Events] Inbox transaction must provide a database session.");
|
|
48
|
+
await execute(session);
|
|
49
|
+
});
|
|
50
|
+
else await execute();
|
|
51
|
+
});
|
|
42
52
|
}
|
|
43
|
-
/** Arc framework lifecycle events — emitted automatically by the framework */
|
|
44
|
-
const ARC_LIFECYCLE_EVENTS = Object.freeze({
|
|
45
|
-
/** Emitted when a resource plugin is registered */
|
|
46
|
-
RESOURCE_REGISTERED: "arc.resource.registered",
|
|
47
|
-
/** Emitted when Arc is fully ready (all resources registered, onReady fired) */
|
|
48
|
-
READY: "arc.ready"
|
|
49
|
-
});
|
|
50
|
-
/** Cache-specific event types for observability and external triggers */
|
|
51
|
-
const CACHE_EVENTS = Object.freeze({
|
|
52
|
-
/** Emitted when a resource's cache version is bumped */
|
|
53
|
-
VERSION_BUMPED: "arc.cache.version.bumped",
|
|
54
|
-
/** Emitted when a tag version is bumped */
|
|
55
|
-
TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
|
|
56
|
-
});
|
|
57
53
|
//#endregion
|
|
58
|
-
export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, EventOutbox, MemoryEventTransport, MemoryOutboxStore, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
|
|
54
|
+
export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, DEFAULT_RELAY_LAG_UNHEALTHY_MS, EVENT_INBOX_OPERATION, EventOutbox, MemoryEventTransport, MemoryOutboxStore, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, readOutboxHealth, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withInbox, withRetry, withSubscriberId, wrapWithBoundary, wrapWithSchema };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-
|
|
2
|
+
import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-BMk-bpQS.mjs";
|
|
3
3
|
export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
|