@earendil-works/pi-server 0.84.4 → 0.85.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/CHANGELOG.md +2 -0
- package/README.md +66 -37
- package/dist/connection.d.ts +9 -5
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js.map +1 -1
- package/dist/errors.d.ts +16 -18
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +23 -27
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/listener.d.ts +1 -3
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js.map +1 -1
- package/dist/server.d.ts +16 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +268 -86
- package/dist/server.js.map +1 -1
- package/dist/session-router.d.ts +40 -0
- package/dist/session-router.d.ts.map +1 -0
- package/dist/session-router.js +264 -0
- package/dist/session-router.js.map +1 -0
- package/dist/testing/client.d.ts +6 -2
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +23 -3
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/host.d.ts +57 -0
- package/dist/testing/host.d.ts.map +1 -0
- package/dist/testing/host.js +189 -0
- package/dist/testing/host.js.map +1 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/server.d.ts +8 -7
- package/dist/testing/server.d.ts.map +1 -1
- package/dist/testing/server.js +7 -7
- package/dist/testing/server.js.map +1 -1
- package/dist/transports/unix/address.d.ts +3 -0
- package/dist/transports/unix/address.d.ts.map +1 -0
- package/dist/transports/unix/address.js +9 -0
- package/dist/transports/unix/address.js.map +1 -0
- package/dist/transports/unix/index.d.ts +1 -0
- package/dist/transports/unix/index.d.ts.map +1 -1
- package/dist/transports/unix/index.js +1 -0
- package/dist/transports/unix/index.js.map +1 -1
- package/dist/transports/unix/listener.d.ts +2 -3
- package/dist/transports/unix/listener.d.ts.map +1 -1
- package/dist/transports/unix/listener.js +13 -24
- package/dist/transports/unix/listener.js.map +1 -1
- package/dist/transports/unix/preset.d.ts +5 -4
- package/dist/transports/unix/preset.d.ts.map +1 -1
- package/dist/transports/unix/preset.js +5 -4
- package/dist/transports/unix/preset.js.map +1 -1
- package/dist/transports/unix/types.d.ts +2 -2
- package/dist/transports/unix/types.d.ts.map +1 -1
- package/dist/transports/unix/types.js.map +1 -1
- package/dist/types.d.ts +40 -47
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
- package/dist/protocol.d.ts +0 -22
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -266
- package/dist/protocol.js.map +0 -1
- package/dist/sessions.d.ts +0 -2174
- package/dist/sessions.d.ts.map +0 -1
- package/dist/sessions.js +0 -305
- package/dist/sessions.js.map +0 -1
- package/dist/snapshots.d.ts +0 -24
- package/dist/snapshots.d.ts.map +0 -1
- package/dist/snapshots.js +0 -39
- package/dist/snapshots.js.map +0 -1
- package/dist/testing/service.d.ts +0 -59
- package/dist/testing/service.d.ts.map +0 -1
- package/dist/testing/service.js +0 -240
- package/dist/testing/service.js.map +0 -1
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\ttype ClientHello,\n\ttype ClientMessage,\n\tClientMessageDecoder,\n\tDEFAULT_MAX_FRAME_LENGTH,\n\tencodeServerMessage,\n\tisSupportedProtocolVersion,\n\tPROTOCOL_VERSION,\n\ttype ProtocolError,\n\tProtocolValidationError,\n\ttype RequestEnvelope,\n\ttype ResponseEnvelope,\n\ttype ServerHello,\n\ttype ServerHelloError,\n\ttype ServerMessage,\n} from \"@earendil-works/pi-protocol\";\nimport {\n\ttype ByteConnection,\n\ttype ByteConnectionHandler,\n\ttype ConnectionState,\n\tisTerminalConnection,\n} from \"./connection.ts\";\nimport {\n\tINTERNAL_SERVER_ERROR_MESSAGE,\n\tInternalServerError,\n\tNOT_IMPLEMENTED_MESSAGE,\n\tPiServerError,\n} from \"./errors.ts\";\nimport type { PiServerListener } from \"./listener.ts\";\nimport { LiveSessionManager } from \"./sessions.ts\";\nimport { ServerSnapshotPublisher } from \"./snapshots.ts\";\nimport type { PiServerOptions, PiServerService } from \"./types.ts\";\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\n\nexport class PiServer {\n\treadonly id: string;\n\n\tprivate readonly listeners: readonly PiServerListener[];\n\tprivate readonly maxFrameLength: number;\n\tprivate readonly handshakeTimeoutMs: number;\n\tprivate readonly onError: ((error: Error) => void) | undefined;\n\tprivate readonly connections = new Set<ConnectionState>();\n\tprivate readonly sessions: LiveSessionManager;\n\tprivate readonly snapshots: ServerSnapshotPublisher;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate startPromise?: Promise<this>;\n\tprivate started = false;\n\n\tconstructor(service: PiServerService, options: PiServerOptions) {\n\t\tconst resolved = resolveOptions(options);\n\t\tthis.listeners = options.listeners;\n\t\tthis.id = options.serverId ?? randomUUID();\n\t\tthis.maxFrameLength = resolved.maxFrameLength;\n\t\tthis.handshakeTimeoutMs = resolved.handshakeTimeoutMs;\n\t\tthis.onError = options.onError;\n\t\tthis.sessions = new LiveSessionManager({\n\t\t\tservice,\n\t\t\tisClosing: () => this.closing,\n\t\t\tsendMessage: (connection, message) => this.sendMessage(connection, message),\n\t\t\tcloseConnection: (connection) => this.closeConnection(connection),\n\t\t\tdisconnect: (connection) => this.disconnect(connection),\n\t\t\tbroadcastServerSnapshot: () => void this.snapshots.broadcast(),\n\t\t\treportError: (error) => this.reportError(error),\n\t\t});\n\t\tthis.snapshots = new ServerSnapshotPublisher({\n\t\t\tserverId: this.id,\n\t\t\tservice,\n\t\t\tconnections: this.connections,\n\t\t\tisClosing: () => this.closing,\n\t\t\tlistSessions: () => this.sessions.listMetadata(),\n\t\t\tsendMessage: (connection, message) => this.sendMessage(connection, message),\n\t\t\treportError: (error) => this.reportError(error),\n\t\t});\n\t}\n\n\tget addresses(): readonly string[] {\n\t\treturn this.listeners.flatMap((listener) => (listener.address === undefined ? [] : [listener.address]));\n\t}\n\n\tstart(): Promise<this> {\n\t\tif (this.started) return Promise.reject(new Error(\"PiServer is already started\"));\n\t\tif (this.startPromise) return Promise.reject(new Error(\"PiServer is already starting\"));\n\t\tif (this.closing) return Promise.reject(new Error(\"PiServer is closing or closed\"));\n\t\tthis.startPromise = this.startInternal();\n\t\treturn this.startPromise;\n\t}\n\n\tprivate async startInternal(): Promise<this> {\n\t\tconst started: PiServerListener[] = [];\n\t\ttry {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\tawait listener.start((connection) => this.accept(connection));\n\t\t\t\tstarted.push(listener);\n\t\t\t}\n\t\t\tthis.started = true;\n\t\t\treturn this;\n\t\t} catch (error) {\n\t\t\tthis.closing = true;\n\t\t\tawait Promise.allSettled(started.map((listener) => listener.close()));\n\t\t\tawait this.closeServerState();\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis.startPromise = undefined;\n\t\t}\n\t}\n\n\taccept(connection: ByteConnection): ByteConnectionHandler {\n\t\tif (this.closing) {\n\t\t\tvoid this.closeConnection(connection);\n\t\t\treturn {\n\t\t\t\tonData: () => {},\n\t\t\t\tonClose: () => {},\n\t\t\t\tonError: (error) => this.reportError(error),\n\t\t\t};\n\t\t}\n\n\t\tlet state: ConnectionState;\n\t\tconst handshakeTimeout = setTimeout(() => {\n\t\t\tvoid this.failProtocol(state, {\n\t\t\t\tcode: \"invalid_request\",\n\t\t\t\tmessage: \"Handshake timeout\",\n\t\t\t});\n\t\t}, this.handshakeTimeoutMs);\n\t\thandshakeTimeout.unref();\n\t\tstate = {\n\t\t\tid: randomUUID(),\n\t\t\tconnection,\n\t\t\tdecoder: new ClientMessageDecoder({ maxFrameLength: this.maxFrameLength }),\n\t\t\tsessionIds: new Set(),\n\t\t\tstage: \"awaitingHello\",\n\t\t\tdisconnected: false,\n\t\t\thandshakeComplete: false,\n\t\t\thandshakeTimeout,\n\t\t};\n\t\tthis.connections.add(state);\n\n\t\treturn {\n\t\t\tonData: (chunk) => this.receive(state, chunk),\n\t\t\tonClose: () => this.transportClosed(state),\n\t\t\tonError: (error) => {\n\t\t\t\tthis.reportError(error);\n\t\t\t\tvoid this.closeConnection(connection).then(() => this.disconnect(state));\n\t\t\t},\n\t\t};\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst starting = this.startPromise;\n\t\tif (starting) await starting.catch(() => {});\n\t\ttry {\n\t\t\tawait Promise.all(this.listeners.map((listener) => listener.close()));\n\t\t} finally {\n\t\t\tawait this.closeServerState();\n\t\t\tthis.started = false;\n\t\t}\n\t}\n\n\tprivate receive(state: ConnectionState, chunk: Uint8Array): void {\n\t\tif (isTerminalConnection(state)) return;\n\t\tlet messages: ClientMessage[];\n\t\ttry {\n\t\t\tmessages = state.decoder.push(chunk);\n\t\t} catch (error) {\n\t\t\tvoid this.failProtocol(state, this.toProtocolError(error));\n\t\t\treturn;\n\t\t}\n\t\tfor (const message of messages) {\n\t\t\tif (isTerminalConnection(state)) return;\n\t\t\tthis.dispatchMessage(state, message);\n\t\t}\n\t}\n\n\tprivate dispatchMessage(state: ConnectionState, message: ClientMessage): void {\n\t\tif (state.stage === \"awaitingHello\") {\n\t\t\tif (message.type !== \"hello\") {\n\t\t\t\tvoid this.failProtocol(state, {\n\t\t\t\t\tcode: \"invalid_request\",\n\t\t\t\t\tmessage: \"The first client message must be hello\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstate.stage = \"handshaking\";\n\t\t\tstate.handshake = this.finishHandshake(state, message).catch((error: unknown) =>\n\t\t\t\tthis.failProtocol(state, this.toProtocolError(error)),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"hello\") {\n\t\t\tvoid this.failProtocol(state, {\n\t\t\t\tcode: \"invalid_request\",\n\t\t\t\tmessage: \"hello may only be sent as the first message\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (state.stage === \"ready\") {\n\t\t\tvoid this.handleRequest(state, message);\n\t\t\treturn;\n\t\t}\n\t\tif (state.stage !== \"handshaking\") return;\n\t\tconst handshake = state.handshake;\n\t\tif (!handshake) return;\n\t\tvoid handshake.then(() => {\n\t\t\tif (state.stage === \"ready\" && !state.disconnected) void this.handleRequest(state, message);\n\t\t});\n\t}\n\n\tprivate async finishHandshake(state: ConnectionState, hello: ClientHello): Promise<void> {\n\t\tif (!isSupportedProtocolVersion(hello.version)) {\n\t\t\tawait this.failProtocol(state, {\n\t\t\t\tcode: \"version\",\n\t\t\t\tmessage: `Unsupported protocol version ${hello.version}; expected ${PROTOCOL_VERSION}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst snapshot = await this.snapshots.get();\n\t\tif (this.closing || state.disconnected || state.stage !== \"handshaking\" || state.connection.closed) return;\n\t\tconst sent = await this.sendMessage(state, {\n\t\t\ttype: \"hello\",\n\t\t\tversion: PROTOCOL_VERSION,\n\t\t\tconnectionId: state.id,\n\t\t\tsnapshot,\n\t\t} satisfies ServerHello);\n\t\tif (sent && !state.disconnected && state.stage === \"handshaking\") {\n\t\t\tstate.handshakeComplete = true;\n\t\t\tstate.stage = \"ready\";\n\t\t\tclearTimeout(state.handshakeTimeout);\n\t\t\tif (snapshot.revision !== this.snapshots.currentRevision) {\n\t\t\t\tconst current = await this.snapshots.get();\n\t\t\t\tawait this.sendMessage(state, {\n\t\t\t\t\ttype: \"event\",\n\t\t\t\t\tevent: { type: \"server_snapshot\", snapshot: current },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async handleRequest(state: ConnectionState, envelope: RequestEnvelope): Promise<void> {\n\t\ttry {\n\t\t\tconst result = await this.sessions.executeCommand(state, envelope.request);\n\t\t\tawait this.sendMessage(state, {\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: envelope.id,\n\t\t\t\tok: true,\n\t\t\t\tresult,\n\t\t\t} satisfies ResponseEnvelope);\n\t\t} catch (error) {\n\t\t\tawait this.sendMessage(state, {\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: envelope.id,\n\t\t\t\tok: false,\n\t\t\t\terror: this.toProtocolError(error),\n\t\t\t} satisfies ResponseEnvelope);\n\t\t}\n\t}\n\n\tprivate transportClosed(connection: ConnectionState): void {\n\t\tif (!connection.disconnected && connection.stage !== \"closing\") {\n\t\t\ttry {\n\t\t\t\tconnection.decoder.end();\n\t\t\t} catch (error) {\n\t\t\t\tthis.reportError(error);\n\t\t\t}\n\t\t}\n\t\tvoid this.disconnect(connection);\n\t}\n\n\tprivate async disconnect(connection: ConnectionState): Promise<void> {\n\t\tif (connection.disconnected) return;\n\t\tconst handshakeComplete = connection.handshakeComplete;\n\t\tconnection.disconnected = true;\n\t\tconnection.stage = \"closed\";\n\t\tclearTimeout(connection.handshakeTimeout);\n\t\tthis.connections.delete(connection);\n\t\tawait this.sessions.disconnect(connection);\n\t\tif (!this.closing && handshakeComplete) void this.snapshots.broadcast();\n\t}\n\n\tprivate async sendMessage(connection: ConnectionState, message: ServerMessage): Promise<boolean> {\n\t\tif (connection.disconnected || connection.connection.closed) return false;\n\t\tlet frame: Uint8Array;\n\t\ttry {\n\t\t\tframe = encodeServerMessage(message, { maxFrameLength: this.maxFrameLength });\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t\tawait this.closeConnection(connection.connection);\n\t\t\tawait this.disconnect(connection);\n\t\t\treturn false;\n\t\t}\n\t\ttry {\n\t\t\tawait connection.connection.send(frame);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t\tawait this.closeConnection(connection.connection);\n\t\t\tawait this.disconnect(connection);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate async failProtocol(connection: ConnectionState, error: ProtocolError): Promise<void> {\n\t\tif (connection.disconnected || connection.stage === \"closing\" || connection.stage === \"closed\") return;\n\t\tconnection.stage = \"closing\";\n\t\tclearTimeout(connection.handshakeTimeout);\n\t\tconst message: ServerHelloError = { type: \"hello_error\", error };\n\t\tlet finalFrame: Uint8Array | undefined;\n\t\ttry {\n\t\t\tfinalFrame = encodeServerMessage(message, { maxFrameLength: this.maxFrameLength });\n\t\t} catch (encodeError) {\n\t\t\tthis.reportError(encodeError);\n\t\t}\n\t\tawait this.closeConnection(connection.connection, finalFrame);\n\t\tawait this.disconnect(connection);\n\t}\n\n\tprivate async closeServerState(): Promise<void> {\n\t\tconst connections = [...this.connections];\n\t\tfor (const connection of connections) {\n\t\t\tconnection.stage = \"closing\";\n\t\t\tclearTimeout(connection.handshakeTimeout);\n\t\t}\n\t\tawait Promise.all(connections.map((connection) => this.closeConnection(connection.connection)));\n\t\tawait Promise.all(connections.map((connection) => this.disconnect(connection)));\n\n\t\tawait this.sessions.close();\n\t\tthis.connections.clear();\n\t}\n\n\tprivate async closeConnection(connection: ByteConnection, finalChunk?: Uint8Array): Promise<void> {\n\t\ttry {\n\t\t\tawait connection.close(finalChunk);\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t}\n\t}\n\n\tprivate toProtocolError(error: unknown): ProtocolError {\n\t\tif (error instanceof InternalServerError) {\n\t\t\tthis.reportError(error.cause);\n\t\t\treturn { code: \"internal_error\", message: INTERNAL_SERVER_ERROR_MESSAGE };\n\t\t}\n\t\tif (error instanceof PiServerError) {\n\t\t\tif (error.code === \"not_implemented\") {\n\t\t\t\treturn { code: \"not_implemented\", message: NOT_IMPLEMENTED_MESSAGE };\n\t\t\t}\n\t\t\treturn error.details === undefined\n\t\t\t\t? { code: error.code, message: error.message }\n\t\t\t\t: { code: error.code, message: error.message, details: error.details };\n\t\t}\n\t\tif (error instanceof ProtocolValidationError) {\n\t\t\treturn { code: \"invalid_request\", message: error.message };\n\t\t}\n\t\tthis.reportError(error);\n\t\treturn { code: \"internal_error\", message: INTERNAL_SERVER_ERROR_MESSAGE };\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect server state.\n\t\t}\n\t}\n}\n\nfunction resolveOptions(options: PiServerOptions): { maxFrameLength: number; handshakeTimeoutMs: number } {\n\tif (!Array.isArray(options.listeners)) throw new TypeError(\"PiServer listeners must be an array\");\n\tif (options.serverId === \"\") throw new TypeError(\"PiServer serverId must not be empty\");\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(handshakeTimeoutMs) ||\n\t\thandshakeTimeoutMs <= 0 ||\n\t\thandshakeTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`PiServer handshakeTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn { maxFrameLength, handshakeTimeoutMs };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcreateServiceStateEncoder,\n\tdecodeServiceControlCall,\n\ttype JsonValue,\n\tparseServiceCall,\n\tparseServiceSubscriptionSnapshot,\n\tRemoteServiceError,\n\ttype ServiceCall,\n\ttype ServiceProviderUpdate,\n} from \"@earendil-works/chord\";\nimport { BACKGROUND_CONTEXT, type SessionMetadata, TODO_CONTEXT, withAbortSignal } from \"@earendil-works/pi-agent-core\";\nimport {\n\ttype CancelEnvelope,\n\ttype ClientHello,\n\ttype ClientMessage,\n\tClientMessageDecoder,\n\tDEFAULT_MAX_FRAME_LENGTH,\n\tencodeServerMessage,\n\tisServerId,\n\tisSupportedProtocolVersion,\n\tPROTOCOL_VERSION,\n\ttype ProtocolError,\n\tProtocolValidationError,\n\ttype RequestEnvelope,\n\ttype ResponseEnvelope,\n\ttype RpcTarget,\n\ttype ServerHello,\n\ttype ServerHelloError,\n\ttype ServerMessage,\n} from \"@earendil-works/pi-protocol\";\nimport {\n\ttype ByteConnection,\n\ttype ByteConnectionHandler,\n\ttype ConnectionState,\n\tisTerminalConnection,\n} from \"./connection.ts\";\nimport { INTERNAL_SERVER_ERROR_MESSAGE, ServerError, WrongServerError } from \"./errors.ts\";\nimport type { ServerListener } from \"./listener.ts\";\nimport { SessionRouter } from \"./session-router.ts\";\nimport type { ServerHost, ServerOptions } from \"./types.ts\";\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 5_000;\nconst MAX_UINT32 = 0xffff_ffff;\nconst MAX_TIMER_DELAY_MS = 2_147_483_647;\n\nexport class Server<TMetadata extends SessionMetadata = SessionMetadata> {\n\treadonly serverId: string;\n\t/** Resolves after shutdown, or rejects when listener or routed-Session cleanup fails. */\n\treadonly closed: Promise<void>;\n\n\tprivate readonly host: ServerHost<TMetadata>;\n\tprivate readonly listeners: readonly ServerListener[];\n\tprivate readonly maxFrameLength: number;\n\tprivate readonly handshakeTimeoutMs: number;\n\tprivate readonly onConnectionCountChanged: ((count: number) => void) | undefined;\n\tprivate readonly onError: ((error: Error) => void) | undefined;\n\tprivate readonly connections = new Set<ConnectionState>();\n\tprivate readonly sessions: SessionRouter<TMetadata>;\n\tprivate closing = false;\n\tprivate closePromise?: Promise<void>;\n\tprivate closedSettled = false;\n\tprivate rejectClosed!: (error: unknown) => void;\n\tprivate resolveClosed!: () => void;\n\tprivate startPromise?: Promise<this>;\n\tprivate started = false;\n\n\tconstructor(host: ServerHost<TMetadata>, options: ServerOptions) {\n\t\tconst resolved = resolveOptions(options);\n\t\tthis.host = host;\n\t\tthis.listeners = options.listeners;\n\t\tthis.serverId = options.serverId;\n\t\tthis.maxFrameLength = resolved.maxFrameLength;\n\t\tthis.handshakeTimeoutMs = resolved.handshakeTimeoutMs;\n\t\tthis.onConnectionCountChanged = options.onConnectionCountChanged;\n\t\tthis.onError = options.onError;\n\t\tthis.sessions = new SessionRouter({\n\t\t\thost,\n\t\t\tserverId: this.serverId,\n\t\t\tisClosing: () => this.closing,\n\t\t\tpublishAttachment: async (client, attachment) => {\n\t\t\t\tawait this.sendMessage(client as ConnectionState, {\n\t\t\t\t\ttype: \"attachment\",\n\t\t\t\t\tattachment: attachment ?? null,\n\t\t\t\t});\n\t\t\t},\n\t\t\treportError: (error) => this.reportError(error),\n\t\t});\n\t\tthis.closed = new Promise((resolve, reject) => {\n\t\t\tthis.resolveClosed = resolve;\n\t\t\tthis.rejectClosed = reject;\n\t\t});\n\t\tvoid this.closed.catch(() => {});\n\t}\n\n\tstart(): Promise<this> {\n\t\tif (this.started) return Promise.reject(new Error(\"Server is already started\"));\n\t\tif (this.startPromise) return Promise.reject(new Error(\"Server is already starting\"));\n\t\tif (this.closing) return Promise.reject(new Error(\"Server is closing or closed\"));\n\t\tthis.startPromise = this.startInternal();\n\t\treturn this.startPromise;\n\t}\n\n\tprivate async startInternal(): Promise<this> {\n\t\tconst started: ServerListener[] = [];\n\t\ttry {\n\t\t\tfor (const listener of this.listeners) {\n\t\t\t\tawait listener.start((connection) => this.accept(connection));\n\t\t\t\tstarted.push(listener);\n\t\t\t}\n\t\t\tthis.started = true;\n\t\t\treturn this;\n\t\t} catch (error) {\n\t\t\tthis.closing = true;\n\t\t\tconst cleanupErrors: unknown[] = [];\n\t\t\tconst listenerResults = await Promise.allSettled(started.map((listener) => listener.close()));\n\t\t\tfor (const result of listenerResults) {\n\t\t\t\tif (result.status === \"rejected\") cleanupErrors.push(result.reason);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.closeServerState();\n\t\t\t} catch (cleanupError) {\n\t\t\t\tcleanupErrors.push(cleanupError);\n\t\t\t}\n\t\t\tif (cleanupErrors.length > 0) {\n\t\t\t\tconst failure = new AggregateError([error, ...cleanupErrors], \"Server startup and cleanup failed\");\n\t\t\t\tthis.settleClosed(failure);\n\t\t\t\tthrow failure;\n\t\t\t}\n\t\t\tthis.settleClosed();\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis.startPromise = undefined;\n\t\t}\n\t}\n\n\taccept(connection: ByteConnection): ByteConnectionHandler {\n\t\tif (this.closing) {\n\t\t\tvoid this.closeConnection(connection);\n\t\t\treturn {\n\t\t\t\tonData: () => {},\n\t\t\t\tonClose: () => {},\n\t\t\t\tonError: (error) => this.reportError(error),\n\t\t\t};\n\t\t}\n\n\t\tlet state: ConnectionState;\n\t\tconst handshakeTimeout = setTimeout(() => {\n\t\t\tvoid this.failProtocol(state, {\n\t\t\t\tcode: \"invalid_request\",\n\t\t\t\tmessage: \"Handshake timeout\",\n\t\t\t});\n\t\t}, this.handshakeTimeoutMs);\n\t\thandshakeTimeout.unref();\n\t\tstate = {\n\t\t\tconnection,\n\t\t\tdecoder: new ClientMessageDecoder({ maxFrameLength: this.maxFrameLength }),\n\t\t\tserviceStateEncoders: new Map(),\n\t\t\tstage: \"awaitingHello\",\n\t\t\tdisconnected: false,\n\t\t\thandshakeTimeout,\n\t\t\tactiveRequests: new Map(),\n\t\t};\n\t\tthis.connections.add(state);\n\t\tthis.notifyConnectionCountChanged();\n\n\t\treturn {\n\t\t\tonData: (chunk) => this.receive(state, chunk),\n\t\t\tonClose: () => this.transportClosed(state),\n\t\t\tonError: (error) => {\n\t\t\t\tthis.reportError(error);\n\t\t\t\tvoid this.closeConnection(connection).then(() => this.disconnect(state));\n\t\t\t},\n\t\t};\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closePromise) return this.closePromise;\n\t\tthis.closing = true;\n\t\tthis.closePromise = this.closeInternal();\n\t\treturn this.closePromise;\n\t}\n\n\tprivate async closeInternal(): Promise<void> {\n\t\tconst starting = this.startPromise;\n\t\tif (starting) await starting.catch(() => {});\n\t\tconst errors: unknown[] = [];\n\t\tconst listenerResults = await Promise.allSettled(this.listeners.map((listener) => listener.close()));\n\t\tfor (const result of listenerResults) {\n\t\t\tif (result.status === \"rejected\") errors.push(result.reason);\n\t\t}\n\t\ttry {\n\t\t\tawait this.closeServerState();\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t\tthis.started = false;\n\t\tif (errors.length > 0) {\n\t\t\tconst failure =\n\t\t\t\terrors.length === 1 && errors[0] instanceof Error\n\t\t\t\t\t? errors[0]\n\t\t\t\t\t: new AggregateError(errors, \"Server shutdown failed\");\n\t\t\tthis.settleClosed(failure);\n\t\t\tthrow failure;\n\t\t}\n\t\tthis.settleClosed();\n\t}\n\n\tprivate receive(state: ConnectionState, chunk: Uint8Array): void {\n\t\tif (isTerminalConnection(state)) return;\n\t\tlet messages: ClientMessage[];\n\t\ttry {\n\t\t\tmessages = state.decoder.push(chunk);\n\t\t} catch (error) {\n\t\t\tvoid this.failProtocol(state, this.toProtocolError(error));\n\t\t\treturn;\n\t\t}\n\t\tfor (const message of messages) {\n\t\t\tif (isTerminalConnection(state)) return;\n\t\t\tthis.dispatchMessage(state, message);\n\t\t}\n\t}\n\n\tprivate dispatchMessage(state: ConnectionState, message: ClientMessage): void {\n\t\tif (state.stage === \"awaitingHello\") {\n\t\t\tif (message.type !== \"hello\") {\n\t\t\t\tvoid this.failProtocol(state, {\n\t\t\t\t\tcode: \"invalid_request\",\n\t\t\t\t\tmessage: \"The first client message must be hello\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstate.stage = \"handshaking\";\n\t\t\tstate.handshake = this.finishHandshake(state, message).catch((error: unknown) =>\n\t\t\t\tthis.failProtocol(state, this.toProtocolError(error)),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"hello\") {\n\t\t\tvoid this.failProtocol(state, {\n\t\t\t\tcode: \"invalid_request\",\n\t\t\t\tmessage: \"hello may only be sent as the first message\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (state.stage === \"ready\") {\n\t\t\tif (message.type === \"cancel\") this.handleCancel(state, message);\n\t\t\telse void this.handleRequest(state, message);\n\t\t\treturn;\n\t\t}\n\t\tif (state.stage !== \"handshaking\") return;\n\t\tconst handshake = state.handshake;\n\t\tif (!handshake) return;\n\t\tvoid handshake.then(() => {\n\t\t\tif (state.stage !== \"ready\" || state.disconnected) return;\n\t\t\tif (message.type === \"cancel\") this.handleCancel(state, message);\n\t\t\telse void this.handleRequest(state, message);\n\t\t});\n\t}\n\n\tprivate async finishHandshake(state: ConnectionState, hello: ClientHello): Promise<void> {\n\t\tif (!isSupportedProtocolVersion(hello.version)) {\n\t\t\tawait this.failProtocol(state, {\n\t\t\t\tcode: \"version\",\n\t\t\t\tmessage: `Unsupported protocol version ${hello.version}; expected ${PROTOCOL_VERSION}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.closing || state.disconnected || state.stage !== \"handshaking\" || state.connection.closed) return;\n\t\tconst services = await this.host.serverServices.attachClient(\n\t\t\t{\n\t\t\t\tattachSession: async (sessionId, context) => {\n\t\t\t\t\tawait this.sessions.attachClient(state, sessionId, context);\n\t\t\t\t},\n\t\t\t\tdetachSession: (context) => this.sessions.detachClient(state, context),\n\t\t\t\tprepareSessionRemoval: (sessionId, context) => this.sessions.removeSession(sessionId, context),\n\t\t\t},\n\t\t\tTODO_CONTEXT,\n\t\t);\n\t\tif (this.closing || state.disconnected || state.stage !== \"handshaking\" || state.connection.closed) {\n\t\t\tawait services.release(TODO_CONTEXT);\n\t\t\treturn;\n\t\t}\n\t\tstate.serverServices = services;\n\t\tconst sent = await this.sendMessage(state, {\n\t\t\ttype: \"hello\",\n\t\t\tversion: PROTOCOL_VERSION,\n\t\t\tserverId: this.serverId,\n\t\t} satisfies ServerHello);\n\t\tif (sent && !state.disconnected && state.stage === \"handshaking\") {\n\t\t\tstate.stage = \"ready\";\n\t\t\tclearTimeout(state.handshakeTimeout);\n\t\t}\n\t}\n\n\tprivate handleCancel(state: ConnectionState, envelope: CancelEnvelope): void {\n\t\tif (envelope.target.serverId !== this.serverId) return;\n\t\tconst active = state.activeRequests.get(envelope.id);\n\t\tif (active !== undefined && sameTarget(active.target, envelope.target)) {\n\t\t\tactive.controller.abort(new DOMException(\"RPC request cancelled\", \"AbortError\"));\n\t\t}\n\t}\n\n\tprivate async handleRequest(state: ConnectionState, envelope: RequestEnvelope): Promise<void> {\n\t\tif (state.activeRequests.has(envelope.id)) {\n\t\t\tawait this.sendMessage(state, {\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: envelope.id,\n\t\t\t\tok: false,\n\t\t\t\terror: { code: \"invalid_request\", message: \"Request ID is already active\" },\n\t\t\t} satisfies ResponseEnvelope);\n\t\t\treturn;\n\t\t}\n\t\tlet call: ServiceCall;\n\t\ttry {\n\t\t\tcall = parseServiceCall(envelope.call);\n\t\t} catch {\n\t\t\tawait this.sendMessage(state, {\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: envelope.id,\n\t\t\t\tok: false,\n\t\t\t\terror: { code: \"invalid_request\", message: \"Invalid service call\" },\n\t\t\t} satisfies ResponseEnvelope);\n\t\t\treturn;\n\t\t}\n\t\tconst controller = new AbortController();\n\t\tconst active = { controller, target: envelope.target };\n\t\tstate.activeRequests.set(envelope.id, active);\n\t\tconst context = withAbortSignal(controller.signal, TODO_CONTEXT);\n\t\tconst control = decodeServiceControlCall(call);\n\t\tconst subscribing = control?.type === \"subscribe\" ? control : undefined;\n\t\tconst pendingUpdates: { readonly update: ServiceProviderUpdate }[] = [];\n\t\tlet subscriptionReady = subscribing === undefined;\n\t\tlet installedSubscriptionEncoder = false;\n\t\tlet responded = false;\n\t\tconst publish = async (subscriptionId: string, update: ServiceProviderUpdate): Promise<void> => {\n\t\t\tif (subscribing !== undefined && subscriptionId === subscribing.subscriptionId && !subscriptionReady) {\n\t\t\t\tpendingUpdates.push({ update });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this.sendServiceUpdate(state, subscriptionId, update);\n\t\t};\n\t\ttry {\n\t\t\tif (envelope.target.serverId !== this.serverId) throw new WrongServerError();\n\t\t\tif (subscribing !== undefined && state.serviceStateEncoders.has(subscribing.subscriptionId)) {\n\t\t\t\tthrow new ProtocolValidationError(`Duplicate service subscription ${subscribing.subscriptionId}`);\n\t\t\t}\n\t\t\tlet result: JsonValue | undefined;\n\t\t\tif (\"sessionId\" in envelope.target) {\n\t\t\t\tresult = await this.sessions.executeServiceCall(call, envelope.target, state, publish, context);\n\t\t\t} else if (state.serverServices !== undefined) {\n\t\t\t\tresult = await state.serverServices.invokeService(call, publish, context);\n\t\t\t} else {\n\t\t\t\tthrow new ProtocolValidationError(`Unknown service member ${call.serviceId}.${call.member}`);\n\t\t\t}\n\t\t\tif (subscribing !== undefined) {\n\t\t\t\tif (result === undefined)\n\t\t\t\t\tthrow new ProtocolValidationError(\"Service subscription did not return a snapshot\");\n\t\t\t\tconst stateEncoder = createServiceStateEncoder();\n\t\t\t\tresult = stateEncoder.encodeSnapshot(parseServiceSubscriptionSnapshot(result)) as unknown as JsonValue;\n\t\t\t\tstate.serviceStateEncoders.set(subscribing.subscriptionId, stateEncoder);\n\t\t\t\tinstalledSubscriptionEncoder = true;\n\t\t\t} else if (control?.type === \"unsubscribe\") {\n\t\t\t\tstate.serviceStateEncoders.delete(control.subscriptionId);\n\t\t\t}\n\t\t\tawait this.sendMessage(\n\t\t\t\tstate,\n\t\t\t\tresult === undefined\n\t\t\t\t\t? { type: \"response\", id: envelope.id, ok: true }\n\t\t\t\t\t: { type: \"response\", id: envelope.id, ok: true, result },\n\t\t\t);\n\t\t\tresponded = true;\n\t\t\tif (subscribing !== undefined) {\n\t\t\t\twhile (pendingUpdates.length > 0) {\n\t\t\t\t\tconst pending = pendingUpdates.shift();\n\t\t\t\t\tif (pending !== undefined)\n\t\t\t\t\t\tawait this.sendServiceUpdate(state, subscribing.subscriptionId, pending.update);\n\t\t\t\t}\n\t\t\t\tsubscriptionReady = true;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (subscribing !== undefined && installedSubscriptionEncoder && !responded) {\n\t\t\t\tstate.serviceStateEncoders.delete(subscribing.subscriptionId);\n\t\t\t}\n\t\t\tif (responded) {\n\t\t\t\tthis.reportError(error);\n\t\t\t\tawait this.closeConnection(state.connection);\n\t\t\t\tthis.disconnect(state);\n\t\t\t} else {\n\t\t\t\tawait this.sendMessage(state, {\n\t\t\t\t\ttype: \"response\",\n\t\t\t\t\tid: envelope.id,\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: controller.signal.aborted\n\t\t\t\t\t\t? { code: \"cancelled\", message: \"RPC request cancelled\" }\n\t\t\t\t\t\t: this.toProtocolError(error),\n\t\t\t\t} satisfies ResponseEnvelope);\n\t\t\t}\n\t\t} finally {\n\t\t\tif (state.activeRequests.get(envelope.id) === active) state.activeRequests.delete(envelope.id);\n\t\t}\n\t}\n\n\tprivate transportClosed(connection: ConnectionState): void {\n\t\tif (!connection.disconnected && connection.stage !== \"closing\") {\n\t\t\ttry {\n\t\t\t\tconnection.decoder.end();\n\t\t\t} catch (error) {\n\t\t\t\tthis.reportError(error);\n\t\t\t}\n\t\t}\n\t\tthis.disconnect(connection);\n\t}\n\n\tprivate disconnect(connection: ConnectionState): void {\n\t\tif (connection.disconnected) return;\n\t\tconnection.disconnected = true;\n\t\tconnection.stage = \"closed\";\n\t\tclearTimeout(connection.handshakeTimeout);\n\t\tfor (const { controller } of connection.activeRequests.values()) {\n\t\t\tcontroller.abort(new Error(\"Client disconnected\"));\n\t\t}\n\t\tconnection.activeRequests.clear();\n\t\tconnection.serviceStateEncoders.clear();\n\t\tif (this.connections.delete(connection)) this.notifyConnectionCountChanged();\n\t\tconst serverServices = connection.serverServices;\n\t\tdelete connection.serverServices;\n\t\tvoid Promise.allSettled([\n\t\t\tthis.sessions.disconnect(connection, TODO_CONTEXT),\n\t\t\tserverServices?.release(TODO_CONTEXT),\n\t\t]).then((results) => {\n\t\t\tfor (const result of results) if (result.status === \"rejected\") this.reportError(result.reason);\n\t\t});\n\t}\n\n\tprivate async sendServiceUpdate(\n\t\tconnection: ConnectionState,\n\t\tsubscriptionId: string,\n\t\tupdate: ServiceProviderUpdate,\n\t): Promise<void> {\n\t\tconst stateEncoder = connection.serviceStateEncoders.get(subscriptionId);\n\t\tif (stateEncoder === undefined) return;\n\t\tawait this.sendMessage(connection, {\n\t\t\ttype: \"service_update\",\n\t\t\tsubscriptionId,\n\t\t\tupdate: stateEncoder.encodeUpdate(update) as unknown as JsonValue,\n\t\t});\n\t}\n\n\tprivate async sendMessage(connection: ConnectionState, message: ServerMessage): Promise<boolean> {\n\t\tif (connection.disconnected || connection.connection.closed) return false;\n\t\tlet frame: Uint8Array;\n\t\ttry {\n\t\t\tframe = encodeServerMessage(message, { maxFrameLength: this.maxFrameLength });\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t\tawait this.closeConnection(connection.connection);\n\t\t\tthis.disconnect(connection);\n\t\t\treturn false;\n\t\t}\n\t\ttry {\n\t\t\tawait connection.connection.send(frame);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t\tawait this.closeConnection(connection.connection);\n\t\t\tthis.disconnect(connection);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate async failProtocol(connection: ConnectionState, error: ProtocolError): Promise<void> {\n\t\tif (connection.disconnected || connection.stage === \"closing\" || connection.stage === \"closed\") return;\n\t\tconnection.stage = \"closing\";\n\t\tclearTimeout(connection.handshakeTimeout);\n\t\tconst message: ServerHelloError = { type: \"hello_error\", error };\n\t\tlet finalFrame: Uint8Array | undefined;\n\t\ttry {\n\t\t\tfinalFrame = encodeServerMessage(message, { maxFrameLength: this.maxFrameLength });\n\t\t} catch (encodeError) {\n\t\t\tthis.reportError(encodeError);\n\t\t}\n\t\tawait this.closeConnection(connection.connection, finalFrame);\n\t\tthis.disconnect(connection);\n\t}\n\n\tprivate async closeServerState(): Promise<void> {\n\t\tconst connections = [...this.connections];\n\t\tfor (const connection of connections) {\n\t\t\tconnection.stage = \"closing\";\n\t\t\tclearTimeout(connection.handshakeTimeout);\n\t\t}\n\t\tawait Promise.all(connections.map((connection) => this.closeConnection(connection.connection)));\n\t\tfor (const connection of connections) this.disconnect(connection);\n\t\tconst cleanup = await Promise.allSettled([this.sessions.close(BACKGROUND_CONTEXT)]);\n\t\tthis.connections.clear();\n\t\tconst errors = cleanup.flatMap((result) => (result.status === \"rejected\" ? [result.reason] : []));\n\t\tif (errors.length === 1) throw errors[0];\n\t\tif (errors.length > 1) throw new AggregateError(errors, \"Failed to close server Sessions\");\n\t}\n\n\tprivate async closeConnection(connection: ByteConnection, finalChunk?: Uint8Array): Promise<void> {\n\t\ttry {\n\t\t\tawait connection.close(finalChunk);\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t}\n\t}\n\n\tprivate toProtocolError(error: unknown): ProtocolError {\n\t\tif (error instanceof ServerError || error instanceof RemoteServiceError) {\n\t\t\treturn { code: error.code, message: error.message };\n\t\t}\n\t\tif (error instanceof ProtocolValidationError) {\n\t\t\treturn { code: \"invalid_request\", message: error.message };\n\t\t}\n\t\tthis.reportError(error);\n\t\treturn { code: \"internal_error\", message: INTERNAL_SERVER_ERROR_MESSAGE };\n\t}\n\n\tprivate notifyConnectionCountChanged(): void {\n\t\ttry {\n\t\t\tthis.onConnectionCountChanged?.(this.connections.size);\n\t\t} catch (error) {\n\t\t\tthis.reportError(error);\n\t\t}\n\t}\n\n\tprivate reportError(error: unknown): void {\n\t\ttry {\n\t\t\tthis.onError?.(error instanceof Error ? error : new Error(String(error)));\n\t\t} catch {\n\t\t\t// Error observers cannot affect server state.\n\t\t}\n\t}\n\n\tprivate settleClosed(error?: unknown): void {\n\t\tif (this.closedSettled) return;\n\t\tthis.closedSettled = true;\n\t\tif (error === undefined) this.resolveClosed();\n\t\telse this.rejectClosed(error);\n\t}\n}\n\nfunction sameTarget(left: RpcTarget, right: RpcTarget): boolean {\n\tif (left.serverId !== right.serverId) return false;\n\tif (!(\"sessionId\" in left) || !(\"sessionId\" in right)) {\n\t\treturn !(\"sessionId\" in left) && !(\"sessionId\" in right);\n\t}\n\treturn left.sessionId === right.sessionId && left.attachmentId === right.attachmentId;\n}\n\nfunction resolveOptions(options: ServerOptions): {\n\tmaxFrameLength: number;\n\thandshakeTimeoutMs: number;\n} {\n\tif (!Array.isArray(options.listeners)) throw new TypeError(\"Server listeners must be an array\");\n\tif (!isServerId(options.serverId)) {\n\t\tthrow new TypeError(\"serverId must be a canonical lowercase UUIDv4\");\n\t}\n\tconst maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\tif (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {\n\t\tthrow new TypeError(`Server maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t}\n\tconst handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\tif (\n\t\t!Number.isSafeInteger(handshakeTimeoutMs) ||\n\t\thandshakeTimeoutMs <= 0 ||\n\t\thandshakeTimeoutMs > MAX_TIMER_DELAY_MS\n\t) {\n\t\tthrow new TypeError(`Server handshakeTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);\n\t}\n\treturn { maxFrameLength, handshakeTimeoutMs };\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JsonValue, ServiceCall, ServiceProviderUpdate } from "@earendil-works/chord";
|
|
2
|
+
import { type Context, type SessionMetadata } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { RpcTarget, SessionTarget } from "@earendil-works/pi-protocol";
|
|
4
|
+
import type { ServerHost } from "./types.ts";
|
|
5
|
+
interface SessionRouterOptions<TMetadata extends SessionMetadata> {
|
|
6
|
+
host: ServerHost<TMetadata>;
|
|
7
|
+
serverId: string;
|
|
8
|
+
isClosing: () => boolean;
|
|
9
|
+
publishAttachment(client: object, attachment: SessionTarget | undefined, context: Context): Promise<void>;
|
|
10
|
+
reportError: (error: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare class SessionRouter<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private readonly hostedSessions;
|
|
15
|
+
private readonly openingSessions;
|
|
16
|
+
private readonly attachmentsByClient;
|
|
17
|
+
private readonly disconnectedClients;
|
|
18
|
+
private readonly clientOperations;
|
|
19
|
+
private closePromise?;
|
|
20
|
+
constructor(options: SessionRouterOptions<TMetadata>);
|
|
21
|
+
executeServiceCall(call: ServiceCall, target: RpcTarget, client: object, publish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => Promise<void>, context: Context): Promise<JsonValue | undefined>;
|
|
22
|
+
attachClient(client: object, sessionId: string, context: Context): Promise<void>;
|
|
23
|
+
detachClient(client: object, context: Context): Promise<void>;
|
|
24
|
+
removeSession(sessionId: string, context: Context): Promise<void>;
|
|
25
|
+
disconnect(client: object, context: Context): Promise<void>;
|
|
26
|
+
close(context: Context): Promise<void>;
|
|
27
|
+
private closeInternal;
|
|
28
|
+
private runForClient;
|
|
29
|
+
private attachClientNow;
|
|
30
|
+
private startServiceCall;
|
|
31
|
+
private trackOperation;
|
|
32
|
+
private requireAttachment;
|
|
33
|
+
private releaseAttachment;
|
|
34
|
+
private clearAttachment;
|
|
35
|
+
private acquire;
|
|
36
|
+
private open;
|
|
37
|
+
private invalidate;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=session-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-router.d.ts","sourceRoot":"","sources":["../src/session-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAsB,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACvG,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5E,OAAO,KAAK,EAAgD,UAAU,EAAE,MAAM,YAAY,CAAC;AAoB3F,UAAU,oBAAoB,CAAC,SAAS,SAAS,eAAe;IAC/D,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1G,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACtC;AAED,qBAAa,aAAa,CAAC,SAAS,SAAS,eAAe,GAAG,eAAe;IAC7E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoC;IACnE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6C;IAC7E,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAuC;IAC3E,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoC;IACrE,OAAO,CAAC,YAAY,CAAC,CAAgB;IAErC,YAAY,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC,EAEnD;IAEK,kBAAkB,CACvB,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,EACnG,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAKhC;IAED,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAG/E;IAED,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAK5D;IAEK,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBtE;IAEK,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhE;IAED,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrC;YAEa,aAAa;IAsC3B,OAAO,CAAC,YAAY;YAcN,eAAe;YAuCf,gBAAgB;IAiB9B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,iBAAiB;YAoBX,eAAe;YAQf,OAAO;YAcP,IAAI;IA0BlB,OAAO,CAAC,UAAU;CAUlB","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { JsonValue, ServiceCall, ServiceProviderUpdate } from \"@earendil-works/chord\";\nimport { BACKGROUND_CONTEXT, type Context, type SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport type { RpcTarget, SessionTarget } from \"@earendil-works/pi-protocol\";\nimport { ServerDrainingError, SessionNotAttachedError } from \"./errors.ts\";\nimport type { RoutedSessionAttachment, RoutedSessionHandle, ServerHost } from \"./types.ts\";\n\nclass SessionCleanupError extends AggregateError {}\n\ninterface ClientAttachment {\n\treadonly id: string;\n\treadonly client: object;\n\treadonly session: HostedSession;\n\treadonly operations: Set<Promise<unknown>>;\n\tacquiring?: Promise<RoutedSessionAttachment>;\n\tlease?: RoutedSessionAttachment;\n\treleasing?: Promise<void>;\n}\n\ninterface HostedSession {\n\treadonly id: string;\n\treadonly handle: RoutedSessionHandle;\n\treadonly attachments: Set<ClientAttachment>;\n}\n\ninterface SessionRouterOptions<TMetadata extends SessionMetadata> {\n\thost: ServerHost<TMetadata>;\n\tserverId: string;\n\tisClosing: () => boolean;\n\tpublishAttachment(client: object, attachment: SessionTarget | undefined, context: Context): Promise<void>;\n\treportError: (error: unknown) => void;\n}\n\nexport class SessionRouter<TMetadata extends SessionMetadata = SessionMetadata> {\n\tprivate readonly options: SessionRouterOptions<TMetadata>;\n\tprivate readonly hostedSessions = new Map<string, HostedSession>();\n\tprivate readonly openingSessions = new Map<string, Promise<HostedSession>>();\n\tprivate readonly attachmentsByClient = new Map<object, ClientAttachment>();\n\tprivate readonly disconnectedClients = new Set<object>();\n\tprivate readonly clientOperations = new Map<object, Promise<void>>();\n\tprivate closePromise?: Promise<void>;\n\n\tconstructor(options: SessionRouterOptions<TMetadata>) {\n\t\tthis.options = options;\n\t}\n\n\tasync executeServiceCall(\n\t\tcall: ServiceCall,\n\t\ttarget: RpcTarget,\n\t\tclient: object,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => Promise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined> {\n\t\tconst admitted = await this.runForClient(client, () =>\n\t\t\tthis.startServiceCall(client, target, call, publish, context),\n\t\t);\n\t\treturn admitted.result;\n\t}\n\n\tattachClient(client: object, sessionId: string, context: Context): Promise<void> {\n\t\tif (this.options.isClosing()) return Promise.reject(new ServerDrainingError());\n\t\treturn this.runForClient(client, () => this.attachClientNow(client, sessionId, context));\n\t}\n\n\tdetachClient(client: object, context: Context): Promise<void> {\n\t\treturn this.runForClient(client, async () => {\n\t\t\tconst attachment = this.attachmentsByClient.get(client);\n\t\t\tif (attachment) await this.releaseAttachment(attachment, context);\n\t\t});\n\t}\n\n\tasync removeSession(sessionId: string, context: Context): Promise<void> {\n\t\tif (this.options.isClosing()) throw new ServerDrainingError();\n\t\tconst hosted = this.hostedSessions.get(sessionId);\n\t\tif (hosted === undefined) return;\n\t\tconst errors: unknown[] = [];\n\t\tconst releases = await Promise.allSettled(\n\t\t\t[...hosted.attachments].map((attachment) => this.releaseAttachment(attachment, context)),\n\t\t);\n\t\tfor (const result of releases) if (result.status === \"rejected\") errors.push(result.reason);\n\t\ttry {\n\t\t\tawait hosted.handle.close(context);\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t\tif (this.hostedSessions.get(sessionId) === hosted) this.hostedSessions.delete(sessionId);\n\t\tif (errors.length === 1) throw errors[0];\n\t\tif (errors.length > 1) throw new AggregateError(errors, `Failed to close Session ${sessionId}`);\n\t}\n\n\tasync disconnect(client: object, context: Context): Promise<void> {\n\t\tthis.disconnectedClients.add(client);\n\t\ttry {\n\t\t\tawait this.runForClient(client, async () => {\n\t\t\t\tconst attachment = this.attachmentsByClient.get(client);\n\t\t\t\tif (attachment) await this.releaseAttachment(attachment, context, false);\n\t\t\t});\n\t\t} finally {\n\t\t\tthis.disconnectedClients.delete(client);\n\t\t}\n\t}\n\n\tclose(context: Context): Promise<void> {\n\t\tthis.closePromise ??= this.closeInternal(context);\n\t\treturn this.closePromise;\n\t}\n\n\tprivate async closeInternal(context: Context): Promise<void> {\n\t\tconst operationPromises = [...this.clientOperations.values()];\n\t\tconst openingPromises = [...this.openingSessions.values()];\n\t\tconst [operationResults, openingResults] = await Promise.all([\n\t\t\tPromise.allSettled(operationPromises),\n\t\t\tPromise.allSettled(openingPromises),\n\t\t]);\n\t\tconst closeErrors: unknown[] = [];\n\t\tfor (const result of [...operationResults, ...openingResults]) {\n\t\t\tif (result.status !== \"rejected\") continue;\n\t\t\tthis.options.reportError(result.reason);\n\t\t\tif (result.reason instanceof SessionCleanupError) closeErrors.push(result.reason);\n\t\t}\n\t\tconst attachmentResults = await Promise.allSettled(\n\t\t\t[...this.hostedSessions.values()].flatMap((session) =>\n\t\t\t\t[...session.attachments].map((attachment) => this.releaseAttachment(attachment, context)),\n\t\t\t),\n\t\t);\n\t\tfor (const result of attachmentResults) {\n\t\t\tif (result.status === \"rejected\") closeErrors.push(result.reason);\n\t\t}\n\t\tconst hosted = [...this.hostedSessions.values()];\n\t\tconst closeResults = await Promise.allSettled(hosted.map(({ handle }) => handle.close(context)));\n\t\tfor (let index = 0; index < closeResults.length; index++) {\n\t\t\tconst result = closeResults[index]!;\n\t\t\tconst session = hosted[index]!;\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\tif (this.hostedSessions.get(session.id) === session) this.hostedSessions.delete(session.id);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.options.reportError(result.reason);\n\t\t\tcloseErrors.push(result.reason);\n\t\t}\n\t\tthis.attachmentsByClient.clear();\n\t\tthis.clientOperations.clear();\n\t\tif (closeErrors.length > 0) throw new AggregateError(closeErrors, \"Failed to close routed Sessions\");\n\t}\n\n\tprivate runForClient<T>(client: object, operation: () => Promise<T>): Promise<T> {\n\t\tconst previous = this.clientOperations.get(client) ?? Promise.resolve();\n\t\tconst result = previous.catch(() => {}).then(operation);\n\t\tconst tail = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\tthis.clientOperations.set(client, tail);\n\t\tvoid tail.finally(() => {\n\t\t\tif (this.clientOperations.get(client) === tail) this.clientOperations.delete(client);\n\t\t});\n\t\treturn result;\n\t}\n\n\tprivate async attachClientNow(client: object, sessionId: string, context: Context): Promise<void> {\n\t\tif (this.options.isClosing() || this.disconnectedClients.has(client)) throw new ServerDrainingError();\n\t\tconst current = this.attachmentsByClient.get(client);\n\t\tif (current?.session.id === sessionId) return;\n\t\tconst hosted = await this.acquire(sessionId, context);\n\t\tif (this.options.isClosing() || this.disconnectedClients.has(client)) throw new ServerDrainingError();\n\t\tif (current) await this.releaseAttachment(current, context, false);\n\t\tconst attachment: ClientAttachment = {\n\t\t\tid: randomUUID(),\n\t\t\tclient,\n\t\t\tsession: hosted,\n\t\t\toperations: new Set(),\n\t\t};\n\t\thosted.attachments.add(attachment);\n\t\ttry {\n\t\t\tconst acquiring = Promise.resolve(hosted.handle.attachClient(context));\n\t\t\tattachment.acquiring = acquiring;\n\t\t\tattachment.lease = await acquiring;\n\t\t} catch (error) {\n\t\t\thosted.attachments.delete(attachment);\n\t\t\tthrow error;\n\t\t}\n\t\tif (\n\t\t\tthis.hostedSessions.get(hosted.id) !== hosted ||\n\t\t\t!hosted.attachments.has(attachment) ||\n\t\t\tthis.disconnectedClients.has(client) ||\n\t\t\tthis.options.isClosing()\n\t\t) {\n\t\t\tawait this.releaseAttachment(attachment, context);\n\t\t\tthrow new ServerDrainingError();\n\t\t}\n\t\tthis.attachmentsByClient.set(client, attachment);\n\t\tawait this.options.publishAttachment(\n\t\t\tclient,\n\t\t\t{ serverId: this.options.serverId, sessionId, attachmentId: attachment.id },\n\t\t\tcontext,\n\t\t);\n\t}\n\n\tprivate async startServiceCall(\n\t\tclient: object,\n\t\ttarget: RpcTarget,\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => Promise<void>,\n\t\tcontext: Context,\n\t): Promise<{ result: Promise<JsonValue | undefined> }> {\n\t\tconst attachment = this.requireAttachment(client, target);\n\t\tconst result = attachment.lease!.invokeService(\n\t\t\tcall,\n\t\t\t(subscriptionId, update, updateContext) => publish(subscriptionId, update, updateContext),\n\t\t\tcontext,\n\t\t);\n\t\tthis.trackOperation(attachment, result);\n\t\treturn { result };\n\t}\n\n\tprivate trackOperation(attachment: ClientAttachment, result: Promise<unknown>): void {\n\t\tattachment.operations.add(result);\n\t\tconst remove = (): void => {\n\t\t\tattachment.operations.delete(result);\n\t\t};\n\t\tvoid result.then(remove, remove);\n\t}\n\n\tprivate requireAttachment(client: object, target: RpcTarget): ClientAttachment {\n\t\tif (this.options.isClosing() || this.disconnectedClients.has(client)) throw new ServerDrainingError();\n\t\tif (!(\"sessionId\" in target)) throw new SessionNotAttachedError();\n\t\tconst attachment = this.attachmentsByClient.get(client);\n\t\tif (!attachment || attachment.session.id !== target.sessionId || attachment.id !== target.attachmentId) {\n\t\t\tthrow new SessionNotAttachedError();\n\t\t}\n\t\treturn attachment;\n\t}\n\n\tprivate releaseAttachment(attachment: ClientAttachment, context: Context, publish = true): Promise<void> {\n\t\tattachment.releasing ??= (async () => {\n\t\t\tconst errors: unknown[] = [];\n\t\t\ttry {\n\t\t\t\tawait Promise.allSettled(attachment.operations);\n\t\t\t\ttry {\n\t\t\t\t\tconst lease = attachment.lease ?? (await attachment.acquiring);\n\t\t\t\t\tif (lease) await lease.release(context);\n\t\t\t\t} catch (error) {\n\t\t\t\t\terrors.push(error);\n\t\t\t\t}\n\t\t\t\tif (errors.length === 1) throw errors[0];\n\t\t\t\tif (errors.length > 1) throw new AggregateError(errors, \"Failed to release Session attachment\");\n\t\t\t} finally {\n\t\t\t\tawait this.clearAttachment(attachment, context, publish);\n\t\t\t}\n\t\t})();\n\t\treturn attachment.releasing;\n\t}\n\n\tprivate async clearAttachment(attachment: ClientAttachment, context: Context, publish: boolean): Promise<void> {\n\t\tattachment.session.attachments.delete(attachment);\n\t\tif (this.attachmentsByClient.get(attachment.client) === attachment) {\n\t\t\tthis.attachmentsByClient.delete(attachment.client);\n\t\t\tif (publish) await this.options.publishAttachment(attachment.client, undefined, context);\n\t\t}\n\t}\n\n\tprivate async acquire(sessionId: string, context: Context): Promise<HostedSession> {\n\t\tconst existing = this.hostedSessions.get(sessionId);\n\t\tif (existing) return existing;\n\t\tconst opening = this.openingSessions.get(sessionId);\n\t\tif (opening) return opening;\n\t\tconst pending = this.open(sessionId, context);\n\t\tthis.openingSessions.set(sessionId, pending);\n\t\ttry {\n\t\t\treturn await pending;\n\t\t} finally {\n\t\t\tif (this.openingSessions.get(sessionId) === pending) this.openingSessions.delete(sessionId);\n\t\t}\n\t}\n\n\tprivate async open(sessionId: string, context: Context): Promise<HostedSession> {\n\t\tconst metadata = await this.options.host.resolveSession(sessionId, context);\n\t\tconst handle = await this.options.host.openSession(metadata, context);\n\t\tif (this.options.isClosing()) {\n\t\t\ttry {\n\t\t\t\tawait handle.close(context);\n\t\t\t} catch (error) {\n\t\t\t\tthis.options.reportError(error);\n\t\t\t\tthrow new SessionCleanupError(\n\t\t\t\t\t[new ServerDrainingError(), error],\n\t\t\t\t\t\"Failed to close routed Session acquired while draining\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new ServerDrainingError();\n\t\t}\n\t\tconst hosted: HostedSession = { id: metadata.id, handle, attachments: new Set() };\n\t\tthis.hostedSessions.set(hosted.id, hosted);\n\t\tif (handle.terminated) {\n\t\t\tvoid handle.terminated.then(\n\t\t\t\t(error) => this.invalidate(hosted, error),\n\t\t\t\t(error: unknown) => this.invalidate(hosted, error instanceof Error ? error : new Error(String(error))),\n\t\t\t);\n\t\t}\n\t\treturn hosted;\n\t}\n\n\tprivate invalidate(hosted: HostedSession, error: Error | undefined): void {\n\t\tif (this.hostedSessions.get(hosted.id) !== hosted) return;\n\t\tthis.hostedSessions.delete(hosted.id);\n\t\tfor (const attachment of hosted.attachments) {\n\t\t\tvoid this.releaseAttachment(attachment, BACKGROUND_CONTEXT).catch((releaseError: unknown) =>\n\t\t\t\tthis.options.reportError(releaseError),\n\t\t\t);\n\t\t}\n\t\tif (error) this.options.reportError(error);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import { ServerDrainingError, SessionNotAttachedError } from "./errors.js";
|
|
4
|
+
class SessionCleanupError extends AggregateError {
|
|
5
|
+
}
|
|
6
|
+
export class SessionRouter {
|
|
7
|
+
options;
|
|
8
|
+
hostedSessions = new Map();
|
|
9
|
+
openingSessions = new Map();
|
|
10
|
+
attachmentsByClient = new Map();
|
|
11
|
+
disconnectedClients = new Set();
|
|
12
|
+
clientOperations = new Map();
|
|
13
|
+
closePromise;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
async executeServiceCall(call, target, client, publish, context) {
|
|
18
|
+
const admitted = await this.runForClient(client, () => this.startServiceCall(client, target, call, publish, context));
|
|
19
|
+
return admitted.result;
|
|
20
|
+
}
|
|
21
|
+
attachClient(client, sessionId, context) {
|
|
22
|
+
if (this.options.isClosing())
|
|
23
|
+
return Promise.reject(new ServerDrainingError());
|
|
24
|
+
return this.runForClient(client, () => this.attachClientNow(client, sessionId, context));
|
|
25
|
+
}
|
|
26
|
+
detachClient(client, context) {
|
|
27
|
+
return this.runForClient(client, async () => {
|
|
28
|
+
const attachment = this.attachmentsByClient.get(client);
|
|
29
|
+
if (attachment)
|
|
30
|
+
await this.releaseAttachment(attachment, context);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async removeSession(sessionId, context) {
|
|
34
|
+
if (this.options.isClosing())
|
|
35
|
+
throw new ServerDrainingError();
|
|
36
|
+
const hosted = this.hostedSessions.get(sessionId);
|
|
37
|
+
if (hosted === undefined)
|
|
38
|
+
return;
|
|
39
|
+
const errors = [];
|
|
40
|
+
const releases = await Promise.allSettled([...hosted.attachments].map((attachment) => this.releaseAttachment(attachment, context)));
|
|
41
|
+
for (const result of releases)
|
|
42
|
+
if (result.status === "rejected")
|
|
43
|
+
errors.push(result.reason);
|
|
44
|
+
try {
|
|
45
|
+
await hosted.handle.close(context);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
errors.push(error);
|
|
49
|
+
}
|
|
50
|
+
if (this.hostedSessions.get(sessionId) === hosted)
|
|
51
|
+
this.hostedSessions.delete(sessionId);
|
|
52
|
+
if (errors.length === 1)
|
|
53
|
+
throw errors[0];
|
|
54
|
+
if (errors.length > 1)
|
|
55
|
+
throw new AggregateError(errors, `Failed to close Session ${sessionId}`);
|
|
56
|
+
}
|
|
57
|
+
async disconnect(client, context) {
|
|
58
|
+
this.disconnectedClients.add(client);
|
|
59
|
+
try {
|
|
60
|
+
await this.runForClient(client, async () => {
|
|
61
|
+
const attachment = this.attachmentsByClient.get(client);
|
|
62
|
+
if (attachment)
|
|
63
|
+
await this.releaseAttachment(attachment, context, false);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
this.disconnectedClients.delete(client);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
close(context) {
|
|
71
|
+
this.closePromise ??= this.closeInternal(context);
|
|
72
|
+
return this.closePromise;
|
|
73
|
+
}
|
|
74
|
+
async closeInternal(context) {
|
|
75
|
+
const operationPromises = [...this.clientOperations.values()];
|
|
76
|
+
const openingPromises = [...this.openingSessions.values()];
|
|
77
|
+
const [operationResults, openingResults] = await Promise.all([
|
|
78
|
+
Promise.allSettled(operationPromises),
|
|
79
|
+
Promise.allSettled(openingPromises),
|
|
80
|
+
]);
|
|
81
|
+
const closeErrors = [];
|
|
82
|
+
for (const result of [...operationResults, ...openingResults]) {
|
|
83
|
+
if (result.status !== "rejected")
|
|
84
|
+
continue;
|
|
85
|
+
this.options.reportError(result.reason);
|
|
86
|
+
if (result.reason instanceof SessionCleanupError)
|
|
87
|
+
closeErrors.push(result.reason);
|
|
88
|
+
}
|
|
89
|
+
const attachmentResults = await Promise.allSettled([...this.hostedSessions.values()].flatMap((session) => [...session.attachments].map((attachment) => this.releaseAttachment(attachment, context))));
|
|
90
|
+
for (const result of attachmentResults) {
|
|
91
|
+
if (result.status === "rejected")
|
|
92
|
+
closeErrors.push(result.reason);
|
|
93
|
+
}
|
|
94
|
+
const hosted = [...this.hostedSessions.values()];
|
|
95
|
+
const closeResults = await Promise.allSettled(hosted.map(({ handle }) => handle.close(context)));
|
|
96
|
+
for (let index = 0; index < closeResults.length; index++) {
|
|
97
|
+
const result = closeResults[index];
|
|
98
|
+
const session = hosted[index];
|
|
99
|
+
if (result.status === "fulfilled") {
|
|
100
|
+
if (this.hostedSessions.get(session.id) === session)
|
|
101
|
+
this.hostedSessions.delete(session.id);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
this.options.reportError(result.reason);
|
|
105
|
+
closeErrors.push(result.reason);
|
|
106
|
+
}
|
|
107
|
+
this.attachmentsByClient.clear();
|
|
108
|
+
this.clientOperations.clear();
|
|
109
|
+
if (closeErrors.length > 0)
|
|
110
|
+
throw new AggregateError(closeErrors, "Failed to close routed Sessions");
|
|
111
|
+
}
|
|
112
|
+
runForClient(client, operation) {
|
|
113
|
+
const previous = this.clientOperations.get(client) ?? Promise.resolve();
|
|
114
|
+
const result = previous.catch(() => { }).then(operation);
|
|
115
|
+
const tail = result.then(() => undefined, () => undefined);
|
|
116
|
+
this.clientOperations.set(client, tail);
|
|
117
|
+
void tail.finally(() => {
|
|
118
|
+
if (this.clientOperations.get(client) === tail)
|
|
119
|
+
this.clientOperations.delete(client);
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
async attachClientNow(client, sessionId, context) {
|
|
124
|
+
if (this.options.isClosing() || this.disconnectedClients.has(client))
|
|
125
|
+
throw new ServerDrainingError();
|
|
126
|
+
const current = this.attachmentsByClient.get(client);
|
|
127
|
+
if (current?.session.id === sessionId)
|
|
128
|
+
return;
|
|
129
|
+
const hosted = await this.acquire(sessionId, context);
|
|
130
|
+
if (this.options.isClosing() || this.disconnectedClients.has(client))
|
|
131
|
+
throw new ServerDrainingError();
|
|
132
|
+
if (current)
|
|
133
|
+
await this.releaseAttachment(current, context, false);
|
|
134
|
+
const attachment = {
|
|
135
|
+
id: randomUUID(),
|
|
136
|
+
client,
|
|
137
|
+
session: hosted,
|
|
138
|
+
operations: new Set(),
|
|
139
|
+
};
|
|
140
|
+
hosted.attachments.add(attachment);
|
|
141
|
+
try {
|
|
142
|
+
const acquiring = Promise.resolve(hosted.handle.attachClient(context));
|
|
143
|
+
attachment.acquiring = acquiring;
|
|
144
|
+
attachment.lease = await acquiring;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
hosted.attachments.delete(attachment);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
if (this.hostedSessions.get(hosted.id) !== hosted ||
|
|
151
|
+
!hosted.attachments.has(attachment) ||
|
|
152
|
+
this.disconnectedClients.has(client) ||
|
|
153
|
+
this.options.isClosing()) {
|
|
154
|
+
await this.releaseAttachment(attachment, context);
|
|
155
|
+
throw new ServerDrainingError();
|
|
156
|
+
}
|
|
157
|
+
this.attachmentsByClient.set(client, attachment);
|
|
158
|
+
await this.options.publishAttachment(client, { serverId: this.options.serverId, sessionId, attachmentId: attachment.id }, context);
|
|
159
|
+
}
|
|
160
|
+
async startServiceCall(client, target, call, publish, context) {
|
|
161
|
+
const attachment = this.requireAttachment(client, target);
|
|
162
|
+
const result = attachment.lease.invokeService(call, (subscriptionId, update, updateContext) => publish(subscriptionId, update, updateContext), context);
|
|
163
|
+
this.trackOperation(attachment, result);
|
|
164
|
+
return { result };
|
|
165
|
+
}
|
|
166
|
+
trackOperation(attachment, result) {
|
|
167
|
+
attachment.operations.add(result);
|
|
168
|
+
const remove = () => {
|
|
169
|
+
attachment.operations.delete(result);
|
|
170
|
+
};
|
|
171
|
+
void result.then(remove, remove);
|
|
172
|
+
}
|
|
173
|
+
requireAttachment(client, target) {
|
|
174
|
+
if (this.options.isClosing() || this.disconnectedClients.has(client))
|
|
175
|
+
throw new ServerDrainingError();
|
|
176
|
+
if (!("sessionId" in target))
|
|
177
|
+
throw new SessionNotAttachedError();
|
|
178
|
+
const attachment = this.attachmentsByClient.get(client);
|
|
179
|
+
if (!attachment || attachment.session.id !== target.sessionId || attachment.id !== target.attachmentId) {
|
|
180
|
+
throw new SessionNotAttachedError();
|
|
181
|
+
}
|
|
182
|
+
return attachment;
|
|
183
|
+
}
|
|
184
|
+
releaseAttachment(attachment, context, publish = true) {
|
|
185
|
+
attachment.releasing ??= (async () => {
|
|
186
|
+
const errors = [];
|
|
187
|
+
try {
|
|
188
|
+
await Promise.allSettled(attachment.operations);
|
|
189
|
+
try {
|
|
190
|
+
const lease = attachment.lease ?? (await attachment.acquiring);
|
|
191
|
+
if (lease)
|
|
192
|
+
await lease.release(context);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
errors.push(error);
|
|
196
|
+
}
|
|
197
|
+
if (errors.length === 1)
|
|
198
|
+
throw errors[0];
|
|
199
|
+
if (errors.length > 1)
|
|
200
|
+
throw new AggregateError(errors, "Failed to release Session attachment");
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
await this.clearAttachment(attachment, context, publish);
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
return attachment.releasing;
|
|
207
|
+
}
|
|
208
|
+
async clearAttachment(attachment, context, publish) {
|
|
209
|
+
attachment.session.attachments.delete(attachment);
|
|
210
|
+
if (this.attachmentsByClient.get(attachment.client) === attachment) {
|
|
211
|
+
this.attachmentsByClient.delete(attachment.client);
|
|
212
|
+
if (publish)
|
|
213
|
+
await this.options.publishAttachment(attachment.client, undefined, context);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async acquire(sessionId, context) {
|
|
217
|
+
const existing = this.hostedSessions.get(sessionId);
|
|
218
|
+
if (existing)
|
|
219
|
+
return existing;
|
|
220
|
+
const opening = this.openingSessions.get(sessionId);
|
|
221
|
+
if (opening)
|
|
222
|
+
return opening;
|
|
223
|
+
const pending = this.open(sessionId, context);
|
|
224
|
+
this.openingSessions.set(sessionId, pending);
|
|
225
|
+
try {
|
|
226
|
+
return await pending;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
if (this.openingSessions.get(sessionId) === pending)
|
|
230
|
+
this.openingSessions.delete(sessionId);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async open(sessionId, context) {
|
|
234
|
+
const metadata = await this.options.host.resolveSession(sessionId, context);
|
|
235
|
+
const handle = await this.options.host.openSession(metadata, context);
|
|
236
|
+
if (this.options.isClosing()) {
|
|
237
|
+
try {
|
|
238
|
+
await handle.close(context);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
this.options.reportError(error);
|
|
242
|
+
throw new SessionCleanupError([new ServerDrainingError(), error], "Failed to close routed Session acquired while draining");
|
|
243
|
+
}
|
|
244
|
+
throw new ServerDrainingError();
|
|
245
|
+
}
|
|
246
|
+
const hosted = { id: metadata.id, handle, attachments: new Set() };
|
|
247
|
+
this.hostedSessions.set(hosted.id, hosted);
|
|
248
|
+
if (handle.terminated) {
|
|
249
|
+
void handle.terminated.then((error) => this.invalidate(hosted, error), (error) => this.invalidate(hosted, error instanceof Error ? error : new Error(String(error))));
|
|
250
|
+
}
|
|
251
|
+
return hosted;
|
|
252
|
+
}
|
|
253
|
+
invalidate(hosted, error) {
|
|
254
|
+
if (this.hostedSessions.get(hosted.id) !== hosted)
|
|
255
|
+
return;
|
|
256
|
+
this.hostedSessions.delete(hosted.id);
|
|
257
|
+
for (const attachment of hosted.attachments) {
|
|
258
|
+
void this.releaseAttachment(attachment, BACKGROUND_CONTEXT).catch((releaseError) => this.options.reportError(releaseError));
|
|
259
|
+
}
|
|
260
|
+
if (error)
|
|
261
|
+
this.options.reportError(error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=session-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-router.js","sourceRoot":"","sources":["../src/session-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { JsonValue, ServiceCall, ServiceProviderUpdate } from \"@earendil-works/chord\";\nimport { BACKGROUND_CONTEXT, type Context, type SessionMetadata } from \"@earendil-works/pi-agent-core\";\nimport type { RpcTarget, SessionTarget } from \"@earendil-works/pi-protocol\";\nimport { ServerDrainingError, SessionNotAttachedError } from \"./errors.ts\";\nimport type { RoutedSessionAttachment, RoutedSessionHandle, ServerHost } from \"./types.ts\";\n\nclass SessionCleanupError extends AggregateError {}\n\ninterface ClientAttachment {\n\treadonly id: string;\n\treadonly client: object;\n\treadonly session: HostedSession;\n\treadonly operations: Set<Promise<unknown>>;\n\tacquiring?: Promise<RoutedSessionAttachment>;\n\tlease?: RoutedSessionAttachment;\n\treleasing?: Promise<void>;\n}\n\ninterface HostedSession {\n\treadonly id: string;\n\treadonly handle: RoutedSessionHandle;\n\treadonly attachments: Set<ClientAttachment>;\n}\n\ninterface SessionRouterOptions<TMetadata extends SessionMetadata> {\n\thost: ServerHost<TMetadata>;\n\tserverId: string;\n\tisClosing: () => boolean;\n\tpublishAttachment(client: object, attachment: SessionTarget | undefined, context: Context): Promise<void>;\n\treportError: (error: unknown) => void;\n}\n\nexport class SessionRouter<TMetadata extends SessionMetadata = SessionMetadata> {\n\tprivate readonly options: SessionRouterOptions<TMetadata>;\n\tprivate readonly hostedSessions = new Map<string, HostedSession>();\n\tprivate readonly openingSessions = new Map<string, Promise<HostedSession>>();\n\tprivate readonly attachmentsByClient = new Map<object, ClientAttachment>();\n\tprivate readonly disconnectedClients = new Set<object>();\n\tprivate readonly clientOperations = new Map<object, Promise<void>>();\n\tprivate closePromise?: Promise<void>;\n\n\tconstructor(options: SessionRouterOptions<TMetadata>) {\n\t\tthis.options = options;\n\t}\n\n\tasync executeServiceCall(\n\t\tcall: ServiceCall,\n\t\ttarget: RpcTarget,\n\t\tclient: object,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => Promise<void>,\n\t\tcontext: Context,\n\t): Promise<JsonValue | undefined> {\n\t\tconst admitted = await this.runForClient(client, () =>\n\t\t\tthis.startServiceCall(client, target, call, publish, context),\n\t\t);\n\t\treturn admitted.result;\n\t}\n\n\tattachClient(client: object, sessionId: string, context: Context): Promise<void> {\n\t\tif (this.options.isClosing()) return Promise.reject(new ServerDrainingError());\n\t\treturn this.runForClient(client, () => this.attachClientNow(client, sessionId, context));\n\t}\n\n\tdetachClient(client: object, context: Context): Promise<void> {\n\t\treturn this.runForClient(client, async () => {\n\t\t\tconst attachment = this.attachmentsByClient.get(client);\n\t\t\tif (attachment) await this.releaseAttachment(attachment, context);\n\t\t});\n\t}\n\n\tasync removeSession(sessionId: string, context: Context): Promise<void> {\n\t\tif (this.options.isClosing()) throw new ServerDrainingError();\n\t\tconst hosted = this.hostedSessions.get(sessionId);\n\t\tif (hosted === undefined) return;\n\t\tconst errors: unknown[] = [];\n\t\tconst releases = await Promise.allSettled(\n\t\t\t[...hosted.attachments].map((attachment) => this.releaseAttachment(attachment, context)),\n\t\t);\n\t\tfor (const result of releases) if (result.status === \"rejected\") errors.push(result.reason);\n\t\ttry {\n\t\t\tawait hosted.handle.close(context);\n\t\t} catch (error) {\n\t\t\terrors.push(error);\n\t\t}\n\t\tif (this.hostedSessions.get(sessionId) === hosted) this.hostedSessions.delete(sessionId);\n\t\tif (errors.length === 1) throw errors[0];\n\t\tif (errors.length > 1) throw new AggregateError(errors, `Failed to close Session ${sessionId}`);\n\t}\n\n\tasync disconnect(client: object, context: Context): Promise<void> {\n\t\tthis.disconnectedClients.add(client);\n\t\ttry {\n\t\t\tawait this.runForClient(client, async () => {\n\t\t\t\tconst attachment = this.attachmentsByClient.get(client);\n\t\t\t\tif (attachment) await this.releaseAttachment(attachment, context, false);\n\t\t\t});\n\t\t} finally {\n\t\t\tthis.disconnectedClients.delete(client);\n\t\t}\n\t}\n\n\tclose(context: Context): Promise<void> {\n\t\tthis.closePromise ??= this.closeInternal(context);\n\t\treturn this.closePromise;\n\t}\n\n\tprivate async closeInternal(context: Context): Promise<void> {\n\t\tconst operationPromises = [...this.clientOperations.values()];\n\t\tconst openingPromises = [...this.openingSessions.values()];\n\t\tconst [operationResults, openingResults] = await Promise.all([\n\t\t\tPromise.allSettled(operationPromises),\n\t\t\tPromise.allSettled(openingPromises),\n\t\t]);\n\t\tconst closeErrors: unknown[] = [];\n\t\tfor (const result of [...operationResults, ...openingResults]) {\n\t\t\tif (result.status !== \"rejected\") continue;\n\t\t\tthis.options.reportError(result.reason);\n\t\t\tif (result.reason instanceof SessionCleanupError) closeErrors.push(result.reason);\n\t\t}\n\t\tconst attachmentResults = await Promise.allSettled(\n\t\t\t[...this.hostedSessions.values()].flatMap((session) =>\n\t\t\t\t[...session.attachments].map((attachment) => this.releaseAttachment(attachment, context)),\n\t\t\t),\n\t\t);\n\t\tfor (const result of attachmentResults) {\n\t\t\tif (result.status === \"rejected\") closeErrors.push(result.reason);\n\t\t}\n\t\tconst hosted = [...this.hostedSessions.values()];\n\t\tconst closeResults = await Promise.allSettled(hosted.map(({ handle }) => handle.close(context)));\n\t\tfor (let index = 0; index < closeResults.length; index++) {\n\t\t\tconst result = closeResults[index]!;\n\t\t\tconst session = hosted[index]!;\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\tif (this.hostedSessions.get(session.id) === session) this.hostedSessions.delete(session.id);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.options.reportError(result.reason);\n\t\t\tcloseErrors.push(result.reason);\n\t\t}\n\t\tthis.attachmentsByClient.clear();\n\t\tthis.clientOperations.clear();\n\t\tif (closeErrors.length > 0) throw new AggregateError(closeErrors, \"Failed to close routed Sessions\");\n\t}\n\n\tprivate runForClient<T>(client: object, operation: () => Promise<T>): Promise<T> {\n\t\tconst previous = this.clientOperations.get(client) ?? Promise.resolve();\n\t\tconst result = previous.catch(() => {}).then(operation);\n\t\tconst tail = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\tthis.clientOperations.set(client, tail);\n\t\tvoid tail.finally(() => {\n\t\t\tif (this.clientOperations.get(client) === tail) this.clientOperations.delete(client);\n\t\t});\n\t\treturn result;\n\t}\n\n\tprivate async attachClientNow(client: object, sessionId: string, context: Context): Promise<void> {\n\t\tif (this.options.isClosing() || this.disconnectedClients.has(client)) throw new ServerDrainingError();\n\t\tconst current = this.attachmentsByClient.get(client);\n\t\tif (current?.session.id === sessionId) return;\n\t\tconst hosted = await this.acquire(sessionId, context);\n\t\tif (this.options.isClosing() || this.disconnectedClients.has(client)) throw new ServerDrainingError();\n\t\tif (current) await this.releaseAttachment(current, context, false);\n\t\tconst attachment: ClientAttachment = {\n\t\t\tid: randomUUID(),\n\t\t\tclient,\n\t\t\tsession: hosted,\n\t\t\toperations: new Set(),\n\t\t};\n\t\thosted.attachments.add(attachment);\n\t\ttry {\n\t\t\tconst acquiring = Promise.resolve(hosted.handle.attachClient(context));\n\t\t\tattachment.acquiring = acquiring;\n\t\t\tattachment.lease = await acquiring;\n\t\t} catch (error) {\n\t\t\thosted.attachments.delete(attachment);\n\t\t\tthrow error;\n\t\t}\n\t\tif (\n\t\t\tthis.hostedSessions.get(hosted.id) !== hosted ||\n\t\t\t!hosted.attachments.has(attachment) ||\n\t\t\tthis.disconnectedClients.has(client) ||\n\t\t\tthis.options.isClosing()\n\t\t) {\n\t\t\tawait this.releaseAttachment(attachment, context);\n\t\t\tthrow new ServerDrainingError();\n\t\t}\n\t\tthis.attachmentsByClient.set(client, attachment);\n\t\tawait this.options.publishAttachment(\n\t\t\tclient,\n\t\t\t{ serverId: this.options.serverId, sessionId, attachmentId: attachment.id },\n\t\t\tcontext,\n\t\t);\n\t}\n\n\tprivate async startServiceCall(\n\t\tclient: object,\n\t\ttarget: RpcTarget,\n\t\tcall: ServiceCall,\n\t\tpublish: (subscriptionId: string, update: ServiceProviderUpdate, context: Context) => Promise<void>,\n\t\tcontext: Context,\n\t): Promise<{ result: Promise<JsonValue | undefined> }> {\n\t\tconst attachment = this.requireAttachment(client, target);\n\t\tconst result = attachment.lease!.invokeService(\n\t\t\tcall,\n\t\t\t(subscriptionId, update, updateContext) => publish(subscriptionId, update, updateContext),\n\t\t\tcontext,\n\t\t);\n\t\tthis.trackOperation(attachment, result);\n\t\treturn { result };\n\t}\n\n\tprivate trackOperation(attachment: ClientAttachment, result: Promise<unknown>): void {\n\t\tattachment.operations.add(result);\n\t\tconst remove = (): void => {\n\t\t\tattachment.operations.delete(result);\n\t\t};\n\t\tvoid result.then(remove, remove);\n\t}\n\n\tprivate requireAttachment(client: object, target: RpcTarget): ClientAttachment {\n\t\tif (this.options.isClosing() || this.disconnectedClients.has(client)) throw new ServerDrainingError();\n\t\tif (!(\"sessionId\" in target)) throw new SessionNotAttachedError();\n\t\tconst attachment = this.attachmentsByClient.get(client);\n\t\tif (!attachment || attachment.session.id !== target.sessionId || attachment.id !== target.attachmentId) {\n\t\t\tthrow new SessionNotAttachedError();\n\t\t}\n\t\treturn attachment;\n\t}\n\n\tprivate releaseAttachment(attachment: ClientAttachment, context: Context, publish = true): Promise<void> {\n\t\tattachment.releasing ??= (async () => {\n\t\t\tconst errors: unknown[] = [];\n\t\t\ttry {\n\t\t\t\tawait Promise.allSettled(attachment.operations);\n\t\t\t\ttry {\n\t\t\t\t\tconst lease = attachment.lease ?? (await attachment.acquiring);\n\t\t\t\t\tif (lease) await lease.release(context);\n\t\t\t\t} catch (error) {\n\t\t\t\t\terrors.push(error);\n\t\t\t\t}\n\t\t\t\tif (errors.length === 1) throw errors[0];\n\t\t\t\tif (errors.length > 1) throw new AggregateError(errors, \"Failed to release Session attachment\");\n\t\t\t} finally {\n\t\t\t\tawait this.clearAttachment(attachment, context, publish);\n\t\t\t}\n\t\t})();\n\t\treturn attachment.releasing;\n\t}\n\n\tprivate async clearAttachment(attachment: ClientAttachment, context: Context, publish: boolean): Promise<void> {\n\t\tattachment.session.attachments.delete(attachment);\n\t\tif (this.attachmentsByClient.get(attachment.client) === attachment) {\n\t\t\tthis.attachmentsByClient.delete(attachment.client);\n\t\t\tif (publish) await this.options.publishAttachment(attachment.client, undefined, context);\n\t\t}\n\t}\n\n\tprivate async acquire(sessionId: string, context: Context): Promise<HostedSession> {\n\t\tconst existing = this.hostedSessions.get(sessionId);\n\t\tif (existing) return existing;\n\t\tconst opening = this.openingSessions.get(sessionId);\n\t\tif (opening) return opening;\n\t\tconst pending = this.open(sessionId, context);\n\t\tthis.openingSessions.set(sessionId, pending);\n\t\ttry {\n\t\t\treturn await pending;\n\t\t} finally {\n\t\t\tif (this.openingSessions.get(sessionId) === pending) this.openingSessions.delete(sessionId);\n\t\t}\n\t}\n\n\tprivate async open(sessionId: string, context: Context): Promise<HostedSession> {\n\t\tconst metadata = await this.options.host.resolveSession(sessionId, context);\n\t\tconst handle = await this.options.host.openSession(metadata, context);\n\t\tif (this.options.isClosing()) {\n\t\t\ttry {\n\t\t\t\tawait handle.close(context);\n\t\t\t} catch (error) {\n\t\t\t\tthis.options.reportError(error);\n\t\t\t\tthrow new SessionCleanupError(\n\t\t\t\t\t[new ServerDrainingError(), error],\n\t\t\t\t\t\"Failed to close routed Session acquired while draining\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new ServerDrainingError();\n\t\t}\n\t\tconst hosted: HostedSession = { id: metadata.id, handle, attachments: new Set() };\n\t\tthis.hostedSessions.set(hosted.id, hosted);\n\t\tif (handle.terminated) {\n\t\t\tvoid handle.terminated.then(\n\t\t\t\t(error) => this.invalidate(hosted, error),\n\t\t\t\t(error: unknown) => this.invalidate(hosted, error instanceof Error ? error : new Error(String(error))),\n\t\t\t);\n\t\t}\n\t\treturn hosted;\n\t}\n\n\tprivate invalidate(hosted: HostedSession, error: Error | undefined): void {\n\t\tif (this.hostedSessions.get(hosted.id) !== hosted) return;\n\t\tthis.hostedSessions.delete(hosted.id);\n\t\tfor (const attachment of hosted.attachments) {\n\t\t\tvoid this.releaseAttachment(attachment, BACKGROUND_CONTEXT).catch((releaseError: unknown) =>\n\t\t\t\tthis.options.reportError(releaseError),\n\t\t\t);\n\t\t}\n\t\tif (error) this.options.reportError(error);\n\t}\n}\n"]}
|
package/dist/testing/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { ServiceCall } from "@earendil-works/chord";
|
|
2
|
+
import { type ClientMessage, type ResponseEnvelope, type RpcTarget, type ServerMessage } from "@earendil-works/pi-protocol";
|
|
2
3
|
export interface WireChannel {
|
|
3
4
|
send(chunk: Uint8Array): Promise<void>;
|
|
4
5
|
sendFragmented(chunk: Uint8Array, splitAt: number): Promise<void>;
|
|
@@ -11,11 +12,14 @@ export declare class ProtocolTestClient {
|
|
|
11
12
|
private readonly waiters;
|
|
12
13
|
private readonly closedDeferred;
|
|
13
14
|
private requestSequence;
|
|
15
|
+
private attachment;
|
|
14
16
|
private closedValue;
|
|
15
17
|
constructor(channel: WireChannel);
|
|
16
18
|
get closed(): boolean;
|
|
17
19
|
hello(version?: number): Promise<ServerMessage>;
|
|
18
|
-
|
|
20
|
+
requestService(target: RpcTarget, call: ServiceCall, id?: string): Promise<ResponseEnvelope>;
|
|
21
|
+
attach(serverId: string, sessionId: string): Promise<ResponseEnvelope>;
|
|
22
|
+
requestSessionService(serverId: string, sessionId: string, call: ServiceCall, id?: string): Promise<ResponseEnvelope>;
|
|
19
23
|
sendMessage(message: ClientMessage): Promise<void>;
|
|
20
24
|
sendBytes(chunk: Uint8Array): Promise<void>;
|
|
21
25
|
sendFragmentedMessage(message: ClientMessage, splitAt: number): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EACN,KAAK,aAAa,EAGlB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,aAAa,EAElB,MAAM,6BAA6B,CAAC;AASrC,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,qBAAa,kBAAkB;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,CAAM;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,UAAU,CAA0D;IAC5E,OAAO,CAAC,WAAW,CAAS;IAE5B,YAAY,OAAO,EAAE,WAAW,EAE/B;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,KAAK,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAIhE;IAEK,cAAc,CACnB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,WAAW,EACjB,EAAE,SAAsC,GACtC,OAAO,CAAC,gBAAgB,CAAC,CAM3B;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAKrE;IAED,qBAAqB,CACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EACjB,EAAE,CAAC,EAAE,MAAM,GACT,OAAO,CAAC,gBAAgB,CAAC,CAO3B;IAED,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjD;IAED,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1C;IAED,qBAAqB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;IAED,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAE3E;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAK9F;IAED,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5B;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAErB;IAED,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAsB/B;IAED,UAAU,IAAI,IAAI,CAKjB;IAED,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAGvB;CACD;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsBrF","sourcesContent":["import { once } from \"node:events\";\nimport { createConnection, type Socket } from \"node:net\";\nimport type { JsonValue, ServiceCall } from \"@earendil-works/chord\";\nimport {\n\ttype ClientMessage,\n\tencodeClientMessage,\n\tPROTOCOL_VERSION,\n\ttype ResponseEnvelope,\n\ttype RpcTarget,\n\ttype ServerMessage,\n\tServerMessageDecoder,\n} from \"@earendil-works/pi-protocol\";\nimport { Deferred } from \"./host.ts\";\n\ninterface MessageWaiter {\n\tpredicate: (message: ServerMessage) => boolean;\n\tresolve: (message: ServerMessage) => void;\n\treject: (error: Error) => void;\n}\n\nexport interface WireChannel {\n\tsend(chunk: Uint8Array): Promise<void>;\n\tsendFragmented(chunk: Uint8Array, splitAt: number): Promise<void>;\n\tclose(): Promise<void>;\n}\n\nexport class ProtocolTestClient {\n\treadonly messages: ServerMessage[] = [];\n\tprivate readonly channel: WireChannel;\n\tprivate readonly decoder = new ServerMessageDecoder();\n\tprivate readonly waiters = new Set<MessageWaiter>();\n\tprivate readonly closedDeferred = new Deferred<void>();\n\tprivate requestSequence = 0;\n\tprivate attachment: { sessionId: string; attachmentId: string } | undefined;\n\tprivate closedValue = false;\n\n\tconstructor(channel: WireChannel) {\n\t\tthis.channel = channel;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\thello(version: number = PROTOCOL_VERSION): Promise<ServerMessage> {\n\t\tconst response = this.next((message) => message.type === \"hello\" || message.type === \"hello_error\");\n\t\tvoid this.sendMessage({ type: \"hello\", version });\n\t\treturn response;\n\t}\n\n\tasync requestService(\n\t\ttarget: RpcTarget,\n\t\tcall: ServiceCall,\n\t\tid = `request-${++this.requestSequence}`,\n\t): Promise<ResponseEnvelope> {\n\t\tconst response = this.next(\n\t\t\t(message): message is ResponseEnvelope => message.type === \"response\" && message.id === id,\n\t\t);\n\t\tawait this.sendMessage({ type: \"request\", id, target, call: call as unknown as JsonValue });\n\t\treturn (await response) as ResponseEnvelope;\n\t}\n\n\tattach(serverId: string, sessionId: string): Promise<ResponseEnvelope> {\n\t\treturn this.requestService(\n\t\t\t{ serverId },\n\t\t\t{ serviceId: \"pi.session-management\", member: \"attach\", args: [sessionId] },\n\t\t);\n\t}\n\n\trequestSessionService(\n\t\tserverId: string,\n\t\tsessionId: string,\n\t\tcall: ServiceCall,\n\t\tid?: string,\n\t): Promise<ResponseEnvelope> {\n\t\tconst attachment = this.attachment;\n\t\tconst target: RpcTarget =\n\t\t\tattachment === undefined || attachment.sessionId !== sessionId\n\t\t\t\t? { serverId, sessionId, attachmentId: \"missing-attachment\" }\n\t\t\t\t: { serverId, ...attachment };\n\t\treturn this.requestService(target, call, id);\n\t}\n\n\tsendMessage(message: ClientMessage): Promise<void> {\n\t\treturn this.channel.send(encodeClientMessage(message));\n\t}\n\n\tsendBytes(chunk: Uint8Array): Promise<void> {\n\t\treturn this.channel.send(chunk);\n\t}\n\n\tsendFragmentedMessage(message: ClientMessage, splitAt: number): Promise<void> {\n\t\treturn this.channel.sendFragmented(encodeClientMessage(message), splitAt);\n\t}\n\n\tnext(predicate: (message: ServerMessage) => boolean): Promise<ServerMessage> {\n\t\treturn this.nextFrom(0, predicate);\n\t}\n\n\tnextFrom(index: number, predicate: (message: ServerMessage) => boolean): Promise<ServerMessage> {\n\t\tconst existing = this.messages.slice(index).find(predicate);\n\t\tif (existing) return Promise.resolve(existing);\n\t\tif (this.closedValue) return Promise.reject(new Error(\"Wire client is closed\"));\n\t\treturn new Promise((resolve, reject) => this.waiters.add({ predicate, resolve, reject }));\n\t}\n\n\twaitForClose(): Promise<void> {\n\t\treturn this.closedValue ? Promise.resolve() : this.closedDeferred.promise;\n\t}\n\n\tclose(): Promise<void> {\n\t\treturn this.channel.close();\n\t}\n\n\treceive(chunk: Uint8Array): void {\n\t\ttry {\n\t\t\tfor (const message of this.decoder.push(chunk)) {\n\t\t\t\tif (message.type === \"attachment\") {\n\t\t\t\t\tthis.attachment =\n\t\t\t\t\t\tmessage.attachment === null\n\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\tsessionId: message.attachment.sessionId,\n\t\t\t\t\t\t\t\t\tattachmentId: message.attachment.attachmentId,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tthis.messages.push(message);\n\t\t\t\tfor (const waiter of this.waiters) {\n\t\t\t\t\tif (!waiter.predicate(message)) continue;\n\t\t\t\t\tthis.waiters.delete(waiter);\n\t\t\t\t\twaiter.resolve(message);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.fail(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closedDeferred.resolve(undefined);\n\t\tthis.fail(new Error(\"Wire connection closed\"));\n\t}\n\n\tfail(error: Error): void {\n\t\tfor (const waiter of this.waiters) waiter.reject(error);\n\t\tthis.waiters.clear();\n\t}\n}\n\nexport async function connectUnixTestClient(path: string): Promise<ProtocolTestClient> {\n\tconst socket = createConnection(path);\n\tawait once(socket, \"connect\");\n\tconst client = new ProtocolTestClient({\n\t\tsend: (chunk) => writeSocket(socket, chunk),\n\t\tasync sendFragmented(chunk, splitAt) {\n\t\t\tawait writeSocket(socket, chunk.subarray(0, splitAt));\n\t\t\tawait writeSocket(socket, chunk.subarray(splitAt));\n\t\t},\n\t\tasync close() {\n\t\t\tif (socket.destroyed) return;\n\t\t\tconst closed = once(socket, \"close\");\n\t\t\tsocket.destroy();\n\t\t\tawait closed;\n\t\t},\n\t});\n\tsocket.on(\"data\", (chunk) => {\n\t\tclient.receive(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t});\n\tsocket.on(\"error\", (error) => client.fail(error));\n\tsocket.once(\"close\", () => client.markClosed());\n\treturn client;\n}\n\nfunction writeSocket(socket: Socket, chunk: Uint8Array): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tsocket.write(chunk, (error) => {\n\t\t\tif (error) reject(error);\n\t\t\telse resolve();\n\t\t});\n\t});\n}\n"]}
|