@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,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
|
/**
|
|
@@ -367,12 +398,13 @@ function buildScope(auth) {
|
|
|
367
398
|
*/
|
|
368
399
|
async function evaluatePermission(check, session, resource, action, input) {
|
|
369
400
|
if (!check) return null;
|
|
401
|
+
const user = session ? {
|
|
402
|
+
id: session.userId,
|
|
403
|
+
_id: session.userId,
|
|
404
|
+
...session
|
|
405
|
+
} : null;
|
|
370
406
|
return evaluatePermissionDecision(check, {
|
|
371
|
-
user
|
|
372
|
-
id: session.userId,
|
|
373
|
-
_id: session.userId,
|
|
374
|
-
...session
|
|
375
|
-
} : null,
|
|
407
|
+
user,
|
|
376
408
|
scope: buildScope(session),
|
|
377
409
|
resource,
|
|
378
410
|
action,
|
|
@@ -465,13 +497,14 @@ function toCallToolSuccess(value) {
|
|
|
465
497
|
*/
|
|
466
498
|
function toCallToolError(input) {
|
|
467
499
|
let contract;
|
|
468
|
-
if (input instanceof Error)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
500
|
+
if (input instanceof Error) {
|
|
501
|
+
if (isArcError(input) || isHttpError(input)) contract = toErrorContract(input);
|
|
502
|
+
else contract = {
|
|
503
|
+
code: "arc.internal_error",
|
|
504
|
+
message: input.message || "Internal Server Error",
|
|
505
|
+
status: 500
|
|
506
|
+
};
|
|
507
|
+
} else contract = {
|
|
475
508
|
code: input.code,
|
|
476
509
|
message: input.message,
|
|
477
510
|
...input.status !== void 0 ? { status: input.status } : {},
|
|
@@ -588,7 +621,7 @@ function invocationFingerprint(resourceName, actionName, input, session) {
|
|
|
588
621
|
return `mcp:${resourceName}:${actionName}:${createHash("sha256").update(JSON.stringify(sortKeys(input))).digest("hex").substring(0, 16)}:u=${session?.userId ?? session?.clientId ?? "anon"}`;
|
|
589
622
|
}
|
|
590
623
|
/** TTLs matching the HTTP idempotency plugin's defaults. */
|
|
591
|
-
const MCP_IDEMPOTENCY_TTL_MS =
|
|
624
|
+
const MCP_IDEMPOTENCY_TTL_MS = 864e5;
|
|
592
625
|
const MCP_IDEMPOTENCY_LOCK_MS = 1e4;
|
|
593
626
|
/**
|
|
594
627
|
* Invoke a controller method through the MCP synthetic-context path.
|
|
@@ -847,9 +880,12 @@ function resolveCrudDescription(override, meta) {
|
|
|
847
880
|
* MCP agent the same field-projection it already has on list.
|
|
848
881
|
*/
|
|
849
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})`);
|
|
850
887
|
const PAGINATION_SHAPE = {
|
|
851
888
|
page: z.number().int().min(1).optional().describe("Page number (1-based). Ignored when `cursor` is set."),
|
|
852
|
-
limit: z.number().int().min(1).max(100).optional().describe("Items per page (max 100)"),
|
|
853
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)."),
|
|
854
890
|
sort: z.string().optional().describe("Sort field, prefix with - for descending"),
|
|
855
891
|
search: z.string().optional().describe("Full-text search query"),
|
|
@@ -937,9 +973,12 @@ function opDescription(op, fieldName) {
|
|
|
937
973
|
}
|
|
938
974
|
/** Build list/query shape with filterable fields, operators, and pagination */
|
|
939
975
|
function buildListShape(fieldRules, options) {
|
|
940
|
-
const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators } = options;
|
|
976
|
+
const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators, maxLimit = DEFAULT_MCP_MAX_LIMIT } = options;
|
|
941
977
|
const allHidden = /* @__PURE__ */ new Set([...hiddenFields, ...extraHideFields]);
|
|
942
|
-
const shape = {
|
|
978
|
+
const shape = {
|
|
979
|
+
...PAGINATION_SHAPE,
|
|
980
|
+
limit: limitField(maxLimit)
|
|
981
|
+
};
|
|
943
982
|
shape._count = z.boolean().optional().describe("Return only { count } of records matching the filters — no documents fetched. Cheapest way to answer 'how many'.");
|
|
944
983
|
shape._exists = z.boolean().optional().describe("Return only whether ANY record matches the filters (boolean existence check).");
|
|
945
984
|
shape._distinct = z.string().optional().describe("Return the distinct values of this field across records matching the filters.");
|
|
@@ -1434,7 +1473,6 @@ function jsonSchemaPropertyToZod(prop) {
|
|
|
1434
1473
|
case "array": return applyDescription(arrayToZod(prop), prop);
|
|
1435
1474
|
case "object": return applyDescription(objectToZod(prop), prop);
|
|
1436
1475
|
case "null": continue;
|
|
1437
|
-
default: break;
|
|
1438
1476
|
}
|
|
1439
1477
|
return applyDescription(z.unknown(), prop);
|
|
1440
1478
|
}
|
|
@@ -1825,6 +1863,7 @@ function resourceToTools(resource, config = {}) {
|
|
|
1825
1863
|
const filterableFields = resource.schemaOptions?.filterableFields ?? resource.queryParser?.allowedFilterFields;
|
|
1826
1864
|
const sortableFields = resource.queryParser?.allowedSortFields;
|
|
1827
1865
|
const allowedOperators = resource.queryParser?.allowedOperators;
|
|
1866
|
+
const maxLimit = resource.queryParser?.maxLimit;
|
|
1828
1867
|
const hasSoftDelete = resource._appliedPresets?.includes("softDelete") ?? false;
|
|
1829
1868
|
const tools = [];
|
|
1830
1869
|
const prefix = config.toolNamePrefix;
|
|
@@ -1845,6 +1884,7 @@ function resourceToTools(resource, config = {}) {
|
|
|
1845
1884
|
extraHideFields: config.hideFields,
|
|
1846
1885
|
filterableFields,
|
|
1847
1886
|
allowedOperators,
|
|
1887
|
+
maxLimit,
|
|
1848
1888
|
adapterBodies
|
|
1849
1889
|
});
|
|
1850
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.");
|
|
@@ -2061,7 +2101,7 @@ function getOps(r, override) {
|
|
|
2061
2101
|
}
|
|
2062
2102
|
//#endregion
|
|
2063
2103
|
//#region src/integrations/mcp/sessionCache.ts
|
|
2064
|
-
const DEFAULT_TTL_MS =
|
|
2104
|
+
const DEFAULT_TTL_MS = 18e5;
|
|
2065
2105
|
const DEFAULT_MAX_SESSIONS = 1e3;
|
|
2066
2106
|
var McpSessionCache = class {
|
|
2067
2107
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -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
|
/**
|
|
@@ -26,11 +26,11 @@ var MemoryCacheStore = class {
|
|
|
26
26
|
constructor(options = {}) {
|
|
27
27
|
this.defaultTtlSeconds = options.defaultTtlSeconds ?? 60;
|
|
28
28
|
this.maxEntries = clamp(options.maxEntries ?? 1e3, 1, 1e5);
|
|
29
|
-
this.maxEntryBytes = clamp(options.maxEntryBytes ??
|
|
30
|
-
this.maxMemoryBytes = options.maxMemoryBytes ??
|
|
29
|
+
this.maxEntryBytes = clamp(options.maxEntryBytes ?? 262144, 1024, 10485760);
|
|
30
|
+
this.maxMemoryBytes = options.maxMemoryBytes ?? 52428800;
|
|
31
31
|
this.evictionWatermark = clamp(options.evictionWatermark ?? .9, .5, 1);
|
|
32
32
|
this.logger = options.logger ?? console;
|
|
33
|
-
const cleanupIntervalMs = clamp(options.cleanupIntervalMs ?? 3e4, 1e3,
|
|
33
|
+
const cleanupIntervalMs = clamp(options.cleanupIntervalMs ?? 3e4, 1e3, 6e5);
|
|
34
34
|
this.cleanupTimer = setInterval(() => this.cleanupExpired(), cleanupIntervalMs);
|
|
35
35
|
if (this.cleanupTimer.unref) this.cleanupTimer.unref();
|
|
36
36
|
}
|
|
@@ -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
|
/**
|
|
@@ -88,7 +88,7 @@ var MongoMigrationStore = class {
|
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
90
|
const MIGRATION_LOCK_NAME = "arc:migrations";
|
|
91
|
-
const DEFAULT_LOCK_LEASE_MS =
|
|
91
|
+
const DEFAULT_LOCK_LEASE_MS = 6e5;
|
|
92
92
|
/**
|
|
93
93
|
* Source checksum for drift detection — SHA-256 over the migration's
|
|
94
94
|
* `up`/`down` source. Stable for a given build; a transpile-target change
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
2
|
import sjson from "secure-json-parse";
|
|
3
3
|
//#region src/middleware/multipartBody.ts
|
|
4
|
-
const DEFAULT_MAX_FILE_SIZE =
|
|
4
|
+
const DEFAULT_MAX_FILE_SIZE = 10485760;
|
|
5
5
|
const DEFAULT_MAX_FILES = 5;
|
|
6
|
-
const DEFAULT_MAX_TOTAL_BYTES =
|
|
6
|
+
const DEFAULT_MAX_TOTAL_BYTES = 26214400;
|
|
7
7
|
const DEFAULT_FILES_KEY = "_files";
|
|
8
8
|
/**
|
|
9
9
|
* Build a matcher for MIME allow-lists that supports exact (e.g. `image/png`),
|
|
@@ -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
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
import { l as PermissionCheck } from "../base-Csjc2oF-.mjs";
|
|
3
|
+
import { t as ArcModule } from "../types-BcTumAz_.mjs";
|
|
4
|
+
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
5
|
+
//#region src/outbox-admin/module.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A store that can answer an operator's questions.
|
|
8
|
+
*
|
|
9
|
+
* Only the four reads this surface actually performs are required — `fail` and
|
|
10
|
+
* `purge` belong to the relay and retention, not here.
|
|
11
|
+
*/
|
|
12
|
+
type OutboxAdminStore = OutboxStore & Required<Pick<OutboxStore, "getDeadLettered" | "requeue" | "countByStatus" | "oldestPendingAgeMs">>;
|
|
13
|
+
interface OutboxAdminPermissions {
|
|
14
|
+
/**
|
|
15
|
+
* Reading health and the dead-letter list.
|
|
16
|
+
*
|
|
17
|
+
* MUST be platform-only — see `allowUnverifiedOperatorPermission`.
|
|
18
|
+
*/
|
|
19
|
+
view: PermissionCheck;
|
|
20
|
+
/**
|
|
21
|
+
* Replaying a dead-lettered event. Defaults to `view` when omitted — but
|
|
22
|
+
* replay RE-DELIVERS an event that already failed five times, so a
|
|
23
|
+
* deployment that separates read access from operational action should gate
|
|
24
|
+
* it distinctly. Also platform-only.
|
|
25
|
+
*/
|
|
26
|
+
replay?: PermissionCheck;
|
|
27
|
+
}
|
|
28
|
+
interface OutboxAdminModuleDeps {
|
|
29
|
+
store: OutboxAdminStore;
|
|
30
|
+
permissions: OutboxAdminPermissions;
|
|
31
|
+
/** Route prefix. Default `/admin/outbox`. */
|
|
32
|
+
prefix?: string;
|
|
33
|
+
/** Module name. Default `outbox-admin`. */
|
|
34
|
+
moduleName?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Name the outbox module was composed under, used to resolve the relay and
|
|
37
|
+
* declared as a `dependsOn` edge. Default `outbox`.
|
|
38
|
+
*/
|
|
39
|
+
outboxModuleName?: string;
|
|
40
|
+
/** Resource names this module supersedes. */
|
|
41
|
+
owns?: readonly string[];
|
|
42
|
+
/** Cap on `?limit`. Default 500 — a dead-letter list is for triage, not export. */
|
|
43
|
+
maxDeadLetterLimit?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Accept gates arc cannot prove are platform-only.
|
|
46
|
+
*
|
|
47
|
+
* By default both gates must carry `_platformOnly` (i.e. come from
|
|
48
|
+
* `requirePlatformRole`), and boot FAILS otherwise. The reason is the same
|
|
49
|
+
* one that hardened `integrations/jobs` in 2.31: these routes are global by
|
|
50
|
+
* construction. `getDeadLettered(limit)` takes no filter and an outbox row
|
|
51
|
+
* carries no tenant identity, so there is nothing to scope by. A gate that
|
|
52
|
+
* grants per organization therefore READS as scoped while serving everyone —
|
|
53
|
+
* `requireOrgRole('manager')` would let org A's manager read org B's failed
|
|
54
|
+
* events. Neither returns a policy, so no per-request check can tell a real
|
|
55
|
+
* operator gate from an org one.
|
|
56
|
+
*
|
|
57
|
+
* Set this only for a custom check you have verified consults platform
|
|
58
|
+
* identity alone.
|
|
59
|
+
*/
|
|
60
|
+
allowUnverifiedOperatorPermission?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Include each dead-lettered event's domain PAYLOAD in the listing.
|
|
63
|
+
* Default `false`.
|
|
64
|
+
*
|
|
65
|
+
* The payload is the original event body — in an ERP that is order totals,
|
|
66
|
+
* customer records and payment references. Triage needs the id, the event
|
|
67
|
+
* name, the attempt count and why it failed; it does not need the contents.
|
|
68
|
+
* Opt in only where the surface is genuinely platform-operator-only and you
|
|
69
|
+
* accept that the response carries business data.
|
|
70
|
+
*/
|
|
71
|
+
exposePayload?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Include the failure STACK TRACE alongside the error message.
|
|
74
|
+
* Default `false` — a stack names internal paths and framework versions.
|
|
75
|
+
*/
|
|
76
|
+
exposeStack?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Pending-event age (ms) past which the outbox reports unhealthy even with an
|
|
79
|
+
* empty dead-letter queue. Default 5 minutes.
|
|
80
|
+
*/
|
|
81
|
+
relayLagUnhealthyMs?: number;
|
|
82
|
+
}
|
|
83
|
+
declare function createOutboxAdminModule(deps: OutboxAdminModuleDeps): ArcModule<void>;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { type OutboxAdminModuleDeps, type OutboxAdminPermissions, type OutboxAdminStore, createOutboxAdminModule };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { i as NotFoundError, u as ValidationError } from "../errors-Cd_yy7aW.mjs";
|
|
3
|
+
import { t as defineResource } from "../defineResource-DR9vsUYy.mjs";
|
|
4
|
+
import { n as getModuleExports, t as defineModule } from "../module-CwKsgZXp.mjs";
|
|
5
|
+
//#region src/outbox-admin/module.ts
|
|
6
|
+
/**
|
|
7
|
+
* Strip the domain payload and the stack unless explicitly opted in.
|
|
8
|
+
*
|
|
9
|
+
* Triage is answered by WHICH event failed, HOW MANY times, WHEN and WHY. The
|
|
10
|
+
* payload answers none of those and carries the business record; the stack
|
|
11
|
+
* names internal paths. Both are off by default, on the same reasoning that
|
|
12
|
+
* made `exposeResult` / `exposeFailureReason` opt-in for jobs.
|
|
13
|
+
*/
|
|
14
|
+
function redactDeadLettered(e, opts) {
|
|
15
|
+
return {
|
|
16
|
+
event: {
|
|
17
|
+
type: e.event.type,
|
|
18
|
+
meta: e.event.meta,
|
|
19
|
+
...opts.exposePayload ? { payload: e.event.payload } : {}
|
|
20
|
+
},
|
|
21
|
+
error: {
|
|
22
|
+
message: e.error.message,
|
|
23
|
+
...e.error.code !== void 0 ? { code: e.error.code } : {},
|
|
24
|
+
...opts.exposeStack && e.error.stack !== void 0 ? { stack: e.error.stack } : {}
|
|
25
|
+
},
|
|
26
|
+
attempts: e.attempts,
|
|
27
|
+
firstFailedAt: e.firstFailedAt,
|
|
28
|
+
lastFailedAt: e.lastFailedAt,
|
|
29
|
+
...e.handlerName !== void 0 ? { handlerName: e.handlerName } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function createOutboxAdminModule(deps) {
|
|
33
|
+
const moduleName = deps.moduleName ?? "outbox-admin";
|
|
34
|
+
const outboxModuleName = deps.outboxModuleName ?? "outbox";
|
|
35
|
+
const maxLimit = deps.maxDeadLetterLimit ?? 500;
|
|
36
|
+
const view = deps.permissions.view;
|
|
37
|
+
const replay = deps.permissions.replay ?? deps.permissions.view;
|
|
38
|
+
const lagUnhealthyMs = deps.relayLagUnhealthyMs ?? 3e5;
|
|
39
|
+
if (!deps.allowUnverifiedOperatorPermission) {
|
|
40
|
+
for (const [label, gate] of [["view", view], ["replay", replay]]) if (!gate._platformOnly) throw new Error(`[arc/outbox-admin] permissions.${label} must be platform-only. These routes are global (an outbox row carries no tenant identity and getDeadLettered takes no filter), so an org-role gate would let a member of one organization read another's failed events and their payloads. Use requirePlatformRole('platform-ops') — or requireRoles([...], { includeOrgRoles: false }) with allowUnverifiedOperatorPermission: true if you have verified the check yourself.`);
|
|
41
|
+
}
|
|
42
|
+
const resource = defineResource({
|
|
43
|
+
name: moduleName,
|
|
44
|
+
displayName: "Outbox Admin",
|
|
45
|
+
tag: "Operations",
|
|
46
|
+
prefix: deps.prefix ?? "/admin/outbox",
|
|
47
|
+
customRoutesOnly: true,
|
|
48
|
+
routes: [
|
|
49
|
+
{
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/health",
|
|
52
|
+
summary: "Outbox health — pending, dead-letter and relay lag",
|
|
53
|
+
permissions: view,
|
|
54
|
+
rawHandler: async (_req, reply) => {
|
|
55
|
+
const [pending, deadLetter, relayLagMs] = await Promise.all([
|
|
56
|
+
deps.store.countByStatus("pending"),
|
|
57
|
+
deps.store.countByStatus("dead_letter"),
|
|
58
|
+
deps.store.oldestPendingAgeMs()
|
|
59
|
+
]);
|
|
60
|
+
const healthy = deadLetter === 0 && (relayLagMs === null || relayLagMs < lagUnhealthyMs);
|
|
61
|
+
return reply.send({
|
|
62
|
+
pending,
|
|
63
|
+
deadLetter,
|
|
64
|
+
relayLagMs,
|
|
65
|
+
healthy
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
method: "GET",
|
|
71
|
+
path: "/dead-letter",
|
|
72
|
+
summary: "List dead-lettered events with their errors",
|
|
73
|
+
permissions: view,
|
|
74
|
+
rawHandler: async (req, reply) => {
|
|
75
|
+
const raw = req.query?.limit;
|
|
76
|
+
const parsed = Number(raw ?? 100);
|
|
77
|
+
const limit = Math.min(Math.max(Number.isFinite(parsed) ? parsed : 100, 1), maxLimit);
|
|
78
|
+
const events = await deps.store.getDeadLettered(limit);
|
|
79
|
+
return reply.send({
|
|
80
|
+
count: events.length,
|
|
81
|
+
limit,
|
|
82
|
+
events: events.map((e) => redactDeadLettered(e, deps))
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
method: "POST",
|
|
88
|
+
path: "/dead-letter/:id/replay",
|
|
89
|
+
summary: "Requeue a dead-lettered event and drain the relay",
|
|
90
|
+
permissions: replay,
|
|
91
|
+
rawHandler: async (req, reply) => {
|
|
92
|
+
const eventId = req.params?.id;
|
|
93
|
+
if (!eventId) throw new ValidationError("event id is required");
|
|
94
|
+
if (!await deps.store.requeue(eventId)) throw new NotFoundError(`Dead-lettered event "${eventId}" — not found, or not currently dead-lettered`);
|
|
95
|
+
const { relay } = getModuleExports(req.server, outboxModuleName);
|
|
96
|
+
const relayDelivered = await relay.relay();
|
|
97
|
+
return reply.send({
|
|
98
|
+
eventId,
|
|
99
|
+
requeued: true,
|
|
100
|
+
relayDelivered,
|
|
101
|
+
note: "relayDelivered counts the entire drained batch, not this event alone"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
return defineModule({
|
|
108
|
+
name: moduleName,
|
|
109
|
+
dependsOn: [outboxModuleName],
|
|
110
|
+
...deps.owns ? { owns: [...deps.owns] } : {},
|
|
111
|
+
resources: () => [resource]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { createOutboxAdminModule };
|