@firtoz/socka 3.0.2 → 3.0.3

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 (44) hide show
  1. package/README.md +15 -1
  2. package/dist/{SockaWebSocketSession-B1w7RAid.d.ts → SockaWebSocketSession-i5U9wvd-.d.ts} +9 -9
  3. package/dist/bun/index.d.ts +6 -6
  4. package/dist/bun/index.js +2 -2
  5. package/dist/bun/index.js.map +1 -1
  6. package/dist/{chunk-LVVCHLNW.js → chunk-6HY22C5G.js} +2 -2
  7. package/dist/chunk-6HY22C5G.js.map +1 -0
  8. package/dist/{chunk-5WQTYLIC.js → chunk-JR2GENNT.js} +2 -2
  9. package/dist/chunk-JR2GENNT.js.map +1 -0
  10. package/dist/{chunk-P3JEEOJL.js → chunk-THFUHQJ3.js} +2 -2
  11. package/dist/chunk-THFUHQJ3.js.map +1 -0
  12. package/dist/client/index.d.ts +10 -10
  13. package/dist/client/index.js +1 -1
  14. package/dist/core/index.d.ts +1 -1
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/do/index.d.ts +14 -7
  17. package/dist/do/index.js +1 -1
  18. package/dist/do/index.js.map +1 -1
  19. package/dist/hono/cloudflare-workers.d.ts +4 -4
  20. package/dist/hono/cloudflare-workers.js +2 -2
  21. package/dist/hono/cloudflare-workers.js.map +1 -1
  22. package/dist/hono/index.d.ts +4 -4
  23. package/dist/hono/index.js +2 -2
  24. package/dist/hono/index.js.map +1 -1
  25. package/dist/react/index.d.ts +13 -13
  26. package/dist/react/index.js +1 -1
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/server/index.d.ts +8 -8
  29. package/dist/server/index.js +4 -4
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/{socka-report-error-CXwpAUgl.d.ts → socka-report-error-nTXJIzNb.d.ts} +32 -12
  32. package/docs/README.md +2 -0
  33. package/docs/client.md +17 -0
  34. package/docs/collaborative-realtime.md +61 -0
  35. package/docs/durable-objects.md +24 -0
  36. package/docs/internals.md +1 -1
  37. package/docs/pushes.md +18 -1
  38. package/docs/react-durable-objects.md +107 -0
  39. package/docs/reference.md +16 -3
  40. package/package.json +1 -1
  41. package/skills/socka/core-rpc/SKILL.md +1 -1
  42. package/dist/chunk-5WQTYLIC.js.map +0 -1
  43. package/dist/chunk-LVVCHLNW.js.map +0 -1
  44. package/dist/chunk-P3JEEOJL.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/attachSockaWebSocket.ts","../../src/server/room-registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport { dispatchSockaInboundMessage } from \"./dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfigUnion,\n} from \"./SockaWebSocketSession\";\n\nexport type AttachedSockaWebSocket<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\tsession: SockaWebSocketSession<TContract, TData>;\n\t/** Remove listeners and delete this session from the map (idempotent). */\n\tdispose: () => void;\n};\n\n/**\n * Register WebSocket `message` / `close` handlers, insert the session into\n * `sessions`, and return `{ session, dispose }`. `dispose` runs\n * {@link SockaWebSocketSession.invokeHandleClose} once, then removes listeners\n * (also triggered by `close`).\n */\nexport function attachSockaWebSocket<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\twebsocket: WebSocket,\n\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>,\n\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n\tinit?: SockaWebSocketInit,\n): AttachedSockaWebSocket<TContract, TData> {\n\tconst session = new SockaWebSocketSession(websocket, sessions, config, init);\n\tsessions.set(websocket, session);\n\trunSockaSessionOnAttached(config, session);\n\n\tlet shuttingDown = false;\n\n\tconst finalize = (): void => {\n\t\twebsocket.removeEventListener(\"message\", onMessage);\n\t\twebsocket.removeEventListener(\"close\", onClose);\n\t\tsessions.delete(websocket);\n\t};\n\n\tconst shutdown = (): void => {\n\t\tif (shuttingDown) return;\n\t\tshuttingDown = true;\n\t\tvoid (async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tawait session.invokeHandleClose();\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tfinalize();\n\t\t\t}\n\t\t})().catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\tadapter: \"attach\",\n\t\t\t\terror,\n\t\t\t});\n\t\t\tfinalize();\n\t\t});\n\t};\n\n\tconst onMessage = (ev: MessageEvent): void => {\n\t\tconst wf = config.wireFormat ?? \"json\";\n\t\tvoid dispatchSockaInboundMessage(session, wf, ev.data).catch(\n\t\t\t(error: unknown) => {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"attach\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t};\n\n\tconst onClose = (): void => {\n\t\tshutdown();\n\t};\n\n\twebsocket.addEventListener(\"message\", onMessage);\n\twebsocket.addEventListener(\"close\", onClose);\n\n\treturn { session, dispose: shutdown };\n}\n","import type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { SockaWebSocketSession } from \"./SockaWebSocketSession\";\nimport type { SockaWebSocketSessionConfig } from \"./SockaWebSocketSessionConfig\";\n\nexport type SockaRoomBundle<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n};\n\n/**\n * Per-room {@link SockaWebSocketSession} maps and configs for Bun/Hono multi-room\n * apps (one bundle per `roomId`).\n */\nexport function createSockaRoomRegistry<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tmakeConfig: (\n\t\troomId: string,\n\t\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>,\n\t) => SockaWebSocketSessionConfig<TContract, TData>,\n): {\n\tget(roomId: string): SockaRoomBundle<TContract, TData>;\n\treadonly rooms: ReadonlyMap<string, SockaRoomBundle<TContract, TData>>;\n} {\n\tconst rooms = new Map<string, SockaRoomBundle<TContract, TData>>();\n\treturn {\n\t\tget(roomId: string): SockaRoomBundle<TContract, TData> {\n\t\t\tlet r = rooms.get(roomId);\n\t\t\tif (!r) {\n\t\t\t\tconst sessionMap = new Map<\n\t\t\t\t\tWebSocket,\n\t\t\t\t\tSockaWebSocketSession<TContract, TData>\n\t\t\t\t>();\n\t\t\t\tconst config = makeConfig(roomId, sessionMap);\n\t\t\t\tr = { sessionMap, config };\n\t\t\t\trooms.set(roomId, r);\n\t\t\t}\n\t\t\treturn r;\n\t\t},\n\t\tget rooms(): ReadonlyMap<string, SockaRoomBundle<TContract, TData>> {\n\t\t\treturn rooms;\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../../src/server/attachSockaWebSocket.ts","../../src/server/room-registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { SockaContractBound } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport { dispatchSockaInboundMessage } from \"./dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfigUnion,\n} from \"./SockaWebSocketSession\";\n\nexport type AttachedSockaWebSocket<\n\tTContract extends SockaContractBound,\n\tTData,\n> = {\n\tsession: SockaWebSocketSession<TContract, TData>;\n\t/** Remove listeners and delete this session from the map (idempotent). */\n\tdispose: () => void;\n};\n\n/**\n * Register WebSocket `message` / `close` handlers, insert the session into\n * `sessions`, and return `{ session, dispose }`. `dispose` runs\n * {@link SockaWebSocketSession.invokeHandleClose} once, then removes listeners\n * (also triggered by `close`).\n */\nexport function attachSockaWebSocket<\n\tTContract extends SockaContractBound,\n\tTData,\n>(\n\twebsocket: WebSocket,\n\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>,\n\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n\tinit?: SockaWebSocketInit,\n): AttachedSockaWebSocket<TContract, TData> {\n\tconst session = new SockaWebSocketSession(websocket, sessions, config, init);\n\tsessions.set(websocket, session);\n\trunSockaSessionOnAttached(config, session);\n\n\tlet shuttingDown = false;\n\n\tconst finalize = (): void => {\n\t\twebsocket.removeEventListener(\"message\", onMessage);\n\t\twebsocket.removeEventListener(\"close\", onClose);\n\t\tsessions.delete(websocket);\n\t};\n\n\tconst shutdown = (): void => {\n\t\tif (shuttingDown) return;\n\t\tshuttingDown = true;\n\t\tvoid (async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tawait session.invokeHandleClose();\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tfinalize();\n\t\t\t}\n\t\t})().catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\tadapter: \"attach\",\n\t\t\t\terror,\n\t\t\t});\n\t\t\tfinalize();\n\t\t});\n\t};\n\n\tconst onMessage = (ev: MessageEvent): void => {\n\t\tconst wf = config.wireFormat ?? \"json\";\n\t\tvoid dispatchSockaInboundMessage(session, wf, ev.data).catch(\n\t\t\t(error: unknown) => {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"attach\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t};\n\n\tconst onClose = (): void => {\n\t\tshutdown();\n\t};\n\n\twebsocket.addEventListener(\"message\", onMessage);\n\twebsocket.addEventListener(\"close\", onClose);\n\n\treturn { session, dispose: shutdown };\n}\n","import type { SockaContractBound } from \"../core/contract\";\nimport type { SockaWebSocketSession } from \"./SockaWebSocketSession\";\nimport type { SockaWebSocketSessionConfig } from \"./SockaWebSocketSessionConfig\";\n\nexport type SockaRoomBundle<TContract extends SockaContractBound, TData> = {\n\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n};\n\n/**\n * Per-room {@link SockaWebSocketSession} maps and configs for Bun/Hono multi-room\n * apps (one bundle per `roomId`).\n */\nexport function createSockaRoomRegistry<\n\tTContract extends SockaContractBound,\n\tTData,\n>(\n\tmakeConfig: (\n\t\troomId: string,\n\t\tsessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>,\n\t) => SockaWebSocketSessionConfig<TContract, TData>,\n): {\n\tget(roomId: string): SockaRoomBundle<TContract, TData>;\n\treadonly rooms: ReadonlyMap<string, SockaRoomBundle<TContract, TData>>;\n} {\n\tconst rooms = new Map<string, SockaRoomBundle<TContract, TData>>();\n\treturn {\n\t\tget(roomId: string): SockaRoomBundle<TContract, TData> {\n\t\t\tlet r = rooms.get(roomId);\n\t\t\tif (!r) {\n\t\t\t\tconst sessionMap = new Map<\n\t\t\t\t\tWebSocket,\n\t\t\t\t\tSockaWebSocketSession<TContract, TData>\n\t\t\t\t>();\n\t\t\t\tconst config = makeConfig(roomId, sessionMap);\n\t\t\t\tr = { sessionMap, config };\n\t\t\t\trooms.set(roomId, r);\n\t\t\t}\n\t\t\treturn r;\n\t\t},\n\t\tget rooms(): ReadonlyMap<string, SockaRoomBundle<TContract, TData>> {\n\t\t\treturn rooms;\n\t\t},\n\t};\n}\n"]}
