@classytic/arc 2.17.1 → 2.18.1
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-CgHomZ4d.mjs → BaseController-DPwe89qs.mjs} +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/context/index.d.mts +54 -1
- package/dist/context/index.mjs +65 -2
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -3
- package/dist/{core-D8tpQ4ZL.mjs → core-B41eZxyM.mjs} +8 -5
- package/dist/{createActionRouter-BHUMb1-y.mjs → createActionRouter-CRaIwB5S.mjs} +2 -2
- package/dist/{createAggregationRouter-BYVvhfc5.mjs → createAggregationRouter-n3O6WdLO.mjs} +1 -1
- package/dist/{createApp-CzCEEsEm.mjs → createApp-DZvrb587.mjs} +29 -7
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/{eventPlugin-CnJCUyM0.mjs → eventPlugin-Dxp9JlKK.mjs} +1 -1
- package/dist/events/index.d.mts +48 -0
- package/dist/events/index.mjs +27 -5
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{fastify-augmentation-7LIR0nDv.d.mts → fastify-augmentation-CL48B6gW.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/{index-C1JSBan-.d.mts → index-C000faom.d.mts} +10 -1
- package/dist/{index-CruZir1o.d.mts → index-CNiryUMN.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +4 -4
- package/dist/{init-DYzfniuX.mjs → init-DQ_rY1J7.mjs} +13 -13
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/mcp/index.d.mts +43 -3
- package/dist/integrations/mcp/index.mjs +102 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +8 -1
- package/dist/integrations/streamline.mjs +34 -3
- package/dist/integrations/websocket-pushref-redis.d.mts +71 -0
- package/dist/integrations/websocket-pushref-redis.mjs +205 -0
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +3 -2
- package/dist/integrations/websocket.mjs +1142 -23
- package/dist/{mcpPlugin-C6FViZXw.mjs → mcpPlugin-BrepZ0cV.mjs} +3 -3
- package/dist/middleware/index.d.mts +1 -1
- package/dist/{openapi-DKDs9NfD.mjs → openapi-CLZx9bwS.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +17 -3
- package/dist/plugins/index.mjs +44 -3
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/search.d.mts +1 -1
- package/dist/pushref-registry-Bq_-EVsC.d.mts +435 -0
- package/dist/registry/index.d.mts +1 -1
- package/dist/{requestContext-SSaaTgW8.mjs → requestContext-DDLS3NVU.mjs} +26 -1
- package/dist/{routerShared-nCyJGOKX.mjs → routerShared-Dl0A77Ba.mjs} +1 -1
- package/dist/{schemaIR-DAOvBWgT.mjs → schemaIR-Bk6Tns82.mjs} +17 -5
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +1 -1
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-BRmS8TNa.d.mts → types-DEEWwQFR.d.mts} +1 -1
- package/dist/{types-CoMjNmkF.d.mts → types-DH-7WO64.d.mts} +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/websocket-BZMUP7Gr.d.mts +456 -0
- package/package.json +12 -1
- package/dist/websocket-BJ0w-3Dt.d.mts +0 -186
- /package/dist/{storage-HcAgP5Ni.d.mts → storage-BFmamN5m.d.mts} +0 -0
- /package/dist/{tracing-bRqqUxFO.d.mts → tracing-BYQ3hsbM.d.mts} +0 -0
|
@@ -917,9 +917,9 @@ ${config.adapter === "mongokit" ? ` await mongoose.connection.collection('use
|
|
|
917
917
|
//#region src/cli/commands/init/dependency-plan.ts
|
|
918
918
|
const SCAFFOLD_DEP_VERSIONS = {
|
|
919
919
|
core: {
|
|
920
|
-
"@classytic/arc": "^2.
|
|
920
|
+
"@classytic/arc": "^2.17.2",
|
|
921
921
|
"@classytic/primitives": "^0.6.0",
|
|
922
|
-
"@classytic/repo-core": "^0.
|
|
922
|
+
"@classytic/repo-core": "^0.5.0",
|
|
923
923
|
"@fastify/cors": "^11.2.0",
|
|
924
924
|
"@fastify/helmet": "^13.0.2",
|
|
925
925
|
"@fastify/rate-limit": "^10.3.0",
|
|
@@ -929,27 +929,27 @@ const SCAFFOLD_DEP_VERSIONS = {
|
|
|
929
929
|
fastify: "^5.8.5"
|
|
930
930
|
},
|
|
931
931
|
authJwt: {
|
|
932
|
-
"@fastify/jwt": "^10.
|
|
933
|
-
bcryptjs: "^3.0.
|
|
932
|
+
"@fastify/jwt": "^10.1.0",
|
|
933
|
+
bcryptjs: "^3.0.3"
|
|
934
934
|
},
|
|
935
935
|
authBetterAuth: {
|
|
936
|
-
"better-auth": "^1.6.
|
|
937
|
-
mongodb: "^7.
|
|
936
|
+
"better-auth": "^1.6.11",
|
|
937
|
+
mongodb: "^7.2.0"
|
|
938
938
|
},
|
|
939
|
-
authBetterAuthApiKey: { "@better-auth/api-key": "^1.6.
|
|
939
|
+
authBetterAuthApiKey: { "@better-auth/api-key": "^1.6.11" },
|
|
940
940
|
adapterMongokit: {
|
|
941
|
-
"@classytic/mongokit": "^3.
|
|
942
|
-
mongoose: "^9.6.
|
|
941
|
+
"@classytic/mongokit": "^3.14.0",
|
|
942
|
+
mongoose: "^9.6.2"
|
|
943
943
|
},
|
|
944
944
|
devCommon: {
|
|
945
945
|
"mongodb-memory-server": "^11.1.0",
|
|
946
|
-
"pino-pretty": "^13.
|
|
947
|
-
vitest: "^4.1.
|
|
946
|
+
"pino-pretty": "^13.1.3",
|
|
947
|
+
vitest: "^4.1.7"
|
|
948
948
|
},
|
|
949
949
|
devTypescript: {
|
|
950
950
|
"@types/node": "^22.10.0",
|
|
951
|
-
tsx: "^4.
|
|
952
|
-
typescript: "^
|
|
951
|
+
tsx: "^4.22.3",
|
|
952
|
+
typescript: "^6.0.3"
|
|
953
953
|
},
|
|
954
954
|
typesJwt: { "@types/bcryptjs": "^3.0.0" }
|
|
955
955
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as DomainEvent } from "../EventTransport-B1GFiYUy.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { c as WebSocketClient, l as WebSocketMessage } from "../websocket-BZMUP7Gr.mjs";
|
|
3
3
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
4
4
|
|
|
5
5
|
//#region src/integrations/event-gateway.d.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as WebSocketClient, l as WebSocketMessage, u as WebSocketPluginOptions } from "../websocket-BZMUP7Gr.mjs";
|
|
2
2
|
import { EventGatewayOptions } from "./event-gateway.mjs";
|
|
3
3
|
import { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats } from "./jobs.mjs";
|
|
4
|
-
import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-
|
|
4
|
+
import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-DH-7WO64.mjs";
|
|
5
5
|
import { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike } from "./streamline.mjs";
|
|
6
6
|
import { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription } from "./webhooks.mjs";
|
|
7
7
|
export { type BetterAuthHandler, type CallToolResult, type CreateMcpServerConfig, type CrudOperation, type EventGatewayOptions, type JobDefinition, type JobDispatchOptions, type JobDispatcher, type JobMeta, type JobsPluginOptions, type McpAuthResult, type McpPluginOptions, type McpResourceConfig, type PromptDefinition, type QueueStats, type StreamlinePluginOptions, type ToolAnnotations, type ToolContext, type ToolDefinition, type WebSocketClient, type WebSocketMessage, type WebSocketPluginOptions, type WebhookDeliveryRecord, type WebhookManager, type WebhookPluginOptions, type WebhookStore, type WebhookSubscription, type WorkflowLike, type WorkflowRunLike };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-
|
|
1
|
+
import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-C000faom.mjs";
|
|
2
2
|
import { i as RequestScope } from "../../types-CfapW2t8.mjs";
|
|
3
3
|
import { c as PermissionCheck } from "../../fields-DqZLp5d6.mjs";
|
|
4
|
-
import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-
|
|
4
|
+
import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-DH-7WO64.mjs";
|
|
5
5
|
import { ErrorContract } from "@classytic/repo-core/errors";
|
|
6
6
|
import { FastifyPluginAsync } from "fastify";
|
|
7
7
|
import { z } from "zod";
|
|
@@ -505,6 +505,46 @@ declare function filterResourcesForMcp<T extends {
|
|
|
505
505
|
}>(resources: readonly T[], inputs: ResourceFilterInputs): T[];
|
|
506
506
|
declare const mcpPlugin: FastifyPluginAsync<McpPluginOptions>;
|
|
507
507
|
//#endregion
|
|
508
|
+
//#region src/integrations/mcp/realtimeBridge.d.ts
|
|
509
|
+
/**
|
|
510
|
+
* Inbound tool-call shape — accepts both LiveKit Agents (`arguments`)
|
|
511
|
+
* and OpenAI Realtime (`arguments_json` string) variants. Other realtime
|
|
512
|
+
* vendors stay flexible because we only look at the canonical fields.
|
|
513
|
+
*/
|
|
514
|
+
interface RealtimeToolCall {
|
|
515
|
+
id: string;
|
|
516
|
+
name: string;
|
|
517
|
+
/** Parsed object form. Preferred over `arguments_json`. */
|
|
518
|
+
arguments?: Record<string, unknown>;
|
|
519
|
+
/** JSON-string form (OpenAI Realtime tools). Parsed if `arguments` is absent. */
|
|
520
|
+
arguments_json?: string;
|
|
521
|
+
}
|
|
522
|
+
/** Outbound shape — mirrors js-genai's sendToolResponse. */
|
|
523
|
+
interface RealtimeToolResponse {
|
|
524
|
+
id: string;
|
|
525
|
+
name: string;
|
|
526
|
+
/** Tool result — `content` from the MCP CallToolResult, normalized to plain JSON. */
|
|
527
|
+
response: Record<string, unknown>;
|
|
528
|
+
/** Surfaces tool-side errors so the model can recover. */
|
|
529
|
+
isError?: boolean;
|
|
530
|
+
}
|
|
531
|
+
interface DispatchOptions<TSession = unknown> {
|
|
532
|
+
session?: TSession | null;
|
|
533
|
+
log?: ToolContext<TSession>["log"];
|
|
534
|
+
/** Forwarded into `ctx.extra` for advanced handlers. */
|
|
535
|
+
extra?: Record<string, unknown>;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Dispatch a batch of tool calls through the host's MCP tool registry.
|
|
539
|
+
*
|
|
540
|
+
* Returns the response array in the same order as the input. Each
|
|
541
|
+
* response carries either the tool's `content` (success path) or a
|
|
542
|
+
* `{isError:true, error:{message,...}}` shape (validation failure or
|
|
543
|
+
* handler throw). The realtime client uses the `id` to correlate
|
|
544
|
+
* responses to the original calls.
|
|
545
|
+
*/
|
|
546
|
+
declare function dispatchRealtimeToolCall<TSession = unknown>(tools: readonly ToolDefinition[], calls: readonly RealtimeToolCall[], opts?: DispatchOptions<TSession>): Promise<RealtimeToolResponse[]>;
|
|
547
|
+
//#endregion
|
|
508
548
|
//#region src/integrations/mcp/resourceToTools.d.ts
|
|
509
549
|
interface ResourceToToolsConfig extends McpResourceConfig {
|
|
510
550
|
toolNamePrefix?: string;
|
|
@@ -578,4 +618,4 @@ declare function permissionDeniedResult(args: {
|
|
|
578
618
|
session: McpAuthResult | null;
|
|
579
619
|
}): CallToolResult;
|
|
580
620
|
//#endregion
|
|
581
|
-
export { type AuthRef, type BetterAuthHandler, type BuildMcpToolsFromBridgesOptions, type CallToolResult, type CreateMcpAuthFromBetterAuthApiKeyOptions, type CreateMcpServerConfig, type CrudDescriptionMeta, type CrudDescriptionOverride, type CrudOperation, type DefinePromptConfig, type DefineToolConfig, type FieldRuleEntry, type FieldRulesToZodOptions, type InvokeControllerOptions, type McpAuthResolver, type McpAuthResult, type McpBridge, type McpGuard, type McpOperation, type McpPluginOptions, type McpResourceConfig, type McpServerInstance, type PromptDefinition, type PromptResult, type ResourceToToolsConfig, type ToolAnnotations, type ToolContext, type ToolDefinition, bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpAuthFromBetterAuthApiKey, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resolveToolCollisions, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
621
|
+
export { type AuthRef, type BetterAuthHandler, type BuildMcpToolsFromBridgesOptions, type CallToolResult, type CreateMcpAuthFromBetterAuthApiKeyOptions, type CreateMcpServerConfig, type CrudDescriptionMeta, type CrudDescriptionOverride, type CrudOperation, type DefinePromptConfig, type DefineToolConfig, type DispatchOptions, type FieldRuleEntry, type FieldRulesToZodOptions, type InvokeControllerOptions, type McpAuthResolver, type McpAuthResult, type McpBridge, type McpGuard, type McpOperation, type McpPluginOptions, type McpResourceConfig, type McpServerInstance, type PromptDefinition, type PromptResult, type RealtimeToolCall, type RealtimeToolResponse, type ResourceToToolsConfig, type ToolAnnotations, type ToolContext, type ToolDefinition, bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpAuthFromBetterAuthApiKey, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, dispatchRealtimeToolCall, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resolveToolCollisions, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
@@ -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-BrepZ0cV.mjs";
|
|
2
2
|
//#region src/integrations/mcp/defineTool.ts
|
|
3
3
|
/**
|
|
4
4
|
* Define a type-safe MCP tool.
|
|
@@ -275,4 +275,104 @@ function definePrompt(name, config) {
|
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
277
|
//#endregion
|
|
278
|
-
|
|
278
|
+
//#region src/integrations/mcp/realtimeBridge.ts
|
|
279
|
+
/**
|
|
280
|
+
* Dispatch a batch of tool calls through the host's MCP tool registry.
|
|
281
|
+
*
|
|
282
|
+
* Returns the response array in the same order as the input. Each
|
|
283
|
+
* response carries either the tool's `content` (success path) or a
|
|
284
|
+
* `{isError:true, error:{message,...}}` shape (validation failure or
|
|
285
|
+
* handler throw). The realtime client uses the `id` to correlate
|
|
286
|
+
* responses to the original calls.
|
|
287
|
+
*/
|
|
288
|
+
async function dispatchRealtimeToolCall(tools, calls, opts = {}) {
|
|
289
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
290
|
+
const noopLog = async () => {};
|
|
291
|
+
const baseCtx = {
|
|
292
|
+
session: opts.session ?? null,
|
|
293
|
+
log: opts.log ?? noopLog,
|
|
294
|
+
extra: opts.extra ?? {}
|
|
295
|
+
};
|
|
296
|
+
return (await Promise.allSettled(calls.map((call) => dispatchOne(byName.get(call.name), call, baseCtx)))).map((s, i) => {
|
|
297
|
+
const call = calls[i];
|
|
298
|
+
if (!call) return {
|
|
299
|
+
id: "unknown",
|
|
300
|
+
name: "unknown",
|
|
301
|
+
isError: true,
|
|
302
|
+
response: { error: "missing call slot" }
|
|
303
|
+
};
|
|
304
|
+
if (s.status === "fulfilled") return s.value;
|
|
305
|
+
return {
|
|
306
|
+
id: call.id,
|
|
307
|
+
name: call.name,
|
|
308
|
+
isError: true,
|
|
309
|
+
response: { error: s.reason instanceof Error ? s.reason.message : String(s.reason) }
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
async function dispatchOne(def, call, ctx) {
|
|
314
|
+
if (!def) return {
|
|
315
|
+
id: call.id,
|
|
316
|
+
name: call.name,
|
|
317
|
+
isError: true,
|
|
318
|
+
response: { error: `unknown tool: ${call.name}` }
|
|
319
|
+
};
|
|
320
|
+
let parsedArgs = call.arguments ?? {};
|
|
321
|
+
if (!call.arguments && typeof call.arguments_json === "string") try {
|
|
322
|
+
parsedArgs = JSON.parse(call.arguments_json);
|
|
323
|
+
} catch {
|
|
324
|
+
return {
|
|
325
|
+
id: call.id,
|
|
326
|
+
name: call.name,
|
|
327
|
+
isError: true,
|
|
328
|
+
response: { error: "arguments_json is not valid JSON" }
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (def.inputSchema) {
|
|
332
|
+
const fieldErrors = [];
|
|
333
|
+
const validated = {};
|
|
334
|
+
for (const [key, schema] of Object.entries(def.inputSchema)) {
|
|
335
|
+
const result = schema.safeParse(parsedArgs[key]);
|
|
336
|
+
if (!result.success) fieldErrors.push(`${key}: ${result.error.message}`);
|
|
337
|
+
else validated[key] = result.data;
|
|
338
|
+
}
|
|
339
|
+
if (fieldErrors.length > 0) return {
|
|
340
|
+
id: call.id,
|
|
341
|
+
name: call.name,
|
|
342
|
+
isError: true,
|
|
343
|
+
response: {
|
|
344
|
+
error: "argument validation failed",
|
|
345
|
+
fields: fieldErrors
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
parsedArgs = validated;
|
|
349
|
+
}
|
|
350
|
+
const result = await def.handler(parsedArgs, ctx);
|
|
351
|
+
return {
|
|
352
|
+
id: call.id,
|
|
353
|
+
name: call.name,
|
|
354
|
+
isError: result.isError,
|
|
355
|
+
response: normalizeResult(result)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Normalize an MCP `CallToolResult` into a flat JSON object the realtime
|
|
360
|
+
* wire can carry. Prefers structuredContent; falls back to concatenated
|
|
361
|
+
* text content.
|
|
362
|
+
*/
|
|
363
|
+
function normalizeResult(result) {
|
|
364
|
+
if (result.structuredContent) return result.structuredContent;
|
|
365
|
+
const texts = [];
|
|
366
|
+
for (const part of result.content) if (part.type === "text" && typeof part.text === "string") texts.push(part.text);
|
|
367
|
+
if (texts.length > 0) {
|
|
368
|
+
const joined = texts.join("");
|
|
369
|
+
try {
|
|
370
|
+
const parsed = JSON.parse(joined);
|
|
371
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
372
|
+
} catch {}
|
|
373
|
+
return { text: joined };
|
|
374
|
+
}
|
|
375
|
+
return {};
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
378
|
+
export { bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpAuthFromBetterAuthApiKey, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, dispatchRealtimeToolCall, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resolveToolCollisions, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
@@ -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-BrepZ0cV.mjs";
|
|
2
2
|
//#region src/integrations/mcp/testing.ts
|
|
3
3
|
/**
|
|
4
4
|
* @classytic/arc/mcp/testing — MCP Test Utilities
|
|
@@ -238,6 +238,13 @@ declare const STREAMLINE_BUS_EVENTS: readonly ["step:started", "step:completed",
|
|
|
238
238
|
* the run is still active after them.
|
|
239
239
|
*/
|
|
240
240
|
declare const STREAMLINE_TERMINAL_EVENTS: readonly ["workflow:completed", "workflow:failed", "workflow:cancelled"];
|
|
241
|
+
/**
|
|
242
|
+
* Terminal run STATUSES (distinct from the terminal EVENT names above).
|
|
243
|
+
* Mirrors streamline's `isTerminalState()` — a run in one of these will
|
|
244
|
+
* emit no further bus events, so the SSE handler sends its snapshot and
|
|
245
|
+
* closes immediately rather than holding a connection open on a dead run.
|
|
246
|
+
*/
|
|
247
|
+
declare const TERMINAL_RUN_STATUSES: Set<string>;
|
|
241
248
|
/**
|
|
242
249
|
* Pluggable streamline integration for Arc.
|
|
243
250
|
*
|
|
@@ -248,4 +255,4 @@ declare const STREAMLINE_TERMINAL_EVENTS: readonly ["workflow:completed", "workf
|
|
|
248
255
|
*/
|
|
249
256
|
declare const streamlinePlugin: FastifyPluginAsync<StreamlinePluginOptions>;
|
|
250
257
|
//#endregion
|
|
251
|
-
export { STREAMLINE_BUS_EVENTS, STREAMLINE_FAILURE_CODES, STREAMLINE_TERMINAL_EVENTS, StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, WorkflowStartOptions, streamlinePlugin };
|
|
258
|
+
export { STREAMLINE_BUS_EVENTS, STREAMLINE_FAILURE_CODES, STREAMLINE_TERMINAL_EVENTS, StreamlinePluginOptions, TERMINAL_RUN_STATUSES, WorkflowLike, WorkflowRunLike, WorkflowStartOptions, streamlinePlugin };
|
|
@@ -58,6 +58,17 @@ const STREAMLINE_TERMINAL_EVENTS = [
|
|
|
58
58
|
"workflow:failed",
|
|
59
59
|
"workflow:cancelled"
|
|
60
60
|
];
|
|
61
|
+
/**
|
|
62
|
+
* Terminal run STATUSES (distinct from the terminal EVENT names above).
|
|
63
|
+
* Mirrors streamline's `isTerminalState()` — a run in one of these will
|
|
64
|
+
* emit no further bus events, so the SSE handler sends its snapshot and
|
|
65
|
+
* closes immediately rather than holding a connection open on a dead run.
|
|
66
|
+
*/
|
|
67
|
+
const TERMINAL_RUN_STATUSES = new Set([
|
|
68
|
+
"done",
|
|
69
|
+
"failed",
|
|
70
|
+
"cancelled"
|
|
71
|
+
]);
|
|
61
72
|
const streamlinePluginImpl = async (fastify, options) => {
|
|
62
73
|
const { workflows, prefix = "/workflows", auth = true, bridgeEvents = true, enableStreaming = false, enableHookEndpoint = false, tenantResolver, bypassTenantResolver, permissions: perms } = options;
|
|
63
74
|
const routeScope = prefix;
|
|
@@ -255,15 +266,19 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
255
266
|
if (enableStreaming && wf.container?.eventBus) fastify.get(`${routePrefix}/runs/:runId/stream`, { preHandler: authPreHandler }, async (request, reply) => {
|
|
256
267
|
if (!await checkPerm("get", request)) throw new ForbiddenError();
|
|
257
268
|
const { runId } = request.params;
|
|
258
|
-
|
|
269
|
+
const run = await wf.get(runId);
|
|
270
|
+
if (!run) throw new NotFoundError("Workflow run", runId);
|
|
259
271
|
reply.raw.writeHead(200, {
|
|
260
272
|
"Content-Type": "text/event-stream",
|
|
261
273
|
"Cache-Control": "no-cache",
|
|
262
|
-
Connection: "keep-alive"
|
|
274
|
+
Connection: "keep-alive",
|
|
275
|
+
"X-Accel-Buffering": "no"
|
|
263
276
|
});
|
|
277
|
+
reply.raw.flushHeaders();
|
|
264
278
|
const terminalEvents = new Set(STREAMLINE_TERMINAL_EVENTS);
|
|
265
279
|
const listeners = [];
|
|
266
280
|
let closed = false;
|
|
281
|
+
let heartbeat;
|
|
267
282
|
const send = (event, data) => {
|
|
268
283
|
if (closed) return;
|
|
269
284
|
try {
|
|
@@ -275,12 +290,28 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
275
290
|
const cleanup = () => {
|
|
276
291
|
if (closed) return;
|
|
277
292
|
closed = true;
|
|
293
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
278
294
|
for (const { event, fn } of listeners) wf.container?.eventBus.off(event, fn);
|
|
279
295
|
listeners.length = 0;
|
|
280
296
|
try {
|
|
281
297
|
reply.raw.end();
|
|
282
298
|
} catch {}
|
|
283
299
|
};
|
|
300
|
+
send("workflow:snapshot", run);
|
|
301
|
+
const runStatus = run.status;
|
|
302
|
+
if (runStatus && TERMINAL_RUN_STATUSES.has(runStatus)) {
|
|
303
|
+
cleanup();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
heartbeat = setInterval(() => {
|
|
307
|
+
if (closed) return;
|
|
308
|
+
try {
|
|
309
|
+
reply.raw.write(`: ping\n\n`);
|
|
310
|
+
} catch {
|
|
311
|
+
cleanup();
|
|
312
|
+
}
|
|
313
|
+
}, 15e3);
|
|
314
|
+
heartbeat.unref?.();
|
|
284
315
|
for (const eventName of STREAMLINE_BUS_EVENTS) {
|
|
285
316
|
const fn = (payload) => {
|
|
286
317
|
const p = payload;
|
|
@@ -336,4 +367,4 @@ const streamlinePlugin = fp(streamlinePluginImpl, {
|
|
|
336
367
|
fastify: "5.x"
|
|
337
368
|
});
|
|
338
369
|
//#endregion
|
|
339
|
-
export { STREAMLINE_BUS_EVENTS, STREAMLINE_FAILURE_CODES, STREAMLINE_TERMINAL_EVENTS, streamlinePlugin };
|
|
370
|
+
export { STREAMLINE_BUS_EVENTS, STREAMLINE_FAILURE_CODES, STREAMLINE_TERMINAL_EVENTS, TERMINAL_RUN_STATUSES, streamlinePlugin };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { c as SerializedEntry, i as Principal, n as FencedWriteOutcome, s as PushRefStore, t as EnvelopeSnapshot } from "../pushref-registry-Bq_-EVsC.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/integrations/websocket-pushref-redis.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Minimal `ioredis` shape — typed structurally so this file doesn't
|
|
6
|
+
* take a typecheck-time dep on `ioredis`. The peer dep is declared
|
|
7
|
+
* `optional: true` in package.json; hosts who don't use this subpath
|
|
8
|
+
* never need to install ioredis.
|
|
9
|
+
*/
|
|
10
|
+
interface RedisLike {
|
|
11
|
+
get(key: string): Promise<string | null>;
|
|
12
|
+
set(key: string, value: string, mode?: string, duration?: number): Promise<unknown>;
|
|
13
|
+
del(key: string): Promise<number>;
|
|
14
|
+
pexpire(key: string, ttlMs: number): Promise<number>;
|
|
15
|
+
scan(cursor: string | number, matchKw: "MATCH", pattern: string, countKw: "COUNT", count: number): Promise<[string, string[]]>;
|
|
16
|
+
eval(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;
|
|
17
|
+
}
|
|
18
|
+
interface RedisPushRefStoreOptions {
|
|
19
|
+
/** Redis key namespace. Default `arc:pushref:`. */
|
|
20
|
+
keyPrefix?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Grace ms added to the Redis-side TTL beyond the registry's logical
|
|
23
|
+
* TTL — protects against clock skew between application and broker.
|
|
24
|
+
* Default 5_000.
|
|
25
|
+
*/
|
|
26
|
+
graceMs?: number;
|
|
27
|
+
}
|
|
28
|
+
declare class RedisPushRefStore implements PushRefStore {
|
|
29
|
+
private redis;
|
|
30
|
+
private prefix;
|
|
31
|
+
private graceMs;
|
|
32
|
+
constructor(redis: RedisLike, opts?: RedisPushRefStoreOptions);
|
|
33
|
+
get(pushRef: string): Promise<SerializedEntry | undefined>;
|
|
34
|
+
set(pushRef: string, entry: SerializedEntry): Promise<void>;
|
|
35
|
+
delete(pushRef: string): Promise<void>;
|
|
36
|
+
claim(args: {
|
|
37
|
+
pushRef: string;
|
|
38
|
+
principal: Principal;
|
|
39
|
+
now: number;
|
|
40
|
+
ttlMs: number;
|
|
41
|
+
mintFresh: () => SerializedEntry;
|
|
42
|
+
maxEntries: number;
|
|
43
|
+
countNonExpired: () => Promise<number>;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
outcome: "new" | "resumed" | "rejected";
|
|
46
|
+
entry?: SerializedEntry;
|
|
47
|
+
supersededActive?: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
releaseIfGen(args: {
|
|
50
|
+
pushRef: string;
|
|
51
|
+
expectedGeneration: number;
|
|
52
|
+
snapshot?: EnvelopeSnapshot;
|
|
53
|
+
expiresAt: number;
|
|
54
|
+
}): Promise<FencedWriteOutcome>;
|
|
55
|
+
persistIfGen(args: {
|
|
56
|
+
pushRef: string;
|
|
57
|
+
expectedGeneration: number;
|
|
58
|
+
snapshot: EnvelopeSnapshot;
|
|
59
|
+
expiresAt: number;
|
|
60
|
+
}): Promise<FencedWriteOutcome>;
|
|
61
|
+
stageIfInactive(args: {
|
|
62
|
+
pushRef: string;
|
|
63
|
+
payload: unknown;
|
|
64
|
+
timestamp: number;
|
|
65
|
+
expiresAt: number;
|
|
66
|
+
}): Promise<"ok" | "missing" | "active" | "raw">;
|
|
67
|
+
gc(_now: number): Promise<number>;
|
|
68
|
+
size(): Promise<number>;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { RedisLike, RedisPushRefStore, RedisPushRefStoreOptions };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//#region src/integrations/websocket-pushref-redis.ts
|
|
2
|
+
/**
|
|
3
|
+
* Lua script for atomic claim. Runs server-side, single-roundtrip.
|
|
4
|
+
*
|
|
5
|
+
* KEYS[1] = key
|
|
6
|
+
* ARGV[1] = principal
|
|
7
|
+
* ARGV[2] = now (ms)
|
|
8
|
+
* ARGV[3] = ttlMs
|
|
9
|
+
* ARGV[4] = mintedJson (serialized SerializedEntry for new-claim path)
|
|
10
|
+
* ARGV[5] = graceMs (added to PEXPIRE so the broker GC's stale keys)
|
|
11
|
+
*
|
|
12
|
+
* Returns: { outcome, json, supersededActive } where
|
|
13
|
+
* outcome ∈ {"new","resumed","rejected"}
|
|
14
|
+
* json is the SerializedEntry (or empty string for reject)
|
|
15
|
+
* supersededActive is "1" when the previous entry was still active
|
|
16
|
+
*
|
|
17
|
+
* Generation handling: every resumed claim bumps `entry.generation` so
|
|
18
|
+
* the connection layer can fence the previous owner. The bump happens
|
|
19
|
+
* atomically inside the script — no read-modify-write race across nodes.
|
|
20
|
+
*/
|
|
21
|
+
const CLAIM_SCRIPT = `
|
|
22
|
+
local existing = redis.call('GET', KEYS[1])
|
|
23
|
+
local now = tonumber(ARGV[2])
|
|
24
|
+
local ttl = tonumber(ARGV[3])
|
|
25
|
+
local pexpire = ttl + tonumber(ARGV[5])
|
|
26
|
+
if existing then
|
|
27
|
+
local entry = cjson.decode(existing)
|
|
28
|
+
if entry.active == false and entry.expiresAt <= now then
|
|
29
|
+
-- expired — fall through to new-claim path
|
|
30
|
+
else
|
|
31
|
+
if entry.principal ~= ARGV[1] then
|
|
32
|
+
return {'rejected', '', '0'}
|
|
33
|
+
end
|
|
34
|
+
local superseded = '0'
|
|
35
|
+
if entry.active == true then
|
|
36
|
+
superseded = '1'
|
|
37
|
+
end
|
|
38
|
+
entry.active = true
|
|
39
|
+
entry.expiresAt = now + ttl
|
|
40
|
+
entry.generation = (entry.generation or 0) + 1
|
|
41
|
+
local payload = cjson.encode(entry)
|
|
42
|
+
redis.call('SET', KEYS[1], payload, 'PX', pexpire)
|
|
43
|
+
return {'resumed', payload, superseded}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
redis.call('SET', KEYS[1], ARGV[4], 'PX', pexpire)
|
|
47
|
+
return {'new', ARGV[4], '0'}
|
|
48
|
+
`;
|
|
49
|
+
/**
|
|
50
|
+
* Generation-fenced release. Single Lua roundtrip:
|
|
51
|
+
*
|
|
52
|
+
* KEYS[1] = key
|
|
53
|
+
* ARGV[1] = expectedGeneration
|
|
54
|
+
* ARGV[2] = expiresAt (ms epoch)
|
|
55
|
+
* ARGV[3] = pexpire ms (relative)
|
|
56
|
+
* ARGV[4] = snapshotJson or empty string (skip envelope flush when empty)
|
|
57
|
+
*
|
|
58
|
+
* Returns "ok" | "stale" | "missing".
|
|
59
|
+
*
|
|
60
|
+
* Stale outcome means another node has claimed a higher generation
|
|
61
|
+
* since the caller captured theirs — caller's release is dropped at
|
|
62
|
+
* the store level. This closes the 2.17.2 race where a fenced Node A's
|
|
63
|
+
* close handler could mutate Node B's freshly-claimed entry.
|
|
64
|
+
*/
|
|
65
|
+
const RELEASE_IF_GEN_SCRIPT = `
|
|
66
|
+
local existing = redis.call('GET', KEYS[1])
|
|
67
|
+
if not existing then return 'missing' end
|
|
68
|
+
local entry = cjson.decode(existing)
|
|
69
|
+
local expected = tonumber(ARGV[1])
|
|
70
|
+
if entry.generation ~= expected then return 'stale' end
|
|
71
|
+
entry.active = false
|
|
72
|
+
entry.expiresAt = tonumber(ARGV[2])
|
|
73
|
+
if ARGV[4] ~= '' then
|
|
74
|
+
local snap = cjson.decode(ARGV[4])
|
|
75
|
+
entry.nextSeq = snap.nextSeq
|
|
76
|
+
entry.deadQueue = snap.deadQueue
|
|
77
|
+
end
|
|
78
|
+
redis.call('SET', KEYS[1], cjson.encode(entry), 'PX', tonumber(ARGV[3]))
|
|
79
|
+
return 'ok'
|
|
80
|
+
`;
|
|
81
|
+
/**
|
|
82
|
+
* Atomic offline-stage. Closes the read-modify-write race the 2.17.2
|
|
83
|
+
* review surfaced for `stageForResume` — a get-then-set could clobber
|
|
84
|
+
* a freshly-claimed higher-generation entry with the stale inactive
|
|
85
|
+
* snapshot. The whole sequence (test active + seq mode, assign seq,
|
|
86
|
+
* wrap payload, append, trim, extend TTL) runs in one EVAL.
|
|
87
|
+
*
|
|
88
|
+
* KEYS[1] = key
|
|
89
|
+
* ARGV[1] = payloadJson (raw payload, NOT pre-wrapped)
|
|
90
|
+
* ARGV[2] = timestamp (ms, server clock)
|
|
91
|
+
* ARGV[3] = expiresAt (ms epoch)
|
|
92
|
+
* ARGV[4] = pexpire ms
|
|
93
|
+
*
|
|
94
|
+
* Returns "ok" | "missing" | "active" | "raw".
|
|
95
|
+
*
|
|
96
|
+
* Note on JSON key order: cjson.encode and JSON.stringify may emit
|
|
97
|
+
* different orderings; consumers parse the envelope and read by key,
|
|
98
|
+
* so order is not part of the wire contract.
|
|
99
|
+
*/
|
|
100
|
+
const STAGE_IF_INACTIVE_SCRIPT = `
|
|
101
|
+
local existing = redis.call('GET', KEYS[1])
|
|
102
|
+
if not existing then return 'missing' end
|
|
103
|
+
local entry = cjson.decode(existing)
|
|
104
|
+
if entry.active then return 'active' end
|
|
105
|
+
if entry.envelopeMode ~= 'seq' then return 'raw' end
|
|
106
|
+
local seq = entry.nextSeq
|
|
107
|
+
entry.nextSeq = seq + 1
|
|
108
|
+
local payload = cjson.decode(ARGV[1])
|
|
109
|
+
local envelope = { seq = seq, t = tonumber(ARGV[2]), msg = payload }
|
|
110
|
+
local serialized = cjson.encode(envelope)
|
|
111
|
+
if not entry.deadQueue then entry.deadQueue = {} end
|
|
112
|
+
table.insert(entry.deadQueue, { seq = seq, payload = serialized })
|
|
113
|
+
while #entry.deadQueue > entry.deadQueueSize do
|
|
114
|
+
table.remove(entry.deadQueue, 1)
|
|
115
|
+
end
|
|
116
|
+
entry.expiresAt = tonumber(ARGV[3])
|
|
117
|
+
redis.call('SET', KEYS[1], cjson.encode(entry), 'PX', tonumber(ARGV[4]))
|
|
118
|
+
return 'ok'
|
|
119
|
+
`;
|
|
120
|
+
/**
|
|
121
|
+
* Generation-fenced persist. Same atomicity contract as release.
|
|
122
|
+
*
|
|
123
|
+
* KEYS[1] = key
|
|
124
|
+
* ARGV[1] = expectedGeneration
|
|
125
|
+
* ARGV[2] = expiresAt
|
|
126
|
+
* ARGV[3] = pexpire ms
|
|
127
|
+
* ARGV[4] = snapshotJson (required for persist — non-empty)
|
|
128
|
+
*/
|
|
129
|
+
const PERSIST_IF_GEN_SCRIPT = `
|
|
130
|
+
local existing = redis.call('GET', KEYS[1])
|
|
131
|
+
if not existing then return 'missing' end
|
|
132
|
+
local entry = cjson.decode(existing)
|
|
133
|
+
local expected = tonumber(ARGV[1])
|
|
134
|
+
if entry.generation ~= expected then return 'stale' end
|
|
135
|
+
local snap = cjson.decode(ARGV[4])
|
|
136
|
+
entry.nextSeq = snap.nextSeq
|
|
137
|
+
entry.deadQueue = snap.deadQueue
|
|
138
|
+
entry.expiresAt = tonumber(ARGV[2])
|
|
139
|
+
redis.call('SET', KEYS[1], cjson.encode(entry), 'PX', tonumber(ARGV[3]))
|
|
140
|
+
return 'ok'
|
|
141
|
+
`;
|
|
142
|
+
var RedisPushRefStore = class {
|
|
143
|
+
redis;
|
|
144
|
+
prefix;
|
|
145
|
+
graceMs;
|
|
146
|
+
constructor(redis, opts = {}) {
|
|
147
|
+
this.redis = redis;
|
|
148
|
+
this.prefix = opts.keyPrefix ?? "arc:pushref:";
|
|
149
|
+
this.graceMs = opts.graceMs ?? 5e3;
|
|
150
|
+
}
|
|
151
|
+
async get(pushRef) {
|
|
152
|
+
const raw = await this.redis.get(this.prefix + pushRef);
|
|
153
|
+
if (!raw) return void 0;
|
|
154
|
+
return JSON.parse(raw);
|
|
155
|
+
}
|
|
156
|
+
async set(pushRef, entry) {
|
|
157
|
+
const json = JSON.stringify(entry);
|
|
158
|
+
const pexpire = Math.max(1e3, entry.expiresAt - Date.now() + this.graceMs);
|
|
159
|
+
await this.redis.set(this.prefix + pushRef, json, "PX", pexpire);
|
|
160
|
+
}
|
|
161
|
+
async delete(pushRef) {
|
|
162
|
+
await this.redis.del(this.prefix + pushRef);
|
|
163
|
+
}
|
|
164
|
+
async claim(args) {
|
|
165
|
+
if (args.maxEntries > 0) {
|
|
166
|
+
if (await args.countNonExpired() >= args.maxEntries) {}
|
|
167
|
+
}
|
|
168
|
+
const minted = JSON.stringify(args.mintFresh());
|
|
169
|
+
const result = await this.redis.eval(CLAIM_SCRIPT, 1, this.prefix + args.pushRef, args.principal, args.now, args.ttlMs, minted, this.graceMs);
|
|
170
|
+
const outcome = result[0];
|
|
171
|
+
if (outcome === "rejected") return { outcome };
|
|
172
|
+
return {
|
|
173
|
+
outcome,
|
|
174
|
+
entry: JSON.parse(result[1]),
|
|
175
|
+
supersededActive: result[2] === "1"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async releaseIfGen(args) {
|
|
179
|
+
const pexpire = Math.max(1e3, args.expiresAt - Date.now() + this.graceMs);
|
|
180
|
+
return await this.redis.eval(RELEASE_IF_GEN_SCRIPT, 1, this.prefix + args.pushRef, args.expectedGeneration, args.expiresAt, pexpire, args.snapshot ? JSON.stringify(args.snapshot) : "");
|
|
181
|
+
}
|
|
182
|
+
async persistIfGen(args) {
|
|
183
|
+
const pexpire = Math.max(1e3, args.expiresAt - Date.now() + this.graceMs);
|
|
184
|
+
return await this.redis.eval(PERSIST_IF_GEN_SCRIPT, 1, this.prefix + args.pushRef, args.expectedGeneration, args.expiresAt, pexpire, JSON.stringify(args.snapshot));
|
|
185
|
+
}
|
|
186
|
+
async stageIfInactive(args) {
|
|
187
|
+
const pexpire = Math.max(1e3, args.expiresAt - Date.now() + this.graceMs);
|
|
188
|
+
return await this.redis.eval(STAGE_IF_INACTIVE_SCRIPT, 1, this.prefix + args.pushRef, JSON.stringify(args.payload), args.timestamp, args.expiresAt, pexpire);
|
|
189
|
+
}
|
|
190
|
+
async gc(_now) {
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
async size() {
|
|
194
|
+
let cursor = "0";
|
|
195
|
+
let count = 0;
|
|
196
|
+
do {
|
|
197
|
+
const [next, keys] = await this.redis.scan(cursor, "MATCH", `${this.prefix}*`, "COUNT", 500);
|
|
198
|
+
count += keys.length;
|
|
199
|
+
cursor = next;
|
|
200
|
+
} while (cursor !== "0");
|
|
201
|
+
return count;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
//#endregion
|
|
205
|
+
export { RedisPushRefStore };
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
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-BZMUP7Gr.mjs";
|
|
2
|
+
import { a as PushRefRegistry, c as SerializedEntry, i as Principal, o as PushRefRegistryOptions, r as MemoryPushRefStore, s as PushRefStore } from "../pushref-registry-Bq_-EVsC.mjs";
|
|
3
|
+
export { AuthResult, GeoPoint, GeoPublishOpts, GeoRoom, GeoRoomOptions, LocalWebSocketAdapter, MemoryPushRefStore, Principal, PushRefRegistry, PushRefRegistryOptions, PushRefStore, RoomManager, SerializedEntry, WebSocketAdapter, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, websocketPlugin };
|