@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.
- package/README.md +15 -1
- package/dist/{SockaWebSocketSession-B1w7RAid.d.ts → SockaWebSocketSession-BaGvSerM.d.ts} +28 -10
- package/dist/bun/index.d.ts +6 -6
- package/dist/bun/index.js +2 -2
- package/dist/bun/index.js.map +1 -1
- package/dist/{chunk-5WQTYLIC.js → chunk-JR2GENNT.js} +2 -2
- package/dist/chunk-JR2GENNT.js.map +1 -0
- package/dist/{chunk-P3JEEOJL.js → chunk-THFUHQJ3.js} +2 -2
- package/dist/chunk-THFUHQJ3.js.map +1 -0
- package/dist/{chunk-LVVCHLNW.js → chunk-TTXY7O5P.js} +20 -4
- package/dist/chunk-TTXY7O5P.js.map +1 -0
- package/dist/client/index.d.ts +10 -10
- package/dist/client/index.js +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/do/index.d.ts +83 -18
- package/dist/do/index.js +62 -6
- package/dist/do/index.js.map +1 -1
- package/dist/hono/cloudflare-workers.d.ts +4 -4
- package/dist/hono/cloudflare-workers.js +2 -2
- package/dist/hono/cloudflare-workers.js.map +1 -1
- package/dist/hono/index.d.ts +4 -4
- package/dist/hono/index.js +2 -2
- package/dist/hono/index.js.map +1 -1
- package/dist/react/index.d.ts +13 -13
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.d.ts +8 -8
- package/dist/server/index.js +4 -4
- package/dist/server/index.js.map +1 -1
- package/dist/{socka-report-error-CXwpAUgl.d.ts → socka-report-error-nTXJIzNb.d.ts} +32 -12
- package/docs/README.md +2 -0
- package/docs/client.md +17 -0
- package/docs/collaborative-realtime.md +61 -0
- package/docs/durable-objects.md +84 -35
- package/docs/internals.md +1 -1
- package/docs/pushes.md +32 -2
- package/docs/react-durable-objects.md +96 -0
- package/docs/recipes.md +1 -1
- package/docs/reference.md +18 -5
- package/package.json +8 -8
- package/skills/socka/core-rpc/SKILL.md +1 -1
- package/skills/socka/do-session/SKILL.md +1 -1
- package/dist/chunk-5WQTYLIC.js.map +0 -1
- package/dist/chunk-LVVCHLNW.js.map +0 -1
- package/dist/chunk-P3JEEOJL.js.map +0 -1
package/dist/react/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { DependencyList, RefObject, ReactNode, ReactElement } from 'react';
|
|
2
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>;
|
package/dist/react/index.js
CHANGED
package/dist/react/index.js.map
CHANGED
|
@@ -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"]}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as SockaWebSocketSession, b as SockaWebSocketSessionConfigUnion, c as SockaWebSocketInit,
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
};
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { dispatchSockaInboundMessage } from '../chunk-
|
|
2
|
-
export { dispatchSockaInboundMessage } from '../chunk-
|
|
3
|
-
import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-
|
|
4
|
-
export { SockaWebSocketSession, broadcastSockaEventToPeers, runSockaSessionOnAttached } from '../chunk-
|
|
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
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/attachSockaWebSocket.ts","../../src/server/room-registry.ts"],"names":[],"mappings":";;;;;;;AAyBO,SAAS,oBAAA,CAIf,SAAA,EACA,QAAA,EACA,MAAA,EACA,IAAA,EAC2C;AAC3C,EAAA,MAAM,UAAU,IAAI,qBAAA,CAAsB,SAAA,EAAW,QAAA,EAAU,QAAQ,IAAI,CAAA;AAC3E,EAAA,QAAA,CAAS,GAAA,CAAI,WAAW,OAAO,CAAA;AAC/B,EAAA,yBAAA,CAA0B,QAAQ,OAAO,CAAA;AAEzC,EAAA,IAAI,YAAA,GAAe,KAAA;AAEnB,EAAA,MAAM,WAAW,MAAY;AAC5B,IAAA,SAAA,CAAU,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAClD,IAAA,SAAA,CAAU,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAC9C,IAAA,QAAA,CAAS,OAAO,SAAS,CAAA;AAAA,EAC1B,CAAA;AAEA,EAAA,MAAM,WAAW,MAAY;AAC5B,IAAA,IAAI,YAAA,EAAc;AAClB,IAAA,YAAA,GAAe,IAAA;AACf,IAAA,KAAA,CAAM,YAA2B;AAChC,MAAA,IAAI;AACH,QAAA,MAAM,QAAQ,iBAAA,EAAkB;AAAA,MACjC,SAAS,KAAA,EAAO;AACf,QAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,UACpC,IAAA,EAAM,mBAAA;AAAA,UACN;AAAA,SACA,CAAA;AAAA,MACF,CAAA,SAAE;AACD,QAAA,QAAA,EAAS;AAAA,MACV;AAAA,IACD,CAAA,GAAG,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AAC9B,MAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,QACpC,IAAA,EAAM,gBAAA;AAAA,QACN,OAAA,EAAS,QAAA;AAAA,QACT;AAAA,OACA,CAAA;AACD,MAAA,QAAA,EAAS;AAAA,IACV,CAAC,CAAA;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,EAAA,KAA2B;AAC7C,IAAA,MAAM,EAAA,GAAK,OAAO,UAAA,IAAc,MAAA;AAChC,IAAA,KAAK,2BAAA,CAA4B,OAAA,EAAS,EAAA,EAAI,EAAA,CAAG,IAAI,CAAA,CAAE,KAAA;AAAA,MACtD,CAAC,KAAA,KAAmB;AACnB,QAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,UACpC,IAAA,EAAM,sBAAA;AAAA,UACN,OAAA,EAAS,QAAA;AAAA,UACT;AAAA,SACA,CAAA;AAAA,MACF;AAAA,KACD;AAAA,EACD,CAAA;AAEA,EAAA,MAAM,UAAU,MAAY;AAC3B,IAAA,QAAA,EAAS;AAAA,EACV,CAAA;AAEA,EAAA,SAAA,CAAU,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC/C,EAAA,SAAA,CAAU,gBAAA,CAAiB,SAAS,OAAO,CAAA;AAE3C,EAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,QAAA,EAAS;AACrC;;;
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|