@classytic/arc 2.31.0 → 2.32.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/{BaseController-De7GqHck.mjs → BaseController-Dk34dxhI.mjs} +16 -13
- package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-BV10D0oG.mjs} +1 -1
- package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-vimw5D7Z.mjs} +2 -2
- package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-BVr7PgA0.mjs} +17 -15
- package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-Dti7ebyv.mjs} +1 -1
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.mjs +11 -10
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-__T3EJDA.mjs} +2 -2
- package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-Dn7rzUJo.mjs} +6 -5
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BFHA6Scf.mjs → caching-DFJqHyFs.mjs} +2 -2
- package/dist/cleanup/index.d.mts +36 -14
- package/dist/cleanup/index.mjs +45 -29
- 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-J99LGTJI.mjs} +1 -1
- package/dist/context/index.mjs +3 -3
- package/dist/core/index.d.mts +1 -1
- package/dist/core/index.mjs +7 -7
- package/dist/{core-BGpIxINN.mjs → core-BwwmNSp7.mjs} +3 -3
- package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DkJZWo0m.mjs} +14 -11
- package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B1exOVD8.mjs} +10 -8
- package/dist/{createApp-Df89BEni.mjs → createApp-Bv0m-Q6_.mjs} +182 -61
- package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-gX4No1gt.mjs} +1 -1
- package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-XFPPUu2c.mjs} +41 -32
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-D2l1mh4g.mjs → elevation-DNXXxgjl.mjs} +4 -4
- package/dist/encryption/index.mjs +5 -4
- package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-B2AZUJUj.mjs} +3 -3
- package/dist/{errors-Cj42AntO.mjs → errors-Bh1KK5ps.mjs} +1 -1
- package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dk_TyXeE.mjs} +8 -7
- package/dist/events/index.d.mts +108 -41
- package/dist/events/index.mjs +166 -142
- package/dist/events/transports/redis-stream-entry.mjs +14 -10
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +5 -5
- package/dist/factory/index.mjs +131 -8
- package/dist/fastify-augmentation-BppG5bMT.d.mts +179 -0
- package/dist/{fields-CB5Cuzk9.mjs → fields-DEi65pGj.mjs} +6 -11
- package/dist/{generate-EcA6ui4i.mjs → generate-CRSG0jTv.mjs} +2 -2
- package/dist/{headers-3zvE0rM3.mjs → headers-BXjQYbeC.mjs} +2 -2
- package/dist/{health-DjZYjM7H.mjs → health-5XnTa2uo.mjs} +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.mjs +8 -6
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-Bc2ox8_U.d.mts → index-DXlHwvuE.d.mts} +1 -1
- package/dist/{index-CFThn1K9.d.mts → index-ZWhLTp_s.d.mts} +70 -4
- package/dist/{index-CEJNZFtX.d.mts → index-zq6VrjaB.d.mts} +70 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +11 -11
- package/dist/{init-BEiNZBsJ.mjs → init-BbfP8aky.mjs} +7 -5
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.mjs +3 -3
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.mjs +1 -1
- 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-BP4IJCBO.mjs} +1 -1
- package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-DDSR2HFl.mjs} +2 -2
- package/dist/{keys-C0_pa1tY.mjs → keys-B-HBTGXl.mjs} +1 -1
- package/dist/{loadResources-yawi9tAE.mjs → loadResources-DisEAmkm.mjs} +4 -3
- package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-D1MJp13f.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-CpfVpKHE.mjs} +32 -31
- package/dist/{memory-DR6wpa9F.mjs → memory-DsuWnj4T.mjs} +5 -5
- package/dist/{metrics-CUdw_wLB.mjs → metrics-DdQL8syd.mjs} +2 -2
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +3 -3
- package/dist/{module-0pFJA6YW.mjs → module-Br81am6m.mjs} +1 -1
- package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-BnkBLN7v.mjs} +3 -3
- package/dist/{openapi-B2L4js1v.mjs → openapi-DIRzvxC0.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +85 -0
- package/dist/outbox-admin/index.mjs +115 -0
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +6 -6
- package/dist/{permissions-L--QDAir.mjs → permissions-Dl_m0NCe.mjs} +101 -8
- package/dist/{pipe-DRDNsI2T.mjs → pipe-ELHCP3zj.mjs} +3 -5
- package/dist/pipeline/index.mjs +2 -2
- package/dist/{pipeline-UPQFatit.mjs → pipeline-CPA-jx0M.mjs} +17 -15
- package/dist/plugins/index.d.mts +2 -2
- 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-C2ZkKbsn.mjs} +1 -1
- package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-CDs87fTw.mjs} +1 -1
- package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-T1hmlTEZ.d.mts} +2 -2
- package/dist/presets/filesUpload.mjs +6 -6
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-C4-g4_4P.mjs → presets-Dntd_pUO.mjs} +6 -5
- package/dist/{projection-BHw6xlOQ.mjs → projection-CjHhM5W1.mjs} +2 -2
- package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-Dhps-Ldt.mjs} +6 -6
- package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-C7mmAGs3.mjs} +2 -2
- package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-CccEFBfk.mjs} +1 -1
- package/dist/{realtime-C5NoxWFY.mjs → realtime-DVvVZZFz.mjs} +10 -10
- package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-B_QrjYZS.mjs} +10 -8
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-4213U4zb.mjs → registry-DqUmzA9W.mjs} +2 -2
- package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BpP9wwj7.mjs} +1 -1
- package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-DS-P0b32.mjs} +2 -2
- package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DotlxG3L.mjs} +1 -1
- package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-B6vQF-sy.mjs} +1 -1
- package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-BhflRrBs.mjs} +1 -1
- package/dist/{schedules-DdtlICTf.mjs → schedules-C2Q3uv2d.mjs} +3 -3
- package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-Crjvczi_.mjs} +1 -1
- package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CnyKK_1j.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-afz_Ieek.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-C4KiMWUa.mjs} +1 -1
- package/dist/{sse-CUTo9y0X.mjs → sse-B9P4Y-UI.mjs} +7 -6
- package/dist/{sseStream-B_5XcVVd.mjs → sseStream-B3zb_d1h.mjs} +2 -2
- package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-DmEMHlPQ.mjs} +1 -1
- package/dist/{streaming-PSZwi8JV.mjs → streaming-DPZdQmDz.mjs} +1 -1
- package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-sp5xTDVA.mjs} +2 -2
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.mjs +10 -6
- package/dist/testing/index.d.mts +31 -4
- package/dist/testing/index.mjs +97 -39
- 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-BvLKvOIL.mjs} +1 -1
- package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-B_5-hKjJ.mjs} +1 -1
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CBgvkp8O.d.mts → types-CT5vu6M0.d.mts} +87 -11
- package/dist/{types-Cys0ko9Z.mjs → types-DXYX7dIR.mjs} +4 -4
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-BNsOXzOd.mjs} +1 -1
- package/dist/utils/index.mjs +10 -10
- package/dist/{utils-vL8BrDE2.mjs → utils-BtOha_Sp.mjs} +3 -3
- package/dist/{validate-_KbifpHF.mjs → validate-PMCPR9El.mjs} +2 -2
- package/dist/{versioning-CHDTor3P.mjs → versioning-CKv2jz_U.mjs} +4 -4
- package/package.json +23 -13
- package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
package/dist/events/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as MemoryEventTransport } from "../EventTransport-
|
|
3
|
-
import { a as createEventRegistry, i as wrapWithSchema, n as subscribeWithSchema, o as defineEvent, r as wrapWithBoundary, t as subscribeWithBoundary } from "../subscribe-helpers-
|
|
4
|
-
import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-
|
|
5
|
-
import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-
|
|
1
|
+
/*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as MemoryEventTransport } from "../EventTransport-BV10D0oG.mjs";
|
|
3
|
+
import { a as createEventRegistry, i as wrapWithSchema, n as subscribeWithSchema, o as defineEvent, r as wrapWithBoundary, t as subscribeWithBoundary } from "../subscribe-helpers-sp5xTDVA.mjs";
|
|
4
|
+
import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-Dk_TyXeE.mjs";
|
|
5
|
+
import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-DmEMHlPQ.mjs";
|
|
6
6
|
import { and, anyOf, eq, lte, ne, or } from "@classytic/repo-core/filter";
|
|
7
|
+
import { MemoryOutboxStore } from "@classytic/primitives/memory-outbox";
|
|
7
8
|
import { InvalidOutboxEventError, OutboxOwnershipError } from "@classytic/primitives/outbox";
|
|
8
9
|
import { update } from "@classytic/repo-core/update";
|
|
9
10
|
//#region src/events/eventTypes.ts
|
|
@@ -57,141 +58,6 @@ const CACHE_EVENTS = Object.freeze({
|
|
|
57
58
|
TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
|
|
58
59
|
});
|
|
59
60
|
//#endregion
|
|
60
|
-
//#region src/events/memory-outbox.ts
|
|
61
|
-
/**
|
|
62
|
-
* In-memory outbox store — reference implementation for dev / testing.
|
|
63
|
-
*
|
|
64
|
-
* Supports the full `OutboxStore` capability set (claim/lease, fail/retry,
|
|
65
|
-
* dedupe, visibleAt). Production deployments should pass a `repository` to
|
|
66
|
-
* `EventOutbox` instead of using this; the memory store exists for unit
|
|
67
|
-
* tests and single-process dev where durable persistence is noise.
|
|
68
|
-
*/
|
|
69
|
-
const DEFAULT_LEASE_MS$2 = 3e4;
|
|
70
|
-
var MemoryOutboxStore = class {
|
|
71
|
-
entries = [];
|
|
72
|
-
seenDedupeKeys = /* @__PURE__ */ new Set();
|
|
73
|
-
/**
|
|
74
|
-
* Drop every entry (including delivered / failed / dead-lettered rows) and
|
|
75
|
-
* the dedupe-key memory.
|
|
76
|
-
*
|
|
77
|
-
* For tests only — it is the in-memory analogue of truncating the table, and
|
|
78
|
-
* exists so a suite can isolate cases against ONE store instance (the
|
|
79
|
-
* `reset` hook of `runOutboxStoreContract`). Not part of `OutboxStore`; a
|
|
80
|
-
* production store must never expose this.
|
|
81
|
-
*/
|
|
82
|
-
clear() {
|
|
83
|
-
this.entries.length = 0;
|
|
84
|
-
this.seenDedupeKeys.clear();
|
|
85
|
-
}
|
|
86
|
-
async save(event, options) {
|
|
87
|
-
if (!event?.type || typeof event.type !== "string") throw new InvalidOutboxEventError("event.type is required");
|
|
88
|
-
if (!event.meta?.id || typeof event.meta.id !== "string") throw new InvalidOutboxEventError("event.meta.id is required");
|
|
89
|
-
if (options?.dedupeKey) {
|
|
90
|
-
if (this.seenDedupeKeys.has(options.dedupeKey)) return;
|
|
91
|
-
this.seenDedupeKeys.add(options.dedupeKey);
|
|
92
|
-
}
|
|
93
|
-
this.entries.push({
|
|
94
|
-
event,
|
|
95
|
-
status: "pending",
|
|
96
|
-
attempts: 0,
|
|
97
|
-
visibleAt: options?.visibleAt?.getTime() ?? 0,
|
|
98
|
-
leaseOwner: null,
|
|
99
|
-
leaseExpiresAt: 0,
|
|
100
|
-
deliveredAt: null,
|
|
101
|
-
firstFailedAt: null,
|
|
102
|
-
lastFailedAt: null,
|
|
103
|
-
lastError: null,
|
|
104
|
-
dedupeKey: options?.dedupeKey
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
async getPending(limit) {
|
|
108
|
-
const now = Date.now();
|
|
109
|
-
return this.entries.filter((e) => e.status === "pending" && e.visibleAt <= now && (e.leaseOwner === null || e.leaseExpiresAt <= now)).slice(0, limit).map((e) => e.event);
|
|
110
|
-
}
|
|
111
|
-
async claimPending(options) {
|
|
112
|
-
const now = Date.now();
|
|
113
|
-
const limit = options?.limit ?? 100;
|
|
114
|
-
const leaseMs = options?.leaseMs ?? DEFAULT_LEASE_MS$2;
|
|
115
|
-
const consumerId = options?.consumerId ?? "anonymous";
|
|
116
|
-
const typeFilter = options?.types ? new Set(options.types) : null;
|
|
117
|
-
const claimed = [];
|
|
118
|
-
for (const entry of this.entries) {
|
|
119
|
-
if (claimed.length >= limit) break;
|
|
120
|
-
if (entry.status !== "pending") continue;
|
|
121
|
-
if (entry.visibleAt > now) continue;
|
|
122
|
-
if (entry.leaseOwner !== null && entry.leaseExpiresAt > now) continue;
|
|
123
|
-
if (typeFilter && !typeFilter.has(entry.event.type)) continue;
|
|
124
|
-
entry.leaseOwner = consumerId;
|
|
125
|
-
entry.leaseExpiresAt = now + leaseMs;
|
|
126
|
-
entry.attempts++;
|
|
127
|
-
claimed.push(entry.event);
|
|
128
|
-
}
|
|
129
|
-
return claimed;
|
|
130
|
-
}
|
|
131
|
-
async acknowledge(eventId, options) {
|
|
132
|
-
const entry = this.entries.find((e) => e.event.meta.id === eventId);
|
|
133
|
-
if (!entry) return;
|
|
134
|
-
if (entry.status === "delivered") return;
|
|
135
|
-
if (options?.consumerId && entry.leaseOwner && entry.leaseOwner !== options.consumerId) throw new OutboxOwnershipError(eventId, options.consumerId, entry.leaseOwner);
|
|
136
|
-
entry.status = "delivered";
|
|
137
|
-
entry.deliveredAt = Date.now();
|
|
138
|
-
entry.leaseOwner = null;
|
|
139
|
-
}
|
|
140
|
-
async fail(eventId, error, options) {
|
|
141
|
-
const entry = this.entries.find((e) => e.event.meta.id === eventId);
|
|
142
|
-
if (!entry) return;
|
|
143
|
-
if (options?.consumerId && entry.leaseOwner && entry.leaseOwner !== options.consumerId) throw new OutboxOwnershipError(eventId, options.consumerId, entry.leaseOwner);
|
|
144
|
-
const now = Date.now();
|
|
145
|
-
entry.lastError = error;
|
|
146
|
-
entry.leaseOwner = null;
|
|
147
|
-
entry.leaseExpiresAt = 0;
|
|
148
|
-
if (entry.firstFailedAt === null) entry.firstFailedAt = now;
|
|
149
|
-
entry.lastFailedAt = now;
|
|
150
|
-
if (options?.deadLetter) {
|
|
151
|
-
entry.status = "dead_letter";
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
entry.status = "pending";
|
|
155
|
-
entry.visibleAt = options?.retryAt ? options.retryAt.getTime() : 0;
|
|
156
|
-
}
|
|
157
|
-
async getDeadLettered(limit) {
|
|
158
|
-
const out = [];
|
|
159
|
-
for (const entry of this.entries) {
|
|
160
|
-
if (out.length >= limit) break;
|
|
161
|
-
if (entry.status !== "dead_letter") continue;
|
|
162
|
-
out.push({
|
|
163
|
-
event: entry.event,
|
|
164
|
-
error: {
|
|
165
|
-
message: entry.lastError?.message ?? "unknown",
|
|
166
|
-
...entry.lastError?.code !== void 0 ? { code: entry.lastError.code } : {}
|
|
167
|
-
},
|
|
168
|
-
attempts: entry.attempts,
|
|
169
|
-
firstFailedAt: new Date(entry.firstFailedAt ?? entry.lastFailedAt ?? Date.now()),
|
|
170
|
-
lastFailedAt: new Date(entry.lastFailedAt ?? entry.firstFailedAt ?? Date.now())
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
return out;
|
|
174
|
-
}
|
|
175
|
-
async purge(olderThanMs) {
|
|
176
|
-
const cutoff = Date.now() - olderThanMs;
|
|
177
|
-
let purged = 0;
|
|
178
|
-
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
179
|
-
const entry = this.entries[i];
|
|
180
|
-
if (!entry) continue;
|
|
181
|
-
if (entry.status === "delivered" && entry.deliveredAt !== null && entry.deliveredAt < cutoff) {
|
|
182
|
-
if (entry.dedupeKey) this.seenDedupeKeys.delete(entry.dedupeKey);
|
|
183
|
-
this.entries.splice(i, 1);
|
|
184
|
-
purged++;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return purged;
|
|
188
|
-
}
|
|
189
|
-
/** Test helper: inspect entry by id */
|
|
190
|
-
_getEntry(eventId) {
|
|
191
|
-
return this.entries.find((e) => e.event.meta.id === eventId);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
//#endregion
|
|
195
61
|
//#region src/events/outbox/backoff.ts
|
|
196
62
|
/**
|
|
197
63
|
* Compute a `retryAt` `Date` using exponential backoff with jitter.
|
|
@@ -487,13 +353,96 @@ function repositoryAsOutboxStore(repository, options) {
|
|
|
487
353
|
if (batch.length < DEFAULT_PURGE_BATCH) break;
|
|
488
354
|
}
|
|
489
355
|
return totalDeleted;
|
|
356
|
+
},
|
|
357
|
+
/**
|
|
358
|
+
* Re-drive one dead-lettered event — the other half of `fail({ deadLetter: true })`.
|
|
359
|
+
*
|
|
360
|
+
* Filtered on `status: 'dead_letter'` as well as the id, so this is a no-op on a row
|
|
361
|
+
* that is merely retrying. Requeuing a backing-off row would zero its attempt count
|
|
362
|
+
* and defeat the failure policy, and the id alone cannot distinguish the two.
|
|
363
|
+
*
|
|
364
|
+
* `attempts` resets because the operator's whole claim is that the CAUSE is fixed:
|
|
365
|
+
* carrying the old count forward would dead-letter the retry again after one more
|
|
366
|
+
* blip, which reads as "the fix did not work".
|
|
367
|
+
*/
|
|
368
|
+
async requeue(eventId) {
|
|
369
|
+
return await r.findOneAndUpdate(and(eq(idField, eventId), eq("status", "dead_letter")), update({ set: {
|
|
370
|
+
status: "pending",
|
|
371
|
+
attempts: 0,
|
|
372
|
+
leaseOwner: null,
|
|
373
|
+
leaseExpiresAt: null,
|
|
374
|
+
[visibleAtField]: /* @__PURE__ */ new Date(),
|
|
375
|
+
lastError: null
|
|
376
|
+
} }), { returnDocument: "after" }) != null;
|
|
377
|
+
},
|
|
378
|
+
/** Rows in `status` — the count behind an operator dashboard or a dead-letter alert. */
|
|
379
|
+
async countByStatus(status) {
|
|
380
|
+
const filters = eq("status", status);
|
|
381
|
+
const counter = repository.count;
|
|
382
|
+
if (typeof counter === "function") return counter.call(repository, filters);
|
|
383
|
+
/**
|
|
384
|
+
* Fallback for a kit without `count`. Two shapes, and the difference decides the
|
|
385
|
+
* query — getting it wrong here produces a number that LIES, which is worse than
|
|
386
|
+
* no number at all: the operator reads a healthy dashboard and stops looking.
|
|
387
|
+
*
|
|
388
|
+
* A paginated kit reports the match count in its envelope, so one row is enough
|
|
389
|
+
* to read it — deliberately not a hydrate-and-length, since a `delivered` count
|
|
390
|
+
* would drag the whole retention window into memory for one integer.
|
|
391
|
+
*
|
|
392
|
+
* A kit that returns a BARE ARRAY has no envelope to read, so the rows ARE the
|
|
393
|
+
* count and every match has to come back. `unwrapDocs` documents that shape as
|
|
394
|
+
* real, so it is not hypothetical. `select: idField` keeps it to one column.
|
|
395
|
+
*/
|
|
396
|
+
const probe = await r.getAll({
|
|
397
|
+
filters,
|
|
398
|
+
page: 1,
|
|
399
|
+
limit: 1,
|
|
400
|
+
select: idField
|
|
401
|
+
});
|
|
402
|
+
if (Array.isArray(probe)) {
|
|
403
|
+
const all = await r.getAll({
|
|
404
|
+
filters,
|
|
405
|
+
select: idField
|
|
406
|
+
});
|
|
407
|
+
return unwrapDocs(all).length;
|
|
408
|
+
}
|
|
409
|
+
const total = probe?.total ?? probe?.meta?.total;
|
|
410
|
+
if (typeof total === "number") return total;
|
|
411
|
+
/**
|
|
412
|
+
* An envelope in a shape neither branch recognises. Throwing beats returning 0:
|
|
413
|
+
* a zero is indistinguishable from a healthy queue, so it would silence exactly
|
|
414
|
+
* the alert this method exists to raise.
|
|
415
|
+
*/
|
|
416
|
+
throw new Error("repositoryAsOutboxStore.countByStatus: the repository's `getAll` returned an envelope with no `total` (and is not a bare array), so the count cannot be read. Implement `count(filter)` on the repository — every kit that has it answers this in one query.");
|
|
417
|
+
},
|
|
418
|
+
/**
|
|
419
|
+
* Age of the oldest PENDING row, or `null` when the queue is empty.
|
|
420
|
+
*
|
|
421
|
+
* The signal a pending count cannot give: a count of 40 looks the same whether the
|
|
422
|
+
* relay is draining briskly or has been wedged for an hour behind one poison row.
|
|
423
|
+
*
|
|
424
|
+
* Measured from `createdAt`, not the last attempt — the question is how long the
|
|
425
|
+
* event has gone undelivered, and restarting the clock on every retry would make a
|
|
426
|
+
* permanently-failing row look permanently fresh, which is the exact row an alert
|
|
427
|
+
* exists to surface.
|
|
428
|
+
*/
|
|
429
|
+
async oldestPendingAgeMs() {
|
|
430
|
+
const result = await r.getAll({
|
|
431
|
+
filters: eq("status", "pending"),
|
|
432
|
+
sort: { createdAt: 1 },
|
|
433
|
+
page: 1,
|
|
434
|
+
limit: 1
|
|
435
|
+
});
|
|
436
|
+
const [oldest] = unwrapDocs(result);
|
|
437
|
+
if (oldest?.createdAt == null) return null;
|
|
438
|
+
return Date.now() - new Date(oldest.createdAt).getTime();
|
|
490
439
|
}
|
|
491
440
|
};
|
|
492
441
|
}
|
|
493
442
|
//#endregion
|
|
494
443
|
//#region src/events/outbox/relay.ts
|
|
495
444
|
/** Default outbox retention — delivered events older than this are eligible for purge */
|
|
496
|
-
const DEFAULT_OUTBOX_RETENTION_MS =
|
|
445
|
+
const DEFAULT_OUTBOX_RETENTION_MS = 6048e5;
|
|
497
446
|
const DEFAULT_LEASE_MS = 3e4;
|
|
498
447
|
/**
|
|
499
448
|
* Hard cap on the in-process attempt-counter map. Entries are cleared on
|
|
@@ -806,4 +755,79 @@ function normalizeError(err) {
|
|
|
806
755
|
return { message: String(err) };
|
|
807
756
|
}
|
|
808
757
|
//#endregion
|
|
809
|
-
|
|
758
|
+
//#region src/events/outbox-module.ts
|
|
759
|
+
/** Default relay cadence. Frequent enough that a retry is not a user-visible delay. */
|
|
760
|
+
const DEFAULT_RELAY_EVERY_MS = 5e3;
|
|
761
|
+
/**
|
|
762
|
+
* Compose the outbox as a module.
|
|
763
|
+
*
|
|
764
|
+
* ```ts
|
|
765
|
+
* // host
|
|
766
|
+
* const model = createOutboxModel(connection, { visibleAtField: 'nextVisibleAt' });
|
|
767
|
+
* createApp({ modules: [
|
|
768
|
+
* createOutboxModule({
|
|
769
|
+
* store: repositoryAsOutboxStore(new Repository(model), { visibleAtField: 'nextVisibleAt' }),
|
|
770
|
+
* onError: (err, ctx) => app.log.error({ err, ...ctx }, 'outbox relay'),
|
|
771
|
+
* }),
|
|
772
|
+
* …
|
|
773
|
+
* ]});
|
|
774
|
+
*
|
|
775
|
+
* // consumer module
|
|
776
|
+
* defineModule({
|
|
777
|
+
* name: 'order',
|
|
778
|
+
* dependsOn: ['outbox'],
|
|
779
|
+
* bootstrap: (f) => createOrder({
|
|
780
|
+
* outbox: getModuleExports<OutboxModuleExports>(f, 'outbox').store,
|
|
781
|
+
* }),
|
|
782
|
+
* });
|
|
783
|
+
* ```
|
|
784
|
+
*
|
|
785
|
+
* `dependsOn` is doing real work there: it both orders the bootstraps and makes a missing outbox a
|
|
786
|
+
* boot error naming the module that needed it.
|
|
787
|
+
*/
|
|
788
|
+
function createOutboxModule(options) {
|
|
789
|
+
const name = options.name ?? "outbox";
|
|
790
|
+
const relayEveryMs = options.relayEveryMs ?? DEFAULT_RELAY_EVERY_MS;
|
|
791
|
+
/**
|
|
792
|
+
* Built at bootstrap and reused by the schedule.
|
|
793
|
+
*
|
|
794
|
+
* Not rebuilt per tick: `EventOutbox` carries the consumer id the lease is keyed on, and a fresh
|
|
795
|
+
* instance per tick would be a fresh claimant every few seconds — which defeats the lease it
|
|
796
|
+
* depends on for exactly-once publishing.
|
|
797
|
+
*/
|
|
798
|
+
let exports;
|
|
799
|
+
return {
|
|
800
|
+
name,
|
|
801
|
+
bootstrap: (fastify) => {
|
|
802
|
+
const store = typeof options.store === "function" ? options.store(fastify) : options.store;
|
|
803
|
+
const transport = options.transport === void 0 ? fastify.events : typeof options.transport === "function" ? options.transport(fastify) : options.transport;
|
|
804
|
+
exports = {
|
|
805
|
+
store,
|
|
806
|
+
relay: new EventOutbox({
|
|
807
|
+
store,
|
|
808
|
+
...transport !== void 0 ? { transport } : {},
|
|
809
|
+
...options.batchSize !== void 0 ? { batchSize: options.batchSize } : {},
|
|
810
|
+
...options.leaseMs !== void 0 ? { leaseMs: options.leaseMs } : {},
|
|
811
|
+
consumerId: options.consumerId ?? "arc-outbox-relay",
|
|
812
|
+
...options.onError !== void 0 ? { onError: options.onError } : {},
|
|
813
|
+
...options.failurePolicy !== void 0 ? { failurePolicy: options.failurePolicy } : {}
|
|
814
|
+
})
|
|
815
|
+
};
|
|
816
|
+
return exports;
|
|
817
|
+
},
|
|
818
|
+
/**
|
|
819
|
+
* The relay runs on the module's OWN schedule arm, so the host stops owning the tick — and
|
|
820
|
+
* a host that forgets to start one cannot exist.
|
|
821
|
+
*/
|
|
822
|
+
scheduledJobs: () => [{
|
|
823
|
+
name: `${name}.relay`,
|
|
824
|
+
every: relayEveryMs,
|
|
825
|
+
runOnStart: options.runOnStart ?? true,
|
|
826
|
+
handler: async () => {
|
|
827
|
+
await exports?.relay.relay();
|
|
828
|
+
}
|
|
829
|
+
}]
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
//#endregion
|
|
833
|
+
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
|
|
2
2
|
import sjson from "secure-json-parse";
|
|
3
3
|
//#region src/events/transports/redis-stream.ts
|
|
4
4
|
/**
|
|
@@ -184,13 +184,15 @@ var RedisStreamTransport = class {
|
|
|
184
184
|
this.handlers.clear();
|
|
185
185
|
this.failureContext.clear();
|
|
186
186
|
if (this.pollPromise) {
|
|
187
|
-
if (await Promise.race([this.pollPromise.then(() => "drained"), this.sleep(this.closeTimeoutMs).then(() => "timeout")]) === "timeout")
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
this.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
if (await Promise.race([this.pollPromise.then(() => "drained"), this.sleep(this.closeTimeoutMs).then(() => "timeout")]) === "timeout") {
|
|
188
|
+
if (!this.externalLifecycle) {
|
|
189
|
+
if (typeof this.redis.disconnect === "function") this.redis.disconnect();
|
|
190
|
+
else await this.redis.quit().catch((err) => {
|
|
191
|
+
this.logger.error("[RedisStreamTransport] quit() during close raced:", err);
|
|
192
|
+
});
|
|
193
|
+
await this.pollPromise.catch(() => void 0);
|
|
194
|
+
} else this.pollPromise.catch(() => void 0);
|
|
195
|
+
}
|
|
194
196
|
this.pollPromise = null;
|
|
195
197
|
}
|
|
196
198
|
if (!this.externalLifecycle) await this.redis.quit().catch(() => void 0);
|
|
@@ -251,8 +253,10 @@ var RedisStreamTransport = class {
|
|
|
251
253
|
const overRetryIds = [];
|
|
252
254
|
for (const entry of pending) {
|
|
253
255
|
const [id, , idleTime, deliveryCount] = entry;
|
|
254
|
-
if (idleTime > this.claimTimeoutMs)
|
|
255
|
-
|
|
256
|
+
if (idleTime > this.claimTimeoutMs) {
|
|
257
|
+
if (deliveryCount >= this.maxRetries) overRetryIds.push(id);
|
|
258
|
+
else staleIds.push(id);
|
|
259
|
+
}
|
|
256
260
|
}
|
|
257
261
|
if (overRetryIds.length > 0) await this.moveToDlq(overRetryIds);
|
|
258
262
|
if (staleIds.length > 0) {
|
package/dist/factory/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as ModuleContribution, c as
|
|
3
|
-
import { a as lazyModuleExports, c as getModuleState, d as resolveModule, f as orderModules, g as
|
|
4
|
-
import "../fastify-augmentation-
|
|
5
|
-
import { a as RawBodyOptions, c as BetterAuthOption, d as JwtAuthOption, i as MultipartOptions, l as CustomAuthenticatorOption, n as preloadResourcesAsync, o as UnderPressureOptions, r as CreateAppOptions, s as AuthOption, t as preloadResources, u as CustomPluginAuthOption } from "../preloadResources-
|
|
2
|
+
import { _ as HealthCheck, a as ModuleContribution, c as LoadResourcesOptions, d as loadResources, i as EventHandlerDefinition, l as ResourceLike, n as ArcModuleInput, o as ModuleDisposer, r as ArcModuleRegistry, s as ModuleSetupContext, t as ArcModule, u as ResourceModule } from "../types-CT5vu6M0.mjs";
|
|
3
|
+
import { _ as collectModuleScheduledJobs, a as lazyModuleExports, c as getModuleState, d as resolveModule, f as orderModules, g as collectModuleHealthChecks, h as extendModule, i as hasModuleExports, l as hasModule, m as ModuleExtension, n as getModuleExports, o as lazyRequiredModuleExports, p as subscribeModuleEventHandlers, r as getOptionalModuleExports, s as ModuleState, t as defineModule, u as resolveContribution, v as collectModuleWorkflows } from "../index-ZWhLTp_s.mjs";
|
|
4
|
+
import { c as ResolvedResourceDescriptor, l as describeResolvedModule, o as ResolvedModuleDescriptor, s as ResolvedModuleLifecycle } from "../fastify-augmentation-BppG5bMT.mjs";
|
|
5
|
+
import { a as RawBodyOptions, c as BetterAuthOption, d as JwtAuthOption, i as MultipartOptions, l as CustomAuthenticatorOption, n as preloadResourcesAsync, o as UnderPressureOptions, r as CreateAppOptions, s as AuthOption, t as preloadResources, u as CustomPluginAuthOption } from "../preloadResources-T1hmlTEZ.mjs";
|
|
6
6
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
7
7
|
//#region src/factory/createApp.d.ts
|
|
8
8
|
/**
|
|
@@ -137,4 +137,4 @@ declare const testingPreset: Partial<CreateAppOptions>;
|
|
|
137
137
|
declare const edgePreset: Partial<CreateAppOptions>;
|
|
138
138
|
declare function getPreset(name: "production" | "development" | "testing" | "edge" | "worker"): Partial<CreateAppOptions>;
|
|
139
139
|
//#endregion
|
|
140
|
-
export { ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type ArcWorker, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CreateWorkerOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type EventHandlerDefinition, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type ModuleContribution, type ModuleState, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, type WorkerHealthOptions, collectModuleHealthChecks, collectModuleScheduledJobs, collectModuleWorkflows, createApp, createWorker, defineModule, developmentPreset, edgePreset, getModuleExports, getModuleState, getOptionalModuleExports, getPreset, hasModule, hasModuleExports, lazyModuleExports, lazyRequiredModuleExports, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveContribution, resolveModule, subscribeModuleEventHandlers, testingPreset, toFetchHandler };
|
|
140
|
+
export { ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type ArcWorker, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CreateWorkerOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type EventHandlerDefinition, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type ModuleContribution, type ModuleDisposer, type ModuleExtension, type ModuleSetupContext, type ModuleState, type MultipartOptions, type RawBodyOptions, type ResolvedModuleDescriptor, type ResolvedModuleLifecycle, type ResolvedResourceDescriptor, type ResourceLike, type ResourceModule, type UnderPressureOptions, type WorkerHealthOptions, collectModuleHealthChecks, collectModuleScheduledJobs, collectModuleWorkflows, createApp, createWorker, defineModule, describeResolvedModule, developmentPreset, edgePreset, extendModule, getModuleExports, getModuleState, getOptionalModuleExports, getPreset, hasModule, hasModuleExports, lazyModuleExports, lazyRequiredModuleExports, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveContribution, resolveModule, subscribeModuleEventHandlers, testingPreset, toFetchHandler };
|
package/dist/factory/index.mjs
CHANGED
|
@@ -1,9 +1,131 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { r as mergeHealthChecks, t as healthPlugin } from "../health-
|
|
3
|
-
import { _ as
|
|
4
|
-
import { a as lazyModuleExports, i as hasModuleExports, n as getModuleExports, o as lazyRequiredModuleExports, r as getOptionalModuleExports, t as defineModule } from "../module-
|
|
5
|
-
import { t as loadResources } from "../loadResources-
|
|
6
|
-
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-
|
|
1
|
+
/*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as mergeHealthChecks, t as healthPlugin } from "../health-5XnTa2uo.mjs";
|
|
3
|
+
import { _ as collectModuleWorkflows, a as edgePreset, c as testingPreset, d as hasModule, f as orderModules, g as collectModuleScheduledJobs, h as collectModuleHealthChecks, i as developmentPreset, l as workerPreset, m as describeResolvedModule, n as createApp, o as getPreset, p as subscribeModuleEventHandlers, s as productionPreset, t as ArcFactory, u as getModuleState, v as resolveContribution, y as resolveModule } from "../createApp-Bv0m-Q6_.mjs";
|
|
4
|
+
import { a as lazyModuleExports, i as hasModuleExports, n as getModuleExports, o as lazyRequiredModuleExports, r as getOptionalModuleExports, t as defineModule } from "../module-Br81am6m.mjs";
|
|
5
|
+
import { t as loadResources } from "../loadResources-DisEAmkm.mjs";
|
|
6
|
+
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-CDs87fTw.mjs";
|
|
7
|
+
//#region src/factory/module/extend.ts
|
|
8
|
+
/** Concatenate two contributions, resolving factories only when arc asks. */
|
|
9
|
+
function mergeContribution(own, extra) {
|
|
10
|
+
if (own === void 0) return extra;
|
|
11
|
+
if (extra === void 0) return own;
|
|
12
|
+
return async (fastify) => {
|
|
13
|
+
const a = typeof own === "function" ? await own(fastify) : own;
|
|
14
|
+
const b = typeof extra === "function" ? await extra(fastify) : extra;
|
|
15
|
+
return [...a, ...b];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Plain-array concat, for the arms that are NOT `ModuleContribution`.
|
|
20
|
+
*
|
|
21
|
+
* `healthChecks` and `errorMappers` are declared as arrays only — arc reads
|
|
22
|
+
* them directly and never calls them. Routing them through the factory merge
|
|
23
|
+
* above would hand arc a function where it expects a list, which does not fail
|
|
24
|
+
* loudly: the arm reads as non-empty and contributes nothing.
|
|
25
|
+
*/
|
|
26
|
+
function mergeArray(own, extra) {
|
|
27
|
+
if (own === void 0) return extra;
|
|
28
|
+
if (extra === void 0) return own;
|
|
29
|
+
return [...own, ...extra];
|
|
30
|
+
}
|
|
31
|
+
/** Order-preserving union — a duplicate edge or claim is not an error. */
|
|
32
|
+
function mergeNames(own, extra) {
|
|
33
|
+
if (own === void 0) return extra;
|
|
34
|
+
if (extra === void 0) return own;
|
|
35
|
+
return [.../* @__PURE__ */ new Set([...own, ...extra])];
|
|
36
|
+
}
|
|
37
|
+
function mergeOwns(own, extra) {
|
|
38
|
+
if (own === "provided" || extra === "provided") return "provided";
|
|
39
|
+
return mergeNames(own, extra);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run both lifecycle hooks, module's first, composing whatever disposers they
|
|
43
|
+
* return. Teardown runs in REVERSE, matching arc's own module teardown order.
|
|
44
|
+
*/
|
|
45
|
+
function mergeHook(own, extra) {
|
|
46
|
+
if (own === void 0) return extra;
|
|
47
|
+
if (extra === void 0) return own;
|
|
48
|
+
return async (fastify, context) => {
|
|
49
|
+
const d1 = await own(fastify, context);
|
|
50
|
+
const d2 = await extra(fastify, context);
|
|
51
|
+
if (!d1 && !d2) return void 0;
|
|
52
|
+
const disposer = async () => {
|
|
53
|
+
if (d2) await d2();
|
|
54
|
+
if (d1) await d1();
|
|
55
|
+
};
|
|
56
|
+
return disposer;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Close the MODULE first, then the host — the opposite of the disposer order
|
|
61
|
+
* above, and deliberately so.
|
|
62
|
+
*
|
|
63
|
+
* A disposer pair is symmetric setup/teardown, so it unwinds LIFO. `onClose` is
|
|
64
|
+
* not: the resource whose lifetime encloses everything is usually the ENGINE,
|
|
65
|
+
* and in a bring-your-own-engine composition the HOST owns it and hands it to
|
|
66
|
+
* the module. Running the host's `onClose` first would destroy the engine while
|
|
67
|
+
* the module is still draining work against it.
|
|
68
|
+
*
|
|
69
|
+
* be-prod's planning module states exactly this order in prose and implements
|
|
70
|
+
* it by hand — "the module's own onClose first (drains its built-in inFlight),
|
|
71
|
+
* then our host-side fallback inFlight, then destroy the BYO engine". Encoding
|
|
72
|
+
* it here is what stops the next host from getting it backwards.
|
|
73
|
+
*/
|
|
74
|
+
function mergeOnClose(own, extra) {
|
|
75
|
+
if (own === void 0) return extra;
|
|
76
|
+
if (extra === void 0) return own;
|
|
77
|
+
return async (fastify) => {
|
|
78
|
+
await own(fastify);
|
|
79
|
+
await extra(fastify);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Merge a host's additions onto a module. Nothing the module declared is lost.
|
|
84
|
+
*
|
|
85
|
+
* ```ts
|
|
86
|
+
* return extendModule(createOrderModule({ … }), {
|
|
87
|
+
* eventHandlers: () => hostHandlers(), // appended after the module's
|
|
88
|
+
* dependsOn: ["outbox"], // unioned
|
|
89
|
+
* bootstrap: (inner) => async (f, c) => { // wraps, never replaces
|
|
90
|
+
* const exports = await inner?.(f, c);
|
|
91
|
+
* return decorate(exports);
|
|
92
|
+
* },
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
function extendModule(mod, extension) {
|
|
97
|
+
const out = { ...mod };
|
|
98
|
+
/**
|
|
99
|
+
* Assign only when the merge produced something.
|
|
100
|
+
*
|
|
101
|
+
* An absent arm must stay ABSENT rather than become `[]` or `undefined` —
|
|
102
|
+
* arc distinguishes "contributes nothing" from "contributes an empty list"
|
|
103
|
+
* when deciding whether a module participates in an arm at all.
|
|
104
|
+
*
|
|
105
|
+
* Each merge is therefore evaluated ONCE, into this call. Writing the
|
|
106
|
+
* condition and the value as two separate calls would leave twelve pairs of
|
|
107
|
+
* duplicated expressions, where editing one half and not the other changes
|
|
108
|
+
* nothing visible and produces a module whose arm silently disagrees with the
|
|
109
|
+
* check that decided to set it.
|
|
110
|
+
*/
|
|
111
|
+
const set = (key, value) => {
|
|
112
|
+
if (value !== void 0) out[key] = value;
|
|
113
|
+
};
|
|
114
|
+
set("dependsOn", mergeNames(mod.dependsOn, extension.dependsOn));
|
|
115
|
+
set("owns", mergeOwns(mod.owns, extension.owns));
|
|
116
|
+
set("resources", mergeContribution(mod.resources, extension.resources));
|
|
117
|
+
set("eventHandlers", mergeContribution(mod.eventHandlers, extension.eventHandlers));
|
|
118
|
+
set("scheduledJobs", mergeContribution(mod.scheduledJobs, extension.scheduledJobs));
|
|
119
|
+
set("workflows", mergeContribution(mod.workflows, extension.workflows));
|
|
120
|
+
set("healthChecks", mergeArray(mod.healthChecks, extension.healthChecks));
|
|
121
|
+
set("errorMappers", mergeArray(mod.errorMappers, extension.errorMappers));
|
|
122
|
+
set("plugins", mergeHook(mod.plugins, extension.plugins));
|
|
123
|
+
set("afterResources", mergeHook(mod.afterResources, extension.afterResources));
|
|
124
|
+
set("onClose", mergeOnClose(mod.onClose, extension.onClose));
|
|
125
|
+
if (extension.bootstrap) out.bootstrap = extension.bootstrap(mod.bootstrap);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
7
129
|
//#region src/factory/createWorker.ts
|
|
8
130
|
async function createWorker(options, workerOptions = {}) {
|
|
9
131
|
const { auth: _auth, helmet: _helmet, cors: _cors, rateLimit: _rateLimit, multipart: _multipart, rawBody: _rawBody, sensible: _sensible, underPressure: _underPressure, replyHelpers: _replyHelpers, ...runtimeOptions } = options;
|
|
@@ -20,10 +142,11 @@ async function createWorker(options, workerOptions = {}) {
|
|
|
20
142
|
});
|
|
21
143
|
if (workerOptions.health) {
|
|
22
144
|
const { port, host = "0.0.0.0", checks = [] } = workerOptions.health;
|
|
145
|
+
const moduleChecks = app.arc?.healthChecks ?? [];
|
|
23
146
|
const mergedChecks = mergeHealthChecks([
|
|
24
147
|
{
|
|
25
148
|
owner: "the module graph",
|
|
26
|
-
checks:
|
|
149
|
+
checks: moduleChecks
|
|
27
150
|
},
|
|
28
151
|
{
|
|
29
152
|
owner: "shared arcPlugins.health",
|
|
@@ -121,4 +244,4 @@ function payloadTooLarge(limit) {
|
|
|
121
244
|
});
|
|
122
245
|
}
|
|
123
246
|
//#endregion
|
|
124
|
-
export { ArcFactory, collectModuleHealthChecks, collectModuleScheduledJobs, collectModuleWorkflows, createApp, createWorker, defineModule, developmentPreset, edgePreset, getModuleExports, getModuleState, getOptionalModuleExports, getPreset, hasModule, hasModuleExports, lazyModuleExports, lazyRequiredModuleExports, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveContribution, resolveModule, subscribeModuleEventHandlers, testingPreset, toFetchHandler };
|
|
247
|
+
export { ArcFactory, collectModuleHealthChecks, collectModuleScheduledJobs, collectModuleWorkflows, createApp, createWorker, defineModule, describeResolvedModule, developmentPreset, edgePreset, extendModule, getModuleExports, getModuleState, getOptionalModuleExports, getPreset, hasModule, hasModuleExports, lazyModuleExports, lazyRequiredModuleExports, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveContribution, resolveModule, subscribeModuleEventHandlers, testingPreset, toFetchHandler };
|