@classytic/arc 2.32.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-Dk34dxhI.mjs → BaseController-CaQC5EXX.mjs} +293 -33
- package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
- package/dist/{EventTransport-BV10D0oG.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
- package/dist/{HookSystem-vimw5D7Z.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
- package/dist/{ResourceRegistry-BVr7PgA0.mjs → ResourceRegistry-DoALUlOO.mjs} +3 -2
- package/dist/{actionPermissions-Dti7ebyv.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 +39 -7
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-__T3EJDA.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
- package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
- package/dist/{buildHandler-Dn7rzUJo.mjs → buildHandler-DLE9xX-V.mjs} +4 -4
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DFJqHyFs.mjs → caching-BcGmvnHh.mjs} +2 -2
- package/dist/cleanup/index.d.mts +17 -5
- package/dist/cleanup/index.mjs +240 -7
- 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-J99LGTJI.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-BwwmNSp7.mjs → core-dxhcHhUD.mjs} +3 -3
- package/dist/{createActionRouter-DkJZWo0m.mjs → createActionRouter-DVpoA0do.mjs} +8 -8
- package/dist/{createAggregationRouter-B1exOVD8.mjs → createAggregationRouter-B_B48JZ-.mjs} +7 -7
- package/dist/{createApp-Bv0m-Q6_.mjs → createApp-CLGegqiG.mjs} +128 -24
- package/dist/{defineAggregation-gX4No1gt.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
- package/dist/{defineResource-XFPPUu2c.mjs → defineResource-DR9vsUYy.mjs} +297 -23
- 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-DNXXxgjl.mjs → elevation-B0tlY8y1.mjs} +4 -4
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-B2AZUJUj.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
- package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
- package/dist/{errors-Bh1KK5ps.mjs → errors-Cd_yy7aW.mjs} +1 -1
- package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
- package/dist/{eventPlugin-Dk_TyXeE.mjs → eventPlugin-Dar8qRaj.mjs} +65 -9
- package/dist/events/index.d.mts +5 -351
- package/dist/events/index.mjs +5 -780
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +1 -1
- 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 +7 -7
- package/dist/{fastify-augmentation-BppG5bMT.d.mts → fastify-augmentation-DVsTAy6X.d.mts} +3 -3
- package/dist/{fields-DEi65pGj.mjs → fields-CojeUkS4.mjs} +1 -1
- package/dist/{generate-CRSG0jTv.mjs → generate-DiB0nHPR.mjs} +2 -2
- package/dist/{headers-BXjQYbeC.mjs → headers-CveNO9gR.mjs} +2 -2
- package/dist/{health-5XnTa2uo.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 +91 -9
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-zq6VrjaB.d.mts → index-B-DvUzsr.d.mts} +12 -2
- package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
- package/dist/{index-DXlHwvuE.d.mts → index-Brin0ODU.d.mts} +4 -4
- package/dist/{index-ZWhLTp_s.d.mts → index-C5NnMEKF.d.mts} +1 -1
- 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-BbfP8aky.mjs → init-mdJ-v4Xp.mjs} +18 -12
- 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 +99 -97
- 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 +1 -1
- package/dist/{interface-BP4IJCBO.mjs → interface-DznJrm9R.mjs} +1 -1
- package/dist/{jsonBody-DDSR2HFl.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
- package/dist/{keys-B-HBTGXl.mjs → keys-WeKvY1dE.mjs} +1 -1
- package/dist/{loadResources-DisEAmkm.mjs → loadResources-Bj-aSsXi.mjs} +2 -2
- package/dist/{loadResourcesFromEntry-D1MJp13f.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-CpfVpKHE.mjs → mcpPlugin-aJg5TqHi.mjs} +60 -21
- package/dist/{memory-DsuWnj4T.mjs → memory-qYSB7rMQ.mjs} +2 -2
- package/dist/{metrics-DdQL8syd.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 +2 -2
- package/dist/{module-Br81am6m.mjs → module-CwKsgZXp.mjs} +1 -1
- package/dist/{multipartBody-BnkBLN7v.mjs → multipartBody-4w-S3-PU.mjs} +1 -1
- package/dist/{openapi-DIRzvxC0.mjs → openapi-BN5cQaO2.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +2 -2
- package/dist/outbox-admin/index.mjs +4 -4
- package/dist/outbox-module-CXB79bvt.mjs +839 -0
- package/dist/outbox-module-D-VFq37T.d.mts +368 -0
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-Dl_m0NCe.mjs → permissions-BAfwgBxc.mjs} +25 -6
- package/dist/{pipe-ELHCP3zj.mjs → pipe-BzzoEhHj.mjs} +2 -2
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/{pipeline-CPA-jx0M.mjs → pipeline-q69DZzgG.mjs} +6 -6
- 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-C2ZkKbsn.mjs → pluralize-B7xf8qZF.mjs} +1 -1
- package/dist/{preloadResources-CDs87fTw.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
- package/dist/{preloadResources-T1hmlTEZ.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
- 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 +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-Dntd_pUO.mjs → presets-D-h0uWtP.mjs} +4 -4
- package/dist/projection-BKM0Ui-s.mjs +42 -0
- package/dist/{queryCachePlugin-Dhps-Ldt.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +49 -7
- package/dist/{rateLimit-C7mmAGs3.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
- package/dist/{rateLimitKey-CccEFBfk.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
- package/dist/{realtime-DVvVZZFz.mjs → realtime-CTkv3O59.mjs} +9 -9
- package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
- package/dist/{registerRoute-B_QrjYZS.mjs → registerRoute-DNnd3vkz.mjs} +4 -4
- package/dist/registry/index.d.mts +3 -3
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-DqUmzA9W.mjs → registry-CLxCR5LF.mjs} +2 -2
- package/dist/{renewingLease-BpP9wwj7.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
- package/dist/{replyHelpers-DS-P0b32.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
- package/dist/{repositoryFilter-DotlxG3L.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
- package/dist/{requestContext-B6vQF-sy.mjs → requestContext-uYE35JxI.mjs} +1 -1
- package/dist/{rolldown-runtime-BhflRrBs.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
- package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
- package/dist/{schedules-C2Q3uv2d.mjs → schedules-CAcZgia0.mjs} +3 -3
- package/dist/{schemaConverter-Crjvczi_.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
- package/dist/{schemaIR-CnyKK_1j.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-afz_Ieek.mjs → scope-BaWcqBvr.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-C4KiMWUa.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
- package/dist/{sse-B9P4Y-UI.mjs → sse-Cf0jUHsh.mjs} +5 -5
- package/dist/{sseStream-B3zb_d1h.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
- package/dist/{store-helpers-DmEMHlPQ.mjs → store-helpers-B88p99Z2.mjs} +1 -1
- package/dist/{streaming-DPZdQmDz.mjs → streaming-DsT1B5zJ.mjs} +1 -1
- package/dist/{subscribe-helpers-sp5xTDVA.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 +3 -1
- package/dist/testing/index.d.mts +24 -12
- package/dist/testing/index.mjs +24 -12
- package/dist/testing/outboxStoreContract.mjs +1 -1
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/{transactionContext-BvLKvOIL.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
- package/dist/{typeGuards-B_5-hKjJ.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-CT5vu6M0.d.mts → types-BcTumAz_.d.mts} +41 -7
- package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
- package/dist/{types-DXYX7dIR.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-BNsOXzOd.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
- package/dist/utils/index.d.mts +57 -5
- package/dist/utils/index.mjs +12 -11
- package/dist/{utils-BtOha_Sp.mjs → utils-h1V_sT-t.mjs} +38 -7
- package/dist/{validate-PMCPR9El.mjs → validate-CdQlz52F.mjs} +2 -2
- package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
- package/dist/{versioning-CKv2jz_U.mjs → versioning-rZbyLVD-.mjs} +3 -3
- package/package.json +17 -16
- 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/projection-CjHhM5W1.mjs +0 -18
|
@@ -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,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";
|
|
@@ -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
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { r as evaluatePermissionDecision } from "./authorizationDecision-
|
|
3
|
-
import { p as isArcError, t as ArcError } from "./errors-
|
|
4
|
-
import { n as requireSingleHeaderValue } from "./headers-
|
|
5
|
-
import { b as isElevated, c as getOrgId, g as getUserId, x as isMember } from "./types-
|
|
6
|
-
import { t as BaseController } from "./BaseController-
|
|
7
|
-
import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-
|
|
8
|
-
import { t as executePipeline } from "./pipe-
|
|
9
|
-
import { t as buildRequestScopeProjection } from "./projection-
|
|
10
|
-
import { r as resolvePipelineSteps } from "./pipeline-
|
|
11
|
-
import { n as validateAggregations } from "./validate-
|
|
12
|
-
import { n as executeAggregation } from "./buildHandler-
|
|
13
|
-
import { t as pluralize } from "./pluralize-
|
|
14
|
-
import { t as resolveActionPermission } from "./actionPermissions-
|
|
15
|
-
import { t as createIdempotencyResult } from "./interface-
|
|
16
|
-
import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as evaluatePermissionDecision } from "./authorizationDecision-B3mP_QTy.mjs";
|
|
3
|
+
import { p as isArcError, t as ArcError } from "./errors-Cd_yy7aW.mjs";
|
|
4
|
+
import { n as requireSingleHeaderValue } from "./headers-CveNO9gR.mjs";
|
|
5
|
+
import { b as isElevated, c as getOrgId, g as getUserId, x as isMember } from "./types-CoqIRrkY.mjs";
|
|
6
|
+
import { t as BaseController } from "./BaseController-CaQC5EXX.mjs";
|
|
7
|
+
import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CojeUkS4.mjs";
|
|
8
|
+
import { t as executePipeline } from "./pipe-BzzoEhHj.mjs";
|
|
9
|
+
import { t as buildRequestScopeProjection } from "./projection-BKM0Ui-s.mjs";
|
|
10
|
+
import { r as resolvePipelineSteps } from "./pipeline-q69DZzgG.mjs";
|
|
11
|
+
import { n as validateAggregations } from "./validate-CdQlz52F.mjs";
|
|
12
|
+
import { n as executeAggregation } from "./buildHandler-DLE9xX-V.mjs";
|
|
13
|
+
import { t as pluralize } from "./pluralize-B7xf8qZF.mjs";
|
|
14
|
+
import { t as resolveActionPermission } from "./actionPermissions-CUeCmpUd.mjs";
|
|
15
|
+
import { t as createIdempotencyResult } from "./interface-DznJrm9R.mjs";
|
|
16
|
+
import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-CDvclAew.mjs";
|
|
17
17
|
import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
|
|
18
18
|
import { createHash, randomUUID } from "node:crypto";
|
|
19
19
|
import fp from "fastify-plugin";
|
|
@@ -332,18 +332,32 @@ function expandOperatorKeys(input) {
|
|
|
332
332
|
*/
|
|
333
333
|
function buildScope(auth) {
|
|
334
334
|
if (!auth) return { kind: "public" };
|
|
335
|
+
/**
|
|
336
|
+
* `context` (and `teamId`, member-only) are carried through, not dropped.
|
|
337
|
+
*
|
|
338
|
+
* A resolver could already return them — `McpAuthResult` has an index
|
|
339
|
+
* signature — and this function silently discarded both, so a resource
|
|
340
|
+
* gated by `requireScopeContext('branchId')` worked over HTTP and DENIED
|
|
341
|
+
* every MCP call, because the dimension it authorises against was never
|
|
342
|
+
* populated. Fail-closed, but it made the tool permanently unusable rather
|
|
343
|
+
* than wrong, which is why nothing caught it.
|
|
344
|
+
*/
|
|
345
|
+
const context = normalizeScopeContext(auth.context);
|
|
335
346
|
if (auth.clientId && auth.organizationId) return {
|
|
336
347
|
kind: "service",
|
|
337
348
|
clientId: auth.clientId,
|
|
338
349
|
organizationId: auth.organizationId,
|
|
339
|
-
scopes: auth.scopes
|
|
350
|
+
scopes: auth.scopes,
|
|
351
|
+
...context ? { context } : {}
|
|
340
352
|
};
|
|
341
353
|
if (auth.organizationId) return {
|
|
342
354
|
kind: "member",
|
|
343
355
|
userId: auth.userId,
|
|
344
356
|
userRoles: auth.roles ?? [],
|
|
345
357
|
organizationId: auth.organizationId,
|
|
346
|
-
orgRoles: auth.orgRoles ?? []
|
|
358
|
+
orgRoles: auth.orgRoles ?? [],
|
|
359
|
+
...typeof auth.teamId === "string" ? { teamId: auth.teamId } : {},
|
|
360
|
+
...context ? { context } : {}
|
|
347
361
|
};
|
|
348
362
|
return {
|
|
349
363
|
kind: "authenticated",
|
|
@@ -351,6 +365,23 @@ function buildScope(auth) {
|
|
|
351
365
|
userRoles: auth.roles ?? []
|
|
352
366
|
};
|
|
353
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Accept a resolver's `context` only as a string→string map.
|
|
370
|
+
*
|
|
371
|
+
* The resolver is host code reached through an index signature, so the value
|
|
372
|
+
* is `unknown`. Coercing arbitrary values (numbers, objects, null) into
|
|
373
|
+
* authorisation dimensions would make `requireScopeContext('branchId')`
|
|
374
|
+
* compare against something that was never a branch id; non-string entries are
|
|
375
|
+
* dropped rather than stringified. Returns `undefined` for an empty result so
|
|
376
|
+
* a scope reports "carries no context" rather than "carries an empty one" —
|
|
377
|
+
* different facts to a caller.
|
|
378
|
+
*/
|
|
379
|
+
function normalizeScopeContext(value) {
|
|
380
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
381
|
+
const out = {};
|
|
382
|
+
for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
|
|
383
|
+
return Object.keys(out).length > 0 ? Object.freeze(out) : void 0;
|
|
384
|
+
}
|
|
354
385
|
//#endregion
|
|
355
386
|
//#region src/integrations/mcp/tool-helpers.ts
|
|
356
387
|
/**
|
|
@@ -849,9 +880,12 @@ function resolveCrudDescription(override, meta) {
|
|
|
849
880
|
* MCP agent the same field-projection it already has on list.
|
|
850
881
|
*/
|
|
851
882
|
const PROJECTION_FIELD = z.string().optional().describe("Comma-separated field list to project (e.g. 'name,price'). Prefix with '-' to exclude (e.g. '-description').");
|
|
883
|
+
/** Default page cap, used only when the resource declares none. */
|
|
884
|
+
const DEFAULT_MCP_MAX_LIMIT = 100;
|
|
885
|
+
/** `limit` is per-resource — see `maxLimit` on the options. */
|
|
886
|
+
const limitField = (maxLimit) => z.number().int().min(1).max(maxLimit).optional().describe(`Items per page (max ${maxLimit})`);
|
|
852
887
|
const PAGINATION_SHAPE = {
|
|
853
888
|
page: z.number().int().min(1).optional().describe("Page number (1-based). Ignored when `cursor` is set."),
|
|
854
|
-
limit: z.number().int().min(1).max(100).optional().describe("Items per page (max 100)"),
|
|
855
889
|
cursor: z.string().optional().describe("Opaque keyset cursor from a previous page's `next`. Pass it to fetch the following page; when set, `page` is ignored (cursor/keyset pagination — stable under concurrent inserts)."),
|
|
856
890
|
sort: z.string().optional().describe("Sort field, prefix with - for descending"),
|
|
857
891
|
search: z.string().optional().describe("Full-text search query"),
|
|
@@ -939,9 +973,12 @@ function opDescription(op, fieldName) {
|
|
|
939
973
|
}
|
|
940
974
|
/** Build list/query shape with filterable fields, operators, and pagination */
|
|
941
975
|
function buildListShape(fieldRules, options) {
|
|
942
|
-
const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators } = options;
|
|
976
|
+
const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators, maxLimit = DEFAULT_MCP_MAX_LIMIT } = options;
|
|
943
977
|
const allHidden = /* @__PURE__ */ new Set([...hiddenFields, ...extraHideFields]);
|
|
944
|
-
const shape = {
|
|
978
|
+
const shape = {
|
|
979
|
+
...PAGINATION_SHAPE,
|
|
980
|
+
limit: limitField(maxLimit)
|
|
981
|
+
};
|
|
945
982
|
shape._count = z.boolean().optional().describe("Return only { count } of records matching the filters — no documents fetched. Cheapest way to answer 'how many'.");
|
|
946
983
|
shape._exists = z.boolean().optional().describe("Return only whether ANY record matches the filters (boolean existence check).");
|
|
947
984
|
shape._distinct = z.string().optional().describe("Return the distinct values of this field across records matching the filters.");
|
|
@@ -1826,6 +1863,7 @@ function resourceToTools(resource, config = {}) {
|
|
|
1826
1863
|
const filterableFields = resource.schemaOptions?.filterableFields ?? resource.queryParser?.allowedFilterFields;
|
|
1827
1864
|
const sortableFields = resource.queryParser?.allowedSortFields;
|
|
1828
1865
|
const allowedOperators = resource.queryParser?.allowedOperators;
|
|
1866
|
+
const maxLimit = resource.queryParser?.maxLimit;
|
|
1829
1867
|
const hasSoftDelete = resource._appliedPresets?.includes("softDelete") ?? false;
|
|
1830
1868
|
const tools = [];
|
|
1831
1869
|
const prefix = config.toolNamePrefix;
|
|
@@ -1846,6 +1884,7 @@ function resourceToTools(resource, config = {}) {
|
|
|
1846
1884
|
extraHideFields: config.hideFields,
|
|
1847
1885
|
filterableFields,
|
|
1848
1886
|
allowedOperators,
|
|
1887
|
+
maxLimit,
|
|
1849
1888
|
adapterBodies
|
|
1850
1889
|
});
|
|
1851
1890
|
if (wiring?.idempotencyStore && op !== "list" && op !== "get") inputSchema._idempotencyKey = z.string().optional().describe("Optional idempotency key — retrying with the same key and input replays the first successful result instead of re-executing.");
|
|
@@ -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
|
//#region src/cache/memory.ts
|
|
4
4
|
var memory_exports = /* @__PURE__ */ __exportAll({ MemoryCacheStore: () => MemoryCacheStore });
|
|
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 fp from "fastify-plugin";
|
|
4
4
|
//#region src/plugins/metrics.ts
|
|
5
5
|
var metrics_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-
|
|
2
|
+
import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-BcR2bFZQ.mjs";
|
|
3
3
|
import { RouteHandlerMethod } from "fastify";
|
|
4
4
|
//#region src/middleware/middleware.d.ts
|
|
5
5
|
interface NamedMiddleware {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as CRUD_OPERATIONS } from "../constants-
|
|
3
|
-
import { t as multipartBody } from "../multipartBody-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as CRUD_OPERATIONS } from "../constants-BxCoxmiN.mjs";
|
|
3
|
+
import { t as multipartBody } from "../multipartBody-4w-S3-PU.mjs";
|
|
4
4
|
//#region src/middleware/middleware.ts
|
|
5
5
|
/**
|
|
6
6
|
* Named Middleware — Priority-based, conditional middleware execution.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as startRenewingLease } from "../renewingLease-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as startRenewingLease } from "../renewingLease-BnWtbA2U.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/migrations/index.ts
|
|
5
5
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { n as getUserRoles } from "./userHelpers-
|
|
3
|
-
import { n as convertRouteSchema } from "./schemaConverter-
|
|
4
|
-
import { t as pluralize } from "./pluralize-
|
|
5
|
-
import { t as resolveActionPermission } from "./actionPermissions-
|
|
6
|
-
import { t as buildActionBodySchema } from "./createActionRouter-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as getUserRoles } from "./userHelpers-CWF5pbZD.mjs";
|
|
3
|
+
import { n as convertRouteSchema } from "./schemaConverter-XH1RZRyu.mjs";
|
|
4
|
+
import { t as pluralize } from "./pluralize-B7xf8qZF.mjs";
|
|
5
|
+
import { t as resolveActionPermission } from "./actionPermissions-CUeCmpUd.mjs";
|
|
6
|
+
import { t as buildActionBodySchema } from "./createActionRouter-DVpoA0do.mjs";
|
|
7
7
|
import fp from "fastify-plugin";
|
|
8
8
|
//#region src/docs/openapi/canonical-schemas.ts
|
|
9
9
|
/**
|