@earendil-works/pi-server 0.84.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +50 -0
  3. package/dist/connection.d.ts +28 -0
  4. package/dist/connection.d.ts.map +1 -0
  5. package/dist/connection.js +4 -0
  6. package/dist/connection.js.map +1 -0
  7. package/dist/errors.d.ts +27 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +45 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +6 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/listener.d.ts +10 -0
  16. package/dist/listener.d.ts.map +1 -0
  17. package/dist/listener.js +2 -0
  18. package/dist/listener.js.map +1 -0
  19. package/dist/protocol.d.ts +22 -0
  20. package/dist/protocol.d.ts.map +1 -0
  21. package/dist/protocol.js +266 -0
  22. package/dist/protocol.js.map +1 -0
  23. package/dist/server.d.ts +36 -0
  24. package/dist/server.d.ts.map +1 -0
  25. package/dist/server.js +365 -0
  26. package/dist/server.js.map +1 -0
  27. package/dist/sessions.d.ts +2174 -0
  28. package/dist/sessions.d.ts.map +1 -0
  29. package/dist/sessions.js +305 -0
  30. package/dist/sessions.js.map +1 -0
  31. package/dist/snapshots.d.ts +24 -0
  32. package/dist/snapshots.d.ts.map +1 -0
  33. package/dist/snapshots.js +39 -0
  34. package/dist/snapshots.js.map +1 -0
  35. package/dist/testing/client.d.ts +31 -0
  36. package/dist/testing/client.d.ts.map +1 -0
  37. package/dist/testing/client.js +118 -0
  38. package/dist/testing/client.js.map +1 -0
  39. package/dist/testing/index.d.ts +6 -0
  40. package/dist/testing/index.d.ts.map +1 -0
  41. package/dist/testing/index.js +4 -0
  42. package/dist/testing/index.js.map +1 -0
  43. package/dist/testing/server.d.ts +12 -0
  44. package/dist/testing/server.d.ts.map +1 -0
  45. package/dist/testing/server.js +17 -0
  46. package/dist/testing/server.js.map +1 -0
  47. package/dist/testing/service.d.ts +59 -0
  48. package/dist/testing/service.d.ts.map +1 -0
  49. package/dist/testing/service.js +240 -0
  50. package/dist/testing/service.js.map +1 -0
  51. package/dist/transports/unix/index.d.ts +4 -0
  52. package/dist/transports/unix/index.d.ts.map +1 -0
  53. package/dist/transports/unix/index.js +3 -0
  54. package/dist/transports/unix/index.js.map +1 -0
  55. package/dist/transports/unix/listener.d.ts +25 -0
  56. package/dist/transports/unix/listener.d.ts.map +1 -0
  57. package/dist/transports/unix/listener.js +432 -0
  58. package/dist/transports/unix/listener.js.map +1 -0
  59. package/dist/transports/unix/preset.d.ts +6 -0
  60. package/dist/transports/unix/preset.d.ts.map +1 -0
  61. package/dist/transports/unix/preset.js +21 -0
  62. package/dist/transports/unix/preset.js.map +1 -0
  63. package/dist/transports/unix/types.d.ts +15 -0
  64. package/dist/transports/unix/types.d.ts.map +1 -0
  65. package/dist/transports/unix/types.js +2 -0
  66. package/dist/transports/unix/types.js.map +1 -0
  67. package/dist/types.d.ts +56 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +57 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype ImageContent as AiImageContent,\n\ttype TextContent as AiTextContent,\n\ttype Usage as AiUsage,\n\ttype Api,\n\ttype AssistantMessage,\n\tgetSupportedThinkingLevels,\n\ttype Model,\n\ttype ModelThinkingLevel,\n\ttype ToolCall,\n\ttype ToolResultMessage,\n\ttype UserMessage,\n} from \"@earendil-works/pi-ai\";\nimport type {\n\tAssistantTranscriptItem,\n\tJsonValue,\n\tModelMetadata,\n\tThinkingLevel,\n\tToolTranscriptItem,\n\tUsage,\n\tUserTranscriptItem,\n} from \"@earendil-works/pi-protocol\";\n\ntype Assert<T extends true> = T;\ntype ExactKeys<T, Keys extends keyof T> = keyof T extends Keys ? true : false;\ntype _AiThinkingLevelsFitProtocol = Assert<ModelThinkingLevel extends ThinkingLevel ? true : false>;\ntype _ProtocolThinkingLevelsFitAi = Assert<ThinkingLevel extends ModelThinkingLevel ? true : false>;\ntype AiModelInput = Model<Api>[\"input\"][number];\ntype ProtocolModelInput = ModelMetadata[\"input\"][number];\ntype _AiModelInputsFitProtocol = Assert<AiModelInput extends ProtocolModelInput ? true : false>;\ntype _ProtocolModelInputsFitAi = Assert<ProtocolModelInput extends AiModelInput ? true : false>;\n/**\n * Enumerate mapped and intentionally omitted pi-ai fields so additions fail compilation here.\n * Provider replay metadata, diagnostics, cache-write retention splits, model transport settings,\n * model sampling defaults, pricing tiers, and deferred-tool availability remain intentionally\n * server-side.\n */\ntype _AiTextContentFieldsAccountedFor = Assert<ExactKeys<AiTextContent, \"type\" | \"text\" | \"textSignature\">>;\ntype _AiThinkingContentFieldsAccountedFor = Assert<\n\tExactKeys<\n\t\tExtract<AssistantMessage[\"content\"][number], { type: \"thinking\" }>,\n\t\t\"type\" | \"thinking\" | \"thinkingSignature\" | \"redacted\"\n\t>\n>;\ntype _AiImageContentFieldsAccountedFor = Assert<ExactKeys<AiImageContent, \"type\" | \"data\" | \"mimeType\">>;\ntype _AiToolCallFieldsAccountedFor = Assert<\n\tExactKeys<ToolCall, \"type\" | \"id\" | \"name\" | \"arguments\" | \"thoughtSignature\">\n>;\ntype _AiUsageFieldsAccountedFor = Assert<\n\tExactKeys<\n\t\tAiUsage,\n\t\t\"input\" | \"output\" | \"cacheRead\" | \"cacheWrite\" | \"cacheWrite1h\" | \"reasoning\" | \"totalTokens\" | \"cost\"\n\t>\n>;\ntype _AiUsageCostFieldsAccountedFor = Assert<\n\tExactKeys<AiUsage[\"cost\"], \"input\" | \"output\" | \"cacheRead\" | \"cacheWrite\" | \"total\">\n>;\ntype _AiModelFieldsAccountedFor = Assert<\n\tExactKeys<\n\t\tModel<Api>,\n\t\t| \"id\"\n\t\t| \"name\"\n\t\t| \"api\"\n\t\t| \"provider\"\n\t\t| \"baseUrl\"\n\t\t| \"reasoning\"\n\t\t| \"thinkingLevelMap\"\n\t\t| \"input\"\n\t\t| \"cost\"\n\t\t| \"contextWindow\"\n\t\t| \"maxTokens\"\n\t\t| \"samplingParams\"\n\t\t| \"headers\"\n\t\t| \"compat\"\n\t>\n>;\ntype _AiModelCostFieldsAccountedFor = Assert<\n\tExactKeys<Model<Api>[\"cost\"], \"input\" | \"output\" | \"cacheRead\" | \"cacheWrite\" | \"tiers\">\n>;\ntype _AiUserMessageFieldsAccountedFor = Assert<ExactKeys<UserMessage, \"role\" | \"content\" | \"timestamp\">>;\ntype _AiAssistantMessageFieldsAccountedFor = Assert<\n\tExactKeys<\n\t\tAssistantMessage,\n\t\t| \"role\"\n\t\t| \"content\"\n\t\t| \"api\"\n\t\t| \"provider\"\n\t\t| \"model\"\n\t\t| \"responseModel\"\n\t\t| \"responseId\"\n\t\t| \"diagnostics\"\n\t\t| \"usage\"\n\t\t| \"stopReason\"\n\t\t| \"deferred\"\n\t\t| \"errorMessage\"\n\t\t| \"rawStopReason\"\n\t\t| \"timestamp\"\n\t>\n>;\ntype _AiToolResultMessageFieldsAccountedFor = Assert<\n\tExactKeys<\n\t\tToolResultMessage,\n\t\t\"role\" | \"toolCallId\" | \"toolName\" | \"content\" | \"details\" | \"usage\" | \"addedToolNames\" | \"isError\" | \"timestamp\"\n\t>\n>;\n\nexport interface AssistantTranscriptOptions {\n\tid: string;\n}\n\nexport interface UserTranscriptOptions {\n\tid: string;\n}\n\nexport interface ToolTranscriptOptions {\n\tid: string;\n\tcall: ToolCall;\n}\n\nfunction nonNegativeInteger(value: number | undefined): number | undefined {\n\tif (value === undefined || !Number.isFinite(value)) return undefined;\n\treturn Math.max(0, Math.floor(value));\n}\n\nfunction nonNegativeNumber(value: number): number {\n\treturn Number.isFinite(value) ? Math.max(0, value) : 0;\n}\n\nfunction identifier(value: string, label: string): string {\n\tif (typeof value !== \"string\" || value.length === 0) throw new TypeError(`${label} must be a non-empty string`);\n\treturn value;\n}\n\nfunction timestamp(value: number): number {\n\tif (!Number.isSafeInteger(value) || value < 0)\n\t\tthrow new TypeError(\"Protocol timestamps must be non-negative integers\");\n\treturn value;\n}\n\n/** Validate and copy a value from an execution boundary into the protocol's JSON-compatible subset. */\nexport function toProtocolJsonValue(value: unknown, seen = new Set<object>()): JsonValue {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) throw new TypeError(\"Protocol JSON numbers must be finite\");\n\t\treturn value;\n\t}\n\tif (typeof value !== \"object\") throw new TypeError(`Unsupported protocol JSON value: ${typeof value}`);\n\tif (seen.has(value)) throw new TypeError(\"Protocol JSON values must not contain circular references\");\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n\t\tthrow new TypeError(\"Protocol JSON objects must be plain objects\");\n\t}\n\tseen.add(value);\n\ttry {\n\t\tif (Array.isArray(value)) return Array.from(value, (entry) => toProtocolJsonValue(entry, seen));\n\t\tconst result: Record<string, JsonValue> = {};\n\t\tfor (const [key, entry] of Object.entries(value)) result[key] = toProtocolJsonValue(entry, seen);\n\t\treturn result;\n\t} finally {\n\t\tseen.delete(value);\n\t}\n}\n\n/** Lossily sanitize diagnostic tool details that must not affect execution semantics. */\nexport function sanitizeProtocolDetails(value: unknown, seen = new Set<object>()): JsonValue | undefined {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n\tif (typeof value === \"number\") return Number.isFinite(value) ? value : String(value);\n\tif (typeof value === \"bigint\") return value.toString();\n\tif (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") return undefined;\n\tif (value instanceof Date) return value.toISOString();\n\tif (typeof value !== \"object\") return String(value);\n\tif (seen.has(value)) return \"[Circular]\";\n\tseen.add(value);\n\ttry {\n\t\tif (Array.isArray(value)) return Array.from(value, (entry) => sanitizeProtocolDetails(entry, seen) ?? null);\n\t\tconst result: Record<string, JsonValue> = {};\n\t\tfor (const [key, entry] of Object.entries(value)) {\n\t\t\tconst normalized = sanitizeProtocolDetails(entry, seen);\n\t\t\tif (normalized !== undefined) result[key] = normalized;\n\t\t}\n\t\treturn result;\n\t} finally {\n\t\tseen.delete(value);\n\t}\n}\n\nexport function toProtocolUsage(usage: AiUsage | undefined): Usage | undefined {\n\tif (!usage) return undefined;\n\tconst reasoning = nonNegativeInteger(usage.reasoning);\n\tconst result = {\n\t\tinput: nonNegativeInteger(usage.input) ?? 0,\n\t\toutput: nonNegativeInteger(usage.output) ?? 0,\n\t\tcacheRead: nonNegativeInteger(usage.cacheRead) ?? 0,\n\t\tcacheWrite: nonNegativeInteger(usage.cacheWrite) ?? 0,\n\t\t...(reasoning === undefined ? {} : { reasoning }),\n\t\ttotalTokens: nonNegativeInteger(usage.totalTokens) ?? 0,\n\t\tcost: {\n\t\t\tinput: nonNegativeNumber(usage.cost.input),\n\t\t\toutput: nonNegativeNumber(usage.cost.output),\n\t\t\tcacheRead: nonNegativeNumber(usage.cost.cacheRead),\n\t\t\tcacheWrite: nonNegativeNumber(usage.cost.cacheWrite),\n\t\t\ttotal: nonNegativeNumber(usage.cost.total),\n\t\t},\n\t} satisfies Usage;\n\treturn result;\n}\n\nexport function toProtocolModelMetadata(model: Model<Api>, authenticated: boolean): ModelMetadata {\n\tconst result = {\n\t\tprovider: identifier(model.provider, \"Model provider\"),\n\t\tid: identifier(model.id, \"Model id\"),\n\t\tname: identifier(model.name, \"Model name\"),\n\t\tapi: identifier(model.api, \"Model API\"),\n\t\treasoning: model.reasoning,\n\t\tinput: [...model.input],\n\t\tcontextWindow: Math.max(1, Math.floor(model.contextWindow)),\n\t\tmaxTokens: Math.max(1, Math.floor(model.maxTokens)),\n\t\tcost: {\n\t\t\tinput: nonNegativeNumber(model.cost.input),\n\t\t\toutput: nonNegativeNumber(model.cost.output),\n\t\t\tcacheRead: nonNegativeNumber(model.cost.cacheRead),\n\t\t\tcacheWrite: nonNegativeNumber(model.cost.cacheWrite),\n\t\t},\n\t\tsupportedThinkingLevels: getSupportedThinkingLevels(model),\n\t\tauthenticated,\n\t} satisfies ModelMetadata;\n\treturn result;\n}\n\nfunction toProtocolUserContent(content: UserMessage[\"content\"]): UserTranscriptItem[\"content\"] {\n\tif (typeof content === \"string\") return [{ type: \"text\", text: content }];\n\treturn content.map((part) => {\n\t\tswitch (part.type) {\n\t\t\tcase \"text\":\n\t\t\t\treturn { type: \"text\", text: part.text };\n\t\t\tcase \"image\":\n\t\t\t\treturn { type: \"image\", data: part.data, mimeType: part.mimeType };\n\t\t\tdefault: {\n\t\t\t\tconst exhaustive: never = part;\n\t\t\t\treturn exhaustive;\n\t\t\t}\n\t\t}\n\t});\n}\n\nexport function toProtocolUserMessage(message: UserMessage, options: UserTranscriptOptions): UserTranscriptItem {\n\tconst result = {\n\t\tid: identifier(options.id, \"Transcript item id\"),\n\t\trole: \"user\",\n\t\tcontent: toProtocolUserContent(message.content),\n\t\ttimestamp: timestamp(message.timestamp),\n\t} satisfies UserTranscriptItem;\n\treturn result;\n}\n\nfunction toProtocolAssistantContent(message: AssistantMessage): AssistantTranscriptItem[\"content\"] {\n\treturn message.content.map((part) => {\n\t\tswitch (part.type) {\n\t\t\tcase \"text\":\n\t\t\t\treturn { type: \"text\", text: part.text };\n\t\t\tcase \"thinking\":\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\tthinking: part.thinking,\n\t\t\t\t\t...(part.redacted === undefined ? {} : { redacted: part.redacted }),\n\t\t\t\t};\n\t\t\tcase \"toolCall\":\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\ttoolCallId: identifier(part.id, \"Tool call id\"),\n\t\t\t\t\ttoolName: identifier(part.name, \"Tool call name\"),\n\t\t\t\t\tinput: toProtocolJsonValue(part.arguments),\n\t\t\t\t};\n\t\t\tdefault: {\n\t\t\t\tconst exhaustive: never = part;\n\t\t\t\treturn exhaustive;\n\t\t\t}\n\t\t}\n\t});\n}\n\nexport function toProtocolAssistantMessage(\n\tmessage: AssistantMessage,\n\toptions: AssistantTranscriptOptions,\n): AssistantTranscriptItem {\n\tconst usage = toProtocolUsage(message.usage);\n\tconst common = {\n\t\tid: identifier(options.id, \"Transcript item id\"),\n\t\trole: \"assistant\",\n\t\tcontent: toProtocolAssistantContent(message),\n\t\tmodel: {\n\t\t\tprovider: identifier(message.provider, \"Assistant provider\"),\n\t\t\tid: identifier(message.model, \"Assistant model\"),\n\t\t},\n\t\t...(message.responseModel === undefined\n\t\t\t? {}\n\t\t\t: { responseModel: identifier(message.responseModel, \"Assistant response model\") }),\n\t\t...(usage ? { usage } : {}),\n\t\ttimestamp: timestamp(message.timestamp),\n\t} as const;\n\tswitch (message.stopReason) {\n\t\tcase \"pending\":\n\t\t\treturn { ...common, status: \"streaming\" } satisfies AssistantTranscriptItem;\n\t\tcase \"stop\":\n\t\tcase \"length\":\n\t\tcase \"toolUse\":\n\t\t\treturn {\n\t\t\t\t...common,\n\t\t\t\tstatus: \"complete\",\n\t\t\t\tstopReason: message.stopReason,\n\t\t\t} satisfies AssistantTranscriptItem;\n\t\tcase \"deferred\":\n\t\t\tthrow new TypeError(\"Deferred assistant messages are not supported by protocol v1\");\n\t\tcase \"error\":\n\t\t\tif (message.errorMessage?.length === 0) {\n\t\t\t\tthrow new TypeError(\"Assistant error messages must not be empty\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t...common,\n\t\t\t\tstatus: \"error\",\n\t\t\t\tstopReason: \"error\",\n\t\t\t\t...(message.errorMessage === undefined ? {} : { errorMessage: message.errorMessage }),\n\t\t\t} satisfies AssistantTranscriptItem;\n\t\tcase \"aborted\":\n\t\t\treturn {\n\t\t\t\t...common,\n\t\t\t\tstatus: \"aborted\",\n\t\t\t\tstopReason: \"aborted\",\n\t\t\t\t...(message.errorMessage === undefined ? {} : { errorMessage: message.errorMessage }),\n\t\t\t} satisfies AssistantTranscriptItem;\n\t\tdefault: {\n\t\t\tconst exhaustive: never = message.stopReason;\n\t\t\treturn exhaustive;\n\t\t}\n\t}\n}\n\nfunction toProtocolToolContent(content: Array<AiTextContent | AiImageContent>): ToolTranscriptItem[\"content\"] {\n\treturn content.map((part) => {\n\t\tswitch (part.type) {\n\t\t\tcase \"text\":\n\t\t\t\treturn { type: \"text\", text: part.text };\n\t\t\tcase \"image\":\n\t\t\t\treturn { type: \"image\", data: part.data, mimeType: part.mimeType };\n\t\t\tdefault: {\n\t\t\t\tconst exhaustive: never = part;\n\t\t\t\treturn exhaustive;\n\t\t\t}\n\t\t}\n\t});\n}\n\nexport function toProtocolToolResultMessage(\n\tmessage: ToolResultMessage,\n\toptions: ToolTranscriptOptions,\n): ToolTranscriptItem {\n\tconst callId = identifier(options.call.id, \"Tool call id\");\n\tconst callName = identifier(options.call.name, \"Tool call name\");\n\tif (identifier(message.toolCallId, \"Tool result call id\") !== callId) {\n\t\tthrow new TypeError(`Tool result ${message.toolCallId} does not match tool call ${callId}`);\n\t}\n\tif (identifier(message.toolName, \"Tool result name\") !== callName) {\n\t\tthrow new TypeError(`Tool result ${message.toolName} does not match tool call ${callName}`);\n\t}\n\tconst details = sanitizeProtocolDetails(message.details);\n\tconst usage = toProtocolUsage(message.usage);\n\tconst common = {\n\t\tid: identifier(options.id, \"Transcript item id\"),\n\t\trole: \"tool\",\n\t\ttoolCallId: callId,\n\t\ttoolName: callName,\n\t\tinput: toProtocolJsonValue(options.call.arguments),\n\t\tcontent: toProtocolToolContent(message.content),\n\t\t...(details === undefined ? {} : { details }),\n\t\t...(usage ? { usage } : {}),\n\t\ttimestamp: timestamp(message.timestamp),\n\t} as const;\n\treturn message.isError\n\t\t? ({ ...common, status: \"error\", isError: true } satisfies ToolTranscriptItem)\n\t\t: ({ ...common, status: \"complete\", isError: false } satisfies ToolTranscriptItem);\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { type ByteConnection, type ByteConnectionHandler } from "./connection.ts";
2
+ import type { PiServerOptions, PiServerService } from "./types.ts";
3
+ export declare class PiServer {
4
+ readonly id: string;
5
+ private readonly listeners;
6
+ private readonly maxFrameLength;
7
+ private readonly handshakeTimeoutMs;
8
+ private readonly onError;
9
+ private readonly connections;
10
+ private readonly sessions;
11
+ private readonly snapshots;
12
+ private closing;
13
+ private closePromise?;
14
+ private startPromise?;
15
+ private started;
16
+ constructor(service: PiServerService, options: PiServerOptions);
17
+ get addresses(): readonly string[];
18
+ start(): Promise<this>;
19
+ private startInternal;
20
+ accept(connection: ByteConnection): ByteConnectionHandler;
21
+ close(): Promise<void>;
22
+ private closeInternal;
23
+ private receive;
24
+ private dispatchMessage;
25
+ private finishHandshake;
26
+ private handleRequest;
27
+ private transportClosed;
28
+ private disconnect;
29
+ private sendMessage;
30
+ private failProtocol;
31
+ private closeServerState;
32
+ private closeConnection;
33
+ private toProtocolError;
34
+ private reportError;
35
+ }
36
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAiBA,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAG1B,MAAM,iBAAiB,CAAC;AAUzB,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAMnE,qBAAa,QAAQ;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8B;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAyB7D;IAED,IAAI,SAAS,IAAI,SAAS,MAAM,EAAE,CAEjC;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrB;YAEa,aAAa;IAmB3B,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,qBAAqB,CAsCxD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;YAEa,aAAa;IAW3B,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,eAAe;YAoCT,eAAe;YA+Bf,aAAa;IAmB3B,OAAO,CAAC,eAAe;YAWT,UAAU;YAWV,WAAW;YAsBX,YAAY;YAeZ,gBAAgB;YAahB,eAAe;IAQ7B,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,WAAW;CAOnB","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"]}
package/dist/server.js ADDED
@@ -0,0 +1,365 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ClientMessageDecoder, DEFAULT_MAX_FRAME_LENGTH, encodeServerMessage, isSupportedProtocolVersion, PROTOCOL_VERSION, ProtocolValidationError, } from "@earendil-works/pi-protocol";
3
+ import { isTerminalConnection, } from "./connection.js";
4
+ import { INTERNAL_SERVER_ERROR_MESSAGE, InternalServerError, NOT_IMPLEMENTED_MESSAGE, PiServerError, } from "./errors.js";
5
+ import { LiveSessionManager } from "./sessions.js";
6
+ import { ServerSnapshotPublisher } from "./snapshots.js";
7
+ const DEFAULT_HANDSHAKE_TIMEOUT_MS = 5_000;
8
+ const MAX_UINT32 = 0xffff_ffff;
9
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
10
+ export class PiServer {
11
+ id;
12
+ listeners;
13
+ maxFrameLength;
14
+ handshakeTimeoutMs;
15
+ onError;
16
+ connections = new Set();
17
+ sessions;
18
+ snapshots;
19
+ closing = false;
20
+ closePromise;
21
+ startPromise;
22
+ started = false;
23
+ constructor(service, options) {
24
+ const resolved = resolveOptions(options);
25
+ this.listeners = options.listeners;
26
+ this.id = options.serverId ?? randomUUID();
27
+ this.maxFrameLength = resolved.maxFrameLength;
28
+ this.handshakeTimeoutMs = resolved.handshakeTimeoutMs;
29
+ this.onError = options.onError;
30
+ this.sessions = new LiveSessionManager({
31
+ service,
32
+ isClosing: () => this.closing,
33
+ sendMessage: (connection, message) => this.sendMessage(connection, message),
34
+ closeConnection: (connection) => this.closeConnection(connection),
35
+ disconnect: (connection) => this.disconnect(connection),
36
+ broadcastServerSnapshot: () => void this.snapshots.broadcast(),
37
+ reportError: (error) => this.reportError(error),
38
+ });
39
+ this.snapshots = new ServerSnapshotPublisher({
40
+ serverId: this.id,
41
+ service,
42
+ connections: this.connections,
43
+ isClosing: () => this.closing,
44
+ listSessions: () => this.sessions.listMetadata(),
45
+ sendMessage: (connection, message) => this.sendMessage(connection, message),
46
+ reportError: (error) => this.reportError(error),
47
+ });
48
+ }
49
+ get addresses() {
50
+ return this.listeners.flatMap((listener) => (listener.address === undefined ? [] : [listener.address]));
51
+ }
52
+ start() {
53
+ if (this.started)
54
+ return Promise.reject(new Error("PiServer is already started"));
55
+ if (this.startPromise)
56
+ return Promise.reject(new Error("PiServer is already starting"));
57
+ if (this.closing)
58
+ return Promise.reject(new Error("PiServer is closing or closed"));
59
+ this.startPromise = this.startInternal();
60
+ return this.startPromise;
61
+ }
62
+ async startInternal() {
63
+ const started = [];
64
+ try {
65
+ for (const listener of this.listeners) {
66
+ await listener.start((connection) => this.accept(connection));
67
+ started.push(listener);
68
+ }
69
+ this.started = true;
70
+ return this;
71
+ }
72
+ catch (error) {
73
+ this.closing = true;
74
+ await Promise.allSettled(started.map((listener) => listener.close()));
75
+ await this.closeServerState();
76
+ throw error;
77
+ }
78
+ finally {
79
+ this.startPromise = undefined;
80
+ }
81
+ }
82
+ accept(connection) {
83
+ if (this.closing) {
84
+ void this.closeConnection(connection);
85
+ return {
86
+ onData: () => { },
87
+ onClose: () => { },
88
+ onError: (error) => this.reportError(error),
89
+ };
90
+ }
91
+ let state;
92
+ const handshakeTimeout = setTimeout(() => {
93
+ void this.failProtocol(state, {
94
+ code: "invalid_request",
95
+ message: "Handshake timeout",
96
+ });
97
+ }, this.handshakeTimeoutMs);
98
+ handshakeTimeout.unref();
99
+ state = {
100
+ id: randomUUID(),
101
+ connection,
102
+ decoder: new ClientMessageDecoder({ maxFrameLength: this.maxFrameLength }),
103
+ sessionIds: new Set(),
104
+ stage: "awaitingHello",
105
+ disconnected: false,
106
+ handshakeComplete: false,
107
+ handshakeTimeout,
108
+ };
109
+ this.connections.add(state);
110
+ return {
111
+ onData: (chunk) => this.receive(state, chunk),
112
+ onClose: () => this.transportClosed(state),
113
+ onError: (error) => {
114
+ this.reportError(error);
115
+ void this.closeConnection(connection).then(() => this.disconnect(state));
116
+ },
117
+ };
118
+ }
119
+ async close() {
120
+ if (this.closePromise)
121
+ return this.closePromise;
122
+ this.closing = true;
123
+ this.closePromise = this.closeInternal();
124
+ return this.closePromise;
125
+ }
126
+ async closeInternal() {
127
+ const starting = this.startPromise;
128
+ if (starting)
129
+ await starting.catch(() => { });
130
+ try {
131
+ await Promise.all(this.listeners.map((listener) => listener.close()));
132
+ }
133
+ finally {
134
+ await this.closeServerState();
135
+ this.started = false;
136
+ }
137
+ }
138
+ receive(state, chunk) {
139
+ if (isTerminalConnection(state))
140
+ return;
141
+ let messages;
142
+ try {
143
+ messages = state.decoder.push(chunk);
144
+ }
145
+ catch (error) {
146
+ void this.failProtocol(state, this.toProtocolError(error));
147
+ return;
148
+ }
149
+ for (const message of messages) {
150
+ if (isTerminalConnection(state))
151
+ return;
152
+ this.dispatchMessage(state, message);
153
+ }
154
+ }
155
+ dispatchMessage(state, message) {
156
+ if (state.stage === "awaitingHello") {
157
+ if (message.type !== "hello") {
158
+ void this.failProtocol(state, {
159
+ code: "invalid_request",
160
+ message: "The first client message must be hello",
161
+ });
162
+ return;
163
+ }
164
+ state.stage = "handshaking";
165
+ state.handshake = this.finishHandshake(state, message).catch((error) => this.failProtocol(state, this.toProtocolError(error)));
166
+ return;
167
+ }
168
+ if (message.type === "hello") {
169
+ void this.failProtocol(state, {
170
+ code: "invalid_request",
171
+ message: "hello may only be sent as the first message",
172
+ });
173
+ return;
174
+ }
175
+ if (state.stage === "ready") {
176
+ void this.handleRequest(state, message);
177
+ return;
178
+ }
179
+ if (state.stage !== "handshaking")
180
+ return;
181
+ const handshake = state.handshake;
182
+ if (!handshake)
183
+ return;
184
+ void handshake.then(() => {
185
+ if (state.stage === "ready" && !state.disconnected)
186
+ void this.handleRequest(state, message);
187
+ });
188
+ }
189
+ async finishHandshake(state, hello) {
190
+ if (!isSupportedProtocolVersion(hello.version)) {
191
+ await this.failProtocol(state, {
192
+ code: "version",
193
+ message: `Unsupported protocol version ${hello.version}; expected ${PROTOCOL_VERSION}`,
194
+ });
195
+ return;
196
+ }
197
+ const snapshot = await this.snapshots.get();
198
+ if (this.closing || state.disconnected || state.stage !== "handshaking" || state.connection.closed)
199
+ return;
200
+ const sent = await this.sendMessage(state, {
201
+ type: "hello",
202
+ version: PROTOCOL_VERSION,
203
+ connectionId: state.id,
204
+ snapshot,
205
+ });
206
+ if (sent && !state.disconnected && state.stage === "handshaking") {
207
+ state.handshakeComplete = true;
208
+ state.stage = "ready";
209
+ clearTimeout(state.handshakeTimeout);
210
+ if (snapshot.revision !== this.snapshots.currentRevision) {
211
+ const current = await this.snapshots.get();
212
+ await this.sendMessage(state, {
213
+ type: "event",
214
+ event: { type: "server_snapshot", snapshot: current },
215
+ });
216
+ }
217
+ }
218
+ }
219
+ async handleRequest(state, envelope) {
220
+ try {
221
+ const result = await this.sessions.executeCommand(state, envelope.request);
222
+ await this.sendMessage(state, {
223
+ type: "response",
224
+ id: envelope.id,
225
+ ok: true,
226
+ result,
227
+ });
228
+ }
229
+ catch (error) {
230
+ await this.sendMessage(state, {
231
+ type: "response",
232
+ id: envelope.id,
233
+ ok: false,
234
+ error: this.toProtocolError(error),
235
+ });
236
+ }
237
+ }
238
+ transportClosed(connection) {
239
+ if (!connection.disconnected && connection.stage !== "closing") {
240
+ try {
241
+ connection.decoder.end();
242
+ }
243
+ catch (error) {
244
+ this.reportError(error);
245
+ }
246
+ }
247
+ void this.disconnect(connection);
248
+ }
249
+ async disconnect(connection) {
250
+ if (connection.disconnected)
251
+ return;
252
+ const handshakeComplete = connection.handshakeComplete;
253
+ connection.disconnected = true;
254
+ connection.stage = "closed";
255
+ clearTimeout(connection.handshakeTimeout);
256
+ this.connections.delete(connection);
257
+ await this.sessions.disconnect(connection);
258
+ if (!this.closing && handshakeComplete)
259
+ void this.snapshots.broadcast();
260
+ }
261
+ async sendMessage(connection, message) {
262
+ if (connection.disconnected || connection.connection.closed)
263
+ return false;
264
+ let frame;
265
+ try {
266
+ frame = encodeServerMessage(message, { maxFrameLength: this.maxFrameLength });
267
+ }
268
+ catch (error) {
269
+ this.reportError(error);
270
+ await this.closeConnection(connection.connection);
271
+ await this.disconnect(connection);
272
+ return false;
273
+ }
274
+ try {
275
+ await connection.connection.send(frame);
276
+ return true;
277
+ }
278
+ catch (error) {
279
+ this.reportError(error);
280
+ await this.closeConnection(connection.connection);
281
+ await this.disconnect(connection);
282
+ return false;
283
+ }
284
+ }
285
+ async failProtocol(connection, error) {
286
+ if (connection.disconnected || connection.stage === "closing" || connection.stage === "closed")
287
+ return;
288
+ connection.stage = "closing";
289
+ clearTimeout(connection.handshakeTimeout);
290
+ const message = { type: "hello_error", error };
291
+ let finalFrame;
292
+ try {
293
+ finalFrame = encodeServerMessage(message, { maxFrameLength: this.maxFrameLength });
294
+ }
295
+ catch (encodeError) {
296
+ this.reportError(encodeError);
297
+ }
298
+ await this.closeConnection(connection.connection, finalFrame);
299
+ await this.disconnect(connection);
300
+ }
301
+ async closeServerState() {
302
+ const connections = [...this.connections];
303
+ for (const connection of connections) {
304
+ connection.stage = "closing";
305
+ clearTimeout(connection.handshakeTimeout);
306
+ }
307
+ await Promise.all(connections.map((connection) => this.closeConnection(connection.connection)));
308
+ await Promise.all(connections.map((connection) => this.disconnect(connection)));
309
+ await this.sessions.close();
310
+ this.connections.clear();
311
+ }
312
+ async closeConnection(connection, finalChunk) {
313
+ try {
314
+ await connection.close(finalChunk);
315
+ }
316
+ catch (error) {
317
+ this.reportError(error);
318
+ }
319
+ }
320
+ toProtocolError(error) {
321
+ if (error instanceof InternalServerError) {
322
+ this.reportError(error.cause);
323
+ return { code: "internal_error", message: INTERNAL_SERVER_ERROR_MESSAGE };
324
+ }
325
+ if (error instanceof PiServerError) {
326
+ if (error.code === "not_implemented") {
327
+ return { code: "not_implemented", message: NOT_IMPLEMENTED_MESSAGE };
328
+ }
329
+ return error.details === undefined
330
+ ? { code: error.code, message: error.message }
331
+ : { code: error.code, message: error.message, details: error.details };
332
+ }
333
+ if (error instanceof ProtocolValidationError) {
334
+ return { code: "invalid_request", message: error.message };
335
+ }
336
+ this.reportError(error);
337
+ return { code: "internal_error", message: INTERNAL_SERVER_ERROR_MESSAGE };
338
+ }
339
+ reportError(error) {
340
+ try {
341
+ this.onError?.(error instanceof Error ? error : new Error(String(error)));
342
+ }
343
+ catch {
344
+ // Error observers cannot affect server state.
345
+ }
346
+ }
347
+ }
348
+ function resolveOptions(options) {
349
+ if (!Array.isArray(options.listeners))
350
+ throw new TypeError("PiServer listeners must be an array");
351
+ if (options.serverId === "")
352
+ throw new TypeError("PiServer serverId must not be empty");
353
+ const maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
354
+ if (!Number.isSafeInteger(maxFrameLength) || maxFrameLength <= 0 || maxFrameLength > MAX_UINT32) {
355
+ throw new TypeError(`PiServer maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
356
+ }
357
+ const handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
358
+ if (!Number.isSafeInteger(handshakeTimeoutMs) ||
359
+ handshakeTimeoutMs <= 0 ||
360
+ handshakeTimeoutMs > MAX_TIMER_DELAY_MS) {
361
+ throw new TypeError(`PiServer handshakeTimeoutMs must be an integer between 1 and ${MAX_TIMER_DELAY_MS}`);
362
+ }
363
+ return { maxFrameLength, handshakeTimeoutMs };
364
+ }
365
+ //# sourceMappingURL=server.js.map
@@ -0,0 +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"]}