@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.
@@ -1,4 +1,61 @@
1
1
  const connectionToken = new WeakMap<RouteHandle, object>();
2
+ const reverseRequests = new WeakMap<RouteHandle, ReverseRequestRegistry>();
3
+
4
+ export interface ReverseRequestContext {
5
+ readonly corr: bigint;
6
+ readonly method: string;
7
+ }
8
+
9
+ export type ReverseRequestHandler = (
10
+ body: Uint8Array,
11
+ ctx: ReverseRequestContext,
12
+ ) => Promise<Uint8Array> | Uint8Array;
13
+
14
+ /** A reverse-request registration cannot add a capability after route.open declared its set. */
15
+ export class ReverseRequestRegistrationError extends Error {
16
+ readonly code = "reverse_request_capability_not_declared";
17
+
18
+ constructor(readonly methodFamily: string) {
19
+ super(`reverse-request capability ${JSON.stringify(methodFamily)} was not registered before route.open`);
20
+ this.name = "ReverseRequestRegistrationError";
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Mutable handler registry prepared before route.open. The client derives
26
+ * consumer_capabilities from its method families and seals that set when the
27
+ * route opens; handlers for declared families may still be replaced afterward.
28
+ */
29
+ export class ReverseRequestRegistry {
30
+ private readonly handlers = new Map<string, ReverseRequestHandler>();
31
+ private declaredFamilies: ReadonlySet<string> | null = null;
32
+
33
+ onRequest(methodFamily: string, handler: ReverseRequestHandler): void {
34
+ if (!isMethodFamily(methodFamily)) {
35
+ throw new TypeError(`reverse-request method family must be a capability identifier, got ${JSON.stringify(methodFamily)}`);
36
+ }
37
+ if (typeof handler !== "function") throw new TypeError("reverse-request handler must be a function");
38
+ if (this.declaredFamilies && !this.declaredFamilies.has(methodFamily)) {
39
+ throw new ReverseRequestRegistrationError(methodFamily);
40
+ }
41
+ this.handlers.set(methodFamily, handler);
42
+ }
43
+
44
+ /** @internal */
45
+ capabilities(): string[] {
46
+ return [...this.handlers.keys()].sort();
47
+ }
48
+
49
+ /** @internal */
50
+ handler(methodFamily: string): ReverseRequestHandler | undefined {
51
+ return this.handlers.get(methodFamily);
52
+ }
53
+
54
+ /** @internal */
55
+ seal(): void {
56
+ this.declaredFamilies ??= new Set(this.handlers.keys());
57
+ }
58
+ }
2
59
 
3
60
  /**
4
61
  * Immutable identity for one route binding on one live socket. Only channel and
@@ -8,7 +65,7 @@ export class RouteHandle {
8
65
  readonly channel: number;
9
66
  readonly epoch: number;
10
67
 
11
- private constructor(channel: number, epoch: number, token: object) {
68
+ private constructor(channel: number, epoch: number, token: object, registry: ReverseRequestRegistry) {
12
69
  if (!Number.isInteger(channel) || channel <= 0 || channel > 0xffff) {
13
70
  throw new RangeError(`route channel must be an integer in 1..65535, got ${channel}`);
14
71
  }
@@ -17,21 +74,47 @@ export class RouteHandle {
17
74
  }
18
75
  this.channel = channel;
19
76
  this.epoch = epoch;
77
+ registry.seal();
20
78
  connectionToken.set(this, token);
79
+ reverseRequests.set(this, registry);
21
80
  Object.freeze(this);
22
81
  }
23
82
 
24
- private static create(channel: number, epoch: number, token: object): RouteHandle {
25
- return new RouteHandle(channel, epoch, token);
83
+ /** Replace the handler for a capability declared when this route opened. */
84
+ onRequest(methodFamily: string, handler: ReverseRequestHandler): void {
85
+ reverseRequests.get(this)!.onRequest(methodFamily, handler);
86
+ }
87
+
88
+ private static create(
89
+ channel: number,
90
+ epoch: number,
91
+ token: object,
92
+ registry: ReverseRequestRegistry,
93
+ ): RouteHandle {
94
+ return new RouteHandle(channel, epoch, token, registry);
26
95
  }
27
96
  }
28
97
 
29
98
  /** @internal SDK factory; not re-exported from the package surface. */
30
- export function createRouteHandle(channel: number, epoch: number, token: object): RouteHandle {
99
+ export function createRouteHandle(
100
+ channel: number,
101
+ epoch: number,
102
+ token: object,
103
+ registry = new ReverseRequestRegistry(),
104
+ ): RouteHandle {
31
105
  const factory = RouteHandle as unknown as {
32
- create(channel: number, epoch: number, token: object): RouteHandle;
106
+ create(channel: number, epoch: number, token: object, registry: ReverseRequestRegistry): RouteHandle;
33
107
  };
34
- return factory.create(channel, epoch, token);
108
+ return factory.create(channel, epoch, token, registry);
109
+ }
110
+
111
+ /** @internal */
112
+ export function reverseRequestHandler(handle: RouteHandle, methodFamily: string): ReverseRequestHandler | undefined {
113
+ return reverseRequests.get(handle)?.handler(methodFamily);
114
+ }
115
+
116
+ function isMethodFamily(value: string): boolean {
117
+ return /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(value);
35
118
  }
36
119
 
37
120
  /** A route handle belongs to another connection or is no longer installed. */