@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
|
@@ -64,5 +64,30 @@ const requestContext = {
|
|
|
64
64
|
*/
|
|
65
65
|
storage
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Returns the W3C Trace Context headers (`traceparent`, `tracestate`) from
|
|
69
|
+
* the current request context as a plain `Record<string, string>`, suitable
|
|
70
|
+
* for passing to `OutboxWriteOptions.headers` or forwarding to downstream
|
|
71
|
+
* HTTP clients.
|
|
72
|
+
*
|
|
73
|
+
* Returns `undefined` when called outside a request scope or when no trace
|
|
74
|
+
* context was propagated by the upstream caller.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Propagate trace context through the outbox so downstream consumers
|
|
79
|
+
* // can stitch together distributed traces.
|
|
80
|
+
* const traceHeaders = getTraceHeaders();
|
|
81
|
+
* await outbox.store(event, { headers: traceHeaders });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
function getTraceHeaders() {
|
|
85
|
+
const store = requestContext.get();
|
|
86
|
+
if (!store) return void 0;
|
|
87
|
+
const headers = {};
|
|
88
|
+
if (store.traceparent) headers["traceparent"] = store.traceparent;
|
|
89
|
+
if (store.tracestate) headers["tracestate"] = store.tracestate;
|
|
90
|
+
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
91
|
+
}
|
|
67
92
|
//#endregion
|
|
68
|
-
export { requestContext as t };
|
|
93
|
+
export { requestContext as n, getTraceHeaders as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { h as SYSTEM_FIELDS } from "./constants-TrJVIJl0.mjs";
|
|
2
2
|
import { f as createError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
|
|
3
3
|
import { b as isElevated, c as getOrgId, g as getUserId, n as PUBLIC_SCOPE, x as isMember } from "./types-Bi0r0vjG.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { n as requestContext } from "./requestContext-DDLS3NVU.mjs";
|
|
5
5
|
import { I as evaluateAndApplyPermission, M as applyFieldWritePermissions, P as resolveEffectiveRoles, j as applyFieldReadPermissions } from "./permissions-B1Qy55fg.mjs";
|
|
6
6
|
import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
|
|
7
7
|
import { t as executePipeline } from "./pipe-DiCyvyPN.mjs";
|
|
@@ -56,7 +56,7 @@ function normalizeSchemaIR(raw) {
|
|
|
56
56
|
required: []
|
|
57
57
|
};
|
|
58
58
|
const properties = converted.properties ?? {};
|
|
59
|
-
const required = Array.isArray(converted.required) ? converted.required : [];
|
|
59
|
+
const required = (Array.isArray(converted.required) ? converted.required : []).filter((field) => !("default" in (properties[field] ?? {})));
|
|
60
60
|
const additionalProperties = converted.additionalProperties;
|
|
61
61
|
return {
|
|
62
62
|
properties,
|
|
@@ -73,14 +73,26 @@ function normalizeSchemaIR(raw) {
|
|
|
73
73
|
* reach AJV intact, so HTTP validation rejects unknown fields before the
|
|
74
74
|
* handler runs. This closes the bug where the documented strict-mode
|
|
75
75
|
* escape hatch silently no-opped because normalization dropped the flag.
|
|
76
|
+
*
|
|
77
|
+
* `default` is stripped from each property schema. Zod v4's `z.toJSONSchema()`
|
|
78
|
+
* emits `default` for `.default()` fields, but AJV with `useDefaults: true`
|
|
79
|
+
* cannot apply defaults inside `oneOf` branches and throws a strict-mode error
|
|
80
|
+
* (`strict mode: default is ignored for: <path>`). Runtime defaults are applied
|
|
81
|
+
* by Zod parsing at the handler layer anyway, so the keyword is a no-op for AJV.
|
|
82
|
+
* The OpenAPI doc path uses a separate `openapi-3.0` target and is unaffected.
|
|
76
83
|
*/
|
|
77
84
|
function schemaIRToJsonSchemaBranch(ir, extras = {}) {
|
|
85
|
+
const mergedProperties = {};
|
|
86
|
+
for (const [key, prop] of Object.entries({
|
|
87
|
+
...extras.properties ?? {},
|
|
88
|
+
...ir.properties
|
|
89
|
+
})) if (typeof prop === "object" && prop !== null && "default" in prop) {
|
|
90
|
+
const { default: _dropped, ...rest } = prop;
|
|
91
|
+
mergedProperties[key] = rest;
|
|
92
|
+
} else mergedProperties[key] = prop;
|
|
78
93
|
return {
|
|
79
94
|
type: "object",
|
|
80
|
-
properties:
|
|
81
|
-
...extras.properties ?? {},
|
|
82
|
-
...ir.properties
|
|
83
|
-
},
|
|
95
|
+
properties: mergedProperties,
|
|
84
96
|
required: [...extras.required ?? [], ...ir.required.filter((f) => !(extras.required ?? []).includes(f))],
|
|
85
97
|
...ir.additionalProperties !== void 0 ? { additionalProperties: ir.additionalProperties } : {}
|
|
86
98
|
};
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Kt as AnyRecord, V as ResourceDefinition } from "../index-
|
|
2
|
-
import { d as preloadResourcesAsync, r as CreateAppOptions, u as preloadResources } from "../types-
|
|
1
|
+
import { Kt as AnyRecord, V as ResourceDefinition } from "../index-C000faom.mjs";
|
|
2
|
+
import { d as preloadResourcesAsync, r as CreateAppOptions, u as preloadResources } from "../types-DEEWwQFR.mjs";
|
|
3
3
|
import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
|
|
4
4
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
5
5
|
import { Mock } from "vitest";
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1036,7 +1036,7 @@ function pickDefaultAuth(authMode, callerAuth) {
|
|
|
1036
1036
|
};
|
|
1037
1037
|
}
|
|
1038
1038
|
async function createTestApp(options = {}) {
|
|
1039
|
-
const { createApp } = await import("../createApp-
|
|
1039
|
+
const { createApp } = await import("../createApp-DZvrb587.mjs").then((n) => n.r);
|
|
1040
1040
|
const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
|
|
1041
1041
|
let dbHandle;
|
|
1042
1042
|
let dbUri;
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as OnTenantDeleteConfig, A as RequestIdOptions, Bt as AuthHelpers, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, G as ActionsMap, Gt as TokenPair, H as ActionDefinition, Ht as Authenticator, I as MiddlewareHandler, J as CrudRouteKey, Jt as ArcRequest, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, M as EventsDecorator, N as FastifyRequestExtras, O as HealthOptions, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, T as CrudRouterOptions, U as ActionEntry, Ut as AuthenticatorContext, Vt as AuthPluginOptions, W as ActionHandlerFn, Wt as JwtContext, X as EventDefinition, Xt as ObjectId, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, Zt as UserLike, _ as ControllerQueryOptions, _t as FastifyHandler, a as InferAdapterDoc, at as ResolvedTenantPurge, b as ParsedQuery, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, ct as ResourceHookContext, d as PaginationResult, dt as RouteDefinition, et as OpenApiSchemas, f as IntrospectionData, ft as RouteMcpConfig, g as ArcInternalMetadata, gt as ControllerLike, h as ResourceMetadata, ht as ControllerHandler, i as ValidationResult, it as RateLimitConfig, j as ArcDecorator, k as IntrospectionPluginOptions, l as TypedRepository, lt as ResourceHooks, m as RegistryStats, mt as RouteSchemaOptions, n as ConfigError, nt as PresetHook, o as InferDocType, ot as ResourceCacheConfig, p as RegistryEntry, pt as RouteMethod, q as CrudController, qt as ApiResponse, r as ValidateOptions, rt as PresetResult, s as InferResourceDoc, st as ResourceConfig, t as RouteHandlerMethod, tt as PresetFunction, u as TypedResourceConfig, ut as ResourcePermissions, v as LookupOption, vt as IController, w as ServiceContext, x as PopulateOption, xt as RouteHandler, y as OwnershipCheck, yt as IControllerResponse } from "../index-
|
|
1
|
+
import { $ as OnTenantDeleteConfig, A as RequestIdOptions, Bt as AuthHelpers, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, G as ActionsMap, Gt as TokenPair, H as ActionDefinition, Ht as Authenticator, I as MiddlewareHandler, J as CrudRouteKey, Jt as ArcRequest, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, M as EventsDecorator, N as FastifyRequestExtras, O as HealthOptions, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, T as CrudRouterOptions, U as ActionEntry, Ut as AuthenticatorContext, Vt as AuthPluginOptions, W as ActionHandlerFn, Wt as JwtContext, X as EventDefinition, Xt as ObjectId, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, Zt as UserLike, _ as ControllerQueryOptions, _t as FastifyHandler, a as InferAdapterDoc, at as ResolvedTenantPurge, b as ParsedQuery, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, ct as ResourceHookContext, d as PaginationResult, dt as RouteDefinition, et as OpenApiSchemas, f as IntrospectionData, ft as RouteMcpConfig, g as ArcInternalMetadata, gt as ControllerLike, h as ResourceMetadata, ht as ControllerHandler, i as ValidationResult, it as RateLimitConfig, j as ArcDecorator, k as IntrospectionPluginOptions, l as TypedRepository, lt as ResourceHooks, m as RegistryStats, mt as RouteSchemaOptions, n as ConfigError, nt as PresetHook, o as InferDocType, ot as ResourceCacheConfig, p as RegistryEntry, pt as RouteMethod, q as CrudController, qt as ApiResponse, r as ValidateOptions, rt as PresetResult, s as InferResourceDoc, st as ResourceConfig, t as RouteHandlerMethod, tt as PresetFunction, u as TypedResourceConfig, ut as ResourcePermissions, v as LookupOption, vt as IController, w as ServiceContext, x as PopulateOption, xt as RouteHandler, y as OwnershipCheck, yt as IControllerResponse } from "../index-C000faom.mjs";
|
|
2
2
|
import { i as RequestScope } from "../types-CfapW2t8.mjs";
|
|
3
3
|
import { c as PermissionCheck, d as UserBase, l as PermissionContext, u as PermissionResult } from "../fields-DqZLp5d6.mjs";
|
|
4
4
|
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-DvwtjDwu.mjs";
|
package/dist/types/storage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-
|
|
1
|
+
import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-BFmamN5m.mjs";
|
|
2
2
|
export { Storage, StorageContext, StorageFile, StorageReadRange, StorageReadResult, StorageUploadInput };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as CacheStore } from "./interface-BVkZsvEe.mjs";
|
|
2
|
-
import { Ht as Authenticator } from "./index-
|
|
2
|
+
import { Ht as Authenticator } from "./index-C000faom.mjs";
|
|
3
3
|
import { n as ElevationOptions } from "./elevation-DvwtjDwu.mjs";
|
|
4
4
|
import { a as EventTransport } from "./EventTransport-B1GFiYUy.mjs";
|
|
5
5
|
import { t as ExternalOpenApiPaths } from "./externalPaths-ClykOQqa.mjs";
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Kt as AnyRecord, S as QueryParserInterface, Zt as UserLike, b as ParsedQuery, et as OpenApiSchemas, st as ResourceConfig } from "../index-
|
|
1
|
+
import { Kt as AnyRecord, S as QueryParserInterface, Zt as UserLike, b as ParsedQuery, et as OpenApiSchemas, st as ResourceConfig } from "../index-C000faom.mjs";
|
|
2
2
|
import { n as ErrorMapper } from "../errorHandler-DuqTqeqn.mjs";
|
|
3
3
|
import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorOptions, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-4ZloYFvm.mjs";
|
|
4
4
|
import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { a as PushRefRegistry, l as EnvelopeWriter, s as PushRefStore } from "./pushref-registry-Bq_-EVsC.mjs";
|
|
2
|
+
import { FastifyBaseLogger, FastifyPluginAsync } from "fastify";
|
|
3
|
+
|
|
4
|
+
//#region src/integrations/websocket/adapter.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* WebSocket cross-instance adapter contract.
|
|
7
|
+
*
|
|
8
|
+
* The adapter is NOT used for local broadcasts — `RoomManager` handles those.
|
|
9
|
+
* The adapter only handles the cross-instance relay (Redis pub/sub, NATS, etc.)
|
|
10
|
+
* so a message broadcast on instance A is also delivered to clients connected
|
|
11
|
+
* to instance B.
|
|
12
|
+
*
|
|
13
|
+
* Implementations:
|
|
14
|
+
* - `LocalWebSocketAdapter` (here) — no-op, single-instance only
|
|
15
|
+
* - `RedisWebSocketAdapter` (@classytic/arc/integrations/websocket-redis)
|
|
16
|
+
*
|
|
17
|
+
* Custom adapters just need to satisfy the interface.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Adapter interface for cross-instance WebSocket broadcast.
|
|
21
|
+
*
|
|
22
|
+
* - `publish()`: Send a message to all instances (via Redis, NATS, etc.)
|
|
23
|
+
* - `subscribe()`: Receive messages from other instances
|
|
24
|
+
* - `close()`: Clean up connections
|
|
25
|
+
*/
|
|
26
|
+
interface WebSocketAdapter {
|
|
27
|
+
/** Adapter name for logging */
|
|
28
|
+
readonly name: string;
|
|
29
|
+
/** Publish a room broadcast to all other instances */
|
|
30
|
+
publish(room: string, message: string): Promise<void>;
|
|
31
|
+
/** Subscribe to broadcasts from other instances */
|
|
32
|
+
subscribe(callback: (room: string, message: string) => void): Promise<void>;
|
|
33
|
+
/** Close adapter connections */
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Default adapter — no cross-instance broadcast (single-instance only).
|
|
38
|
+
* All methods are no-ops. Used when no adapter is configured.
|
|
39
|
+
*/
|
|
40
|
+
declare class LocalWebSocketAdapter implements WebSocketAdapter {
|
|
41
|
+
readonly name = "local";
|
|
42
|
+
publish(): Promise<void>;
|
|
43
|
+
subscribe(): Promise<void>;
|
|
44
|
+
close(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/integrations/websocket/safe-async.d.ts
|
|
48
|
+
type SafeAsyncLogger = Pick<FastifyBaseLogger, "warn">;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/integrations/websocket/send-queue.d.ts
|
|
51
|
+
interface SendQueueSocket {
|
|
52
|
+
send(data: string): void;
|
|
53
|
+
readyState: number;
|
|
54
|
+
bufferedAmount?: number;
|
|
55
|
+
terminate?(): void;
|
|
56
|
+
close?(code?: number, reason?: string): void;
|
|
57
|
+
}
|
|
58
|
+
interface SendQueueOptions {
|
|
59
|
+
capacity?: number;
|
|
60
|
+
drainThreshold?: number;
|
|
61
|
+
/** Auto-flush retry delay in ms when backpressure pauses draining. */
|
|
62
|
+
retryDelayMs?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Per-socket bounded queue. Construct one per accepted connection;
|
|
66
|
+
* `dispose()` on socket close to drop refs.
|
|
67
|
+
*/
|
|
68
|
+
declare class SendQueue {
|
|
69
|
+
private entries;
|
|
70
|
+
/** `coalesceKey → entries[] index` for O(1) replacement. */
|
|
71
|
+
private coalesceIndex;
|
|
72
|
+
private socket;
|
|
73
|
+
private capacity;
|
|
74
|
+
private drainThreshold;
|
|
75
|
+
private retryDelayMs;
|
|
76
|
+
/** One-shot retry timer set while waiting for backpressure to clear. */
|
|
77
|
+
private retryTimer?;
|
|
78
|
+
private disposed;
|
|
79
|
+
constructor(socket: SendQueueSocket, opts?: SendQueueOptions);
|
|
80
|
+
/** Ordered, guaranteed-best-effort delivery. Overflow ⇒ terminate. */
|
|
81
|
+
send(payload: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Fire-and-forget, droppable. With `coalesceKey`, older entry for the
|
|
84
|
+
* same key is replaced in place (preserves queue order, keeps latest
|
|
85
|
+
* payload). Without a key, oldest droppable entry is shed on overflow.
|
|
86
|
+
*/
|
|
87
|
+
sendRealtime(payload: string, coalesceKey?: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Drain entries to the socket until either the queue empties or the
|
|
90
|
+
* socket's outbound buffer crosses the drain threshold. Idempotent —
|
|
91
|
+
* also runs on a self-scheduled retry timer when backpressure pauses
|
|
92
|
+
* draining (so callers never need to manually re-prod the queue).
|
|
93
|
+
*/
|
|
94
|
+
flush(): void;
|
|
95
|
+
size(): number;
|
|
96
|
+
dispose(): void;
|
|
97
|
+
/**
|
|
98
|
+
* One-shot retry timer. Called when `flush()` hits backpressure and
|
|
99
|
+
* leaves entries queued — without this, queued frames would stall
|
|
100
|
+
* indefinitely after the socket's buffer drains, because nothing
|
|
101
|
+
* else prods the queue. The timer self-clears on each fire so a
|
|
102
|
+
* single delay is the worst case between drain and resume.
|
|
103
|
+
*/
|
|
104
|
+
private scheduleRetry;
|
|
105
|
+
private terminate;
|
|
106
|
+
/** Rebuild the coalesce index from `start` onward after a splice/shift. */
|
|
107
|
+
private reindexCoalesceFrom;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/integrations/websocket/types.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* A connected WebSocket client — one entry per TCP socket.
|
|
113
|
+
*
|
|
114
|
+
* `subscriptions` is mutated by `RoomManager`; other fields are set once at
|
|
115
|
+
* handshake time and treated as effectively immutable for the lifetime of
|
|
116
|
+
* the connection.
|
|
117
|
+
*/
|
|
118
|
+
interface WebSocketClient {
|
|
119
|
+
/**
|
|
120
|
+
* Transport-level connection ID (one per TCP socket). Stable for the
|
|
121
|
+
* lifetime of THIS connection only — a reconnect mints a new `id`.
|
|
122
|
+
*/
|
|
123
|
+
id: string;
|
|
124
|
+
/**
|
|
125
|
+
* Session reference — stable across reconnects, distinct from `userId`.
|
|
126
|
+
* One user with three browser tabs = three `pushRef`s, one `userId`.
|
|
127
|
+
* Routing layer keys off this for "send to THIS tab" semantics.
|
|
128
|
+
*
|
|
129
|
+
* Clients MAY supply their own pushRef via the `?pushRef=` query param
|
|
130
|
+
* on the upgrade URL (e.g. resumed reconnect); otherwise the server mints
|
|
131
|
+
* one at handshake time.
|
|
132
|
+
*/
|
|
133
|
+
pushRef: string;
|
|
134
|
+
socket: {
|
|
135
|
+
send(data: string): void;
|
|
136
|
+
close(code?: number, reason?: string): void;
|
|
137
|
+
readyState: number;
|
|
138
|
+
bufferedAmount?: number; /** RFC 6455 §5.5.2 ping control frame. Browsers auto-respond at the protocol layer. */
|
|
139
|
+
ping?(data?: unknown): void; /** Immediate socket destroy — no Close frame, no flush. Use for zombies. */
|
|
140
|
+
terminate?(): void;
|
|
141
|
+
};
|
|
142
|
+
subscriptions: Set<string>;
|
|
143
|
+
userId?: string;
|
|
144
|
+
organizationId?: string;
|
|
145
|
+
/** OAuth client ID — present for service/machine-to-machine connections */
|
|
146
|
+
clientId?: string;
|
|
147
|
+
/** OAuth scopes — present for service/machine-to-machine connections */
|
|
148
|
+
scopes?: readonly string[];
|
|
149
|
+
metadata?: Record<string, unknown>;
|
|
150
|
+
/**
|
|
151
|
+
* Outbound send queue — created at connect time, disposed on close.
|
|
152
|
+
* `RoomManager.send/sendRealtime` enqueue via this; the raw
|
|
153
|
+
* `client.socket.send()` path bypasses queue/coalesce/overflow policy
|
|
154
|
+
* and should only be used for the connection-lifecycle handshake.
|
|
155
|
+
*/
|
|
156
|
+
queue?: SendQueue;
|
|
157
|
+
/**
|
|
158
|
+
* Sequence-numbered envelope writer + dead queue. Present iff the
|
|
159
|
+
* plugin was started with `messageEnvelope: 'seq'`. Critical sends are
|
|
160
|
+
* wrapped via this so the client can RESUME after reconnect.
|
|
161
|
+
*/
|
|
162
|
+
envelope?: EnvelopeWriter;
|
|
163
|
+
/**
|
|
164
|
+
* Connection generation captured at claim time. The fence + CAS
|
|
165
|
+
* write paths use this — every release/persist passes it so the
|
|
166
|
+
* store can refuse stale writes from a connection that's been
|
|
167
|
+
* superseded by a newer claim on another node.
|
|
168
|
+
*/
|
|
169
|
+
generation: number;
|
|
170
|
+
}
|
|
171
|
+
interface WebSocketMessage {
|
|
172
|
+
type: string;
|
|
173
|
+
resource?: string;
|
|
174
|
+
channel?: string;
|
|
175
|
+
data?: unknown;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Result of a successful authentication. The plugin's handshake and the
|
|
179
|
+
* optional re-auth loop both return this shape so the downstream code
|
|
180
|
+
* doesn't branch on auth mode. `null` means rejected.
|
|
181
|
+
*/
|
|
182
|
+
interface AuthResult {
|
|
183
|
+
userId?: string;
|
|
184
|
+
organizationId?: string;
|
|
185
|
+
/** Set for machine-to-machine / service account auth */
|
|
186
|
+
clientId?: string;
|
|
187
|
+
/** OAuth scopes for service accounts */
|
|
188
|
+
scopes?: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
interface WebSocketPluginOptions {
|
|
191
|
+
/** WebSocket endpoint path (default: '/ws') */
|
|
192
|
+
path?: string;
|
|
193
|
+
/** Require authentication for WebSocket connections (default: true) */
|
|
194
|
+
auth?: boolean;
|
|
195
|
+
/** Resources to auto-broadcast CRUD events for */
|
|
196
|
+
resources?: string[];
|
|
197
|
+
/** Heartbeat interval in ms (default: 30000). Set 0 to disable. */
|
|
198
|
+
heartbeatInterval?: number;
|
|
199
|
+
/** Custom authentication function for WebSocket upgrade */
|
|
200
|
+
authenticate?: (request: unknown) => Promise<AuthResult | null>;
|
|
201
|
+
/** Max clients per resource subscription (default: 10000) */
|
|
202
|
+
maxClientsPerRoom?: number;
|
|
203
|
+
/**
|
|
204
|
+
* Expose a stats endpoint at `{path}/stats`.
|
|
205
|
+
* - `false` (default): stats endpoint is not registered
|
|
206
|
+
* - `true`: registered without auth
|
|
207
|
+
* - `'authenticated'`: guarded by `fastify.authenticate` if available
|
|
208
|
+
*/
|
|
209
|
+
exposeStats?: boolean | "authenticated";
|
|
210
|
+
/**
|
|
211
|
+
* Authorize room subscriptions. Return true to allow, false to deny.
|
|
212
|
+
* Called before every subscribe. If not provided, all rooms are allowed.
|
|
213
|
+
*/
|
|
214
|
+
roomPolicy?: (client: WebSocketClient, room: string) => boolean | Promise<boolean>;
|
|
215
|
+
/** Maximum message size in bytes from client (default: 16384 = 16KB). Messages exceeding this are dropped. */
|
|
216
|
+
maxMessageBytes?: number;
|
|
217
|
+
/** Maximum subscriptions per client (default: 100). Prevents resource exhaustion. */
|
|
218
|
+
maxSubscriptionsPerClient?: number;
|
|
219
|
+
/**
|
|
220
|
+
* Periodic re-authentication interval in ms (default: 0 = disabled).
|
|
221
|
+
* When set, the server periodically re-validates the client's auth token.
|
|
222
|
+
* If the token is expired/revoked, the client is disconnected with code 4003.
|
|
223
|
+
*
|
|
224
|
+
* Recommended: 300000 (5 minutes) for production.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* websocketPlugin({ reauthInterval: 5 * 60 * 1000 }) // re-check every 5 min
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
reauthInterval?: number;
|
|
232
|
+
/** Custom message handler */
|
|
233
|
+
onMessage?: (client: WebSocketClient, message: WebSocketMessage) => void | Promise<void>;
|
|
234
|
+
/** Called when a client connects */
|
|
235
|
+
onConnect?: (client: WebSocketClient) => void | Promise<void>;
|
|
236
|
+
/** Called when a client disconnects */
|
|
237
|
+
onDisconnect?: (client: WebSocketClient) => void | Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Cross-instance broadcast adapter (default: LocalWebSocketAdapter — single-instance only).
|
|
240
|
+
* Provide a RedisWebSocketAdapter for multi-instance deployments.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* import { RedisWebSocketAdapter } from '@classytic/arc/integrations/websocket-redis';
|
|
245
|
+
* adapter: new RedisWebSocketAdapter(redis, { channel: 'arc-ws' })
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
adapter?: WebSocketAdapter;
|
|
249
|
+
/**
|
|
250
|
+
* Outbound message wire format.
|
|
251
|
+
* - `'raw'` (default): write payloads as-is — no envelope, no replay.
|
|
252
|
+
* - `'seq'`: wrap every critical send in `{seq, t, msg}`. The dead
|
|
253
|
+
* queue of the last N envelopes is owned by the **PushRefRegistry**
|
|
254
|
+
* (per-pushRef / per-session, NOT per-connection) so a client
|
|
255
|
+
* reconnecting under the same pushRef within `pushRefTtlMs` can
|
|
256
|
+
* RESUME via `{type:'resume', lastSeq}` and replay missed messages
|
|
257
|
+
* across the disconnect — not just within a single socket.
|
|
258
|
+
*
|
|
259
|
+
* Opt-in to preserve compatibility with existing clients. Set to
|
|
260
|
+
* `'seq'` for chat, ride-status, payment flows — anything where a
|
|
261
|
+
* message lost mid-disconnect is a real bug.
|
|
262
|
+
*/
|
|
263
|
+
messageEnvelope?: "raw" | "seq";
|
|
264
|
+
/** Dead queue size (per pushRef) when `messageEnvelope: 'seq'`. Defaults to 128. */
|
|
265
|
+
deadQueueSize?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Time (ms) a released pushRef stays in the registry waiting for a
|
|
268
|
+
* reconnect to RESUME against its envelope + dead queue. Past this
|
|
269
|
+
* window the entry is GC'd. Default `60_000` (60 s) — typical mobile
|
|
270
|
+
* lock-screen + carrier-handoff reconnect window.
|
|
271
|
+
*/
|
|
272
|
+
pushRefTtlMs?: number;
|
|
273
|
+
/**
|
|
274
|
+
* Hard cap on pushRef registry entries. When full, the longest-inactive
|
|
275
|
+
* entry is evicted to make room. Default `10_000` — sized for a single
|
|
276
|
+
* Node process supporting tens of thousands of concurrent users with
|
|
277
|
+
* tab-level granularity.
|
|
278
|
+
*/
|
|
279
|
+
pushRefMaxEntries?: number;
|
|
280
|
+
/**
|
|
281
|
+
* Maximum bytes per outbound frame. Payloads exceeding this are
|
|
282
|
+
* replaced with a `{type:'truncated', originalBytes, hint}` placeholder
|
|
283
|
+
* so a misbehaving server-side emitter can't pin socket buffers across
|
|
284
|
+
* the fleet. Default `5 * 1024 * 1024` (5 MiB) — same as n8n's
|
|
285
|
+
* `relayViaPubSub` ceiling. Hosts may tighten for low-bandwidth tiers.
|
|
286
|
+
*/
|
|
287
|
+
maxOutboundBytes?: number;
|
|
288
|
+
/**
|
|
289
|
+
* Optional external store for cross-instance pushRef state. When
|
|
290
|
+
* provided, the envelope + dead queue survive reconnects to ANY node
|
|
291
|
+
* in the cluster (not just the originator). See
|
|
292
|
+
* `@classytic/arc/integrations/websocket-pushref-redis` for the
|
|
293
|
+
* Redis-backed implementation.
|
|
294
|
+
*
|
|
295
|
+
* Without this, the in-memory registry is per-process — fine for
|
|
296
|
+
* single-node deployments OR sticky-session load balancing, but a
|
|
297
|
+
* client reconnecting through a different node loses dead-queue
|
|
298
|
+
* state.
|
|
299
|
+
*/
|
|
300
|
+
pushRefStore?: PushRefStore;
|
|
301
|
+
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/integrations/websocket/room-manager.d.ts
|
|
304
|
+
declare class RoomManager {
|
|
305
|
+
private rooms;
|
|
306
|
+
private clients;
|
|
307
|
+
/**
|
|
308
|
+
* Secondary index: `pushRef → clientId`. A pushRef is stable across
|
|
309
|
+
* reconnects (one per browser tab/device), where `clientId` is per-socket.
|
|
310
|
+
* Lets the host send to a logical session ("this tab") without caring
|
|
311
|
+
* which TCP connection currently backs it.
|
|
312
|
+
*/
|
|
313
|
+
private clientIdByPushRef;
|
|
314
|
+
/**
|
|
315
|
+
* Tertiary index: `userId → Set<clientId>`. Lets `sendToUser(userId, msg)`
|
|
316
|
+
* fan out to every open tab/device for that user without scanning the
|
|
317
|
+
* full client set.
|
|
318
|
+
*/
|
|
319
|
+
private clientIdsByUserId;
|
|
320
|
+
private maxPerRoom;
|
|
321
|
+
private adapter?;
|
|
322
|
+
private registry?;
|
|
323
|
+
private logger;
|
|
324
|
+
constructor(maxPerRoom?: number, adapter?: WebSocketAdapter, registry?: PushRefRegistry, logger?: SafeAsyncLogger);
|
|
325
|
+
addClient(client: WebSocketClient): void;
|
|
326
|
+
removeClient(clientId: string): void;
|
|
327
|
+
/** Resolve a pushRef to its current backing client, if any. */
|
|
328
|
+
getClientByPushRef(pushRef: string): WebSocketClient | undefined;
|
|
329
|
+
/** Snapshot of all clients currently owned by `userId`. */
|
|
330
|
+
getClientsByUserId(userId: string): WebSocketClient[];
|
|
331
|
+
subscribe(clientId: string, room: string): boolean;
|
|
332
|
+
unsubscribe(clientId: string, room: string): void;
|
|
333
|
+
broadcast(room: string, message: string, excludeClientId?: string): void;
|
|
334
|
+
/**
|
|
335
|
+
* Droppable, coalesce-by-key broadcast to a room. Mirrors `broadcast()`
|
|
336
|
+
* but routes through each client's `SendQueue.sendRealtime` so high-
|
|
337
|
+
* frequency streams (GPS, cursors, presence) collapse to one queued
|
|
338
|
+
* frame per recipient.
|
|
339
|
+
*
|
|
340
|
+
* Used by `GeoRoom.publish` for hyperlocal fan-out where the latest
|
|
341
|
+
* snapshot is the only one that matters.
|
|
342
|
+
*/
|
|
343
|
+
broadcastRealtime(room: string, message: string, coalesceKey: string): void;
|
|
344
|
+
broadcastToOrg(organizationId: string, room: string, message: string): void;
|
|
345
|
+
/**
|
|
346
|
+
* Broadcast locally AND through adapter (for cross-instance delivery).
|
|
347
|
+
* Use this instead of broadcast() when multi-instance is possible.
|
|
348
|
+
*/
|
|
349
|
+
broadcastWithAdapter(room: string, message: string, excludeClientId?: string): Promise<void>;
|
|
350
|
+
/**
|
|
351
|
+
* Org-scoped broadcast locally AND through adapter.
|
|
352
|
+
* Uses a namespaced room key for the adapter so other instances
|
|
353
|
+
* can filter by org when delivering locally.
|
|
354
|
+
*/
|
|
355
|
+
broadcastToOrgWithAdapter(organizationId: string, room: string, message: string): Promise<void>;
|
|
356
|
+
/**
|
|
357
|
+
* Addressed sends across instances. Wire format reuses the existing
|
|
358
|
+
* room-prefix convention (`pushref:<id>`, `userid:<id>`) so the adapter
|
|
359
|
+
* interface stays unchanged. Receiver-side plugin code dispatches on the
|
|
360
|
+
* prefix and calls `sendToPushRef` / `sendToUser` locally.
|
|
361
|
+
*
|
|
362
|
+
* Critical guarantee (n8n shouldRelayViaPubSub pattern): the local call
|
|
363
|
+
* is a no-op on every instance EXCEPT the one that owns the pushRef —
|
|
364
|
+
* the address lookup itself is the ownership check.
|
|
365
|
+
*/
|
|
366
|
+
sendToPushRefWithAdapter(pushRef: string, message: string): Promise<void>;
|
|
367
|
+
sendRealtimeToPushRefWithAdapter(pushRef: string, message: string, coalesceKey?: string): Promise<void>;
|
|
368
|
+
sendToUserWithAdapter(userId: string, message: string): Promise<void>;
|
|
369
|
+
sendRealtimeToUserWithAdapter(userId: string, message: string, coalesceKey?: string): Promise<void>;
|
|
370
|
+
/** Ordered send to a single pushRef. Returns true if delivered/queued. */
|
|
371
|
+
sendToPushRef(pushRef: string, message: string): boolean;
|
|
372
|
+
/** Droppable send to a single pushRef. */
|
|
373
|
+
sendRealtimeToPushRef(pushRef: string, message: string, coalesceKey?: string): boolean;
|
|
374
|
+
/**
|
|
375
|
+
* Ordered fan-out to every open connection for `userId` (all tabs/devices).
|
|
376
|
+
* Returns the count successfully queued.
|
|
377
|
+
*/
|
|
378
|
+
sendToUser(userId: string, message: string): number;
|
|
379
|
+
/** Droppable fan-out to every open connection for `userId`. */
|
|
380
|
+
sendRealtimeToUser(userId: string, message: string, coalesceKey?: string): number;
|
|
381
|
+
private deliver;
|
|
382
|
+
getClient(clientId: string): WebSocketClient | undefined;
|
|
383
|
+
getStats(): {
|
|
384
|
+
clients: number;
|
|
385
|
+
rooms: number;
|
|
386
|
+
subscriptions: Record<string, number>;
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/integrations/websocket/geo-room.d.ts
|
|
391
|
+
interface GeoRoomOptions {
|
|
392
|
+
/** H3 resolution. 9 ≈ 100 m edge (ride-hailing default); 10 ≈ 40 m. */
|
|
393
|
+
resolution?: number;
|
|
394
|
+
/**
|
|
395
|
+
* Room-key prefix so geo-rooms don't collide with arbitrary topic names.
|
|
396
|
+
* Defaults to `geo:`. Resulting room: `geo:<h3cell>`.
|
|
397
|
+
*/
|
|
398
|
+
prefix?: string;
|
|
399
|
+
}
|
|
400
|
+
interface GeoPoint {
|
|
401
|
+
lat: number;
|
|
402
|
+
lng: number;
|
|
403
|
+
}
|
|
404
|
+
interface GeoPublishOpts {
|
|
405
|
+
/** k-ring radius — `0` = just the cell, `1` = cell + 6 neighbors, etc. */
|
|
406
|
+
ring?: number;
|
|
407
|
+
/**
|
|
408
|
+
* Coalesce key for queue-side keep-latest-snapshot. Defaults to the
|
|
409
|
+
* client's pushRef so a driver streaming 5 Hz GPS collapses to one
|
|
410
|
+
* queued frame per recipient.
|
|
411
|
+
*/
|
|
412
|
+
coalesceKey?: string;
|
|
413
|
+
/**
|
|
414
|
+
* Delivery class. `'droppable'` (default) routes through `sendRealtime`
|
|
415
|
+
* — correct for high-frequency location streams. `'critical'` routes
|
|
416
|
+
* through `send` — use sparingly for dispatch events like "ride accepted".
|
|
417
|
+
*/
|
|
418
|
+
delivery?: "droppable" | "critical";
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* A geo-aware subscription layer on top of `RoomManager`. Construct one
|
|
422
|
+
* per logical channel (e.g. one for `drivers`, one for `couriers`) so
|
|
423
|
+
* resolutions and prefixes don't cross-contaminate.
|
|
424
|
+
*/
|
|
425
|
+
declare class GeoRoom {
|
|
426
|
+
private rooms;
|
|
427
|
+
private resolution;
|
|
428
|
+
private prefix;
|
|
429
|
+
private h3?;
|
|
430
|
+
constructor(rooms: RoomManager, opts?: GeoRoomOptions);
|
|
431
|
+
/** Subscribe a client to every cell within `ring` of `point`. */
|
|
432
|
+
subscribe(clientId: string, point: GeoPoint, opts?: {
|
|
433
|
+
ring?: number;
|
|
434
|
+
}): Promise<string[]>;
|
|
435
|
+
/** Symmetric — unsubscribe from every cell within `ring`. */
|
|
436
|
+
unsubscribe(clientId: string, point: GeoPoint, opts?: {
|
|
437
|
+
ring?: number;
|
|
438
|
+
}): Promise<void>;
|
|
439
|
+
/**
|
|
440
|
+
* Publish to every client subscribed to a cell within `ring` of `point`.
|
|
441
|
+
* Droppable by default — every recipient queue keeps only the latest
|
|
442
|
+
* per `coalesceKey` (defaults to the publisher's pushRef).
|
|
443
|
+
*/
|
|
444
|
+
publish(publisher: {
|
|
445
|
+
pushRef: string;
|
|
446
|
+
}, point: GeoPoint, payload: string, opts?: GeoPublishOpts): Promise<void>;
|
|
447
|
+
/** Compute the cell ID for a point without subscribing — useful for tests. */
|
|
448
|
+
cellFor(point: GeoPoint): Promise<string>;
|
|
449
|
+
private h3lib;
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/integrations/websocket/plugin.d.ts
|
|
453
|
+
/** Pluggable WebSocket integration for Arc */
|
|
454
|
+
declare const websocketPlugin: FastifyPluginAsync<WebSocketPluginOptions>;
|
|
455
|
+
//#endregion
|
|
456
|
+
export { GeoRoomOptions as a, WebSocketClient as c, LocalWebSocketAdapter as d, WebSocketAdapter as f, GeoRoom as i, WebSocketMessage as l, GeoPoint as n, RoomManager as o, GeoPublishOpts as r, AuthResult as s, websocketPlugin as t, WebSocketPluginOptions as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.1",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
"types": "./dist/integrations/websocket-redis.d.mts",
|
|
157
157
|
"default": "./dist/integrations/websocket-redis.mjs"
|
|
158
158
|
},
|
|
159
|
+
"./integrations/websocket-pushref-redis": {
|
|
160
|
+
"types": "./dist/integrations/websocket-pushref-redis.d.mts",
|
|
161
|
+
"default": "./dist/integrations/websocket-pushref-redis.mjs"
|
|
162
|
+
},
|
|
159
163
|
"./integrations/jobs": {
|
|
160
164
|
"types": "./dist/integrations/jobs.d.mts",
|
|
161
165
|
"default": "./dist/integrations/jobs.mjs"
|
|
@@ -227,6 +231,8 @@
|
|
|
227
231
|
"test:coverage": "vitest run --coverage",
|
|
228
232
|
"test:e2e": "vitest run tests/e2e",
|
|
229
233
|
"test:unit": "vitest run tests/core tests/hooks tests/utils tests/plugins",
|
|
234
|
+
"test:smoke:ws": "vitest run tests/integrations/websocket/smoke.test.ts",
|
|
235
|
+
"test:smoke:redis": "node scripts/smoke-ws-redis.mjs",
|
|
230
236
|
"smoke": "node scripts/smoke-test.mjs",
|
|
231
237
|
"push": "classytic-push",
|
|
232
238
|
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
@@ -261,6 +267,7 @@
|
|
|
261
267
|
"bullmq": ">=5.0.0",
|
|
262
268
|
"fastify": "^5.8.5",
|
|
263
269
|
"fastify-raw-body": ">=5.0.0",
|
|
270
|
+
"h3-js": ">=4.0.0",
|
|
264
271
|
"ioredis": ">=5.0.0",
|
|
265
272
|
"pino-pretty": ">=13.0.0",
|
|
266
273
|
"zod": ">=4.0.0"
|
|
@@ -296,6 +303,9 @@
|
|
|
296
303
|
"fastify-raw-body": {
|
|
297
304
|
"optional": true
|
|
298
305
|
},
|
|
306
|
+
"h3-js": {
|
|
307
|
+
"optional": true
|
|
308
|
+
},
|
|
299
309
|
"pino-pretty": {
|
|
300
310
|
"optional": true
|
|
301
311
|
},
|
|
@@ -378,6 +388,7 @@
|
|
|
378
388
|
"fast-check": "^4.8.0",
|
|
379
389
|
"fastify": "^5.8.5",
|
|
380
390
|
"fastify-raw-body": "^5.0.0",
|
|
391
|
+
"h3-js": "^4.4.0",
|
|
381
392
|
"ioredis": "^5.10.1",
|
|
382
393
|
"jsonwebtoken": "^9.0.0",
|
|
383
394
|
"knip": "^6.14.1",
|