@firtoz/socka 3.0.2 → 4.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.
Files changed (46) hide show
  1. package/README.md +15 -1
  2. package/dist/{SockaWebSocketSession-B1w7RAid.d.ts → SockaWebSocketSession-BaGvSerM.d.ts} +28 -10
  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-5WQTYLIC.js → chunk-JR2GENNT.js} +2 -2
  7. package/dist/chunk-JR2GENNT.js.map +1 -0
  8. package/dist/{chunk-P3JEEOJL.js → chunk-THFUHQJ3.js} +2 -2
  9. package/dist/chunk-THFUHQJ3.js.map +1 -0
  10. package/dist/{chunk-LVVCHLNW.js → chunk-TTXY7O5P.js} +20 -4
  11. package/dist/chunk-TTXY7O5P.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 +83 -18
  17. package/dist/do/index.js +62 -6
  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 +84 -35
  36. package/docs/internals.md +1 -1
  37. package/docs/pushes.md +32 -2
  38. package/docs/react-durable-objects.md +96 -0
  39. package/docs/recipes.md +1 -1
  40. package/docs/reference.md +18 -5
  41. package/package.json +8 -8
  42. package/skills/socka/core-rpc/SKILL.md +1 -1
  43. package/skills/socka/do-session/SKILL.md +1 -1
  44. package/dist/chunk-5WQTYLIC.js.map +0 -1
  45. package/dist/chunk-LVVCHLNW.js.map +0 -1
  46. package/dist/chunk-P3JEEOJL.js.map +0 -1
@@ -1,15 +1,15 @@
1
1
  import { DependencyList, RefObject, ReactNode, ReactElement } from 'react';
2
- import { S as SockaContract, a as SockaContractConfig, e as InferSockaPushHandlers, I as InferSockaSend, f as InferSockaPushPayload } from '../socka-report-error-CXwpAUgl.js';
2
+ import { c as SockaContractBound, g as InferSockaPushHandlers, I as InferSockaSend, h as InferSockaPushPayload } from '../socka-report-error-nTXJIzNb.js';
3
3
  import { SockaSessionOptions, SockaSession, SockaConnectionStatus } from '../client/index.js';
4
4
  import '@standard-schema/spec';
5
5
 
6
6
  /** Options for {@link useSocka}. */
7
- type UseSockaOptions<TContract extends SockaContract<SockaContractConfig>> = SockaSessionOptions<TContract>;
7
+ type UseSockaOptions<TContract extends SockaContractBound> = SockaSessionOptions<TContract>;
8
8
  /**
9
9
  * Connects a {@link SockaSession} in an effect: rejects all pending calls and closes
10
10
  * the socket on cleanup or when `deps` change.
11
11
  */
