@the-open-engine/zeroshot 6.25.1 → 6.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cluster/client.cjs +30 -5
- package/lib/cluster/client.d.ts +11 -1
- package/lib/cluster/client.mjs +29 -5
- package/lib/cluster/connection.cjs +38 -2
- package/lib/cluster/connection.d.ts +3 -0
- package/lib/cluster/connection.mjs +37 -1
- package/lib/cluster/index.cjs +3 -1
- package/lib/cluster/index.d.ts +3 -3
- package/lib/cluster/index.mjs +2 -2
- package/lib/hosted-session/coordinator.cjs +101 -0
- package/lib/hosted-session/coordinator.d.ts +9 -0
- package/lib/hosted-session/coordinator.mjs +97 -0
- package/lib/hosted-session/index.cjs +5 -0
- package/lib/hosted-session/index.d.ts +2 -0
- package/lib/hosted-session/index.mjs +1 -0
- package/lib/hosted-session/types.cjs +2 -0
- package/lib/hosted-session/types.d.ts +20 -0
- package/lib/hosted-session/types.mjs +1 -0
- package/package.json +14 -5
- package/scripts/build-cluster.js +21 -7
- package/src/cluster/client.ts +45 -5
- package/src/cluster/connection.ts +32 -1
- package/src/cluster/index.ts +4 -2
- package/src/cluster/ws.d.ts +1 -0
- package/src/hosted-session/coordinator.ts +110 -0
- package/src/hosted-session/index.ts +2 -0
- package/src/hosted-session/types.ts +21 -0
- package/src/hosted-target/bounds.ts +6 -0
- package/src/hosted-target/errors.ts +90 -0
- package/src/hosted-target/index.ts +44 -0
- package/src/hosted-target/response-validation.ts +86 -0
- package/src/hosted-target/retry.ts +46 -0
- package/src/hosted-target/target-adapter.ts +10 -0
- package/src/hosted-target/types.ts +58 -0
- package/src/hosted-target/zero-cloud-v1-adapter.ts +386 -0
package/lib/cluster/client.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ClusterClient = void 0;
|
|
4
4
|
exports.connect = connect;
|
|
5
|
+
exports.connectInitialized = connectInitialized;
|
|
5
6
|
const protocol_js_1 = require("./generated/protocol.cjs");
|
|
6
7
|
const connection_js_1 = require("./connection.cjs");
|
|
7
8
|
const socket_js_1 = require("./socket.cjs");
|
|
@@ -79,12 +80,15 @@ class ClusterClient {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
exports.ClusterClient = ClusterClient;
|
|
82
|
-
async function defaultWebSocketFactory(url, protocols) {
|
|
83
|
+
async function defaultWebSocketFactory(url, protocols, options) {
|
|
83
84
|
const globalWebSocket = globalThis.WebSocket;
|
|
84
|
-
if (globalWebSocket)
|
|
85
|
+
if (globalWebSocket) {
|
|
86
|
+
if (options?.headers && Object.keys(options.headers).length > 0) {
|
|
87
|
+
throw new errors_js_1.ClusterConfigError('WebSocket upgrade headers require the ws library; the browser WebSocket API cannot carry request headers', 'HEADERS_UNSUPPORTED');
|
|
88
|
+
}
|
|
85
89
|
return new globalWebSocket(url, protocols);
|
|
90
|
+
}
|
|
86
91
|
try {
|
|
87
|
-
// Dynamic loading keeps the optional Node runtime off the browser/global-WebSocket path.
|
|
88
92
|
const imported = await Promise.resolve().then(() => require('ws'));
|
|
89
93
|
const candidate = imported !== null && typeof imported === 'object' && 'default' in imported
|
|
90
94
|
? imported.default
|
|
@@ -93,7 +97,7 @@ async function defaultWebSocketFactory(url, protocols) {
|
|
|
93
97
|
throw new TypeError("The installed 'ws' module does not export a WebSocket constructor");
|
|
94
98
|
}
|
|
95
99
|
const Constructor = candidate;
|
|
96
|
-
return new Constructor(url, protocols);
|
|
100
|
+
return options?.headers ? new Constructor(url, protocols, { headers: options.headers }) : new Constructor(url, protocols);
|
|
97
101
|
}
|
|
98
102
|
catch (cause) {
|
|
99
103
|
throw new errors_js_1.ClusterConfigError("No WebSocket runtime is available; install 'ws' or pass webSocketFactory", 'WEBSOCKET_UNAVAILABLE', { cause });
|
|
@@ -128,7 +132,7 @@ async function connect(url, options = {}) {
|
|
|
128
132
|
const factory = options.webSocketFactory ?? defaultWebSocketFactory;
|
|
129
133
|
let socket;
|
|
130
134
|
try {
|
|
131
|
-
socket = await factory(url, options.protocols);
|
|
135
|
+
socket = await factory(url, options.protocols, options.headers ? { headers: options.headers } : undefined);
|
|
132
136
|
await waitForOpen(socket, options.signal);
|
|
133
137
|
const connection = new connection_js_1.Connection(socket);
|
|
134
138
|
await new ClusterClient(connection).initialize(options.initialize, options.signal === undefined ? {} : { signal: options.signal });
|
|
@@ -144,3 +148,24 @@ async function connect(url, options = {}) {
|
|
|
144
148
|
throw error;
|
|
145
149
|
}
|
|
146
150
|
}
|
|
151
|
+
async function connectInitialized(url, options = {}) {
|
|
152
|
+
const factory = options.webSocketFactory ?? defaultWebSocketFactory;
|
|
153
|
+
let socket;
|
|
154
|
+
try {
|
|
155
|
+
socket = await factory(url, options.protocols, options.headers ? { headers: options.headers } : undefined);
|
|
156
|
+
await waitForOpen(socket, options.signal);
|
|
157
|
+
const connection = new connection_js_1.Connection(socket);
|
|
158
|
+
const client = new ClusterClient(connection);
|
|
159
|
+
const initializeResult = await client.initialize(options.initialize, options.signal === undefined ? {} : { signal: options.signal });
|
|
160
|
+
return { connection, client, initializeResult };
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (socket) {
|
|
164
|
+
try {
|
|
165
|
+
await socket.close();
|
|
166
|
+
}
|
|
167
|
+
catch { /* preserve the construction error */ }
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/lib/cluster/client.d.ts
CHANGED
|
@@ -3,11 +3,15 @@ import { Connection } from './connection.js';
|
|
|
3
3
|
import type { CallOptions } from './connection.js';
|
|
4
4
|
import type { WebSocketLike } from './socket.js';
|
|
5
5
|
import { AgentAttachSubscriptionStream, LogsSubscriptionStream, WatchSubscriptionStream } from './subscriptions.js';
|
|
6
|
+
export interface WebSocketFactoryOptions {
|
|
7
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
8
|
+
}
|
|
6
9
|
export interface ConnectOptions {
|
|
7
10
|
readonly protocols?: string | readonly string[];
|
|
8
|
-
readonly webSocketFactory?: (url: string, protocols?: string | readonly string[]) => WebSocketLike | Promise<WebSocketLike>;
|
|
11
|
+
readonly webSocketFactory?: (url: string, protocols?: string | readonly string[], options?: WebSocketFactoryOptions) => WebSocketLike | Promise<WebSocketLike>;
|
|
9
12
|
readonly signal?: AbortSignal;
|
|
10
13
|
readonly initialize?: InitializeParams;
|
|
14
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
11
15
|
}
|
|
12
16
|
export interface WatchSubscription {
|
|
13
17
|
readonly result: WatchResult;
|
|
@@ -24,6 +28,11 @@ export interface AgentAttachSubscription {
|
|
|
24
28
|
export interface CoherentWatchSubscription extends WatchSubscription {
|
|
25
29
|
readonly snapshot: GetResult;
|
|
26
30
|
}
|
|
31
|
+
export interface ConnectInitializedResult {
|
|
32
|
+
readonly connection: Connection;
|
|
33
|
+
readonly client: ClusterClient;
|
|
34
|
+
readonly initializeResult: InitializeResult;
|
|
35
|
+
}
|
|
27
36
|
export declare class ClusterClient {
|
|
28
37
|
readonly connection: Connection;
|
|
29
38
|
constructor(connection: Connection);
|
|
@@ -42,3 +51,4 @@ export declare class ClusterClient {
|
|
|
42
51
|
agentAttach(params: AgentAttachParams, options?: CallOptions): Promise<AgentAttachSubscription>;
|
|
43
52
|
}
|
|
44
53
|
export declare function connect(url: string, options?: ConnectOptions): Promise<Connection>;
|
|
54
|
+
export declare function connectInitialized(url: string, options?: ConnectOptions): Promise<ConnectInitializedResult>;
|
package/lib/cluster/client.mjs
CHANGED
|
@@ -74,12 +74,15 @@ export class ClusterClient {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
async function defaultWebSocketFactory(url, protocols) {
|
|
77
|
+
async function defaultWebSocketFactory(url, protocols, options) {
|
|
78
78
|
const globalWebSocket = globalThis.WebSocket;
|
|
79
|
-
if (globalWebSocket)
|
|
79
|
+
if (globalWebSocket) {
|
|
80
|
+
if (options?.headers && Object.keys(options.headers).length > 0) {
|
|
81
|
+
throw new ClusterConfigError('WebSocket upgrade headers require the ws library; the browser WebSocket API cannot carry request headers', 'HEADERS_UNSUPPORTED');
|
|
82
|
+
}
|
|
80
83
|
return new globalWebSocket(url, protocols);
|
|
84
|
+
}
|
|
81
85
|
try {
|
|
82
|
-
// Dynamic loading keeps the optional Node runtime off the browser/global-WebSocket path.
|
|
83
86
|
const imported = await import('ws');
|
|
84
87
|
const candidate = imported !== null && typeof imported === 'object' && 'default' in imported
|
|
85
88
|
? imported.default
|
|
@@ -88,7 +91,7 @@ async function defaultWebSocketFactory(url, protocols) {
|
|
|
88
91
|
throw new TypeError("The installed 'ws' module does not export a WebSocket constructor");
|
|
89
92
|
}
|
|
90
93
|
const Constructor = candidate;
|
|
91
|
-
return new Constructor(url, protocols);
|
|
94
|
+
return options?.headers ? new Constructor(url, protocols, { headers: options.headers }) : new Constructor(url, protocols);
|
|
92
95
|
}
|
|
93
96
|
catch (cause) {
|
|
94
97
|
throw new ClusterConfigError("No WebSocket runtime is available; install 'ws' or pass webSocketFactory", 'WEBSOCKET_UNAVAILABLE', { cause });
|
|
@@ -123,7 +126,7 @@ export async function connect(url, options = {}) {
|
|
|
123
126
|
const factory = options.webSocketFactory ?? defaultWebSocketFactory;
|
|
124
127
|
let socket;
|
|
125
128
|
try {
|
|
126
|
-
socket = await factory(url, options.protocols);
|
|
129
|
+
socket = await factory(url, options.protocols, options.headers ? { headers: options.headers } : undefined);
|
|
127
130
|
await waitForOpen(socket, options.signal);
|
|
128
131
|
const connection = new Connection(socket);
|
|
129
132
|
await new ClusterClient(connection).initialize(options.initialize, options.signal === undefined ? {} : { signal: options.signal });
|
|
@@ -139,3 +142,24 @@ export async function connect(url, options = {}) {
|
|
|
139
142
|
throw error;
|
|
140
143
|
}
|
|
141
144
|
}
|
|
145
|
+
export async function connectInitialized(url, options = {}) {
|
|
146
|
+
const factory = options.webSocketFactory ?? defaultWebSocketFactory;
|
|
147
|
+
let socket;
|
|
148
|
+
try {
|
|
149
|
+
socket = await factory(url, options.protocols, options.headers ? { headers: options.headers } : undefined);
|
|
150
|
+
await waitForOpen(socket, options.signal);
|
|
151
|
+
const connection = new Connection(socket);
|
|
152
|
+
const client = new ClusterClient(connection);
|
|
153
|
+
const initializeResult = await client.initialize(options.initialize, options.signal === undefined ? {} : { signal: options.signal });
|
|
154
|
+
return { connection, client, initializeResult };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (socket) {
|
|
158
|
+
try {
|
|
159
|
+
await socket.close();
|
|
160
|
+
}
|
|
161
|
+
catch { /* preserve the construction error */ }
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Connection = exports.PROTOCOL_DIAGNOSTIC_CAPACITY = exports.CONNECTION_TRANSITIONS = void 0;
|
|
3
|
+
exports.Connection = exports.CLOSE_REASON_MAX_BYTES = exports.PROTOCOL_DIAGNOSTIC_CAPACITY = exports.CONNECTION_TRANSITIONS = void 0;
|
|
4
4
|
const protocol_js_1 = require("./generated/protocol.cjs");
|
|
5
5
|
const errors_js_1 = require("./errors.cjs");
|
|
6
6
|
const frames_js_1 = require("./frames.cjs");
|
|
@@ -19,6 +19,21 @@ exports.CONNECTION_TRANSITIONS = Object.freeze({
|
|
|
19
19
|
CLOSED: Object.freeze([]),
|
|
20
20
|
});
|
|
21
21
|
exports.PROTOCOL_DIAGNOSTIC_CAPACITY = 128;
|
|
22
|
+
exports.CLOSE_REASON_MAX_BYTES = 123;
|
|
23
|
+
const CLOSE_REASON_ENCODER = new TextEncoder();
|
|
24
|
+
function boundedCloseReason(reason) {
|
|
25
|
+
const retained = [];
|
|
26
|
+
const scratch = new Uint8Array(4);
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
for (const codePoint of reason) {
|
|
29
|
+
const { written } = CLOSE_REASON_ENCODER.encodeInto(codePoint, scratch);
|
|
30
|
+
if (bytes + written > exports.CLOSE_REASON_MAX_BYTES)
|
|
31
|
+
break;
|
|
32
|
+
retained.push(codePoint);
|
|
33
|
+
bytes += written;
|
|
34
|
+
}
|
|
35
|
+
return retained.join('');
|
|
36
|
+
}
|
|
22
37
|
function deferred() {
|
|
23
38
|
let resolve;
|
|
24
39
|
let reject;
|
|
@@ -37,6 +52,8 @@ class Connection {
|
|
|
37
52
|
#removeSocketListeners = [];
|
|
38
53
|
#ownedSubscriptions = new WeakSet();
|
|
39
54
|
#closePromise;
|
|
55
|
+
#closeCode;
|
|
56
|
+
#closeReason;
|
|
40
57
|
closeDiagnostics = [];
|
|
41
58
|
protocolDiagnostics = [];
|
|
42
59
|
#socket;
|
|
@@ -44,11 +61,13 @@ class Connection {
|
|
|
44
61
|
if (socket.readyState !== 1)
|
|
45
62
|
throw new errors_js_1.ClusterStateError('Connection requires an already-open WebSocket', 'SOCKET_NOT_OPEN');
|
|
46
63
|
this.#socket = socket;
|
|
47
|
-
this.#removeSocketListeners.push((0, socket_js_1.addSocketListener)(socket, 'message', (event) => this.#onMessage(event)), (0, socket_js_1.addSocketListener)(socket, 'error', () => { void this.#startClose(false); }), (0, socket_js_1.addSocketListener)(socket, 'close', () => { void this.#startClose(false); }));
|
|
64
|
+
this.#removeSocketListeners.push((0, socket_js_1.addSocketListener)(socket, 'message', (event) => this.#onMessage(event)), (0, socket_js_1.addSocketListener)(socket, 'error', () => { void this.#startClose(false); }), (0, socket_js_1.addSocketListener)(socket, 'close', (...args) => { this.#captureCloseState(args); void this.#startClose(false); }));
|
|
48
65
|
}
|
|
49
66
|
get state() { return this.#state; }
|
|
50
67
|
get pendingSize() { return this.#pending.size; }
|
|
51
68
|
get subscriptionCount() { return this.#subscriptions.size; }
|
|
69
|
+
get closeCode() { return this.#closeCode; }
|
|
70
|
+
get closeReason() { return this.#closeReason; }
|
|
52
71
|
call(method, params, options = {}) {
|
|
53
72
|
if (!protocol_js_1.UNARY_METHODS.includes(method)) {
|
|
54
73
|
throw new errors_js_1.ClusterConfigError(`${method} is a subscription method`, 'INVALID_METHOD');
|
|
@@ -322,6 +341,23 @@ class Connection {
|
|
|
322
341
|
this.protocolDiagnostics.shift();
|
|
323
342
|
this.protocolDiagnostics.push(new errors_js_1.ClusterProtocolError(message, 'INVALID_PEER_FRAME', cause === undefined ? undefined : { cause }));
|
|
324
343
|
}
|
|
344
|
+
#captureCloseState(args) {
|
|
345
|
+
if (args.length === 0)
|
|
346
|
+
return;
|
|
347
|
+
const first = args[0];
|
|
348
|
+
if (typeof first === 'number') {
|
|
349
|
+
this.#closeCode = first;
|
|
350
|
+
const raw = args.length > 1 ? String(args[1]) : undefined;
|
|
351
|
+
this.#closeReason = raw === undefined ? undefined : boundedCloseReason(raw);
|
|
352
|
+
}
|
|
353
|
+
else if (first !== null && typeof first === 'object') {
|
|
354
|
+
const event = first;
|
|
355
|
+
if (typeof event.code === 'number')
|
|
356
|
+
this.#closeCode = event.code;
|
|
357
|
+
if (typeof event.reason === 'string')
|
|
358
|
+
this.#closeReason = boundedCloseReason(event.reason);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
325
361
|
#startClose(sendCancels) {
|
|
326
362
|
if (this.#closePromise)
|
|
327
363
|
return this.#closePromise;
|
|
@@ -6,6 +6,7 @@ import type { WebSocketLike } from './socket.js';
|
|
|
6
6
|
export type ConnectionState = 'OPEN' | 'CLOSING' | 'CLOSED';
|
|
7
7
|
export declare const CONNECTION_TRANSITIONS: Readonly<Record<ConnectionState, readonly ConnectionState[]>>;
|
|
8
8
|
export declare const PROTOCOL_DIAGNOSTIC_CAPACITY = 128;
|
|
9
|
+
export declare const CLOSE_REASON_MAX_BYTES = 123;
|
|
9
10
|
export interface CallOptions {
|
|
10
11
|
readonly signal?: AbortSignal;
|
|
11
12
|
readonly requestTimeoutMs?: number;
|
|
@@ -32,6 +33,8 @@ export declare class Connection {
|
|
|
32
33
|
get state(): ConnectionState;
|
|
33
34
|
get pendingSize(): number;
|
|
34
35
|
get subscriptionCount(): number;
|
|
36
|
+
get closeCode(): number | undefined;
|
|
37
|
+
get closeReason(): string | undefined;
|
|
35
38
|
call<M extends UnaryClusterMethod>(method: M, params: ClusterMethodParams[M], options?: CallOptions): Promise<ClusterMethodResults[M]>;
|
|
36
39
|
cancelSubscription(registration: SubscriptionRegistration): Promise<void>;
|
|
37
40
|
openSubscription<M extends SubscriptionMethod>(method: M, params: ClusterMethodParams[M], options?: CallOptions): Promise<EstablishedSubscription<ClusterMethodResults[M]>>;
|
|
@@ -16,6 +16,21 @@ export const CONNECTION_TRANSITIONS = Object.freeze({
|
|
|
16
16
|
CLOSED: Object.freeze([]),
|
|
17
17
|
});
|
|
18
18
|
export const PROTOCOL_DIAGNOSTIC_CAPACITY = 128;
|
|
19
|
+
export const CLOSE_REASON_MAX_BYTES = 123;
|
|
20
|
+
const CLOSE_REASON_ENCODER = new TextEncoder();
|
|
21
|
+
function boundedCloseReason(reason) {
|
|
22
|
+
const retained = [];
|
|
23
|
+
const scratch = new Uint8Array(4);
|
|
24
|
+
let bytes = 0;
|
|
25
|
+
for (const codePoint of reason) {
|
|
26
|
+
const { written } = CLOSE_REASON_ENCODER.encodeInto(codePoint, scratch);
|
|
27
|
+
if (bytes + written > CLOSE_REASON_MAX_BYTES)
|
|
28
|
+
break;
|
|
29
|
+
retained.push(codePoint);
|
|
30
|
+
bytes += written;
|
|
31
|
+
}
|
|
32
|
+
return retained.join('');
|
|
33
|
+
}
|
|
19
34
|
function deferred() {
|
|
20
35
|
let resolve;
|
|
21
36
|
let reject;
|
|
@@ -34,6 +49,8 @@ export class Connection {
|
|
|
34
49
|
#removeSocketListeners = [];
|
|
35
50
|
#ownedSubscriptions = new WeakSet();
|
|
36
51
|
#closePromise;
|
|
52
|
+
#closeCode;
|
|
53
|
+
#closeReason;
|
|
37
54
|
closeDiagnostics = [];
|
|
38
55
|
protocolDiagnostics = [];
|
|
39
56
|
#socket;
|
|
@@ -41,11 +58,13 @@ export class Connection {
|
|
|
41
58
|
if (socket.readyState !== 1)
|
|
42
59
|
throw new ClusterStateError('Connection requires an already-open WebSocket', 'SOCKET_NOT_OPEN');
|
|
43
60
|
this.#socket = socket;
|
|
44
|
-
this.#removeSocketListeners.push(addSocketListener(socket, 'message', (event) => this.#onMessage(event)), addSocketListener(socket, 'error', () => { void this.#startClose(false); }), addSocketListener(socket, 'close', () => { void this.#startClose(false); }));
|
|
61
|
+
this.#removeSocketListeners.push(addSocketListener(socket, 'message', (event) => this.#onMessage(event)), addSocketListener(socket, 'error', () => { void this.#startClose(false); }), addSocketListener(socket, 'close', (...args) => { this.#captureCloseState(args); void this.#startClose(false); }));
|
|
45
62
|
}
|
|
46
63
|
get state() { return this.#state; }
|
|
47
64
|
get pendingSize() { return this.#pending.size; }
|
|
48
65
|
get subscriptionCount() { return this.#subscriptions.size; }
|
|
66
|
+
get closeCode() { return this.#closeCode; }
|
|
67
|
+
get closeReason() { return this.#closeReason; }
|
|
49
68
|
call(method, params, options = {}) {
|
|
50
69
|
if (!UNARY_METHODS.includes(method)) {
|
|
51
70
|
throw new ClusterConfigError(`${method} is a subscription method`, 'INVALID_METHOD');
|
|
@@ -319,6 +338,23 @@ export class Connection {
|
|
|
319
338
|
this.protocolDiagnostics.shift();
|
|
320
339
|
this.protocolDiagnostics.push(new ClusterProtocolError(message, 'INVALID_PEER_FRAME', cause === undefined ? undefined : { cause }));
|
|
321
340
|
}
|
|
341
|
+
#captureCloseState(args) {
|
|
342
|
+
if (args.length === 0)
|
|
343
|
+
return;
|
|
344
|
+
const first = args[0];
|
|
345
|
+
if (typeof first === 'number') {
|
|
346
|
+
this.#closeCode = first;
|
|
347
|
+
const raw = args.length > 1 ? String(args[1]) : undefined;
|
|
348
|
+
this.#closeReason = raw === undefined ? undefined : boundedCloseReason(raw);
|
|
349
|
+
}
|
|
350
|
+
else if (first !== null && typeof first === 'object') {
|
|
351
|
+
const event = first;
|
|
352
|
+
if (typeof event.code === 'number')
|
|
353
|
+
this.#closeCode = event.code;
|
|
354
|
+
if (typeof event.reason === 'string')
|
|
355
|
+
this.#closeReason = boundedCloseReason(event.reason);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
322
358
|
#startClose(sendCancels) {
|
|
323
359
|
if (this.#closePromise)
|
|
324
360
|
return this.#closePromise;
|
package/lib/cluster/index.cjs
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.connect = exports.ClusterClient = exports.WatchSubscriptionStream = exports.LogsSubscriptionStream = exports.AgentAttachSubscriptionStream = exports.Connection = exports.PROTOCOL_DIAGNOSTIC_CAPACITY = exports.CONNECTION_TRANSITIONS = exports.assertGraphSpec = exports.assertGraphProfileSupported = exports.assertGraphProfile = exports.ClusterTransportError = exports.ClusterTimeoutError = exports.ClusterStateError = exports.ClusterRpcError = exports.ClusterRequestError = exports.ClusterProtocolError = exports.ClusterInternalError = exports.ClusterError = exports.ClusterConfigError = exports.SUBSCRIPTION_QUEUE_MAX_BYTES = void 0;
|
|
17
|
+
exports.connectInitialized = exports.connect = exports.ClusterClient = exports.WatchSubscriptionStream = exports.LogsSubscriptionStream = exports.AgentAttachSubscriptionStream = exports.Connection = exports.PROTOCOL_DIAGNOSTIC_CAPACITY = exports.CONNECTION_TRANSITIONS = exports.CLOSE_REASON_MAX_BYTES = exports.assertGraphSpec = exports.assertGraphProfileSupported = exports.assertGraphProfile = exports.ClusterTransportError = exports.ClusterTimeoutError = exports.ClusterStateError = exports.ClusterRpcError = exports.ClusterRequestError = exports.ClusterProtocolError = exports.ClusterInternalError = exports.ClusterError = exports.ClusterConfigError = exports.SUBSCRIPTION_QUEUE_MAX_BYTES = void 0;
|
|
18
18
|
__exportStar(require("./generated/protocol.cjs"), exports);
|
|
19
19
|
var queue_js_1 = require("./queue.cjs");
|
|
20
20
|
Object.defineProperty(exports, "SUBSCRIPTION_QUEUE_MAX_BYTES", { enumerable: true, get: function () { return queue_js_1.SUBSCRIPTION_QUEUE_MAX_BYTES; } });
|
|
@@ -35,6 +35,7 @@ Object.defineProperty(exports, "assertGraphSpec", { enumerable: true, get: funct
|
|
|
35
35
|
__exportStar(require("./payload-value.cjs"), exports);
|
|
36
36
|
__exportStar(require("./json-source.cjs"), exports);
|
|
37
37
|
var connection_js_1 = require("./connection.cjs");
|
|
38
|
+
Object.defineProperty(exports, "CLOSE_REASON_MAX_BYTES", { enumerable: true, get: function () { return connection_js_1.CLOSE_REASON_MAX_BYTES; } });
|
|
38
39
|
Object.defineProperty(exports, "CONNECTION_TRANSITIONS", { enumerable: true, get: function () { return connection_js_1.CONNECTION_TRANSITIONS; } });
|
|
39
40
|
Object.defineProperty(exports, "PROTOCOL_DIAGNOSTIC_CAPACITY", { enumerable: true, get: function () { return connection_js_1.PROTOCOL_DIAGNOSTIC_CAPACITY; } });
|
|
40
41
|
Object.defineProperty(exports, "Connection", { enumerable: true, get: function () { return connection_js_1.Connection; } });
|
|
@@ -45,3 +46,4 @@ Object.defineProperty(exports, "WatchSubscriptionStream", { enumerable: true, ge
|
|
|
45
46
|
var client_js_1 = require("./client.cjs");
|
|
46
47
|
Object.defineProperty(exports, "ClusterClient", { enumerable: true, get: function () { return client_js_1.ClusterClient; } });
|
|
47
48
|
Object.defineProperty(exports, "connect", { enumerable: true, get: function () { return client_js_1.connect; } });
|
|
49
|
+
Object.defineProperty(exports, "connectInitialized", { enumerable: true, get: function () { return client_js_1.connectInitialized; } });
|
package/lib/cluster/index.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ export { ClusterConfigError, ClusterError, ClusterInternalError, ClusterProtocol
|
|
|
4
4
|
export { assertGraphProfile, assertGraphProfileSupported, assertGraphSpec } from './validators.js';
|
|
5
5
|
export * from './payload-value.js';
|
|
6
6
|
export * from './json-source.js';
|
|
7
|
-
export { CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.js';
|
|
7
|
+
export { CLOSE_REASON_MAX_BYTES, CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.js';
|
|
8
8
|
export type { CallOptions, ConnectionState, } from './connection.js';
|
|
9
9
|
export type { WebSocketLike } from './socket.js';
|
|
10
10
|
export { AgentAttachSubscriptionStream, LogsSubscriptionStream, WatchSubscriptionStream, } from './subscriptions.js';
|
|
11
11
|
export type { Subscription, SubscriptionClosedItem, SubscriptionItem, WatchSubscriptionItem, WatchSubscriptionClosedItem, } from './subscriptions.js';
|
|
12
|
-
export { ClusterClient, connect } from './client.js';
|
|
13
|
-
export type { AgentAttachSubscription, CoherentWatchSubscription, ConnectOptions, LogsSubscription, WatchSubscription, } from './client.js';
|
|
12
|
+
export { ClusterClient, connect, connectInitialized } from './client.js';
|
|
13
|
+
export type { AgentAttachSubscription, CoherentWatchSubscription, ConnectInitializedResult, ConnectOptions, LogsSubscription, WatchSubscription, WebSocketFactoryOptions, } from './client.js';
|
package/lib/cluster/index.mjs
CHANGED
|
@@ -4,6 +4,6 @@ export { ClusterConfigError, ClusterError, ClusterInternalError, ClusterProtocol
|
|
|
4
4
|
export { assertGraphProfile, assertGraphProfileSupported, assertGraphSpec } from './validators.mjs';
|
|
5
5
|
export * from './payload-value.mjs';
|
|
6
6
|
export * from './json-source.mjs';
|
|
7
|
-
export { CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.mjs';
|
|
7
|
+
export { CLOSE_REASON_MAX_BYTES, CONNECTION_TRANSITIONS, PROTOCOL_DIAGNOSTIC_CAPACITY, Connection } from './connection.mjs';
|
|
8
8
|
export { AgentAttachSubscriptionStream, LogsSubscriptionStream, WatchSubscriptionStream, } from './subscriptions.mjs';
|
|
9
|
-
export { ClusterClient, connect } from './client.mjs';
|
|
9
|
+
export { ClusterClient, connect, connectInitialized } from './client.mjs';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HostedSessionCoordinator = void 0;
|
|
4
|
+
const index_js_1 = require("../cluster/index.cjs");
|
|
5
|
+
function combineSignals(signals) {
|
|
6
|
+
const defined = signals.filter((s) => s !== undefined);
|
|
7
|
+
if (defined.length === 0)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (defined.length === 1)
|
|
10
|
+
return defined[0];
|
|
11
|
+
return AbortSignal.any(defined);
|
|
12
|
+
}
|
|
13
|
+
class HostedSessionCoordinator {
|
|
14
|
+
#getAccess;
|
|
15
|
+
#connectOptions;
|
|
16
|
+
#clock;
|
|
17
|
+
#closeController = new AbortController();
|
|
18
|
+
#referenceCapabilities;
|
|
19
|
+
#closed = false;
|
|
20
|
+
constructor(init) {
|
|
21
|
+
this.#getAccess = init.getAccess;
|
|
22
|
+
this.#connectOptions = init.connectOptions;
|
|
23
|
+
this.#clock = init.clock ?? Date;
|
|
24
|
+
}
|
|
25
|
+
async open(signal) {
|
|
26
|
+
this.#requireNotClosed();
|
|
27
|
+
const session = await this.#createSession(signal);
|
|
28
|
+
this.#referenceCapabilities = session.initializeResult.capabilities;
|
|
29
|
+
return session;
|
|
30
|
+
}
|
|
31
|
+
async replace(signal) {
|
|
32
|
+
this.#requireNotClosed();
|
|
33
|
+
const session = await this.#createSession(signal);
|
|
34
|
+
this.#verifyCapabilities(session.initializeResult.capabilities, session);
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
renewalDeadline(access, receivedAt) {
|
|
38
|
+
const expiresAt = Date.parse(access.expiresAt);
|
|
39
|
+
if (Number.isNaN(expiresAt)) {
|
|
40
|
+
throw new index_js_1.ClusterConfigError(`invalid expiresAt: ${access.expiresAt}`, 'INVALID_EXPIRY');
|
|
41
|
+
}
|
|
42
|
+
const lifetime = expiresAt - receivedAt;
|
|
43
|
+
return Math.min(expiresAt - 30_000, receivedAt + 0.8 * lifetime);
|
|
44
|
+
}
|
|
45
|
+
async close() {
|
|
46
|
+
this.#closed = true;
|
|
47
|
+
this.#closeController.abort();
|
|
48
|
+
}
|
|
49
|
+
async #createSession(signal) {
|
|
50
|
+
const combined = combineSignals([signal, this.#closeController.signal]);
|
|
51
|
+
const access = await this.#getAccess(combined);
|
|
52
|
+
const expiresAt = Date.parse(access.expiresAt);
|
|
53
|
+
if (Number.isNaN(expiresAt)) {
|
|
54
|
+
throw new index_js_1.ClusterConfigError(`invalid expiresAt: ${access.expiresAt}`, 'INVALID_EXPIRY');
|
|
55
|
+
}
|
|
56
|
+
if (expiresAt <= this.#clock.now()) {
|
|
57
|
+
throw new index_js_1.ClusterConfigError('access token is already expired', 'ACCESS_EXPIRED');
|
|
58
|
+
}
|
|
59
|
+
let endpoint;
|
|
60
|
+
try {
|
|
61
|
+
endpoint = new URL(access.endpoint);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new index_js_1.ClusterConfigError('hosted access endpoint is invalid', 'INVALID_ENDPOINT');
|
|
65
|
+
}
|
|
66
|
+
if (endpoint.protocol !== 'wss:') {
|
|
67
|
+
throw new index_js_1.ClusterConfigError('hosted access endpoint must use wss', 'INSECURE_ENDPOINT');
|
|
68
|
+
}
|
|
69
|
+
return (0, index_js_1.connectInitialized)(endpoint.href, {
|
|
70
|
+
...this.#connectOptions,
|
|
71
|
+
headers: { Authorization: `Bearer ${access.token}` },
|
|
72
|
+
...(combined !== undefined ? { signal: combined } : {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
#verifyCapabilities(incoming, session) {
|
|
76
|
+
if (!this.#referenceCapabilities)
|
|
77
|
+
return;
|
|
78
|
+
const ref = this.#referenceCapabilities;
|
|
79
|
+
const mismatches = [];
|
|
80
|
+
if (ref.graphProfiles) {
|
|
81
|
+
const incomingProfiles = new Set(incoming.graphProfiles ?? []);
|
|
82
|
+
for (const profile of ref.graphProfiles) {
|
|
83
|
+
if (!incomingProfiles.has(profile))
|
|
84
|
+
mismatches.push(`missing graphProfile: ${profile}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (ref.logs && !incoming.logs)
|
|
88
|
+
mismatches.push('missing capability: logs');
|
|
89
|
+
if (ref.agentAttach && !incoming.agentAttach)
|
|
90
|
+
mismatches.push('missing capability: agentAttach');
|
|
91
|
+
if (mismatches.length > 0) {
|
|
92
|
+
void session.connection.close();
|
|
93
|
+
throw new index_js_1.ClusterConfigError(`replacement capabilities incompatible: ${mismatches.join(', ')}`, 'INCOMPATIBLE_CAPABILITIES');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
#requireNotClosed() {
|
|
97
|
+
if (this.#closed)
|
|
98
|
+
throw new index_js_1.ClusterConfigError('coordinator is closed', 'COORDINATOR_CLOSED');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.HostedSessionCoordinator = HostedSessionCoordinator;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AccessResponse, HostedSessionInit, InitializedSession } from './types.js';
|
|
2
|
+
export declare class HostedSessionCoordinator {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(init: HostedSessionInit);
|
|
5
|
+
open(signal?: AbortSignal): Promise<InitializedSession>;
|
|
6
|
+
replace(signal?: AbortSignal): Promise<InitializedSession>;
|
|
7
|
+
renewalDeadline(access: AccessResponse, receivedAt: number): number;
|
|
8
|
+
close(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ClusterConfigError, connectInitialized } from '../cluster/index.mjs';
|
|
2
|
+
function combineSignals(signals) {
|
|
3
|
+
const defined = signals.filter((s) => s !== undefined);
|
|
4
|
+
if (defined.length === 0)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (defined.length === 1)
|
|
7
|
+
return defined[0];
|
|
8
|
+
return AbortSignal.any(defined);
|
|
9
|
+
}
|
|
10
|
+
export class HostedSessionCoordinator {
|
|
11
|
+
#getAccess;
|
|
12
|
+
#connectOptions;
|
|
13
|
+
#clock;
|
|
14
|
+
#closeController = new AbortController();
|
|
15
|
+
#referenceCapabilities;
|
|
16
|
+
#closed = false;
|
|
17
|
+
constructor(init) {
|
|
18
|
+
this.#getAccess = init.getAccess;
|
|
19
|
+
this.#connectOptions = init.connectOptions;
|
|
20
|
+
this.#clock = init.clock ?? Date;
|
|
21
|
+
}
|
|
22
|
+
async open(signal) {
|
|
23
|
+
this.#requireNotClosed();
|
|
24
|
+
const session = await this.#createSession(signal);
|
|
25
|
+
this.#referenceCapabilities = session.initializeResult.capabilities;
|
|
26
|
+
return session;
|
|
27
|
+
}
|
|
28
|
+
async replace(signal) {
|
|
29
|
+
this.#requireNotClosed();
|
|
30
|
+
const session = await this.#createSession(signal);
|
|
31
|
+
this.#verifyCapabilities(session.initializeResult.capabilities, session);
|
|
32
|
+
return session;
|
|
33
|
+
}
|
|
34
|
+
renewalDeadline(access, receivedAt) {
|
|
35
|
+
const expiresAt = Date.parse(access.expiresAt);
|
|
36
|
+
if (Number.isNaN(expiresAt)) {
|
|
37
|
+
throw new ClusterConfigError(`invalid expiresAt: ${access.expiresAt}`, 'INVALID_EXPIRY');
|
|
38
|
+
}
|
|
39
|
+
const lifetime = expiresAt - receivedAt;
|
|
40
|
+
return Math.min(expiresAt - 30_000, receivedAt + 0.8 * lifetime);
|
|
41
|
+
}
|
|
42
|
+
async close() {
|
|
43
|
+
this.#closed = true;
|
|
44
|
+
this.#closeController.abort();
|
|
45
|
+
}
|
|
46
|
+
async #createSession(signal) {
|
|
47
|
+
const combined = combineSignals([signal, this.#closeController.signal]);
|
|
48
|
+
const access = await this.#getAccess(combined);
|
|
49
|
+
const expiresAt = Date.parse(access.expiresAt);
|
|
50
|
+
if (Number.isNaN(expiresAt)) {
|
|
51
|
+
throw new ClusterConfigError(`invalid expiresAt: ${access.expiresAt}`, 'INVALID_EXPIRY');
|
|
52
|
+
}
|
|
53
|
+
if (expiresAt <= this.#clock.now()) {
|
|
54
|
+
throw new ClusterConfigError('access token is already expired', 'ACCESS_EXPIRED');
|
|
55
|
+
}
|
|
56
|
+
let endpoint;
|
|
57
|
+
try {
|
|
58
|
+
endpoint = new URL(access.endpoint);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new ClusterConfigError('hosted access endpoint is invalid', 'INVALID_ENDPOINT');
|
|
62
|
+
}
|
|
63
|
+
if (endpoint.protocol !== 'wss:') {
|
|
64
|
+
throw new ClusterConfigError('hosted access endpoint must use wss', 'INSECURE_ENDPOINT');
|
|
65
|
+
}
|
|
66
|
+
return connectInitialized(endpoint.href, {
|
|
67
|
+
...this.#connectOptions,
|
|
68
|
+
headers: { Authorization: `Bearer ${access.token}` },
|
|
69
|
+
...(combined !== undefined ? { signal: combined } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
#verifyCapabilities(incoming, session) {
|
|
73
|
+
if (!this.#referenceCapabilities)
|
|
74
|
+
return;
|
|
75
|
+
const ref = this.#referenceCapabilities;
|
|
76
|
+
const mismatches = [];
|
|
77
|
+
if (ref.graphProfiles) {
|
|
78
|
+
const incomingProfiles = new Set(incoming.graphProfiles ?? []);
|
|
79
|
+
for (const profile of ref.graphProfiles) {
|
|
80
|
+
if (!incomingProfiles.has(profile))
|
|
81
|
+
mismatches.push(`missing graphProfile: ${profile}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (ref.logs && !incoming.logs)
|
|
85
|
+
mismatches.push('missing capability: logs');
|
|
86
|
+
if (ref.agentAttach && !incoming.agentAttach)
|
|
87
|
+
mismatches.push('missing capability: agentAttach');
|
|
88
|
+
if (mismatches.length > 0) {
|
|
89
|
+
void session.connection.close();
|
|
90
|
+
throw new ClusterConfigError(`replacement capabilities incompatible: ${mismatches.join(', ')}`, 'INCOMPATIBLE_CAPABILITIES');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
#requireNotClosed() {
|
|
94
|
+
if (this.#closed)
|
|
95
|
+
throw new ClusterConfigError('coordinator is closed', 'COORDINATOR_CLOSED');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HostedSessionCoordinator = void 0;
|
|
4
|
+
var coordinator_js_1 = require("./coordinator.cjs");
|
|
5
|
+
Object.defineProperty(exports, "HostedSessionCoordinator", { enumerable: true, get: function () { return coordinator_js_1.HostedSessionCoordinator; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HostedSessionCoordinator } from './coordinator.mjs';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Connection } from '../cluster/index.js';
|
|
2
|
+
import type { ClusterClient, ConnectOptions } from '../cluster/index.js';
|
|
3
|
+
import type { InitializeResult } from '../cluster/index.js';
|
|
4
|
+
export interface AccessResponse {
|
|
5
|
+
readonly endpoint: string;
|
|
6
|
+
readonly token: string;
|
|
7
|
+
readonly expiresAt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface HostedSessionInit {
|
|
10
|
+
readonly getAccess: (signal?: AbortSignal) => Promise<AccessResponse>;
|
|
11
|
+
readonly connectOptions?: Omit<ConnectOptions, 'headers' | 'signal'>;
|
|
12
|
+
readonly clock?: {
|
|
13
|
+
now(): number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface InitializedSession {
|
|
17
|
+
readonly connection: Connection;
|
|
18
|
+
readonly client: ClusterClient;
|
|
19
|
+
readonly initializeResult: InitializeResult;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|