@cortexkit/subc-client 0.12.0 → 0.13.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/client.d.ts +13 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +105 -10
- package/dist/client.js.map +1 -1
- package/dist/envelope.d.ts +1 -0
- package/dist/envelope.d.ts.map +1 -1
- package/dist/envelope.js +2 -1
- package/dist/envelope.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/route-handle.d.ts +32 -1
- package/dist/route-handle.d.ts.map +1 -1
- package/dist/route-handle.js +61 -5
- package/dist/route-handle.js.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +171 -11
- package/src/envelope.ts +2 -1
- package/src/index.ts +9 -1
- package/src/route-handle.ts +89 -6
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,EACjC,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,EACjC,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAC/B,WAAW,EACX,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,QAAQ,GACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,SAAS,EACT,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,UAAU,EACV,WAAW,EACX,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,gCAAgC,EACrC,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,0BAA0B,EAC/B,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { SubcClient, SubcError, SubcCallError, DEFAULT_RECONNECT_BACKOFF, SUBC_MODULE_ID_ENV, SUBC_LAUNCH_NONCE_ENV, isConsumerReconnectTransient, parseRouteCloseReason, classifyRouteCloseReason, routeCloseReason, isValidCapabilityIdentifier, connectionFileExists, } from "./client.js";
|
|
2
|
-
export { RouteHandle, StaleRouteHandleError } from "./route-handle.js";
|
|
2
|
+
export { ReverseRequestRegistry, ReverseRequestRegistrationError, RouteHandle, StaleRouteHandleError, } from "./route-handle.js";
|
|
3
3
|
export { readConnectionFile, ConnectionFileError, } from "./connection-file.js";
|
|
4
|
-
export { FrameType, Priority, AdmissionClass, DAEMON_ORIGIN_FLAG, PROTOCOL_VERSION, HEADER_LEN, buildFrame, buildFrameWithVersion, buildFlags, encodeFrame, decodeHeader, hasBinary, hasDaemonOrigin, encodeHeader, DecodeError, } from "./envelope.js";
|
|
4
|
+
export { FrameType, Priority, AdmissionClass, DAEMON_ORIGIN_FLAG, SUBSCRIPTION_FLAG, PROTOCOL_VERSION, HEADER_LEN, buildFrame, buildFrameWithVersion, buildFlags, encodeFrame, decodeHeader, hasBinary, hasDaemonOrigin, encodeHeader, DecodeError, } from "./envelope.js";
|
|
5
5
|
export { authenticateClient, computeProof, AuthError, NONCE_LEN, PROOF_LEN, SERVER_PROOF_DOMAIN, CLIENT_AUTH_DOMAIN, } from "./auth.js";
|
|
6
6
|
export { SubcSocket, SocketClosedError, SocketTimeoutError } from "./socket.js";
|
|
7
7
|
export { SubcProvider, SubcProviderError, HELLO_CORR, managementSurfaceManifest, jsonProviderHandler, } from "./provider.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,oBAAoB,GAwBrB,MAAM,aAAa,CAAC;AACrB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,aAAa,EACb,yBAAyB,EACzB,kBAAkB,EAClB,qBAAqB,EACrB,4BAA4B,EAC5B,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,oBAAoB,GAwBrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAC/B,WAAW,EACX,qBAAqB,GAGtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,SAAS,EACT,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,qBAAqB,EACrB,UAAU,EACV,WAAW,EACX,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,WAAW,GAIZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,yBAAyB,EACzB,mBAAmB,GAmCpB,MAAM,eAAe,CAAC"}
|
package/dist/route-handle.d.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
export interface ReverseRequestContext {
|
|
2
|
+
readonly corr: bigint;
|
|
3
|
+
readonly method: string;
|
|
4
|
+
}
|
|
5
|
+
export type ReverseRequestHandler = (body: Uint8Array, ctx: ReverseRequestContext) => Promise<Uint8Array> | Uint8Array;
|
|
6
|
+
/** A reverse-request registration cannot add a capability after route.open declared its set. */
|
|
7
|
+
export declare class ReverseRequestRegistrationError extends Error {
|
|
8
|
+
readonly methodFamily: string;
|
|
9
|
+
readonly code = "reverse_request_capability_not_declared";
|
|
10
|
+
constructor(methodFamily: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Mutable handler registry prepared before route.open. The client derives
|
|
14
|
+
* consumer_capabilities from its method families and seals that set when the
|
|
15
|
+
* route opens; handlers for declared families may still be replaced afterward.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ReverseRequestRegistry {
|
|
18
|
+
private readonly handlers;
|
|
19
|
+
private declaredFamilies;
|
|
20
|
+
onRequest(methodFamily: string, handler: ReverseRequestHandler): void;
|
|
21
|
+
/** @internal */
|
|
22
|
+
capabilities(): string[];
|
|
23
|
+
/** @internal */
|
|
24
|
+
handler(methodFamily: string): ReverseRequestHandler | undefined;
|
|
25
|
+
/** @internal */
|
|
26
|
+
seal(): void;
|
|
27
|
+
}
|
|
1
28
|
/**
|
|
2
29
|
* Immutable identity for one route binding on one live socket. Only channel and
|
|
3
30
|
* epoch cross the wire; the connection token remains private to this SDK.
|
|
@@ -6,10 +33,14 @@ export declare class RouteHandle {
|
|
|
6
33
|
readonly channel: number;
|
|
7
34
|
readonly epoch: number;
|
|
8
35
|
private constructor();
|
|
36
|
+
/** Replace the handler for a capability declared when this route opened. */
|
|
37
|
+
onRequest(methodFamily: string, handler: ReverseRequestHandler): void;
|
|
9
38
|
private static create;
|
|
10
39
|
}
|
|
11
40
|
/** @internal SDK factory; not re-exported from the package surface. */
|
|
12
|
-
export declare function createRouteHandle(channel: number, epoch: number, token: object): RouteHandle;
|
|
41
|
+
export declare function createRouteHandle(channel: number, epoch: number, token: object, registry?: ReverseRequestRegistry): RouteHandle;
|
|
42
|
+
/** @internal */
|
|
43
|
+
export declare function reverseRequestHandler(handle: RouteHandle, methodFamily: string): ReverseRequestHandler | undefined;
|
|
13
44
|
/** A route handle belongs to another connection or is no longer installed. */
|
|
14
45
|
export declare class StaleRouteHandleError extends Error {
|
|
15
46
|
readonly handle: RouteHandle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handle.d.ts","sourceRoot":"","sources":["../src/route-handle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route-handle.d.ts","sourceRoot":"","sources":["../src/route-handle.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,qBAAqB,KACvB,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;AAEtC,gGAAgG;AAChG,qBAAa,+BAAgC,SAAQ,KAAK;IAG5C,QAAQ,CAAC,YAAY,EAAE,MAAM;IAFzC,QAAQ,CAAC,IAAI,6CAA6C;gBAErC,YAAY,EAAE,MAAM;CAI1C;AAED;;;;GAIG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,gBAAgB,CAAoC;IAE5D,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAWrE,gBAAgB;IAChB,YAAY,IAAI,MAAM,EAAE;IAIxB,gBAAgB;IAChB,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS;IAIhE,gBAAgB;IAChB,IAAI,IAAI,IAAI;CAGb;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,OAAO;IAeP,4EAA4E;IAC5E,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAIrE,OAAO,CAAC,MAAM,CAAC,MAAM;CAQtB;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,yBAA+B,GACtC,WAAW,CAKb;AAED,gBAAgB;AAChB,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAElH;AAMD,8EAA8E;AAC9E,qBAAa,qBAAsB,SAAQ,KAAK;IAGlC,QAAQ,CAAC,MAAM,EAAE,WAAW;IAFxC,QAAQ,CAAC,IAAI,wBAAwB;gBAEhB,MAAM,EAAE,WAAW;CAIzC;AAED,gBAAgB;AAChB,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,gBAAgB;AAChB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/E;AAED,gBAAgB;AAChB,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAE9E"}
|
package/dist/route-handle.js
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
const connectionToken = new WeakMap();
|
|
2
|
+
const reverseRequests = new WeakMap();
|
|
3
|
+
/** A reverse-request registration cannot add a capability after route.open declared its set. */
|
|
4
|
+
export class ReverseRequestRegistrationError extends Error {
|
|
5
|
+
methodFamily;
|
|
6
|
+
code = "reverse_request_capability_not_declared";
|
|
7
|
+
constructor(methodFamily) {
|
|
8
|
+
super(`reverse-request capability ${JSON.stringify(methodFamily)} was not registered before route.open`);
|
|
9
|
+
this.methodFamily = methodFamily;
|
|
10
|
+
this.name = "ReverseRequestRegistrationError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Mutable handler registry prepared before route.open. The client derives
|
|
15
|
+
* consumer_capabilities from its method families and seals that set when the
|
|
16
|
+
* route opens; handlers for declared families may still be replaced afterward.
|
|
17
|
+
*/
|
|
18
|
+
export class ReverseRequestRegistry {
|
|
19
|
+
handlers = new Map();
|
|
20
|
+
declaredFamilies = null;
|
|
21
|
+
onRequest(methodFamily, handler) {
|
|
22
|
+
if (!isMethodFamily(methodFamily)) {
|
|
23
|
+
throw new TypeError(`reverse-request method family must be a capability identifier, got ${JSON.stringify(methodFamily)}`);
|
|
24
|
+
}
|
|
25
|
+
if (typeof handler !== "function")
|
|
26
|
+
throw new TypeError("reverse-request handler must be a function");
|
|
27
|
+
if (this.declaredFamilies && !this.declaredFamilies.has(methodFamily)) {
|
|
28
|
+
throw new ReverseRequestRegistrationError(methodFamily);
|
|
29
|
+
}
|
|
30
|
+
this.handlers.set(methodFamily, handler);
|
|
31
|
+
}
|
|
32
|
+
/** @internal */
|
|
33
|
+
capabilities() {
|
|
34
|
+
return [...this.handlers.keys()].sort();
|
|
35
|
+
}
|
|
36
|
+
/** @internal */
|
|
37
|
+
handler(methodFamily) {
|
|
38
|
+
return this.handlers.get(methodFamily);
|
|
39
|
+
}
|
|
40
|
+
/** @internal */
|
|
41
|
+
seal() {
|
|
42
|
+
this.declaredFamilies ??= new Set(this.handlers.keys());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
2
45
|
/**
|
|
3
46
|
* Immutable identity for one route binding on one live socket. Only channel and
|
|
4
47
|
* epoch cross the wire; the connection token remains private to this SDK.
|
|
@@ -6,7 +49,7 @@ const connectionToken = new WeakMap();
|
|
|
6
49
|
export class RouteHandle {
|
|
7
50
|
channel;
|
|
8
51
|
epoch;
|
|
9
|
-
constructor(channel, epoch, token) {
|
|
52
|
+
constructor(channel, epoch, token, registry) {
|
|
10
53
|
if (!Number.isInteger(channel) || channel <= 0 || channel > 0xffff) {
|
|
11
54
|
throw new RangeError(`route channel must be an integer in 1..65535, got ${channel}`);
|
|
12
55
|
}
|
|
@@ -15,17 +58,30 @@ export class RouteHandle {
|
|
|
15
58
|
}
|
|
16
59
|
this.channel = channel;
|
|
17
60
|
this.epoch = epoch;
|
|
61
|
+
registry.seal();
|
|
18
62
|
connectionToken.set(this, token);
|
|
63
|
+
reverseRequests.set(this, registry);
|
|
19
64
|
Object.freeze(this);
|
|
20
65
|
}
|
|
21
|
-
|
|
22
|
-
|
|
66
|
+
/** Replace the handler for a capability declared when this route opened. */
|
|
67
|
+
onRequest(methodFamily, handler) {
|
|
68
|
+
reverseRequests.get(this).onRequest(methodFamily, handler);
|
|
69
|
+
}
|
|
70
|
+
static create(channel, epoch, token, registry) {
|
|
71
|
+
return new RouteHandle(channel, epoch, token, registry);
|
|
23
72
|
}
|
|
24
73
|
}
|
|
25
74
|
/** @internal SDK factory; not re-exported from the package surface. */
|
|
26
|
-
export function createRouteHandle(channel, epoch, token) {
|
|
75
|
+
export function createRouteHandle(channel, epoch, token, registry = new ReverseRequestRegistry()) {
|
|
27
76
|
const factory = RouteHandle;
|
|
28
|
-
return factory.create(channel, epoch, token);
|
|
77
|
+
return factory.create(channel, epoch, token, registry);
|
|
78
|
+
}
|
|
79
|
+
/** @internal */
|
|
80
|
+
export function reverseRequestHandler(handle, methodFamily) {
|
|
81
|
+
return reverseRequests.get(handle)?.handler(methodFamily);
|
|
82
|
+
}
|
|
83
|
+
function isMethodFamily(value) {
|
|
84
|
+
return /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(value);
|
|
29
85
|
}
|
|
30
86
|
/** A route handle belongs to another connection or is no longer installed. */
|
|
31
87
|
export class StaleRouteHandleError extends Error {
|
package/dist/route-handle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handle.js","sourceRoot":"","sources":["../src/route-handle.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,IAAI,OAAO,EAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"route-handle.js","sourceRoot":"","sources":["../src/route-handle.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/subc-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "TypeScript client for the subc daemon. Wire-compatible (byte-for-byte) with the Rust subc-transport handshake and subc-protocol envelope.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"bun-types": "^1.3.14",
|
|
31
31
|
"typescript": "^5.6.0"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -22,12 +22,15 @@ import {
|
|
|
22
22
|
FrameType,
|
|
23
23
|
hasBinary,
|
|
24
24
|
Priority,
|
|
25
|
+
SUBSCRIPTION_FLAG,
|
|
25
26
|
type Frame,
|
|
26
27
|
} from "./envelope.js";
|
|
27
28
|
import {
|
|
28
29
|
belongsToConnection,
|
|
29
30
|
createRouteHandle,
|
|
30
31
|
newConnectionToken,
|
|
32
|
+
reverseRequestHandler,
|
|
33
|
+
ReverseRequestRegistry,
|
|
31
34
|
RouteHandle,
|
|
32
35
|
sameRouteHandle,
|
|
33
36
|
StaleRouteHandleError,
|
|
@@ -109,8 +112,11 @@ export interface ConsumerIdentity {
|
|
|
109
112
|
export interface RouteOpenOptions {
|
|
110
113
|
/** Optional override for the consumer identity; by default the SUBC_MODULE_ID and SUBC_LAUNCH_NONCE environment variables are used when both are non-empty. Set null to send route.open without consumer_identity. */
|
|
111
114
|
consumerIdentity?: ConsumerIdentity | null;
|
|
112
|
-
/**
|
|
113
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Handlers prepared before route.open. The client derives consumer_capabilities
|
|
117
|
+
* from this registry; callers cannot declare a capability without a handler.
|
|
118
|
+
*/
|
|
119
|
+
reverseRequests?: ReverseRequestRegistry;
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
export interface CatalogCapabilityRequirement {
|
|
@@ -193,6 +199,8 @@ export interface ManagedCallOptions extends RequestOptions {
|
|
|
193
199
|
targetKind?: ManagedRouteKind;
|
|
194
200
|
/** Optional override for the consumer identity; by default the SUBC_MODULE_ID and SUBC_LAUNCH_NONCE environment variables are used when both are non-empty. Set null to send route.open without consumer_identity. */
|
|
195
201
|
consumerIdentity?: ConsumerIdentity | null;
|
|
202
|
+
/** Reverse-request handlers used by the managed route and all of its reconnect reopens. */
|
|
203
|
+
reverseRequests?: ReverseRequestRegistry;
|
|
196
204
|
}
|
|
197
205
|
|
|
198
206
|
export interface SubscribeOptions {
|
|
@@ -221,6 +229,8 @@ export interface CloseRouteOptions {
|
|
|
221
229
|
export interface ManagedCloseRouteOptions extends CloseRouteOptions {
|
|
222
230
|
/** Consumer identity used by the cached managed route. */
|
|
223
231
|
consumerIdentity?: ConsumerIdentity | null;
|
|
232
|
+
/** The registry whose derived capability set identifies the cached route. */
|
|
233
|
+
reverseRequests?: ReverseRequestRegistry;
|
|
224
234
|
}
|
|
225
235
|
|
|
226
236
|
/**
|
|
@@ -468,6 +478,7 @@ interface CachedRoute {
|
|
|
468
478
|
target: Extract<RouteTarget, { kind: ManagedRouteKind }>;
|
|
469
479
|
identity: BindIdentity;
|
|
470
480
|
consumerIdentity?: ConsumerIdentity;
|
|
481
|
+
reverseRequests: ReverseRequestRegistry;
|
|
471
482
|
handle: RouteHandle | null;
|
|
472
483
|
opening: Promise<RouteHandle> | null;
|
|
473
484
|
/**
|
|
@@ -566,13 +577,15 @@ export class SubcClient {
|
|
|
566
577
|
/** Open a route and return its connection-bound immutable handle. */
|
|
567
578
|
async routeOpen(target: RouteTarget, identity: BindIdentity, opts: RouteOpenOptions = {}): Promise<RouteHandle> {
|
|
568
579
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
569
|
-
const
|
|
580
|
+
const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry();
|
|
581
|
+
reverseRequests.seal();
|
|
582
|
+
const consumerCapabilities = reverseRequests.capabilities();
|
|
570
583
|
const body = this.encode({
|
|
571
584
|
op: "route.open",
|
|
572
585
|
target,
|
|
573
586
|
identity,
|
|
574
587
|
...(consumerIdentity ? { consumer_identity: consumerIdentity } : {}),
|
|
575
|
-
...(consumerCapabilities
|
|
588
|
+
...(consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {}),
|
|
576
589
|
});
|
|
577
590
|
|
|
578
591
|
let installed: RouteHandle | null = null;
|
|
@@ -582,7 +595,7 @@ export class SubcClient {
|
|
|
582
595
|
if (typeof parsed.route_channel !== "number" || typeof parsed.route_epoch !== "number") {
|
|
583
596
|
throw new SubcError(`route.open returned no route handle: ${JSON.stringify(parsed)}`);
|
|
584
597
|
}
|
|
585
|
-
installed = this.installRoute(parsed.route_channel, parsed.route_epoch);
|
|
598
|
+
installed = this.installRoute(parsed.route_channel, parsed.route_epoch, reverseRequests);
|
|
586
599
|
return true;
|
|
587
600
|
};
|
|
588
601
|
const closeLateRoute = (frame: Frame): void => {
|
|
@@ -747,6 +760,20 @@ export class SubcClient {
|
|
|
747
760
|
subscription: true,
|
|
748
761
|
};
|
|
749
762
|
this.pending.set(key, subscriptionPending);
|
|
763
|
+
// BIT 7 IS NOT EMITTED YET, DELIBERATELY. `SUBSCRIPTION_FLAG` is allocated
|
|
764
|
+
// and the daemon reads it, but every decoder built against subc-protocol
|
|
765
|
+
// <= 0.20.0 treats bit 7 as a RESERVED-BIT TRIPWIRE and REFUSES the frame
|
|
766
|
+
// rather than ignoring a bit it does not know. The splice forwards the
|
|
767
|
+
// flags byte verbatim, so the rejection lands at the far end, inside a
|
|
768
|
+
// module whose author opted into nothing, as a decode error on a frame the
|
|
769
|
+
// daemon considered well-formed.
|
|
770
|
+
//
|
|
771
|
+
// Restoring this line is PHASE 2 of a two-phase fleet operation and must
|
|
772
|
+
// not happen until Phase 1 is done and CENSUSED: every module linking a
|
|
773
|
+
// subc-protocol whose decoder ACCEPTS bit 7. Bit 6 (DAEMON_ORIGIN) was
|
|
774
|
+
// done exactly this way, in separate PRs, for exactly this reason.
|
|
775
|
+
//
|
|
776
|
+
// ... | SUBSCRIPTION_FLAG, <- Phase 2, not before the census
|
|
750
777
|
const frame = buildFrame(
|
|
751
778
|
FrameType.Request,
|
|
752
779
|
buildFlags(false, priority, false, admission),
|
|
@@ -857,7 +884,12 @@ export class SubcClient {
|
|
|
857
884
|
identity: BindIdentity,
|
|
858
885
|
opts: ManagedCloseRouteOptions = {},
|
|
859
886
|
): Promise<void> {
|
|
860
|
-
const key = routeCacheKey(
|
|
887
|
+
const key = routeCacheKey(
|
|
888
|
+
target,
|
|
889
|
+
identity,
|
|
890
|
+
routeOpenConsumerIdentity(opts),
|
|
891
|
+
(opts.reverseRequests ?? new ReverseRequestRegistry()).capabilities(),
|
|
892
|
+
);
|
|
861
893
|
const cached = this.routes.get(key);
|
|
862
894
|
if (!cached) return;
|
|
863
895
|
cached.closed = true;
|
|
@@ -1139,7 +1171,9 @@ export class SubcClient {
|
|
|
1139
1171
|
{ kind: ManagedRouteKind }
|
|
1140
1172
|
>;
|
|
1141
1173
|
const consumerIdentity = routeOpenConsumerIdentity(opts);
|
|
1142
|
-
const
|
|
1174
|
+
const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry();
|
|
1175
|
+
reverseRequests.seal();
|
|
1176
|
+
const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities());
|
|
1143
1177
|
let cached = this.routes.get(key);
|
|
1144
1178
|
if (!cached) {
|
|
1145
1179
|
cached = {
|
|
@@ -1148,6 +1182,7 @@ export class SubcClient {
|
|
|
1148
1182
|
target,
|
|
1149
1183
|
identity,
|
|
1150
1184
|
consumerIdentity,
|
|
1185
|
+
reverseRequests,
|
|
1151
1186
|
handle: null,
|
|
1152
1187
|
opening: null,
|
|
1153
1188
|
};
|
|
@@ -1178,6 +1213,7 @@ export class SubcClient {
|
|
|
1178
1213
|
try {
|
|
1179
1214
|
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
1180
1215
|
consumerIdentity: cached.consumerIdentity ?? null,
|
|
1216
|
+
reverseRequests: cached.reverseRequests,
|
|
1181
1217
|
});
|
|
1182
1218
|
if (cached.closed) {
|
|
1183
1219
|
this.liveRoutes.delete(handle.channel);
|
|
@@ -1401,6 +1437,7 @@ export class SubcClient {
|
|
|
1401
1437
|
try {
|
|
1402
1438
|
const handle = await this.routeOpen(cached.target, cached.identity, {
|
|
1403
1439
|
consumerIdentity: cached.consumerIdentity ?? null,
|
|
1440
|
+
reverseRequests: cached.reverseRequests,
|
|
1404
1441
|
});
|
|
1405
1442
|
if (cached.closed) {
|
|
1406
1443
|
this.liveRoutes.delete(handle.channel);
|
|
@@ -1504,6 +1541,11 @@ export class SubcClient {
|
|
|
1504
1541
|
}
|
|
1505
1542
|
}
|
|
1506
1543
|
|
|
1544
|
+
if (frame.header.ty === FrameType.Request && handle) {
|
|
1545
|
+
this.dispatchReverseRequest(frame, handle);
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1507
1549
|
const key = pendingKey(handle, frame.header.corr);
|
|
1508
1550
|
const pending = this.pending.get(key);
|
|
1509
1551
|
if (pending) {
|
|
@@ -1607,6 +1649,109 @@ export class SubcClient {
|
|
|
1607
1649
|
this.settle(key, pending, () => pending.reject(pending.classifyFailure?.(err) ?? err));
|
|
1608
1650
|
}
|
|
1609
1651
|
|
|
1652
|
+
private dispatchReverseRequest(frame: Frame, handle: RouteHandle): void {
|
|
1653
|
+
const method = reverseRequestMethod(frame.body);
|
|
1654
|
+
const methodFamily = method?.split("/", 1)[0];
|
|
1655
|
+
const handler = methodFamily ? reverseRequestHandler(handle, methodFamily) : undefined;
|
|
1656
|
+
const sock = this.sock;
|
|
1657
|
+
const generation = this.generation;
|
|
1658
|
+
|
|
1659
|
+
// ONE CODE, TWO SITES, ON PURPOSE. `reverse_request_unhandled` is emitted
|
|
1660
|
+
// both here (no handler registered) and below (a handler that threw), with
|
|
1661
|
+
// different MESSAGES and the same CODE. That is a ruling, not an oversight:
|
|
1662
|
+
// the distinction between "nobody listening" and "the listener broke" is
|
|
1663
|
+
// ours to log, and every provider's decision is identical for both -- not
|
|
1664
|
+
// granted, retry later. A second code would be a second thing every
|
|
1665
|
+
// consumer's decoder must learn for a branch none of them takes. If one ever
|
|
1666
|
+
// needs it, the message carries it. (Settled with prefrontal, the lane's
|
|
1667
|
+
// first consumer, before anyone pinned on it.)
|
|
1668
|
+
//
|
|
1669
|
+
// What must NOT join them: a handler that RETURNS bytes declining the
|
|
1670
|
+
// request answers with an ordinary Response in the provider's own
|
|
1671
|
+
// vocabulary. Error means "the consumer did not answer", never "the consumer
|
|
1672
|
+
// said no" -- that separation is the reason this lane exists.
|
|
1673
|
+
const reply = async (): Promise<void> => {
|
|
1674
|
+
if (!handler || !method) {
|
|
1675
|
+
await this.sendReverseError(
|
|
1676
|
+
frame,
|
|
1677
|
+
handle,
|
|
1678
|
+
"reverse_request_unhandled",
|
|
1679
|
+
methodFamily
|
|
1680
|
+
? `no reverse-request handler is registered for ${methodFamily}`
|
|
1681
|
+
: "reverse request has no valid method",
|
|
1682
|
+
sock,
|
|
1683
|
+
generation,
|
|
1684
|
+
);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
let body: Uint8Array;
|
|
1689
|
+
try {
|
|
1690
|
+
body = await handler(frame.body, { corr: frame.header.corr, method });
|
|
1691
|
+
if (!(body instanceof Uint8Array)) {
|
|
1692
|
+
throw new TypeError("reverse-request handler must return a Uint8Array");
|
|
1693
|
+
}
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
await this.sendReverseError(
|
|
1696
|
+
frame,
|
|
1697
|
+
handle,
|
|
1698
|
+
"reverse_request_unhandled",
|
|
1699
|
+
error instanceof Error ? error.message : String(error),
|
|
1700
|
+
sock,
|
|
1701
|
+
generation,
|
|
1702
|
+
);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
await this.sendReverseReply(frame, handle, FrameType.Response, body, sock, generation);
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
void reply().catch((error) => {
|
|
1710
|
+
if (this.sock === sock && this.generation === generation && !this.closeStarted) {
|
|
1711
|
+
debug("failed to answer reverse request: %s", causeMessage(error));
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
private async sendReverseError(
|
|
1717
|
+
request: Frame,
|
|
1718
|
+
handle: RouteHandle,
|
|
1719
|
+
code: string,
|
|
1720
|
+
message: string,
|
|
1721
|
+
sock: SubcSocket,
|
|
1722
|
+
generation: number,
|
|
1723
|
+
): Promise<void> {
|
|
1724
|
+
await this.sendReverseReply(request, handle, FrameType.Error, this.encode({ code, message }), sock, generation);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
private async sendReverseReply(
|
|
1728
|
+
request: Frame,
|
|
1729
|
+
handle: RouteHandle,
|
|
1730
|
+
type: FrameType.Response | FrameType.Error,
|
|
1731
|
+
body: Uint8Array,
|
|
1732
|
+
sock: SubcSocket,
|
|
1733
|
+
generation: number,
|
|
1734
|
+
): Promise<void> {
|
|
1735
|
+
if (
|
|
1736
|
+
this.sock !== sock ||
|
|
1737
|
+
this.generation !== generation ||
|
|
1738
|
+
!this.isLiveHandle(handle) ||
|
|
1739
|
+
this.closeStarted ||
|
|
1740
|
+
this.closedErr
|
|
1741
|
+
) {
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
const frame = buildFrame(
|
|
1745
|
+
type,
|
|
1746
|
+
buildFlags(false, Priority.Interactive, false),
|
|
1747
|
+
handle.channel,
|
|
1748
|
+
handle.epoch,
|
|
1749
|
+
request.header.corr,
|
|
1750
|
+
body,
|
|
1751
|
+
);
|
|
1752
|
+
await writeBorrowed(sock, encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1610
1755
|
private errorFromFrame(frame: Frame): SubcError {
|
|
1611
1756
|
try {
|
|
1612
1757
|
const parsed = JSON.parse(Buffer.from(frame.body).toString("utf8")) as {
|
|
@@ -1663,8 +1808,12 @@ export class SubcClient {
|
|
|
1663
1808
|
return this.ingressEpochDropCount;
|
|
1664
1809
|
}
|
|
1665
1810
|
|
|
1666
|
-
private installRoute(
|
|
1667
|
-
|
|
1811
|
+
private installRoute(
|
|
1812
|
+
channel: number,
|
|
1813
|
+
epoch: number,
|
|
1814
|
+
reverseRequests = new ReverseRequestRegistry(),
|
|
1815
|
+
): RouteHandle {
|
|
1816
|
+
const handle = createRouteHandle(channel, epoch, this.connectionToken, reverseRequests);
|
|
1668
1817
|
this.liveRoutes.set(channel, handle);
|
|
1669
1818
|
return handle;
|
|
1670
1819
|
}
|
|
@@ -1813,12 +1962,14 @@ export function isValidCapabilityIdentifier(identifier: string): boolean {
|
|
|
1813
1962
|
function routeCacheKey(
|
|
1814
1963
|
target: Extract<RouteTarget, { kind: ManagedRouteKind }>,
|
|
1815
1964
|
identity: BindIdentity,
|
|
1816
|
-
consumerIdentity
|
|
1965
|
+
consumerIdentity: ConsumerIdentity | undefined,
|
|
1966
|
+
consumerCapabilities: readonly string[],
|
|
1817
1967
|
): string {
|
|
1818
1968
|
const consumerPart = consumerIdentity
|
|
1819
1969
|
? `${consumerIdentity.module_id}\0${consumerIdentity.launch_nonce}`
|
|
1820
1970
|
: "";
|
|
1821
|
-
|
|
1971
|
+
const capabilitiesPart = [...consumerCapabilities].sort().join("\0");
|
|
1972
|
+
return `${target.kind}\0${target.module_id}\0${identity.project_root}\0${identity.harness}\0${identity.session}\0${consumerPart}\0${capabilitiesPart}`;
|
|
1822
1973
|
}
|
|
1823
1974
|
|
|
1824
1975
|
function routeOpenConsumerIdentity(opts: RouteOpenOptions = {}): ConsumerIdentity | undefined {
|
|
@@ -1845,3 +1996,12 @@ function causeMessage(cause: unknown): string {
|
|
|
1845
1996
|
function pendingKey(handle: RouteHandle | null, corr: bigint): string {
|
|
1846
1997
|
return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
|
|
1847
1998
|
}
|
|
1999
|
+
|
|
2000
|
+
function reverseRequestMethod(body: Uint8Array): string | undefined {
|
|
2001
|
+
try {
|
|
2002
|
+
const parsed = JSON.parse(Buffer.from(body).toString("utf8")) as { method?: unknown };
|
|
2003
|
+
return typeof parsed.method === "string" && parsed.method.length > 0 ? parsed.method : undefined;
|
|
2004
|
+
} catch {
|
|
2005
|
+
return undefined;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
package/src/envelope.ts
CHANGED
|
@@ -51,7 +51,8 @@ const FLAG_LAST = 0b0000_1000;
|
|
|
51
51
|
const FLAG_ADMISSION_MASK = 0b0011_0000;
|
|
52
52
|
const FLAG_ADMISSION_SHIFT = 4;
|
|
53
53
|
export const DAEMON_ORIGIN_FLAG = 0x40;
|
|
54
|
-
const
|
|
54
|
+
export const SUBSCRIPTION_FLAG = 0x80;
|
|
55
|
+
const FLAG_RESERVED_MASK = 0;
|
|
55
56
|
|
|
56
57
|
/** Build flags from typed components. Admission defaults to NORMAL. */
|
|
57
58
|
export function buildFlags(
|
package/src/index.ts
CHANGED
|
@@ -35,7 +35,14 @@ export {
|
|
|
35
35
|
type RoutePollKind,
|
|
36
36
|
type RoutePollResult,
|
|
37
37
|
} from "./client.js";
|
|
38
|
-
export {
|
|
38
|
+
export {
|
|
39
|
+
ReverseRequestRegistry,
|
|
40
|
+
ReverseRequestRegistrationError,
|
|
41
|
+
RouteHandle,
|
|
42
|
+
StaleRouteHandleError,
|
|
43
|
+
type ReverseRequestContext,
|
|
44
|
+
type ReverseRequestHandler,
|
|
45
|
+
} from "./route-handle.js";
|
|
39
46
|
export {
|
|
40
47
|
readConnectionFile,
|
|
41
48
|
ConnectionFileError,
|
|
@@ -47,6 +54,7 @@ export {
|
|
|
47
54
|
Priority,
|
|
48
55
|
AdmissionClass,
|
|
49
56
|
DAEMON_ORIGIN_FLAG,
|
|
57
|
+
SUBSCRIPTION_FLAG,
|
|
50
58
|
PROTOCOL_VERSION,
|
|
51
59
|
HEADER_LEN,
|
|
52
60
|
buildFrame,
|