@classytic/arc 2.38.0 → 2.41.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-CYaDvmOB.mjs → BaseController-DYJRZ0EJ.mjs} +114 -24
- package/dist/{EventTransport-Cx60_s-6.d.mts → EventTransport-CbUKDzZZ.d.mts} +33 -0
- package/dist/{EventTransport-Gq7JWbWw.mjs → EventTransport-n7qO5Cz3.mjs} +22 -2
- package/dist/{HookSystem-C0ikzqQf.mjs → HookSystem-CxvZBo77.mjs} +2 -2
- package/dist/{ResourceRegistry-DPcX--zk.mjs → ResourceRegistry-Cu7uTJp9.mjs} +2 -2
- package/dist/{actionPermissions-jlFZ1it4.mjs → actionPermissions-DPwPWPOV.mjs} +7 -6
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +10 -3
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +12 -6
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-BP8GHtjc.mjs → authorizationDecision-2YY_Kaj1.mjs} +2 -2
- package/dist/{base-C-NL2fTi.d.mts → base-CxrV1LmG.d.mts} +10 -0
- package/dist/{buildHandler-DjXOqIvX.mjs → buildHandler-CUhjGHGZ.mjs} +4 -4
- package/dist/cache/index.d.mts +4 -2
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-a3ZEcJJs.mjs → caching-DsWDjIxx.mjs} +2 -2
- package/dist/cleanup/index.d.mts +2 -2
- package/dist/cleanup/index.mjs +21 -5
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +3 -3
- 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-BONghE1Y.mjs → constants-BxLgNFF7.mjs} +1 -1
- package/dist/context/index.d.mts +34 -6
- package/dist/context/index.mjs +42 -24
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -7
- package/dist/{core-8She7FgF.mjs → core-iNGqYerO.mjs} +3 -3
- package/dist/{createActionRouter-Bqi6Bhhx.mjs → createActionRouter-BBlbPs3k.mjs} +7 -7
- package/dist/{createAggregationRouter-BsYO7ktd.mjs → createAggregationRouter-Dv5SuK2x.mjs} +7 -7
- package/dist/createMcpServer-CbDWkZ1T.d.mts +48 -0
- package/dist/{defineAggregation-0KBvqEEC.mjs → defineAggregation-UyeurGkp.mjs} +1 -1
- package/dist/{defineResource-BMdyRelW.mjs → defineResource-DzGT-2LP.mjs} +20 -22
- 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-C6woysGC.mjs → elevation-BF-vhGxY.mjs} +4 -4
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-DI5AN2b5.d.mts → errorHandler--4rSMMvq.d.mts} +7 -1
- package/dist/{errorHandler-BdmIWPba.mjs → errorHandler-CIMkCOWm.mjs} +3 -3
- package/dist/{errors-2ExmOgIJ.mjs → errors-C_YhELhe.mjs} +1 -1
- package/dist/{eventPlugin-DFvHa4Nf.d.mts → eventPlugin-B52abJ8W.d.mts} +1 -1
- package/dist/{eventPlugin-DJwpGMzx.mjs → eventPlugin-CmXaPWLv.mjs} +5 -5
- package/dist/events/index.d.mts +4 -4
- package/dist/events/index.mjs +5 -5
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +34 -24
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +11 -8
- package/dist/factory/index.d.mts +5 -5
- package/dist/factory/index.mjs +5 -5
- package/dist/{fastify-augmentation-jYA2oHyL.d.mts → fastify-augmentation-Cq2nHVaW.d.mts} +3 -3
- package/dist/{fields-Cha2QCKH.mjs → fields-BTyPLHK0.mjs} +1 -1
- package/dist/{generate-BUP4Oydg.mjs → generate-D_YmiCNf.mjs} +4 -3
- package/dist/{headers-DeXNifVj.mjs → headers-CNECDE6q.mjs} +2 -2
- package/dist/{health-Bh0N_FLk.mjs → health-CxQzbJmr.mjs} +10 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +4 -4
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-BtYoMip1.d.mts → index-BHdZD8zK.d.mts} +10 -2
- package/dist/{index-D2uIUSQk.d.mts → index-CFfLKMH4.d.mts} +2 -2
- package/dist/{index-eyg5WEcy.d.mts → index-CbJiYrfv.d.mts} +1 -1
- package/dist/{index-BRoDtjek.d.mts → index-D4u76eQW.d.mts} +3 -3
- package/dist/{index-B78WZom8.d.mts → index-DHbyPo5j.d.mts} +52 -12
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +11 -11
- package/dist/{init-Dg_Am2o_.mjs → init-Y2MlSN9n.mjs} +9 -9
- 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 +2 -2
- package/dist/integrations/mcp/index.d.mts +4 -47
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +22 -2
- package/dist/integrations/mcp/testing.mjs +55 -33
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +2 -2
- package/dist/integrations/webhooks.mjs +3 -3
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +24 -6
- package/dist/{interface-DYPVscCU.mjs → interface-BIpgzbZl.mjs} +1 -1
- package/dist/{jsonBody-DVui0uJl.mjs → jsonBody-Cq9KYl2Z.mjs} +2 -2
- package/dist/{keys-BSuxGS-B.mjs → keys-Cmv4cBRa.mjs} +1 -1
- package/dist/{loadResources-CNd1DWnK.mjs → loadResources-CblF7xt2.mjs} +2 -2
- package/dist/{loadResourcesFromEntry-DNj8cpiY.mjs → loadResourcesFromEntry-99fIMC3p.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-KWsezMx7.mjs → mcpPlugin-xF3Gw-HW.mjs} +75 -31
- package/dist/{memory-BiPVaCPg.mjs → memory-CpDZ9NPw.mjs} +2 -2
- package/dist/{metrics-CsWaIVTa.mjs → metrics-BaKnq8ag.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-BQXHxMEk.mjs → module-BkQEesNQ.mjs} +1 -1
- package/dist/{multipartBody-u6VrS_iH.mjs → multipartBody-C92aJ8am.mjs} +1 -1
- package/dist/{openapi-Dxc8RLy_.mjs → openapi-C3LFRzPq.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +2 -2
- package/dist/outbox-admin/index.mjs +4 -4
- package/dist/{outbox-module-CQXu_O6o.d.mts → outbox-module-BHdUnuuK.d.mts} +15 -2
- package/dist/{outbox-module-DyIqA5vo.mjs → outbox-module-dsCyLxXH.mjs} +43 -21
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-H7Zcb82Z.mjs → permissions-DiHXy8Ck.mjs} +12 -6
- package/dist/{pipe-SunhEaZ6.mjs → pipe-CBVmUJ-j.mjs} +2 -2
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plugins/index.d.mts +7 -37
- package/dist/plugins/index.mjs +34 -22
- package/dist/plugins/response-cache.mjs +9 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-Da7IT_qJ.mjs → pluralize-2_U6QZQg.mjs} +1 -1
- package/dist/{preloadResources-DlhH81py.mjs → preloadResources-Bbp4QJ8V.mjs} +82 -26
- package/dist/{preloadResources-BbBkNkqK.d.mts → preloadResources-CjS2OfpU.d.mts} +14 -9
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +38 -1
- package/dist/presets/multiTenant.mjs +24 -6
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-CNyfaxGL.mjs → presets-BdlugYTc.mjs} +4 -4
- package/dist/{projection-DSfEoooA.mjs → projection-Uvgp-EEj.mjs} +2 -2
- package/dist/{queryCachePlugin-oTsmVyEQ.mjs → queryCachePlugin-B5F09bYr.mjs} +11 -5
- package/dist/rateLimitKey-CK-EU2L8.mjs +41 -0
- package/dist/{realtime-CPmjjgqd.mjs → realtime-FnnD0n3Z.mjs} +9 -9
- package/dist/{redis-CduhKsJi.d.mts → redis-Bzi1y3F9.d.mts} +10 -2
- package/dist/{redis-stream-eh0eOtjh.d.mts → redis-stream-BPKscsTB.d.mts} +12 -7
- package/dist/{registerRoute-XvcI_3sh.mjs → registerRoute-DRsSHPSM.mjs} +6 -6
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-UFEJQN8R.mjs → registry-D3y8VEJ1.mjs} +2 -2
- package/dist/{renewingLease-DuRGVOMe.mjs → renewingLease-B3VYh5aU.mjs} +1 -1
- package/dist/{replyHelpers-BHebarf0.mjs → replyHelpers-DPHsdw-5.mjs} +2 -2
- package/dist/{requestContext-DpmntRZy.mjs → requestContext-cSVKnYPk.mjs} +1 -1
- package/dist/{rolldown-runtime-Add2L50I.mjs → rolldown-runtime-DjDuyglE.mjs} +1 -1
- package/dist/{runtimeCapabilities-D793Pxci.mjs → runtimeCapabilities-DSA_dH1_.mjs} +31 -5
- package/dist/{schedules-DthM8Qag.mjs → schedules-BktJb3Y3.mjs} +8 -4
- package/dist/{schemaConverter-Db-MMsH6.mjs → schemaConverter-BLCSmNbr.mjs} +1 -1
- package/dist/{schemaIR-CIXShch7.mjs → schemaIR-K16Bb5BX.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-D03sk17n.mjs → scope-jSxuBTFM.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-B7qVjQBv.mjs → simpleEqualityMatcher-BiOnyflP.mjs} +1 -1
- package/dist/{sse-D4MYqkic.mjs → sse-CH7Slxc1.mjs} +5 -5
- package/dist/{sseStream-BuMNPR6r.mjs → sseStream-CN-Lh1Tu.mjs} +2 -2
- package/dist/{store-helpers-CIF4RG8s.mjs → store-helpers-9DX9-1j_.mjs} +1 -1
- package/dist/{streaming-DkpW1HwJ.mjs → streaming-dvppnxsr.mjs} +1 -1
- package/dist/{subscribe-helpers-CBZOi6pk.mjs → subscribe-helpers-AVYPgqTl.mjs} +2 -2
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +5 -5
- package/dist/testing/index.mjs +10 -10
- package/dist/testing/outboxStoreContract.mjs +14 -3
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/{transactionContext-DMmvcfdo.mjs → transactionContext-DCWMI6hV.mjs} +1 -1
- package/dist/{typeGuards-DHTnEs5X.mjs → typeGuards-D7tHFdH6.mjs} +1 -1
- package/dist/types/index.d.mts +2 -2
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-BqLyyH0o.mjs → types-CDy3-zw1.mjs} +4 -4
- package/dist/{types-LQFwC37d.d.mts → types-CWQn9-IC.d.mts} +46 -1
- package/dist/{types-BRU-Kd3t.d.mts → types-DfVDvkdP.d.mts} +20 -2
- package/dist/usage/index.d.mts +58 -2
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-CXySQFRg.mjs → userHelpers-DDMOZ_Pm.mjs} +1 -1
- package/dist/utils/index.d.mts +110 -8
- package/dist/utils/index.mjs +12 -12
- package/dist/{utils-BbGG8XNT.mjs → utils-B-zj_b__.mjs} +61 -12
- package/dist/{validate-CidSeUrx.mjs → validate-R0G_JrnK.mjs} +2 -2
- package/dist/{versioning-B843jOYX.d.mts → versioning-D6JIK8Tk.d.mts} +58 -2
- package/dist/{versioning-C2r0eOZ4.mjs → versioning-DuVWcu-F.mjs} +3 -3
- package/dist/workScope-BhsWde4k.mjs +41 -0
- package/package.json +574 -565
- package/skills/arc/SKILL.md +8 -4
- package/skills/arc/references/mcp.md +20 -0
- package/skills/arc/references/production.md +94 -9
- package/skills/arc/references/testing.md +0 -12
- package/skills/arc-code-review/references/anti-patterns.md +1 -1
- package/skills/arc-code-review/references/migration-recipes.md +0 -60
- package/skills/arc-code-review/references/mongokit-migration.md +0 -27
- package/skills/arc-module-publishing/references/package-manifest.md +3 -1
- package/dist/rateLimitKey-CewdR-m6.mjs +0 -15
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { V as ResourceDefinition } from "./index-
|
|
2
|
+
import { V as ResourceDefinition } from "./index-DHbyPo5j.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/integrations/mcp/types.d.ts
|
|
5
5
|
/** Behavioral hints for MCP clients (tool annotations per MCP spec) */
|
|
@@ -275,6 +275,51 @@ interface McpPluginOptions {
|
|
|
275
275
|
* - `McpAuthResolver` — custom function that resolves identity from headers
|
|
276
276
|
*/
|
|
277
277
|
auth?: BetterAuthHandler | McpAuthResolver | false;
|
|
278
|
+
/**
|
|
279
|
+
* DNS-rebinding protection for the MCP routes — `Host` / `Origin` validation.
|
|
280
|
+
*
|
|
281
|
+
* The attack this closes: a page the developer visits can POST to
|
|
282
|
+
* `127.0.0.1:<port>`. If a DNS name the attacker controls resolves to
|
|
283
|
+
* loopback, the browser treats the response as same-origin and the page can
|
|
284
|
+
* READ it — every registered tool, from any website. Validating that `Host`
|
|
285
|
+
* names a host you expect is what breaks it, which is why the MCP SDK ships
|
|
286
|
+
* this as a default for localhost servers.
|
|
287
|
+
*
|
|
288
|
+
* **Default: on when `auth: false`, off otherwise.** Not a guess — it follows
|
|
289
|
+
* the threat:
|
|
290
|
+
* - `auth: false` is the local/dev shape the attack targets, and nothing else
|
|
291
|
+
* is standing in the way. Defaults to the SDK's localhost set
|
|
292
|
+
* (`localhost`, `127.0.0.1`, `[::1]`).
|
|
293
|
+
* - With auth configured, the endpoint is normally a real domain whose `Host`
|
|
294
|
+
* is not localhost — defaulting protection ON would 403 every production
|
|
295
|
+
* deployment. There, credentials are the gate.
|
|
296
|
+
*
|
|
297
|
+
* Set explicitly whenever the default's assumption is wrong for you: a
|
|
298
|
+
* localhost server that DOES use auth, or a deliberately-public `auth: false`
|
|
299
|
+
* endpoint that must keep answering (`false` to disable).
|
|
300
|
+
*
|
|
301
|
+
* Validation itself is `@modelcontextprotocol/server`'s `validateHostHeader` /
|
|
302
|
+
* `validateOriginHeader` — arc does not reimplement the matching rules.
|
|
303
|
+
* Hostnames only, no scheme or port; IPv6 in brackets (`[::1]`). A request
|
|
304
|
+
* with no `Origin` header passes (non-browser MCP clients send none); a
|
|
305
|
+
* present-but-unlisted `Origin` is rejected.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* // localhost server that uses auth — opt in explicitly
|
|
310
|
+
* mcpPlugin({ resources, auth, dnsRebindingProtection: {} });
|
|
311
|
+
* // reachable under a known public name
|
|
312
|
+
* mcpPlugin({ resources, dnsRebindingProtection: { allowedHosts: ['mcp.example.com'] } });
|
|
313
|
+
* // deliberately public, no Host constraint
|
|
314
|
+
* mcpPlugin({ resources, auth: false, dnsRebindingProtection: false });
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
dnsRebindingProtection?: false | {
|
|
318
|
+
/** Allowed `Host` hostnames. Default: `localhost`, `127.0.0.1`, `[::1]`. */
|
|
319
|
+
allowedHosts?: string[];
|
|
320
|
+
/** Allowed `Origin` hostnames. Default: same localhost set. */
|
|
321
|
+
allowedOrigins?: string[];
|
|
322
|
+
};
|
|
278
323
|
/** MCP endpoint path (default: '/mcp') */
|
|
279
324
|
prefix?: string;
|
|
280
325
|
/** Server identity */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { n as DomainEvent, r as EventHandler } from "./EventTransport-
|
|
3
|
-
import { n as ErrorMapper } from "./errorHandler
|
|
2
|
+
import { n as DomainEvent, r as EventHandler } from "./EventTransport-CbUKDzZZ.mjs";
|
|
3
|
+
import { n as ErrorMapper } from "./errorHandler--4rSMMvq.mjs";
|
|
4
4
|
import { FastifyInstance, FastifyPluginAsync } from "fastify";
|
|
5
5
|
import { LockAdapter } from "@classytic/repo-core/lock";
|
|
6
6
|
//#region src/plugins/health.d.ts
|
|
@@ -90,6 +90,24 @@ interface SchedulesPluginOptions {
|
|
|
90
90
|
* environments), same pattern as `idempotencyPlugin`.
|
|
91
91
|
*/
|
|
92
92
|
enabled?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* States that exactly ONE replica of this deployment arms schedules, so no
|
|
95
|
+
* lock is needed. Same idiom as `eventPlugin, { singleProcess: true }`.
|
|
96
|
+
*
|
|
97
|
+
* Under `runtime: 'distributed'`, schedules with neither `lock` nor this
|
|
98
|
+
* flag FAIL the boot. Without one of them arc cannot tell a deliberately
|
|
99
|
+
* single-armed deployment — the `role: 'scheduler'` process pinned to one
|
|
100
|
+
* replica, which is the recommended topology — from the accidental default,
|
|
101
|
+
* where every replica in a fleet fires every tick. The accidental case is
|
|
102
|
+
* duplicate side effects: two invoices, two dunning emails, two charges.
|
|
103
|
+
* That is a worse failure than the replica-local stores arc already refuses
|
|
104
|
+
* to boot on, so it is held to the same standard rather than warned about.
|
|
105
|
+
*
|
|
106
|
+
* Set it only when the replica count for the process that arms schedules is
|
|
107
|
+
* genuinely one. It is a claim about your deployment, not a silencer — if
|
|
108
|
+
* you scale that deployment past one replica, remove it and pass a `lock`.
|
|
109
|
+
*/
|
|
110
|
+
singleReplica?: boolean;
|
|
93
111
|
/**
|
|
94
112
|
* How long `onClose` waits for in-flight runs before abandoning them.
|
|
95
113
|
* Default: 5000.
|
package/dist/usage/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { i as RequestScope } from "../types-Ddl9OjmE.mjs";
|
|
3
|
-
import { l as PermissionCheck, u as PermissionContext } from "../base-
|
|
3
|
+
import { l as PermissionCheck, u as PermissionContext } from "../base-CxrV1LmG.mjs";
|
|
4
4
|
import { UsageBucket, UsageStore, usagePeriod } from "@classytic/repo-core/usage";
|
|
5
5
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
6
6
|
//#region src/usage/requireQuota.d.ts
|
|
@@ -40,6 +40,62 @@ declare class MemoryUsageStore implements UsageStore {
|
|
|
40
40
|
clear(): void;
|
|
41
41
|
}
|
|
42
42
|
//#endregion
|
|
43
|
+
//#region src/usage/stores/redis.d.ts
|
|
44
|
+
/**
|
|
45
|
+
* The three commands this store needs. Fixed arity with an `unknown[]` tail
|
|
46
|
+
* where ioredis overloads one — see the note in `idempotency/stores/redis.ts`
|
|
47
|
+
* for why a narrower tail makes a real `Redis` unassignable.
|
|
48
|
+
*/
|
|
49
|
+
interface UsageRedisLike {
|
|
50
|
+
hincrby(key: string, field: string, increment: number): Promise<number>;
|
|
51
|
+
hgetall(key: string): Promise<Record<string, string>>;
|
|
52
|
+
expire(key: string, seconds: number, ...args: unknown[]): Promise<number>;
|
|
53
|
+
/**
|
|
54
|
+
* Optional. When present, a retention-enabled increment sends `HINCRBY` and
|
|
55
|
+
* `EXPIRE` as ONE round trip instead of two. ioredis has it; a minimal
|
|
56
|
+
* wrapper may not, and the store falls back to two sequential calls.
|
|
57
|
+
*/
|
|
58
|
+
pipeline?(): {
|
|
59
|
+
hincrby(key: string, field: string, increment: number): unknown;
|
|
60
|
+
expire(key: string, seconds: number): unknown;
|
|
61
|
+
exec(): Promise<unknown>;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
interface RedisUsageStoreOptions {
|
|
65
|
+
/** Redis client (ioredis or compatible). */
|
|
66
|
+
redis: UsageRedisLike;
|
|
67
|
+
/**
|
|
68
|
+
* Key prefix (default: `'arc:usage:'`).
|
|
69
|
+
*
|
|
70
|
+
* Keys are `{prefix}{actor}:{period}`. `usagePeriod()` never emits a `:`,
|
|
71
|
+
* so the split is unambiguous for every canonical period; a host that
|
|
72
|
+
* passes its own period key should keep `:` out of it.
|
|
73
|
+
*/
|
|
74
|
+
prefix?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Seconds a bucket survives after its LAST write, or `undefined` (the
|
|
77
|
+
* default) to keep counters forever.
|
|
78
|
+
*
|
|
79
|
+
* The default is deliberate and it is not the usual Redis default. These
|
|
80
|
+
* counters are what quota decisions and invoices are computed from —
|
|
81
|
+
* expiring them by default would delete billing history on a schedule
|
|
82
|
+
* nobody chose, and the loss is silent because `summary()` answers `{}` for
|
|
83
|
+
* a bucket that never existed and one that expired alike. Set it once you
|
|
84
|
+
* know your retention window (e.g. `86400 * 400` to keep a year plus a
|
|
85
|
+
* grace period), and only if the counters here are not your system of
|
|
86
|
+
* record.
|
|
87
|
+
*/
|
|
88
|
+
retentionSeconds?: number;
|
|
89
|
+
}
|
|
90
|
+
/** Period-bucketed usage counters in Redis hashes. See the module header. */
|
|
91
|
+
declare class RedisUsageStore implements UsageStore {
|
|
92
|
+
#private;
|
|
93
|
+
readonly name = "redis";
|
|
94
|
+
constructor(options: RedisUsageStoreOptions);
|
|
95
|
+
increment(bucket: UsageBucket, amount: number): Promise<void>;
|
|
96
|
+
summary(actor: string, period: string): Promise<Record<string, number>>;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
43
99
|
//#region src/usage/usagePlugin.d.ts
|
|
44
100
|
interface UsageTrackOptions {
|
|
45
101
|
/** Count every completed request as `api.requests` (default: true). */
|
|
@@ -91,4 +147,4 @@ declare module "fastify" {
|
|
|
91
147
|
declare const usagePluginFn: FastifyPluginAsync<UsagePluginOptions>;
|
|
92
148
|
declare const _default: FastifyPluginAsync<UsagePluginOptions>;
|
|
93
149
|
//#endregion
|
|
94
|
-
export { MemoryUsageStore, type QuotaOptions, type UsageBucket, type UsageMeter, type UsagePluginOptions, type UsageStore, type UsageTrackOptions, _default as default, requireQuota, usagePeriod, usagePluginFn as usagePlugin };
|
|
150
|
+
export { MemoryUsageStore, type QuotaOptions, RedisUsageStore, type RedisUsageStoreOptions, type UsageBucket, type UsageMeter, type UsagePluginOptions, type UsageRedisLike, type UsageStore, type UsageTrackOptions, _default as default, requireQuota, usagePeriod, usagePluginFn as usagePlugin };
|
package/dist/usage/index.mjs
CHANGED
|
Binary file
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import { F as getHeader, I as requireSingleHeaderValue, P as ArcHeaders } from "../types-Ddl9OjmE.mjs";
|
|
3
|
-
import { f as getUserId, m as normalizeRoles, p as getUserRoles, t as AnyRecord } from "../base-
|
|
4
|
-
import { $ as OpenApiSchemas, S as QueryParserInterface, b as ParsedQuery } from "../index-
|
|
5
|
-
import { n as ErrorMapper } from "../errorHandler
|
|
3
|
+
import { f as getUserId, m as normalizeRoles, p as getUserRoles, t as AnyRecord } from "../base-CxrV1LmG.mjs";
|
|
4
|
+
import { $ as OpenApiSchemas, S as QueryParserInterface, b as ParsedQuery } from "../index-DHbyPo5j.mjs";
|
|
5
|
+
import { n as ErrorMapper } from "../errorHandler--4rSMMvq.mjs";
|
|
6
6
|
import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorOptions, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-BGdVwP25.mjs";
|
|
7
7
|
import { scheduleBackground } from "@classytic/repo-core/cache";
|
|
8
8
|
import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
|
|
@@ -54,6 +54,53 @@ type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
|
|
|
54
54
|
* Promise<any>` would silently disable type checking everywhere `T` flows.
|
|
55
55
|
*/
|
|
56
56
|
type AnyAsyncFn = (...args: never[]) => Promise<unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* Cluster-wide failure counting for a circuit.
|
|
59
|
+
*
|
|
60
|
+
* ## What this shares, and what it deliberately does not
|
|
61
|
+
*
|
|
62
|
+
* It shares the failure SIGNAL, not the state machine. Each replica still owns
|
|
63
|
+
* its own CLOSED/OPEN/HALF_OPEN transitions and its own probe scheduling; what
|
|
64
|
+
* becomes cluster-wide is the count that decides when to trip.
|
|
65
|
+
*
|
|
66
|
+
* That is the whole of the problem worth solving. Per-replica counting means a
|
|
67
|
+
* downstream outage costs `failureThreshold × replicas` real requests before
|
|
68
|
+
* anything opens — at 5 and 50 that is 250 errors a failing payment gateway
|
|
69
|
+
* absorbs while every pod independently rediscovers the same outage. Sharing
|
|
70
|
+
* the count collapses it: the first replica trips at the threshold, and every
|
|
71
|
+
* other replica trips on its NEXT failure, because the shared window is
|
|
72
|
+
* already over the line.
|
|
73
|
+
*
|
|
74
|
+
* Sharing the full state machine instead would mean electing which replica
|
|
75
|
+
* probes in HALF_OPEN — consensus, plus a lock, plus a read on the hot path.
|
|
76
|
+
* The remaining gap without it is bounded and small: one extra failure per
|
|
77
|
+
* replica, once, versus `failureThreshold` per replica every time.
|
|
78
|
+
*
|
|
79
|
+
* ## Cost
|
|
80
|
+
*
|
|
81
|
+
* ONLY the failure path calls this. A successful call never touches the
|
|
82
|
+
* network, which matters because success is the common case and this wraps
|
|
83
|
+
* things like a payment charge. `reset` is called when a circuit closes.
|
|
84
|
+
*
|
|
85
|
+
* ## Failure of the store itself
|
|
86
|
+
*
|
|
87
|
+
* Never propagates. If the shared counter is unreachable the breaker falls
|
|
88
|
+
* back to its local count — degraded to the old behaviour, which is the
|
|
89
|
+
* correct direction: a Redis blip must not disable the protection that exists
|
|
90
|
+
* for a downstream outage, and the two often arrive together.
|
|
91
|
+
*/
|
|
92
|
+
interface CircuitBreakerSharedState {
|
|
93
|
+
/** Store name for diagnostics. */
|
|
94
|
+
readonly name: string;
|
|
95
|
+
/**
|
|
96
|
+
* Record one failure for `circuit` and return the CLUSTER-wide count within
|
|
97
|
+
* the current window. Implementations expire the window so a long-ago
|
|
98
|
+
* outage does not trip a healthy circuit.
|
|
99
|
+
*/
|
|
100
|
+
recordFailure(circuit: string): Promise<number>;
|
|
101
|
+
/** Clear the window — called when this replica closes the circuit. */
|
|
102
|
+
reset(circuit: string): Promise<void>;
|
|
103
|
+
}
|
|
57
104
|
interface CircuitBreakerOptions {
|
|
58
105
|
/**
|
|
59
106
|
* Number of failures before opening circuit
|
|
@@ -117,6 +164,19 @@ interface CircuitBreakerOptions {
|
|
|
117
164
|
* Name for logging/monitoring
|
|
118
165
|
*/
|
|
119
166
|
name?: string;
|
|
167
|
+
/**
|
|
168
|
+
* Cluster-wide failure counting — see {@link CircuitBreakerSharedState}.
|
|
169
|
+
*
|
|
170
|
+
* Omit for a single process, where the local count already IS the cluster
|
|
171
|
+
* count. Under a multi-replica deployment this is what stops every replica
|
|
172
|
+
* absorbing the full threshold before anything trips.
|
|
173
|
+
*
|
|
174
|
+
* Requires a stable {@link CircuitBreakerOptions.name}: it is the key every
|
|
175
|
+
* replica must agree on. A breaker with `sharedState` and no `name` throws
|
|
176
|
+
* at construction rather than silently counting under a generated id that no
|
|
177
|
+
* other replica shares.
|
|
178
|
+
*/
|
|
179
|
+
sharedState?: CircuitBreakerSharedState;
|
|
120
180
|
}
|
|
121
181
|
interface CircuitBreakerStats {
|
|
122
182
|
name?: string;
|
|
@@ -150,6 +210,7 @@ declare class CircuitBreaker<T extends AnyAsyncFn> {
|
|
|
150
210
|
private readonly onStateChange?;
|
|
151
211
|
private readonly onError?;
|
|
152
212
|
private readonly name;
|
|
213
|
+
private readonly sharedState?;
|
|
153
214
|
private readonly fn;
|
|
154
215
|
constructor(fn: T, options?: CircuitBreakerOptions);
|
|
155
216
|
/**
|
|
@@ -171,6 +232,10 @@ declare class CircuitBreaker<T extends AnyAsyncFn> {
|
|
|
171
232
|
* Handle failed call
|
|
172
233
|
*/
|
|
173
234
|
private onFailure;
|
|
235
|
+
/** Open the circuit and schedule the next probe. */
|
|
236
|
+
private trip;
|
|
237
|
+
/** Best-effort clear of the cluster failure window. Never throws. */
|
|
238
|
+
private clearSharedWindow;
|
|
174
239
|
/**
|
|
175
240
|
* Change circuit state
|
|
176
241
|
*/
|
|
@@ -254,6 +319,42 @@ declare class CircuitBreakerRegistry {
|
|
|
254
319
|
*/
|
|
255
320
|
declare function createCircuitBreakerRegistry(): CircuitBreakerRegistry;
|
|
256
321
|
//#endregion
|
|
322
|
+
//#region src/utils/circuitBreakerRedis.d.ts
|
|
323
|
+
/**
|
|
324
|
+
* The two commands this needs. Variadic tail is `unknown[]` for the same
|
|
325
|
+
* reason as every other `*Like` shape in arc — ioredis types its commands as
|
|
326
|
+
* overloads mixing literal tokens and an optional callback, which a narrower
|
|
327
|
+
* tail matches none of. Pinned by `tests/types/ioredis-assignability.test.ts`.
|
|
328
|
+
*/
|
|
329
|
+
interface CircuitBreakerRedisLike {
|
|
330
|
+
incr(key: string): Promise<number>;
|
|
331
|
+
pexpire(key: string, milliseconds: number, ...args: unknown[]): Promise<number>;
|
|
332
|
+
del(...keys: string[]): Promise<number>;
|
|
333
|
+
}
|
|
334
|
+
interface RedisCircuitBreakerStateOptions {
|
|
335
|
+
/** Redis client (ioredis or compatible). */
|
|
336
|
+
redis: CircuitBreakerRedisLike;
|
|
337
|
+
/**
|
|
338
|
+
* Key prefix (default: `'arc:circuit:'`). Keys are `{prefix}{circuitName}`,
|
|
339
|
+
* so the circuit's `name` is what every replica must agree on.
|
|
340
|
+
*/
|
|
341
|
+
prefix?: string;
|
|
342
|
+
/**
|
|
343
|
+
* Counting window in ms (default: `60_000`, matching the breaker's default
|
|
344
|
+
* `resetTimeout`). The period over which `failureThreshold` failures mean
|
|
345
|
+
* the downstream is down.
|
|
346
|
+
*/
|
|
347
|
+
windowMs?: number;
|
|
348
|
+
}
|
|
349
|
+
/** Fixed-window cluster failure counter. See the module header. */
|
|
350
|
+
declare class RedisCircuitBreakerState implements CircuitBreakerSharedState {
|
|
351
|
+
#private;
|
|
352
|
+
readonly name = "redis";
|
|
353
|
+
constructor(options: RedisCircuitBreakerStateOptions);
|
|
354
|
+
recordFailure(circuit: string): Promise<number>;
|
|
355
|
+
reset(circuit: string): Promise<void>;
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
257
358
|
//#region src/utils/compensation.d.ts
|
|
258
359
|
/**
|
|
259
360
|
* Compensating Transaction — In-Process Rollback Primitive
|
|
@@ -691,8 +792,8 @@ declare const paginationSchema: JsonSchema;
|
|
|
691
792
|
* validation PER RESPONSE to pick the branch, on the hottest route of
|
|
692
793
|
* every arc app. The merge keeps the exact bytes (pinned by
|
|
693
794
|
* `tests/utils/list-response-serialization.test.ts`) and drops the
|
|
694
|
-
* per-response branch validation.
|
|
695
|
-
*
|
|
795
|
+
* per-response branch validation. The list route also serves count, exists
|
|
796
|
+
* and distinct dispatch verbs, so no field is common to every valid shape.
|
|
696
797
|
*/
|
|
697
798
|
declare function listResponse(itemSchema: JsonSchema): JsonSchema;
|
|
698
799
|
/** Offset variant — `{ method: 'offset', data, page, limit, total, pages, hasNext, hasPrev }`. */
|
|
@@ -1152,8 +1253,9 @@ interface RuntimeCapabilityDeclaration {
|
|
|
1152
1253
|
}
|
|
1153
1254
|
/**
|
|
1154
1255
|
* Declare a runtime capability. Callable from ANY registration context —
|
|
1155
|
-
* arc's own plugins, host `plugins()` callbacks, module `plugins` phases
|
|
1156
|
-
* which is the point: the audit
|
|
1256
|
+
* arc's own plugins, host `plugins()` callbacks, module `plugins` phases,
|
|
1257
|
+
* and encapsulated children of any of them — which is the point: the audit
|
|
1258
|
+
* sees what the constructor-time guard cannot.
|
|
1157
1259
|
*/
|
|
1158
1260
|
declare function declareRuntimeCapability(fastify: FastifyInstance, decl: RuntimeCapabilityDeclaration): void;
|
|
1159
1261
|
//#endregion
|
|
@@ -1540,4 +1642,4 @@ declare function hasEvents(instance: FastifyInstance): instance is FastifyInstan
|
|
|
1540
1642
|
events: EventsDecorator;
|
|
1541
1643
|
};
|
|
1542
1644
|
//#endregion
|
|
1543
|
-
export { ArcError, type ArcHeaders, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, ConflictError, type ErrorOptions, type EventsDecorator, type FilterDialect, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, type JsonSchemaTarget, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, type PipeUIMessageStreamOptions, RateLimitError, type RuntimeCapabilityDeclaration, type SchemaIo, ServiceUnavailableError, type SseStream, type SseStreamOptions, type StateMachine, type TransitionConfig, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, declareRuntimeCapability, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, getUserRoles, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, normalizeRoles, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, requireSingleHeaderValue, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, withCompensation };
|
|
1645
|
+
export { ArcError, type ArcHeaders, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, type CircuitBreakerRedisLike, CircuitBreakerRegistry, type CircuitBreakerSharedState, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, ConflictError, type ErrorOptions, type EventsDecorator, type FilterDialect, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, type JsonSchemaTarget, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, type PipeUIMessageStreamOptions, RateLimitError, RedisCircuitBreakerState, type RedisCircuitBreakerStateOptions, type RuntimeCapabilityDeclaration, type SchemaIo, ServiceUnavailableError, type SseStream, type SseStreamOptions, type StateMachine, type TransitionConfig, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, declareRuntimeCapability, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, getUserRoles, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, normalizeRoles, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, requireSingleHeaderValue, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, withCompensation };
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-
|
|
3
|
-
import { n as requireSingleHeaderValue, t as getHeader } from "../headers-
|
|
4
|
-
import { n as getUserRoles, r as normalizeRoles, t as getUserId } from "../userHelpers-
|
|
5
|
-
import { t as simpleEqualityMatcher } from "../simpleEqualityMatcher-
|
|
6
|
-
import { A as
|
|
7
|
-
import { a as isZodSchema, i as isJsonSchema, n as convertRouteSchema, o as toJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-
|
|
8
|
-
import { a as promoteStreamTokenToHeader, i as pipeUIMessageStreamToReply, n as forwardedStreamHeaders, r as isReadableStream, t as UI_MESSAGE_STREAM_HEADERS } from "../streaming-
|
|
9
|
-
import { n as declareRuntimeCapability } from "../runtimeCapabilities-
|
|
10
|
-
import { t as openSseStream } from "../sseStream-
|
|
11
|
-
import { t as hasEvents } from "../typeGuards-
|
|
12
|
-
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, declareRuntimeCapability, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, getUserRoles, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, normalizeRoles, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, requireSingleHeaderValue, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, withCompensation };
|
|
1
|
+
/*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
|
|
2
|
+
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-C_YhELhe.mjs";
|
|
3
|
+
import { n as requireSingleHeaderValue, t as getHeader } from "../headers-CNECDE6q.mjs";
|
|
4
|
+
import { n as getUserRoles, r as normalizeRoles, t as getUserId } from "../userHelpers-DDMOZ_Pm.mjs";
|
|
5
|
+
import { t as simpleEqualityMatcher } from "../simpleEqualityMatcher-BiOnyflP.mjs";
|
|
6
|
+
import { A as NEUTRAL_DIALECT, C as offsetListResponse, D as ArcQueryParser, E as responses, O as createQueryParser, S as listResponse, T as queryParams, _ as errorContractSchema, a as defineCompensation, b as getListQueryParams, c as CircuitBreaker, d as CircuitState, f as createCircuitBreaker, g as deleteResponse, h as bareListResponse, i as defineErrorMapper, j as scheduleBackground, k as MONGO_DIALECT, l as CircuitBreakerError, m as aggregateListResponse, n as handleRaw, o as withCompensation, p as createCircuitBreakerRegistry, r as defineGuard, s as RedisCircuitBreakerState, t as createStateMachine, u as CircuitBreakerRegistry, v as errorDetailSchema, w as paginationSchema, x as keysetListResponse, y as getDefaultCrudSchemas } from "../utils-B-zj_b__.mjs";
|
|
7
|
+
import { a as isZodSchema, i as isJsonSchema, n as convertRouteSchema, o as toJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-BLCSmNbr.mjs";
|
|
8
|
+
import { a as promoteStreamTokenToHeader, i as pipeUIMessageStreamToReply, n as forwardedStreamHeaders, r as isReadableStream, t as UI_MESSAGE_STREAM_HEADERS } from "../streaming-dvppnxsr.mjs";
|
|
9
|
+
import { n as declareRuntimeCapability } from "../runtimeCapabilities-DSA_dH1_.mjs";
|
|
10
|
+
import { t as openSseStream } from "../sseStream-CN-Lh1Tu.mjs";
|
|
11
|
+
import { t as hasEvents } from "../typeGuards-D7tHFdH6.mjs";
|
|
12
|
+
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, MONGO_DIALECT, NEUTRAL_DIALECT, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, RedisCircuitBreakerState, ServiceUnavailableError, UI_MESSAGE_STREAM_HEADERS, UnauthorizedError, ValidationError, aggregateListResponse, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, declareRuntimeCapability, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, forwardedStreamHeaders, getDefaultCrudSchemas, getHeader, getListQueryParams, getUserId, getUserRoles, handleRaw, hasEvents, isArcError, isJsonSchema, isReadableStream, isZodSchema, keysetListResponse, listResponse, normalizeRoles, offsetListResponse, openSseStream, paginationSchema, pipeUIMessageStreamToReply, promoteStreamTokenToHeader, queryParams, requireSingleHeaderValue, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, withCompensation };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { m as RESERVED_QUERY_PARAMS } from "./constants-
|
|
1
|
+
/*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
|
|
2
|
+
import { m as RESERVED_QUERY_PARAMS } from "./constants-BxLgNFF7.mjs";
|
|
3
3
|
import { arcLog } from "./logger/index.mjs";
|
|
4
|
-
import { t as ArcError, u as ValidationError } from "./errors-
|
|
4
|
+
import { t as ArcError, u as ValidationError } from "./errors-C_YhELhe.mjs";
|
|
5
5
|
import { scheduleBackground } from "@classytic/repo-core/cache";
|
|
6
6
|
import { errorContractSchema as errorContractSchema$1, errorDetailSchema as errorDetailSchema$1 } from "@classytic/repo-core/errors";
|
|
7
7
|
//#region src/utils/filter-dialect.ts
|
|
@@ -561,8 +561,8 @@ const paginationSchema = {
|
|
|
561
561
|
* validation PER RESPONSE to pick the branch, on the hottest route of
|
|
562
562
|
* every arc app. The merge keeps the exact bytes (pinned by
|
|
563
563
|
* `tests/utils/list-response-serialization.test.ts`) and drops the
|
|
564
|
-
* per-response branch validation.
|
|
565
|
-
*
|
|
564
|
+
* per-response branch validation. The list route also serves count, exists
|
|
565
|
+
* and distinct dispatch verbs, so no field is common to every valid shape.
|
|
566
566
|
*/
|
|
567
567
|
function listResponse(itemSchema) {
|
|
568
568
|
return {
|
|
@@ -577,6 +577,15 @@ function listResponse(itemSchema) {
|
|
|
577
577
|
type: "array",
|
|
578
578
|
items: itemSchema
|
|
579
579
|
},
|
|
580
|
+
count: {
|
|
581
|
+
type: "integer",
|
|
582
|
+
minimum: 0
|
|
583
|
+
},
|
|
584
|
+
exists: { type: "boolean" },
|
|
585
|
+
values: {
|
|
586
|
+
type: "array",
|
|
587
|
+
items: {}
|
|
588
|
+
},
|
|
580
589
|
page: {
|
|
581
590
|
type: "integer",
|
|
582
591
|
example: 1
|
|
@@ -607,7 +616,6 @@ function listResponse(itemSchema) {
|
|
|
607
616
|
description: "Cursor token for the next page, or null."
|
|
608
617
|
}
|
|
609
618
|
},
|
|
610
|
-
required: ["data"],
|
|
611
619
|
additionalProperties: true
|
|
612
620
|
};
|
|
613
621
|
}
|
|
@@ -963,9 +971,12 @@ var CircuitBreaker = class {
|
|
|
963
971
|
onStateChange;
|
|
964
972
|
onError;
|
|
965
973
|
name;
|
|
974
|
+
sharedState;
|
|
966
975
|
fn;
|
|
967
976
|
constructor(fn, options = {}) {
|
|
968
977
|
this.fn = fn;
|
|
978
|
+
if (options.sharedState && !options.name) throw new Error("[arc] CircuitBreaker: `sharedState` requires an explicit `name` — it is the key every replica counts under. Without it each replica would count under its own key.");
|
|
979
|
+
this.sharedState = options.sharedState;
|
|
969
980
|
this.failureThreshold = options.failureThreshold ?? 5;
|
|
970
981
|
this.resetTimeout = options.resetTimeout ?? 6e4;
|
|
971
982
|
this.timeout = options.timeout ?? 1e4;
|
|
@@ -1043,6 +1054,7 @@ var CircuitBreaker = class {
|
|
|
1043
1054
|
if (this.successes >= this.successThreshold) {
|
|
1044
1055
|
this.setState(CircuitState.CLOSED);
|
|
1045
1056
|
this.successes = 0;
|
|
1057
|
+
this.clearSharedWindow();
|
|
1046
1058
|
}
|
|
1047
1059
|
}
|
|
1048
1060
|
}
|
|
@@ -1053,11 +1065,23 @@ var CircuitBreaker = class {
|
|
|
1053
1065
|
this.failures++;
|
|
1054
1066
|
this.successes = 0;
|
|
1055
1067
|
if (this.onError) this.onError(error);
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1068
|
+
const trippedLocally = this.state === CircuitState.HALF_OPEN || this.failures >= this.failureThreshold;
|
|
1069
|
+
if (trippedLocally) this.trip();
|
|
1070
|
+
if (!this.sharedState) return;
|
|
1071
|
+
this.sharedState.recordFailure(this.name).then((clusterFailures) => {
|
|
1072
|
+
if (!trippedLocally && this.state === CircuitState.CLOSED && clusterFailures >= this.failureThreshold) this.trip();
|
|
1073
|
+
}).catch(() => {});
|
|
1074
|
+
}
|
|
1075
|
+
/** Open the circuit and schedule the next probe. */
|
|
1076
|
+
trip() {
|
|
1077
|
+
this.setState(CircuitState.OPEN);
|
|
1078
|
+
this.nextAttempt = Date.now() + this.resetTimeout;
|
|
1079
|
+
this.openedAt = Date.now();
|
|
1080
|
+
}
|
|
1081
|
+
/** Best-effort clear of the cluster failure window. Never throws. */
|
|
1082
|
+
clearSharedWindow() {
|
|
1083
|
+
if (!this.sharedState) return;
|
|
1084
|
+
this.sharedState.reset(this.name).catch(() => {});
|
|
1061
1085
|
}
|
|
1062
1086
|
/**
|
|
1063
1087
|
* Change circuit state
|
|
@@ -1205,6 +1229,31 @@ function createCircuitBreakerRegistry() {
|
|
|
1205
1229
|
return new CircuitBreakerRegistry();
|
|
1206
1230
|
}
|
|
1207
1231
|
//#endregion
|
|
1232
|
+
//#region src/utils/circuitBreakerRedis.ts
|
|
1233
|
+
/** Fixed-window cluster failure counter. See the module header. */
|
|
1234
|
+
var RedisCircuitBreakerState = class {
|
|
1235
|
+
name = "redis";
|
|
1236
|
+
#redis;
|
|
1237
|
+
#prefix;
|
|
1238
|
+
#windowMs;
|
|
1239
|
+
constructor(options) {
|
|
1240
|
+
this.#redis = options.redis;
|
|
1241
|
+
this.#prefix = options.prefix ?? "arc:circuit:";
|
|
1242
|
+
const windowMs = options.windowMs ?? 6e4;
|
|
1243
|
+
if (!Number.isFinite(windowMs) || windowMs <= 0) throw new Error(`[arc] RedisCircuitBreakerState: windowMs must be a positive number, got ${String(options.windowMs)}.`);
|
|
1244
|
+
this.#windowMs = windowMs;
|
|
1245
|
+
}
|
|
1246
|
+
async recordFailure(circuit) {
|
|
1247
|
+
const key = this.#prefix + circuit;
|
|
1248
|
+
const count = await this.#redis.incr(key);
|
|
1249
|
+
if (count === 1) await this.#redis.pexpire(key, this.#windowMs);
|
|
1250
|
+
return count;
|
|
1251
|
+
}
|
|
1252
|
+
async reset(circuit) {
|
|
1253
|
+
await this.#redis.del(this.#prefix + circuit);
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
//#endregion
|
|
1208
1257
|
//#region src/utils/compensation.ts
|
|
1209
1258
|
/**
|
|
1210
1259
|
* Observability hooks must never alter the transaction: a throwing metrics
|
|
@@ -1472,4 +1521,4 @@ function createStateMachine(name, transitions = {}, options = {}) {
|
|
|
1472
1521
|
};
|
|
1473
1522
|
}
|
|
1474
1523
|
//#endregion
|
|
1475
|
-
export {
|
|
1524
|
+
export { NEUTRAL_DIALECT as A, offsetListResponse as C, ArcQueryParser as D, responses as E, createQueryParser as O, listResponse as S, queryParams as T, errorContractSchema$1 as _, defineCompensation as a, getListQueryParams as b, CircuitBreaker as c, CircuitState as d, createCircuitBreaker as f, deleteResponse as g, bareListResponse as h, defineErrorMapper as i, scheduleBackground as j, MONGO_DIALECT as k, CircuitBreakerError as l, aggregateListResponse as m, handleRaw as n, withCompensation as o, createCircuitBreakerRegistry as p, defineGuard as r, RedisCircuitBreakerState as s, createStateMachine as t, CircuitBreakerRegistry as u, errorDetailSchema$1 as v, paginationSchema as w, keysetListResponse as x, getDefaultCrudSchemas as y };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
|
|
3
3
|
//#region src/core/aggregation/normalize.ts
|
|
4
4
|
/**
|
|
5
5
|
* Framework cap on URL-driven aggregation limits when the host opts
|
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
|
|
2
|
-
import { n as DomainEvent } from "./EventTransport-
|
|
2
|
+
import { n as DomainEvent } from "./EventTransport-CbUKDzZZ.mjs";
|
|
3
3
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
4
|
+
//#region src/plugins/gracefulShutdown.d.ts
|
|
5
|
+
interface GracefulShutdownOptions {
|
|
6
|
+
/** Maximum time to wait for graceful shutdown in ms (default: 30000) */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
/** Custom cleanup function called before exit */
|
|
9
|
+
onShutdown?: () => Promise<void> | void;
|
|
10
|
+
/** Signals to handle (default: ['SIGTERM', 'SIGINT']) */
|
|
11
|
+
signals?: NodeJS.Signals[];
|
|
12
|
+
/** Whether to log shutdown events (default: true) */
|
|
13
|
+
logEvents?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Lame-duck window in ms between flipping readiness and closing the
|
|
16
|
+
* server (default: 0 — close immediately, the pre-2.41 behaviour).
|
|
17
|
+
*
|
|
18
|
+
* A load balancer keeps routing to an instance until its readiness
|
|
19
|
+
* probe has failed for a few intervals; closing the moment SIGTERM
|
|
20
|
+
* arrives is the classic rolling-deploy 502. Set this to exceed the LB's
|
|
21
|
+
* deregistration window (typically 5000–15000) so `/ready` reports
|
|
22
|
+
* `draining` while the instance still answers everything routed to it.
|
|
23
|
+
* Counts against `timeout`.
|
|
24
|
+
*/
|
|
25
|
+
drainDelayMs?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Called when shutdown times out or encounters an error.
|
|
28
|
+
* Defaults to `process.exit(1)` — appropriate for production but dangerous in:
|
|
29
|
+
* - **Tests**: kills the test runner. Pass `() => {}` or `() => { throw … }`.
|
|
30
|
+
* - **Shared runtimes** (e.g., serverless): may kill unrelated handlers.
|
|
31
|
+
*
|
|
32
|
+
* @param reason - `'timeout'` if shutdown exceeded `timeout` ms,
|
|
33
|
+
* `'error'` if `onShutdown` or `fastify.close()` threw.
|
|
34
|
+
*/
|
|
35
|
+
onForceExit?: (reason: "timeout" | "error") => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Readable shutdown state — `fastify.shutdownState`. Set the moment a signal
|
|
39
|
+
* (or `fastify.shutdown()`) arrives, BEFORE the server closes, so readiness
|
|
40
|
+
* can start failing while the instance is still serving. `/live` ignores it:
|
|
41
|
+
* a draining process is alive, that is the whole point of the window.
|
|
42
|
+
*/
|
|
43
|
+
interface ShutdownState {
|
|
44
|
+
/** True from the shutdown signal onward — never resets. */
|
|
45
|
+
readonly draining: boolean;
|
|
46
|
+
/** When draining began. */
|
|
47
|
+
readonly since?: Date;
|
|
48
|
+
}
|
|
49
|
+
declare const gracefulShutdownPlugin: FastifyPluginAsync<GracefulShutdownOptions>;
|
|
50
|
+
declare module "fastify" {
|
|
51
|
+
interface FastifyInstance {
|
|
52
|
+
/** Trigger graceful shutdown manually */
|
|
53
|
+
shutdown: () => Promise<void>;
|
|
54
|
+
/** Draining flag + start time; read by the health plugin's `/ready`. */
|
|
55
|
+
shutdownState: ShutdownState;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
declare const _default$5: FastifyPluginAsync<GracefulShutdownOptions>;
|
|
59
|
+
//#endregion
|
|
4
60
|
//#region src/plugins/caching.d.ts
|
|
5
61
|
interface CachingRule {
|
|
6
62
|
/** Path prefix to match (e.g., '/api/products') */
|
|
@@ -172,4 +228,4 @@ declare module "fastify" {
|
|
|
172
228
|
declare const versioningPlugin: FastifyPluginAsync<VersioningOptions>;
|
|
173
229
|
declare const _default: FastifyPluginAsync<VersioningOptions>;
|
|
174
230
|
//#endregion
|
|
175
|
-
export { CachingRule as _, MetricsCollector as a, metricsPlugin as c, _default$2 as d, realtimePlugin as f, CachingOptions as g, ssePlugin as h, MetricEntry as i, ChangeOperation as l, _default$3 as m, _default as n, MetricsOptions as o, SSEOptions as p, versioningPlugin as r, _default$1 as s, VersioningOptions as t, RealtimeOptions as u, _default$4 as v, cachingPlugin as y };
|
|
231
|
+
export { gracefulShutdownPlugin as C, _default$5 as S, CachingRule as _, MetricsCollector as a, GracefulShutdownOptions as b, metricsPlugin as c, _default$2 as d, realtimePlugin as f, CachingOptions as g, ssePlugin as h, MetricEntry as i, ChangeOperation as l, _default$3 as m, _default as n, MetricsOptions as o, SSEOptions as p, versioningPlugin as r, _default$1 as s, VersioningOptions as t, RealtimeOptions as u, _default$4 as v, ShutdownState as x, cachingPlugin as y };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
3
|
-
import { d as createDomainError } from "./errors-
|
|
1
|
+
/*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
|
|
3
|
+
import { d as createDomainError } from "./errors-C_YhELhe.mjs";
|
|
4
4
|
import fp from "fastify-plugin";
|
|
5
5
|
//#region src/plugins/versioning.ts
|
|
6
6
|
var versioning_exports = /* @__PURE__ */ __exportAll({
|