12
- declare function useSocka<TContract extends SockaContract<SockaContractConfig>>(options: UseSockaOptions<TContract>, deps: DependencyList): {
12
+ declare function useSocka<TContract extends SockaContractBound>(options: UseSockaOptions<TContract>, deps: DependencyList): {
13
13
  ready: boolean;
14
14
  sessionRef: RefObject<SockaSession<TContract> | null>;
15
15
  status: SockaConnectionStatus;
@@ -17,21 +17,21 @@ declare function useSocka<TContract extends SockaContract<SockaContractConfig>>(
17
17
  reconnectAttempt: number;
18
18
  };
19
19
 
20
- type UseSockaSessionOptions<TContract extends SockaContract<SockaContractConfig>> = Omit<UseSockaOptions<TContract>, "contract" | "pushHandlers"> & {
20
+ type UseSockaSessionOptions<TContract extends SockaContractBound> = Omit<UseSockaOptions<TContract>, "contract" | "pushHandlers"> & {
21
21
  pushHandlers?: Partial<InferSockaPushHandlers<TContract>>;
22
22
  };
23
23
  /**
24
24
  * Builds the same typed `send` object as {@link useSockaSession} from a live session ref.
25
25
  * Used by {@link useSockaSessionContext} so consumers do not open extra connections.
26
26
  */
27
- declare function createSockaSendProxyFromSession<TContract extends SockaContract<SockaContractConfig>>(contract: TContract, sessionRef: RefObject<SockaSession<TContract> | null>): InferSockaSend<TContract>;
27
+ declare function createSockaSendProxyFromSession<TContract extends SockaContractBound>(contract: TContract, sessionRef: RefObject<SockaSession<TContract> | null>): InferSockaSend<TContract>;
28
28
  /**
29
29
  * ```tsx
30
30
  * const { ready, send } = useSockaSession(myContract, { url }, deps);
31
31
  * await send.echo({ message: "hi" });
32
32
  * ```
33
33
  */
34
- declare function useSockaSession<TContract extends SockaContract<SockaContractConfig>>(contract: TContract, options: UseSockaSessionOptions<TContract>, deps: DependencyList): {
34
+ declare function useSockaSession<TContract extends SockaContractBound>(contract: TContract, options: UseSockaSessionOptions<TContract>, deps: DependencyList): {
35
35
  ready: boolean;
36
36
  send: InferSockaSend<TContract>;
37
37
  sessionRef: RefObject<SockaSession<TContract> | null>;
@@ -40,7 +40,7 @@ declare function useSockaSession<TContract extends SockaContract<SockaContractCo
40
40
  reconnectAttempt: number;
41
41
  };
42
42
 
43
- type SockaPresenceOptions<TContract extends SockaContract<SockaContractConfig>, TUser extends {
43
+ type SockaPresenceOptions<TContract extends SockaContractBound, TUser extends {
44
44
  userId: string;
45
45
  }, KJoin extends keyof TContract["pushes"] & string, KLeave extends keyof TContract["pushes"] & string> = {
46
46
  snapshot: () => Promise<{
@@ -59,7 +59,7 @@ type SockaPresenceOptions<TContract extends SockaContract<SockaContractConfig>,
59
59
  * Pass the same **`deps`** you use for {@link useSocka} when room identity changes.
60
60
  * Options are read from a ref so you do not need to memoize the **`options`** object.
61
61
  */
62
- declare function useSockaPresence<TContract extends SockaContract<SockaContractConfig>, TUser extends {
62
+ declare function useSockaPresence<TContract extends SockaContractBound, TUser extends {
63
63
  userId: string;
64
64
  }, KJoin extends keyof TContract["pushes"] & string, KLeave extends keyof TContract["pushes"] & string>(sessionRef: RefObject<SockaSession<TContract> | null>, ready: boolean, options: SockaPresenceOptions<TContract, TUser, KJoin, KLeave>, deps: DependencyList): {
65
65
  users: TUser[];
@@ -67,13 +67,13 @@ declare function useSockaPresence<TContract extends SockaContract<SockaContractC
67
67
  loading: boolean;
68
68
  };
69
69
 
70
- type AnySockaContract = SockaContract<SockaContractConfig>;
70
+ type AnySockaContract = SockaContractBound;
71
71
  /**
72
72
  * Session slice stored on React context by {@link SockaSessionProvider}. The typed
73
73
  * `send` object is built in {@link useSockaSessionContext} (same as {@link useSockaSession})
74
74
  * so children do not open duplicate WebSockets.
75
75
  */
76
- type SockaSessionContextValue<TContract extends SockaContract<SockaContractConfig> = AnySockaContract> = {
76
+ type SockaSessionContextValue<TContract extends SockaContractBound = AnySockaContract> = {
77
77
  readonly contract: TContract;
78
78
  readonly ready: boolean;
79
79
  readonly sessionRef: RefObject<SockaSession<TContract> | null>;
@@ -81,7 +81,7 @@ type SockaSessionContextValue<TContract extends SockaContract<SockaContractConfi
81
81
  readonly reconnecting: boolean;
82
82
  readonly reconnectAttempt: number;
83
83
  };
84
- type SockaSessionProviderProps<TContract extends SockaContract<SockaContractConfig>> = {
84
+ type SockaSessionProviderProps<TContract extends SockaContractBound> = {
85
85
  readonly contract: TContract;
86
86
  readonly deps: DependencyList;
87
87
  readonly children: ReactNode;
@@ -91,7 +91,7 @@ type SockaSessionProviderProps<TContract extends SockaContract<SockaContractConf
91
91
  * {@link useSockaSessionContext}. Mount once per connection (e.g. layout); avoid
92
92
  * calling {@link useSockaSession} in every leaf—use the context hook instead.
93
93
  */
94
- declare function SockaSessionProvider<TContract extends SockaContract<SockaContractConfig>>(props: SockaSessionProviderProps<TContract>): ReactElement;
94
+ declare function SockaSessionProvider<TContract extends SockaContractBound>(props: SockaSessionProviderProps<TContract>): ReactElement;
95
95
  declare namespace SockaSessionProvider {
96
96
  var displayName: string;
97
97
  }
@@ -99,7 +99,7 @@ declare namespace SockaSessionProvider {
99
99
  * Reads the socka session from the nearest {@link SockaSessionProvider}.
100
100
  * Pass the **same** `contract` reference as the provider for typing and validation.
101
101
  */
102
- declare function useSockaSessionContext<TContract extends SockaContract<SockaContractConfig>>(contract: TContract): {
102
+ declare function useSockaSessionContext<TContract extends SockaContractBound>(contract: TContract): {
103
103
  ready: boolean;
104
104
  send: InferSockaSend<TContract>;
105
105
  sessionRef: RefObject<SockaSession<TContract> | null>;
@@ -1,4 +1,4 @@
1
- import { SockaSession } from '../chunk-P3JEEOJL.js';
1
+ import { SockaSession } from '../chunk-THFUHQJ3.js';
2
2
  import '../chunk-YMT4HAH7.js';
3
3
  import '../chunk-IFIGKR3W.js';
4
4
  import { createContext, useRef, useState, useEffect, useMemo, useContext } from 'react';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/useSocka.ts","../../src/react/useSockaSession.ts","../../src/react/useSockaPresence.ts","../../src/react/SockaSessionProvider.tsx"],"names":["useState","useRef","useEffect","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import type { DependencyList, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { SockaSession, type SockaSessionOptions } from \"../client/SockaSession\";\nimport type { SockaConnectionStatus } from \"../client/SockaWebSocketClient\";\n\n/** Options for {@link useSocka}. */\nexport type UseSockaOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = SockaSessionOptions<TContract>;\n\n/**\n * Connects a {@link SockaSession} in an effect: rejects all pending calls and closes\n * the socket on cleanup or when `deps` change.\n */\nexport function useSocka<TContract extends SockaContract<SockaContractConfig>>(\n\toptions: UseSockaOptions<TContract>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n\tstatus: SockaConnectionStatus;\n\treconnecting: boolean;\n\treconnectAttempt: number;\n} {\n\tconst { onOpen, onClose, onReconnecting, onReconnected, ...restOptions } =\n\t\toptions;\n\n\tconst onOpenRef = useRef(onOpen);\n\tonOpenRef.current = onOpen;\n\tconst onCloseRef = useRef(onClose);\n\tonCloseRef.current = onClose;\n\tconst onReconnectingRef = useRef(onReconnecting);\n\tonReconnectingRef.current = onReconnecting;\n\tconst onReconnectedRef = useRef(onReconnected);\n\tonReconnectedRef.current = onReconnected;\n\n\tconst [ready, setReady] = useState(false);\n\tconst [status, setStatus] = useState<SockaConnectionStatus>(() =>\n\t\toptions.autoConnect === false ? \"idle\" : \"connecting\",\n\t);\n\tconst [reconnectAttempt, setReconnectAttempt] = useState(0);\n\tconst sessionRef = useRef<SockaSession<TContract> | null>(null);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetReady(false);\n\t\tsetReconnectAttempt(0);\n\n\t\tconst session = new SockaSession({\n\t\t\t...restOptions,\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\tonReconnecting: (info) => {\n\t\t\t\tsetReconnectAttempt(info.attempt);\n\t\t\t\tonReconnectingRef.current?.(info);\n\t\t\t},\n\t\t\tonReconnected: (info) => {\n\t\t\t\tsetReconnectAttempt(0);\n\t\t\t\tonReconnectedRef.current?.(info);\n\t\t\t},\n\t\t});\n\n\t\tconst unsubStatus = session.onStatusChange((s) => {\n\t\t\tif (!cancelled) setStatus(s);\n\t\t});\n\n\t\tsessionRef.current = session;\n\t\tvoid session.client.connect().then(\n\t\t\t() => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(true);\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t/* connect failure: onError / onClose handle UX */\n\t\t\t},\n\t\t);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tunsubStatus();\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}, deps); // deps: explicit reconnect contract for useSocka (see hook docs)\n\n\treturn {\n\t\tready,\n\t\tsessionRef,\n\t\tstatus,\n\t\treconnecting: status === \"reconnecting\",\n\t\treconnectAttempt,\n\t};\n}\n","import { useMemo, type DependencyList, type RefObject } from \"react\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { InferSockaSend, InferSockaPushHandlers } from \"../core/contract\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type { SockaConnectionStatus } from \"../client/SockaWebSocketClient\";\nimport { useSocka, type UseSockaOptions } from \"./useSocka\";\n\nexport type UseSockaSessionOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = Omit<UseSockaOptions<TContract>, \"contract\" | \"pushHandlers\"> & {\n\tpushHandlers?: Partial<InferSockaPushHandlers<TContract>>;\n};\n\n/**\n * Builds the same typed `send` object as {@link useSockaSession} from a live session ref.\n * Used by {@link useSockaSessionContext} so consumers do not open extra connections.\n */\nexport function createSockaSendProxyFromSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tcontract: TContract,\n\tsessionRef: RefObject<SockaSession<TContract> | null>,\n): InferSockaSend<TContract> {\n\tconst proxy: Record<string, unknown> = {};\n\tfor (const name of Object.keys(contract.calls)) {\n\t\tproxy[name] = (...args: unknown[]) => {\n\t\t\tconst session = sessionRef.current;\n\t\t\tif (!session) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"socka: session ref is null; cannot send\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst fn = session.send[name as keyof typeof session.send] as (\n\t\t\t\t...a: unknown[]\n\t\t\t) => Promise<unknown>;\n\t\t\treturn fn.apply(session.send, args);\n\t\t};\n\t}\n\treturn proxy as InferSockaSend<TContract>;\n}\n\n/**\n * ```tsx\n * const { ready, send } = useSockaSession(myContract, { url }, deps);\n * await send.echo({ message: \"hi\" });\n * ```\n */\nexport function useSockaSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tcontract: TContract,\n\toptions: UseSockaSessionOptions<TContract>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsend: InferSockaSend<TContract>;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n\tstatus: SockaConnectionStatus;\n\treconnecting: boolean;\n\treconnectAttempt: number;\n} {\n\tconst { pushHandlers, ...sockaOpts } = options;\n\tconst { ready, sessionRef, status, reconnecting, reconnectAttempt } =\n\t\tuseSocka(\n\t\t\t{\n\t\t\t\t...sockaOpts,\n\t\t\t\tcontract,\n\t\t\t\tpushHandlers,\n\t\t\t},\n\t\t\tdeps,\n\t\t);\n\n\tconst send = useMemo(\n\t\t() => createSockaSendProxyFromSession(contract, sessionRef),\n\t\t[contract, sessionRef],\n\t);\n\n\treturn {\n\t\tready,\n\t\tsend,\n\t\tsessionRef,\n\t\tstatus,\n\t\treconnecting,\n\t\treconnectAttempt,\n\t};\n}\n","import type { DependencyList, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContract,\n\tSockaContractConfig,\n} from \"../core/contract\";\n\nexport type SockaPresenceOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTUser extends { userId: string },\n\tKJoin extends keyof TContract[\"pushes\"] & string,\n\tKLeave extends keyof TContract[\"pushes\"] & string,\n> = {\n\tsnapshot: () => Promise<{ selfUserId: string; users: TUser[] }>;\n\tjoinPush: KJoin;\n\tleavePush: KLeave;\n\tmapJoinUser: (p: InferSockaPushPayload<TContract, KJoin>) => TUser;\n\tmapLeaveUserId: (p: InferSockaPushPayload<TContract, KLeave>) => string;\n\t/** Optional display order after each update (e.g. by `displayName`). */\n\tsortUsers?: (a: TUser, b: TUser) => number;\n};\n\n/**\n * Loads a presence snapshot RPC once, then merges **`joinPush`** / **`leavePush`** deltas.\n * Pass the same **`deps`** you use for {@link useSocka} when room identity changes.\n * Options are read from a ref so you do not need to memoize the **`options`** object.\n */\nexport function useSockaPresence<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTUser extends { userId: string },\n\tKJoin extends keyof TContract[\"pushes\"] & string,\n\tKLeave extends keyof TContract[\"pushes\"] & string,\n>(\n\tsessionRef: RefObject<SockaSession<TContract> | null>,\n\tready: boolean,\n\toptions: SockaPresenceOptions<TContract, TUser, KJoin, KLeave>,\n\tdeps: DependencyList,\n): {\n\tusers: TUser[];\n\tselfUserId: string | undefined;\n\tloading: boolean;\n} {\n\tconst [users, setUsers] = useState<TUser[]>([]);\n\tconst [selfUserId, setSelfUserId] = useState<string | undefined>();\n\tconst [loading, setLoading] = useState(true);\n\tconst optionsRef = useRef(options);\n\toptionsRef.current = options;\n\n\tuseEffect(() => {\n\t\tif (!ready) {\n\t\t\tsetUsers([]);\n\t\t\tsetSelfUserId(undefined);\n\t\t\tsetLoading(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst s = sessionRef.current;\n\t\tif (!s) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst o = optionsRef.current;\n\t\tlet cancelled = false;\n\n\t\tvoid (async () => {\n\t\t\tsetLoading(true);\n\t\t\ttry {\n\t\t\t\tconst snap = await o.snapshot();\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSelfUserId(snap.selfUserId);\n\t\t\t\tsetUsers(o.sortUsers ? [...snap.users].sort(o.sortUsers) : snap.users);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t})();\n\n\t\tconst onJoin = (p: InferSockaPushPayload<TContract, KJoin>) => {\n\t\t\tconst cur = optionsRef.current;\n\t\t\tconst u = cur.mapJoinUser(p);\n\t\t\tsetUsers((prev) => {\n\t\t\t\tconst next = prev.filter((x) => x.userId !== u.userId);\n\t\t\t\tconst merged = [...next, u];\n\t\t\t\treturn cur.sortUsers ? merged.sort(cur.sortUsers) : merged;\n\t\t\t});\n\t\t};\n\n\t\tconst onLeave = (p: InferSockaPushPayload<TContract, KLeave>) => {\n\t\t\tconst id = optionsRef.current.mapLeaveUserId(p);\n\t\t\tsetUsers((prev) => prev.filter((x) => x.userId !== id));\n\t\t};\n\n\t\ts.subscribe.on(o.joinPush, onJoin);\n\t\ts.subscribe.on(o.leavePush, onLeave);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\ts.subscribe.off(o.joinPush, onJoin);\n\t\t\ts.subscribe.off(o.leavePush, onLeave);\n\t\t};\n\t}, [ready, sessionRef, ...deps]);\n\n\treturn { users, selfUserId, loading };\n}\n","import type { DependencyList, ReactElement, ReactNode, RefObject } from \"react\";\nimport { createContext, useContext, useMemo } from \"react\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type { SockaConnectionStatus } from \"../client/SockaWebSocketClient\";\nimport type {\n\tSockaContract,\n\tSockaContractConfig,\n\tInferSockaSend,\n} from \"../core/contract\";\nimport {\n\tcreateSockaSendProxyFromSession,\n\tuseSockaSession,\n\ttype UseSockaSessionOptions,\n} from \"./useSockaSession\";\n\ntype AnySockaContract = SockaContract<SockaContractConfig>;\n\n/**\n * Session slice stored on React context by {@link SockaSessionProvider}. The typed\n * `send` object is built in {@link useSockaSessionContext} (same as {@link useSockaSession})\n * so children do not open duplicate WebSockets.\n */\nexport type SockaSessionContextValue<\n\tTContract extends SockaContract<SockaContractConfig> = AnySockaContract,\n> = {\n\treadonly contract: TContract;\n\treadonly ready: boolean;\n\treadonly sessionRef: RefObject<SockaSession<TContract> | null>;\n\treadonly status: SockaConnectionStatus;\n\treadonly reconnecting: boolean;\n\treadonly reconnectAttempt: number;\n};\n\nconst SockaSessionContext =\n\tcreateContext<SockaSessionContextValue<AnySockaContract> | null>(null);\n\nfunction contextMatchesContract<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tctx: SockaSessionContextValue<AnySockaContract>,\n\tcontract: TContract,\n): ctx is SockaSessionContextValue<TContract> {\n\treturn ctx.contract === contract;\n}\n\nexport type SockaSessionProviderProps<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = {\n\treadonly contract: TContract;\n\treadonly deps: DependencyList;\n\treadonly children: ReactNode;\n} & UseSockaSessionOptions<TContract>;\n\n/**\n * Owns a single {@link SockaSession} / WebSocket and exposes it to descendants via\n * {@link useSockaSessionContext}. Mount once per connection (e.g. layout); avoid\n * calling {@link useSockaSession} in every leaf—use the context hook instead.\n */\nexport function SockaSessionProvider<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(props: SockaSessionProviderProps<TContract>): ReactElement {\n\tconst { contract, deps, children, ...sessionOptions } = props;\n\tconst value = useSockaSession(contract, sessionOptions, deps);\n\tconst merged: SockaSessionContextValue<TContract> = {\n\t\tcontract,\n\t\tready: value.ready,\n\t\tsessionRef: value.sessionRef,\n\t\tstatus: value.status,\n\t\treconnecting: value.reconnecting,\n\t\treconnectAttempt: value.reconnectAttempt,\n\t};\n\treturn (\n\t\t<SockaSessionContext.Provider value={merged}>\n\t\t\t{children}\n\t\t</SockaSessionContext.Provider>\n\t);\n}\n\nSockaSessionProvider.displayName = \"SockaSessionProvider\";\n\n/**\n * Reads the socka session from the nearest {@link SockaSessionProvider}.\n * Pass the **same** `contract` reference as the provider for typing and validation.\n */\nexport function useSockaSessionContext<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tcontract: TContract,\n): {\n\tready: boolean;\n\tsend: InferSockaSend<TContract>;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n\tstatus: SockaConnectionStatus;\n\treconnecting: boolean;\n\treconnectAttempt: number;\n} {\n\tconst ctx = useContext(SockaSessionContext);\n\tif (ctx === null) {\n\t\tthrow new Error(\n\t\t\t\"useSockaSessionContext must be used within a SockaSessionProvider\",\n\t\t);\n\t}\n\tif (!contextMatchesContract(ctx, contract)) {\n\t\tthrow new Error(\n\t\t\t\"useSockaSessionContext: `contract` must be the same reference as SockaSessionProvider's `contract`\",\n\t\t);\n\t}\n\tconst send = useMemo(\n\t\t() => createSockaSendProxyFromSession(contract, ctx.sessionRef),\n\t\t[contract, ctx.sessionRef],\n\t);\n\treturn {\n\t\tready: ctx.ready,\n\t\tsend,\n\t\tsessionRef: ctx.sessionRef,\n\t\tstatus: ctx.status,\n\t\treconnecting: ctx.reconnecting,\n\t\treconnectAttempt: ctx.reconnectAttempt,\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/useSocka.ts","../../src/react/useSockaSession.ts","../../src/react/useSockaPresence.ts","../../src/react/SockaSessionProvider.tsx"],"names":["useState","useRef","useEffect","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import type { DependencyList, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SockaContractBound } from \"../core/contract\";\nimport { SockaSession, type SockaSessionOptions } from \"../client/SockaSession\";\nimport type { SockaConnectionStatus } from \"../client/SockaWebSocketClient\";\n\n/** Options for {@link useSocka}. */\nexport type UseSockaOptions<TContract extends SockaContractBound> =\n\tSockaSessionOptions<TContract>;\n\n/**\n * Connects a {@link SockaSession} in an effect: rejects all pending calls and closes\n * the socket on cleanup or when `deps` change.\n */\nexport function useSocka<TContract extends SockaContractBound>(\n\toptions: UseSockaOptions<TContract>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n\tstatus: SockaConnectionStatus;\n\treconnecting: boolean;\n\treconnectAttempt: number;\n} {\n\tconst { onOpen, onClose, onReconnecting, onReconnected, ...restOptions } =\n\t\toptions;\n\n\tconst onOpenRef = useRef(onOpen);\n\tonOpenRef.current = onOpen;\n\tconst onCloseRef = useRef(onClose);\n\tonCloseRef.current = onClose;\n\tconst onReconnectingRef = useRef(onReconnecting);\n\tonReconnectingRef.current = onReconnecting;\n\tconst onReconnectedRef = useRef(onReconnected);\n\tonReconnectedRef.current = onReconnected;\n\n\tconst [ready, setReady] = useState(false);\n\tconst [status, setStatus] = useState<SockaConnectionStatus>(() =>\n\t\toptions.autoConnect === false ? \"idle\" : \"connecting\",\n\t);\n\tconst [reconnectAttempt, setReconnectAttempt] = useState(0);\n\tconst sessionRef = useRef<SockaSession<TContract> | null>(null);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetReady(false);\n\t\tsetReconnectAttempt(0);\n\n\t\tconst session = new SockaSession({\n\t\t\t...restOptions,\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\tonReconnecting: (info) => {\n\t\t\t\tsetReconnectAttempt(info.attempt);\n\t\t\t\tonReconnectingRef.current?.(info);\n\t\t\t},\n\t\t\tonReconnected: (info) => {\n\t\t\t\tsetReconnectAttempt(0);\n\t\t\t\tonReconnectedRef.current?.(info);\n\t\t\t},\n\t\t});\n\n\t\tconst unsubStatus = session.onStatusChange((s) => {\n\t\t\tif (!cancelled) setStatus(s);\n\t\t});\n\n\t\tsessionRef.current = session;\n\t\tvoid session.client.connect().then(\n\t\t\t() => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(true);\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t/* connect failure: onError / onClose handle UX */\n\t\t\t},\n\t\t);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tunsubStatus();\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}, deps); // deps: explicit reconnect contract for useSocka (see hook docs)\n\n\treturn {\n\t\tready,\n\t\tsessionRef,\n\t\tstatus,\n\t\treconnecting: status === \"reconnecting\",\n\t\treconnectAttempt,\n\t};\n}\n","import { useMemo, type DependencyList, type RefObject } from \"react\";\nimport type { SockaContractBound } from \"../core/contract\";\nimport type { InferSockaSend, InferSockaPushHandlers } from \"../core/contract\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type { SockaConnectionStatus } from \"../client/SockaWebSocketClient\";\nimport { useSocka, type UseSockaOptions } from \"./useSocka\";\n\nexport type UseSockaSessionOptions<TContract extends SockaContractBound> = Omit<\n\tUseSockaOptions<TContract>,\n\t\"contract\" | \"pushHandlers\"\n> & {\n\tpushHandlers?: Partial<InferSockaPushHandlers<TContract>>;\n};\n\n/**\n * Builds the same typed `send` object as {@link useSockaSession} from a live session ref.\n * Used by {@link useSockaSessionContext} so consumers do not open extra connections.\n */\nexport function createSockaSendProxyFromSession<\n\tTContract extends SockaContractBound,\n>(\n\tcontract: TContract,\n\tsessionRef: RefObject<SockaSession<TContract> | null>,\n): InferSockaSend<TContract> {\n\tconst proxy: Record<string, unknown> = {};\n\tfor (const name of Object.keys(contract.calls)) {\n\t\tproxy[name] = (...args: unknown[]) => {\n\t\t\tconst session = sessionRef.current;\n\t\t\tif (!session) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"socka: session ref is null; cannot send\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst fn = session.send[name as keyof typeof session.send] as (\n\t\t\t\t...a: unknown[]\n\t\t\t) => Promise<unknown>;\n\t\t\treturn fn.apply(session.send, args);\n\t\t};\n\t}\n\treturn proxy as InferSockaSend<TContract>;\n}\n\n/**\n * ```tsx\n * const { ready, send } = useSockaSession(myContract, { url }, deps);\n * await send.echo({ message: \"hi\" });\n * ```\n */\nexport function useSockaSession<TContract extends SockaContractBound>(\n\tcontract: TContract,\n\toptions: UseSockaSessionOptions<TContract>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsend: InferSockaSend<TContract>;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n\tstatus: SockaConnectionStatus;\n\treconnecting: boolean;\n\treconnectAttempt: number;\n} {\n\tconst { pushHandlers, ...sockaOpts } = options;\n\tconst { ready, sessionRef, status, reconnecting, reconnectAttempt } =\n\t\tuseSocka(\n\t\t\t{\n\t\t\t\t...sockaOpts,\n\t\t\t\tcontract,\n\t\t\t\tpushHandlers,\n\t\t\t},\n\t\t\tdeps,\n\t\t);\n\n\tconst send = useMemo(\n\t\t() => createSockaSendProxyFromSession(contract, sessionRef),\n\t\t[contract, sessionRef],\n\t);\n\n\treturn {\n\t\tready,\n\t\tsend,\n\t\tsessionRef,\n\t\tstatus,\n\t\treconnecting,\n\t\treconnectAttempt,\n\t};\n}\n","import type { DependencyList, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type {\n\tInferSockaPushPayload,\n\tSockaContractBound,\n} from \"../core/contract\";\n\nexport type SockaPresenceOptions<\n\tTContract extends SockaContractBound,\n\tTUser extends { userId: string },\n\tKJoin extends keyof TContract[\"pushes\"] & string,\n\tKLeave extends keyof TContract[\"pushes\"] & string,\n> = {\n\tsnapshot: () => Promise<{ selfUserId: string; users: TUser[] }>;\n\tjoinPush: KJoin;\n\tleavePush: KLeave;\n\tmapJoinUser: (p: InferSockaPushPayload<TContract, KJoin>) => TUser;\n\tmapLeaveUserId: (p: InferSockaPushPayload<TContract, KLeave>) => string;\n\t/** Optional display order after each update (e.g. by `displayName`). */\n\tsortUsers?: (a: TUser, b: TUser) => number;\n};\n\n/**\n * Loads a presence snapshot RPC once, then merges **`joinPush`** / **`leavePush`** deltas.\n * Pass the same **`deps`** you use for {@link useSocka} when room identity changes.\n * Options are read from a ref so you do not need to memoize the **`options`** object.\n */\nexport function useSockaPresence<\n\tTContract extends SockaContractBound,\n\tTUser extends { userId: string },\n\tKJoin extends keyof TContract[\"pushes\"] & string,\n\tKLeave extends keyof TContract[\"pushes\"] & string,\n>(\n\tsessionRef: RefObject<SockaSession<TContract> | null>,\n\tready: boolean,\n\toptions: SockaPresenceOptions<TContract, TUser, KJoin, KLeave>,\n\tdeps: DependencyList,\n): {\n\tusers: TUser[];\n\tselfUserId: string | undefined;\n\tloading: boolean;\n} {\n\tconst [users, setUsers] = useState<TUser[]>([]);\n\tconst [selfUserId, setSelfUserId] = useState<string | undefined>();\n\tconst [loading, setLoading] = useState(true);\n\tconst optionsRef = useRef(options);\n\toptionsRef.current = options;\n\n\tuseEffect(() => {\n\t\tif (!ready) {\n\t\t\tsetUsers([]);\n\t\t\tsetSelfUserId(undefined);\n\t\t\tsetLoading(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst s = sessionRef.current;\n\t\tif (!s) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tconst o = optionsRef.current;\n\t\tlet cancelled = false;\n\n\t\tvoid (async () => {\n\t\t\tsetLoading(true);\n\t\t\ttry {\n\t\t\t\tconst snap = await o.snapshot();\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSelfUserId(snap.selfUserId);\n\t\t\t\tsetUsers(o.sortUsers ? [...snap.users].sort(o.sortUsers) : snap.users);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t})();\n\n\t\tconst onJoin = (p: InferSockaPushPayload<TContract, KJoin>) => {\n\t\t\tconst cur = optionsRef.current;\n\t\t\tconst u = cur.mapJoinUser(p);\n\t\t\tsetUsers((prev) => {\n\t\t\t\tconst next = prev.filter((x) => x.userId !== u.userId);\n\t\t\t\tconst merged = [...next, u];\n\t\t\t\treturn cur.sortUsers ? merged.sort(cur.sortUsers) : merged;\n\t\t\t});\n\t\t};\n\n\t\tconst onLeave = (p: InferSockaPushPayload<TContract, KLeave>) => {\n\t\t\tconst id = optionsRef.current.mapLeaveUserId(p);\n\t\t\tsetUsers((prev) => prev.filter((x) => x.userId !== id));\n\t\t};\n\n\t\ts.subscribe.on(o.joinPush, onJoin);\n\t\ts.subscribe.on(o.leavePush, onLeave);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\ts.subscribe.off(o.joinPush, onJoin);\n\t\t\ts.subscribe.off(o.leavePush, onLeave);\n\t\t};\n\t}, [ready, sessionRef, ...deps]);\n\n\treturn { users, selfUserId, loading };\n}\n","import type { DependencyList, ReactElement, ReactNode, RefObject } from \"react\";\nimport { createContext, useContext, useMemo } from \"react\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type { SockaConnectionStatus } from \"../client/SockaWebSocketClient\";\nimport type { SockaContractBound, InferSockaSend } from \"../core/contract\";\nimport {\n\tcreateSockaSendProxyFromSession,\n\tuseSockaSession,\n\ttype UseSockaSessionOptions,\n} from \"./useSockaSession\";\n\ntype AnySockaContract = SockaContractBound;\n\n/**\n * Session slice stored on React context by {@link SockaSessionProvider}. The typed\n * `send` object is built in {@link useSockaSessionContext} (same as {@link useSockaSession})\n * so children do not open duplicate WebSockets.\n */\nexport type SockaSessionContextValue<\n\tTContract extends SockaContractBound = AnySockaContract,\n> = {\n\treadonly contract: TContract;\n\treadonly ready: boolean;\n\treadonly sessionRef: RefObject<SockaSession<TContract> | null>;\n\treadonly status: SockaConnectionStatus;\n\treadonly reconnecting: boolean;\n\treadonly reconnectAttempt: number;\n};\n\nconst SockaSessionContext =\n\tcreateContext<SockaSessionContextValue<AnySockaContract> | null>(null);\n\nfunction contextMatchesContract<TContract extends SockaContractBound>(\n\tctx: SockaSessionContextValue<AnySockaContract>,\n\tcontract: TContract,\n): ctx is SockaSessionContextValue<TContract> {\n\treturn ctx.contract === contract;\n}\n\nexport type SockaSessionProviderProps<TContract extends SockaContractBound> = {\n\treadonly contract: TContract;\n\treadonly deps: DependencyList;\n\treadonly children: ReactNode;\n} & UseSockaSessionOptions<TContract>;\n\n/**\n * Owns a single {@link SockaSession} / WebSocket and exposes it to descendants via\n * {@link useSockaSessionContext}. Mount once per connection (e.g. layout); avoid\n * calling {@link useSockaSession} in every leaf—use the context hook instead.\n */\nexport function SockaSessionProvider<TContract extends SockaContractBound>(\n\tprops: SockaSessionProviderProps<TContract>,\n): ReactElement {\n\tconst { contract, deps, children, ...sessionOptions } = props;\n\tconst value = useSockaSession(contract, sessionOptions, deps);\n\tconst merged: SockaSessionContextValue<TContract> = {\n\t\tcontract,\n\t\tready: value.ready,\n\t\tsessionRef: value.sessionRef,\n\t\tstatus: value.status,\n\t\treconnecting: value.reconnecting,\n\t\treconnectAttempt: value.reconnectAttempt,\n\t};\n\treturn (\n\t\t<SockaSessionContext.Provider value={merged}>\n\t\t\t{children}\n\t\t</SockaSessionContext.Provider>\n\t);\n}\n\nSockaSessionProvider.displayName = \"SockaSessionProvider\";\n\n/**\n * Reads the socka session from the nearest {@link SockaSessionProvider}.\n * Pass the **same** `contract` reference as the provider for typing and validation.\n */\nexport function useSockaSessionContext<TContract extends SockaContractBound>(\n\tcontract: TContract,\n): {\n\tready: boolean;\n\tsend: InferSockaSend<TContract>;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n\tstatus: SockaConnectionStatus;\n\treconnecting: boolean;\n\treconnectAttempt: number;\n} {\n\tconst ctx = useContext(SockaSessionContext);\n\tif (ctx === null) {\n\t\tthrow new Error(\n\t\t\t\"useSockaSessionContext must be used within a SockaSessionProvider\",\n\t\t);\n\t}\n\tif (!contextMatchesContract(ctx, contract)) {\n\t\tthrow new Error(\n\t\t\t\"useSockaSessionContext: `contract` must be the same reference as SockaSessionProvider's `contract`\",\n\t\t);\n\t}\n\tconst send = useMemo(\n\t\t() => createSockaSendProxyFromSession(contract, ctx.sessionRef),\n\t\t[contract, ctx.sessionRef],\n\t);\n\treturn {\n\t\tready: ctx.ready,\n\t\tsend,\n\t\tsessionRef: ctx.sessionRef,\n\t\tstatus: ctx.status,\n\t\treconnecting: ctx.reconnecting,\n\t\treconnectAttempt: ctx.reconnectAttempt,\n\t};\n}\n"]}
@@ -1,9 +1,9 @@
1
- import { a as SockaWebSocketSession, b as SockaWebSocketSessionConfigUnion, c as SockaWebSocketInit, e as SockaWebSocketSessionConfig } from '../SockaWebSocketSession-B1w7RAid.js';
2
- export { g as SockaEmitCapable, d as SockaPushSession, S as SockaStrictWebSocketInit, h as SockaWebSocketSessionConfigLoose, f as broadcastSockaEventToPeers, r as runSockaSessionOnAttached } from '../SockaWebSocketSession-B1w7RAid.js';
3
- import { S as SockaContract, a as SockaContractConfig, w as SockaWireFormat } from '../socka-report-error-CXwpAUgl.js';
1
+ import { a as SockaWebSocketSession, b as SockaWebSocketSessionConfigUnion, c as SockaWebSocketInit, d as SockaWebSocketSessionConfig } from '../SockaWebSocketSession-BaGvSerM.js';
2
+ export { h as SockaEmitCapable, i as SockaPushSession, S as SockaStrictWebSocketInit, j as SockaWebSocketSessionConfigLoose, e as broadcastContractPushToAll, f as broadcastSockaEventToAll, g as broadcastSockaEventToPeers, r as runSockaSessionOnAttached } from '../SockaWebSocketSession-BaGvSerM.js';
3
+ import { c as SockaContractBound, y as SockaWireFormat } from '../socka-report-error-nTXJIzNb.js';
4
4
  import '@standard-schema/spec';
5
5
 
6
- type AttachedSockaWebSocket<TContract extends SockaContract<SockaContractConfig>, TData> = {
6
+ type AttachedSockaWebSocket<TContract extends SockaContractBound, TData> = {
7
7
  session: SockaWebSocketSession<TContract, TData>;
8
8
  /** Remove listeners and delete this session from the map (idempotent). */
9
9
  dispose: () => void;
@@ -14,7 +14,7 @@ type AttachedSockaWebSocket<TContract extends SockaContract<SockaContractConfig>
14
14
  * {@link SockaWebSocketSession.invokeHandleClose} once, then removes listeners
15
15
  * (also triggered by `close`).
16
16
  */
17
- declare function attachSockaWebSocket<TContract extends SockaContract<SockaContractConfig>, TData>(websocket: WebSocket, sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>, config: SockaWebSocketSessionConfigUnion<TContract, TData>, init?: SockaWebSocketInit): AttachedSockaWebSocket<TContract, TData>;
17
+ declare function attachSockaWebSocket<TContract extends SockaContractBound, TData>(websocket: WebSocket, sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>, config: SockaWebSocketSessionConfigUnion<TContract, TData>, init?: SockaWebSocketInit): AttachedSockaWebSocket<TContract, TData>;
18
18
 
19
19
  /**
20
20
  * Decode a WebSocket `message` payload and dispatch it to the session (same
@@ -22,9 +22,9 @@ declare function attachSockaWebSocket<TContract extends SockaContract<SockaContr
22
22
  * Use this when the runtime does not support `addEventListener` on the socket
23
23
  * (e.g. Bun {@link ServerWebSocket}) or when handling messages manually.
24
24
  */
25
- declare function dispatchSockaInboundMessage<TContract extends SockaContract<SockaContractConfig>, TData>(session: SockaWebSocketSession<TContract, TData>, wireFormat: SockaWireFormat, data: MessageEvent["data"]): Promise<void>;
25
+ declare function dispatchSockaInboundMessage<TContract extends SockaContractBound, TData>(session: SockaWebSocketSession<TContract, TData>, wireFormat: SockaWireFormat, data: MessageEvent["data"]): Promise<void>;
26
26
 
27
- type SockaRoomBundle<TContract extends SockaContract<SockaContractConfig>, TData> = {
27
+ type SockaRoomBundle<TContract extends SockaContractBound, TData> = {
28
28
  sessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
29
29
  config: SockaWebSocketSessionConfig<TContract, TData>;
30
30
  };
@@ -32,7 +32,7 @@ type SockaRoomBundle<TContract extends SockaContract<SockaContractConfig>, TData
32
32
  * Per-room {@link SockaWebSocketSession} maps and configs for Bun/Hono multi-room
33
33
  * apps (one bundle per `roomId`).
34
34
  */
35
- declare function createSockaRoomRegistry<TContract extends SockaContract<SockaContractConfig>, TData>(makeConfig: (roomId: string, sessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>) => SockaWebSocketSessionConfig<TContract, TData>): {
35
+ declare function createSockaRoomRegistry<TContract extends SockaContractBound, TData>(makeConfig: (roomId: string, sessionMap: Map<WebSocket, SockaWebSocketSession<TContract, TData>>) => SockaWebSocketSessionConfig<TContract, TData>): {
36
36
  get(roomId: string): SockaRoomBundle<TContract, TData>;
37
37
  readonly rooms: ReadonlyMap<string, SockaRoomBundle<TContract, TData>>;
38
38
  };
@@ -1,7 +1,7 @@
1
- import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
2
- export { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
3
- import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-LVVCHLNW.js';
4
- export { SockaWebSocketSession, broadcastSockaEventToPeers, runSockaSessionOnAttached } from '../chunk-LVVCHLNW.js';
1
+ import { dispatchSockaInboundMessage } from '../chunk-JR2GENNT.js';
2
+ export { dispatchSockaInboundMessage } from '../chunk-JR2GENNT.js';
3
+ import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-TTXY7O5P.js';
4
+ export { SockaWebSocketSession, broadcastContractPushToAll, broadcastSockaEventToAll, broadcastSockaEventToPeers, runSockaSessionOnAttached } from '../chunk-TTXY7O5P.js';
5
5
  import { reportSockaError } from '../chunk-IFIGKR3W.js';
6
6
 
7
7
  // src/server/attachSockaWebSocket.ts
@@ -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