@firtoz/websocket-do 10.0.0 → 13.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 +68 -61
- package/dist/BaseSession.d.ts +41 -0
- package/dist/BaseSession.js +5 -0
- package/dist/BaseSession.js.map +1 -0
- package/dist/BaseWebSocketDO.d.ts +42 -0
- package/dist/BaseWebSocketDO.js +4 -0
- package/dist/BaseWebSocketDO.js.map +1 -0
- package/dist/StandardSchemaSession.d.ts +41 -0
- package/dist/StandardSchemaSession.js +8 -0
- package/dist/StandardSchemaSession.js.map +1 -0
- package/dist/StandardSchemaWebSocketClient.d.ts +45 -0
- package/dist/StandardSchemaWebSocketClient.js +5 -0
- package/dist/StandardSchemaWebSocketClient.js.map +1 -0
- package/dist/StandardSchemaWebSocketDO.d.ts +28 -0
- package/dist/StandardSchemaWebSocketDO.js +5 -0
- package/dist/StandardSchemaWebSocketDO.js.map +1 -0
- package/dist/WebsocketWrapper.d.ts +9 -0
- package/dist/WebsocketWrapper.js +4 -0
- package/dist/WebsocketWrapper.js.map +1 -0
- package/dist/chunk-3C77OSOD.js +54 -0
- package/dist/chunk-3C77OSOD.js.map +1 -0
- package/dist/chunk-3LWVEY3R.js +130 -0
- package/dist/chunk-3LWVEY3R.js.map +1 -0
- package/dist/chunk-53MFRNQS.js +153 -0
- package/dist/chunk-53MFRNQS.js.map +1 -0
- package/dist/chunk-CAX4POIL.js +13 -0
- package/dist/chunk-CAX4POIL.js.map +1 -0
- package/dist/chunk-KCPOB32E.js +20 -0
- package/dist/chunk-KCPOB32E.js.map +1 -0
- package/dist/chunk-NOUFNU2O.js +10 -0
- package/dist/chunk-NOUFNU2O.js.map +1 -0
- package/dist/chunk-QMGIRIHJ.js +18 -0
- package/dist/chunk-QMGIRIHJ.js.map +1 -0
- package/dist/chunk-ULGH6X42.js +23 -0
- package/dist/chunk-ULGH6X42.js.map +1 -0
- package/dist/chunk-WJIQBI6I.js +35 -0
- package/dist/chunk-WJIQBI6I.js.map +1 -0
- package/dist/chunk-XFB6C3NZ.js +134 -0
- package/dist/chunk-XFB6C3NZ.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/parseStandardSchema.d.ts +9 -0
- package/dist/parseStandardSchema.js +4 -0
- package/dist/parseStandardSchema.js.map +1 -0
- package/dist/standardSchemaMsgpack.d.ts +8 -0
- package/dist/standardSchemaMsgpack.js +5 -0
- package/dist/standardSchemaMsgpack.js.map +1 -0
- package/dist/standardSchemaRpc.d.ts +30 -0
- package/dist/standardSchemaRpc.js +6 -0
- package/dist/standardSchemaRpc.js.map +1 -0
- package/dist/standardSchemaRpcReact.d.ts +27 -0
- package/dist/standardSchemaRpcReact.js +54 -0
- package/dist/standardSchemaRpcReact.js.map +1 -0
- package/package.json +36 -18
- package/src/BaseSession.ts +15 -5
- package/src/{ZodSession.ts → StandardSchemaSession.ts} +71 -70
- package/src/{ZodWebSocketClient.ts → StandardSchemaWebSocketClient.ts} +30 -50
- package/src/{ZodWebSocketDO.ts → StandardSchemaWebSocketDO.ts} +29 -22
- package/src/index.ts +15 -12
- package/src/parseStandardSchema.ts +17 -0
- package/src/standardSchemaMsgpack.ts +17 -0
- package/src/standardSchemaRpc.ts +83 -0
- package/src/standardSchemaRpcReact.ts +107 -0
- package/src/zodMsgpack.ts +0 -13
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { __privateAdd, __privateMethod } from './chunk-NOUFNU2O.js';
|
|
2
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
|
|
5
|
+
var _BaseWebSocketDO_instances, handleClose_fn;
|
|
6
|
+
var BaseWebSocketDO = class extends DurableObject {
|
|
7
|
+
constructor(ctx, env, options) {
|
|
8
|
+
super(ctx, env);
|
|
9
|
+
this.options = options;
|
|
10
|
+
__privateAdd(this, _BaseWebSocketDO_instances);
|
|
11
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
12
|
+
this.ctx.blockConcurrencyWhile(async () => {
|
|
13
|
+
const websockets = this.ctx.getWebSockets();
|
|
14
|
+
await Promise.all(
|
|
15
|
+
websockets.map(async (websocket) => {
|
|
16
|
+
try {
|
|
17
|
+
const session = await options.createSession(void 0, websocket);
|
|
18
|
+
session.resume();
|
|
19
|
+
this.sessions.set(websocket, session);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(`Error during session setup: ${error}`);
|
|
22
|
+
await this.webSocketError(websocket, error);
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
getBaseApp() {
|
|
29
|
+
return new Hono().get(
|
|
30
|
+
"/websocket",
|
|
31
|
+
async (ctx) => {
|
|
32
|
+
const { req } = ctx;
|
|
33
|
+
if (req.header("Upgrade") !== "websocket") {
|
|
34
|
+
console.error("Expected websocket");
|
|
35
|
+
return ctx.text("Expected websocket", 400);
|
|
36
|
+
}
|
|
37
|
+
const [client, server] = Object.values(new WebSocketPair());
|
|
38
|
+
try {
|
|
39
|
+
await this.handleSession(ctx, server);
|
|
40
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(error);
|
|
43
|
+
client.accept();
|
|
44
|
+
client.send(
|
|
45
|
+
JSON.stringify({
|
|
46
|
+
error: "Uncaught exception during session setup."
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
client.close(1011, "Uncaught exception during session setup.");
|
|
50
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
async handleSession(ctx, ws) {
|
|
56
|
+
this.ctx.acceptWebSocket(ws);
|
|
57
|
+
try {
|
|
58
|
+
const session = await this.options.createSession(ctx, ws);
|
|
59
|
+
session.startFresh(ctx);
|
|
60
|
+
this.sessions.set(ws, session);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`Error during session setup: ${error}`);
|
|
63
|
+
await this.webSocketError(ws, error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async webSocketMessage(ws, message) {
|
|
67
|
+
const session = this.sessions.get(ws);
|
|
68
|
+
if (!session) return;
|
|
69
|
+
try {
|
|
70
|
+
if (message instanceof ArrayBuffer) {
|
|
71
|
+
await session.handleBufferMessage(message);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const rawMessageSession = session;
|
|
75
|
+
if (rawMessageSession.handleRawMessage) {
|
|
76
|
+
await rawMessageSession.handleRawMessage(message);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const parsed = JSON.parse(message);
|
|
80
|
+
await session.handleMessage(parsed);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(`Error during session message: ${error}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async webSocketClose(ws, _code, _reason, _wasClean) {
|
|
86
|
+
const session = this.sessions.get(ws);
|
|
87
|
+
if (!session) return;
|
|
88
|
+
try {
|
|
89
|
+
await __privateMethod(this, _BaseWebSocketDO_instances, handleClose_fn).call(this, session);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`Error during session close: ${error}`);
|
|
92
|
+
} finally {
|
|
93
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CLOSING) {
|
|
94
|
+
ws.close(1e3, "Normal closure");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async webSocketError(ws, error) {
|
|
99
|
+
const session = this.sessions.get(ws);
|
|
100
|
+
if (!session) {
|
|
101
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CLOSING) {
|
|
102
|
+
ws.close(1011, "Error during session setup.");
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.error(`Error for session: ${error}`);
|
|
107
|
+
try {
|
|
108
|
+
await __privateMethod(this, _BaseWebSocketDO_instances, handleClose_fn).call(this, session);
|
|
109
|
+
} catch (error2) {
|
|
110
|
+
console.error(`Error during session close: ${error2}`);
|
|
111
|
+
} finally {
|
|
112
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CLOSING) {
|
|
113
|
+
ws.close(1011, "Error during session.");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
fetch(request) {
|
|
118
|
+
return this.app.fetch(request, this.env);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
_BaseWebSocketDO_instances = new WeakSet();
|
|
122
|
+
handleClose_fn = async function(session) {
|
|
123
|
+
try {
|
|
124
|
+
await session.handleClose();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(`Error during session close: ${error}`);
|
|
127
|
+
} finally {
|
|
128
|
+
this.sessions.delete(session.websocket);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export { BaseWebSocketDO };
|
|
133
|
+
//# sourceMappingURL=chunk-XFB6C3NZ.js.map
|
|
134
|
+
//# sourceMappingURL=chunk-XFB6C3NZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/BaseWebSocketDO.ts"],"names":["error"],"mappings":"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","file":"chunk-XFB6C3NZ.js","sourcesContent":["import { DurableObject } from \"cloudflare:workers\";\nimport type { DOWithHonoApp } from \"@firtoz/hono-fetcher/honoDoFetcher\";\nimport { type Context, Hono } from \"hono\";\nimport type {\n\tBaseSession,\n\tSessionClientMessage,\n\tSessionEnv,\n} from \"./BaseSession\";\n\nexport type BaseWebSocketDOOptions<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends BaseSession<any, any, any, any>,\n\tTEnv extends SessionEnv<TSession>,\n> = {\n\tcreateSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t) => TSession | Promise<TSession>;\n};\n\nexport abstract class BaseWebSocketDO<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\tTSession extends BaseSession<any, any, any, any> = BaseSession<\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany\n\t\t>,\n\t\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n\t>\n\textends DurableObject<TEnv>\n\timplements DOWithHonoApp\n{\n\tprotected readonly sessions = new Map<WebSocket, TSession>();\n\tabstract readonly app: Hono<{ Bindings: TEnv }>;\n\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\tprivate readonly options: BaseWebSocketDOOptions<TSession, TEnv>,\n\t) {\n\t\tsuper(ctx, env);\n\n\t\tthis.ctx.blockConcurrencyWhile(async () => {\n\t\t\tconst websockets = this.ctx.getWebSockets();\n\t\t\tawait Promise.all(\n\t\t\t\twebsockets.map(async (websocket) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// For resumed sessions, we don't have a Hono context\n\t\t\t\t\t\t// Pass undefined and let implementers handle it\n\t\t\t\t\t\tconst session = await options.createSession(undefined, websocket);\n\t\t\t\t\t\tsession.resume();\n\t\t\t\t\t\tthis.sessions.set(websocket, session);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(`Error during session setup: ${error}`);\n\t\t\t\t\t\tawait this.webSocketError(websocket, error);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected getBaseApp() {\n\t\treturn new Hono<{ Bindings: TEnv }>().get(\n\t\t\t\"/websocket\",\n\t\t\tasync (ctx): Promise<Response> => {\n\t\t\t\tconst { req } = ctx;\n\t\t\t\tif (req.header(\"Upgrade\") !== \"websocket\") {\n\t\t\t\t\tconsole.error(\"Expected websocket\");\n\t\t\t\t\treturn ctx.text(\"Expected websocket\", 400);\n\t\t\t\t}\n\n\t\t\t\tconst [client, server] = Object.values(new WebSocketPair()) as [\n\t\t\t\t\tWebSocket,\n\t\t\t\t\tWebSocket,\n\t\t\t\t];\n\n\t\t\t\ttry {\n\t\t\t\t\tawait this.handleSession(ctx, server);\n\t\t\t\t\treturn new Response(null, { status: 101, webSocket: client });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(error);\n\t\t\t\t\tclient.accept();\n\t\t\t\t\tclient.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\terror: \"Uncaught exception during session setup.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tclient.close(1011, \"Uncaught exception during session setup.\");\n\t\t\t\t\treturn new Response(null, { status: 101, webSocket: client });\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\tasync handleSession(\n\t\tctx: Context<{ Bindings: TEnv }>,\n\t\tws: WebSocket,\n\t): Promise<void> {\n\t\tthis.ctx.acceptWebSocket(ws);\n\t\ttry {\n\t\t\tconst session = await this.options.createSession(ctx, ws);\n\t\t\tsession.startFresh(ctx);\n\t\t\tthis.sessions.set(ws, session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session setup: ${error}`);\n\t\t\tawait this.webSocketError(ws, error);\n\t\t}\n\t}\n\n\toverride async webSocketMessage(\n\t\tws: WebSocket,\n\t\tmessage: string | ArrayBuffer,\n\t): Promise<void> {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) return;\n\n\t\ttry {\n\t\t\tif (message instanceof ArrayBuffer) {\n\t\t\t\tawait session.handleBufferMessage(message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rawMessageSession = session as BaseSession<\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tTEnv\n\t\t\t> & {\n\t\t\t\thandleRawMessage?: (rawMessage: string) => Promise<void>;\n\t\t\t};\n\t\t\tif (rawMessageSession.handleRawMessage) {\n\t\t\t\tawait rawMessageSession.handleRawMessage(message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst parsed = JSON.parse(message) as SessionClientMessage<TSession>;\n\t\t\tawait session.handleMessage(parsed);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session message: ${error}`);\n\t\t\t// Let the implementer decide how to handle errors in their session implementation\n\t\t\t// The session can optionally implement error handling that closes the connection if needed\n\t\t}\n\t}\n\n\toverride async webSocketClose(\n\t\tws: WebSocket,\n\t\t_code: number,\n\t\t_reason: string,\n\t\t_wasClean: boolean,\n\t) {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) return;\n\n\t\ttry {\n\t\t\tawait this.#handleClose(session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\t// Call close() for both OPEN and CLOSING states\n\t\t\t// For CLOSING, this can help ensure the WebSocket fully transitions to CLOSED\n\t\t\tif (\n\t\t\t\tws.readyState === WebSocket.OPEN ||\n\t\t\t\tws.readyState === WebSocket.CLOSING\n\t\t\t) {\n\t\t\t\tws.close(1000, \"Normal closure\");\n\t\t\t}\n\t\t}\n\t}\n\n\toverride async webSocketError(ws: WebSocket, error: unknown) {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) {\n\t\t\t// Call close() for both OPEN and CLOSING states\n\t\t\tif (\n\t\t\t\tws.readyState === WebSocket.OPEN ||\n\t\t\t\tws.readyState === WebSocket.CLOSING\n\t\t\t) {\n\t\t\t\tws.close(1011, \"Error during session setup.\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.error(`Error for session: ${error}`);\n\t\ttry {\n\t\t\tawait this.#handleClose(session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\t// Call close() for both OPEN and CLOSING states\n\t\t\tif (\n\t\t\t\tws.readyState === WebSocket.OPEN ||\n\t\t\t\tws.readyState === WebSocket.CLOSING\n\t\t\t) {\n\t\t\t\tws.close(1011, \"Error during session.\");\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleClose(session: TSession) {\n\t\ttry {\n\t\t\tawait session.handleClose();\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\tthis.sessions.delete(session.websocket);\n\t\t}\n\t}\n\n\toverride fetch(request: Request): Response | Promise<Response> {\n\t\treturn this.app.fetch(request, this.env);\n\t}\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { BaseSession, BaseSessionHandlers, SessionClientMessage, SessionEnv, SessionServerMessage } from './BaseSession.js';
|
|
2
|
+
export { BaseWebSocketDO, BaseWebSocketDOOptions } from './BaseWebSocketDO.js';
|
|
3
|
+
export { WebsocketWrapper } from './WebsocketWrapper.js';
|
|
4
|
+
export { StandardSchemaSession, StandardSchemaSessionHandlers, StandardSchemaSessionOptions } from './StandardSchemaSession.js';
|
|
5
|
+
export { StandardSchemaWebSocketClient, StandardSchemaWebSocketClientOptions } from './StandardSchemaWebSocketClient.js';
|
|
6
|
+
export { StandardSchemaSessionOptionsOrFactory, StandardSchemaWebSocketDO, StandardSchemaWebSocketDOOptions } from './StandardSchemaWebSocketDO.js';
|
|
7
|
+
export { parseStandardSchema } from './parseStandardSchema.js';
|
|
8
|
+
export { standardSchemaMsgpack } from './standardSchemaMsgpack.js';
|
|
9
|
+
import 'hono';
|
|
10
|
+
import 'hono/hono-base';
|
|
11
|
+
import 'hono/utils/http-status';
|
|
12
|
+
import 'cloudflare:workers';
|
|
13
|
+
import '@firtoz/hono-fetcher/honoDoFetcher';
|
|
14
|
+
import '@standard-schema/spec';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { StandardSchemaSession } from './chunk-53MFRNQS.js';
|
|
2
|
+
export { standardSchemaMsgpack } from './chunk-QMGIRIHJ.js';
|
|
3
|
+
export { BaseSession } from './chunk-3C77OSOD.js';
|
|
4
|
+
export { StandardSchemaWebSocketClient } from './chunk-3LWVEY3R.js';
|
|
5
|
+
export { StandardSchemaWebSocketDO } from './chunk-ULGH6X42.js';
|
|
6
|
+
export { BaseWebSocketDO } from './chunk-XFB6C3NZ.js';
|
|
7
|
+
export { WebsocketWrapper } from './chunk-KCPOB32E.js';
|
|
8
|
+
export { parseStandardSchema } from './chunk-CAX4POIL.js';
|
|
9
|
+
import './chunk-NOUFNU2O.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates {@link value} with a Standard Schema v1 schema and returns the output,
|
|
5
|
+
* or throws an {@link Error} whose message aggregates issue messages.
|
|
6
|
+
*/
|
|
7
|
+
declare function parseStandardSchema<T>(schema: StandardSchemaV1<unknown, T>, value: unknown): Promise<T>;
|
|
8
|
+
|
|
9
|
+
export { parseStandardSchema };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"parseStandardSchema.js"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
3
|
+
declare const standardSchemaMsgpack: <T>(schema: StandardSchemaV1<unknown, T>) => {
|
|
4
|
+
encode(value: T): Promise<Uint8Array>;
|
|
5
|
+
decode(bytes: Uint8Array): Promise<T>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export { standardSchemaMsgpack };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"standardSchemaMsgpack.js"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { StandardSchemaWebSocketClientOptions, StandardSchemaWebSocketClient } from './StandardSchemaWebSocketClient.js';
|
|
2
|
+
import '@standard-schema/spec';
|
|
3
|
+
|
|
4
|
+
type StandardSchemaWebSocketRpcSessionConstructorOptions<TClientMsg, TServerMsg, TPending extends {
|
|
5
|
+
reject: (error: Error) => void;
|
|
6
|
+
}> = Omit<StandardSchemaWebSocketClientOptions<TClientMsg, TServerMsg>, "onMessage"> & {
|
|
7
|
+
onMessage: (message: TServerMsg, session: StandardSchemaWebSocketRpcSession<TClientMsg, TServerMsg, TPending>) => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* WebSocket client session with a pending map for request/response RPC and a
|
|
11
|
+
* monotonic id helper. Wire formats stay in your Standard Schema schemas; you dispatch
|
|
12
|
+
* {@link TServerMsg} in `onMessage` (typically with `switch` + `exhaustiveGuard`)
|
|
13
|
+
* and resolve/reject entries in {@link pending}.
|
|
14
|
+
*/
|
|
15
|
+
declare class StandardSchemaWebSocketRpcSession<TClientMsg, TServerMsg, TPending extends {
|
|
16
|
+
reject: (error: Error) => void;
|
|
17
|
+
}> {
|
|
18
|
+
readonly pending: Map<string, TPending>;
|
|
19
|
+
readonly client: StandardSchemaWebSocketClient<TClientMsg, TServerMsg>;
|
|
20
|
+
private idSeq;
|
|
21
|
+
constructor(options: StandardSchemaWebSocketRpcSessionConstructorOptions<TClientMsg, TServerMsg, TPending>);
|
|
22
|
+
nextId(prefix: string): string;
|
|
23
|
+
rejectAllPending(reason: Error): void;
|
|
24
|
+
close(code?: number, reason?: string): void;
|
|
25
|
+
}
|
|
26
|
+
declare function createStandardSchemaWebSocketRpcSession<TClientMsg, TServerMsg, TPending extends {
|
|
27
|
+
reject: (error: Error) => void;
|
|
28
|
+
}>(options: StandardSchemaWebSocketRpcSessionConstructorOptions<TClientMsg, TServerMsg, TPending>): StandardSchemaWebSocketRpcSession<TClientMsg, TServerMsg, TPending>;
|
|
29
|
+
|
|
30
|
+
export { StandardSchemaWebSocketRpcSession, type StandardSchemaWebSocketRpcSessionConstructorOptions, createStandardSchemaWebSocketRpcSession };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { StandardSchemaWebSocketRpcSession, createStandardSchemaWebSocketRpcSession } from './chunk-WJIQBI6I.js';
|
|
2
|
+
import './chunk-3LWVEY3R.js';
|
|
3
|
+
import './chunk-CAX4POIL.js';
|
|
4
|
+
import './chunk-NOUFNU2O.js';
|
|
5
|
+
//# sourceMappingURL=standardSchemaRpc.js.map
|
|
6
|
+
//# sourceMappingURL=standardSchemaRpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"standardSchemaRpc.js"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DependencyList, RefObject } from 'react';
|
|
2
|
+
import { StandardSchemaWebSocketRpcSessionConstructorOptions, StandardSchemaWebSocketRpcSession } from './standardSchemaRpc.js';
|
|
3
|
+
import './StandardSchemaWebSocketClient.js';
|
|
4
|
+
import '@standard-schema/spec';
|
|
5
|
+
|
|
6
|
+
/** Options for {@link useStandardSchemaWebSocketRpc} (same as constructor options for {@link StandardSchemaWebSocketRpcSession}). */
|
|
7
|
+
type UseStandardSchemaWebSocketRpcOptions<TClientMsg, TServerMsg, TPending extends {
|
|
8
|
+
reject: (error: Error) => void;
|
|
9
|
+
}> = StandardSchemaWebSocketRpcSessionConstructorOptions<TClientMsg, TServerMsg, TPending>;
|
|
10
|
+
/**
|
|
11
|
+
* Connects a {@link StandardSchemaWebSocketRpcSession} in an effect: rejects all pending
|
|
12
|
+
* RPCs and closes the socket on cleanup or when `deps` change.
|
|
13
|
+
*
|
|
14
|
+
* Callback refs keep the latest `onMessage` / `onOpen` / `onClose` without
|
|
15
|
+
* listing them in `deps`, so inline handlers do not reconnect every render.
|
|
16
|
+
*
|
|
17
|
+
* Pass `deps` as the second argument; keep it aligned with values used to
|
|
18
|
+
* build `url` / `webSocket`.
|
|
19
|
+
*/
|
|
20
|
+
declare function useStandardSchemaWebSocketRpc<TClientMsg, TServerMsg, TPending extends {
|
|
21
|
+
reject: (error: Error) => void;
|
|
22
|
+
}>(options: UseStandardSchemaWebSocketRpcOptions<TClientMsg, TServerMsg, TPending>, deps: DependencyList): {
|
|
23
|
+
ready: boolean;
|
|
24
|
+
sessionRef: RefObject<StandardSchemaWebSocketRpcSession<TClientMsg, TServerMsg, TPending> | null>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { type UseStandardSchemaWebSocketRpcOptions, useStandardSchemaWebSocketRpc };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createStandardSchemaWebSocketRpcSession } from './chunk-WJIQBI6I.js';
|
|
2
|
+
import './chunk-3LWVEY3R.js';
|
|
3
|
+
import './chunk-CAX4POIL.js';
|
|
4
|
+
import './chunk-NOUFNU2O.js';
|
|
5
|
+
import { useRef, useState, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
function useStandardSchemaWebSocketRpc(options, deps) {
|
|
8
|
+
const { onMessage, onOpen, onClose, ...clientOptions } = options;
|
|
9
|
+
const onMessageRef = useRef(onMessage);
|
|
10
|
+
onMessageRef.current = onMessage;
|
|
11
|
+
const onOpenRef = useRef(onOpen);
|
|
12
|
+
onOpenRef.current = onOpen;
|
|
13
|
+
const onCloseRef = useRef(onClose);
|
|
14
|
+
onCloseRef.current = onClose;
|
|
15
|
+
const [ready, setReady] = useState(false);
|
|
16
|
+
const sessionRef = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
setReady(false);
|
|
20
|
+
const session = createStandardSchemaWebSocketRpcSession({
|
|
21
|
+
...clientOptions,
|
|
22
|
+
onMessage: (msg, s) => {
|
|
23
|
+
onMessageRef.current(msg, s);
|
|
24
|
+
},
|
|
25
|
+
onOpen: (event) => {
|
|
26
|
+
if (!cancelled) {
|
|
27
|
+
setReady(true);
|
|
28
|
+
}
|
|
29
|
+
onOpenRef.current?.(event);
|
|
30
|
+
},
|
|
31
|
+
onClose: (event) => {
|
|
32
|
+
if (!cancelled) {
|
|
33
|
+
setReady(false);
|
|
34
|
+
}
|
|
35
|
+
onCloseRef.current?.(event);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
sessionRef.current = session;
|
|
39
|
+
if (session.client.socket.readyState === WebSocket.OPEN) {
|
|
40
|
+
setReady(true);
|
|
41
|
+
}
|
|
42
|
+
return () => {
|
|
43
|
+
cancelled = true;
|
|
44
|
+
sessionRef.current = null;
|
|
45
|
+
session.rejectAllPending(new Error("WebSocket closed"));
|
|
46
|
+
session.close();
|
|
47
|
+
};
|
|
48
|
+
}, deps);
|
|
49
|
+
return { ready, sessionRef };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { useStandardSchemaWebSocketRpc };
|
|
53
|
+
//# sourceMappingURL=standardSchemaRpcReact.js.map
|
|
54
|
+
//# sourceMappingURL=standardSchemaRpcReact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/standardSchemaRpcReact.ts"],"names":[],"mappings":";;;;;;AA6BO,SAAS,6BAAA,CAKf,SAKA,IAAA,EAQC;AACD,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,EAAQ,OAAA,EAAS,GAAG,eAAc,GAAI,OAAA;AAEzD,EAAA,MAAM,YAAA,GAAe,OAAO,SAAS,CAAA;AACrC,EAAA,YAAA,CAAa,OAAA,GAAU,SAAA;AACvB,EAAA,MAAM,SAAA,GAAY,OAAO,MAAM,CAAA;AAC/B,EAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AACpB,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,KAAK,CAAA;AACxC,EAAA,MAAM,UAAA,GAAa,OAIT,IAAI,CAAA;AAEd,EAAA,SAAA,CAAU,MAAM;AACf,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,QAAA,CAAS,KAAK,CAAA;AAEd,IAAA,MAAM,UAAU,uCAAA,CAId;AAAA,MACD,GAAG,aAAA;AAAA,MACH,SAAA,EAAW,CAAC,GAAA,EAAK,CAAA,KAAM;AACtB,QAAA,YAAA,CAAa,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,MAC5B,CAAA;AAAA,MACA,MAAA,EAAQ,CAAC,KAAA,KAAU;AAClB,QAAA,IAAI,CAAC,SAAA,EAAW;AACf,UAAA,QAAA,CAAS,IAAI,CAAA;AAAA,QACd;AACA,QAAA,SAAA,CAAU,UAAU,KAAK,CAAA;AAAA,MAC1B,CAAA;AAAA,MACA,OAAA,EAAS,CAAC,KAAA,KAAU;AACnB,QAAA,IAAI,CAAC,SAAA,EAAW;AACf,UAAA,QAAA,CAAS,KAAK,CAAA;AAAA,QACf;AACA,QAAA,UAAA,CAAW,UAAU,KAAK,CAAA;AAAA,MAC3B;AAAA,KACA,CAAA;AAED,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AACrB,IAAA,IAAI,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,UAAA,KAAe,UAAU,IAAA,EAAM;AACxD,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACd;AAEA,IAAA,OAAO,MAAM;AACZ,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AACrB,MAAA,OAAA,CAAQ,gBAAA,CAAiB,IAAI,KAAA,CAAM,kBAAkB,CAAC,CAAA;AACtD,MAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,IACf,CAAA;AAAA,EAED,GAAG,IAAI,CAAA;AAEP,EAAA,OAAO,EAAE,OAAO,UAAA,EAAW;AAC5B","file":"standardSchemaRpcReact.js","sourcesContent":["import type { DependencyList, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n\ttype StandardSchemaWebSocketRpcSession,\n\ttype StandardSchemaWebSocketRpcSessionConstructorOptions,\n\tcreateStandardSchemaWebSocketRpcSession,\n} from \"./standardSchemaRpc\";\n\n/** Options for {@link useStandardSchemaWebSocketRpc} (same as constructor options for {@link StandardSchemaWebSocketRpcSession}). */\nexport type UseStandardSchemaWebSocketRpcOptions<\n\tTClientMsg,\n\tTServerMsg,\n\tTPending extends { reject: (error: Error) => void },\n> = StandardSchemaWebSocketRpcSessionConstructorOptions<\n\tTClientMsg,\n\tTServerMsg,\n\tTPending\n>;\n\n/**\n * Connects a {@link StandardSchemaWebSocketRpcSession} in an effect: rejects all pending\n * RPCs and closes the socket on cleanup or when `deps` change.\n *\n * Callback refs keep the latest `onMessage` / `onOpen` / `onClose` without\n * listing them in `deps`, so inline handlers do not reconnect every render.\n *\n * Pass `deps` as the second argument; keep it aligned with values used to\n * build `url` / `webSocket`.\n */\nexport function useStandardSchemaWebSocketRpc<\n\tTClientMsg,\n\tTServerMsg,\n\tTPending extends { reject: (error: Error) => void },\n>(\n\toptions: UseStandardSchemaWebSocketRpcOptions<\n\t\tTClientMsg,\n\t\tTServerMsg,\n\t\tTPending\n\t>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsessionRef: RefObject<StandardSchemaWebSocketRpcSession<\n\t\tTClientMsg,\n\t\tTServerMsg,\n\t\tTPending\n\t> | null>;\n} {\n\tconst { onMessage, onOpen, onClose, ...clientOptions } = options;\n\n\tconst onMessageRef = useRef(onMessage);\n\tonMessageRef.current = onMessage;\n\tconst onOpenRef = useRef(onOpen);\n\tonOpenRef.current = onOpen;\n\tconst onCloseRef = useRef(onClose);\n\tonCloseRef.current = onClose;\n\n\tconst [ready, setReady] = useState(false);\n\tconst sessionRef = useRef<StandardSchemaWebSocketRpcSession<\n\t\tTClientMsg,\n\t\tTServerMsg,\n\t\tTPending\n\t> | null>(null);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetReady(false);\n\n\t\tconst session = createStandardSchemaWebSocketRpcSession<\n\t\t\tTClientMsg,\n\t\t\tTServerMsg,\n\t\t\tTPending\n\t\t>({\n\t\t\t...clientOptions,\n\t\t\tonMessage: (msg, s) => {\n\t\t\t\tonMessageRef.current(msg, s);\n\t\t\t},\n\t\t\tonOpen: (event) => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(true);\n\t\t\t\t}\n\t\t\t\tonOpenRef.current?.(event);\n\t\t\t},\n\t\t\tonClose: (event) => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(false);\n\t\t\t\t}\n\t\t\t\tonCloseRef.current?.(event);\n\t\t\t},\n\t\t});\n\n\t\tsessionRef.current = session;\n\t\tif (session.client.socket.readyState === WebSocket.OPEN) {\n\t\t\tsetReady(true);\n\t\t}\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tsessionRef.current = null;\n\t\t\tsession.rejectAllPending(new Error(\"WebSocket closed\"));\n\t\t\tsession.close();\n\t\t};\n\t\t// biome-ignore lint/correctness/useExhaustiveDependencies: deps is the explicit contract\n\t}, deps);\n\n\treturn { ready, sessionRef };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/websocket-do",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "Type-safe WebSocket session management for Cloudflare Durable Objects with Hono integration",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"import": "./
|
|
12
|
-
"require": "./src/index.ts"
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
-
"./
|
|
15
|
-
"types": "./
|
|
16
|
-
"import": "./
|
|
17
|
-
|
|
14
|
+
"./schema-client": {
|
|
15
|
+
"types": "./dist/StandardSchemaWebSocketClient.d.ts",
|
|
16
|
+
"import": "./dist/StandardSchemaWebSocketClient.js"
|
|
17
|
+
},
|
|
18
|
+
"./rpc": {
|
|
19
|
+
"types": "./dist/standardSchemaRpc.d.ts",
|
|
20
|
+
"import": "./dist/standardSchemaRpc.js"
|
|
21
|
+
},
|
|
22
|
+
"./rpc-react": {
|
|
23
|
+
"types": "./dist/standardSchemaRpcReact.d.ts",
|
|
24
|
+
"import": "./dist/standardSchemaRpcReact.js"
|
|
18
25
|
}
|
|
19
26
|
},
|
|
20
27
|
"files": [
|
|
28
|
+
"dist/**/*.js",
|
|
29
|
+
"dist/**/*.js.map",
|
|
30
|
+
"dist/**/*.d.ts",
|
|
21
31
|
"src/**/*.ts",
|
|
22
32
|
"!src/**/*.test.ts",
|
|
23
33
|
"README.md"
|
|
24
34
|
],
|
|
25
35
|
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"prepack": "bun run build",
|
|
26
38
|
"typecheck": "tsgo --noEmit -p ./tsconfig.json",
|
|
27
39
|
"lint": "biome check --write src",
|
|
28
40
|
"lint:ci": "biome ci src",
|
|
@@ -51,13 +63,18 @@
|
|
|
51
63
|
},
|
|
52
64
|
"peerDependencies": {
|
|
53
65
|
"@cloudflare/workers-types": "^4.20260329.1",
|
|
54
|
-
"@firtoz/hono-fetcher": "^2.
|
|
55
|
-
"hono": "^4.12.9"
|
|
66
|
+
"@firtoz/hono-fetcher": "^2.7.0",
|
|
67
|
+
"hono": "^4.12.9",
|
|
68
|
+
"react": ">=18.0.0"
|
|
56
69
|
},
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"react": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@standard-schema/spec": "^1.1.0",
|
|
77
|
+
"msgpackr": "^1.11.9"
|
|
61
78
|
},
|
|
62
79
|
"engines": {
|
|
63
80
|
"node": ">=18.0.0"
|
|
@@ -68,6 +85,7 @@
|
|
|
68
85
|
"devDependencies": {
|
|
69
86
|
"@types/react": "^19.2.14",
|
|
70
87
|
"bun-types": "^1.3.11",
|
|
71
|
-
"typescript": "^6.0.2"
|
|
88
|
+
"typescript": "^6.0.2",
|
|
89
|
+
"zod": "^4.3.6"
|
|
72
90
|
}
|
|
73
91
|
}
|
package/src/BaseSession.ts
CHANGED
|
@@ -53,14 +53,21 @@ export type SessionEnv<
|
|
|
53
53
|
|
|
54
54
|
export type BaseSessionHandlers<
|
|
55
55
|
TData,
|
|
56
|
-
|
|
56
|
+
TServerMessage,
|
|
57
57
|
TClientMessage,
|
|
58
58
|
TEnv extends object = Cloudflare.Env,
|
|
59
59
|
> = {
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Per-connection state. If omitted, `startFresh` initializes `data` as `{}`
|
|
62
|
+
* (use empty `TData`, e.g. `Record<string, never>`).
|
|
63
|
+
*/
|
|
64
|
+
createData?: (ctx: Context<{ Bindings: TEnv }>) => TData;
|
|
61
65
|
handleMessage: (message: TClientMessage) => Promise<void>;
|
|
62
66
|
handleBufferMessage: (message: ArrayBuffer) => Promise<void>;
|
|
63
|
-
|
|
67
|
+
/** Called when this connection closes; `session` is this {@link BaseSession} instance. */
|
|
68
|
+
handleClose: (
|
|
69
|
+
session: BaseSession<TData, TServerMessage, TClientMessage, TEnv>,
|
|
70
|
+
) => Promise<void>;
|
|
64
71
|
};
|
|
65
72
|
|
|
66
73
|
export class BaseSession<
|
|
@@ -100,7 +107,10 @@ export class BaseSession<
|
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
public startFresh(ctx: Context<{ Bindings: TEnv }>) {
|
|
103
|
-
this.data =
|
|
110
|
+
this.data =
|
|
111
|
+
this.handlers.createData !== undefined
|
|
112
|
+
? this.handlers.createData(ctx)
|
|
113
|
+
: ({} as TData);
|
|
104
114
|
this.wrapper.serializeAttachment(this.data);
|
|
105
115
|
}
|
|
106
116
|
|
|
@@ -137,6 +147,6 @@ export class BaseSession<
|
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
async handleClose(): Promise<void> {
|
|
140
|
-
return this.handlers.handleClose();
|
|
150
|
+
return this.handlers.handleClose(this);
|
|
141
151
|
}
|
|
142
152
|
}
|