@vgai/p2p-colyseus 0.5.2 → 0.5.4

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/src/codec.ts DELETED
@@ -1,78 +0,0 @@
1
- import type { StatePatch, StatePatchOperation } from './protocol';
2
- import { encodeSnapshotState } from './schema';
3
-
4
- export function encodeSnapshot(value: unknown): unknown {
5
- return encodeSnapshotState(value);
6
- }
7
-
8
- export function createStatePatch(previous: unknown, next: unknown): StatePatch {
9
- const operations: StatePatchOperation[] = [];
10
- diffValue([], previous, next, operations);
11
- return { type: 'patch', operations };
12
- }
13
-
14
- export function applyStatePatch(previous: unknown, patch: StatePatch): unknown {
15
- const root = clone(previous);
16
- for (const operation of patch.operations) {
17
- if (operation.op === 'set') setPath(root, operation.path, operation.value);
18
- else deletePath(root, operation.path);
19
- }
20
- return root;
21
- }
22
-
23
- export function clone<T>(value: T): T {
24
- if (value === undefined) return value;
25
- return JSON.parse(JSON.stringify(value)) as T;
26
- }
27
-
28
- function diffValue(
29
- path: string[],
30
- previous: unknown,
31
- next: unknown,
32
- operations: StatePatchOperation[],
33
- ): void {
34
- if (isRecord(previous) && isRecord(next)) {
35
- for (const key of Object.keys(previous)) {
36
- if (!(key in next)) operations.push({ op: 'delete', path: [...path, key] });
37
- }
38
- for (const [key, nextValue] of Object.entries(next)) {
39
- diffValue([...path, key], previous[key], nextValue, operations);
40
- }
41
- return;
42
- }
43
-
44
- if (JSON.stringify(previous) !== JSON.stringify(next)) {
45
- operations.push({ op: 'set', path, value: clone(next) });
46
- }
47
- }
48
-
49
- function setPath(root: unknown, path: readonly string[], value: unknown): void {
50
- if (path.length === 0) return;
51
- const parent = parentAt(root, path);
52
- parent[path[path.length - 1]!] = clone(value);
53
- }
54
-
55
- function deletePath(root: unknown, path: readonly string[]): void {
56
- if (path.length === 0) return;
57
- const parent = parentAt(root, path);
58
- delete parent[path[path.length - 1]!];
59
- }
60
-
61
- function parentAt(root: unknown, path: readonly string[]): Record<string, unknown> {
62
- if (!isRecord(root)) throw new Error('Cannot patch non-object state root');
63
- let parent = root;
64
- for (const segment of path.slice(0, -1)) {
65
- const child = parent[segment];
66
- if (!isRecord(child)) {
67
- parent[segment] = {};
68
- parent = parent[segment] as Record<string, unknown>;
69
- } else {
70
- parent = child;
71
- }
72
- }
73
- return parent;
74
- }
75
-
76
- function isRecord(value: unknown): value is Record<string, unknown> {
77
- return typeof value === 'object' && value !== null && !Array.isArray(value);
78
- }
@@ -1,164 +0,0 @@
1
- type AddHandler = (item: Record<string, unknown>, key: string) => void;
2
- type RemoveHandler = (item: Record<string, unknown>, key: string) => void;
3
- type ChangeHandler = () => void;
4
- type StateHandler = (state: Record<string, unknown>) => void;
5
-
6
- export class ClientReplication {
7
- state: Record<string, unknown> = {};
8
- private readonly collectionAdd = new Map<string, Set<AddHandler>>();
9
- private readonly collectionRemove = new Map<string, Set<RemoveHandler>>();
10
- private readonly itemChange = new WeakMap<object, Set<ChangeHandler>>();
11
- private readonly stateChange = new Set<StateHandler>();
12
-
13
- applySnapshot(next: unknown): void {
14
- const incoming = isRecord(next) ? next : {};
15
- this.mergeRoot(incoming);
16
- for (const handler of [...this.stateChange]) handler(this.state);
17
- }
18
-
19
- onAdd(collection: string, handler: AddHandler): () => void {
20
- const handlers = getSet(this.collectionAdd, collection);
21
- handlers.add(handler);
22
- const current = this.state[collection];
23
- if (isRecord(current)) {
24
- for (const [key, value] of Object.entries(current)) {
25
- if (isRecord(value)) handler(value, key);
26
- }
27
- }
28
- return () => handlers.delete(handler);
29
- }
30
-
31
- onRemove(collection: string, handler: RemoveHandler): () => void {
32
- const handlers = getSet(this.collectionRemove, collection);
33
- handlers.add(handler);
34
- return () => handlers.delete(handler);
35
- }
36
-
37
- onChange(item: Record<string, unknown>, handler: ChangeHandler): () => void {
38
- const handlers = this.itemChange.get(item) ?? new Set<ChangeHandler>();
39
- handlers.add(handler);
40
- this.itemChange.set(item, handlers);
41
- return () => handlers.delete(handler);
42
- }
43
-
44
- /**
45
- * Mirror `properties` of a replicated item onto `target` — immediately (when
46
- * `immediate`, the default, matching the real SDK) and again on every change.
47
- * Compat with @colyseus/sdk's `StateCallbacks.bindTo(from, to, props?, immediate?)`;
48
- * returns the unbind function.
49
- */
50
- bindTo<TTarget extends Record<string, unknown>>(
51
- from: Record<string, unknown>,
52
- to: TTarget,
53
- properties?: readonly string[],
54
- immediate = true,
55
- ): () => void {
56
- const copy = () => {
57
- const keys = properties ?? Object.keys(from).filter((key) => typeof from[key] !== 'function');
58
- for (const key of keys) {
59
- (to as Record<string, unknown>)[key] = from[key];
60
- }
61
- };
62
- if (immediate) copy();
63
- return this.onChange(from, copy);
64
- }
65
-
66
- onStateChange(handler: StateHandler): () => void {
67
- this.stateChange.add(handler);
68
- return () => this.stateChange.delete(handler);
69
- }
70
-
71
- private mergeRoot(incoming: Record<string, unknown>): void {
72
- for (const key of Object.keys(this.state)) {
73
- if (!(key in incoming)) delete this.state[key];
74
- }
75
-
76
- for (const [key, value] of Object.entries(incoming)) {
77
- const current = this.state[key];
78
- if (isRecord(value) && isCollectionRecord(value)) {
79
- this.state[key] = this.mergeCollection(key, isRecord(current) ? current : {}, value);
80
- } else if (isRecord(value)) {
81
- this.state[key] = mergeRecord(isRecord(current) ? current : {}, value, this.itemChange);
82
- } else {
83
- this.state[key] = value;
84
- }
85
- }
86
- }
87
-
88
- private mergeCollection(
89
- collection: string,
90
- current: Record<string, unknown>,
91
- incoming: Record<string, unknown>,
92
- ): Record<string, unknown> {
93
- const removeHandlers = this.collectionRemove.get(collection);
94
- for (const key of Object.keys(current)) {
95
- if (!(key in incoming)) {
96
- const old = current[key];
97
- if (isRecord(old)) {
98
- for (const handler of removeHandlers ?? []) handler(old, key);
99
- }
100
- delete current[key];
101
- }
102
- }
103
-
104
- const addHandlers = this.collectionAdd.get(collection);
105
- for (const [key, value] of Object.entries(incoming)) {
106
- if (!isRecord(value)) {
107
- current[key] = value;
108
- continue;
109
- }
110
-
111
- const existed = key in current;
112
- const target = isRecord(current[key]) ? current[key] : {};
113
- current[key] = mergeRecord(target, value, this.itemChange);
114
- if (!existed && isRecord(current[key])) {
115
- for (const handler of addHandlers ?? []) handler(current[key], key);
116
- }
117
- }
118
-
119
- return current;
120
- }
121
- }
122
-
123
- function mergeRecord(
124
- target: Record<string, unknown>,
125
- incoming: Record<string, unknown>,
126
- itemChange: WeakMap<object, Set<ChangeHandler>>,
127
- ): Record<string, unknown> {
128
- let changed = false;
129
- for (const key of Object.keys(target)) {
130
- if (!(key in incoming)) {
131
- delete target[key];
132
- changed = true;
133
- }
134
- }
135
- for (const [key, value] of Object.entries(incoming)) {
136
- if (isRecord(value)) {
137
- const child = isRecord(target[key]) ? target[key] : {};
138
- target[key] = mergeRecord(child, value, itemChange);
139
- } else if (target[key] !== value) {
140
- target[key] = value;
141
- changed = true;
142
- }
143
- }
144
- if (changed) {
145
- for (const handler of itemChange.get(target) ?? []) handler();
146
- }
147
- return target;
148
- }
149
-
150
- function getSet<K, V>(map: Map<K, Set<V>>, key: K): Set<V> {
151
- const existing = map.get(key);
152
- if (existing) return existing;
153
- const created = new Set<V>();
154
- map.set(key, created);
155
- return created;
156
- }
157
-
158
- function isRecord(value: unknown): value is Record<string, unknown> {
159
- return typeof value === 'object' && value !== null && !Array.isArray(value);
160
- }
161
-
162
- function isCollectionRecord(value: Record<string, unknown>): boolean {
163
- return Object.values(value).every((item) => isRecord(item));
164
- }
package/src/schema.ts DELETED
@@ -1,287 +0,0 @@
1
- const SCHEMA_TYPES = Symbol('p2pColyseusSchemaTypes');
2
-
3
- export const $refId = '~refId';
4
- export const $track = '~track';
5
- export const $encoder = '~encoder';
6
- export const $decoder = '~decoder';
7
- export const $filter = '~filter';
8
- export const $getByIndex = '~getByIndex';
9
- export const $deleteByIndex = '~deleteByIndex';
10
- export const $changes = '~changes';
11
- export const $childType = '~childType';
12
-
13
- export const OPERATION = {
14
- ADD: 128,
15
- REPLACE: 0,
16
- DELETE: 64,
17
- DELETE_AND_MOVE: 96,
18
- MOVE_AND_ADD: 160,
19
- DELETE_AND_ADD: 192,
20
- CLEAR: 10,
21
- REVERSE: 15,
22
- MOVE: 32,
23
- DELETE_BY_REFID: 33,
24
- ADD_BY_REFID: 129,
25
- } as const;
26
-
27
- export class Schema {
28
- constructor(props?: Record<string, unknown>) {
29
- if (props) this.assign(props as Partial<this>);
30
- }
31
-
32
- static getSchemaTypes(): ReadonlyMap<string, unknown> {
33
- const combined = new Map<string, unknown>();
34
- // biome-ignore lint/complexity/noThisInStatic: schema metadata must be read from the concrete subclass.
35
- const parent = Object.getPrototypeOf(this) as {
36
- getSchemaTypes?: () => ReadonlyMap<string, unknown>;
37
- } | null;
38
- if (parent && parent !== Schema && parent.getSchemaTypes) {
39
- for (const [key, value] of parent.getSchemaTypes()) combined.set(key, value);
40
- }
41
- // biome-ignore lint/complexity/noThisInStatic: schema metadata must be read from the concrete subclass.
42
- const own = (this as unknown as { [SCHEMA_TYPES]?: Map<string, unknown> })[SCHEMA_TYPES];
43
- for (const [key, value] of own ?? []) combined.set(key, value);
44
- return combined;
45
- }
46
-
47
- static is(type: unknown): boolean {
48
- return typeof type === 'function' && (type === Schema || type.prototype instanceof Schema);
49
- }
50
-
51
- static isSchema(obj: unknown): obj is Schema {
52
- return obj instanceof Schema;
53
- }
54
-
55
- assign<T extends Partial<this>>(props: T): this {
56
- Object.assign(this, props);
57
- return this;
58
- }
59
-
60
- restore(jsonData: Record<string, unknown>): this {
61
- Object.assign(this, jsonData);
62
- return this;
63
- }
64
-
65
- setDirty(_property?: string | number | symbol, _operation?: unknown): void {
66
- // This shim snapshots mutable state each tick, so explicit dirty marking is unnecessary.
67
- }
68
-
69
- clone(): this {
70
- const cloned = new (this.constructor as new () => this)();
71
- return cloned.restore(deepClone(this.toJSON() as Record<string, unknown>));
72
- }
73
-
74
- toJSON(): unknown {
75
- return encodeSnapshotState(this);
76
- }
77
-
78
- discardAllChanges(): void {
79
- // Compatibility no-op. Change tracking is handled by snapshot diffing.
80
- }
81
- }
82
-
83
- export class MapSchema<T> extends Map<string, T> {
84
- constructor(entries?: Iterable<readonly [string, T]> | null) {
85
- super(entries);
86
- }
87
- }
88
-
89
- export class ArraySchema<T> extends Array<T> {
90
- constructor(...items: T[]) {
91
- super(...items);
92
- }
93
- }
94
-
95
- export class CollectionSchema<T> extends Set<T> {
96
- constructor(values?: Iterable<T> | null) {
97
- super(values);
98
- }
99
- }
100
-
101
- export class SetSchema<T> extends Set<T> {
102
- constructor(values?: Iterable<T> | null) {
103
- super(values);
104
- }
105
- }
106
-
107
- export function type(_kind: unknown): PropertyDecorator {
108
- return (target, propertyKey) => {
109
- const ctor = target.constructor as unknown as { [SCHEMA_TYPES]?: Map<string, unknown> };
110
- ctor[SCHEMA_TYPES] ??= new Map();
111
- ctor[SCHEMA_TYPES].set(String(propertyKey), _kind);
112
- };
113
- }
114
-
115
- export const schema = type;
116
- export const view = type;
117
- export const entity = type;
118
- export const deprecated = type;
119
-
120
- export function defineTypes<T extends typeof Schema>(
121
- target: T,
122
- fields: Record<string, unknown>,
123
- ): T {
124
- const ctor = target as unknown as { [SCHEMA_TYPES]?: Map<string, unknown> };
125
- ctor[SCHEMA_TYPES] ??= new Map();
126
- for (const [key, value] of Object.entries(fields)) ctor[SCHEMA_TYPES].set(key, value);
127
- return target;
128
- }
129
-
130
- export function defineCustomTypes<T extends Record<string, unknown>>(types: T): T {
131
- return types;
132
- }
133
-
134
- export function registerType(): void {
135
- // Custom binary type registration is not needed by the JSON snapshot codec.
136
- }
137
-
138
- export function dumpChanges(value: unknown): unknown {
139
- return encodeSnapshotState(value);
140
- }
141
-
142
- export const encode = {
143
- number(value: number): number {
144
- return value;
145
- },
146
- string(value: string): string {
147
- return value;
148
- },
149
- boolean(value: boolean): boolean {
150
- return value;
151
- },
152
- };
153
-
154
- export const decode = {
155
- number(value: number): number {
156
- return value;
157
- },
158
- string(value: string): string {
159
- return value;
160
- },
161
- boolean(value: boolean): boolean {
162
- return value;
163
- },
164
- };
165
-
166
- export class Encoder<T = unknown> {
167
- constructor(public state?: T) {}
168
-
169
- encode(): Uint8Array {
170
- return new TextEncoder().encode(JSON.stringify(encodeSnapshotState(this.state)));
171
- }
172
-
173
- encodeAll(): Uint8Array {
174
- return this.encode();
175
- }
176
-
177
- discardChanges(): void {}
178
- }
179
-
180
- export class Decoder<T = unknown> {
181
- state: T | undefined;
182
-
183
- decode(bytes: Uint8Array): T | undefined {
184
- const text = new TextDecoder().decode(bytes);
185
- this.state = text ? (JSON.parse(text) as T) : undefined;
186
- return this.state;
187
- }
188
- }
189
-
190
- export class StateView {
191
- readonly items = new Set<unknown>();
192
-
193
- add(item: unknown): void {
194
- this.items.add(item);
195
- }
196
-
197
- remove(item: unknown): void {
198
- this.items.delete(item);
199
- }
200
-
201
- has(item: unknown): boolean {
202
- return this.items.has(item);
203
- }
204
- }
205
-
206
- export class Reflection {}
207
- export class ReflectionType extends Schema {}
208
- export class ReflectionField extends Schema {}
209
- export class Metadata {}
210
- export class TypeContext {}
211
- export class ChangeTree {}
212
-
213
- export class Callbacks {
214
- static get(state: unknown): unknown {
215
- return state;
216
- }
217
- }
218
-
219
- export const StateCallbackStrategy = {};
220
-
221
- export function getDecoderStateCallbacks(state: unknown): unknown {
222
- return state;
223
- }
224
-
225
- export function getRawChangesCallback(
226
- callback: (...args: unknown[]) => void,
227
- ): (...args: unknown[]) => void {
228
- return callback;
229
- }
230
-
231
- export function encodeSchemaOperation(value: unknown): unknown {
232
- return value;
233
- }
234
-
235
- export function encodeArray(value: unknown): unknown {
236
- return value;
237
- }
238
-
239
- export function encodeKeyValueOperation(value: unknown): unknown {
240
- return value;
241
- }
242
-
243
- export function decodeSchemaOperation(value: unknown): unknown {
244
- return value;
245
- }
246
-
247
- export function decodeKeyValueOperation(value: unknown): unknown {
248
- return value;
249
- }
250
-
251
- export function encodeSnapshotState(value: unknown): unknown {
252
- if (value instanceof ArraySchema || Array.isArray(value)) {
253
- return value.map((item) => encodeSnapshotState(item));
254
- }
255
-
256
- if (value instanceof CollectionSchema || value instanceof SetSchema || value instanceof Set) {
257
- return [...value.values()].map((item) => encodeSnapshotState(item));
258
- }
259
-
260
- if (value instanceof MapSchema || value instanceof Map) {
261
- const out: Record<string, unknown> = {};
262
- for (const [key, item] of value.entries()) out[String(key)] = encodeSnapshotState(item);
263
- return out;
264
- }
265
-
266
- if (value instanceof Schema || isPlainObject(value)) {
267
- const out: Record<string, unknown> = {};
268
- for (const [key, item] of Object.entries(value)) {
269
- if (typeof item === 'function') continue;
270
- out[key] = encodeSnapshotState(item);
271
- }
272
- return out;
273
- }
274
-
275
- return value;
276
- }
277
-
278
- function deepClone<T>(value: T): T {
279
- if (typeof structuredClone === 'function') return structuredClone(value);
280
- return JSON.parse(JSON.stringify(value)) as T;
281
- }
282
-
283
- function isPlainObject(value: unknown): value is Record<string, unknown> {
284
- return (
285
- typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype
286
- );
287
- }