@classytic/arc 2.31.0 → 2.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/arc.js +58 -17
- package/dist/{BaseController-De7GqHck.mjs → BaseController-CaQC5EXX.mjs} +298 -35
- package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
- package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
- package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
- package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-DoALUlOO.mjs} +18 -15
- package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-CUeCmpUd.mjs} +1 -1
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +44 -11
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
- package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
- package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-DLE9xX-V.mjs} +6 -5
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BFHA6Scf.mjs → caching-BcGmvnHh.mjs} +2 -2
- package/dist/cleanup/index.d.mts +52 -18
- package/dist/cleanup/index.mjs +281 -32
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +4 -6
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D3Dv4LAV.mjs → constants-BxCoxmiN.mjs} +1 -1
- package/dist/context/index.mjs +3 -3
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -7
- package/dist/{core-BGpIxINN.mjs → core-dxhcHhUD.mjs} +3 -3
- package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DVpoA0do.mjs} +14 -11
- package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B_B48JZ-.mjs} +10 -8
- package/dist/{createApp-Df89BEni.mjs → createApp-CLGegqiG.mjs} +287 -62
- package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
- package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-DR9vsUYy.mjs} +318 -35
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-D2l1mh4g.mjs → elevation-B0tlY8y1.mjs} +4 -4
- package/dist/encryption/index.mjs +5 -4
- package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
- package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
- package/dist/{errors-Cj42AntO.mjs → errors-Cd_yy7aW.mjs} +1 -1
- package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
- package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dar8qRaj.mjs} +68 -11
- package/dist/events/index.d.mts +18 -297
- package/dist/events/index.mjs +7 -758
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +14 -10
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +20 -5
- package/dist/factory/index.mjs +131 -8
- package/dist/fastify-augmentation-DVsTAy6X.d.mts +179 -0
- package/dist/{fields-CB5Cuzk9.mjs → fields-CojeUkS4.mjs} +6 -11
- package/dist/{generate-EcA6ui4i.mjs → generate-DiB0nHPR.mjs} +2 -2
- package/dist/{headers-3zvE0rM3.mjs → headers-CveNO9gR.mjs} +2 -2
- package/dist/{health-DjZYjM7H.mjs → health-CasG2A28.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +45 -1
- package/dist/idempotency/index.mjs +95 -11
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-CEJNZFtX.d.mts → index-B-DvUzsr.d.mts} +82 -3
- package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
- package/dist/{index-Bc2ox8_U.d.mts → index-Brin0ODU.d.mts} +4 -4
- package/dist/{index-CFThn1K9.d.mts → index-C5NnMEKF.d.mts} +70 -4
- package/dist/{index-C-481Zf2.d.mts → index-ugr1tQlW.d.mts} +17 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +11 -11
- package/dist/{init-BEiNZBsJ.mjs → init-mdJ-v4Xp.mjs} +24 -16
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +100 -98
- package/dist/integrations/mcp/index.d.mts +12 -3
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +124 -1
- package/dist/integrations/webhooks.mjs +180 -2
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +38 -35
- package/dist/{interface-DYzM0q-u.mjs → interface-DznJrm9R.mjs} +1 -1
- package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
- package/dist/{keys-C0_pa1tY.mjs → keys-WeKvY1dE.mjs} +1 -1
- package/dist/{loadResources-yawi9tAE.mjs → loadResources-Bj-aSsXi.mjs} +4 -3
- package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-aJg5TqHi.mjs} +76 -36
- package/dist/{memory-DR6wpa9F.mjs → memory-qYSB7rMQ.mjs} +5 -5
- package/dist/{metrics-CUdw_wLB.mjs → metrics-D7uchLjN.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +3 -3
- package/dist/{module-0pFJA6YW.mjs → module-CwKsgZXp.mjs} +1 -1
- package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-4w-S3-PU.mjs} +3 -3
- package/dist/{openapi-B2L4js1v.mjs → openapi-BN5cQaO2.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +85 -0
- package/dist/outbox-admin/index.mjs +115 -0
- package/dist/outbox-module-CXB79bvt.mjs +839 -0
- package/dist/outbox-module-D-VFq37T.d.mts +368 -0
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +6 -6
- package/dist/{permissions-L--QDAir.mjs → permissions-BAfwgBxc.mjs} +120 -8
- package/dist/{pipe-DRDNsI2T.mjs → pipe-BzzoEhHj.mjs} +3 -5
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/{pipeline-UPQFatit.mjs → pipeline-q69DZzgG.mjs} +17 -15
- package/dist/plugins/index.d.mts +6 -6
- package/dist/plugins/index.mjs +16 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-O0FZgESl.mjs → pluralize-B7xf8qZF.mjs} +1 -1
- package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
- package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +6 -6
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-C4-g4_4P.mjs → presets-D-h0uWtP.mjs} +6 -5
- package/dist/projection-BKM0Ui-s.mjs +42 -0
- package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +50 -8
- package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
- package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
- package/dist/{realtime-C5NoxWFY.mjs → realtime-CTkv3O59.mjs} +10 -10
- package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
- package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-DNnd3vkz.mjs} +10 -8
- package/dist/registry/index.d.mts +3 -3
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-4213U4zb.mjs → registry-CLxCR5LF.mjs} +2 -2
- package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
- package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
- package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
- package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-uYE35JxI.mjs} +1 -1
- package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
- package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
- package/dist/{schedules-DdtlICTf.mjs → schedules-CAcZgia0.mjs} +3 -3
- package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
- package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CDvclAew.mjs} +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.mjs +5 -5
- package/dist/{scope-DHx5Fous.mjs → scope-BaWcqBvr.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
- package/dist/{sse-CUTo9y0X.mjs → sse-Cf0jUHsh.mjs} +7 -6
- package/dist/{sseStream-B_5XcVVd.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
- package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-B88p99Z2.mjs} +1 -1
- package/dist/{streaming-PSZwi8JV.mjs → streaming-DsT1B5zJ.mjs} +1 -1
- package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-CLIOHsOK.mjs} +2 -2
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.d.mts +1 -1
- package/dist/testing/cleanupStoreContract.mjs +12 -6
- package/dist/testing/index.d.mts +52 -13
- package/dist/testing/index.mjs +110 -40
- package/dist/testing/outboxStoreContract.d.mts +11 -2
- package/dist/testing/outboxStoreContract.mjs +110 -21
- package/dist/testing/storageContract.mjs +11 -6
- package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
- package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-jjKJDN13.mjs} +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CBgvkp8O.d.mts → types-BcTumAz_.d.mts} +128 -18
- package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
- package/dist/{types-Cys0ko9Z.mjs → types-CoqIRrkY.mjs} +4 -4
- package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
- package/dist/usage/index.d.mts +1 -1
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
- package/dist/utils/index.d.mts +57 -5
- package/dist/utils/index.mjs +12 -11
- package/dist/{utils-vL8BrDE2.mjs → utils-h1V_sT-t.mjs} +38 -7
- package/dist/{validate-_KbifpHF.mjs → validate-CdQlz52F.mjs} +2 -2
- package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
- package/dist/{versioning-CHDTor3P.mjs → versioning-rZbyLVD-.mjs} +4 -4
- package/package.json +31 -20
- package/skills/arc/SKILL.md +96 -4
- package/skills/arc/references/api-reference.md +19 -0
- package/skills/arc/references/production.md +2 -2
- package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
- package/dist/projection-BHw6xlOQ.mjs +0 -18
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
import { Ht as ServerAccessor, Rt as IControllerResponse, V as ResourceDefinition, zt as IRequestContext } from "../../index-CpzxqePp.mjs";
|
|
3
2
|
import { i as RequestScope } from "../../types-ChuShpCP.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { l as PermissionCheck } from "../../base-Csjc2oF-.mjs";
|
|
4
|
+
import { Fn as IControllerResponse, In as IRequestContext, V as ResourceDefinition, zn as ServerAccessor } from "../../index-BcR2bFZQ.mjs";
|
|
5
5
|
import { n as FieldPermissionMap } from "../../fields-I_EeRWND.mjs";
|
|
6
|
-
import { _ as ToolDefinition, a as CrudDescriptionOverride, c as McpAuthResult, d as McpResourceConfig, f as PromptDefinition, g as ToolContext, h as ToolAnnotations, i as CrudDescriptionMeta, l as McpExecutionWiring, m as SessionEntry, n as CallToolResult, o as CrudOperation, p as PromptResult, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpPluginOptions } from "../../types-
|
|
6
|
+
import { _ as ToolDefinition, a as CrudDescriptionOverride, c as McpAuthResult, d as McpResourceConfig, f as PromptDefinition, g as ToolContext, h as ToolAnnotations, i as CrudDescriptionMeta, l as McpExecutionWiring, m as SessionEntry, n as CallToolResult, o as CrudOperation, p as PromptResult, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpPluginOptions } from "../../types-BfC83KsP.mjs";
|
|
7
7
|
import { ErrorContract } from "@classytic/repo-core/errors";
|
|
8
8
|
import { FastifyPluginAsync } from "fastify";
|
|
9
9
|
import { z } from "zod";
|
|
@@ -222,6 +222,15 @@ interface FieldRulesToZodOptions {
|
|
|
222
222
|
filterableFields?: readonly string[];
|
|
223
223
|
/** Allowed filter operators — generates `field[op]` entries in list mode (e.g., price[gt], price[lte]) */
|
|
224
224
|
allowedOperators?: readonly string[];
|
|
225
|
+
/**
|
|
226
|
+
* The resource's page-size cap, for the `limit` field's `max` AND its description.
|
|
227
|
+
*
|
|
228
|
+
* Defaults to 100 only when the resource states nothing. Hardcoding 100 here made
|
|
229
|
+
* MCP a FOURTH independent cap: a resource configured for 1000 exposed a tool that
|
|
230
|
+
* rejected `limit: 500` outright and told the agent "max 100" — so the model was
|
|
231
|
+
* misinformed about the ceiling, not merely limited by it.
|
|
232
|
+
*/
|
|
233
|
+
maxLimit?: number;
|
|
225
234
|
}
|
|
226
235
|
/** Single field rule entry from Arc's schemaOptions.fieldRules */
|
|
227
236
|
interface FieldRuleEntry {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-aJg5TqHi.mjs";
|
|
3
3
|
//#region src/integrations/mcp/defineTool.ts
|
|
4
4
|
/**
|
|
5
5
|
* Define a type-safe MCP tool.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { c as McpAuthResult, u as McpPluginOptions } from "../../types-
|
|
2
|
+
import { c as McpAuthResult, u as McpPluginOptions } from "../../types-BfC83KsP.mjs";
|
|
3
3
|
//#region src/integrations/mcp/testing.d.ts
|
|
4
4
|
interface TestMcpClientOptions {
|
|
5
5
|
/** MCP plugin options (resources, overrides, etc.) — same as mcpPlugin config */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-aJg5TqHi.mjs";
|
|
3
3
|
//#region src/integrations/mcp/testing.ts
|
|
4
4
|
/**
|
|
5
5
|
* @classytic/arc/mcp/testing — MCP Test Utilities
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { f as createError, i as NotFoundError, r as ForbiddenError } from "../errors-
|
|
3
|
-
import { a as promoteStreamTokenToHeader, n as forwardedStreamHeaders } from "../streaming-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { f as createError, i as NotFoundError, r as ForbiddenError } from "../errors-Cd_yy7aW.mjs";
|
|
3
|
+
import { a as promoteStreamTokenToHeader, n as forwardedStreamHeaders } from "../streaming-DsT1B5zJ.mjs";
|
|
4
4
|
import fp from "fastify-plugin";
|
|
5
5
|
//#region src/integrations/streamline.ts
|
|
6
6
|
/**
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
|
|
2
|
+
import { a as EventTransport, n as DomainEvent } from "../EventTransport-BEVP50ka.mjs";
|
|
3
|
+
import { n as OutboxModuleOptions, r as createOutboxModule } from "../outbox-module-D-VFq37T.mjs";
|
|
2
4
|
import { FastifyPluginAsync } from "fastify";
|
|
5
|
+
import { OutboxFailurePolicy, OutboxStore } from "@classytic/primitives/outbox";
|
|
3
6
|
//#region src/integrations/webhooks.d.ts
|
|
4
7
|
interface WebhookSubscription {
|
|
5
8
|
/** Unique subscription ID */
|
|
@@ -86,6 +89,23 @@ interface WebhookPluginOptions {
|
|
|
86
89
|
* framework.
|
|
87
90
|
*/
|
|
88
91
|
validateUrl?: (url: URL, subscription: WebhookSubscription) => void | Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* DURABLE delivery — the outbox→worker composition. When set, a matching
|
|
94
|
+
* event no longer POSTs inline: each (event × subscription) pair becomes ONE
|
|
95
|
+
* durable row in the given outbox store, and `createDurableWebhookModule`'s
|
|
96
|
+
* relay claims, delivers, retries with backoff, and dead-letters — all
|
|
97
|
+
* surviving crash and deploy, which the in-process `retry` cannot.
|
|
98
|
+
*
|
|
99
|
+
* The row id is deterministic (`<event id>:wh:<subscription id>`) and the
|
|
100
|
+
* save carries it as `dedupeKey`, so an at-least-once upstream (Redis
|
|
101
|
+
* Streams, an events outbox) redelivering the same event enqueues each
|
|
102
|
+
* delivery exactly once. Mutually exclusive with `retry` — the retry axis
|
|
103
|
+
* moves to the relay's failure policy.
|
|
104
|
+
*/
|
|
105
|
+
durable?: {
|
|
106
|
+
/** Outbox store the delivery rows are written to — share it with the module. */
|
|
107
|
+
store: OutboxStore;
|
|
108
|
+
};
|
|
89
109
|
}
|
|
90
110
|
/** `WebhookSubscription` as returned by `list()` — secret withheld. */
|
|
91
111
|
type WebhookSubscriptionPublic = Omit<WebhookSubscription, "secret">;
|
|
@@ -245,5 +265,108 @@ interface VerifySignatureOptions {
|
|
|
245
265
|
declare function verifySignature(body: string | Buffer, secret: string, signature: string | undefined, options?: VerifySignatureOptions): boolean;
|
|
246
266
|
declare const webhookPlugin: FastifyPluginAsync<WebhookPluginOptions>;
|
|
247
267
|
declare const _default: FastifyPluginAsync<WebhookPluginOptions>;
|
|
268
|
+
/** Event type carried by durable webhook delivery rows. */
|
|
269
|
+
declare const WEBHOOK_DELIVERY_EVENT = "arc.webhook.delivery";
|
|
270
|
+
/** Payload of a {@link WEBHOOK_DELIVERY_EVENT} row. */
|
|
271
|
+
interface WebhookDeliveryPayload {
|
|
272
|
+
subscriptionId: string;
|
|
273
|
+
/** The original event, verbatim — what the receiver is signed and sent. */
|
|
274
|
+
event: {
|
|
275
|
+
type: string;
|
|
276
|
+
payload: unknown;
|
|
277
|
+
meta: DomainEvent["meta"];
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* A failed webhook POST, classified for the failure policy: `transient`
|
|
282
|
+
* failures (network error, timeout, 429, 5xx) are worth retrying; the rest
|
|
283
|
+
* (4xx rejection, 3xx under `redirect: "manual"`, URL-policy rejection,
|
|
284
|
+
* malformed row) are final for this payload and dead-letter immediately.
|
|
285
|
+
*/
|
|
286
|
+
declare class WebhookDeliveryError extends Error {
|
|
287
|
+
readonly status?: number;
|
|
288
|
+
readonly transient: boolean;
|
|
289
|
+
constructor(message: string, options: {
|
|
290
|
+
status?: number;
|
|
291
|
+
transient: boolean;
|
|
292
|
+
cause?: unknown;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
interface WebhookDeliveryTransportOptions {
|
|
296
|
+
/**
|
|
297
|
+
* Resolve a subscription (WITH secret) at delivery time — fresh URL and
|
|
298
|
+
* secret apply to every attempt, and an unregistered id is an acknowledged
|
|
299
|
+
* no-op (the delivery is moot, not an error).
|
|
300
|
+
*/
|
|
301
|
+
getSubscription: (id: string) => WebhookSubscription | undefined | Promise<WebhookSubscription | undefined>;
|
|
302
|
+
/** Custom fetch (for testing). */
|
|
303
|
+
fetch?: typeof globalThis.fetch;
|
|
304
|
+
/** Per-POST timeout in ms (default: 10000). */
|
|
305
|
+
timeout?: number;
|
|
306
|
+
/**
|
|
307
|
+
* URL policy — same seam as {@link WebhookPluginOptions.validateUrl}, but
|
|
308
|
+
* enforced at DELIVERY time (covers rows enqueued before a policy existed
|
|
309
|
+
* and out-of-band store writes). When set, `redirect: "manual"` applies so
|
|
310
|
+
* an allowed URL can't 302 into a blocked target; a rejection is permanent.
|
|
311
|
+
*/
|
|
312
|
+
validateUrl?: (url: URL, subscription: WebhookSubscription) => void | Promise<void>;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Publish-only `EventTransport` whose `publish()` IS the webhook POST —
|
|
316
|
+
* plugged into an outbox relay, delivery inherits the relay's guarantees.
|
|
317
|
+
* Throws {@link WebhookDeliveryError} on failure so the relay observes it
|
|
318
|
+
* (fail → policy → retryAt/deadLetter); returns normally on 2xx and on
|
|
319
|
+
* unregistered subscriptions (acknowledge).
|
|
320
|
+
*
|
|
321
|
+
* The wire contract is IDENTICAL to inline dispatch: v1 signing, and
|
|
322
|
+
* `x-arc-webhook-delivery` carries the ORIGINAL event's `meta.id` — stable
|
|
323
|
+
* across retries, so receiver-side dedup is unchanged.
|
|
324
|
+
*/
|
|
325
|
+
declare function createWebhookDeliveryTransport(options: WebhookDeliveryTransportOptions): EventTransport;
|
|
326
|
+
interface WebhookDeliveryPolicyOptions {
|
|
327
|
+
/** Total attempts before dead-lettering (default: 8). */
|
|
328
|
+
maxAttempts?: number;
|
|
329
|
+
/** First retry delay in ms (default: 5000). */
|
|
330
|
+
baseMs?: number;
|
|
331
|
+
/** Backoff cap in ms (default: 900000 — 15 minutes). */
|
|
332
|
+
maxMs?: number;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Default failure policy for durable webhook delivery: permanent failures
|
|
336
|
+
* ({@link WebhookDeliveryError} with `transient: false`) dead-letter on the
|
|
337
|
+
* first attempt — repeating a rejected payload can't succeed; transient ones
|
|
338
|
+
* retry on exponential backoff until `maxAttempts`, then dead-letter.
|
|
339
|
+
*/
|
|
340
|
+
declare function webhookDeliveryFailurePolicy(options?: WebhookDeliveryPolicyOptions): OutboxFailurePolicy;
|
|
341
|
+
interface DurableWebhookModuleOptions extends Omit<OutboxModuleOptions, "name" | "transport" | "failurePolicy">, Pick<WebhookDeliveryTransportOptions, "fetch" | "timeout" | "validateUrl"> {
|
|
342
|
+
/** Module name / `dependsOn` key. Default `'webhook-delivery'`. */
|
|
343
|
+
name?: string;
|
|
344
|
+
/** Retry/backoff tuning for the default failure policy. */
|
|
345
|
+
delivery?: WebhookDeliveryPolicyOptions;
|
|
346
|
+
/** Full policy override — replaces {@link webhookDeliveryFailurePolicy}. */
|
|
347
|
+
failurePolicy?: OutboxFailurePolicy;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Durable webhook delivery as a module: the outbox relay machinery pointed at
|
|
351
|
+
* webhook POSTs. Pair with `webhookPlugin`'s `durable` option sharing the
|
|
352
|
+
* same store:
|
|
353
|
+
*
|
|
354
|
+
* ```ts
|
|
355
|
+
* const store = repositoryAsOutboxStore(deliveryRepo);
|
|
356
|
+
* createApp({
|
|
357
|
+
* modules: [createDurableWebhookModule({ store })],
|
|
358
|
+
* plugins: async (f) => {
|
|
359
|
+
* await f.register(webhookPlugin, { durable: { store } });
|
|
360
|
+
* },
|
|
361
|
+
* });
|
|
362
|
+
* ```
|
|
363
|
+
*
|
|
364
|
+
* The relay arm is `kind: 'relay'`, so `role: 'relay'` deployments run
|
|
365
|
+
* delivery and `role: 'api'` deployments only enqueue. The delivering process
|
|
366
|
+
* must ALSO register `webhookPlugin` (with the shared subscription store) —
|
|
367
|
+
* subscriptions resolve through `fastify.webhooks` at delivery time, which is
|
|
368
|
+
* boot-checked, not discovered on the first row.
|
|
369
|
+
*/
|
|
370
|
+
declare function createDurableWebhookModule(options: DurableWebhookModuleOptions): ReturnType<typeof createOutboxModule>;
|
|
248
371
|
//#endregion
|
|
249
|
-
export { VerifySignatureOptions, VerifyWebhookInput, VerifyWebhookResult, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WebhookSubscriptionPublic, _default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookPlugin };
|
|
372
|
+
export { DurableWebhookModuleOptions, VerifySignatureOptions, VerifyWebhookInput, VerifyWebhookResult, WEBHOOK_DELIVERY_EVENT, WebhookDeliveryError, WebhookDeliveryPayload, WebhookDeliveryPolicyOptions, WebhookDeliveryRecord, WebhookDeliveryTransportOptions, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WebhookSubscriptionPublic, createDurableWebhookModule, createWebhookDeliveryTransport, _default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookDeliveryFailurePolicy, webhookPlugin };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as declareRuntimeCapability } from "../runtimeCapabilities-Dm88DaTW.mjs";
|
|
3
|
+
import { i as exponentialBackoff, t as createOutboxModule } from "../outbox-module-CXB79bvt.mjs";
|
|
2
4
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
5
|
import fp from "fastify-plugin";
|
|
6
|
+
import { createChildEvent } from "@classytic/primitives/events";
|
|
4
7
|
//#region src/integrations/webhooks.ts
|
|
5
8
|
/**
|
|
6
9
|
* @classytic/arc — Webhook Outbound Integration
|
|
@@ -233,6 +236,11 @@ var MemoryWebhookStore = class {
|
|
|
233
236
|
};
|
|
234
237
|
const webhookPlugin = async (fastify, opts = {}) => {
|
|
235
238
|
const store = opts.store ?? new MemoryWebhookStore();
|
|
239
|
+
declareRuntimeCapability(fastify, {
|
|
240
|
+
subsystem: "webhooks.store",
|
|
241
|
+
durability: opts.store ? "shared" : "memory",
|
|
242
|
+
detail: "default MemoryWebhookStore: subscriptions + delivery log are replica-local"
|
|
243
|
+
});
|
|
236
244
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
237
245
|
const timeout = opts.timeout ?? 1e4;
|
|
238
246
|
const maxLogEntries = opts.maxLogEntries ?? 1e3;
|
|
@@ -241,6 +249,8 @@ const webhookPlugin = async (fastify, opts = {}) => {
|
|
|
241
249
|
const retryAttempts = opts.retry?.attempts ?? 1;
|
|
242
250
|
const retryBackoffMs = opts.retry?.backoffMs ?? 1e3;
|
|
243
251
|
if (!Number.isInteger(retryAttempts) || retryAttempts < 1) throw new Error(`webhookPlugin: retry.attempts must be an integer >= 1 (got ${String(opts.retry?.attempts)})`);
|
|
252
|
+
if (opts.durable && opts.retry) throw new Error("webhookPlugin: `durable` and `retry` are mutually exclusive — durable delivery retries via the relay's failure policy (webhookDeliveryFailurePolicy), not in-process.");
|
|
253
|
+
const durableStore = opts.durable?.store;
|
|
244
254
|
let subscriptions = [];
|
|
245
255
|
const log = [];
|
|
246
256
|
subscriptions = await store.getAll();
|
|
@@ -261,6 +271,21 @@ const webhookPlugin = async (fastify, opts = {}) => {
|
|
|
261
271
|
async function dispatchEvent(event) {
|
|
262
272
|
const matching = subscriptions.filter((s) => matchesPattern(s.events, event.type));
|
|
263
273
|
if (matching.length === 0) return;
|
|
274
|
+
if (durableStore) {
|
|
275
|
+
for (const sub of matching) {
|
|
276
|
+
const deliveryId = `${event.meta.id}:wh:${sub.id}`;
|
|
277
|
+
const delivery = createChildEvent(event, WEBHOOK_DELIVERY_EVENT, {
|
|
278
|
+
subscriptionId: sub.id,
|
|
279
|
+
event: {
|
|
280
|
+
type: event.type,
|
|
281
|
+
payload: event.payload,
|
|
282
|
+
meta: event.meta
|
|
283
|
+
}
|
|
284
|
+
}, { id: deliveryId });
|
|
285
|
+
await durableStore.save(delivery, { dedupeKey: deliveryId });
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
264
289
|
const body = JSON.stringify({
|
|
265
290
|
type: event.type,
|
|
266
291
|
payload: event.payload,
|
|
@@ -361,5 +386,158 @@ var webhooks_default = fp(webhookPlugin, {
|
|
|
361
386
|
fastify: "5.x",
|
|
362
387
|
dependencies: ["arc-events"]
|
|
363
388
|
});
|
|
389
|
+
/** Event type carried by durable webhook delivery rows. */
|
|
390
|
+
const WEBHOOK_DELIVERY_EVENT = "arc.webhook.delivery";
|
|
391
|
+
/**
|
|
392
|
+
* A failed webhook POST, classified for the failure policy: `transient`
|
|
393
|
+
* failures (network error, timeout, 429, 5xx) are worth retrying; the rest
|
|
394
|
+
* (4xx rejection, 3xx under `redirect: "manual"`, URL-policy rejection,
|
|
395
|
+
* malformed row) are final for this payload and dead-letter immediately.
|
|
396
|
+
*/
|
|
397
|
+
var WebhookDeliveryError = class extends Error {
|
|
398
|
+
status;
|
|
399
|
+
transient;
|
|
400
|
+
constructor(message, options) {
|
|
401
|
+
super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
|
|
402
|
+
this.name = "WebhookDeliveryError";
|
|
403
|
+
if (options.status !== void 0) this.status = options.status;
|
|
404
|
+
this.transient = options.transient;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* Publish-only `EventTransport` whose `publish()` IS the webhook POST —
|
|
409
|
+
* plugged into an outbox relay, delivery inherits the relay's guarantees.
|
|
410
|
+
* Throws {@link WebhookDeliveryError} on failure so the relay observes it
|
|
411
|
+
* (fail → policy → retryAt/deadLetter); returns normally on 2xx and on
|
|
412
|
+
* unregistered subscriptions (acknowledge).
|
|
413
|
+
*
|
|
414
|
+
* The wire contract is IDENTICAL to inline dispatch: v1 signing, and
|
|
415
|
+
* `x-arc-webhook-delivery` carries the ORIGINAL event's `meta.id` — stable
|
|
416
|
+
* across retries, so receiver-side dedup is unchanged.
|
|
417
|
+
*/
|
|
418
|
+
function createWebhookDeliveryTransport(options) {
|
|
419
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
420
|
+
const timeout = options.timeout ?? 1e4;
|
|
421
|
+
return {
|
|
422
|
+
name: "webhook-delivery",
|
|
423
|
+
async publish(event) {
|
|
424
|
+
if (event.type !== "arc.webhook.delivery") throw new WebhookDeliveryError(`webhook-delivery transport received event type '${event.type}' — this store must contain only ${WEBHOOK_DELIVERY_EVENT} rows (wiring bug: wrong store or transport)`, { transient: false });
|
|
425
|
+
const payload = event.payload;
|
|
426
|
+
if (!payload?.subscriptionId || !payload.event?.meta?.id) throw new WebhookDeliveryError("webhook-delivery row is malformed: payload.subscriptionId and payload.event required", { transient: false });
|
|
427
|
+
const sub = await options.getSubscription(payload.subscriptionId);
|
|
428
|
+
if (!sub) return;
|
|
429
|
+
let url;
|
|
430
|
+
try {
|
|
431
|
+
url = new URL(sub.url);
|
|
432
|
+
if (options.validateUrl) await options.validateUrl(url, sub);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
throw new WebhookDeliveryError(`webhook URL rejected for subscription '${sub.id}': ${err instanceof Error ? err.message : String(err)}`, {
|
|
435
|
+
transient: false,
|
|
436
|
+
cause: err
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
const body = JSON.stringify(payload.event);
|
|
440
|
+
const timestamp = Date.now();
|
|
441
|
+
const v1Signature = signWebhook(body, sub.secret, {
|
|
442
|
+
timestamp,
|
|
443
|
+
deliveryId: payload.event.meta.id
|
|
444
|
+
});
|
|
445
|
+
const ac = new AbortController();
|
|
446
|
+
const timer = setTimeout(() => ac.abort(), timeout);
|
|
447
|
+
let response;
|
|
448
|
+
try {
|
|
449
|
+
response = await fetchFn(sub.url, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: {
|
|
452
|
+
"content-type": "application/json",
|
|
453
|
+
"x-arc-webhook-signature": v1Signature,
|
|
454
|
+
"x-arc-webhook-timestamp": String(timestamp),
|
|
455
|
+
"x-arc-webhook-delivery": payload.event.meta.id
|
|
456
|
+
},
|
|
457
|
+
body,
|
|
458
|
+
signal: ac.signal,
|
|
459
|
+
...options.validateUrl ? { redirect: "manual" } : {}
|
|
460
|
+
});
|
|
461
|
+
} catch (err) {
|
|
462
|
+
throw new WebhookDeliveryError(`webhook POST to subscription '${sub.id}' failed: ${err instanceof Error ? err.message : String(err)}`, {
|
|
463
|
+
transient: true,
|
|
464
|
+
cause: err
|
|
465
|
+
});
|
|
466
|
+
} finally {
|
|
467
|
+
clearTimeout(timer);
|
|
468
|
+
}
|
|
469
|
+
if (!response.ok) throw new WebhookDeliveryError(`webhook POST to subscription '${sub.id}' returned ${response.status}`, {
|
|
470
|
+
status: response.status,
|
|
471
|
+
transient: response.status === 429 || response.status >= 500
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Default failure policy for durable webhook delivery: permanent failures
|
|
478
|
+
* ({@link WebhookDeliveryError} with `transient: false`) dead-letter on the
|
|
479
|
+
* first attempt — repeating a rejected payload can't succeed; transient ones
|
|
480
|
+
* retry on exponential backoff until `maxAttempts`, then dead-letter.
|
|
481
|
+
*/
|
|
482
|
+
function webhookDeliveryFailurePolicy(options = {}) {
|
|
483
|
+
const maxAttempts = options.maxAttempts ?? 8;
|
|
484
|
+
const baseMs = options.baseMs ?? 5e3;
|
|
485
|
+
const maxMs = options.maxMs ?? 9e5;
|
|
486
|
+
return ({ error, attempts }) => {
|
|
487
|
+
if (error instanceof WebhookDeliveryError && !error.transient) return { deadLetter: true };
|
|
488
|
+
if (attempts >= maxAttempts) return { deadLetter: true };
|
|
489
|
+
return { retryAt: exponentialBackoff({
|
|
490
|
+
attempt: attempts,
|
|
491
|
+
baseMs,
|
|
492
|
+
maxMs
|
|
493
|
+
}) };
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Durable webhook delivery as a module: the outbox relay machinery pointed at
|
|
498
|
+
* webhook POSTs. Pair with `webhookPlugin`'s `durable` option sharing the
|
|
499
|
+
* same store:
|
|
500
|
+
*
|
|
501
|
+
* ```ts
|
|
502
|
+
* const store = repositoryAsOutboxStore(deliveryRepo);
|
|
503
|
+
* createApp({
|
|
504
|
+
* modules: [createDurableWebhookModule({ store })],
|
|
505
|
+
* plugins: async (f) => {
|
|
506
|
+
* await f.register(webhookPlugin, { durable: { store } });
|
|
507
|
+
* },
|
|
508
|
+
* });
|
|
509
|
+
* ```
|
|
510
|
+
*
|
|
511
|
+
* The relay arm is `kind: 'relay'`, so `role: 'relay'` deployments run
|
|
512
|
+
* delivery and `role: 'api'` deployments only enqueue. The delivering process
|
|
513
|
+
* must ALSO register `webhookPlugin` (with the shared subscription store) —
|
|
514
|
+
* subscriptions resolve through `fastify.webhooks` at delivery time, which is
|
|
515
|
+
* boot-checked, not discovered on the first row.
|
|
516
|
+
*/
|
|
517
|
+
function createDurableWebhookModule(options) {
|
|
518
|
+
const { name, delivery, failurePolicy, fetch, timeout, validateUrl, ...outboxOptions } = options;
|
|
519
|
+
return createOutboxModule({
|
|
520
|
+
...outboxOptions,
|
|
521
|
+
name: name ?? "webhook-delivery",
|
|
522
|
+
transport: (fastify) => {
|
|
523
|
+
if (!fastify.hasDecorator("webhooks")) throw new Error(`Durable webhook module "${name ?? "webhook-delivery"}" requires webhookPlugin — subscriptions resolve through fastify.webhooks at delivery time. Register it in plugins() (with the SHARED subscription store in multi-process topologies).`);
|
|
524
|
+
return createWebhookDeliveryTransport({
|
|
525
|
+
getSubscription: (id) => {
|
|
526
|
+
const pub = fastify.webhooks.list().find((s) => s.id === id);
|
|
527
|
+
if (!pub) return void 0;
|
|
528
|
+
const secret = fastify.webhooks.getSecret(id);
|
|
529
|
+
return secret === void 0 ? void 0 : {
|
|
530
|
+
...pub,
|
|
531
|
+
secret
|
|
532
|
+
};
|
|
533
|
+
},
|
|
534
|
+
...fetch !== void 0 ? { fetch } : {},
|
|
535
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
536
|
+
...validateUrl !== void 0 ? { validateUrl } : {}
|
|
537
|
+
});
|
|
538
|
+
},
|
|
539
|
+
failurePolicy: failurePolicy ?? webhookDeliveryFailurePolicy(delivery)
|
|
540
|
+
});
|
|
541
|
+
}
|
|
364
542
|
//#endregion
|
|
365
|
-
export { webhooks_default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookPlugin };
|
|
543
|
+
export { WEBHOOK_DELIVERY_EVENT, WebhookDeliveryError, createDurableWebhookModule, createWebhookDeliveryTransport, webhooks_default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookDeliveryFailurePolicy, webhookPlugin };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import sjson from "secure-json-parse";
|
|
4
4
|
import fp from "fastify-plugin";
|
|
@@ -334,19 +334,21 @@ var MemoryPushRefStore = class {
|
|
|
334
334
|
}
|
|
335
335
|
async claim(args) {
|
|
336
336
|
const existing = this.entries.get(args.pushRef);
|
|
337
|
-
if (existing)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
337
|
+
if (existing) {
|
|
338
|
+
if (!existing.active && existing.expiresAt <= args.now) this.entries.delete(args.pushRef);
|
|
339
|
+
else {
|
|
340
|
+
if (existing.principal !== args.principal) return { outcome: "rejected" };
|
|
341
|
+
const supersededActive = existing.active === true;
|
|
342
|
+
existing.active = true;
|
|
343
|
+
existing.expiresAt = args.now + args.ttlMs;
|
|
344
|
+
existing.generation = (existing.generation ?? 0) + 1;
|
|
345
|
+
this.entries.set(args.pushRef, existing);
|
|
346
|
+
return {
|
|
347
|
+
outcome: "resumed",
|
|
348
|
+
entry: existing,
|
|
349
|
+
supersededActive
|
|
350
|
+
};
|
|
351
|
+
}
|
|
350
352
|
}
|
|
351
353
|
if (this.entries.size >= args.maxEntries && !this.evictOldestInactive()) return { outcome: "rejected" };
|
|
352
354
|
const fresh = args.mintFresh();
|
|
@@ -1053,9 +1055,7 @@ async function handleConnection(ctx, socket, request) {
|
|
|
1053
1055
|
}));
|
|
1054
1056
|
break;
|
|
1055
1057
|
}
|
|
1056
|
-
default:
|
|
1057
|
-
await options.onMessage?.(client, msg);
|
|
1058
|
-
break;
|
|
1058
|
+
default: await options.onMessage?.(client, msg);
|
|
1059
1059
|
}
|
|
1060
1060
|
});
|
|
1061
1061
|
socket.on("close", async () => {
|
|
@@ -1137,10 +1137,12 @@ function registerStatsRoute(fastify, rooms, path, expose) {
|
|
|
1137
1137
|
fastify.get(`${path}/stats`, async () => rooms.getStats());
|
|
1138
1138
|
return;
|
|
1139
1139
|
}
|
|
1140
|
-
if (expose === "authenticated")
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1140
|
+
if (expose === "authenticated") {
|
|
1141
|
+
if (fastify.hasDecorator("authenticate")) {
|
|
1142
|
+
const authenticate = fastify.authenticate;
|
|
1143
|
+
fastify.get(`${path}/stats`, { preHandler: authenticate }, async () => rooms.getStats());
|
|
1144
|
+
} else fastify.log.warn("arc-websocket: exposeStats is \"authenticated\" but fastify.authenticate is not registered — stats endpoint skipped");
|
|
1145
|
+
}
|
|
1144
1146
|
}
|
|
1145
1147
|
//#endregion
|
|
1146
1148
|
//#region src/integrations/websocket/outbound-truncate.ts
|
|
@@ -1164,7 +1166,7 @@ function registerStatsRoute(fastify, rooms, path, expose) {
|
|
|
1164
1166
|
* instance, eliminating module state.
|
|
1165
1167
|
*/
|
|
1166
1168
|
/** 5 MiB default — same as n8n's `relayViaPubSub` ceiling. */
|
|
1167
|
-
const DEFAULT_MAX_OUTBOUND_BYTES =
|
|
1169
|
+
const DEFAULT_MAX_OUTBOUND_BYTES = 5242880;
|
|
1168
1170
|
/**
|
|
1169
1171
|
* Returns either the original payload (when within `max`) or a
|
|
1170
1172
|
* placeholder JSON string (when oversize). The placeholder carries the
|
|
@@ -1207,7 +1209,7 @@ function createTruncator(max = DEFAULT_MAX_OUTBOUND_BYTES) {
|
|
|
1207
1209
|
* other instances fan out to their local sockets.
|
|
1208
1210
|
*/
|
|
1209
1211
|
/** Drop fan-out to a client whose outbound buffer exceeds this threshold. */
|
|
1210
|
-
const WS_BACKPRESSURE_LIMIT =
|
|
1212
|
+
const WS_BACKPRESSURE_LIMIT = 262144;
|
|
1211
1213
|
var RoomManager = class {
|
|
1212
1214
|
rooms = /* @__PURE__ */ new Map();
|
|
1213
1215
|
clients = /* @__PURE__ */ new Map();
|
|
@@ -1426,18 +1428,19 @@ var RoomManager = class {
|
|
|
1426
1428
|
if (client.socket.readyState !== 1) return false;
|
|
1427
1429
|
const queue = client.queue;
|
|
1428
1430
|
if (queue) {
|
|
1429
|
-
if (cls === "critical")
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1431
|
+
if (cls === "critical") {
|
|
1432
|
+
if (client.envelope) try {
|
|
1433
|
+
const parsed = JSON.parse(message);
|
|
1434
|
+
queue.send(client.envelope.wrap(parsed));
|
|
1435
|
+
if (this.registry) safeAsync(this.registry.persist(client.pushRef, client.generation), this.logger, "registry.persist", {
|
|
1436
|
+
pushRef: client.pushRef,
|
|
1437
|
+
generation: client.generation
|
|
1438
|
+
});
|
|
1439
|
+
} catch {
|
|
1440
|
+
queue.send(message);
|
|
1441
|
+
}
|
|
1442
|
+
else queue.send(message);
|
|
1443
|
+
} else queue.sendRealtime(message, coalesceKey);
|
|
1441
1444
|
return true;
|
|
1442
1445
|
}
|
|
1443
1446
|
if ((client.socket.bufferedAmount ?? 0) > WS_BACKPRESSURE_LIMIT) return false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as ArcError } from "./errors-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as ArcError } from "./errors-Cd_yy7aW.mjs";
|
|
3
3
|
import sjson from "secure-json-parse";
|
|
4
4
|
//#region src/utils/jsonBody.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
|
|
3
3
|
import { arcLog } from "./logger/index.mjs";
|
|
4
4
|
import { readdir } from "node:fs/promises";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -61,7 +61,8 @@ var loadResources_exports = /* @__PURE__ */ __exportAll({ loadResources: () => l
|
|
|
61
61
|
*/
|
|
62
62
|
async function loadResources(dir, options = {}) {
|
|
63
63
|
const { suffix = ".resource", recursive = true, exclude, include, filter } = options;
|
|
64
|
-
const
|
|
64
|
+
const resolvedDir = dir.startsWith("file://") ? dirname(fileURLToPath(dir)) : dir;
|
|
65
|
+
const absDir = resolve(resolvedDir);
|
|
65
66
|
const discovered = await collectFiles(absDir, new RegExp(`${escapeRegex(suffix)}\\.(ts|js|mts|mjs)$`), recursive);
|
|
66
67
|
discovered.sort();
|
|
67
68
|
const files = filter ? discovered.filter((f) => filter(f)) : discovered;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as ResourceRegistry } from "./ResourceRegistry-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as ResourceRegistry } from "./ResourceRegistry-DoALUlOO.mjs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
//#region src/cli/utils/loadResourcesFromEntry.ts
|
package/dist/logger/index.mjs
CHANGED