@@ -29,6 +29,33 @@ type SockaContractConfig = {
29
29
  readonly calls: Record<string, SockaProcedureDef>;
30
30
  readonly pushes?: Record<string, StandardSchemaV1>;
31
31
  };
32
+ /** Runtime contract returned by {@link defineSocka}, preserving full generic types. */
33
+ type SockaContract<T extends SockaContractConfig = SockaContractConfig> = {
34
+ readonly calls: T["calls"];
35
+ readonly pushes: T extends {
36
+ pushes: Record<string, StandardSchemaV1>;
37
+ } ? T["pushes"] : Record<string, never>;
38
+ };
39
+ /**
40
+ * Wide config shape for generic *bounds* on APIs that must accept any `defineSocka`
41
+ * result, including contracts with **server pushes**.
42
+ *
43
+ * `SockaContract` instantiated with {@link SockaContractConfig} alone resolves
44
+ * `pushes` to `Record<string, never>` (because `pushes` is optional, so the
45
+ * conditional in {@link SockaContract} does not keep concrete push fields). A generic
46
+ * bound of `extends SockaContract<SockaContractConfig>` therefore **rejects** real
47
+ * contracts with named server pushes. Use {@link SockaContractBound} (equivalently
48
+ * `extends SockaContract<SockaContractConfigBound>`) for those constraints.
49
+ */
50
+ type SockaContractConfigBound = {
51
+ readonly calls: Record<string, SockaProcedureDef>;
52
+ readonly pushes: Record<string, StandardSchemaV1>;
53
+ };
54
+ /**
55
+ * Widen any concrete `defineSocka` contract for use in `extends` constraints
56
+ * (e.g. `SockaDoSession`, `SockaSession`, `useSockaSession`).
57
+ */
58
+ type SockaContractBound = SockaContract<SockaContractConfigBound>;
32
59
  /**
33
60
  * When call keys are a **narrow** object type, rejects keys in
34
61
  * {@link ReservedSockaProcedureName} (thenable / `Object.prototype` hazards on
@@ -37,13 +64,6 @@ type SockaContractConfig = {
37
64
  * `send`).
38
65
  */
39
66
  type ValidateSockaCallKeys<P extends Record<string, SockaProcedureDef>> = string extends keyof P ? P : keyof P & ReservedSockaProcedureName extends never ? P : never;
40
- /** Runtime contract returned by {@link defineSocka}, preserving full generic types. */
41
- type SockaContract<T extends SockaContractConfig = SockaContractConfig> = {
42
- readonly calls: T["calls"];
43
- readonly pushes: T extends {
44
- pushes: Record<string, StandardSchemaV1>;
45
- } ? T["pushes"] : Record<string, never>;
46
- };
47
67
  /** Inferred client return type for a call: payload type or `void` when `output` is omitted. */
48
68
  type InferSockaCallReturn<P extends SockaProcedureDef> = P["output"] extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<P["output"]> : void;
