@kyneta/sse-transport 1.5.2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js +8 -1
- package/dist/express.js.map +1 -1
- package/dist/{server-transport-C-yuOKEa.js → server-transport-DX3-TbCZ.js} +2 -3
- package/dist/{server-transport-C-yuOKEa.js.map → server-transport-DX3-TbCZ.js.map} +1 -1
- package/dist/server-transport-DXK7KMx4.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/types-Bg1SdZ2w.d.ts.map +1 -1
- package/package.json +10 -11
- package/src/server-transport.ts +1 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Transport } from "@kyneta/transport";
|
|
1
|
+
import { Transport, randomPeerId } from "@kyneta/transport";
|
|
2
2
|
import { TEXT_WIRE_VERSION, TextReassembler, applyInboundAliasing, applyOutboundAliasing, complete, createFrameIdCounter, decodeTextWires, emptyAliasState, encodeTextFrame, encodeTextWireMessage, fragmentTextPayload } from "@kyneta/wire";
|
|
3
|
-
import { randomPeerId } from "@kyneta/random";
|
|
4
3
|
//#region src/connection.ts
|
|
5
4
|
/**
|
|
6
5
|
* Default fragment threshold in characters for outbound SSE messages.
|
|
@@ -296,4 +295,4 @@ var SseServerTransport = class extends Transport {
|
|
|
296
295
|
//#endregion
|
|
297
296
|
export { DEFAULT_FRAGMENT_THRESHOLD as n, SseConnection as r, SseServerTransport as t };
|
|
298
297
|
|
|
299
|
-
//# sourceMappingURL=server-transport-
|
|
298
|
+
//# sourceMappingURL=server-transport-DX3-TbCZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-transport-C-yuOKEa.js","names":["#fragmentThreshold","#channel","#sendFn","#onDisconnect","#aliasState","#nextFrameId","#fragmentThreshold","#connections"],"sources":["../src/connection.ts","../src/server-transport.ts"],"sourcesContent":["// connection — SseConnection for server-side peer connections.\n//\n// Wraps a TextReassembler + alias-aware pipeline to provide send/receive\n// for ChannelMsg over a single SSE connection.\n//\n// Used by SseServerTransport to manage individual client connections.\n// The client adapter handles its own encoding/decoding inline since it\n// manages a single EventSource with reconnection logic.\n//\n// The sendFn receives pre-encoded text frame strings. Framework\n// integrations just wrap them in SSE syntax:\n// Express: res.write(`data: ${textFrame}\\n\\n`)\n// Hono: stream.writeSSE({ data: textFrame })\n\nimport type { Channel, ChannelMsg, PeerId } from \"@kyneta/transport\"\nimport {\n type AliasState,\n applyInboundAliasing,\n applyOutboundAliasing,\n complete,\n createFrameIdCounter,\n decodeTextWires,\n emptyAliasState,\n encodeTextFrame,\n encodeTextWireMessage,\n fragmentTextPayload,\n TEXT_WIRE_VERSION,\n TextReassembler,\n} from \"@kyneta/wire\"\n\n// ---------------------------------------------------------------------------\n// Result types\n// ---------------------------------------------------------------------------\n\n/**\n * Response to send back to the client after processing a POST.\n */\nexport interface SsePostResponse {\n status: 200 | 202 | 400\n body: { ok: true } | { pending: true } | { error: string }\n}\n\n/**\n * Result of parsing a text POST body.\n *\n * Discriminated union describing what happened:\n * - \"messages\": Complete message(s) decoded, ready to deliver\n * - \"pending\": Fragment received, waiting for more\n * - \"error\": Decode/reassembly error\n */\nexport type SsePostResult =\n | { type: \"messages\"; messages: ChannelMsg[]; response: SsePostResponse }\n | { type: \"pending\"; response: SsePostResponse }\n | { type: \"error\"; response: SsePostResponse }\n\n/**\n * Default fragment threshold in characters for outbound SSE messages.\n * 60K chars provides a safety margin below typical infrastructure limits.\n */\nexport const DEFAULT_FRAGMENT_THRESHOLD = 60_000\n\n/**\n * Configuration for creating an SseConnection.\n */\nexport interface SseConnectionConfig {\n /**\n * Fragment threshold in characters. Messages larger than this are fragmented.\n * Set to 0 to disable fragmentation.\n * Default: 60000 (60K chars)\n */\n fragmentThreshold?: number\n}\n\n/**\n * Represents a single SSE connection to a peer (server-side).\n *\n * Manages encoding, framing, fragmentation, reassembly, and alias\n * resolution for one connected client. Created by\n * `SseServerTransport.registerConnection()`.\n *\n * The connection uses the alias-aware text pipeline — the same\n * `applyOutboundAliasing` / `applyInboundAliasing` transformer that\n * every other transport uses. This means SSE now participates in\n * docId/schemaHash aliasing just like binary transports.\n */\nexport class SseConnection {\n readonly peerId: PeerId\n readonly channelId: number\n\n #channel: Channel | null = null\n #sendFn: ((textFrame: string) => void) | null = null\n #onDisconnect: (() => void) | null = null\n\n // Fragmentation support\n readonly #fragmentThreshold: number\n #nextFrameId = createFrameIdCounter()\n\n // Alias-aware pipeline state\n #aliasState: AliasState = emptyAliasState()\n\n /**\n * Text reassembler for handling fragmented POST bodies.\n * Each connection has its own reassembler to track in-flight fragment batches.\n */\n readonly reassembler: TextReassembler\n\n constructor(peerId: PeerId, channelId: number, config?: SseConnectionConfig) {\n this.peerId = peerId\n this.channelId = channelId\n this.#fragmentThreshold =\n config?.fragmentThreshold ?? DEFAULT_FRAGMENT_THRESHOLD\n this.reassembler = new TextReassembler({\n timeoutMs: 10_000,\n onTimeout: (frameId: number) => {\n console.warn(\n `[SseConnection] Fragment batch timed out for peer ${peerId}: ${frameId}`,\n )\n },\n })\n }\n\n // ==========================================================================\n // INTERNAL API — for adapter use\n // ==========================================================================\n\n /**\n * Set the channel reference.\n * Called by the adapter when the channel is created.\n * @internal\n */\n _setChannel(channel: Channel): void {\n this.#channel = channel\n }\n\n // ==========================================================================\n // PUBLIC API\n // ==========================================================================\n\n /**\n * Set the function to call when sending messages to this peer.\n *\n * The function receives a fully encoded text frame string.\n * The framework integration just wraps it in SSE syntax:\n * - Express: `res.write(\\`data: \\${textFrame}\\\\n\\\\n\\`)`\n * - Hono: `stream.writeSSE({ data: textFrame })`\n *\n * @param sendFn Function that writes a text frame string to the SSE stream\n */\n setSendFunction(sendFn: (textFrame: string) => void): void {\n this.#sendFn = sendFn\n }\n\n /**\n * Set the function to call when this connection is disconnected.\n */\n setDisconnectHandler(handler: () => void): void {\n this.#onDisconnect = handler\n }\n\n /**\n * Send a ChannelMsg to the peer through the SSE stream.\n *\n * Runs the alias-aware outbound pipeline:\n * ChannelMsg → applyOutboundAliasing → WireMessage → encodeTextWireMessage → text frame → sendFn()\n *\n * Fragmentation is transparent to callers — the connection splits\n * large frames into multiple sendFn calls automatically.\n */\n send(msg: ChannelMsg): void {\n if (!this.#sendFn) {\n throw new Error(\n `Cannot send message: send function not set for peer ${this.peerId}`,\n )\n }\n\n const { state, wire } = applyOutboundAliasing(this.#aliasState, msg)\n this.#aliasState = state\n\n const payload = JSON.stringify(encodeTextWireMessage(wire))\n\n const textFrame = encodeTextFrame(complete(TEXT_WIRE_VERSION, payload))\n\n if (\n this.#fragmentThreshold > 0 &&\n textFrame.length > this.#fragmentThreshold\n ) {\n const fragments = fragmentTextPayload(\n payload,\n this.#fragmentThreshold,\n this.#nextFrameId(),\n )\n for (const fragment of fragments) {\n this.#sendFn(fragment)\n }\n } else {\n this.#sendFn(textFrame)\n }\n }\n\n /**\n * Handle an inbound POST body through the full alias-aware inbound pipeline.\n *\n * Pipeline: text frame → TextReassembler → decodeTextWireMessage → applyInboundAliasing → ChannelMsg\n *\n * Messages that fail alias resolution are silently skipped (logged and\n * dropped) — the connection continues processing remaining messages.\n * This matches the error-dropping behavior of every other transport.\n *\n * @param body - Text wire frame string (JSON array with \"1c\"/\"1f\" prefix)\n * @returns Result describing what happened\n */\n handlePostBody(body: string): SsePostResult {\n try {\n const wires = decodeTextWires(this.reassembler, body)\n if (wires === null) {\n return {\n type: \"pending\",\n response: { status: 202, body: { pending: true } },\n }\n }\n\n const messages: ChannelMsg[] = []\n for (const wire of wires) {\n const result = applyInboundAliasing(this.#aliasState, wire)\n this.#aliasState = result.state\n if (result.error) {\n console.warn(\n `[SseConnection] alias resolution failed for peer ${this.peerId}:`,\n result.error,\n )\n continue\n }\n if (result.msg) {\n messages.push(result.msg)\n }\n }\n\n return {\n type: \"messages\",\n messages,\n response: { status: 200, body: { ok: true } },\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"decode_failed\"\n return {\n type: \"error\",\n response: { status: 400, body: { error: errorMessage } },\n }\n }\n }\n\n /**\n * Receive a message from the peer and route it to the channel.\n *\n * Called by the framework integration after processing a POST body\n * through `handlePostBody`.\n */\n receive(msg: ChannelMsg): void {\n if (!this.#channel) {\n throw new Error(\n `Cannot receive message: channel not set for peer ${this.peerId}`,\n )\n }\n this.#channel.onReceive(msg)\n }\n\n /**\n * Disconnect this connection.\n */\n disconnect(): void {\n this.#onDisconnect?.()\n }\n\n /**\n * Dispose of resources held by this connection.\n * Must be called when the connection is closed to prevent timer leaks.\n */\n dispose(): void {\n this.reassembler.dispose()\n }\n}\n","// server-adapter — SSE server adapter for @kyneta/exchange.\n//\n// Manages SSE connections from clients, encoding/decoding via the\n// kyneta text wire format. Framework-agnostic — works with any HTTP\n// framework through the SseConnection's setSendFunction() callback.\n//\n// Usage with Express:\n// import { SseServerTransport } from \"@kyneta/sse-network-adapter/server\"\n// import { createSseExpressRouter } from \"@kyneta/sse-network-adapter/express\"\n//\n// const serverAdapter = new SseServerTransport()\n// app.use(\"/sse\", createSseExpressRouter(serverAdapter))\n//\n// Usage with Hono:\n// import { SseServerTransport } from \"@kyneta/sse-network-adapter/server\"\n// import { SseConnection } from \"@kyneta/sse-network-adapter/express\"\n//\n// const serverAdapter = new SseServerTransport()\n// // Wire up GET /events and POST /sync manually using\n// // serverAdapter.registerConnection() and connection.handlePostBody()\n\nimport { randomPeerId } from \"@kyneta/random\"\nimport type { ChannelMsg, GeneratedChannel, PeerId } from \"@kyneta/transport\"\nimport { Transport } from \"@kyneta/transport\"\nimport { DEFAULT_FRAGMENT_THRESHOLD, SseConnection } from \"./connection.js\"\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the SSE server adapter.\n */\nexport interface SseServerTransportOptions {\n /**\n * Fragment threshold in characters. Messages larger than this are fragmented\n * into multiple SSE events.\n * Set to 0 to disable fragmentation.\n * Default: 60000 (60K chars)\n */\n fragmentThreshold?: number\n}\n\n// ---------------------------------------------------------------------------\n// SseServerTransport\n// ---------------------------------------------------------------------------\n\n/**\n * SSE server network adapter.\n *\n * Framework-agnostic — works with any HTTP framework through the\n * `SseConnection.setSendFunction()` callback. Use `registerConnection()`\n * to integrate with your framework's SSE endpoint handler.\n *\n * Each client connection is tracked as an `SseConnection` keyed by peer ID.\n * The adapter creates a channel per connection and routes outbound messages\n * through the connection's send method (which encodes to text wire format\n * and calls the injected sendFn).\n *\n * The connection handshake:\n * 1. Client opens EventSource (GET /events)\n * 2. Server calls `registerConnection(peerId)` → creates channel\n * 3. Client's EventSource.onopen fires → client sends establish (POST)\n * 4. Server receives establish → Synchronizer upgrades channel and sends present (SSE)\n *\n * The server does NOT call `establishChannel()` — it waits for the client's\n * establish message, which arrives via POST after the EventSource is open.\n */\nexport class SseServerTransport extends Transport<PeerId> {\n #connections = new Map<PeerId, SseConnection>()\n readonly #fragmentThreshold: number\n\n constructor(options?: SseServerTransportOptions) {\n super({ transportType: \"sse-server\" })\n this.#fragmentThreshold =\n options?.fragmentThreshold ?? DEFAULT_FRAGMENT_THRESHOLD\n }\n\n // ==========================================================================\n // Adapter abstract method implementations\n // ==========================================================================\n\n protected generate(peerId: PeerId): GeneratedChannel {\n return {\n transportType: this.transportType,\n send: (msg: ChannelMsg) => {\n const connection = this.#connections.get(peerId)\n if (connection) {\n connection.send(msg)\n }\n },\n stop: () => {\n this.unregisterConnection(peerId)\n },\n }\n }\n\n async onStart(): Promise<void> {\n // Server adapter starts passively — connections arrive via registerConnection()\n }\n\n async onStop(): Promise<void> {\n // Disconnect all active connections\n for (const connection of this.#connections.values()) {\n connection.disconnect()\n }\n this.#connections.clear()\n }\n\n // ==========================================================================\n // Connection management\n // ==========================================================================\n\n /**\n * Register a new peer connection.\n *\n * Call this from your framework's SSE endpoint handler when a client\n * connects via EventSource. Returns an `SseConnection` that you wire\n * up with `setSendFunction()` and `setDisconnectHandler()`.\n *\n * @param peerId The unique identifier for the peer (from query param or header)\n * @returns An SseConnection object for managing the connection\n *\n * @example Express\n * ```typescript\n * const connection = serverAdapter.registerConnection(peerId)\n * connection.setSendFunction((textFrame) => {\n * res.write(`data: ${textFrame}\\n\\n`)\n * })\n * ```\n *\n * @example Hono\n * ```typescript\n * const connection = serverAdapter.registerConnection(peerId)\n * connection.setSendFunction((textFrame) => {\n * stream.writeSSE({ data: textFrame })\n * })\n * ```\n */\n registerConnection(peerId?: PeerId): SseConnection {\n const resolvedPeerId = peerId ?? (`sse-${randomPeerId()}` as PeerId)\n\n // Check for existing connection and clean it up\n const existingConnection = this.#connections.get(resolvedPeerId)\n if (existingConnection) {\n existingConnection.dispose()\n this.unregisterConnection(resolvedPeerId)\n }\n\n // Create channel for this peer\n const channel = this.addChannel(resolvedPeerId)\n\n // Create connection object with fragmentation config\n const connection = new SseConnection(resolvedPeerId, channel.channelId, {\n fragmentThreshold: this.#fragmentThreshold,\n })\n connection._setChannel(channel)\n\n // Store connection\n this.#connections.set(resolvedPeerId, connection)\n\n return connection\n }\n\n /**\n * Unregister a peer connection.\n *\n * Removes the channel, disposes the connection's reassembler,\n * and cleans up tracking state. Called automatically when the\n * client disconnects (via req.on(\"close\")) or manually.\n *\n * @param peerId The unique identifier for the peer\n */\n unregisterConnection(peerId: PeerId): void {\n const connection = this.#connections.get(peerId)\n if (connection) {\n connection.dispose()\n this.removeChannel(connection.channelId)\n this.#connections.delete(peerId)\n }\n }\n\n /**\n * Get an active connection by peer ID.\n */\n getConnection(peerId: PeerId): SseConnection | undefined {\n return this.#connections.get(peerId)\n }\n\n /**\n * Get all active connections.\n */\n getAllConnections(): SseConnection[] {\n return Array.from(this.#connections.values())\n }\n\n /**\n * Check if a peer is connected.\n */\n isConnected(peerId: PeerId): boolean {\n return this.#connections.has(peerId)\n }\n\n /**\n * Get the number of connected peers.\n */\n get connectionCount(): number {\n return this.#connections.size\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server-transport-DX3-TbCZ.js","names":["#fragmentThreshold","#channel","#sendFn","#onDisconnect","#aliasState","#nextFrameId","#fragmentThreshold","#connections"],"sources":["../src/connection.ts","../src/server-transport.ts"],"sourcesContent":["// connection — SseConnection for server-side peer connections.\n//\n// Wraps a TextReassembler + alias-aware pipeline to provide send/receive\n// for ChannelMsg over a single SSE connection.\n//\n// Used by SseServerTransport to manage individual client connections.\n// The client adapter handles its own encoding/decoding inline since it\n// manages a single EventSource with reconnection logic.\n//\n// The sendFn receives pre-encoded text frame strings. Framework\n// integrations just wrap them in SSE syntax:\n// Express: res.write(`data: ${textFrame}\\n\\n`)\n// Hono: stream.writeSSE({ data: textFrame })\n\nimport type { Channel, ChannelMsg, PeerId } from \"@kyneta/transport\"\nimport {\n type AliasState,\n applyInboundAliasing,\n applyOutboundAliasing,\n complete,\n createFrameIdCounter,\n decodeTextWires,\n emptyAliasState,\n encodeTextFrame,\n encodeTextWireMessage,\n fragmentTextPayload,\n TEXT_WIRE_VERSION,\n TextReassembler,\n} from \"@kyneta/wire\"\n\n// ---------------------------------------------------------------------------\n// Result types\n// ---------------------------------------------------------------------------\n\n/**\n * Response to send back to the client after processing a POST.\n */\nexport interface SsePostResponse {\n status: 200 | 202 | 400\n body: { ok: true } | { pending: true } | { error: string }\n}\n\n/**\n * Result of parsing a text POST body.\n *\n * Discriminated union describing what happened:\n * - \"messages\": Complete message(s) decoded, ready to deliver\n * - \"pending\": Fragment received, waiting for more\n * - \"error\": Decode/reassembly error\n */\nexport type SsePostResult =\n | { type: \"messages\"; messages: ChannelMsg[]; response: SsePostResponse }\n | { type: \"pending\"; response: SsePostResponse }\n | { type: \"error\"; response: SsePostResponse }\n\n/**\n * Default fragment threshold in characters for outbound SSE messages.\n * 60K chars provides a safety margin below typical infrastructure limits.\n */\nexport const DEFAULT_FRAGMENT_THRESHOLD = 60_000\n\n/**\n * Configuration for creating an SseConnection.\n */\nexport interface SseConnectionConfig {\n /**\n * Fragment threshold in characters. Messages larger than this are fragmented.\n * Set to 0 to disable fragmentation.\n * Default: 60000 (60K chars)\n */\n fragmentThreshold?: number\n}\n\n/**\n * Represents a single SSE connection to a peer (server-side).\n *\n * Manages encoding, framing, fragmentation, reassembly, and alias\n * resolution for one connected client. Created by\n * `SseServerTransport.registerConnection()`.\n *\n * The connection uses the alias-aware text pipeline — the same\n * `applyOutboundAliasing` / `applyInboundAliasing` transformer that\n * every other transport uses. This means SSE now participates in\n * docId/schemaHash aliasing just like binary transports.\n */\nexport class SseConnection {\n readonly peerId: PeerId\n readonly channelId: number\n\n #channel: Channel | null = null\n #sendFn: ((textFrame: string) => void) | null = null\n #onDisconnect: (() => void) | null = null\n\n // Fragmentation support\n readonly #fragmentThreshold: number\n #nextFrameId = createFrameIdCounter()\n\n // Alias-aware pipeline state\n #aliasState: AliasState = emptyAliasState()\n\n /**\n * Text reassembler for handling fragmented POST bodies.\n * Each connection has its own reassembler to track in-flight fragment batches.\n */\n readonly reassembler: TextReassembler\n\n constructor(peerId: PeerId, channelId: number, config?: SseConnectionConfig) {\n this.peerId = peerId\n this.channelId = channelId\n this.#fragmentThreshold =\n config?.fragmentThreshold ?? DEFAULT_FRAGMENT_THRESHOLD\n this.reassembler = new TextReassembler({\n timeoutMs: 10_000,\n onTimeout: (frameId: number) => {\n console.warn(\n `[SseConnection] Fragment batch timed out for peer ${peerId}: ${frameId}`,\n )\n },\n })\n }\n\n // ==========================================================================\n // INTERNAL API — for adapter use\n // ==========================================================================\n\n /**\n * Set the channel reference.\n * Called by the adapter when the channel is created.\n * @internal\n */\n _setChannel(channel: Channel): void {\n this.#channel = channel\n }\n\n // ==========================================================================\n // PUBLIC API\n // ==========================================================================\n\n /**\n * Set the function to call when sending messages to this peer.\n *\n * The function receives a fully encoded text frame string.\n * The framework integration just wraps it in SSE syntax:\n * - Express: `res.write(\\`data: \\${textFrame}\\\\n\\\\n\\`)`\n * - Hono: `stream.writeSSE({ data: textFrame })`\n *\n * @param sendFn Function that writes a text frame string to the SSE stream\n */\n setSendFunction(sendFn: (textFrame: string) => void): void {\n this.#sendFn = sendFn\n }\n\n /**\n * Set the function to call when this connection is disconnected.\n */\n setDisconnectHandler(handler: () => void): void {\n this.#onDisconnect = handler\n }\n\n /**\n * Send a ChannelMsg to the peer through the SSE stream.\n *\n * Runs the alias-aware outbound pipeline:\n * ChannelMsg → applyOutboundAliasing → WireMessage → encodeTextWireMessage → text frame → sendFn()\n *\n * Fragmentation is transparent to callers — the connection splits\n * large frames into multiple sendFn calls automatically.\n */\n send(msg: ChannelMsg): void {\n if (!this.#sendFn) {\n throw new Error(\n `Cannot send message: send function not set for peer ${this.peerId}`,\n )\n }\n\n const { state, wire } = applyOutboundAliasing(this.#aliasState, msg)\n this.#aliasState = state\n\n const payload = JSON.stringify(encodeTextWireMessage(wire))\n\n const textFrame = encodeTextFrame(complete(TEXT_WIRE_VERSION, payload))\n\n if (\n this.#fragmentThreshold > 0 &&\n textFrame.length > this.#fragmentThreshold\n ) {\n const fragments = fragmentTextPayload(\n payload,\n this.#fragmentThreshold,\n this.#nextFrameId(),\n )\n for (const fragment of fragments) {\n this.#sendFn(fragment)\n }\n } else {\n this.#sendFn(textFrame)\n }\n }\n\n /**\n * Handle an inbound POST body through the full alias-aware inbound pipeline.\n *\n * Pipeline: text frame → TextReassembler → decodeTextWireMessage → applyInboundAliasing → ChannelMsg\n *\n * Messages that fail alias resolution are silently skipped (logged and\n * dropped) — the connection continues processing remaining messages.\n * This matches the error-dropping behavior of every other transport.\n *\n * @param body - Text wire frame string (JSON array with \"1c\"/\"1f\" prefix)\n * @returns Result describing what happened\n */\n handlePostBody(body: string): SsePostResult {\n try {\n const wires = decodeTextWires(this.reassembler, body)\n if (wires === null) {\n return {\n type: \"pending\",\n response: { status: 202, body: { pending: true } },\n }\n }\n\n const messages: ChannelMsg[] = []\n for (const wire of wires) {\n const result = applyInboundAliasing(this.#aliasState, wire)\n this.#aliasState = result.state\n if (result.error) {\n console.warn(\n `[SseConnection] alias resolution failed for peer ${this.peerId}:`,\n result.error,\n )\n continue\n }\n if (result.msg) {\n messages.push(result.msg)\n }\n }\n\n return {\n type: \"messages\",\n messages,\n response: { status: 200, body: { ok: true } },\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"decode_failed\"\n return {\n type: \"error\",\n response: { status: 400, body: { error: errorMessage } },\n }\n }\n }\n\n /**\n * Receive a message from the peer and route it to the channel.\n *\n * Called by the framework integration after processing a POST body\n * through `handlePostBody`.\n */\n receive(msg: ChannelMsg): void {\n if (!this.#channel) {\n throw new Error(\n `Cannot receive message: channel not set for peer ${this.peerId}`,\n )\n }\n this.#channel.onReceive(msg)\n }\n\n /**\n * Disconnect this connection.\n */\n disconnect(): void {\n this.#onDisconnect?.()\n }\n\n /**\n * Dispose of resources held by this connection.\n * Must be called when the connection is closed to prevent timer leaks.\n */\n dispose(): void {\n this.reassembler.dispose()\n }\n}\n","// server-adapter — SSE server adapter for @kyneta/exchange.\n//\n// Manages SSE connections from clients, encoding/decoding via the\n// kyneta text wire format. Framework-agnostic — works with any HTTP\n// framework through the SseConnection's setSendFunction() callback.\n//\n// Usage with Express:\n// import { SseServerTransport } from \"@kyneta/sse-network-adapter/server\"\n// import { createSseExpressRouter } from \"@kyneta/sse-network-adapter/express\"\n//\n// const serverAdapter = new SseServerTransport()\n// app.use(\"/sse\", createSseExpressRouter(serverAdapter))\n//\n// Usage with Hono:\n// import { SseServerTransport } from \"@kyneta/sse-network-adapter/server\"\n// import { SseConnection } from \"@kyneta/sse-network-adapter/express\"\n//\n// const serverAdapter = new SseServerTransport()\n// // Wire up GET /events and POST /sync manually using\n// // serverAdapter.registerConnection() and connection.handlePostBody()\n\nimport type { ChannelMsg, GeneratedChannel, PeerId } from \"@kyneta/transport\"\nimport { randomPeerId, Transport } from \"@kyneta/transport\"\nimport { DEFAULT_FRAGMENT_THRESHOLD, SseConnection } from \"./connection.js\"\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the SSE server adapter.\n */\nexport interface SseServerTransportOptions {\n /**\n * Fragment threshold in characters. Messages larger than this are fragmented\n * into multiple SSE events.\n * Set to 0 to disable fragmentation.\n * Default: 60000 (60K chars)\n */\n fragmentThreshold?: number\n}\n\n// ---------------------------------------------------------------------------\n// SseServerTransport\n// ---------------------------------------------------------------------------\n\n/**\n * SSE server network adapter.\n *\n * Framework-agnostic — works with any HTTP framework through the\n * `SseConnection.setSendFunction()` callback. Use `registerConnection()`\n * to integrate with your framework's SSE endpoint handler.\n *\n * Each client connection is tracked as an `SseConnection` keyed by peer ID.\n * The adapter creates a channel per connection and routes outbound messages\n * through the connection's send method (which encodes to text wire format\n * and calls the injected sendFn).\n *\n * The connection handshake:\n * 1. Client opens EventSource (GET /events)\n * 2. Server calls `registerConnection(peerId)` → creates channel\n * 3. Client's EventSource.onopen fires → client sends establish (POST)\n * 4. Server receives establish → Synchronizer upgrades channel and sends present (SSE)\n *\n * The server does NOT call `establishChannel()` — it waits for the client's\n * establish message, which arrives via POST after the EventSource is open.\n */\nexport class SseServerTransport extends Transport<PeerId> {\n #connections = new Map<PeerId, SseConnection>()\n readonly #fragmentThreshold: number\n\n constructor(options?: SseServerTransportOptions) {\n super({ transportType: \"sse-server\" })\n this.#fragmentThreshold =\n options?.fragmentThreshold ?? DEFAULT_FRAGMENT_THRESHOLD\n }\n\n // ==========================================================================\n // Adapter abstract method implementations\n // ==========================================================================\n\n protected generate(peerId: PeerId): GeneratedChannel {\n return {\n transportType: this.transportType,\n send: (msg: ChannelMsg) => {\n const connection = this.#connections.get(peerId)\n if (connection) {\n connection.send(msg)\n }\n },\n stop: () => {\n this.unregisterConnection(peerId)\n },\n }\n }\n\n async onStart(): Promise<void> {\n // Server adapter starts passively — connections arrive via registerConnection()\n }\n\n async onStop(): Promise<void> {\n // Disconnect all active connections\n for (const connection of this.#connections.values()) {\n connection.disconnect()\n }\n this.#connections.clear()\n }\n\n // ==========================================================================\n // Connection management\n // ==========================================================================\n\n /**\n * Register a new peer connection.\n *\n * Call this from your framework's SSE endpoint handler when a client\n * connects via EventSource. Returns an `SseConnection` that you wire\n * up with `setSendFunction()` and `setDisconnectHandler()`.\n *\n * @param peerId The unique identifier for the peer (from query param or header)\n * @returns An SseConnection object for managing the connection\n *\n * @example Express\n * ```typescript\n * const connection = serverAdapter.registerConnection(peerId)\n * connection.setSendFunction((textFrame) => {\n * res.write(`data: ${textFrame}\\n\\n`)\n * })\n * ```\n *\n * @example Hono\n * ```typescript\n * const connection = serverAdapter.registerConnection(peerId)\n * connection.setSendFunction((textFrame) => {\n * stream.writeSSE({ data: textFrame })\n * })\n * ```\n */\n registerConnection(peerId?: PeerId): SseConnection {\n const resolvedPeerId = peerId ?? (`sse-${randomPeerId()}` as PeerId)\n\n // Check for existing connection and clean it up\n const existingConnection = this.#connections.get(resolvedPeerId)\n if (existingConnection) {\n existingConnection.dispose()\n this.unregisterConnection(resolvedPeerId)\n }\n\n // Create channel for this peer\n const channel = this.addChannel(resolvedPeerId)\n\n // Create connection object with fragmentation config\n const connection = new SseConnection(resolvedPeerId, channel.channelId, {\n fragmentThreshold: this.#fragmentThreshold,\n })\n connection._setChannel(channel)\n\n // Store connection\n this.#connections.set(resolvedPeerId, connection)\n\n return connection\n }\n\n /**\n * Unregister a peer connection.\n *\n * Removes the channel, disposes the connection's reassembler,\n * and cleans up tracking state. Called automatically when the\n * client disconnects (via req.on(\"close\")) or manually.\n *\n * @param peerId The unique identifier for the peer\n */\n unregisterConnection(peerId: PeerId): void {\n const connection = this.#connections.get(peerId)\n if (connection) {\n connection.dispose()\n this.removeChannel(connection.channelId)\n this.#connections.delete(peerId)\n }\n }\n\n /**\n * Get an active connection by peer ID.\n */\n getConnection(peerId: PeerId): SseConnection | undefined {\n return this.#connections.get(peerId)\n }\n\n /**\n * Get all active connections.\n */\n getAllConnections(): SseConnection[] {\n return Array.from(this.#connections.values())\n }\n\n /**\n * Check if a peer is connected.\n */\n isConnected(peerId: PeerId): boolean {\n return this.#connections.has(peerId)\n }\n\n /**\n * Get the number of connected peers.\n */\n get connectionCount(): number {\n return this.#connections.size\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-transport-DXK7KMx4.d.ts","names":[],"sources":["../src/connection.ts","../src/server-transport.ts"],"mappings":";;;;;;AAqCA;UAAiB,eAAA;EACf,MAAA;EACA,IAAA;IAAQ,EAAA;EAAA;IAAe,OAAA;EAAA;IAAoB,KAAA;EAAA;AAAA;AAW7C;;;;;;;;AAAA,KAAY,aAAA;EACN,IAAA;EAAkB,QAAA,EAAU,UAAA;EAAc,QAAA,EAAU,eAAA;AAAA;EACpD,IAAA;EAAiB,QAAA,EAAU,eAAA;AAAA;EAC3B,IAAA;EAAe,QAAA,EAAU,eAAA;AAAA
|
|
1
|
+
{"version":3,"file":"server-transport-DXK7KMx4.d.ts","names":[],"sources":["../src/connection.ts","../src/server-transport.ts"],"mappings":";;;;;;AAqCA;UAAiB,eAAA;EACf,MAAA;EACA,IAAA;IAAQ,EAAA;EAAA;IAAe,OAAA;EAAA;IAAoB,KAAA;EAAA;AAAA;AAW7C;;;;;;;;AAAA,KAAY,aAAA;EACN,IAAA;EAAkB,QAAA,EAAU,UAAA;EAAc,QAAA,EAAU,eAAA;AAAA;EACpD,IAAA;EAAiB,QAAA,EAAU,eAAA;AAAA;EAC3B,IAAA;EAAe,QAAA,EAAU,eAAA;AAAA;;;AAAe;AAM9C;cAAa,0BAAA;;;AAA0B;UAKtB,mBAAA;EAAmB;;;AAMjB;AAenB;EAfE,iBAAiB;AAAA;;;;;;;;;;;;;cAeN,aAAA;EAAA;WACF,MAAA,EAAQ,MAAA;EAAA,SACR,SAAA;;;;;WAiBA,WAAA,EAAa,eAAA;cAEV,MAAA,EAAQ,MAAA,EAAQ,SAAA,UAAmB,MAAA,GAAS,mBAAA;EAwBxD;;;;;EAAA,WAAA,CAAY,OAAA,EAAS,OAAA;EAyBrB;;;;;;;;;;EAPA,eAAA,CAAgB,MAAA,GAAS,SAAA;EAyHzB;;;EAlHA,oBAAA,CAAqB,OAAA;;;;AC3HvB;;;;AAOmB;AA4BnB;EDqGE,IAAA,CAAK,GAAA,EAAK,UAAA;;;;;;;;;;;;;EA2CV,cAAA,CAAe,IAAA,WAAe,aAAA;ECbV;;;;;;ED2DpB,OAAA,CAAQ,GAAA,EAAK,UAAA;;;;EAYb,UAAA,CAAA;EC5L2B;;;;EDoM3B,OAAA,CAAA;AAAA;;;;AAhPF;;UCLiB,yBAAA;EDKe;;;;;;ECE9B,iBAAiB;AAAA;ADWnB;;;;;;;;;;;;;;;;;;;;;AAAA,cCiBa,kBAAA,SAA2B,SAAA,CAAU,MAAA;EAAA;cAIpC,OAAA,GAAU,yBAAA;EAAA,UAUZ,QAAA,CAAS,MAAA,EAAQ,MAAA,GAAS,gBAAA;EAe9B,OAAA,CAAA,GAAW,OAAA;EAIX,MAAA,CAAA,GAAU,OAAA;EDpCD;;;;AAME;AAenB;;;;;;;;;;;;;;;;;;;;;ECqDE,kBAAA,CAAmB,MAAA,GAAS,MAAA,GAAS,aAAA;EDhCT;;;;;;;;;ECkE5B,oBAAA,CAAqB,MAAA,EAAQ,MAAA;EDjBR;;;EC6BrB,aAAA,CAAc,MAAA,EAAQ,MAAA,GAAS,aAAA;ED2B/B;;;ECpBA,iBAAA,CAAA,GAAqB,aAAA;EDkER;;;EC3Db,WAAA,CAAY,MAAA,EAAQ,MAAA;ED+Eb;AAAA;;EAAA,ICxEH,eAAA,CAAA;AAAA"}
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as DEFAULT_FRAGMENT_THRESHOLD, r as SseConnection, t as SseServerTransport } from "./server-transport-
|
|
1
|
+
import { n as DEFAULT_FRAGMENT_THRESHOLD, r as SseConnection, t as SseServerTransport } from "./server-transport-DX3-TbCZ.js";
|
|
2
2
|
export { DEFAULT_FRAGMENT_THRESHOLD, SseConnection, SseServerTransport };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-Bg1SdZ2w.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;AAyBA;;;;;KAAY,gBAAA;EACN,IAAA;AAAA;EACA,IAAA;EAAe,KAAA,EAAO,
|
|
1
|
+
{"version":3,"file":"types-Bg1SdZ2w.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;AAyBA;;;;;KAAY,gBAAA;EACN,IAAA;AAAA;EACA,IAAA;EAAe,KAAA,EAAO,KAAK;AAAA;EAC3B,IAAA;EAA8B,QAAA;AAAA;;;;;;;;;;;;;AAwBwB;AAS5D;KAbY,cAAA;EACN,MAAA;EAAwB,MAAA,GAAS,gBAAgB;AAAA;EACjD,MAAA;EAAsB,OAAA;AAAA;EACtB,MAAA;AAAA;EACA,MAAA;EAAwB,OAAA;EAAiB,aAAA;AAAA;AAuBd;AAUjC;;AAViC,UAdhB,mBAAA;EA2BP;EAAA,SAzBC,MAAA,EAAQ,MAAM;EA+BC;EAAA,SA7Bf,SAAA;EA6B+B;EA3BxC,UAAA;AAAA;;;;UAMe,mBAAA;EAcE;EAZjB,UAAA,EAAY,mBAAmB;AAAA;;;;UAUhB,wBAAA;EAef;EAbA,aAAA,IAAiB,UAAA;IACf,IAAA,EAAM,cAAA;IACN,EAAA,EAAI,cAAA;IACJ,SAAA;EAAA;;EAIF,YAAA,IAAgB,MAAA,EAAQ,gBAAA;;EAGxB,cAAA,IAAkB,OAAA,UAAiB,aAAA;;EAGnC,aAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kyneta/sse-transport",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "SSE (Server-Sent Events) network adapter for @kyneta/exchange — client, server, and Express integration",
|
|
5
5
|
"author": "Duane Johnson",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,10 +33,9 @@
|
|
|
33
33
|
"./src/*": "./src/*"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@kyneta/machine": "^1.
|
|
37
|
-
"@kyneta/
|
|
38
|
-
"@kyneta/transport": "^1.
|
|
39
|
-
"@kyneta/wire": "^1.5.2"
|
|
36
|
+
"@kyneta/machine": "^1.6.1",
|
|
37
|
+
"@kyneta/wire": "^1.6.1",
|
|
38
|
+
"@kyneta/transport": "^1.6.1"
|
|
40
39
|
},
|
|
41
40
|
"peerDependenciesMeta": {
|
|
42
41
|
"express": {
|
|
@@ -47,14 +46,14 @@
|
|
|
47
46
|
"@types/express": "^4.17.23",
|
|
48
47
|
"@types/node": "^22",
|
|
49
48
|
"express": "^4.21.0",
|
|
50
|
-
"tsdown": "^0.
|
|
49
|
+
"tsdown": "^0.22.0",
|
|
51
50
|
"typescript": "^5.9.2",
|
|
52
51
|
"vitest": "^4.0.17",
|
|
53
|
-
"@kyneta/exchange": "^1.
|
|
54
|
-
"@kyneta/
|
|
55
|
-
"@kyneta/
|
|
56
|
-
"@kyneta/
|
|
57
|
-
"@kyneta/
|
|
52
|
+
"@kyneta/exchange": "^1.6.1",
|
|
53
|
+
"@kyneta/transport": "^1.6.1",
|
|
54
|
+
"@kyneta/machine": "^1.6.1",
|
|
55
|
+
"@kyneta/schema": "^1.6.1",
|
|
56
|
+
"@kyneta/wire": "^1.6.1"
|
|
58
57
|
},
|
|
59
58
|
"scripts": {
|
|
60
59
|
"build": "tsdown",
|
package/src/server-transport.ts
CHANGED
|
@@ -19,9 +19,8 @@
|
|
|
19
19
|
// // Wire up GET /events and POST /sync manually using
|
|
20
20
|
// // serverAdapter.registerConnection() and connection.handlePostBody()
|
|
21
21
|
|
|
22
|
-
import { randomPeerId } from "@kyneta/random"
|
|
23
22
|
import type { ChannelMsg, GeneratedChannel, PeerId } from "@kyneta/transport"
|
|
24
|
-
import { Transport } from "@kyneta/transport"
|
|
23
|
+
import { randomPeerId, Transport } from "@kyneta/transport"
|
|
25
24
|
import { DEFAULT_FRAGMENT_THRESHOLD, SseConnection } from "./connection.js"
|
|
26
25
|
|
|
27
26
|
// ---------------------------------------------------------------------------
|