@couch-kit/core 0.8.3 → 0.9.1
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 +16 -0
- package/dist/index.cjs +287 -0
- package/dist/index.js +9 -8
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/reducer.d.ts +32 -0
- package/lib/reducer.d.ts.map +1 -0
- package/lib/types.d.ts +0 -30
- package/lib/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +1 -0
- package/src/reducer.ts +126 -0
- package/src/types.ts +0 -120
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @couch-kit/core
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#54](https://github.com/faluciano/react-native-couch-kit/pull/54) [`6860811`](https://github.com/faluciano/react-native-couch-kit/commit/6860811ec2fc0648fd9fd9d676c1b6ebcb4d14d0) Thanks [@faluciano](https://github.com/faluciano)! - Move `createGameReducer` and `CreateGameReducerOptions` from `types.ts` into a dedicated `reducer.ts` module. This is an internal refactor — the public API is unchanged (both remain exported from `@couch-kit/core`) and runtime behaviour is identical. It also removes a circular import between `types.ts` and `middleware.ts`.
|
|
8
|
+
|
|
9
|
+
## 0.9.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#36](https://github.com/faluciano/react-native-couch-kit/pull/36) [`a17371d`](https://github.com/faluciano/react-native-couch-kit/commit/a17371d9523e59c1fbffe94248bb2af080de3b51) Thanks [@faluciano](https://github.com/faluciano)! - Add CJS export for core package and fix host main field
|
|
14
|
+
- Added `require` export condition and CJS build step to `@couch-kit/core` for CommonJS consumers
|
|
15
|
+
- Fixed `@couch-kit/host` main field from `lib/index.d.ts` to `./src/index.tsx`
|
|
16
|
+
- Removed `typescript` from core `peerDependencies`
|
|
17
|
+
- Added `engines` field (`node >=18`) to all packages
|
|
18
|
+
|
|
3
19
|
## 0.8.3
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
6
|
+
var __toCommonJS = (from) => {
|
|
7
|
+
var entry = __moduleCache.get(from), desc;
|
|
8
|
+
if (entry)
|
|
9
|
+
return entry;
|
|
10
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
13
|
+
get: () => from[key],
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
}));
|
|
16
|
+
__moduleCache.set(from, entry);
|
|
17
|
+
return entry;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var exports_src = {};
|
|
31
|
+
__export(exports_src, {
|
|
32
|
+
toErrorMessage: () => toErrorMessage,
|
|
33
|
+
replayActions: () => replayActions,
|
|
34
|
+
isValidSecret: () => isValidSecret,
|
|
35
|
+
generateId: () => generateId,
|
|
36
|
+
derivePlayerIdLegacy: () => derivePlayerIdLegacy,
|
|
37
|
+
derivePlayerId: () => derivePlayerId,
|
|
38
|
+
createGameReducer: () => createGameReducer,
|
|
39
|
+
applyMiddleware: () => applyMiddleware,
|
|
40
|
+
actionValidator: () => actionValidator,
|
|
41
|
+
actionLogger: () => actionLogger,
|
|
42
|
+
MessageTypes: () => MessageTypes,
|
|
43
|
+
MAX_PENDING_PINGS: () => MAX_PENDING_PINGS,
|
|
44
|
+
MAX_FRAME_SIZE: () => MAX_FRAME_SIZE,
|
|
45
|
+
KEEPALIVE_TIMEOUT: () => KEEPALIVE_TIMEOUT,
|
|
46
|
+
KEEPALIVE_INTERVAL: () => KEEPALIVE_INTERVAL,
|
|
47
|
+
InternalActionTypes: () => InternalActionTypes,
|
|
48
|
+
DEFAULT_WS_PORT_OFFSET: () => DEFAULT_WS_PORT_OFFSET,
|
|
49
|
+
DEFAULT_WS_PATH: () => DEFAULT_WS_PATH,
|
|
50
|
+
DEFAULT_SYNC_INTERVAL: () => DEFAULT_SYNC_INTERVAL,
|
|
51
|
+
DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
|
|
52
|
+
DEFAULT_MAX_DELAY: () => DEFAULT_MAX_DELAY,
|
|
53
|
+
DEFAULT_HTTP_PORT: () => DEFAULT_HTTP_PORT,
|
|
54
|
+
DEFAULT_DISCONNECT_TIMEOUT: () => DEFAULT_DISCONNECT_TIMEOUT,
|
|
55
|
+
DEFAULT_BASE_DELAY: () => DEFAULT_BASE_DELAY
|
|
56
|
+
});
|
|
57
|
+
module.exports = __toCommonJS(exports_src);
|
|
58
|
+
|
|
59
|
+
// src/types.ts
|
|
60
|
+
var InternalActionTypes = {
|
|
61
|
+
HYDRATE: "__HYDRATE__",
|
|
62
|
+
PLAYER_JOINED: "__PLAYER_JOINED__",
|
|
63
|
+
PLAYER_LEFT: "__PLAYER_LEFT__",
|
|
64
|
+
PLAYER_RECONNECTED: "__PLAYER_RECONNECTED__",
|
|
65
|
+
PLAYER_REMOVED: "__PLAYER_REMOVED__"
|
|
66
|
+
};
|
|
67
|
+
// src/middleware.ts
|
|
68
|
+
function applyMiddleware(...middlewares) {
|
|
69
|
+
return (reducer) => {
|
|
70
|
+
let currentState;
|
|
71
|
+
const api = {
|
|
72
|
+
getState: () => currentState
|
|
73
|
+
};
|
|
74
|
+
const baseDispatch = (action) => {
|
|
75
|
+
currentState = reducer(currentState, action);
|
|
76
|
+
return currentState;
|
|
77
|
+
};
|
|
78
|
+
const chain = middlewares.map((mw, index) => {
|
|
79
|
+
const layer = mw(api);
|
|
80
|
+
return (next) => {
|
|
81
|
+
return (action) => {
|
|
82
|
+
let nextCalled = false;
|
|
83
|
+
const safeNext = (a) => {
|
|
84
|
+
nextCalled = true;
|
|
85
|
+
return next(a);
|
|
86
|
+
};
|
|
87
|
+
const safeHandler = layer(safeNext);
|
|
88
|
+
try {
|
|
89
|
+
return safeHandler(action);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`[couch-kit] Middleware ${index} threw on action "${action.type}":`, error);
|
|
92
|
+
return nextCalled ? api.getState() : next(action);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
const dispatch = chain.reduceRight((next, layer) => layer(next), baseDispatch);
|
|
98
|
+
return (state, action) => {
|
|
99
|
+
currentState = state;
|
|
100
|
+
return dispatch(action);
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function actionLogger(options) {
|
|
105
|
+
const collapsed = options?.collapsed ?? true;
|
|
106
|
+
return (api) => (next) => (action) => {
|
|
107
|
+
const prevState = api.getState();
|
|
108
|
+
const group = collapsed ? console.groupCollapsed : console.group;
|
|
109
|
+
group(`[couch-kit] action ${action.type}`);
|
|
110
|
+
console.log("prev state", prevState);
|
|
111
|
+
console.log("action", action);
|
|
112
|
+
try {
|
|
113
|
+
const nextState = next(action);
|
|
114
|
+
console.log("next state", nextState);
|
|
115
|
+
return nextState;
|
|
116
|
+
} finally {
|
|
117
|
+
console.groupEnd();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
var _internalTypes;
|
|
122
|
+
function getInternalTypes() {
|
|
123
|
+
if (!_internalTypes) {
|
|
124
|
+
_internalTypes = new Set(Object.values(InternalActionTypes));
|
|
125
|
+
}
|
|
126
|
+
return _internalTypes;
|
|
127
|
+
}
|
|
128
|
+
function actionValidator(schema) {
|
|
129
|
+
return (_api) => (next) => (action) => {
|
|
130
|
+
if (getInternalTypes().has(action.type))
|
|
131
|
+
return next(action);
|
|
132
|
+
const validate = schema[action.type];
|
|
133
|
+
if (validate && !validate(action)) {
|
|
134
|
+
console.warn(`[couch-kit] Action "${action.type}" failed validation, skipping`);
|
|
135
|
+
return _api.getState();
|
|
136
|
+
}
|
|
137
|
+
return next(action);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/reducer.ts
|
|
142
|
+
function createGameReducer(reducer, options) {
|
|
143
|
+
const baseReducer = (state, action) => {
|
|
144
|
+
switch (action.type) {
|
|
145
|
+
case InternalActionTypes.HYDRATE:
|
|
146
|
+
return action.payload;
|
|
147
|
+
case InternalActionTypes.PLAYER_JOINED: {
|
|
148
|
+
const { id, name, avatar } = action.payload;
|
|
149
|
+
return {
|
|
150
|
+
...state,
|
|
151
|
+
players: {
|
|
152
|
+
...state.players,
|
|
153
|
+
[id]: {
|
|
154
|
+
id,
|
|
155
|
+
name,
|
|
156
|
+
avatar,
|
|
157
|
+
isHost: false,
|
|
158
|
+
connected: true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case InternalActionTypes.PLAYER_LEFT: {
|
|
164
|
+
const { playerId } = action.payload;
|
|
165
|
+
const player = state.players[playerId];
|
|
166
|
+
if (!player)
|
|
167
|
+
return state;
|
|
168
|
+
return {
|
|
169
|
+
...state,
|
|
170
|
+
players: {
|
|
171
|
+
...state.players,
|
|
172
|
+
[playerId]: { ...player, connected: false }
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
case InternalActionTypes.PLAYER_RECONNECTED: {
|
|
177
|
+
const { playerId } = action.payload;
|
|
178
|
+
const player = state.players[playerId];
|
|
179
|
+
if (!player)
|
|
180
|
+
return state;
|
|
181
|
+
return {
|
|
182
|
+
...state,
|
|
183
|
+
players: {
|
|
184
|
+
...state.players,
|
|
185
|
+
[playerId]: { ...player, connected: true }
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
case InternalActionTypes.PLAYER_REMOVED: {
|
|
190
|
+
const { playerId } = action.payload;
|
|
191
|
+
if (!state.players[playerId])
|
|
192
|
+
return state;
|
|
193
|
+
const { [playerId]: _, ...remainingPlayers } = state.players;
|
|
194
|
+
return {
|
|
195
|
+
...state,
|
|
196
|
+
players: remainingPlayers
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
return reducer(state, action);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const middlewares = options?.middleware;
|
|
204
|
+
if (!middlewares || middlewares.length === 0)
|
|
205
|
+
return baseReducer;
|
|
206
|
+
return applyMiddleware(...middlewares)(baseReducer);
|
|
207
|
+
}
|
|
208
|
+
// src/protocol.ts
|
|
209
|
+
var MessageTypes = {
|
|
210
|
+
JOIN: "JOIN",
|
|
211
|
+
ACTION: "ACTION",
|
|
212
|
+
PING: "PING",
|
|
213
|
+
ASSETS_LOADED: "ASSETS_LOADED",
|
|
214
|
+
WELCOME: "WELCOME",
|
|
215
|
+
STATE_UPDATE: "STATE_UPDATE",
|
|
216
|
+
PONG: "PONG",
|
|
217
|
+
RECONNECTED: "RECONNECTED",
|
|
218
|
+
ERROR: "ERROR"
|
|
219
|
+
};
|
|
220
|
+
// src/constants.ts
|
|
221
|
+
var DEFAULT_HTTP_PORT = 8080;
|
|
222
|
+
var DEFAULT_WS_PORT_OFFSET = 2;
|
|
223
|
+
var DEFAULT_WS_PATH = "/ws";
|
|
224
|
+
var MAX_FRAME_SIZE = 1024 * 1024;
|
|
225
|
+
var DEFAULT_MAX_RETRIES = 5;
|
|
226
|
+
var DEFAULT_BASE_DELAY = 1000;
|
|
227
|
+
var DEFAULT_MAX_DELAY = 1e4;
|
|
228
|
+
var DEFAULT_SYNC_INTERVAL = 5000;
|
|
229
|
+
var MAX_PENDING_PINGS = 50;
|
|
230
|
+
var DEFAULT_DISCONNECT_TIMEOUT = 5 * 60 * 1000;
|
|
231
|
+
var KEEPALIVE_INTERVAL = 30000;
|
|
232
|
+
var KEEPALIVE_TIMEOUT = 1e4;
|
|
233
|
+
function generateId() {
|
|
234
|
+
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
|
|
235
|
+
return globalThis.crypto.randomUUID();
|
|
236
|
+
}
|
|
237
|
+
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
|
|
238
|
+
const bytes = new Uint8Array(16);
|
|
239
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
240
|
+
return Array.from(bytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
241
|
+
}
|
|
242
|
+
const a = Math.random().toString(36).substring(2, 15);
|
|
243
|
+
const b = Math.random().toString(36).substring(2, 10);
|
|
244
|
+
return a + b;
|
|
245
|
+
}
|
|
246
|
+
function isValidSecret(secret) {
|
|
247
|
+
const hex = secret.replace(/-/g, "");
|
|
248
|
+
return hex.length >= 32 && /^[0-9a-f]+$/i.test(hex);
|
|
249
|
+
}
|
|
250
|
+
async function derivePlayerId(secret) {
|
|
251
|
+
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle?.digest === "function") {
|
|
252
|
+
const data = new TextEncoder().encode(secret);
|
|
253
|
+
const hash = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
254
|
+
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
|
|
255
|
+
}
|
|
256
|
+
return derivePlayerIdLegacy(secret);
|
|
257
|
+
}
|
|
258
|
+
function derivePlayerIdLegacy(secret) {
|
|
259
|
+
return secret.replace(/-/g, "").slice(0, 16);
|
|
260
|
+
}
|
|
261
|
+
function toErrorMessage(error) {
|
|
262
|
+
if (error instanceof Error)
|
|
263
|
+
return error.message;
|
|
264
|
+
return String(error);
|
|
265
|
+
}
|
|
266
|
+
// src/replay.ts
|
|
267
|
+
function replayActions(recording, reducer) {
|
|
268
|
+
const snapshots = [];
|
|
269
|
+
let currentState = recording.initialState;
|
|
270
|
+
for (let i = 0;i < recording.actions.length; i++) {
|
|
271
|
+
const { action, timestamp } = recording.actions[i];
|
|
272
|
+
currentState = reducer(currentState, action);
|
|
273
|
+
snapshots.push({
|
|
274
|
+
state: currentState,
|
|
275
|
+
action,
|
|
276
|
+
timestamp,
|
|
277
|
+
index: i
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const endTimestamp = recording.endTimestamp ?? (recording.actions.length > 0 ? recording.actions[recording.actions.length - 1].timestamp : recording.startTimestamp);
|
|
281
|
+
return {
|
|
282
|
+
finalState: currentState,
|
|
283
|
+
snapshots,
|
|
284
|
+
duration: endTimestamp - recording.startTimestamp,
|
|
285
|
+
actionCount: recording.actions.length
|
|
286
|
+
};
|
|
287
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var InternalActionTypes = {
|
|
3
|
+
HYDRATE: "__HYDRATE__",
|
|
4
|
+
PLAYER_JOINED: "__PLAYER_JOINED__",
|
|
5
|
+
PLAYER_LEFT: "__PLAYER_LEFT__",
|
|
6
|
+
PLAYER_RECONNECTED: "__PLAYER_RECONNECTED__",
|
|
7
|
+
PLAYER_REMOVED: "__PLAYER_REMOVED__"
|
|
8
|
+
};
|
|
1
9
|
// src/middleware.ts
|
|
2
10
|
function applyMiddleware(...middlewares) {
|
|
3
11
|
return (reducer) => {
|
|
@@ -72,14 +80,7 @@ function actionValidator(schema) {
|
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
// src/
|
|
76
|
-
var InternalActionTypes = {
|
|
77
|
-
HYDRATE: "__HYDRATE__",
|
|
78
|
-
PLAYER_JOINED: "__PLAYER_JOINED__",
|
|
79
|
-
PLAYER_LEFT: "__PLAYER_LEFT__",
|
|
80
|
-
PLAYER_RECONNECTED: "__PLAYER_RECONNECTED__",
|
|
81
|
-
PLAYER_REMOVED: "__PLAYER_REMOVED__"
|
|
82
|
-
};
|
|
83
|
+
// src/reducer.ts
|
|
83
84
|
function createGameReducer(reducer, options) {
|
|
84
85
|
const baseReducer = (state, action) => {
|
|
85
86
|
switch (action.type) {
|
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC"}
|
package/lib/reducer.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Middleware } from "./middleware";
|
|
2
|
+
import type { IGameState, IAction, InternalAction, GameReducer } from "./types";
|
|
3
|
+
/** Options for `createGameReducer`. */
|
|
4
|
+
export interface CreateGameReducerOptions<S extends IGameState, A extends IAction> {
|
|
5
|
+
/**
|
|
6
|
+
* Optional middleware stack. Middlewares are applied in order — the first
|
|
7
|
+
* middleware in the array is the outermost layer and sees every action first.
|
|
8
|
+
*
|
|
9
|
+
* Middleware can observe **and** transform both user actions and internal
|
|
10
|
+
* actions. Custom middleware should avoid blocking internal actions (types
|
|
11
|
+
* prefixed with `__`) to prevent breaking framework behaviour.
|
|
12
|
+
*/
|
|
13
|
+
middleware?: Middleware<S, A>[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wraps a user-provided reducer with automatic handling of internal actions:
|
|
17
|
+
*
|
|
18
|
+
* - `__HYDRATE__` -- Replaces state wholesale (used for server→client state sync).
|
|
19
|
+
* - `__PLAYER_JOINED__` -- Adds a player to `state.players`.
|
|
20
|
+
* - `__PLAYER_LEFT__` -- Marks a player as disconnected in `state.players`.
|
|
21
|
+
* - `__PLAYER_RECONNECTED__` -- Restores a returning player's connection status, preserving all existing data.
|
|
22
|
+
* - `__PLAYER_REMOVED__` -- Permanently removes a timed-out disconnected player from `state.players`.
|
|
23
|
+
*
|
|
24
|
+
* The wrapped reducer accepts both `A` (user actions) and `InternalAction<S>`.
|
|
25
|
+
* User reducers only need to handle their own action types.
|
|
26
|
+
*
|
|
27
|
+
* When `options.middleware` is provided, the middleware stack wraps around the
|
|
28
|
+
* entire reducer (including internal-action handling). Middleware can observe
|
|
29
|
+
* all actions flowing through the reducer.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createGameReducer<S extends IGameState, A extends IAction>(reducer: GameReducer<S, A>, options?: CreateGameReducerOptions<S, A>): GameReducer<S, A | InternalAction<S>>;
|
|
32
|
+
//# sourceMappingURL=reducer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/reducer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,uCAAuC;AACvC,MAAM,WAAW,wBAAwB,CACvC,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,OAAO;IAEjB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;CACjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,EACvE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,GACvC,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAgFvC"}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Middleware } from "./middleware";
|
|
2
1
|
/**
|
|
3
2
|
* Represents a player connected to the game session.
|
|
4
3
|
*
|
|
@@ -85,33 +84,4 @@ export declare const InternalActionTypes: {
|
|
|
85
84
|
* Used by both `GameHostProvider` and `useGameClient` to define game logic.
|
|
86
85
|
*/
|
|
87
86
|
export type GameReducer<S extends IGameState, A extends IAction> = (state: S, action: A) => S;
|
|
88
|
-
/** Options for `createGameReducer`. */
|
|
89
|
-
export interface CreateGameReducerOptions<S extends IGameState, A extends IAction> {
|
|
90
|
-
/**
|
|
91
|
-
* Optional middleware stack. Middlewares are applied in order — the first
|
|
92
|
-
* middleware in the array is the outermost layer and sees every action first.
|
|
93
|
-
*
|
|
94
|
-
* Middleware can observe **and** transform both user actions and internal
|
|
95
|
-
* actions. Custom middleware should avoid blocking internal actions (types
|
|
96
|
-
* prefixed with `__`) to prevent breaking framework behaviour.
|
|
97
|
-
*/
|
|
98
|
-
middleware?: Middleware<S, A>[];
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Wraps a user-provided reducer with automatic handling of internal actions:
|
|
102
|
-
*
|
|
103
|
-
* - `__HYDRATE__` -- Replaces state wholesale (used for server→client state sync).
|
|
104
|
-
* - `__PLAYER_JOINED__` -- Adds a player to `state.players`.
|
|
105
|
-
* - `__PLAYER_LEFT__` -- Marks a player as disconnected in `state.players`.
|
|
106
|
-
* - `__PLAYER_RECONNECTED__` -- Restores a returning player's connection status, preserving all existing data.
|
|
107
|
-
* - `__PLAYER_REMOVED__` -- Permanently removes a timed-out disconnected player from `state.players`.
|
|
108
|
-
*
|
|
109
|
-
* The wrapped reducer accepts both `A` (user actions) and `InternalAction<S>`.
|
|
110
|
-
* User reducers only need to handle their own action types.
|
|
111
|
-
*
|
|
112
|
-
* When `options.middleware` is provided, the middleware stack wraps around the
|
|
113
|
-
* entire reducer (including internal-action handling). Middleware can observe
|
|
114
|
-
* all actions flowing through the reducer.
|
|
115
|
-
*/
|
|
116
|
-
export declare function createGameReducer<S extends IGameState, A extends IAction>(reducer: GameReducer<S, A>, options?: CreateGameReducerOptions<S, A>): GameReducer<S, A | InternalAction<S>>;
|
|
117
87
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IACxD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,CAAC,CAAA;CAAE,GACnC;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD,GACD;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAElE,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,OAAO,IAAI,CACjE,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,CAAC,KACN,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@couch-kit/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
7
7
|
},
|
|
8
8
|
"description": "Shared types, protocol, and game reducer for Couch Kit party games",
|
|
9
9
|
"license": "MIT",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
12
|
+
},
|
|
10
13
|
"repository": {
|
|
11
14
|
"type": "git",
|
|
12
15
|
"url": "https://github.com/faluciano/react-native-couch-kit.git",
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
".": {
|
|
27
30
|
"types": "./lib/index.d.ts",
|
|
28
31
|
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.cjs",
|
|
29
33
|
"default": "./dist/index.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
],
|
|
39
43
|
"sideEffects": false,
|
|
40
44
|
"scripts": {
|
|
41
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target browser && tsc -p tsconfig.build.json",
|
|
45
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target browser && bun build ./src/index.ts --outfile ./dist/index.cjs --target browser --format cjs && tsc -p tsconfig.build.json",
|
|
42
46
|
"prepublishOnly": "bun run build",
|
|
43
47
|
"test": "bun test",
|
|
44
48
|
"lint": "eslint src/",
|
|
@@ -46,10 +50,6 @@
|
|
|
46
50
|
"clean": "rm -rf dist lib"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
|
-
"typescript": "^5.4.0"
|
|
50
|
-
"eslint": "^8.57.0"
|
|
51
|
-
},
|
|
52
|
-
"peerDependencies": {
|
|
53
|
-
"typescript": "^5.0.0"
|
|
53
|
+
"typescript": "^5.4.0"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/index.ts
CHANGED
package/src/reducer.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { applyMiddleware } from "./middleware";
|
|
2
|
+
import type { Middleware } from "./middleware";
|
|
3
|
+
import { InternalActionTypes } from "./types";
|
|
4
|
+
import type {
|
|
5
|
+
IGameState,
|
|
6
|
+
IAction,
|
|
7
|
+
InternalAction,
|
|
8
|
+
GameReducer,
|
|
9
|
+
} from "./types";
|
|
10
|
+
|
|
11
|
+
/** Options for `createGameReducer`. */
|
|
12
|
+
export interface CreateGameReducerOptions<
|
|
13
|
+
S extends IGameState,
|
|
14
|
+
A extends IAction,
|
|
15
|
+
> {
|
|
16
|
+
/**
|
|
17
|
+
* Optional middleware stack. Middlewares are applied in order — the first
|
|
18
|
+
* middleware in the array is the outermost layer and sees every action first.
|
|
19
|
+
*
|
|
20
|
+
* Middleware can observe **and** transform both user actions and internal
|
|
21
|
+
* actions. Custom middleware should avoid blocking internal actions (types
|
|
22
|
+
* prefixed with `__`) to prevent breaking framework behaviour.
|
|
23
|
+
*/
|
|
24
|
+
middleware?: Middleware<S, A>[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wraps a user-provided reducer with automatic handling of internal actions:
|
|
29
|
+
*
|
|
30
|
+
* - `__HYDRATE__` -- Replaces state wholesale (used for server→client state sync).
|
|
31
|
+
* - `__PLAYER_JOINED__` -- Adds a player to `state.players`.
|
|
32
|
+
* - `__PLAYER_LEFT__` -- Marks a player as disconnected in `state.players`.
|
|
33
|
+
* - `__PLAYER_RECONNECTED__` -- Restores a returning player's connection status, preserving all existing data.
|
|
34
|
+
* - `__PLAYER_REMOVED__` -- Permanently removes a timed-out disconnected player from `state.players`.
|
|
35
|
+
*
|
|
36
|
+
* The wrapped reducer accepts both `A` (user actions) and `InternalAction<S>`.
|
|
37
|
+
* User reducers only need to handle their own action types.
|
|
38
|
+
*
|
|
39
|
+
* When `options.middleware` is provided, the middleware stack wraps around the
|
|
40
|
+
* entire reducer (including internal-action handling). Middleware can observe
|
|
41
|
+
* all actions flowing through the reducer.
|
|
42
|
+
*/
|
|
43
|
+
export function createGameReducer<S extends IGameState, A extends IAction>(
|
|
44
|
+
reducer: GameReducer<S, A>,
|
|
45
|
+
options?: CreateGameReducerOptions<S, A>,
|
|
46
|
+
): GameReducer<S, A | InternalAction<S>> {
|
|
47
|
+
const baseReducer: GameReducer<S, A | InternalAction<S>> = (
|
|
48
|
+
state: S,
|
|
49
|
+
action: A | InternalAction<S>,
|
|
50
|
+
): S => {
|
|
51
|
+
switch (action.type) {
|
|
52
|
+
case InternalActionTypes.HYDRATE:
|
|
53
|
+
return (action as InternalAction<S> & { type: "__HYDRATE__" }).payload;
|
|
54
|
+
|
|
55
|
+
case InternalActionTypes.PLAYER_JOINED: {
|
|
56
|
+
const { id, name, avatar } = (
|
|
57
|
+
action as InternalAction<S> & { type: "__PLAYER_JOINED__" }
|
|
58
|
+
).payload;
|
|
59
|
+
return {
|
|
60
|
+
...state,
|
|
61
|
+
players: {
|
|
62
|
+
...state.players,
|
|
63
|
+
[id]: {
|
|
64
|
+
id,
|
|
65
|
+
name,
|
|
66
|
+
avatar,
|
|
67
|
+
isHost: false,
|
|
68
|
+
connected: true,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case InternalActionTypes.PLAYER_LEFT: {
|
|
75
|
+
const { playerId } = (
|
|
76
|
+
action as InternalAction<S> & { type: "__PLAYER_LEFT__" }
|
|
77
|
+
).payload;
|
|
78
|
+
const player = state.players[playerId];
|
|
79
|
+
if (!player) return state;
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
players: {
|
|
83
|
+
...state.players,
|
|
84
|
+
[playerId]: { ...player, connected: false },
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case InternalActionTypes.PLAYER_RECONNECTED: {
|
|
90
|
+
const { playerId } = (
|
|
91
|
+
action as InternalAction<S> & { type: "__PLAYER_RECONNECTED__" }
|
|
92
|
+
).payload;
|
|
93
|
+
const player = state.players[playerId];
|
|
94
|
+
if (!player) return state;
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
players: {
|
|
98
|
+
...state.players,
|
|
99
|
+
[playerId]: { ...player, connected: true },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case InternalActionTypes.PLAYER_REMOVED: {
|
|
105
|
+
const { playerId } = (
|
|
106
|
+
action as InternalAction<S> & { type: "__PLAYER_REMOVED__" }
|
|
107
|
+
).payload;
|
|
108
|
+
if (!state.players[playerId]) return state;
|
|
109
|
+
const { [playerId]: _, ...remainingPlayers } = state.players;
|
|
110
|
+
return {
|
|
111
|
+
...state,
|
|
112
|
+
players: remainingPlayers,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
return reducer(state, action as A);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const middlewares = options?.middleware;
|
|
122
|
+
|
|
123
|
+
if (!middlewares || middlewares.length === 0) return baseReducer;
|
|
124
|
+
|
|
125
|
+
return applyMiddleware<S, A>(...middlewares)(baseReducer);
|
|
126
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { applyMiddleware } from "./middleware";
|
|
2
|
-
import type { Middleware } from "./middleware";
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* Represents a player connected to the game session.
|
|
6
3
|
*
|
|
@@ -78,120 +75,3 @@ export type GameReducer<S extends IGameState, A extends IAction> = (
|
|
|
78
75
|
state: S,
|
|
79
76
|
action: A,
|
|
80
77
|
) => S;
|
|
81
|
-
|
|
82
|
-
/** Options for `createGameReducer`. */
|
|
83
|
-
export interface CreateGameReducerOptions<
|
|
84
|
-
S extends IGameState,
|
|
85
|
-
A extends IAction,
|
|
86
|
-
> {
|
|
87
|
-
/**
|
|
88
|
-
* Optional middleware stack. Middlewares are applied in order — the first
|
|
89
|
-
* middleware in the array is the outermost layer and sees every action first.
|
|
90
|
-
*
|
|
91
|
-
* Middleware can observe **and** transform both user actions and internal
|
|
92
|
-
* actions. Custom middleware should avoid blocking internal actions (types
|
|
93
|
-
* prefixed with `__`) to prevent breaking framework behaviour.
|
|
94
|
-
*/
|
|
95
|
-
middleware?: Middleware<S, A>[];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Wraps a user-provided reducer with automatic handling of internal actions:
|
|
100
|
-
*
|
|
101
|
-
* - `__HYDRATE__` -- Replaces state wholesale (used for server→client state sync).
|
|
102
|
-
* - `__PLAYER_JOINED__` -- Adds a player to `state.players`.
|
|
103
|
-
* - `__PLAYER_LEFT__` -- Marks a player as disconnected in `state.players`.
|
|
104
|
-
* - `__PLAYER_RECONNECTED__` -- Restores a returning player's connection status, preserving all existing data.
|
|
105
|
-
* - `__PLAYER_REMOVED__` -- Permanently removes a timed-out disconnected player from `state.players`.
|
|
106
|
-
*
|
|
107
|
-
* The wrapped reducer accepts both `A` (user actions) and `InternalAction<S>`.
|
|
108
|
-
* User reducers only need to handle their own action types.
|
|
109
|
-
*
|
|
110
|
-
* When `options.middleware` is provided, the middleware stack wraps around the
|
|
111
|
-
* entire reducer (including internal-action handling). Middleware can observe
|
|
112
|
-
* all actions flowing through the reducer.
|
|
113
|
-
*/
|
|
114
|
-
export function createGameReducer<S extends IGameState, A extends IAction>(
|
|
115
|
-
reducer: GameReducer<S, A>,
|
|
116
|
-
options?: CreateGameReducerOptions<S, A>,
|
|
117
|
-
): GameReducer<S, A | InternalAction<S>> {
|
|
118
|
-
const baseReducer: GameReducer<S, A | InternalAction<S>> = (
|
|
119
|
-
state: S,
|
|
120
|
-
action: A | InternalAction<S>,
|
|
121
|
-
): S => {
|
|
122
|
-
switch (action.type) {
|
|
123
|
-
case InternalActionTypes.HYDRATE:
|
|
124
|
-
return (action as InternalAction<S> & { type: "__HYDRATE__" }).payload;
|
|
125
|
-
|
|
126
|
-
case InternalActionTypes.PLAYER_JOINED: {
|
|
127
|
-
const { id, name, avatar } = (
|
|
128
|
-
action as InternalAction<S> & { type: "__PLAYER_JOINED__" }
|
|
129
|
-
).payload;
|
|
130
|
-
return {
|
|
131
|
-
...state,
|
|
132
|
-
players: {
|
|
133
|
-
...state.players,
|
|
134
|
-
[id]: {
|
|
135
|
-
id,
|
|
136
|
-
name,
|
|
137
|
-
avatar,
|
|
138
|
-
isHost: false,
|
|
139
|
-
connected: true,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
case InternalActionTypes.PLAYER_LEFT: {
|
|
146
|
-
const { playerId } = (
|
|
147
|
-
action as InternalAction<S> & { type: "__PLAYER_LEFT__" }
|
|
148
|
-
).payload;
|
|
149
|
-
const player = state.players[playerId];
|
|
150
|
-
if (!player) return state;
|
|
151
|
-
return {
|
|
152
|
-
...state,
|
|
153
|
-
players: {
|
|
154
|
-
...state.players,
|
|
155
|
-
[playerId]: { ...player, connected: false },
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
case InternalActionTypes.PLAYER_RECONNECTED: {
|
|
161
|
-
const { playerId } = (
|
|
162
|
-
action as InternalAction<S> & { type: "__PLAYER_RECONNECTED__" }
|
|
163
|
-
).payload;
|
|
164
|
-
const player = state.players[playerId];
|
|
165
|
-
if (!player) return state;
|
|
166
|
-
return {
|
|
167
|
-
...state,
|
|
168
|
-
players: {
|
|
169
|
-
...state.players,
|
|
170
|
-
[playerId]: { ...player, connected: true },
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
case InternalActionTypes.PLAYER_REMOVED: {
|
|
176
|
-
const { playerId } = (
|
|
177
|
-
action as InternalAction<S> & { type: "__PLAYER_REMOVED__" }
|
|
178
|
-
).payload;
|
|
179
|
-
if (!state.players[playerId]) return state;
|
|
180
|
-
const { [playerId]: _, ...remainingPlayers } = state.players;
|
|
181
|
-
return {
|
|
182
|
-
...state,
|
|
183
|
-
players: remainingPlayers,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
default:
|
|
188
|
-
return reducer(state, action as A);
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const middlewares = options?.middleware;
|
|
193
|
-
|
|
194
|
-
if (!middlewares || middlewares.length === 0) return baseReducer;
|
|
195
|
-
|
|
196
|
-
return applyMiddleware<S, A>(...middlewares)(baseReducer);
|
|
197
|
-
}
|