@firtoz/socka 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/assets/banner.png +0 -0
- package/dist/SockaWebSocketSession-Bru8yFcK.d.ts +107 -0
- package/dist/bun/index.d.ts +38 -0
- package/dist/bun/index.js +121 -0
- package/dist/bun/index.js.map +1 -0
- package/dist/chunk-45D4T232.js +236 -0
- package/dist/chunk-45D4T232.js.map +1 -0
- package/dist/chunk-5WQTYLIC.js +46 -0
- package/dist/chunk-5WQTYLIC.js.map +1 -0
- package/dist/chunk-AM7PB26G.js +421 -0
- package/dist/chunk-AM7PB26G.js.map +1 -0
- package/dist/chunk-MZCQHJXY.js +158 -0
- package/dist/chunk-MZCQHJXY.js.map +1 -0
- package/dist/chunk-YMT4HAH7.js +20 -0
- package/dist/chunk-YMT4HAH7.js.map +1 -0
- package/dist/client/index.d.ts +119 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/do/index.d.ts +80 -0
- package/dist/do/index.js +110 -0
- package/dist/do/index.js.map +1 -0
- package/dist/hono/cloudflare-workers.d.ts +21 -0
- package/dist/hono/cloudflare-workers.js +68 -0
- package/dist/hono/cloudflare-workers.js.map +1 -0
- package/dist/hono/index.d.ts +30 -0
- package/dist/hono/index.js +74 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/react/index.d.ts +72 -0
- package/dist/react/index.js +126 -0
- package/dist/react/index.js.map +1 -0
- package/dist/server/index.d.ts +27 -0
- package/dist/server/index.js +63 -0
- package/dist/server/index.js.map +1 -0
- package/dist/socka-report-error-DzFI2Tr7.d.ts +206 -0
- package/docs/README.md +18 -0
- package/docs/client.md +85 -0
- package/docs/comparison.md +36 -0
- package/docs/durable-objects.md +74 -0
- package/docs/events.md +48 -0
- package/docs/getting-started.md +138 -0
- package/docs/lifecycle.md +31 -0
- package/docs/multi-room.md +31 -0
- package/docs/peers.md +85 -0
- package/docs/reference.md +123 -0
- package/docs/server.md +124 -0
- package/examples/minimal-socka.ts +31 -0
- package/package.json +148 -0
- package/roadmap.md +8 -0
- package/skills/socka/core-rpc/SKILL.md +36 -0
- package/skills/socka/do-session/SKILL.md +33 -0
- package/skills/socka/standard-schema/SKILL.md +26 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { pack, unpack } from 'msgpackr';
|
|
2
|
+
import { exhaustiveGuard } from '@firtoz/maybe-error';
|
|
3
|
+
|
|
4
|
+
// src/core/validate.ts
|
|
5
|
+
async function parseStandardSchema(schema, value) {
|
|
6
|
+
const result = await schema["~standard"].validate(value);
|
|
7
|
+
if (result.issues) {
|
|
8
|
+
const messages = result.issues.map((issue) => issue.message).join("; ");
|
|
9
|
+
throw new Error(messages || "Validation failed");
|
|
10
|
+
}
|
|
11
|
+
return result.value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/core/socka-error.ts
|
|
15
|
+
var SockaError = class _SockaError extends Error {
|
|
16
|
+
constructor(message, options) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "SockaError";
|
|
19
|
+
this.requestId = options?.requestId;
|
|
20
|
+
this.code = options?.code;
|
|
21
|
+
if (options?.cause !== void 0) {
|
|
22
|
+
Object.defineProperty(this, "cause", {
|
|
23
|
+
value: options.cause,
|
|
24
|
+
configurable: true,
|
|
25
|
+
enumerable: false,
|
|
26
|
+
writable: true
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
Object.setPrototypeOf(this, _SockaError.prototype);
|
|
30
|
+
}
|
|
31
|
+
/** Builds a {@link SockaError} from a standard RPC error envelope. */
|
|
32
|
+
static fromWire(msg) {
|
|
33
|
+
return new _SockaError(msg.error, { requestId: msg.id });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/core/envelope.ts
|
|
38
|
+
var SOCKA_WIRE_VERSION = 1;
|
|
39
|
+
var SockaWireError = class extends Error {
|
|
40
|
+
constructor() {
|
|
41
|
+
super(...arguments);
|
|
42
|
+
this.name = "SockaWireError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function isRecord(value) {
|
|
46
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
function decodeSockaWire(parsed) {
|
|
49
|
+
if (!isRecord(parsed)) {
|
|
50
|
+
throw new SockaWireError("socka: expected a JSON object");
|
|
51
|
+
}
|
|
52
|
+
if (parsed.socka === void 0) {
|
|
53
|
+
throw new SockaWireError('socka: missing "socka" discriminator');
|
|
54
|
+
}
|
|
55
|
+
if (parsed.v !== SOCKA_WIRE_VERSION) {
|
|
56
|
+
throw new SockaWireError("socka: unsupported wire version");
|
|
57
|
+
}
|
|
58
|
+
const socka = parsed.socka;
|
|
59
|
+
if (socka === "clientRequest" && typeof parsed.id === "string" && typeof parsed.rpc === "string" && isRecord(parsed.body)) {
|
|
60
|
+
return {
|
|
61
|
+
kind: "clientRequest",
|
|
62
|
+
frame: parsed
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (socka === "serverResponse" && typeof parsed.id === "string" && typeof parsed.rpc === "string") {
|
|
66
|
+
return {
|
|
67
|
+
kind: "serverResponse",
|
|
68
|
+
frame: parsed
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (socka === "serverError" && typeof parsed.id === "string" && typeof parsed.error === "string") {
|
|
72
|
+
return {
|
|
73
|
+
kind: "serverError",
|
|
74
|
+
frame: parsed
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (socka === "serverEvent" && typeof parsed.event === "string") {
|
|
78
|
+
return {
|
|
79
|
+
kind: "serverEvent",
|
|
80
|
+
frame: parsed
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw new SockaWireError(
|
|
84
|
+
`socka: unknown or invalid frame kind ${String(socka)}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function encodeClientRequest(id, rpc, body) {
|
|
88
|
+
return { socka: "clientRequest", v: SOCKA_WIRE_VERSION, id, rpc, body };
|
|
89
|
+
}
|
|
90
|
+
function encodeServerResponse(id, rpc, body) {
|
|
91
|
+
return { socka: "serverResponse", v: SOCKA_WIRE_VERSION, id, rpc, body };
|
|
92
|
+
}
|
|
93
|
+
function encodeServerError(id, error) {
|
|
94
|
+
return { socka: "serverError", v: SOCKA_WIRE_VERSION, id, error };
|
|
95
|
+
}
|
|
96
|
+
function encodeServerEvent(event, body) {
|
|
97
|
+
return { socka: "serverEvent", v: SOCKA_WIRE_VERSION, event, body };
|
|
98
|
+
}
|
|
99
|
+
function encodeSockaWire(frame, format, serializeJson = JSON.stringify) {
|
|
100
|
+
if (format === "json") {
|
|
101
|
+
return serializeJson(frame);
|
|
102
|
+
}
|
|
103
|
+
return pack(frame);
|
|
104
|
+
}
|
|
105
|
+
function parseWirePayload(data, format, deserializeJson = JSON.parse) {
|
|
106
|
+
if (format === "json") {
|
|
107
|
+
if (typeof data !== "string") {
|
|
108
|
+
throw new Error("socka: expected a JSON text frame");
|
|
109
|
+
}
|
|
110
|
+
return deserializeJson(data);
|
|
111
|
+
}
|
|
112
|
+
if (data instanceof Uint8Array) {
|
|
113
|
+
return unpack(data);
|
|
114
|
+
}
|
|
115
|
+
if (data instanceof ArrayBuffer) {
|
|
116
|
+
return unpack(new Uint8Array(data));
|
|
117
|
+
}
|
|
118
|
+
throw new Error("socka: expected an ArrayBuffer or Uint8Array msgpack frame");
|
|
119
|
+
}
|
|
120
|
+
function defaultReportError(event) {
|
|
121
|
+
switch (event.kind) {
|
|
122
|
+
case "clientEventListener":
|
|
123
|
+
console.error("socka: event listener error", event.error);
|
|
124
|
+
return;
|
|
125
|
+
case "clientEventValidation":
|
|
126
|
+
console.error("socka: event validation error", event.error);
|
|
127
|
+
return;
|
|
128
|
+
case "serverOnAttached":
|
|
129
|
+
console.error("socka: onAttached error:", event.error);
|
|
130
|
+
return;
|
|
131
|
+
case "serverInboundMessage":
|
|
132
|
+
if (event.adapter === "hono") {
|
|
133
|
+
console.error("socka: onMessage error:", event.error);
|
|
134
|
+
} else {
|
|
135
|
+
console.error("socka: message handler error:", event.error);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
case "serverHandleClose":
|
|
139
|
+
console.error("socka: handleClose error:", event.error);
|
|
140
|
+
return;
|
|
141
|
+
case "serverShutdown":
|
|
142
|
+
if (event.adapter === "hono") {
|
|
143
|
+
console.error("socka: onClose error:", event.error);
|
|
144
|
+
} else {
|
|
145
|
+
console.error("socka: shutdown error:", event.error);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
default:
|
|
149
|
+
exhaustiveGuard(event);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function reportSockaError(reportError, event) {
|
|
153
|
+
(reportError ?? defaultReportError)(event);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { SOCKA_WIRE_VERSION, SockaError, SockaWireError, decodeSockaWire, defaultReportError, encodeClientRequest, encodeServerError, encodeServerEvent, encodeServerResponse, encodeSockaWire, parseStandardSchema, parseWirePayload, reportSockaError };
|
|
157
|
+
//# sourceMappingURL=chunk-MZCQHJXY.js.map
|
|
158
|
+
//# sourceMappingURL=chunk-MZCQHJXY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/validate.ts","../src/core/socka-error.ts","../src/core/envelope.ts","../src/core/wire-codec.ts","../src/core/socka-report-error.ts"],"names":[],"mappings":"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","file":"chunk-MZCQHJXY.js","sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Validates {@link value} with a Standard Schema v1 schema and returns the output,\n * or throws an {@link Error} whose message aggregates issue messages.\n */\nexport async function parseStandardSchema<T>(\n\tschema: StandardSchemaV1<unknown, T>,\n\tvalue: unknown,\n): Promise<T> {\n\tconst result = await schema[\"~standard\"].validate(value);\n\tif (result.issues) {\n\t\tconst messages = result.issues.map((issue) => issue.message).join(\"; \");\n\t\tthrow new Error(messages || \"Validation failed\");\n\t}\n\treturn result.value;\n}\n","/**\n * Thrown on the server and surfaced on the client when the wire uses a shared\n * `{ type: \"error\", id, error }` envelope for correlated RPC failures.\n */\nexport class SockaError extends Error {\n\treadonly requestId?: string;\n\treadonly code?: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: { requestId?: string; code?: string; cause?: unknown },\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SockaError\";\n\t\tthis.requestId = options?.requestId;\n\t\tthis.code = options?.code;\n\t\tif (options?.cause !== undefined) {\n\t\t\tObject.defineProperty(this, \"cause\", {\n\t\t\t\tvalue: options.cause,\n\t\t\t\tconfigurable: true,\n\t\t\t\tenumerable: false,\n\t\t\t\twritable: true,\n\t\t\t});\n\t\t}\n\t\tObject.setPrototypeOf(this, SockaError.prototype);\n\t}\n\n\t/** Builds a {@link SockaError} from a standard RPC error envelope. */\n\tstatic fromWire(msg: { id: string; error: string }): SockaError {\n\t\treturn new SockaError(msg.error, { requestId: msg.id });\n\t}\n}\n","/**\n * Versioned socka wire framing. After JSON parse or msgpack unpack, every frame\n * must satisfy {@link decodeSockaWire}; procedure bodies are validated with Standard Schema on each side.\n */\n\nexport const SOCKA_WIRE_VERSION = 1 as const;\n\nexport class SockaWireError extends Error {\n\toverride readonly name = \"SockaWireError\";\n}\n\nexport type SockaClientRequestFrame = {\n\treadonly socka: \"clientRequest\";\n\treadonly v: typeof SOCKA_WIRE_VERSION;\n\treadonly id: string;\n\treadonly rpc: string;\n\treadonly body: Record<string, unknown>;\n};\n\nexport type SockaServerResponseFrame = {\n\treadonly socka: \"serverResponse\";\n\treadonly v: typeof SOCKA_WIRE_VERSION;\n\treadonly id: string;\n\treadonly rpc: string;\n\treadonly body: unknown;\n};\n\nexport type SockaServerErrorFrame = {\n\treadonly socka: \"serverError\";\n\treadonly v: typeof SOCKA_WIRE_VERSION;\n\treadonly id: string;\n\treadonly error: string;\n};\n\nexport type SockaServerEventFrame = {\n\treadonly socka: \"serverEvent\";\n\treadonly v: typeof SOCKA_WIRE_VERSION;\n\treadonly event: string;\n\treadonly body: unknown;\n};\n\nexport type SockaWireFrame =\n\t| SockaClientRequestFrame\n\t| SockaServerResponseFrame\n\t| SockaServerErrorFrame\n\t| SockaServerEventFrame;\n\nexport type DecodedSockaWire =\n\t| { readonly kind: \"clientRequest\"; readonly frame: SockaClientRequestFrame }\n\t| {\n\t\t\treadonly kind: \"serverResponse\";\n\t\t\treadonly frame: SockaServerResponseFrame;\n\t }\n\t| { readonly kind: \"serverError\"; readonly frame: SockaServerErrorFrame }\n\t| { readonly kind: \"serverEvent\"; readonly frame: SockaServerEventFrame };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Decodes a parsed wire object (from JSON or msgpack). Throws {@link SockaWireError}\n * if the payload is not a valid socka v1 frame.\n */\nexport function decodeSockaWire(parsed: unknown): DecodedSockaWire {\n\tif (!isRecord(parsed)) {\n\t\tthrow new SockaWireError(\"socka: expected a JSON object\");\n\t}\n\tif (parsed.socka === undefined) {\n\t\tthrow new SockaWireError('socka: missing \"socka\" discriminator');\n\t}\n\tif (parsed.v !== SOCKA_WIRE_VERSION) {\n\t\tthrow new SockaWireError(\"socka: unsupported wire version\");\n\t}\n\tconst socka = parsed.socka;\n\tif (\n\t\tsocka === \"clientRequest\" &&\n\t\ttypeof parsed.id === \"string\" &&\n\t\ttypeof parsed.rpc === \"string\" &&\n\t\tisRecord(parsed.body)\n\t) {\n\t\treturn {\n\t\t\tkind: \"clientRequest\",\n\t\t\tframe: parsed as SockaClientRequestFrame,\n\t\t};\n\t}\n\tif (\n\t\tsocka === \"serverResponse\" &&\n\t\ttypeof parsed.id === \"string\" &&\n\t\ttypeof parsed.rpc === \"string\"\n\t) {\n\t\treturn {\n\t\t\tkind: \"serverResponse\",\n\t\t\tframe: parsed as SockaServerResponseFrame,\n\t\t};\n\t}\n\tif (\n\t\tsocka === \"serverError\" &&\n\t\ttypeof parsed.id === \"string\" &&\n\t\ttypeof parsed.error === \"string\"\n\t) {\n\t\treturn {\n\t\t\tkind: \"serverError\",\n\t\t\tframe: parsed as SockaServerErrorFrame,\n\t\t};\n\t}\n\tif (socka === \"serverEvent\" && typeof parsed.event === \"string\") {\n\t\treturn {\n\t\t\tkind: \"serverEvent\",\n\t\t\tframe: parsed as SockaServerEventFrame,\n\t\t};\n\t}\n\tthrow new SockaWireError(\n\t\t`socka: unknown or invalid frame kind ${String(socka)}`,\n\t);\n}\n\n/** Builds a socka v1 client request frame. */\nexport function encodeClientRequest(\n\tid: string,\n\trpc: string,\n\tbody: Record<string, unknown>,\n): SockaClientRequestFrame {\n\treturn { socka: \"clientRequest\", v: SOCKA_WIRE_VERSION, id, rpc, body };\n}\n\n/** Builds a socka v1 server response frame. */\nexport function encodeServerResponse(\n\tid: string,\n\trpc: string,\n\tbody: unknown,\n): SockaServerResponseFrame {\n\treturn { socka: \"serverResponse\", v: SOCKA_WIRE_VERSION, id, rpc, body };\n}\n\n/** Builds a socka v1 server error frame. */\nexport function encodeServerError(\n\tid: string,\n\terror: string,\n): SockaServerErrorFrame {\n\treturn { socka: \"serverError\", v: SOCKA_WIRE_VERSION, id, error };\n}\n\n/** Builds a socka v1 server event frame. */\nexport function encodeServerEvent(\n\tevent: string,\n\tbody: unknown,\n): SockaServerEventFrame {\n\treturn { socka: \"serverEvent\", v: SOCKA_WIRE_VERSION, event, body };\n}\n","/**\n * JSON text frames vs msgpack binary frames for the same socka v1 object graph.\n * Matches {@link decodeSockaWire} after parse/unpack.\n */\n\nimport { pack, unpack } from \"msgpackr\";\nimport type { SockaWireFrame } from \"./envelope\";\n\n/** Wire encoding: UTF-8 JSON strings (default) or msgpack `ArrayBuffer` frames. */\nexport type SockaWireFormat = \"json\" | \"msgpack\";\n\n/**\n * Encodes a socka frame for the wire. JSON returns a string; msgpack returns bytes\n * suitable for `WebSocket.send`.\n */\nexport function encodeSockaWire(\n\tframe: SockaWireFrame,\n\tformat: SockaWireFormat,\n\tserializeJson: (value: unknown) => string = JSON.stringify,\n): string | Uint8Array {\n\tif (format === \"json\") {\n\t\treturn serializeJson(frame);\n\t}\n\treturn pack(frame) as Uint8Array;\n}\n\n/**\n * Decodes a wire payload to a plain object before {@link decodeSockaWire}.\n * Msgpack mode accepts `ArrayBuffer` or `Uint8Array` (e.g. from `msgpackr` / `WebSocket`).\n */\nexport function parseWirePayload(\n\tdata: string | ArrayBuffer | Uint8Array,\n\tformat: SockaWireFormat,\n\tdeserializeJson: (raw: string) => unknown = JSON.parse,\n): unknown {\n\tif (format === \"json\") {\n\t\tif (typeof data !== \"string\") {\n\t\t\tthrow new Error(\"socka: expected a JSON text frame\");\n\t\t}\n\t\treturn deserializeJson(data);\n\t}\n\tif (data instanceof Uint8Array) {\n\t\treturn unpack(data);\n\t}\n\tif (data instanceof ArrayBuffer) {\n\t\treturn unpack(new Uint8Array(data));\n\t}\n\tthrow new Error(\"socka: expected an ArrayBuffer or Uint8Array msgpack frame\");\n}\n","import { exhaustiveGuard } from \"@firtoz/maybe-error\";\n\n/**\n * Single discriminated union for optional `reportError` on session config and\n * `SockaSession` options: `kind` narrows context; `error` is what was thrown or rejected.\n */\nexport type SockaReportError =\n\t| { kind: \"clientEventListener\"; eventName: string; error: unknown }\n\t| { kind: \"clientEventValidation\"; eventName: string; error: unknown }\n\t| { kind: \"serverOnAttached\"; error: unknown }\n\t| {\n\t\t\tkind: \"serverInboundMessage\";\n\t\t\t/** `hono` uses the same log line as the Hono adapters; others use attach-style. */\n\t\t\tadapter: \"attach\" | \"hono\" | \"bun\";\n\t\t\terror: unknown;\n\t }\n\t| { kind: \"serverHandleClose\"; error: unknown }\n\t| {\n\t\t\tkind: \"serverShutdown\";\n\t\t\tadapter: \"attach\" | \"hono\";\n\t\t\terror: unknown;\n\t };\n\n/** Default `console.error` behavior; same messages as pre–`reportError` socka. */\nexport function defaultReportError(event: SockaReportError): void {\n\tswitch (event.kind) {\n\t\tcase \"clientEventListener\":\n\t\t\tconsole.error(\"socka: event listener error\", event.error);\n\t\t\treturn;\n\t\tcase \"clientEventValidation\":\n\t\t\tconsole.error(\"socka: event validation error\", event.error);\n\t\t\treturn;\n\t\tcase \"serverOnAttached\":\n\t\t\tconsole.error(\"socka: onAttached error:\", event.error);\n\t\t\treturn;\n\t\tcase \"serverInboundMessage\":\n\t\t\tif (event.adapter === \"hono\") {\n\t\t\t\tconsole.error(\"socka: onMessage error:\", event.error);\n\t\t\t} else {\n\t\t\t\tconsole.error(\"socka: message handler error:\", event.error);\n\t\t\t}\n\t\t\treturn;\n\t\tcase \"serverHandleClose\":\n\t\t\tconsole.error(\"socka: handleClose error:\", event.error);\n\t\t\treturn;\n\t\tcase \"serverShutdown\":\n\t\t\tif (event.adapter === \"hono\") {\n\t\t\t\tconsole.error(\"socka: onClose error:\", event.error);\n\t\t\t} else {\n\t\t\t\tconsole.error(\"socka: shutdown error:\", event.error);\n\t\t\t}\n\t\t\treturn;\n\t\tdefault:\n\t\t\texhaustiveGuard(event);\n\t}\n}\n\n/** Invokes the optional `reportError` callback when provided, otherwise `defaultReportError`. */\nexport function reportSockaError(\n\treportError: ((event: SockaReportError) => void) | undefined,\n\tevent: SockaReportError,\n): void {\n\t(reportError ?? defaultReportError)(event);\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/core/reserved-procedure-names.ts
|
|
2
|
+
var RESERVED_SOCKA_PROCEDURE_NAMES = [
|
|
3
|
+
// Promise-like (thenables)
|
|
4
|
+
"then",
|
|
5
|
+
"catch",
|
|
6
|
+
"finally",
|
|
7
|
+
// Instance shape
|
|
8
|
+
"constructor",
|
|
9
|
+
// Typical Object.prototype / debugging
|
|
10
|
+
"toString",
|
|
11
|
+
"valueOf",
|
|
12
|
+
"toLocaleString",
|
|
13
|
+
"hasOwnProperty",
|
|
14
|
+
"isPrototypeOf",
|
|
15
|
+
"propertyIsEnumerable"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export { RESERVED_SOCKA_PROCEDURE_NAMES };
|
|
19
|
+
//# sourceMappingURL=chunk-YMT4HAH7.js.map
|
|
20
|
+
//# sourceMappingURL=chunk-YMT4HAH7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/reserved-procedure-names.ts"],"names":[],"mappings":";AAMO,IAAM,8BAAA,GAAiC;AAAA;AAAA,EAE7C,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA;AAAA,EAEA,aAAA;AAAA;AAAA,EAEA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,eAAA;AAAA,EACA;AACD","file":"chunk-YMT4HAH7.js","sourcesContent":["/**\n * Call names that cannot be used in {@link defineSocka} `calls` because they would\n * make {@link SockaSession} `send` look Promise-like or clash with ordinary object\n * shape (`constructor`, `Object.prototype`). Call names live only under `send`, so\n * session fields like `client` / `close` need not be reserved.\n */\nexport const RESERVED_SOCKA_PROCEDURE_NAMES = [\n\t// Promise-like (thenables)\n\t\"then\",\n\t\"catch\",\n\t\"finally\",\n\t// Instance shape\n\t\"constructor\",\n\t// Typical Object.prototype / debugging\n\t\"toString\",\n\t\"valueOf\",\n\t\"toLocaleString\",\n\t\"hasOwnProperty\",\n\t\"isPrototypeOf\",\n\t\"propertyIsEnumerable\",\n] as const;\n\n/** Union of {@link RESERVED_SOCKA_PROCEDURE_NAMES}. */\nexport type ReservedSockaProcedureName =\n\t(typeof RESERVED_SOCKA_PROCEDURE_NAMES)[number];\n"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat, n as SockaServerResponseFrame, l as SockaServerErrorFrame, m as SockaServerEventFrame, I as InferSockaSend, g as InferSockaPushPayload, f as InferSockaPushHandlers, y as SockaReportError } from '../socka-report-error-DzFI2Tr7.js';
|
|
2
|
+
export { x as reportSockaError } from '../socka-report-error-DzFI2Tr7.js';
|
|
3
|
+
import '@standard-schema/spec';
|
|
4
|
+
|
|
5
|
+
interface SockaWebSocketClientOptions<TContract extends SockaContract<SockaContractConfig>> {
|
|
6
|
+
contract: TContract;
|
|
7
|
+
/** Default `"json"` (text frames). Use `"msgpack"` for binary `ArrayBuffer` frames. */
|
|
8
|
+
wireFormat?: SockaWireFormat;
|
|
9
|
+
url?: string;
|
|
10
|
+
webSocket?: WebSocket;
|
|
11
|
+
/**
|
|
12
|
+
* When `false`, the socket is not created until {@link SockaWebSocketClient.connect}
|
|
13
|
+
* (or the first operation that implicitly opens, e.g. {@link SockaSession} `send`).
|
|
14
|
+
* Default `true`.
|
|
15
|
+
*/
|
|
16
|
+
autoConnect?: boolean;
|
|
17
|
+
serializeJson?: (value: unknown) => string;
|
|
18
|
+
deserializeJson?: (raw: string) => unknown;
|
|
19
|
+
onOpen?: (event: Event) => void;
|
|
20
|
+
onClose?: (event: CloseEvent) => void;
|
|
21
|
+
onError?: (event: Event) => void;
|
|
22
|
+
onResponse?: (frame: SockaServerResponseFrame) => void;
|
|
23
|
+
onServerError?: (frame: SockaServerErrorFrame) => void;
|
|
24
|
+
onEvent?: (frame: SockaServerEventFrame) => void;
|
|
25
|
+
onValidationError?: (error: Error, rawMessage: unknown) => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Browser WebSocket client driven by a socka contract. Sends client request
|
|
29
|
+
* frames and dispatches decoded server frames to callbacks.
|
|
30
|
+
*/
|
|
31
|
+
declare class SockaWebSocketClient<TContract extends SockaContract<SockaContractConfig>> {
|
|
32
|
+
private ws;
|
|
33
|
+
private readonly opts;
|
|
34
|
+
private readonly wireFormat;
|
|
35
|
+
private readonly serializeJson;
|
|
36
|
+
private readonly deserializeJson;
|
|
37
|
+
private readonly onResponseCb?;
|
|
38
|
+
private readonly onServerErrorCb?;
|
|
39
|
+
private readonly onEventCb?;
|
|
40
|
+
private readonly onValidationError?;
|
|
41
|
+
readonly contract: TContract;
|
|
42
|
+
constructor(options: SockaWebSocketClientOptions<TContract>);
|
|
43
|
+
private createSocket;
|
|
44
|
+
private attachSocket;
|
|
45
|
+
private handleMessageEvent;
|
|
46
|
+
/**
|
|
47
|
+
* Creates the WebSocket (when {@link SockaWebSocketClientOptions.autoConnect}
|
|
48
|
+
* was `false`) and waits until the connection is open.
|
|
49
|
+
*/
|
|
50
|
+
connect(): Promise<void>;
|
|
51
|
+
sendRequest(id: string, rpc: string, body: Record<string, unknown>): void;
|
|
52
|
+
close(code?: number, reason?: string): void;
|
|
53
|
+
get readyState(): number;
|
|
54
|
+
get socket(): WebSocket;
|
|
55
|
+
waitForOpen(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type SockaSessionPushWaitOptions<TContract extends SockaContract<SockaContractConfig>, K extends keyof TContract["pushes"] & string> = {
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
predicate?: (payload: InferSockaPushPayload<TContract, K>) => boolean;
|
|
62
|
+
};
|
|
63
|
+
type SockaSessionSubscribeApi<TContract extends SockaContract<SockaContractConfig>> = {
|
|
64
|
+
on<K extends keyof TContract["pushes"] & string>(name: K, handler: (payload: InferSockaPushPayload<TContract, K>) => void | Promise<void>): void;
|
|
65
|
+
off<K extends keyof TContract["pushes"] & string>(name: K, handler: (payload: InferSockaPushPayload<TContract, K>) => void | Promise<void>): void;
|
|
66
|
+
once<K extends keyof TContract["pushes"] & string>(name: K, handler: (payload: InferSockaPushPayload<TContract, K>) => void | Promise<void>): void;
|
|
67
|
+
waitForPush<K extends keyof TContract["pushes"] & string>(name: K, options?: SockaSessionPushWaitOptions<TContract, K>): Promise<InferSockaPushPayload<TContract, K>>;
|
|
68
|
+
};
|
|
69
|
+
type SockaSessionOptions<TContract extends SockaContract<SockaContractConfig>> = Omit<SockaWebSocketClientOptions<TContract>, "onResponse" | "onServerError" | "onEvent"> & {
|
|
70
|
+
onOpen?: (event: Event) => void;
|
|
71
|
+
onClose?: (event: CloseEvent) => void;
|
|
72
|
+
onError?: (event: Event) => void;
|
|
73
|
+
pushHandlers?: Partial<InferSockaPushHandlers<TContract>>;
|
|
74
|
+
/**
|
|
75
|
+
* Optional sink for client-side push pipeline failures (listener throws,
|
|
76
|
+
* push payload validation). Defaults to `console.error`; see
|
|
77
|
+
* `SockaReportError` in `@firtoz/socka/core`.
|
|
78
|
+
*/
|
|
79
|
+
reportError?: (event: SockaReportError) => void;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Browser WebSocket session: **`session.send`** for contract calls, **`session.subscribe`**
|
|
83
|
+
* for server pushes, **`session.client`** for low-level wire access.
|
|
84
|
+
*/
|
|
85
|
+
declare class SockaSessionBase<TContract extends SockaContract<SockaContractConfig>> {
|
|
86
|
+
readonly client: SockaWebSocketClient<TContract>;
|
|
87
|
+
readonly send: InferSockaSend<TContract>;
|
|
88
|
+
readonly subscribe: SockaSessionSubscribeApi<TContract>;
|
|
89
|
+
private readonly pending;
|
|
90
|
+
private idSeq;
|
|
91
|
+
private readonly pushListeners;
|
|
92
|
+
private readonly reportError?;
|
|
93
|
+
constructor(options: SockaSessionOptions<TContract>);
|
|
94
|
+
private createSubscribeApi;
|
|
95
|
+
private addPushListener;
|
|
96
|
+
private removePushListener;
|
|
97
|
+
private waitForPushImpl;
|
|
98
|
+
private buildSendMethods;
|
|
99
|
+
private call;
|
|
100
|
+
private handleResponse;
|
|
101
|
+
private handleServerError;
|
|
102
|
+
private handleEvent;
|
|
103
|
+
private dispatchValidatedPush;
|
|
104
|
+
private nextId;
|
|
105
|
+
rejectAllPending(reason: Error): void;
|
|
106
|
+
close(code?: number, reason?: string): void;
|
|
107
|
+
/** Opens the WebSocket when using {@link SockaWebSocketClientOptions.autoConnect} `false`. */
|
|
108
|
+
connect(): Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
interface SockaSessionConstructor {
|
|
111
|
+
new <TContract extends SockaContract<SockaContractConfig>>(options: SockaSessionOptions<TContract>): SockaSession<TContract>;
|
|
112
|
+
}
|
|
113
|
+
type SockaSession<TContract extends SockaContract<SockaContractConfig>> = SockaSessionBase<TContract>;
|
|
114
|
+
/**
|
|
115
|
+
* WebSocket session: **`session.send`** for contract calls, **`session.subscribe`** for server pushes.
|
|
116
|
+
*/
|
|
117
|
+
declare const SockaSession: SockaSessionConstructor;
|
|
118
|
+
|
|
119
|
+
export { SockaReportError, SockaSession, type SockaSessionConstructor, type SockaSessionOptions, type SockaSessionPushWaitOptions, type SockaSessionSubscribeApi, SockaWebSocketClient, type SockaWebSocketClientOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { D as DecodedSockaWire, e as InferSockaHandlers, f as InferSockaPushHandlers, g as InferSockaPushPayload, I as InferSockaSend, R as RESERVED_SOCKA_PROCEDURE_NAMES, h as ReservedSockaProcedureName, i as SOCKA_WIRE_VERSION, k as SockaClientRequestFrame, S as SockaContract, a as SockaContractConfig, c as SockaProcedureDef, y as SockaReportError, l as SockaServerErrorFrame, m as SockaServerEventFrame, n as SockaServerResponseFrame, j as SockaWireError, b as SockaWireFormat, o as SockaWireFrame, V as ValidateSockaCallKeys, p as decodeSockaWire, w as defaultReportError, d as defineSocka, q as encodeClientRequest, s as encodeServerError, t as encodeServerEvent, r as encodeServerResponse, u as encodeSockaWire, v as parseWirePayload, x as reportSockaError } from '../socka-report-error-DzFI2Tr7.js';
|
|
2
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validates {@link value} with a Standard Schema v1 schema and returns the output,
|
|
6
|
+
* or throws an {@link Error} whose message aggregates issue messages.
|
|
7
|
+
*/
|
|
8
|
+
declare function parseStandardSchema<T>(schema: StandardSchemaV1<unknown, T>, value: unknown): Promise<T>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Thrown on the server and surfaced on the client when the wire uses a shared
|
|
12
|
+
* `{ type: "error", id, error }` envelope for correlated RPC failures.
|
|
13
|
+
*/
|
|
14
|
+
declare class SockaError extends Error {
|
|
15
|
+
readonly requestId?: string;
|
|
16
|
+
readonly code?: string;
|
|
17
|
+
constructor(message: string, options?: {
|
|
18
|
+
requestId?: string;
|
|
19
|
+
code?: string;
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
});
|
|
22
|
+
/** Builds a {@link SockaError} from a standard RPC error envelope. */
|
|
23
|
+
static fromWire(msg: {
|
|
24
|
+
id: string;
|
|
25
|
+
error: string;
|
|
26
|
+
}): SockaError;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { SockaError, parseStandardSchema };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { RESERVED_SOCKA_PROCEDURE_NAMES } from '../chunk-YMT4HAH7.js';
|
|
2
|
+
export { SOCKA_WIRE_VERSION, SockaError, SockaWireError, decodeSockaWire, defaultReportError, encodeClientRequest, encodeServerError, encodeServerEvent, encodeServerResponse, encodeSockaWire, parseStandardSchema, parseWirePayload, reportSockaError } from '../chunk-MZCQHJXY.js';
|
|
3
|
+
|
|
4
|
+
// src/core/contract.ts
|
|
5
|
+
function defineSocka(config) {
|
|
6
|
+
return {
|
|
7
|
+
calls: config.calls,
|
|
8
|
+
pushes: config.pushes ?? {}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { defineSocka };
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/contract.ts"],"names":[],"mappings":";;;;AAiHO,SAAS,YACf,MAAA,EACmB;AACnB,EAAA,OAAO;AAAA,IACN,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,MAAA,EAAS,MAAA,CAAO,MAAA,IAAU;AAAC,GAC5B;AACD","file":"index.js","sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ReservedSockaProcedureName } from \"./reserved-procedure-names\";\n\n/**\n * Defines one client-initiated call: an optional input schema and a required output schema.\n * Both must be Standard Schema v1 compliant (Zod v4, Valibot, ArkType, etc.).\n */\nexport type SockaProcedureDef = {\n\treadonly input?: StandardSchemaV1;\n\treadonly output: StandardSchemaV1;\n};\n\n/** Configuration object accepted by {@link defineSocka}. */\nexport type SockaContractConfig = {\n\treadonly calls: Record<string, SockaProcedureDef>;\n\treadonly pushes?: Record<string, StandardSchemaV1>;\n};\n\n/**\n * When call keys are a **narrow** object type, rejects keys in\n * {@link ReservedSockaProcedureName} (thenable / `Object.prototype` hazards on\n * `session.send`). Wide `Record<string, SockaProcedureDef>` is unchanged so\n * dynamic maps still typecheck; use runtime validation (see {@link SockaSession}\n * `send`).\n */\nexport type ValidateSockaCallKeys<P extends Record<string, SockaProcedureDef>> =\n\tstring extends keyof P\n\t\t? P\n\t\t: keyof P & ReservedSockaProcedureName extends never\n\t\t\t? P\n\t\t\t: never;\n\n/** Runtime contract returned by {@link defineSocka}, preserving full generic types. */\nexport type SockaContract<T extends SockaContractConfig = SockaContractConfig> =\n\t{\n\t\treadonly calls: T[\"calls\"];\n\t\treadonly pushes: T extends { pushes: Record<string, StandardSchemaV1> }\n\t\t\t? T[\"pushes\"]\n\t\t\t: Record<string, never>;\n\t};\n\ntype CallFn<P extends SockaProcedureDef> = P extends {\n\tinput: infer I extends StandardSchemaV1;\n}\n\t? (\n\t\t\tinput: StandardSchemaV1.InferInput<I>,\n\t\t) => Promise<StandardSchemaV1.InferOutput<P[\"output\"]>>\n\t: () => Promise<StandardSchemaV1.InferOutput<P[\"output\"]>>;\n\n/**\n * Infers the typed `session.send.*` method map for a contract.\n */\nexport type InferSockaSend<C extends SockaContract> = {\n\t[K in keyof C[\"calls\"]]: CallFn<C[\"calls\"][K]>;\n};\n\ntype HandlerOut<P extends SockaProcedureDef> =\n\t| StandardSchemaV1.InferOutput<P[\"output\"]>\n\t| Promise<StandardSchemaV1.InferOutput<P[\"output\"]>>;\n\ntype HandlerFn<P extends SockaProcedureDef, TSession> = P extends {\n\tinput: infer I extends StandardSchemaV1;\n}\n\t? (input: StandardSchemaV1.InferInput<I>, session: TSession) => HandlerOut<P>\n\t: (session: TSession) => HandlerOut<P>;\n\n/**\n * Infers the typed server handler map for a contract. Handlers with an input\n * schema take `(input, session)`; calls without input take `(session)` only.\n * Each handler returns the output that will be validated before sending.\n */\nexport type InferSockaHandlers<C extends SockaContract, TSession> = {\n\t[K in keyof C[\"calls\"]]: HandlerFn<C[\"calls\"][K], TSession>;\n};\n\ntype InferPushPayload<S extends StandardSchemaV1> =\n\tStandardSchemaV1.InferOutput<S>;\n\n/**\n * Payload type for a contract push (output of the push's Standard Schema).\n */\nexport type InferSockaPushPayload<\n\tC extends SockaContract<SockaContractConfig>,\n\tK extends keyof C[\"pushes\"],\n> = C[\"pushes\"][K] extends StandardSchemaV1\n\t? InferPushPayload<C[\"pushes\"][K]>\n\t: never;\n\n/**\n * Infers the typed push subscription handler map for a contract's `pushes`.\n */\nexport type InferSockaPushHandlers<C extends SockaContract> = {\n\t[K in keyof C[\"pushes\"]]: C[\"pushes\"][K] extends StandardSchemaV1\n\t\t? (payload: InferPushPayload<C[\"pushes\"][K]>) => void | Promise<void>\n\t\t: never;\n};\n\n/**\n * Creates a socka contract from call and push definitions. Pass Zod, Valibot,\n * ArkType, or any Standard Schema v1 schemas directly — no adapters needed.\n *\n * ```ts\n * export const myContract = defineSocka({\n * calls: {\n * list: { output: z.array(itemSchema) },\n * insert: { input: z.object({ item: itemSchema }), output: z.void() },\n * },\n * });\n * ```\n *\n * Call names must not be {@link ReservedSockaProcedureName} — they would make\n * `session.send` thenable or unsafe as a plain method bag.\n */\nexport function defineSocka<const T extends SockaContractConfig>(\n\tconfig: T & { calls: ValidateSockaCallKeys<T[\"calls\"]> },\n): SockaContract<T> {\n\treturn {\n\t\tcalls: config.calls,\n\t\tpushes: (config.pushes ?? {}) as SockaContract<T>[\"pushes\"],\n\t};\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { BaseSession, SessionEnv, BaseWebSocketDO } from '@firtoz/websocket-do';
|
|
3
|
+
import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat, e as InferSockaHandlers, y as SockaReportError, g as InferSockaPushPayload } from '../socka-report-error-DzFI2Tr7.js';
|
|
4
|
+
import { e as SockaPushSession } from '../SockaWebSocketSession-Bru8yFcK.js';
|
|
5
|
+
import '@standard-schema/spec';
|
|
6
|
+
|
|
7
|
+
/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */
|
|
8
|
+
type EmptySockaSessionData = Record<string, never>;
|
|
9
|
+
type SockaDoOuterSession<TContract extends SockaContract<SockaContractConfig>, TData, TEnv extends object> = SockaDoSession<TContract, TData, TEnv>;
|
|
10
|
+
type SockaDoSessionCreateData<TData, TEnv extends object> = [TData] extends [
|
|
11
|
+
EmptySockaSessionData
|
|
12
|
+
] ? {
|
|
13
|
+
createData?: (ctx: Context<{
|
|
14
|
+
Bindings: TEnv;
|
|
15
|
+
}>) => TData;
|
|
16
|
+
} : {
|
|
17
|
+
createData: (ctx: Context<{
|
|
18
|
+
Bindings: TEnv;
|
|
19
|
+
}>) => TData;
|
|
20
|
+
};
|
|
21
|
+
type SockaDoSessionConfig<TContract extends SockaContract<SockaContractConfig>, TData, TEnv extends object> = {
|
|
22
|
+
contract: TContract;
|
|
23
|
+
/** Default `"json"`. Use `"msgpack"` for binary frames (must match client). */
|
|
24
|
+
wireFormat?: SockaWireFormat;
|
|
25
|
+
handlers: InferSockaHandlers<TContract, SockaDoOuterSession<TContract, TData, TEnv>>;
|
|
26
|
+
handleClose: (session: SockaDoOuterSession<TContract, TData, TEnv>) => Promise<void>;
|
|
27
|
+
onHandlerError?: (error: unknown, rpcName: string, input: unknown, session: SockaDoOuterSession<TContract, TData, TEnv>) => void;
|
|
28
|
+
onValidationError?: (error: unknown, originalMessage: unknown) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Optional sink for non-RPC failures (e.g. `onAttached`). Defaults to
|
|
31
|
+
* `console.error`; see `SockaReportError` in `@firtoz/socka/core`.
|
|
32
|
+
*/
|
|
33
|
+
reportError?: (event: SockaReportError) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Called after this session is registered in the DO `sessions` map (next
|
|
36
|
+
* microtask). Use for join broadcasts and other logic that must run only
|
|
37
|
+
* when peers can see this connection.
|
|
38
|
+
*/
|
|
39
|
+
onAttached?: (session: SockaDoOuterSession<TContract, TData, TEnv>) => void | Promise<void>;
|
|
40
|
+
serializeJson?: (value: unknown) => string;
|
|
41
|
+
deserializeJson?: (raw: string) => unknown;
|
|
42
|
+
} & SockaDoSessionCreateData<TData, TEnv>;
|
|
43
|
+
/**
|
|
44
|
+
* Durable Object WebSocket session driven by a socka contract.
|
|
45
|
+
* Dispatches client requests to typed handler functions, validates
|
|
46
|
+
* input/output via Standard Schema, and auto-sends response/error frames.
|
|
47
|
+
*/
|
|
48
|
+
declare class SockaDoSession<TContract extends SockaContract<SockaContractConfig>, TData = EmptySockaSessionData, TEnv extends object = Cloudflare.Env> extends BaseSession<TData, unknown, unknown, TEnv> implements SockaPushSession<TContract> {
|
|
49
|
+
private socka;
|
|
50
|
+
constructor(websocket: WebSocket, sessions: Map<WebSocket, SockaDoSession<TContract, TData, TEnv>>, config: SockaDoSessionConfig<TContract, TData, TEnv>);
|
|
51
|
+
handleRawMessage(rawMessage: string): Promise<void>;
|
|
52
|
+
emitWireEvent(event: string, body: unknown): void;
|
|
53
|
+
emitPush<K extends keyof TContract["pushes"] & string>(name: K, body: InferSockaPushPayload<TContract, K>): Promise<void>;
|
|
54
|
+
broadcastPush<K extends keyof TContract["pushes"] & string>(name: K, body: InferSockaPushPayload<TContract, K>, excludeSelf?: boolean): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type SockaWebSocketDOOptions<TEnv extends object, TSession extends SockaDoSession<any, any, TEnv>> = {
|
|
58
|
+
createSockaSession: (ctx: Context<{
|
|
59
|
+
Bindings: TEnv;
|
|
60
|
+
}> | undefined, websocket: WebSocket) => TSession | Promise<TSession>;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Durable Object base class for WebSocket apps using {@link SockaDoSession}
|
|
64
|
+
* (Standard Schema contract-driven).
|
|
65
|
+
*/
|
|
66
|
+
declare abstract class SockaWebSocketDO<TSession extends SockaDoSession<any, any, any> = SockaDoSession<SockaContract<SockaContractConfig>, any, any>, TEnv extends SessionEnv<TSession> = SessionEnv<TSession>> extends BaseWebSocketDO<TSession, TEnv> {
|
|
67
|
+
constructor(ctx: DurableObjectState, env: TEnv, options: SockaWebSocketDOOptions<TEnv, TSession>);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Shared error reply shape for correlate-by-id RPC when the handler throws.
|
|
72
|
+
* Narrow at the call site to your server message union if needed.
|
|
73
|
+
*/
|
|
74
|
+
declare function toErrorReply(id: string, error: string): {
|
|
75
|
+
type: "error";
|
|
76
|
+
id: string;
|
|
77
|
+
error: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export { SockaDoSession, type SockaDoSessionConfig, SockaWebSocketDO, type SockaWebSocketDOOptions, toErrorReply };
|
package/dist/do/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { SockaWebSocketSession } from '../chunk-45D4T232.js';
|
|
2
|
+
import { reportSockaError } from '../chunk-MZCQHJXY.js';
|
|
3
|
+
import { BaseSession, BaseWebSocketDO } from '@firtoz/websocket-do';
|
|
4
|
+
|
|
5
|
+
function runSockaDoSessionOnAttached(config, session) {
|
|
6
|
+
const cb = config.onAttached;
|
|
7
|
+
if (!cb) return;
|
|
8
|
+
try {
|
|
9
|
+
const result = cb(session);
|
|
10
|
+
void Promise.resolve(result).catch((error) => {
|
|
11
|
+
reportSockaError(config.reportError, {
|
|
12
|
+
kind: "serverOnAttached",
|
|
13
|
+
error
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
} catch (error) {
|
|
17
|
+
reportSockaError(config.reportError, { kind: "serverOnAttached", error });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function wrapHandlersForInnerSockaEngine(contract, userHandlers, outer) {
|
|
21
|
+
const calls = contract.calls;
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const key of Object.keys(calls)) {
|
|
24
|
+
const proc = calls[key];
|
|
25
|
+
const userFn = userHandlers[key];
|
|
26
|
+
if (proc.input) {
|
|
27
|
+
out[key] = (input, _inner) => userFn(input, outer);
|
|
28
|
+
} else {
|
|
29
|
+
out[key] = (_inner) => userFn(outer);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
var SockaDoSession = class extends BaseSession {
|
|
35
|
+
constructor(websocket, sessions, config) {
|
|
36
|
+
const wireFormat = config.wireFormat ?? "json";
|
|
37
|
+
super(
|
|
38
|
+
websocket,
|
|
39
|
+
sessions,
|
|
40
|
+
{
|
|
41
|
+
createData: config.createData ?? ((_ctx) => ({})),
|
|
42
|
+
handleMessage: async () => {
|
|
43
|
+
},
|
|
44
|
+
handleBufferMessage: async (message) => {
|
|
45
|
+
await this.socka.handleBinaryMessage(message);
|
|
46
|
+
},
|
|
47
|
+
handleClose: async (baseSession) => {
|
|
48
|
+
await config.handleClose(
|
|
49
|
+
baseSession
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
const sockaConfig = {
|
|
55
|
+
contract: config.contract,
|
|
56
|
+
wireFormat,
|
|
57
|
+
handlers: wrapHandlersForInnerSockaEngine(
|
|
58
|
+
config.contract,
|
|
59
|
+
config.handlers,
|
|
60
|
+
this
|
|
61
|
+
),
|
|
62
|
+
handleClose: async () => {
|
|
63
|
+
},
|
|
64
|
+
onHandlerError: config.onHandlerError ? (err, rpcName, input, _inner) => {
|
|
65
|
+
config.onHandlerError?.(err, rpcName, input, this);
|
|
66
|
+
} : void 0,
|
|
67
|
+
onValidationError: config.onValidationError,
|
|
68
|
+
serializeJson: config.serializeJson,
|
|
69
|
+
deserializeJson: config.deserializeJson
|
|
70
|
+
};
|
|
71
|
+
this.socka = new SockaWebSocketSession(
|
|
72
|
+
websocket,
|
|
73
|
+
sessions,
|
|
74
|
+
sockaConfig
|
|
75
|
+
);
|
|
76
|
+
queueMicrotask(() => {
|
|
77
|
+
queueMicrotask(() => {
|
|
78
|
+
runSockaDoSessionOnAttached(config, this);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async handleRawMessage(rawMessage) {
|
|
83
|
+
return this.socka.handleRawMessage(rawMessage);
|
|
84
|
+
}
|
|
85
|
+
emitWireEvent(event, body) {
|
|
86
|
+
this.socka.emitWireEvent(event, body);
|
|
87
|
+
}
|
|
88
|
+
emitPush(name, body) {
|
|
89
|
+
return this.socka.emitPush(name, body);
|
|
90
|
+
}
|
|
91
|
+
broadcastPush(name, body, excludeSelf = false) {
|
|
92
|
+
return this.socka.broadcastPush(name, body, excludeSelf);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var SockaWebSocketDO = class extends BaseWebSocketDO {
|
|
96
|
+
constructor(ctx, env, options) {
|
|
97
|
+
super(ctx, env, {
|
|
98
|
+
createSession: (sessionCtx, websocket) => options.createSockaSession(sessionCtx, websocket)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/do/dispatch.ts
|
|
104
|
+
function toErrorReply(id, error) {
|
|
105
|
+
return { type: "error", id, error };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { SockaDoSession, SockaWebSocketDO, toErrorReply };
|
|
109
|
+
//# sourceMappingURL=index.js.map
|
|
110
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/do/SockaDoSession.ts","../../src/do/SockaWebSocketDO.ts","../../src/do/dispatch.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Context } from \"hono\";\nimport { BaseSession } from \"@firtoz/websocket-do\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContract,\n\tSockaContractConfig,\n\tInferSockaHandlers,\n} from \"../core/contract\";\nimport {\n\tSockaWebSocketSession,\n\ttype SockaPushSession,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaReportError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\n\n/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */\ntype EmptySockaSessionData = Record<string, never>;\n\ntype SockaDoOuterSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n> = import(\"./SockaDoSession\").SockaDoSession<TContract, TData, TEnv>;\n\ntype SockaDoSessionCreateData<TData, TEnv extends object> = [TData] extends [\n\tEmptySockaSessionData,\n]\n\t? {\n\t\t\tcreateData?: (ctx: Context<{ Bindings: TEnv }>) => TData;\n\t\t}\n\t: {\n\t\t\tcreateData: (ctx: Context<{ Bindings: TEnv }>) => TData;\n\t\t};\n\nexport type SockaDoSessionConfig<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n> = {\n\tcontract: TContract;\n\t/** Default `\"json\"`. Use `\"msgpack\"` for binary frames (must match client). */\n\twireFormat?: SockaWireFormat;\n\thandlers: InferSockaHandlers<\n\t\tTContract,\n\t\tSockaDoOuterSession<TContract, TData, TEnv>\n\t>;\n\thandleClose: (\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => Promise<void>;\n\tonHandlerError?: (\n\t\terror: unknown,\n\t\trpcName: string,\n\t\tinput: unknown,\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => void;\n\tonValidationError?: (\n\t\terror: unknown,\n\t\toriginalMessage: unknown,\n\t) => Promise<void>;\n\t/**\n\t * Optional sink for non-RPC failures (e.g. `onAttached`). Defaults to\n\t * `console.error`; see `SockaReportError` in `@firtoz/socka/core`.\n\t */\n\treportError?: (event: SockaReportError) => void;\n\t/**\n\t * Called after this session is registered in the DO `sessions` map (next\n\t * microtask). Use for join broadcasts and other logic that must run only\n\t * when peers can see this connection.\n\t */\n\tonAttached?: (\n\t\tsession: SockaDoOuterSession<TContract, TData, TEnv>,\n\t) => void | Promise<void>;\n\tserializeJson?: (value: unknown) => string;\n\tdeserializeJson?: (raw: string) => unknown;\n} & SockaDoSessionCreateData<TData, TEnv>;\n\nfunction runSockaDoSessionOnAttached<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n>(\n\tconfig: SockaDoSessionConfig<TContract, TData, TEnv>,\n\tsession: SockaDoSession<TContract, TData, TEnv>,\n): void {\n\tconst cb = config.onAttached;\n\tif (!cb) return;\n\ttry {\n\t\tconst result = cb(session);\n\t\tvoid Promise.resolve(result).catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverOnAttached\",\n\t\t\t\terror,\n\t\t\t});\n\t\t});\n\t} catch (error) {\n\t\treportSockaError(config.reportError, { kind: \"serverOnAttached\", error });\n\t}\n}\n\nfunction wrapHandlersForInnerSockaEngine<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n\tTEnv extends object,\n>(\n\tcontract: TContract,\n\tuserHandlers: InferSockaHandlers<\n\t\tTContract,\n\t\tSockaDoSession<TContract, TData, TEnv>\n\t>,\n\touter: SockaDoSession<TContract, TData, TEnv>,\n): InferSockaHandlers<\n\tTContract,\n\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n> {\n\tconst calls = contract.calls;\n\tconst out: Record<\n\t\tstring,\n\t\t| ((\n\t\t\t\tinput: unknown,\n\t\t\t\tinner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t ) => unknown | Promise<unknown>)\n\t\t| ((\n\t\t\t\tinner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t ) => unknown | Promise<unknown>)\n\t> = {};\n\n\tfor (const key of Object.keys(calls) as Array<keyof typeof calls & string>) {\n\t\tconst proc = calls[key];\n\t\tconst userFn = userHandlers[key as keyof typeof userHandlers];\n\t\tif (proc.input) {\n\t\t\tout[key] = (\n\t\t\t\tinput,\n\t\t\t\t_inner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t\t) =>\n\t\t\t\t(\n\t\t\t\t\tuserFn as (\n\t\t\t\t\t\ti: unknown,\n\t\t\t\t\t\ts: SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t) => unknown | Promise<unknown>\n\t\t\t\t)(input, outer);\n\t\t} else {\n\t\t\tout[key] = (\n\t\t\t\t_inner: SockaWebSocketSession<TContract, EmptySockaSessionData>,\n\t\t\t) =>\n\t\t\t\t(\n\t\t\t\t\tuserFn as (\n\t\t\t\t\t\ts: SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t) => unknown | Promise<unknown>\n\t\t\t\t)(outer);\n\t\t}\n\t}\n\n\treturn out as InferSockaHandlers<\n\t\tTContract,\n\t\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n\t>;\n}\n\n/**\n * Durable Object WebSocket session driven by a socka contract.\n * Dispatches client requests to typed handler functions, validates\n * input/output via Standard Schema, and auto-sends response/error frames.\n */\nexport class SockaDoSession<\n\t\tTContract extends SockaContract<SockaContractConfig>,\n\t\tTData = EmptySockaSessionData,\n\t\tTEnv extends object = Cloudflare.Env,\n\t>\n\textends BaseSession<TData, unknown, unknown, TEnv>\n\timplements SockaPushSession<TContract>\n{\n\tprivate socka!: SockaWebSocketSession<TContract, EmptySockaSessionData>;\n\n\tconstructor(\n\t\twebsocket: WebSocket,\n\t\tsessions: Map<WebSocket, SockaDoSession<TContract, TData, TEnv>>,\n\t\tconfig: SockaDoSessionConfig<TContract, TData, TEnv>,\n\t) {\n\t\tconst wireFormat = config.wireFormat ?? \"json\";\n\t\tsuper(\n\t\t\twebsocket,\n\t\t\tsessions as Map<WebSocket, BaseSession<TData, unknown, unknown, TEnv>>,\n\t\t\t{\n\t\t\t\tcreateData:\n\t\t\t\t\tconfig.createData ??\n\t\t\t\t\t((_ctx: Context<{ Bindings: TEnv }>) => ({}) as TData),\n\t\t\t\thandleMessage: async () => {\n\t\t\t\t\t// Raw message handling goes through handleRawMessage / handleBufferMessage\n\t\t\t\t},\n\t\t\t\thandleBufferMessage: async (message) => {\n\t\t\t\t\tawait this.socka.handleBinaryMessage(message);\n\t\t\t\t},\n\t\t\t\thandleClose: async (baseSession) => {\n\t\t\t\t\tawait config.handleClose(\n\t\t\t\t\t\tbaseSession as SockaDoSession<TContract, TData, TEnv>,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\tconst sockaConfig: SockaWebSocketSessionConfig<\n\t\t\tTContract,\n\t\t\tEmptySockaSessionData\n\t\t> = {\n\t\t\tcontract: config.contract,\n\t\t\twireFormat,\n\t\t\thandlers: wrapHandlersForInnerSockaEngine(\n\t\t\t\tconfig.contract,\n\t\t\t\tconfig.handlers,\n\t\t\t\tthis,\n\t\t\t),\n\t\t\thandleClose: async () => {\n\t\t\t\t// Outer DO lifecycle uses SockaDoSessionConfig.handleClose; inner engine no-op.\n\t\t\t},\n\t\t\tonHandlerError: config.onHandlerError\n\t\t\t\t? (err, rpcName, input, _inner) => {\n\t\t\t\t\t\tconfig.onHandlerError?.(err, rpcName, input, this);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t\tonValidationError: config.onValidationError,\n\t\t\tserializeJson: config.serializeJson,\n\t\t\tdeserializeJson: config.deserializeJson,\n\t\t};\n\t\tthis.socka = new SockaWebSocketSession(\n\t\t\twebsocket,\n\t\t\tsessions as unknown as Map<\n\t\t\t\tWebSocket,\n\t\t\t\tSockaWebSocketSession<TContract, EmptySockaSessionData>\n\t\t\t>,\n\t\t\tsockaConfig,\n\t\t);\n\t\t// Defer past the outer `await createSession()` continuation so\n\t\t// `BaseSession.startFresh` has run and `session.data` exists (single\n\t\t// `queueMicrotask` runs before that continuation).\n\t\tqueueMicrotask(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\trunSockaDoSessionOnAttached(config, this);\n\t\t\t});\n\t\t});\n\t}\n\n\tpublic async handleRawMessage(rawMessage: string): Promise<void> {\n\t\treturn this.socka.handleRawMessage(rawMessage);\n\t}\n\n\tpublic emitWireEvent(event: string, body: unknown): void {\n\t\tthis.socka.emitWireEvent(event, body);\n\t}\n\n\tpublic emitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void> {\n\t\treturn this.socka.emitPush(name, body);\n\t}\n\n\tpublic broadcastPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t\texcludeSelf = false,\n\t): Promise<void> {\n\t\treturn this.socka.broadcastPush(name, body, excludeSelf);\n\t}\n}\n","import type { Context } from \"hono\";\nimport type { SessionEnv } from \"@firtoz/websocket-do\";\nimport { BaseWebSocketDO } from \"@firtoz/websocket-do\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { SockaDoSession } from \"./SockaDoSession\";\n\nexport type SockaWebSocketDOOptions<\n\tTEnv extends object,\n\t// `any` contract slot: concrete sessions use `defineSocka` contracts that do\n\t// not assign to `SockaContract<SockaContractConfig>` under strict generics.\n\tTSession extends SockaDoSession<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\tTEnv\n\t>,\n> = {\n\tcreateSockaSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t) => TSession | Promise<TSession>;\n};\n\n/**\n * Durable Object base class for WebSocket apps using {@link SockaDoSession}\n * (Standard Schema contract-driven).\n */\nexport abstract class SockaWebSocketDO<\n\tTSession extends SockaDoSession<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany\n\t> = SockaDoSession<\n\t\tSockaContract<SockaContractConfig>,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: session family type erasure\n\t\tany\n\t>,\n\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n> extends BaseWebSocketDO<TSession, TEnv> {\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\toptions: SockaWebSocketDOOptions<TEnv, TSession>,\n\t) {\n\t\tsuper(ctx, env, {\n\t\t\tcreateSession: (sessionCtx, websocket) =>\n\t\t\t\toptions.createSockaSession(sessionCtx, websocket),\n\t\t});\n\t}\n}\n","/**\n * Shared error reply shape for correlate-by-id RPC when the handler throws.\n * Narrow at the call site to your server message union if needed.\n */\nexport function toErrorReply(\n\tid: string,\n\terror: string,\n): {\n\ttype: \"error\";\n\tid: string;\n\terror: string;\n} {\n\treturn { type: \"error\", id, error };\n}\n"]}
|