49
69
  type CallFn<P extends SockaProcedureDef> = P extends {
@@ -52,7 +72,7 @@ type CallFn<P extends SockaProcedureDef> = P extends {
52
72
  /**
53
73
  * Infers the typed `session.send.*` method map for a contract.
54
74
  */
55
- type InferSockaSend<C extends SockaContract> = {
75
+ type InferSockaSend<C extends SockaContractBound> = {
56
76
  [K in keyof C["calls"]]: CallFn<C["calls"][K]>;
57
77
  };
58
78
  type HandlerOut<P extends SockaProcedureDef> = P["output"] extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<P["output"]> | Promise<StandardSchemaV1.InferOutput<P["output"]>> : void | Promise<void>;
@@ -66,18 +86,18 @@ type HandlerFn<P extends SockaProcedureDef, TSession> = P extends {
66
86
  * When `output` is omitted (fire-and-forget), the handler should return `void`; the
67
87
  * server does not send a success response.
68
88
  */
69
- type InferSockaHandlers<C extends SockaContract, TSession> = {
89
+ type InferSockaHandlers<C extends SockaContractBound, TSession> = {
70
90
  [K in keyof C["calls"]]: HandlerFn<C["calls"][K], TSession>;
71
91
  };
72
92
  type InferPushPayload<S extends StandardSchemaV1> = StandardSchemaV1.InferOutput<S>;
73
93
  /**
74
94
  * Payload type for a contract push (output of the push's Standard Schema).
75
95
  */
76
- type InferSockaPushPayload<C extends SockaContract<SockaContractConfig>, K extends keyof C["pushes"]> = C["pushes"][K] extends StandardSchemaV1 ? InferPushPayload<C["pushes"][K]> : never;
96
+ type InferSockaPushPayload<C extends SockaContractBound, K extends keyof C["pushes"]> = C["pushes"][K] extends StandardSchemaV1 ? InferPushPayload<C["pushes"][K]> : never;
77
97
  /**
78
98
  * Infers the typed push subscription handler map for a contract's `pushes`.
79
99
  */
80
- type InferSockaPushHandlers<C extends SockaContract> = {
100
+ type InferSockaPushHandlers<C extends SockaContractBound> = {
81
101
  [K in keyof C["pushes"]]: C["pushes"][K] extends StandardSchemaV1 ? (payload: InferPushPayload<C["pushes"][K]>) => void | Promise<void> : never;
82
102
  };
83
103
  /**
@@ -275,4 +295,4 @@ declare function defaultReportError(event: SockaReportError): void;
275
295
  /** Invokes the optional `reportError` callback when provided, otherwise `defaultReportError`. */
276
296
  declare function reportSockaError(reportError: ((event: SockaReportError) => void) | undefined, event: SockaReportError): void;
277
297
 
278
- export { type DecodedSockaWire as D, type InferSockaSend as I, RESERVED_SOCKA_PROCEDURE_NAMES as R, type SockaContract as S, type ValidateSockaCallKeys as V, type SockaContractConfig as a, type SockaProcedureDef as b, type InferSockaHandlers as c, defineSocka as d, type InferSockaPushHandlers as e, type InferSockaPushPayload as f, type ReservedSockaProcedureName as g, SockaError as h, SOCKA_WIRE_VERSION as i, SockaWireError as j, type SockaClientRequestFrame as k, type SockaServerErrorFrame as l, type SockaServerEventFrame as m, type SockaServerResponseFrame as n, type SockaWireFrame as o, decodeSockaWire as p, encodeClientRequest as q, encodeServerResponse as r, encodeServerError as s, encodeServerEvent as t, encodeSockaWire as u, parseWirePayload as v, type SockaWireFormat as w, defaultReportError as x, reportSockaError as y, type SockaReportError as z };
298
+ export { reportSockaError as A, type SockaReportError as B, type DecodedSockaWire as D, type InferSockaSend as I, RESERVED_SOCKA_PROCEDURE_NAMES as R, type SockaContract as S, type ValidateSockaCallKeys as V, type SockaContractConfig as a, type SockaContractConfigBound as b, type SockaContractBound as c, defineSocka as d, type SockaProcedureDef as e, type InferSockaHandlers as f, type InferSockaPushHandlers as g, type InferSockaPushPayload as h, type ReservedSockaProcedureName as i, SockaError as j, SOCKA_WIRE_VERSION as k, SockaWireError as l, type SockaClientRequestFrame as m, type SockaServerErrorFrame as n, type SockaServerEventFrame as o, type SockaServerResponseFrame as p, type SockaWireFrame as q, decodeSockaWire as r, encodeClientRequest as s, encodeServerResponse as t, encodeServerError as u, encodeServerEvent as v, encodeSockaWire as w, parseWirePayload as x, type SockaWireFormat as y, defaultReportError as z };
package/docs/README.md CHANGED
@@ -5,6 +5,8 @@ In-repo guides for the **[Socka](../README.md)** library (**npm** [`@firtoz/sock
5
5
  | Doc | Description |
6
6
  |-----|-------------|
7
7
  | [Getting started](./getting-started.md) | Multi-room chat tutorial (RPC + pushes + history); links to **chatroom-*** examples |
8
+ | [React + Durable Objects](./react-durable-objects.md) | Shared `defineSocka`, `SockaWebSocketDO`, `useSockaSession`, `pushHandlers`—no casts |
9
+ | [Collaborative realtime](./collaborative-realtime.md) | Canvas / whiteboard-style contract sketch (ops, drafts, batched cursors) |
8
10
  | [Peers](./peers.md) | Which dependencies to install per import path and why |
9
11
  | [Multi-room](./multi-room.md) | Scopes, patterns per runtime, pitfalls |
10
12
  | [Lifecycle](./lifecycle.md) | `onAttached`, inbound RPCs, `handleClose` ordering |
package/docs/client.md CHANGED
@@ -20,6 +20,13 @@ Full list: **[Reference — Client configuration](./reference.md#client-configur
20
20
 
21
21
  Calls that **omit** **`output`** resolve **`await session.send.*`** as soon as the request is sent; they do **not** wait for **`serverResponse`**. If the server returns **`serverError`**, handle it with **`reportError`** (see **[Reference — Errors](./reference.md#errors-and-observability)**) — there is no rejected promise for that call. Prefer **`output: z.void()`** when you still want **`await`** to track success or failure through the returned promise.
22
22
 
23
+ ### Fire-and-forget observability
24
+
25
+ For **output-less** calls, the **`send`** method returns a **`Promise<void>`** that resolves when the **outbound request is queued**; it does **not** reject if the server later answers with **`serverError`**. Patterns like **`void send.sendCursor({ ... }).catch(() => undefined)`** therefore **do not** observe **server-side** handler failures, validation errors on the response path, or structured **`SockaError`** for that procedure.
26
+
27
+ - **Client** — pass **`reportError`** to **`SockaSession`** or **`useSockaSession`** / **`useSocka`** (and **`SockaSessionProvider`**, which forwards session options). Inspect **`event.kind`**; fire-and-forget server failures often surface as **`clientFireAndForgetRpcError`**. See **[Reference — Optional output (fire-and-forget)](./reference.md#optional-output-fire-and-forget)** and **[Reference — RPC handler errors](./reference.md#rpc-handler-errors)**.
28
+ - **Server** — use **`onHandlerError`** and **`onValidationError`** on **`SockaWebSocketSessionConfig`** / **`SockaDoSessionConfig`** for logs and metrics when a handler throws or inbound frames fail before your handler.
29
+
23
30
  **Call names** — For literal `calls` objects, **`defineSocka`** rejects names that would make **`session.send`** Promise-like or clash with object shape (e.g. **`then`**, **`toString`**). If you use a wide **`Record<string, SockaProcedureDef>`**, TypeScript cannot apply that check; **`SockaSession`** still validates at construction (see **`RESERVED_SOCKA_PROCEDURE_NAMES`** in **`@firtoz/socka/core`**).
24
31
 
25
32
  ```ts
@@ -54,6 +61,16 @@ function App() {
54
61
  useSockaSession(myContract, { url: "wss://...", wireFormat: "msgpack" }, []);
55
62
  ```
56
63
 
64
+ Optional **`pushHandlers`** in the second argument matches **`Partial<InferSockaPushHandlers<typeof myContract>>`** — see **[Pushes — Typing `pushHandlers`](./pushes.md#typing-pushhandlers)**.
65
+
66
+ ### SSR and WebSocket URLs
67
+
68
+ `useSockaSession` and **`SockaSessionProvider`** need a real **`url`** for **`new WebSocket(url)`**. In **React Router SSR** (or any **SSR** tree), `window` is missing on the server, and derived **`ws:` / `wss:`** URLs must not run during **render** with a **fake** placeholder like `ws://127.0.0.1/...` (it can connect to the wrong place or open before you know the page origin).
69
+
70
+ **Recommended pattern:** In the route module, set **`const [url, setUrl] = useState<string | null>(null)`**, and in **`useEffect`** (browser-only), build a **`wss://` / `ws://`** URL from **`window.location`** (protocol, host, path to your Worker’s WebSocket route). If **`url === null`**, render a **loading** shell; only render a child component that calls **`useSockaSession(myContract, { url, pushHandlers? }, [url])`** when **`url`** is set. Keep **`url`** in the hook **`deps`** so identity changes re-open the right socket.
71
+
72
+ **React + Durable Object** end-to-end wiring: **[React + Durable Objects](./react-durable-objects.md)**.
73
+
57
74
  ### `useSocka` — hold a `SockaSession` ref
58
75
 
59
76
  Use **`useSocka(options, deps)`** when you need the full **`SockaSession`** (e.g. **`session.subscribe`**, **`session.client`**, **`waitForPush`**, or passing the session into non-React helpers). It returns **`{ ready, sessionRef, status, reconnecting, reconnectAttempt }`** — read **`sessionRef.current`** in effects or callbacks (it is **`null`** until the effect runs). Use **`reconnecting`** or **`status === "reconnecting"`** for banners; **`reconnectAttempt`** counts backoff attempts.
@@ -0,0 +1,61 @@
1
+ # Collaborative realtime (canvas / whiteboard)
2
+
3
+ Chat examples cover **room messages** and **history** well. A **shared canvas** often adds different concerns:
4
+
5
+ - **Durable, committed** operations (authoritative state, sequence numbers).
6
+ - **Transient** live updates (in-progress strokes, cursors) that should not bloat the document history.
7
+ - **High-frequency** pointer data, often **batched** and pushed to peers.
8
+
9
+ The pattern below is **contract shape only**—it is not a full app. It mirrors how you might name **RPCs** and **pushes** for Driftboard-style flows. For end-to-end wiring, see **[React + Durable Objects](./react-durable-objects.md)**, **[Multi-room](./multi-room.md)**, and the runnable **[chatroom-do](../../examples/chatroom-do)** example.
10
+
11
+ ## Example contract (sketch)
12
+
13
+ ```ts
14
+ import { defineSocka } from "@firtoz/socka/core";
15
+ import { z } from "zod";
16
+
17
+ // Replace with your real Standard Schema–compatible shapes
18
+ const opSchema = z.object({ type: z.string() });
19
+ const shapeSchema = z.object({ id: z.string() });
20
+ const cursorInputSchema = z.object({
21
+ x: z.number(),
22
+ y: z.number(),
23
+ pressure: z.number().optional(),
24
+ });
25
+ const cursorSchema = z.object({ userId: z.string() }).and(cursorInputSchema);
26
+
27
+ export const boardContract = defineSocka({
28
+ calls: {
29
+ // Request/response: commit an operation and return monotonic `seq`
30
+ applyOp: {
31
+ input: z.object({ op: opSchema }),
32
+ output: z.object({ seq: z.number() }),
33
+ },
34
+ // Fire-and-forget: transient draft; omit `output` (no useless `{ ok: true }` at high rate)
35
+ sendDraft: {
36
+ input: z.object({ shape: shapeSchema.nullable() }),
37
+ },
38
+ sendCursor: {
39
+ input: cursorInputSchema,
40
+ },
41
+ },
42
+ pushes: {
43
+ opApplied: z.object({ op: opSchema, seq: z.number() }),
44
+ draftUpdated: z.object({
45
+ userId: z.string(),
46
+ shape: shapeSchema.nullable(),
47
+ }),
48
+ cursorBatch: z.object({ cursors: z.array(cursorSchema) }),
49
+ },
50
+ });
51
+ ```
52
+
53
+ **High-frequency cursors and drafts** — **omit** `output` on **`sendCursor`** and **`sendDraft`** so the client’s **`send`** does not wait for a **`serverResponse`**. If you need an **ack** for a one-off action, use **`output: z.void()`** or a real **`output` schema** instead. See **[Client — Fire-and-forget](./client.md#fire-and-forget)** and **[Fire-and-forget observability](./client.md#fire-and-forget-observability)**.
54
+
55
+ **Optional fields in hand-written types** — If you define **`Point`** next to the schema and use **`exactOptionalPropertyTypes`**, keep optional fields consistent with the inferred zod type (e.g. **`pressure?: number | undefined`**) or use **`z.infer<typeof cursorInputSchema>`**—see **[TypeScript and exact optional properties](./reference.md#typescript-and-exact-optional-properties)**.
56
+
57
+ ## See also
58
+
59
+ - **[Pushes](./pushes.md)** — `broadcastPush`, `pushHandlers` typing
60
+ - **[Durable Objects](./durable-objects.md)** — one DO instance per room, `sessions` map
61
+ - **[Backpressure](./backpressure.md)** — when updates flood the connection
@@ -7,6 +7,30 @@ On Cloudflare **Durable Objects**, socka splits into two pieces:
7
7
 
8
8
  You still define one **`defineSocka`** contract; this page is only about hosting it on a **Durable Object**.
9
9
 
10
+ ## Typing `SockaDoSession` in app code
11
+
12
+ **Prefer** a concrete contract on the session class, not **`any`**, so **`InferSockaHandlers`**, pushes, and **`session.data`** stay accurate:
13
+
14
+ ```ts
15
+ export class MySockaSession extends SockaDoSession<
16
+ typeof myContract,
17
+ SessionData,
18
+ Env
19
+ > {
20
+ constructor(
21
+ ws: WebSocket,
22
+ sessions: Map<WebSocket, MySockaSession>,
23
+ config: SockaDoSessionConfig<typeof myContract, SessionData, Env>,
24
+ ) {
25
+ super(ws, sessions, config);
26
+ }
27
+ }
28
+ ```
29
+
30
+ A runnable example is **[`examples/chatroom-do/src/do.ts`](../../../examples/chatroom-do/src/do.ts)** (`ChatSockaSession` extends **`SockaDoSession<typeof chatContract, …>`**).
31
+
32
+ **Library note:** The **`SockaWebSocketDO`** base class in **`@firtoz/socka/do`** uses a session generic that erases the contract in a few **`any`** positions for variance across **`SockaDoSession`** subclasses. That is an internal detail—**application code** should still pass **`typeof myContract`** to **`SockaDoSession`** / **`SockaDoSessionConfig`** and use **`class MySession extends SockaDoSession<typeof myContract, …>`** as above. Do not copy **`any`** from library typings into your own session types.
33
+
10
34
  ## Cloudflare Worker checklist
11
35
 
12
36
  This is the **Cloudflare** side (bindings, Wrangler, generated types)—not socka-specific, but you need it before **`SockaWebSocketDO`** can run.
package/docs/internals.md CHANGED
@@ -45,7 +45,7 @@ Clients generate **`id`** strings per request; servers echo them on **`serverRes
45
45
 
46
46
  ## TypeScript: `SockaWebSocketDO` and contract erasure
47
47
 
48
- `@firtoz/socka/do` **erases** the contract slot on **`SockaWebSocketDO`** so concrete `defineSocka(...)` contracts stay strict under TypeScript. If a generic base class rejects your session type, keep using **your** contract type from the module where you called **`defineSocka`**—do not expect an unconstrained `SockaContract<SockaContractConfig>` to accept every concrete contract without that erasure.
48
+ `@firtoz/socka/do` uses **`any` for the contract type parameter on `SockaWebSocketDO`’s session generic** because **`SockaDoSession` is invariant in `TContract`**: `SockaDoSession<typeof myContract, …>` does not extend `SockaDoSession<SockaContractBound, …>` at the class level even when `typeof myContract` satisfies **`SockaContractBound`**. Session *values* and APIs that take **`TContract extends SockaContractBound`** (not a `SockaDoSession` subclass) should use **`typeof myContract`** from the module where you called **`defineSocka`**—that bound is wide enough for contracts **with** server pushes; the old `extends SockaContract<SockaContractConfig>` shape was not.
49
49
 
50
50
  ---
51
51
 
package/docs/pushes.md CHANGED
@@ -13,6 +13,23 @@ export const myContract = defineSocka({
13
13
  });
14
14
  ```
15
15
 
16
+ ## Typing `pushHandlers`
17
+
18
+ On **`SockaSession`** and **`useSockaSession`**, the **`pushHandlers`** option is **`Partial<InferSockaPushHandlers<typeof myContract>>`**. You can use **`satisfies`** to check an object literal without changing its inferred payload types (payloads for each key still narrow from the contract):
19
+
20
+ ```ts
21
+ import type { InferSockaPushHandlers } from "@firtoz/socka/core";
22
+ import { myContract } from "./contract";
23
+
24
+ const pushHandlers = {
25
+ itemsChanged: (payload) => {
26
+ // `payload` is typed from the contract
27
+ },
28
+ } satisfies Partial<InferSockaPushHandlers<typeof myContract>>;
29
+ ```
30
+
31
+ Types are exported from **`@firtoz/socka/core`** (same as **`@firtoz/socka`**) — see **[Reference — Type inference](./reference.md#type-inference)**.
32
+
16
33
  ## Server: emit and broadcast
17
34
 
18
35
  - **`await session.emitPush("itemsChanged", payload)`** — send one **validated** push to **this** socket (typical for private notifications).
@@ -36,7 +53,7 @@ const payload = await session.subscribe.waitForPush("itemsChanged", {
36
53
  });
37
54
  ```
38
55
 
39
- Use **`InferSockaPushPayload<typeof myContract, "itemsChanged">`** (from **`@firtoz/socka/core`**) when typing callbacks or reducers. If the server never emits a push your client subscribed to, **`waitForPush`** can time out—handle **`AbortSignal`** and UI loading states.
56
+ Use **`InferSockaPushPayload<typeof myContract, "itemsChanged">`** (from **`@firtoz/socka/core`**) when typing callbacks or reducers one-off. For tables of handlers, prefer **`InferSockaPushHandlers`** (see [Typing `pushHandlers`](#typing-pushhandlers) above). If the server never emits a push your client subscribed to, **`waitForPush`** can time out—handle **`AbortSignal`** and UI loading states.
40
57
 
41
58
  ## Who receives `broadcastPush`?
42
59
 
@@ -0,0 +1,107 @@
1
+ # React + Cloudflare Durable Objects
2
+
3
+ This page is a **minimal wiring guide** for the common stack: **one shared `defineSocka` contract**, a **Durable Object** extending **`SockaWebSocketDO`**, and a **React** client using **`useSockaSession`**. A full app with routing and persistence is **[chatroom-do](../../examples/chatroom-do)** in this repo.
4
+
5
+ ## Install
6
+
7
+ You need **`@firtoz/socka`**, **`@firtoz/socka/do`**, **`@firtoz/socka/react`**, and peer deps for Cloudflare and **`@firtoz/websocket-do`**—see **[Peers](./peers.md)** and **[Durable Objects](./durable-objects.md)** (Wrangler, `Env` types, **do not** hand-edit generated worker env `.d.ts` files).
8
+
9
+ ## Shared contract (no client casts)
10
+
11
+ Export **one** contract from a shared module and import the **same reference** in the worker and the browser. You should **not** need `as never`, `as unknown`, manual `send` types, or `InferSockaSend` in normal app code—if TypeScript widens or fails to infer, align **Zod (or other Standard Schema) output** with any hand-written types (see **[TypeScript and exact optional properties](./reference.md#typescript-and-exact-optional-properties)**) and re-export **types** from the schema with **`z.infer`** when possible.
12
+
13
+ ```ts
14
+ // shared/contract.ts
15
+ import { defineSocka } from "@firtoz/socka/core";
16
+ import { z } from "zod";
17
+
18
+ export const roomContract = defineSocka({
19
+ calls: {
20
+ list: {
21
+ input: z.object({}).optional(),
22
+ output: z.object({ items: z.array(z.string()) }),
23
+ },
24
+ sendCursor: {
25
+ input: z.object({ x: z.number(), y: z.number() }),
26
+ // no `output` — fire-and-forget; see README “Call `output` shapes”
27
+ },
28
+ },
29
+ pushes: {
30
+ cursorBatch: z.object({
31
+ cursors: z.array(z.object({ x: z.number(), y: z.number() })),
32
+ }),
33
+ },
34
+ });
35
+ ```
36
+
37
+ **Output shapes** — For high-frequency messages (cursors, live drafts), **omit** `output`. Use **`output: z.void()`** when the client should **await** a server ack. See **[Client — Fire-and-forget](./client.md#fire-and-forget)** and **[Reference — Optional output](./reference.md#optional-output-fire-and-forget)**.
38
+
39
+ ## Durable Object
40
+
41
+ Subclass **`SockaWebSocketDO`** and return a **`SockaDoSession`** (or subclass) from **`createSockaSession`**. Type your session with **`typeof roomContract`**, not `any` (see **[Durable Objects — Typing `SockaDoSession` in app code](./durable-objects.md#typing-sockadosession-in-app-code)**). Full patterns: **[Durable Objects](./durable-objects.md)**, example **[`examples/chatroom-do/src/do.ts`](../../examples/chatroom-do/src/do.ts)**.
42
+
43
+ ```ts
44
+ // do.ts (sketch)
45
+ import {
46
+ SockaDoSession,
47
+ SockaWebSocketDO,
48
+ type SockaDoSessionConfig,
49
+ } from "@firtoz/socka/do";
50
+ import { roomContract } from "./contract";
51
+
52
+ type SessionData = { userId: string };
53
+
54
+ export class RoomSockaSession extends SockaDoSession<
55
+ typeof roomContract,
56
+ SessionData,
57
+ Env
58
+ > {
59
+ constructor(
60
+ ws: WebSocket,
61
+ sessions: Map<WebSocket, RoomSockaSession>,
62
+ config: SockaDoSessionConfig<typeof roomContract, SessionData, Env>,
63
+ ) {
64
+ super(ws, sessions, config);
65
+ }
66
+ }
67
+
68
+ export class RoomDo extends SockaWebSocketDO<RoomSockaSession, Env> {
69
+ constructor(ctx: DurableObjectState, env: Env) {
70
+ super(ctx, env, {
71
+ createSockaSession: (_c, ws) =>
72
+ new RoomSockaSession(ws, this.sessions, this.buildConfig()),
73
+ });
74
+ }
75
+ // `buildConfig()` returns SockaDoSessionConfig<typeof roomContract, SessionData, Env>
76
+ }
77
+ ```
78
+
79
+ ## React client
80
+
81
+ Use **`useSockaSession(contract, { url, pushHandlers? }, deps)`** with the **same** `roomContract` import. For **`pushHandlers`**, you can use **`satisfies Partial<InferSockaPushHandlers<typeof roomContract>>`**—see **[Pushes — Typing `pushHandlers`](./pushes.md#typing-pushhandlers)**.
82
+
83
+ ```tsx
84
+ // RoomClient.tsx (sketch; URL must exist before connecting — see Client “SSR and WebSocket URLs”)
85
+ import { useSockaSession } from "@firtoz/socka/react";
86
+ import { roomContract } from "./contract";
87
+
88
+ function RoomClient({ url }: { url: string }) {
89
+ const { ready, send } = useSockaSession(
90
+ roomContract,
91
+ { url, pushHandlers: { cursorBatch: (p) => { /* set state */ } } },
92
+ [url],
93
+ );
94
+ // `await send.list({})` — request/response
95
+ // `void send.sendCursor({ x, y })` — fire-and-forget; see observability in Client + Reference
96
+ return null;
97
+ }
98
+ ```
99
+
100
+ Whiteboard-style contracts (ops + draft + cursors) are sketched in **[Collaborative realtime](./collaborative-realtime.md)**.
101
+
102
+ ## See also
103
+
104
+ - **[Client](./client.md)** — `useSockaSession`, **SSR and WebSocket URLs**, fire-and-forget **observability**
105
+ - **[Reference](./reference.md)** — `InferSocka*` types, `reportError`, `SockaReportError` kinds
106
+ - **[Durable Objects](./durable-objects.md)** — hibernation, `session.update()`, `createData`
107
+ - **[chatroom-do](../../examples/chatroom-do)** — SQLite + Drizzle + UI
package/docs/reference.md CHANGED
@@ -5,20 +5,33 @@ User-facing **API** and **configuration**. For **wire protocol details** (frame
5
5
  ## Type inference
6
6
 
7
7
  ```ts
8
- import type { InferSockaSend, InferSockaHandlers } from "@firtoz/socka/core";
8
+ import type {
9
+ InferSockaSend,
10
+ InferSockaHandlers,
11
+ InferSockaPushHandlers,
12
+ InferSockaPushPayload,
13
+ } from "@firtoz/socka/core";
9
14
 
10
15
  type Send = InferSockaSend<typeof myContract>;
11
16
  type Handlers = InferSockaHandlers<
12
17
  typeof myContract,
13
18
  SockaWebSocketSession<typeof myContract>
14
19
  >;
20
+ type PushHandlers = Partial<InferSockaPushHandlers<typeof myContract>>;
21
+ type RoomMessagePayload = InferSockaPushPayload<typeof myContract, "roomMessage">;
15
22
  ```
16
23
 
17
- **`InferSockaSend`** — Call names become methods on **`session.send`**; inputs/outputs follow the contract. **`InferSockaHandlers`** — Server handler arity matches **`calls`** (with or without `input`).
24
+ **`InferSockaSend`** — Call names become methods on **`session.send`**; inputs/outputs follow the contract. **`InferSockaHandlers`** — server handler arity matches **`calls`** (with or without `input`). **`InferSockaPushPayload`** — payload type for a given push name. Use **`Partial<InferSockaPushHandlers<typeof myContract>>`** (or **`satisfies Partial<...>`** on an object literal) to type **`pushHandlers`** on **`SockaSession`** / **`useSockaSession`** — see **[Pushes — Typing `pushHandlers`](./pushes.md#typing-pushhandlers)**.
25
+
26
+ ### TypeScript and exact optional properties
27
+
28
+ If you define **hand-written** types next to Zod (or other Standard Schema) objects with **`z.optional()`** (or **`.optional()`** on a field) and you enable TypeScript’s **`exactOptionalPropertyTypes`**, a property that is “optional” in the schema is often **inferred** as **`name?: T | undefined`**, not **`name?: T`**. A plain **`pressure?: number`** in your `type` can therefore **widen** differently from `z.infer<typeof pointSchema>`, and **contract** inference (or assignment to a handler parameter) can fail.
29
+
30
+ **Remedies:** prefer **`z.infer<typeof pointSchema>`** (or the inferred output type of your field) for the type; or in hand-written types, include **`| undefined`**, e.g. **`pressure?: number | undefined`**, so they match the schema end to end.
18
31
 
19
32
  ### Optional output (fire-and-forget)
20
33
 
21
- If a call omits **`output`**, the server sends **no** **`serverResponse`** on success, and the client **`send`** method returns **`Promise<void>`** that resolves after the request is queued to the socket (not after the server runs the handler). **`output: z.void()`** keeps full request/response: the server still sends **`serverResponse`** and the client **`await`** waits for it. For output-less calls, server **`serverError`** frames include an optional **`rpc`** field so **`reportError`** can attribute failures when there is no pending promise.
34
+ If a call omits **`output`**, the server sends **no** **`serverResponse`** on success, and the client **`send`** method returns **`Promise<void>`** that resolves after the request is queued to the socket (not after the server runs the handler). **`output: z.void()`** keeps full request/response: the server still sends **`serverResponse`** and the client **`await`** waits for it. For output-less calls, server **`serverError`** frames include an optional **`rpc`** field so **`reportError`** can attribute failures when there is no pending promise. For **client** observability when you cannot rely on **`.catch`** on **`send`**, see **[Client — Fire-and-forget observability](./client.md#fire-and-forget-observability)**.
22
35
 
23
36
  ## Errors and observability
24
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/socka",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "type": "module",
5
5
  "description": "Standard Schema–first WebSocket RPC for TypeScript — Bun, Hono, Node ws, Cloudflare Workers, Durable Objects",
6
6
  "main": "./dist/core/index.js",
@@ -29,7 +29,7 @@ description: Standard Schema socka contracts (defineSocka), v1 wire envelopes, S
29
29
 
30
30
  ## Durable Objects
31
31
 
32
- - **`SockaDoSession`** / **`SockaWebSocketDO`** in **`@firtoz/socka/do`**—see **`@firtoz/socka/do-session`** skill.
32
+ - **`SockaDoSession`** / **`SockaWebSocketDO`** in **`@firtoz/socka/do`**—see **`@firtoz/socka/do-session`** skill. Human-oriented wiring for **React + DO**: [React + Durable Objects](../../../docs/react-durable-objects.md); **canvas / whiteboard** contract sketch: [Collaborative realtime](../../../docs/collaborative-realtime.md).
33
33
 
34
34
  ## Low-level
35
35
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/dispatchSockaInboundMessage.ts"],"names":[],"mappings":";AAUA,eAAsB,2BAAA,CAIrB,OAAA,EACA,UAAA,EACA,IAAA,EACgB;AAChB,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC7B,IAAA,MAAM,OAAA,CAAQ,iBAAiB,IAAI,CAAA;AACnC,IAAA;AAAA,EACD;AACA,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AAC3D,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,OAAA,CAAQ,gBAAA,CAAiB,IAAA,CAAK,QAAA,CAAS,MAAM,CAAC,CAAA;AACpD,MAAA;AAAA,IACD;AACA,IAAA,MAAM,QAAQ,mBAAA,CAAoB,IAAI,UAAA,CAAW,IAAI,EAAE,MAAM,CAAA;AAC7D,IAAA;AAAA,EACD;AACA,EAAA,IAAI,gBAAgB,WAAA,EAAa;AAChC,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAI,aAAY,CAAE,MAAA,CAAO,IAAI,CAAC,CAAA;AAC7D,MAAA;AAAA,IACD;AACA,IAAA,MAAM,OAAA,CAAQ,oBAAoB,IAAI,CAAA;AACtC,IAAA;AAAA,EACD;AACA,EAAA,IAAI,gBAAgB,IAAA,EAAM;AACzB,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,OAAA,CAAQ,gBAAA,CAAiB,MAAM,IAAA,CAAK,MAAM,CAAA;AAAA,IACjD,CAAA,MAAO;AACN,MAAA,MAAM,OAAA,CAAQ,mBAAA,CAAoB,MAAM,IAAA,CAAK,aAAa,CAAA;AAAA,IAC3D;AACA,IAAA;AAAA,EACD;AACA,EAAA,IAAI,WAAA,CAAY,MAAA,CAAO,IAAI,CAAA,EAAG;AAC7B,IAAA,MAAM,CAAA,GAAI,IAAA;AACV,IAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,CAAA,CAAE,QAAQ,CAAA,CAAE,UAAA,EAAY,EAAE,UAAU,CAAA;AAChE,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC1B,MAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAI,aAAY,CAAE,MAAA,CAAO,IAAI,CAAC,CAAA;AAC7D,MAAA;AAAA,IACD;AACA,IAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA;AACvC,IAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AACb,IAAA,MAAM,OAAA,CAAQ,mBAAA,CAAoB,IAAA,CAAK,MAAM,CAAA;AAAA,EAC9C;AACD","file":"chunk-5WQTYLIC.js","sourcesContent":["import type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport type { SockaWebSocketSession } from \"./SockaWebSocketSession\";\n\n/**\n * Decode a WebSocket `message` payload and dispatch it to the session (same\n * behavior as the `message` handler installed by {@link attachSockaWebSocket}).\n * Use this when the runtime does not support `addEventListener` on the socket\n * (e.g. Bun {@link ServerWebSocket}) or when handling messages manually.\n */\nexport async function dispatchSockaInboundMessage<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tsession: SockaWebSocketSession<TContract, TData>,\n\twireFormat: SockaWireFormat,\n\tdata: MessageEvent[\"data\"],\n): Promise<void> {\n\tif (typeof data === \"string\") {\n\t\tawait session.handleRawMessage(data);\n\t\treturn;\n\t}\n\tif (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data)) {\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(data.toString(\"utf8\"));\n\t\t\treturn;\n\t\t}\n\t\tawait session.handleBinaryMessage(new Uint8Array(data).buffer);\n\t\treturn;\n\t}\n\tif (data instanceof ArrayBuffer) {\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(new TextDecoder().decode(data));\n\t\t\treturn;\n\t\t}\n\t\tawait session.handleBinaryMessage(data);\n\t\treturn;\n\t}\n\tif (data instanceof Blob) {\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(await data.text());\n\t\t} else {\n\t\t\tawait session.handleBinaryMessage(await data.arrayBuffer());\n\t\t}\n\t\treturn;\n\t}\n\tif (ArrayBuffer.isView(data)) {\n\t\tconst v = data;\n\t\tconst view = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);\n\t\tif (wireFormat === \"json\") {\n\t\t\tawait session.handleRawMessage(new TextDecoder().decode(view));\n\t\t\treturn;\n\t\t}\n\t\tconst copy = new Uint8Array(view.length);\n\t\tcopy.set(view);\n\t\tawait session.handleBinaryMessage(copy.buffer);\n\t}\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/SockaWebSocketSession.ts"],"names":[],"mappings":"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","file":"chunk-LVVCHLNW.js","sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { exhaustiveGuard } from \"@firtoz/maybe-error\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContract,\n\tSockaContractConfig,\n} from \"../core/contract\";\nimport {\n\tSockaWireError,\n\tdecodeSockaWire,\n\tencodeServerResponse,\n\tencodeServerError,\n\tencodeServerEvent,\n\ttype SockaClientRequestFrame,\n\ttype SockaWireFrame,\n} from \"../core/envelope\";\nimport {\n\tencodeSockaWire,\n\tparseWirePayload,\n\ttype SockaWireFormat,\n} from \"../core/wire-codec\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport { parseStandardSchema } from \"../core/validate\";\nimport { SockaError } from \"../core/socka-error\";\nimport type {\n\tSockaStrictWebSocketInit,\n\tSockaWebSocketInit,\n\tSockaWebSocketSessionConfig,\n\tSockaWebSocketSessionConfigLoose,\n\tSockaWebSocketSessionConfigUnion,\n} from \"./SockaWebSocketSessionConfig\";\n\n/** Session data with no fields — `createData` may be omitted (defaults to `{}`). */\ntype EmptySockaSessionData = Record<string, never>;\n\nexport type {\n\tSockaStrictWebSocketInit,\n\tSockaWebSocketInit,\n\tSockaWebSocketSessionConfig,\n\tSockaWebSocketSessionConfigLoose,\n\tSockaWebSocketSessionConfigUnion,\n};\n\nfunction isLooseUpgradeConfig<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n): config is SockaWebSocketSessionConfigLoose<TContract, TData> {\n\treturn (\n\t\t\"strictUpgradeRequest\" in config && config.strictUpgradeRequest === false\n\t);\n}\n\n/** Session that can send a wire-level server event (already validated). */\nexport type SockaEmitCapable = {\n\temitWireEvent(event: string, body: unknown): void;\n};\n\n/**\n * Contract-typed session surface for handlers that push to clients.\n */\nexport interface SockaPushSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n> {\n\temitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void>;\n\tbroadcastPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t\texcludeSelf?: boolean,\n\t): Promise<void>;\n}\n\n/**\n * Broadcast a socka server event to every session in the map (optionally\n * excluding the caller). Payload must already be contract-validated.\n *\n * Exclusion uses the **WebSocket** identity (`self.websocket`), not the session\n * object reference, so the same `sessions` map can hold `SockaDoSession` while\n * `broadcastPush` runs on `this.socka` (inner {@link SockaWebSocketSession}).\n */\nexport function broadcastSockaEventToPeers(\n\tsessions: Map<WebSocket, SockaEmitCapable>,\n\tself: SockaEmitCapable & { readonly websocket: WebSocket },\n\tevent: string,\n\tbody: unknown,\n\texcludeSelf = false,\n): void {\n\tfor (const [ws, session] of sessions) {\n\t\tif (excludeSelf && ws === self.websocket) continue;\n\t\tsession.emitWireEvent(event, body);\n\t}\n}\n\n/**\n * Runtime-agnostic socka server session: standard {@link WebSocket} wire\n * dispatch without Cloudflare Durable Object APIs.\n */\nexport class SockaWebSocketSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData = EmptySockaSessionData,\n> implements SockaPushSession<TContract>\n{\n\tprivate readonly config: SockaWebSocketSessionConfigUnion<TContract, TData>;\n\tprivate readonly wireFormat: SockaWireFormat;\n\tprivate _data!: TData;\n\n\tpublic constructor(\n\t\tpublic readonly websocket: WebSocket,\n\t\tprotected readonly sessions: Map<\n\t\t\tWebSocket,\n\t\t\tSockaWebSocketSession<TContract, TData>\n\t\t>,\n\t\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n\t\tinit?: SockaWebSocketInit,\n\t) {\n\t\tthis.config = config;\n\t\tthis.wireFormat = config.wireFormat ?? \"json\";\n\t\tif (isLooseUpgradeConfig(config)) {\n\t\t\tconst createData = config.createData as\n\t\t\t\t| ((init: SockaWebSocketInit) => TData)\n\t\t\t\t| undefined;\n\t\t\tconst create = createData ?? ((_i: SockaWebSocketInit) => ({}) as TData);\n\t\t\tthis._data = create(init ?? {});\n\t\t} else {\n\t\t\tif (!init?.request) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"socka: strict upgrade (default) requires a Request on the upgrade init (e.g. Bun upgrade with `data: { …, request: req }`, or Hono default sockaInit), or use SockaWebSocketSessionConfigLoose with strictUpgradeRequest: false\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst strictInit: SockaStrictWebSocketInit = { request: init.request };\n\t\t\tif (config.createData) {\n\t\t\t\tthis._data = config.createData(strictInit);\n\t\t\t} else {\n\t\t\t\tthis._data = {} as TData;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic get data(): TData {\n\t\treturn this._data;\n\t}\n\n\t/**\n\t * Session data for every connection in the same {@link sessions} map (same room),\n\t * optionally excluding this socket.\n\t */\n\tpublic listPeers(options?: { excludeSelf?: boolean }): TData[] {\n\t\tconst out: TData[] = [];\n\t\tfor (const [ws, s] of this.sessions) {\n\t\t\tif (options?.excludeSelf && ws === this.websocket) continue;\n\t\t\tout.push(s.data);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/**\n\t * Like {@link listPeers} but maps each peer {@link SockaWebSocketSession}\n\t * (e.g. when you need more than {@link #data}).\n\t */\n\tpublic listPeersWith<R>(\n\t\tmap: (session: SockaWebSocketSession<TContract, TData>) => R,\n\t\toptions?: { excludeSelf?: boolean },\n\t): R[] {\n\t\tconst out: R[] = [];\n\t\tfor (const [ws, s] of this.sessions) {\n\t\t\tif (options?.excludeSelf && ws === this.websocket) continue;\n\t\t\tout.push(map(s));\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Count of sessions in this room (same {@link sessions} map), optionally excluding self. */\n\tpublic peerCount(options?: { excludeSelf?: boolean }): number {\n\t\tlet n = 0;\n\t\tfor (const [ws] of this.sessions) {\n\t\t\tif (options?.excludeSelf && ws === this.websocket) continue;\n\t\t\tn += 1;\n\t\t}\n\t\treturn n;\n\t}\n\n\t/** Whether any peer sessions exist (optionally excluding self). */\n\tpublic hasPeers(options?: { excludeSelf?: boolean }): boolean {\n\t\treturn this.peerCount(options) > 0;\n\t}\n\n\t/**\n\t * Invokes the user {@link typeof SockaWebSocketSessionConfig.handleClose} callback.\n\t * Server adapters should call this when the WebSocket closes, **before** deleting\n\t * this session from the shared `sessions` map.\n\t */\n\tpublic async invokeHandleClose(): Promise<void> {\n\t\tawait this.config.handleClose(this);\n\t}\n\n\tpublic async handleRawMessage(rawMessage: string): Promise<void> {\n\t\tif (this.wireFormat !== \"json\") {\n\t\t\tawait this.reportValidationError(\n\t\t\t\tnew Error(\"socka: unexpected JSON frame in msgpack mode\"),\n\t\t\t\trawMessage,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst deserialize = this.config.deserializeJson ?? JSON.parse;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = deserialize(rawMessage);\n\t\t} catch {\n\t\t\tawait this.reportValidationError(\n\t\t\t\tnew Error(\"socka: invalid JSON\"),\n\t\t\t\trawMessage,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tawait this.dispatchAfterParsed(parsed, rawMessage);\n\t}\n\n\tpublic async handleBinaryMessage(buffer: ArrayBuffer): Promise<void> {\n\t\tif (this.wireFormat !== \"msgpack\") {\n\t\t\tawait this.reportValidationError(\n\t\t\t\tnew Error(\"socka: unexpected binary frame in JSON mode\"),\n\t\t\t\tbuffer,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = parseWirePayload(buffer, \"msgpack\");\n\t\t} catch (err) {\n\t\t\tawait this.reportValidationError(\n\t\t\t\terr instanceof Error ? err : new Error(\"socka: msgpack decode failed\"),\n\t\t\t\tbuffer,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tawait this.dispatchAfterParsed(parsed, buffer);\n\t}\n\n\tprivate async dispatchAfterParsed(\n\t\tparsed: unknown,\n\t\toriginalWire: unknown,\n\t): Promise<void> {\n\t\tlet decoded: ReturnType<typeof decodeSockaWire>;\n\t\ttry {\n\t\t\tdecoded = decodeSockaWire(parsed);\n\t\t} catch (err) {\n\t\t\tif (err instanceof SockaWireError) {\n\t\t\t\tawait this.reportValidationError(err, originalWire);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\n\t\tswitch (decoded.kind) {\n\t\t\tcase \"clientRequest\":\n\t\t\t\tawait this.dispatchClientRequest(decoded.frame, originalWire);\n\t\t\t\treturn;\n\t\t\tcase \"serverResponse\":\n\t\t\tcase \"serverError\":\n\t\t\tcase \"serverEvent\":\n\t\t\t\tawait this.reportValidationError(\n\t\t\t\t\tnew Error(\"socka: unexpected server-originated frame from client\"),\n\t\t\t\t\tparsed,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\tdefault:\n\t\t\t\texhaustiveGuard(decoded);\n\t\t}\n\t}\n\n\tprivate async dispatchClientRequest(\n\t\tframe: SockaClientRequestFrame,\n\t\t_originalWire: unknown,\n\t): Promise<void> {\n\t\tconst rpcName = frame.rpc;\n\t\tconst procedure = this.config.contract.calls[rpcName];\n\n\t\tif (!procedure) {\n\t\t\tconst errorFrame = encodeServerError(\n\t\t\t\tframe.id,\n\t\t\t\t`Unknown call: ${rpcName}`,\n\t\t\t\t{ rpc: rpcName },\n\t\t\t);\n\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\treturn;\n\t\t}\n\n\t\tlet validatedInput: unknown;\n\t\tif (procedure.input) {\n\t\t\ttry {\n\t\t\t\tvalidatedInput = await parseStandardSchema(procedure.input, frame.body);\n\t\t\t} catch (err) {\n\t\t\t\tconst msg =\n\t\t\t\t\terr instanceof Error ? err.message : \"Input validation failed\";\n\t\t\t\tconst errorFrame = encodeServerError(frame.id, msg, {\n\t\t\t\t\trpc: rpcName,\n\t\t\t\t});\n\t\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tlet result: unknown;\n\t\ttry {\n\t\t\tif (procedure.input) {\n\t\t\t\tconst handler = this.config.handlers[rpcName] as (\n\t\t\t\t\tinput: unknown,\n\t\t\t\t\ts: SockaWebSocketSession<TContract, TData>,\n\t\t\t\t) => unknown | Promise<unknown>;\n\t\t\t\tresult = await handler(validatedInput, this);\n\t\t\t} else {\n\t\t\t\tconst handler = this.config.handlers[rpcName] as (\n\t\t\t\t\ts: SockaWebSocketSession<TContract, TData>,\n\t\t\t\t) => unknown | Promise<unknown>;\n\t\t\t\tresult = await handler(this);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.config.onHandlerError?.(err, rpcName, validatedInput, this);\n\t\t\tconst sockaErr =\n\t\t\t\terr instanceof SockaError\n\t\t\t\t\t? err\n\t\t\t\t\t: new SockaError(\n\t\t\t\t\t\t\terr instanceof Error ? err.message : \"Handler failed\",\n\t\t\t\t\t\t);\n\t\t\tconst errorFrame = encodeServerError(frame.id, sockaErr.message, {\n\t\t\t\trpc: rpcName,\n\t\t\t\tcode: sockaErr.code,\n\t\t\t\tdata: sockaErr.data,\n\t\t\t});\n\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\treturn;\n\t\t}\n\n\t\tif (procedure.output === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet validatedOutput: unknown;\n\t\ttry {\n\t\t\tvalidatedOutput = await parseStandardSchema(procedure.output, result);\n\t\t} catch (err) {\n\t\t\tconst msg =\n\t\t\t\terr instanceof Error ? err.message : \"Output validation failed\";\n\t\t\tconst errorFrame = encodeServerError(frame.id, msg, { rpc: rpcName });\n\t\t\tthis.sendWireFrame(errorFrame);\n\t\t\treturn;\n\t\t}\n\n\t\tconst responseFrame = encodeServerResponse(\n\t\t\tframe.id,\n\t\t\trpcName,\n\t\t\tvalidatedOutput,\n\t\t);\n\t\tthis.sendWireFrame(responseFrame);\n\t}\n\n\tprivate encodeOutgoing(frame: SockaWireFrame): string | Uint8Array {\n\t\treturn encodeSockaWire(\n\t\t\tframe,\n\t\t\tthis.wireFormat,\n\t\t\tthis.config.serializeJson ?? JSON.stringify,\n\t\t);\n\t}\n\n\tprivate sendWireFrame(frame: SockaWireFrame): void {\n\t\tif (this.websocket.readyState !== WebSocket.OPEN) {\n\t\t\treturn;\n\t\t}\n\t\tconst encoded = this.encodeOutgoing(frame);\n\t\tif (typeof encoded === \"string\") {\n\t\t\tthis.websocket.send(encoded);\n\t\t\treturn;\n\t\t}\n\t\tconst copy = new Uint8Array(encoded.byteLength);\n\t\tcopy.set(encoded);\n\t\tthis.websocket.send(copy.buffer);\n\t}\n\n\t/**\n\t * Send a server event frame (wire). Prefer {@link emitPush} so\n\t * payloads are validated against the contract.\n\t */\n\tpublic emitWireEvent(event: string, body: unknown): void {\n\t\tconst frame = encodeServerEvent(event, body);\n\t\tthis.sendWireFrame(frame);\n\t}\n\n\tpublic async emitPush<K extends keyof TContract[\"pushes\"] & string>(\n\t\tname: K,\n\t\tbody: InferSockaPushPayload<TContract, K>,\n\t): Promise<void> {\n\t\tconst schema = this.config.contract.pushes[name];\n\t\tif (!schema) {\n\t\t\tthrow new Error(`socka: unknown push ${String(name)}`);\n\t\t}\n\t\tconst validated = await parseStandardSchema(\n\t\t\tschema as StandardSchemaV1<unknown, InferSockaPushPayload<TContract, K>>,\n\t\t\tbody,\n\t\t);\n\t\tthis.emitWireEvent(name, validated);\n\t}\n\n\tpublic async 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\tconst schema = this.config.contract.pushes[name];\n\t\tif (!schema) {\n\t\t\tthrow new Error(`socka: unknown push ${String(name)}`);\n\t\t}\n\t\tconst validated = await parseStandardSchema(\n\t\t\tschema as StandardSchemaV1<unknown, InferSockaPushPayload<TContract, K>>,\n\t\t\tbody,\n\t\t);\n\t\tbroadcastSockaEventToPeers(\n\t\t\tthis.sessions,\n\t\t\tthis,\n\t\t\tname,\n\t\t\tvalidated,\n\t\t\texcludeSelf,\n\t\t);\n\t}\n\n\tprivate async reportValidationError(\n\t\terror: unknown,\n\t\toriginalMessage: unknown,\n\t): Promise<void> {\n\t\tif (this.config.onValidationError) {\n\t\t\tawait this.config.onValidationError(error, originalMessage);\n\t\t} else {\n\t\t\tconsole.error(\"socka: validation error:\", error, originalMessage);\n\t\t}\n\t}\n}\n\n/**\n * Invoke {@link SockaWebSocketSessionConfig.onAttached} after the session is\n * registered in the shared map.\n */\nexport function runSockaSessionOnAttached<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfigUnion<TContract, TData>,\n\tsession: SockaWebSocketSession<TContract, TData>,\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"]}