@couch-kit/client 0.11.0 → 0.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @couch-kit/client
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#155](https://github.com/faluciano/react-native-couch-kit/pull/155) [`4f297a1`](https://github.com/faluciano/react-native-couch-kit/commit/4f297a19c442541703c3bee7bee26354ae3476a4) Thanks [@faluciano](https://github.com/faluciano)! - Hidden information can now be hidden for real: `GameHostRuntimeConfig` takes an
8
+ optional `project(state, playerId)` that narrows the authoritative state to what
9
+ one player may see.
10
+
11
+ Without it the runtime broadcasts the same state to everyone, so hiding a hand
12
+ depends on the client choosing not to look — any player could read opponents'
13
+ cards from devtools. With it, the data never reaches their device: the runtime
14
+ sends each connection its own projection, in `WELCOME`, `RECONNECTED`, and every
15
+ state update.
16
+
17
+ Games with no hidden information omit `project` and are unchanged — state is
18
+ still broadcast in one frame.
19
+
20
+ Because a projected client holds a _view_ rather than the whole state, it cannot
21
+ run the game reducer, so `ClientConfig.reducer` is now optional. Omit it and the
22
+ client renders what the host sends (no optimistic updates); a round trip is
23
+ imperceptible for turn-based games, and it is what makes the guarantee real.
24
+
3
25
  ## 0.11.0
4
26
 
5
27
  ### Minor Changes
package/dist/index.js CHANGED
@@ -168,7 +168,7 @@ function useGameClient(config) {
168
168
  const [status, setStatus] = useState2("disconnected");
169
169
  const [playerId, setPlayerId] = useState2(null);
170
170
  const [disconnectReason, setDisconnectReason] = useState2(null);
171
- const [state, dispatchLocal] = useReducer(createGameReducer(config.reducer), config.initialState);
171
+ const [state, dispatchLocal] = useReducer(createGameReducer(config.reducer ?? ((current) => current)), config.initialState);
172
172
  const socketRef = useRef2(null);
173
173
  const reconnectAttempts = useRef2(0);
174
174
  const reconnectTimer = useRef2(null);
package/lib/client.d.ts CHANGED
@@ -3,7 +3,17 @@ import { type CreateClientTransport } from "./transport";
3
3
  export interface ClientConfig<S extends IGameState, A extends IAction> {
4
4
  url?: string;
5
5
  wsPort?: number;
6
- reducer: (state: S, action: A) => S;
6
+ /**
7
+ * Applies actions locally for an optimistic update before the host confirms.
8
+ *
9
+ * **Omit it when the host projects state per player** (`project` in
10
+ * `GameHostRuntimeConfig`): the client then holds a *view* rather than the
11
+ * whole state, and the game reducer cannot run over a partial view. Without a
12
+ * reducer the client simply renders what the host sends — a round trip that
13
+ * is imperceptible for turn-based games, and the price of hidden information
14
+ * never reaching the device.
15
+ */
16
+ reducer?: (state: S, action: A) => S;
7
17
  initialState: S;
8
18
  name?: string;
9
19
  avatar?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACnE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;IAyOxB;;;;OAIG;;yBAvBmC,CAAC;;IA2BvC,8EAA8E;;IAE9E,0EAA0E;;IAE1E,+EAA+E;;EAGlF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACrC,YAAY,EAAE,CAAC,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACnE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;IA2OxB;;;;OAIG;;yBAvBmC,CAAC;;IA2BvC,8EAA8E;;IAE9E,0EAA0E;;IAE1E,+EAA+E;;EAGlF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couch-kit/client",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
package/src/client.ts CHANGED
@@ -29,7 +29,17 @@ import {
29
29
  export interface ClientConfig<S extends IGameState, A extends IAction> {
30
30
  url?: string; // Full WebSocket URL (overrides auto-detection)
31
31
  wsPort?: number; // WebSocket port (default: auto-detected as HTTP port + 2)
32
- reducer: (state: S, action: A) => S;
32
+ /**
33
+ * Applies actions locally for an optimistic update before the host confirms.
34
+ *
35
+ * **Omit it when the host projects state per player** (`project` in
36
+ * `GameHostRuntimeConfig`): the client then holds a *view* rather than the
37
+ * whole state, and the game reducer cannot run over a partial view. Without a
38
+ * reducer the client simply renders what the host sends — a round trip that
39
+ * is imperceptible for turn-based games, and the price of hidden information
40
+ * never reaching the device.
41
+ */
42
+ reducer?: (state: S, action: A) => S;
33
43
  initialState: S;
34
44
  name?: string; // Player display name (default: "Player")
35
45
  avatar?: string; // Player avatar emoji (default: "\u{1F600}")
@@ -83,9 +93,11 @@ export function useGameClient<S extends IGameState, A extends IAction>(
83
93
  const [disconnectReason, setDisconnectReason] = useState<string | null>(null);
84
94
 
85
95
  // Local Optimistic State
86
- // Wrap the user's reducer with createGameReducer to handle HYDRATE automatically
96
+ // Wrap the user's reducer with createGameReducer to handle HYDRATE automatically.
97
+ // With no reducer (server-projected views) the identity function keeps HYDRATE
98
+ // working while local application becomes a no-op.
87
99
  const [state, dispatchLocal] = useReducer(
88
- createGameReducer(config.reducer),
100
+ createGameReducer(config.reducer ?? ((current: S) => current)),
89
101
  config.initialState,
90
102
  );
91
103