@classytic/arc 2.23.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseController-BQ8KNvWC.mjs → BaseController--M8EMzt0.mjs} +90 -21
- package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
- package/dist/actionPermissions-CAphcmUG.mjs +24 -0
- package/dist/audit/index.d.mts +32 -3
- package/dist/audit/index.mjs +7 -4
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +263 -259
- package/dist/{buildHandler-D_2ULWxZ.mjs → buildHandler-Dtwd8fdz.mjs} +1 -1
- package/dist/cache/index.d.mts +41 -3
- package/dist/cache/index.mjs +33 -4
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +2 -2
- package/dist/context/index.mjs +2 -64
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-DQ6rDTyH.mjs → core-EUNu0u07.mjs} +296 -11
- package/dist/{createActionRouter-BuOv-m4Y.mjs → createActionRouter-B6GvAuAX.mjs} +3 -2
- package/dist/{createAggregationRouter-CgJv_BU_.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-COpYKt2h.mjs → elevation-CIz1GjGm.mjs} +4 -3
- package/dist/{eventPlugin-A6EbthXR.d.mts → eventPlugin-Bg3cP2_u.d.mts} +38 -3
- package/dist/{eventPlugin-DJG2D7sy.mjs → eventPlugin-DrQ_FesK.mjs} +22 -4
- package/dist/events/index.d.mts +57 -314
- package/dist/events/index.mjs +163 -91
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +24 -2
- package/dist/factory/index.d.mts +4 -5
- package/dist/factory/index.mjs +28 -8
- package/dist/{fastify-augmentation-g_f9omSc.d.mts → fastify-augmentation-dPNBVeIj.d.mts} +25 -2
- package/dist/{generate-usRSdQmP.mjs → generate-D0wZupBg.mjs} +1 -1
- package/dist/headers-BvlQSDMB.mjs +45 -0
- package/dist/health-CB_m4S32.mjs +377 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +27 -3
- package/dist/{index-wMoWBLkn.d.mts → index-BMnbzvtO.d.mts} +3 -3
- package/dist/{integrations/jobs.d.mts → index-CG4yqMDB.d.mts} +82 -2
- package/dist/{index-BBsvQt7U.d.mts → index-DPRH0Cme.d.mts} +36 -106
- package/dist/{module-B4u10iOJ.d.mts → index-KUoxjIq1.d.mts} +263 -23
- package/dist/{index-CC5ruDGh.d.mts → index-f4PTrk3B.d.mts} +37 -5
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +5 -5
- package/dist/{init-CjO4p_q1.mjs → init-tlRSt667.mjs} +206 -208
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/jobs/index.d.mts +2 -0
- package/dist/integrations/{jobs.mjs → jobs/index.mjs} +110 -67
- package/dist/integrations/mcp/index.d.mts +4 -4
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +37 -30
- package/dist/integrations/webhooks.mjs +51 -62
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +41 -14
- package/dist/{interface-C1_WLTMK.d.mts → interface-BGZCzOGL.d.mts} +24 -31
- package/dist/{mcpPlugin-BtzIL_kM.mjs → mcpPlugin-Cb7olw0j.mjs} +11 -9
- package/dist/{memory-CLXA2VeJ.mjs → memory-CmnzXE5Z.mjs} +29 -0
- package/dist/middleware/index.d.mts +17 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/migrations/index.d.mts +94 -44
- package/dist/migrations/index.mjs +196 -44
- package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
- package/dist/{openapi-jSDx-VDv.mjs → openapi-DrLtog_B.mjs} +3 -3
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-tbxFHxjZ.mjs → permissions-BrtiBKGs.mjs} +229 -229
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/{actionPermissions-lszoJle1.mjs → pipeline-BN5G05rd.mjs} +3 -26
- package/dist/plugins/index.d.mts +6 -77
- package/dist/plugins/index.mjs +11 -95
- package/dist/plugins/response-cache.mjs +1 -0
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{index-Ba10fdKp.d.mts → preloadResources-0oe-JSSS.d.mts} +50 -50
- package/dist/{createApp-CjOeWJ9_.mjs → preloadResources-DfyngIN9.mjs} +233 -70
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +3 -3
- package/dist/presets/index.d.mts +2 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-BOe4Ugkl.mjs → presets-DwFDVKP0.mjs} +2 -2
- package/dist/{queryCachePlugin-DzqLN904.mjs → queryCachePlugin-DSGuSVLW.mjs} +39 -8
- package/dist/{queryCachePlugin-CfYWq-Ak.d.mts → queryCachePlugin-br4otiqF.d.mts} +2 -2
- package/dist/{registerRoute-CWO6mdYe.mjs → rateLimit-DrED-C4v.mjs} +2 -109
- package/dist/{realtime-I8OrfnMR.mjs → realtime-Cuw_deA-.mjs} +2 -2
- package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
- package/dist/registerRoute-v0Wb6Plt.mjs +108 -0
- package/dist/registry/index.d.mts +2 -2
- package/dist/renewingLease-3cuQH7h8.mjs +44 -0
- package/dist/{requestContext-BZKOr13H.mjs → requestContext-MrlJwECf.mjs} +2 -2
- package/dist/schedules-CjOd2MG9.mjs +113 -0
- package/dist/schemas/index.d.mts +67 -67
- package/dist/schemas/index.mjs +2 -3
- package/dist/scim/index.mjs +1 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +4 -4
- package/dist/{scope-LONQ3mfZ.mjs → scope-DfaPntpo.mjs} +4 -5
- package/dist/{sse-0u4P4-OI.mjs → sse-BQ1w0dfl.mjs} +1 -1
- package/dist/testing/index.d.mts +4 -3
- package/dist/testing/index.mjs +2 -2
- package/dist/transactionContext-PNdoyLLm.mjs +64 -0
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-bHblZ9YN.mjs → types-CSyCxl2V.mjs} +38 -7
- package/dist/{types-DH0GupI7.d.mts → types-DjC-KUCz.d.mts} +118 -20
- package/dist/{types-Csxga7d3.d.mts → types-Dmw-6Qeo.d.mts} +96 -4
- package/dist/{types-5OuqeTDX.d.mts → types-QFGc3Cjv.d.mts} +1 -1
- package/dist/usage/index.d.mts +3 -52
- package/dist/usage/index.mjs +0 -0
- package/dist/userHelpers-Cg_y3fBz.mjs +29 -0
- package/dist/utils/index.d.mts +58 -85
- package/dist/utils/index.mjs +4 -2
- package/dist/{utils-CZyeRXTh.mjs → utils-taUVD4p3.mjs} +47 -337
- package/dist/{validate-C2mGgSXg.mjs → validate-Bk44F2yT.mjs} +1 -1
- package/dist/{versioning-D6PvCwpq.d.mts → versioning-DXgY5BUm.d.mts} +11 -29
- package/dist/{versioning-Dk-71iIz.mjs → versioning-gKWyryT3.mjs} +8 -1
- package/dist/{websocket-uuWqM6UY.d.mts → websocket-_Pd0CgR0.d.mts} +15 -1
- package/package.json +526 -513
- package/skills/arc/references/api-reference.md +3 -1
- package/skills/arc/references/integrations.md +20 -11
- package/dist/QueryCache-Z1B38koh.d.mts +0 -40
- package/dist/betterAuthOpenApi-CV1lxpRX.mjs +0 -2
- package/dist/health-ScyjIVo5.mjs +0 -202
- package/dist/requestId-5BhTVzBv.mjs +0 -113
- package/dist/types-XTApa8b6.mjs +0 -24
|
@@ -1,45 +1,53 @@
|
|
|
1
1
|
import fp from "fastify-plugin";
|
|
2
|
-
//#region src/integrations/jobs.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
2
|
+
//#region src/integrations/jobs/execution.ts
|
|
3
|
+
async function executeTimedHandler(options) {
|
|
4
|
+
const { run, label, jobId, timeoutMs, cancelGraceMs = 3e4, logger } = options;
|
|
5
|
+
let released = false;
|
|
6
|
+
const releaseSlot = () => {
|
|
7
|
+
if (released) return;
|
|
8
|
+
released = true;
|
|
9
|
+
options.releaseSlot?.();
|
|
10
|
+
};
|
|
11
|
+
const abort = new AbortController();
|
|
12
|
+
const handlerPromise = (async () => run(abort.signal))();
|
|
13
|
+
handlerPromise.then(releaseSlot, releaseSlot);
|
|
14
|
+
if (!timeoutMs) return handlerPromise;
|
|
15
|
+
let timer;
|
|
16
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
17
|
+
timer = setTimeout(() => {
|
|
18
|
+
const err = /* @__PURE__ */ new Error(`Job '${label}' timed out after ${timeoutMs}ms`);
|
|
19
|
+
abort.abort(err);
|
|
20
|
+
const timedOutAt = Date.now();
|
|
21
|
+
const graceTimer = setTimeout(() => {
|
|
22
|
+
logger?.warn({
|
|
23
|
+
job: label,
|
|
24
|
+
jobId,
|
|
25
|
+
graceMs: cancelGraceMs
|
|
26
|
+
}, "timed-out job handler still running after grace period — force-releasing its maxConcurrent slot (bulkhead may now be exceeded); make the handler honor its AbortSignal");
|
|
27
|
+
releaseSlot();
|
|
28
|
+
}, cancelGraceMs);
|
|
29
|
+
graceTimer.unref?.();
|
|
30
|
+
handlerPromise.then(() => logger?.warn({
|
|
31
|
+
job: label,
|
|
32
|
+
jobId,
|
|
33
|
+
afterMs: Date.now() - timedOutAt
|
|
34
|
+
}, "timed-out job handler eventually completed (result discarded)"), (handlerErr) => logger?.warn({
|
|
35
|
+
job: label,
|
|
36
|
+
jobId,
|
|
37
|
+
afterMs: Date.now() - timedOutAt,
|
|
38
|
+
err: handlerErr
|
|
39
|
+
}, "timed-out job handler eventually failed")).finally(() => clearTimeout(graceTimer));
|
|
40
|
+
reject(err);
|
|
41
|
+
}, timeoutMs);
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
return await Promise.race([handlerPromise, timeoutPromise]);
|
|
45
|
+
} finally {
|
|
46
|
+
clearTimeout(timer);
|
|
41
47
|
}
|
|
42
|
-
}
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/integrations/jobs/repeat.ts
|
|
43
51
|
/**
|
|
44
52
|
* Whether an existing schedule matches the currently configured repeat spec.
|
|
45
53
|
*
|
|
@@ -60,11 +68,6 @@ function repeatSpecMatches(record, spec) {
|
|
|
60
68
|
* Remove repeatable schedules left behind by a previous deploy whose repeat
|
|
61
69
|
* spec no longer matches the current `JobDefinition.repeat`.
|
|
62
70
|
*
|
|
63
|
-
* BullMQ keys repeatable schedules by a hash of their repeat options, so
|
|
64
|
-
* re-adding a job with a CHANGED pattern/every/tz/endDate creates a NEW
|
|
65
|
-
* schedule while the old one keeps firing — the job double-fires after every
|
|
66
|
-
* repeat-config change. This reconciles at plugin registration:
|
|
67
|
-
*
|
|
68
71
|
* - Only schedulers whose `name` equals the registered job's name are
|
|
69
72
|
* candidates — foreign/unknown schedulers are never touched.
|
|
70
73
|
* - A scheduler matching the current spec is kept (the subsequent
|
|
@@ -102,6 +105,32 @@ async function removeStaleRepeatSchedulers(queue, jobName, spec, log) {
|
|
|
102
105
|
}, `[arc/jobs] Failed to reconcile repeatable schedules for '${jobName}' — continuing (boot-time hygiene only)`);
|
|
103
106
|
}
|
|
104
107
|
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/integrations/jobs/plugin.ts
|
|
110
|
+
var Semaphore = class {
|
|
111
|
+
available;
|
|
112
|
+
waiters = [];
|
|
113
|
+
constructor(permits) {
|
|
114
|
+
this.available = permits;
|
|
115
|
+
}
|
|
116
|
+
get isFull() {
|
|
117
|
+
return this.available === 0;
|
|
118
|
+
}
|
|
119
|
+
acquire() {
|
|
120
|
+
if (this.available > 0) {
|
|
121
|
+
this.available--;
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
this.waiters.push(resolve);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
release() {
|
|
129
|
+
const next = this.waiters.shift();
|
|
130
|
+
if (next) next();
|
|
131
|
+
else this.available++;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
105
134
|
const jobsPluginImpl = async (fastify, options) => {
|
|
106
135
|
const { connection, jobs, prefix = "/jobs", bridgeEvents = true, defaults = {} } = options;
|
|
107
136
|
let Queue;
|
|
@@ -158,11 +187,6 @@ const jobsPluginImpl = async (fastify, options) => {
|
|
|
158
187
|
const throttledSet = /* @__PURE__ */ new Set();
|
|
159
188
|
throttledJobs.set(queueName, throttledSet);
|
|
160
189
|
const worker = new Worker(queueName, async (bullJob) => {
|
|
161
|
-
const meta = {
|
|
162
|
-
jobId: bullJob.id ?? "",
|
|
163
|
-
attemptsMade: bullJob.attemptsMade ?? 0,
|
|
164
|
-
timestamp: Date.now()
|
|
165
|
-
};
|
|
166
190
|
if (semaphore) {
|
|
167
191
|
if (semaphore.isFull) throttledSet.add(bullJob.id ?? "");
|
|
168
192
|
try {
|
|
@@ -171,22 +195,23 @@ const jobsPluginImpl = async (fastify, options) => {
|
|
|
171
195
|
throttledSet.delete(bullJob.id ?? "");
|
|
172
196
|
}
|
|
173
197
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
const result = await executeTimedHandler({
|
|
199
|
+
label: queueName,
|
|
200
|
+
...bullJob.id !== void 0 ? { jobId: bullJob.id } : {},
|
|
201
|
+
...jobTimeout !== void 0 ? { timeoutMs: jobTimeout } : {},
|
|
202
|
+
...job.cancelGraceMs !== void 0 ? { cancelGraceMs: job.cancelGraceMs } : {},
|
|
203
|
+
...semaphore ? { releaseSlot: () => semaphore.release() } : {},
|
|
204
|
+
logger: fastify.log,
|
|
205
|
+
run: (signal) => {
|
|
206
|
+
const meta = {
|
|
207
|
+
jobId: bullJob.id ?? "",
|
|
208
|
+
attemptsMade: bullJob.attemptsMade ?? 0,
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
signal
|
|
211
|
+
};
|
|
212
|
+
return job.handler(bullJob.data, meta);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
190
215
|
if (bridgeEvents && fastify.events?.publish) try {
|
|
191
216
|
await fastify.events.publish(`job.${queueName}.completed`, {
|
|
192
217
|
jobId: bullJob.id,
|
|
@@ -366,4 +391,22 @@ const jobsPlugin = fp(jobsPluginImpl, {
|
|
|
366
391
|
fastify: "5.x"
|
|
367
392
|
});
|
|
368
393
|
//#endregion
|
|
369
|
-
|
|
394
|
+
//#region src/integrations/jobs/types.ts
|
|
395
|
+
/**
|
|
396
|
+
* Define a background job with typed data and configuration.
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* const processImage = defineJob({
|
|
400
|
+
* name: 'process-image',
|
|
401
|
+
* handler: async (data: { url: string; width: number }) => {
|
|
402
|
+
* return await sharp(data.url).resize(data.width).toBuffer();
|
|
403
|
+
* },
|
|
404
|
+
* retries: 3,
|
|
405
|
+
* timeout: 60000,
|
|
406
|
+
* });
|
|
407
|
+
*/
|
|
408
|
+
function defineJob(definition) {
|
|
409
|
+
return definition;
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
export { defineJob, executeTimedHandler, jobsPlugin };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Rt as IControllerResponse, V as ResourceDefinition, Vt as ServerAccessor, zt as IRequestContext } from "../../index-
|
|
2
|
-
import { i as RequestScope } from "../../types-
|
|
3
|
-
import { t as PermissionCheck } from "../../types-
|
|
1
|
+
import { Rt as IControllerResponse, V as ResourceDefinition, Vt as ServerAccessor, zt as IRequestContext } from "../../index-DPRH0Cme.mjs";
|
|
2
|
+
import { i as RequestScope } from "../../types-DjC-KUCz.mjs";
|
|
3
|
+
import { t as PermissionCheck } from "../../types-Dmw-6Qeo.mjs";
|
|
4
4
|
import { n as FieldPermissionMap } from "../../fields-D3lhgzhD.mjs";
|
|
5
|
-
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-
|
|
5
|
+
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-QFGc3Cjv.mjs";
|
|
6
6
|
import { ErrorContract } from "@classytic/repo-core/errors";
|
|
7
7
|
import { FastifyPluginAsync } from "fastify";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
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
|
+
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-Cb7olw0j.mjs";
|
|
2
2
|
//#region src/integrations/mcp/defineTool.ts
|
|
3
3
|
/**
|
|
4
4
|
* Define a type-safe MCP tool.
|
|
@@ -206,7 +206,7 @@ function createMcpAuthFromBetterAuthApiKey(auth, options = {}) {
|
|
|
206
206
|
...userId ? { userId } : {},
|
|
207
207
|
...organizationId ? { organizationId } : {},
|
|
208
208
|
...clientId ? { clientId } : {},
|
|
209
|
-
...scopes
|
|
209
|
+
...scopes?.length ? { scopes } : {}
|
|
210
210
|
};
|
|
211
211
|
};
|
|
212
212
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as McpAuthResult, u as McpPluginOptions } from "../../types-
|
|
1
|
+
import { c as McpAuthResult, u as McpPluginOptions } from "../../types-QFGc3Cjv.mjs";
|
|
2
2
|
//#region src/integrations/mcp/testing.d.ts
|
|
3
3
|
interface TestMcpClientOptions {
|
|
4
4
|
/** MCP plugin options (resources, overrides, etc.) — same as mcpPlugin config */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-
|
|
1
|
+
import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-Cb7olw0j.mjs";
|
|
2
2
|
//#region src/integrations/mcp/testing.ts
|
|
3
3
|
/**
|
|
4
4
|
* @classytic/arc/mcp/testing — MCP Test Utilities
|
|
@@ -19,6 +19,8 @@ interface WebhookDeliveryRecord {
|
|
|
19
19
|
status?: number;
|
|
20
20
|
error?: string;
|
|
21
21
|
timestamp: Date;
|
|
22
|
+
/** Delivery attempts made (present when `retry` is configured). */
|
|
23
|
+
attempts?: number;
|
|
22
24
|
}
|
|
23
25
|
/** Pluggable persistence — memory for dev, bring your own DB for prod */
|
|
24
26
|
interface WebhookStore {
|
|
@@ -42,6 +44,25 @@ interface WebhookPluginOptions {
|
|
|
42
44
|
* (a zero would make the delivery loop spin forever without progress).
|
|
43
45
|
*/
|
|
44
46
|
concurrency?: number;
|
|
47
|
+
/**
|
|
48
|
+
* In-process retry for failed deliveries. Default: single attempt.
|
|
49
|
+
* Retries fire on network errors, timeouts, 429 and 5xx — NOT on other
|
|
50
|
+
* 4xx (the receiver rejected the payload; repeating it can't succeed).
|
|
51
|
+
* Each attempt is re-signed with a fresh timestamp so the receiver's
|
|
52
|
+
* replay-tolerance window applies per attempt; `x-arc-webhook-delivery`
|
|
53
|
+
* stays constant, which is exactly what receiver-side dedup keys on.
|
|
54
|
+
*
|
|
55
|
+
* This is BEST-EFFORT: attempts live in this process, so a crash or
|
|
56
|
+
* deploy drops the remaining ones. Guaranteed delivery is a durable-job
|
|
57
|
+
* concern (queue keyed by subscription + delivery id) — pair the events
|
|
58
|
+
* outbox with a worker if you need it.
|
|
59
|
+
*/
|
|
60
|
+
retry?: {
|
|
61
|
+
/** Total attempts including the first. Must be an integer >= 1. */
|
|
62
|
+
attempts: number;
|
|
63
|
+
/** Backoff before retry N, as `backoffMs * 2^(N-1)` ms (default: 1000). */
|
|
64
|
+
backoffMs?: number;
|
|
65
|
+
};
|
|
45
66
|
/**
|
|
46
67
|
* URL policy hook — the SSRF seam for tenant-registered URLs. Throw to
|
|
47
68
|
* reject. Pass {@link defaultWebhookUrlPolicy} to require HTTPS and block
|
|
@@ -88,19 +109,6 @@ declare module "fastify" {
|
|
|
88
109
|
webhooks: WebhookManager;
|
|
89
110
|
}
|
|
90
111
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Sign a payload with HMAC-SHA256 for outbound webhook delivery.
|
|
93
|
-
*
|
|
94
|
-
* @deprecated LEGACY (pre-2.23) format — signs the BODY ONLY, so a captured
|
|
95
|
-
* request can be replayed indefinitely and the event-id header is not
|
|
96
|
-
* cryptographically bound to the body. Still emitted on the legacy
|
|
97
|
-
* `x-webhook-signature` header for existing receivers; new receivers should
|
|
98
|
-
* verify the v1 contract (`x-arc-webhook-signature`) via {@link verifyWebhook}.
|
|
99
|
-
* Legacy emission is scheduled for removal in v3.
|
|
100
|
-
*
|
|
101
|
-
* @returns `sha256=<hex>` — the format written to `x-webhook-signature`
|
|
102
|
-
*/
|
|
103
|
-
declare function signPayload(payload: string, secret: string): string;
|
|
104
112
|
/**
|
|
105
113
|
* Signed-string layout for the v1 webhook contract:
|
|
106
114
|
*
|
|
@@ -185,15 +193,18 @@ interface VerifySignatureOptions {
|
|
|
185
193
|
prefix?: string;
|
|
186
194
|
/**
|
|
187
195
|
* HMAC algorithm (default: `'sha256'`).
|
|
188
|
-
* Must match what the sender uses
|
|
196
|
+
* Must match what the sender uses.
|
|
189
197
|
*/
|
|
190
198
|
algorithm?: string;
|
|
191
199
|
}
|
|
192
200
|
/**
|
|
193
|
-
* Verify an
|
|
201
|
+
* Verify an INBOUND third-party webhook signature using timing-safe
|
|
202
|
+
* comparison — for webhooks arc RECEIVES (GitHub, Stripe, ...), not for
|
|
203
|
+
* arc-to-arc delivery. Arc's own outbound contract is v1
|
|
204
|
+
* (`x-arc-webhook-*`) — receivers of arc webhooks use {@link verifyWebhook}.
|
|
194
205
|
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
206
|
+
* Defaults to the common `sha256=<hex>` format; configurable for any HMAC
|
|
207
|
+
* scheme via options.
|
|
197
208
|
*
|
|
198
209
|
* ⚠ The `body` argument must be the exact bytes the sender signed. Fastify
|
|
199
210
|
* parses JSON bodies by default and the re-serialised object will NOT match
|
|
@@ -211,21 +222,17 @@ interface VerifySignatureOptions {
|
|
|
211
222
|
* ```typescript
|
|
212
223
|
* import { verifySignature } from '@classytic/arc/integrations/webhooks';
|
|
213
224
|
*
|
|
214
|
-
* //
|
|
215
|
-
* //
|
|
216
|
-
* //
|
|
217
|
-
* //
|
|
218
|
-
*
|
|
219
|
-
*
|
|
225
|
+
* // `config: { rawBody: true }` is REQUIRED — arc registers
|
|
226
|
+
* // fastify-raw-body with `global: false`, so routes opt in per-route;
|
|
227
|
+
* // without it `req.rawBody` is undefined and verifySignature throws a
|
|
228
|
+
* // TypeError.
|
|
229
|
+
*
|
|
230
|
+
* // GitHub-style sender (sha256=<hex>)
|
|
231
|
+
* fastify.post('/webhooks/github', { config: { rawBody: true } }, async (req, reply) => {
|
|
232
|
+
* const sig = req.headers['x-hub-signature-256'] as string;
|
|
220
233
|
* if (!verifySignature(req.rawBody, secret, sig)) {
|
|
221
234
|
* return reply.status(401).send({ error: 'Invalid signature' });
|
|
222
235
|
* }
|
|
223
|
-
* // handle event via req.headers['x-webhook-event']
|
|
224
|
-
* });
|
|
225
|
-
*
|
|
226
|
-
* // Third-party sender with custom header + bare hex
|
|
227
|
-
* const valid = verifySignature(body, secret, req.headers['x-hub-signature'], {
|
|
228
|
-
* prefix: 'sha256=', // GitHub format
|
|
229
236
|
* });
|
|
230
237
|
*
|
|
231
238
|
* // Stripe-style (bare hex, different header)
|
|
@@ -238,4 +245,4 @@ declare function verifySignature(body: string | Buffer, secret: string, signatur
|
|
|
238
245
|
declare const webhookPlugin: FastifyPluginAsync<WebhookPluginOptions>;
|
|
239
246
|
declare const _default: FastifyPluginAsync<WebhookPluginOptions>;
|
|
240
247
|
//#endregion
|
|
241
|
-
export { VerifySignatureOptions, VerifyWebhookInput, VerifyWebhookResult, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WebhookSubscriptionPublic, _default as default, defaultWebhookUrlPolicy,
|
|
248
|
+
export { VerifySignatureOptions, VerifyWebhookInput, VerifyWebhookResult, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WebhookSubscriptionPublic, _default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookPlugin };
|
|
@@ -31,23 +31,6 @@ import fp from "fastify-plugin";
|
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
33
|
/**
|
|
34
|
-
* Sign a payload with HMAC-SHA256 for outbound webhook delivery.
|
|
35
|
-
*
|
|
36
|
-
* @deprecated LEGACY (pre-2.23) format — signs the BODY ONLY, so a captured
|
|
37
|
-
* request can be replayed indefinitely and the event-id header is not
|
|
38
|
-
* cryptographically bound to the body. Still emitted on the legacy
|
|
39
|
-
* `x-webhook-signature` header for existing receivers; new receivers should
|
|
40
|
-
* verify the v1 contract (`x-arc-webhook-signature`) via {@link verifyWebhook}.
|
|
41
|
-
* Legacy emission is scheduled for removal in v3.
|
|
42
|
-
*
|
|
43
|
-
* @returns `sha256=<hex>` — the format written to `x-webhook-signature`
|
|
44
|
-
*/
|
|
45
|
-
function signPayload(payload, secret) {
|
|
46
|
-
const hmac = createHmac("sha256", secret);
|
|
47
|
-
hmac.update(payload);
|
|
48
|
-
return `sha256=${hmac.digest("hex")}`;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
34
|
* Signed-string layout for the v1 webhook contract:
|
|
52
35
|
*
|
|
53
36
|
* v1.<timestampMs>.<deliveryId>.<body>
|
|
@@ -162,10 +145,13 @@ function decodeMappedV4(host) {
|
|
|
162
145
|
return `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;
|
|
163
146
|
}
|
|
164
147
|
/**
|
|
165
|
-
* Verify an
|
|
148
|
+
* Verify an INBOUND third-party webhook signature using timing-safe
|
|
149
|
+
* comparison — for webhooks arc RECEIVES (GitHub, Stripe, ...), not for
|
|
150
|
+
* arc-to-arc delivery. Arc's own outbound contract is v1
|
|
151
|
+
* (`x-arc-webhook-*`) — receivers of arc webhooks use {@link verifyWebhook}.
|
|
166
152
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
153
|
+
* Defaults to the common `sha256=<hex>` format; configurable for any HMAC
|
|
154
|
+
* scheme via options.
|
|
169
155
|
*
|
|
170
156
|
* ⚠ The `body` argument must be the exact bytes the sender signed. Fastify
|
|
171
157
|
* parses JSON bodies by default and the re-serialised object will NOT match
|
|
@@ -183,21 +169,17 @@ function decodeMappedV4(host) {
|
|
|
183
169
|
* ```typescript
|
|
184
170
|
* import { verifySignature } from '@classytic/arc/integrations/webhooks';
|
|
185
171
|
*
|
|
186
|
-
* //
|
|
187
|
-
* //
|
|
188
|
-
* //
|
|
189
|
-
* //
|
|
190
|
-
*
|
|
191
|
-
*
|
|
172
|
+
* // `config: { rawBody: true }` is REQUIRED — arc registers
|
|
173
|
+
* // fastify-raw-body with `global: false`, so routes opt in per-route;
|
|
174
|
+
* // without it `req.rawBody` is undefined and verifySignature throws a
|
|
175
|
+
* // TypeError.
|
|
176
|
+
*
|
|
177
|
+
* // GitHub-style sender (sha256=<hex>)
|
|
178
|
+
* fastify.post('/webhooks/github', { config: { rawBody: true } }, async (req, reply) => {
|
|
179
|
+
* const sig = req.headers['x-hub-signature-256'] as string;
|
|
192
180
|
* if (!verifySignature(req.rawBody, secret, sig)) {
|
|
193
181
|
* return reply.status(401).send({ error: 'Invalid signature' });
|
|
194
182
|
* }
|
|
195
|
-
* // handle event via req.headers['x-webhook-event']
|
|
196
|
-
* });
|
|
197
|
-
*
|
|
198
|
-
* // Third-party sender with custom header + bare hex
|
|
199
|
-
* const valid = verifySignature(body, secret, req.headers['x-hub-signature'], {
|
|
200
|
-
* prefix: 'sha256=', // GitHub format
|
|
201
183
|
* });
|
|
202
184
|
*
|
|
203
185
|
* // Stripe-style (bare hex, different header)
|
|
@@ -255,6 +237,9 @@ const webhookPlugin = async (fastify, opts = {}) => {
|
|
|
255
237
|
const maxLogEntries = opts.maxLogEntries ?? 1e3;
|
|
256
238
|
const concurrency = opts.concurrency ?? 5;
|
|
257
239
|
if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`webhookPlugin: concurrency must be an integer >= 1 (got ${String(opts.concurrency)})`);
|
|
240
|
+
const retryAttempts = opts.retry?.attempts ?? 1;
|
|
241
|
+
const retryBackoffMs = opts.retry?.backoffMs ?? 1e3;
|
|
242
|
+
if (!Number.isInteger(retryAttempts) || retryAttempts < 1) throw new Error(`webhookPlugin: retry.attempts must be an integer >= 1 (got ${String(opts.retry?.attempts)})`);
|
|
258
243
|
let subscriptions = [];
|
|
259
244
|
const log = [];
|
|
260
245
|
subscriptions = await store.getAll();
|
|
@@ -287,38 +272,42 @@ const webhookPlugin = async (fastify, opts = {}) => {
|
|
|
287
272
|
success: false,
|
|
288
273
|
timestamp: /* @__PURE__ */ new Date()
|
|
289
274
|
};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
deliveryId: event.meta.id
|
|
295
|
-
});
|
|
296
|
-
const legacySignature = signPayload(body, sub.secret);
|
|
297
|
-
const ac = new AbortController();
|
|
298
|
-
const timer = setTimeout(() => ac.abort(), timeout);
|
|
275
|
+
for (let attempt = 1; attempt <= retryAttempts; attempt++) {
|
|
276
|
+
record.attempts = attempt;
|
|
277
|
+
delete record.status;
|
|
278
|
+
delete record.error;
|
|
299
279
|
try {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"x-arc-webhook-signature": v1Signature,
|
|
305
|
-
"x-arc-webhook-timestamp": String(timestamp),
|
|
306
|
-
"x-arc-webhook-delivery": event.meta.id,
|
|
307
|
-
"x-webhook-signature": legacySignature,
|
|
308
|
-
"x-webhook-id": event.meta.id,
|
|
309
|
-
"x-webhook-event": event.type
|
|
310
|
-
},
|
|
311
|
-
body,
|
|
312
|
-
signal: ac.signal,
|
|
313
|
-
...opts.validateUrl ? { redirect: "manual" } : {}
|
|
280
|
+
const timestamp = Date.now();
|
|
281
|
+
const v1Signature = signWebhook(body, sub.secret, {
|
|
282
|
+
timestamp,
|
|
283
|
+
deliveryId: event.meta.id
|
|
314
284
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
285
|
+
const ac = new AbortController();
|
|
286
|
+
const timer = setTimeout(() => ac.abort(), timeout);
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetchFn(sub.url, {
|
|
289
|
+
method: "POST",
|
|
290
|
+
headers: {
|
|
291
|
+
"content-type": "application/json",
|
|
292
|
+
"x-arc-webhook-signature": v1Signature,
|
|
293
|
+
"x-arc-webhook-timestamp": String(timestamp),
|
|
294
|
+
"x-arc-webhook-delivery": event.meta.id
|
|
295
|
+
},
|
|
296
|
+
body,
|
|
297
|
+
signal: ac.signal,
|
|
298
|
+
...opts.validateUrl ? { redirect: "manual" } : {}
|
|
299
|
+
});
|
|
300
|
+
record.success = response.ok;
|
|
301
|
+
record.status = response.status;
|
|
302
|
+
} finally {
|
|
303
|
+
clearTimeout(timer);
|
|
304
|
+
}
|
|
305
|
+
} catch (err) {
|
|
306
|
+
record.error = err instanceof Error ? err.message : String(err);
|
|
319
307
|
}
|
|
320
|
-
|
|
321
|
-
record.
|
|
308
|
+
if (record.success) break;
|
|
309
|
+
if (!(record.status === void 0 || record.status === 429 || record.status >= 500) || attempt === retryAttempts) break;
|
|
310
|
+
await new Promise((r) => setTimeout(r, retryBackoffMs * 2 ** (attempt - 1)));
|
|
322
311
|
}
|
|
323
312
|
log.push(record);
|
|
324
313
|
if (log.length > maxLogEntries) log.splice(0, log.length - maxLogEntries);
|
|
@@ -372,4 +361,4 @@ var webhooks_default = fp(webhookPlugin, {
|
|
|
372
361
|
dependencies: ["arc-events"]
|
|
373
362
|
});
|
|
374
363
|
//#endregion
|
|
375
|
-
export { webhooks_default as default, defaultWebhookUrlPolicy,
|
|
364
|
+
export { webhooks_default as default, defaultWebhookUrlPolicy, signWebhook, verifySignature, verifyWebhook, webhookPlugin };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as GeoRoomOptions, c as WebSocketClient, d as LocalWebSocketAdapter, f as WebSocketAdapter, i as GeoRoom, l as WebSocketMessage, n as GeoPoint, o as RoomManager, r as GeoPublishOpts, s as AuthResult, t as websocketPlugin, u as WebSocketPluginOptions } from "../websocket-
|
|
1
|
+
import { a as GeoRoomOptions, c as WebSocketClient, d as LocalWebSocketAdapter, f as WebSocketAdapter, i as GeoRoom, l as WebSocketMessage, n as GeoPoint, o as RoomManager, r as GeoPublishOpts, s as AuthResult, t as websocketPlugin, u as WebSocketPluginOptions } from "../websocket-_Pd0CgR0.mjs";
|
|
2
2
|
import { a as PushRefRegistry, c as SerializedEntry, i as Principal, o as PushRefRegistryOptions, r as MemoryPushRefStore, s as PushRefStore } from "../pushref-registry-BbcnpNTm.mjs";
|
|
3
3
|
export { type AuthResult, type GeoPoint, type GeoPublishOpts, GeoRoom, type GeoRoomOptions, LocalWebSocketAdapter, MemoryPushRefStore, type Principal, PushRefRegistry, type PushRefRegistryOptions, type PushRefStore, RoomManager, type SerializedEntry, type WebSocketAdapter, type WebSocketClient, type WebSocketMessage, type WebSocketPluginOptions, websocketPlugin };
|