@colyseus/schema 5.0.19 → 5.0.20

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.
@@ -9,10 +9,17 @@ import type { StreamSchema } from "./custom/StreamSchema.js";
9
9
  import type { FieldBuilder } from "./builder.js";
10
10
  export type Constructor<T = {}> = new (...args: any[]) => T;
11
11
  type PrimitiveStringToType<T> = T extends "string" ? string : T extends "number" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float32" | "float64" ? number : T extends "boolean" ? boolean : T;
12
- export interface Collection<K = any, V = any, IT = V> {
12
+ /**
13
+ * What the decoder callbacks accept as "a collection": the public shape, which
14
+ * a plain array satisfies too — `@type([X]) items: X[]` is a common way to
15
+ * declare a field. {@link Collection} is the runtime contract on top of it.
16
+ */
17
+ export interface CollectionLike<K = any, V = any, IT = V> {
13
18
  [Symbol.iterator](): IterableIterator<IT>;
14
19
  forEach(callback: Function): void;
15
20
  entries(): IterableIterator<[K, V]>;
21
+ }
22
+ export interface Collection<K = any, V = any, IT = V> extends CollectionLike<K, V, IT> {
16
23
  /** See {@link $resyncPrune} — every collection kind must declare its resync-sweep semantics. */
17
24
  [$resyncPrune](visited: Set<number | string>, prune: (value: V, identity: number | string) => void, keep: (value: V) => void): void;
18
25
  }
@@ -69,35 +76,39 @@ export type InferValueType<T> = T extends FieldBuilder<infer V> ? V : T extends
69
76
  } ? StreamSchema<InstanceType<ChildType>> : T extends {
70
77
  stream: infer ChildType;
71
78
  } ? StreamSchema<ChildType> : T extends Constructor ? InstanceType<T> : T extends Record<string | number, string | number> ? T[keyof T] : T extends PrimitiveType ? T : never;
79
+ type IsOptionalBuilderKey<T, K extends keyof T> = T[K] extends FieldBuilder<unknown, boolean, infer O extends boolean> ? O : false;
72
80
  type OptionalBuilderKeys<T> = {
73
- [K in keyof T]: T[K] extends FieldBuilder<unknown, boolean, infer O extends boolean> ? (O extends true ? K : never) : never;
81
+ [K in keyof T]-?: IsOptionalBuilderKey<T, K> extends true ? K : never;
82
+ }[keyof T];
83
+ type RequiredBuilderKeys<T> = {
84
+ [K in keyof T]-?: IsOptionalBuilderKey<T, K> extends true ? never : K;
74
85
  }[keyof T];
75
- type RequiredBuilderKeys<T> = Exclude<keyof T, OptionalBuilderKeys<T>>;
76
86
  export type InferSchemaInstanceType<T> = {
77
87
  [K in RequiredBuilderKeys<T>]: T[K] extends FieldBuilder<any> ? InferValueType<T[K]> : T[K] extends (...args: any[]) => any ? (T[K] extends new (...args: any[]) => any ? InferValueType<T[K]> : T[K]) : InferValueType<T[K]>;
78
88
  } & {
79
89
  [K in OptionalBuilderKeys<T>]?: T[K] extends FieldBuilder<infer V> ? V : never;
80
90
  } & Schema;
81
- export type NonFunctionProps<T> = Omit<T, {
82
- [K in keyof T]: T[K] extends Function ? K : never;
83
- }[keyof T]>;
91
+ type DataKey<T, K extends keyof T> = K extends keyof Schema ? never : T[K] extends Function ? never : K;
84
92
  export type NonFunctionPropNames<T> = {
85
- [K in keyof T]: T[K] extends Function ? never : K;
93
+ [K in keyof T]-?: DataKey<T, K>;
86
94
  }[keyof T];
87
95
  export type NonFunctionNonPrimitivePropNames<T> = {
88
- [K in keyof T]: T[K] extends Function ? never : T[K] extends number | string | boolean ? never : K;
96
+ [K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : T[K] extends number | string | boolean ? never : K;
89
97
  }[keyof T];
90
98
  type ToJSONValue<U> = U extends Schema ? ToJSON<U> : PrimitiveStringToType<U>;
91
99
  type ToJSONField<X> = X extends MapSchema<infer U> ? Record<string, ToJSONValue<U>> : X extends Map<string, infer U> ? Record<string, ToJSONValue<U>> : X extends ArraySchema<infer U> ? ToJSONValue<U>[] : X extends SetSchema<infer U> ? ToJSONValue<U>[] : X extends CollectionSchema<infer U> ? ToJSONValue<U>[] : X extends Schema ? ToJSON<X> : X;
100
+ type IsOptionalKey<T, K extends keyof T> = undefined extends {} ? ({} extends Pick<T, K> ? true : false) : (undefined extends T[K] ? true : false);
101
+ type ToJSONRequiredKeys<T> = {
102
+ [K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : IsOptionalKey<T, K> extends true ? never : K;
103
+ }[keyof T];
92
104
  type ToJSONOptionalKeys<T> = {
93
- [K in keyof T]-?: undefined extends {} ? ({} extends Pick<T, K> ? K : never) : (undefined extends T[K] ? K : never);
105
+ [K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : IsOptionalKey<T, K> extends true ? K : never;
94
106
  }[keyof T];
95
- type ToJSONRequiredKeys<T> = Exclude<keyof T, ToJSONOptionalKeys<T>>;
96
- export type ToJSON<T> = NonFunctionProps<{
107
+ export type ToJSON<T> = {
97
108
  [K in ToJSONRequiredKeys<T>]: ToJSONField<T[K]>;
98
109
  } & {
99
110
  [K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>>;
100
- }>;
111
+ };
101
112
  /**
102
113
  * The plain DATA shape of a Schema instance type `T`: its synchronized fields
103
114
  * with all `Schema` machinery stripped (`assign`, `clone`, `toJSON`, the
@@ -116,8 +127,7 @@ export type ToJSON<T> = NonFunctionProps<{
116
127
  *
117
128
  * Unlike {@link ToJSON} (a recursive *serialization* shape), this is a flat
118
129
  * structural projection: nested Schema / collection fields keep their instance
119
- * types, and it does not retain the non-method `Schema` members that `ToJSON`'s
120
- * `NonFunctionProps` pass leaves behind.
130
+ * types.
121
131
  */
122
132
  export type Data<T> = Omit<T, keyof Schema>;
123
133
  export type IsNever<T> = [T] extends [never] ? true : false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "5.0.19",
4
- "description": "Binary state serializer with delta encoding for games",
3
+ "version": "5.0.20",
4
+ "description": "Automatic state replication for multiplayer games. Mutate plain objects, and every client gets a live, typed mirror through delta encoding.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "schema-codegen": "bin/schema-codegen",
@@ -14,13 +14,13 @@
14
14
  "bench:gate": "node bench/run.mjs --assert --samples 3 --filter gate",
15
15
  "profile:cpu": "node bench/profile.mjs --cpu",
16
16
  "profile:heap": "node bench/profile.mjs --heap",
17
- "build": "tsc -p tsconfig.build.json && rollup -c rollup.config.mjs",
18
- "watch": "tsc -p tsconfig.build.json -w",
19
- "typecheck:build": "tsc -p tsconfig.build.json --noEmit",
17
+ "build": "node node_modules/typescript/bin/tsc -p tsconfig.build.json && rollup -c rollup.config.mjs",
18
+ "watch": "node node_modules/typescript/bin/tsc -p tsconfig.build.json -w",
19
+ "typecheck:build": "node node_modules/typescript/bin/tsc -p tsconfig.build.json --noEmit",
20
20
  "test": "npm run test:types && tsx --tsconfig tsconfig.test.json ./node_modules/mocha/bin/mocha.js \"test/*.test.ts\" \"test/**/*.test.ts\"",
21
- "test:types": "tsc -p tsconfig.build.json && tsc -p tsconfig.types.json && tsc -p tsconfig.types-nostrict.json",
21
+ "test:types": "node node_modules/typescript/bin/tsc -p tsconfig.build.json && node test/types/run.mjs typescript typescript-7",
22
22
  "test:exports": "node test/verify-exports.mjs",
23
- "typecheck": "tsc -p tsconfig.test.json",
23
+ "typecheck": "node node_modules/typescript/bin/tsc -p tsconfig.test.json",
24
24
  "coverage": "c8 npm run test",
25
25
  "generate-test-1": "bin/schema-codegen test-external/PrimitiveTypes.ts --namespace SchemaTest.PrimitiveTypes --output ../colyseus-unity-sdk/Assets/Colyseus/Tests/Editor/ColyseusTests/Schema/PrimitiveTypes",
26
26
  "generate-test-2": "bin/schema-codegen test-external/ChildSchemaTypes.ts --namespace SchemaTest.ChildSchemaTypes --output ../colyseus-unity-sdk/Assets/Colyseus/Tests/Editor/ColyseusTests/Schema/ChildSchemaTypes",
@@ -94,7 +94,8 @@
94
94
  "source-map-support": "^0.5.13",
95
95
  "tslib": "^2.1.0",
96
96
  "tsx": "^4.21.0",
97
- "typescript": "^5.9.3"
97
+ "typescript": "^5.9.3",
98
+ "typescript-7": "npm:typescript@^7.0.2"
98
99
  },
99
100
  "peerDependencies": {
100
101
  "typescript": ">=5.0.0"
@@ -668,9 +668,13 @@ type HasExplicitInit<T> = T extends { initialize: (...args: infer P) => void }
668
668
  */
669
669
  export type FieldsAndMethods = Record<string, FieldBuilder<any, boolean, boolean> | (new (...args: any[]) => Schema) | Function>;
670
670
 
671
+ // One spelling for every site that names the instance — identical
672
+ // instantiations compare by identity, not structurally.
673
+ type SchemaInstance<T, P extends typeof Schema> = InferSchemaInstanceType<T> & InstanceType<P>;
674
+
671
675
  export interface SchemaWithExtends<T, P extends typeof Schema> {
672
676
  extend: <T2 extends FieldsAndMethods = FieldsAndMethods>(
673
- fields: T2 & ThisType<InferSchemaInstanceType<T & T2>>,
677
+ fields: T2 & ThisType<SchemaInstance<T & T2, P>>,
674
678
  name?: string,
675
679
  ) => SchemaWithExtendsConstructor<T & T2, ExtractInitProps<T & T2>, P>;
676
680
  }
@@ -692,7 +696,7 @@ export interface SchemaWithExtendsConstructor<
692
696
  InitProps,
693
697
  P extends typeof Schema
694
698
  > extends SchemaWithExtends<T, P> {
695
- '~type': InferSchemaInstanceType<T>;
699
+ '~type': SchemaInstance<T, P>;
696
700
  // Constructor signature:
697
701
  // - InitProps = never (zero-arg initialize): no args.
698
702
  // - InitProps is a tuple (multi-arg initialize): spread it.
@@ -708,8 +712,8 @@ export interface SchemaWithExtendsConstructor<
708
712
  : HasExplicitInit<T> extends true ? [InitProps]
709
713
  : IsInitPropsRequired<T> extends true ? ([] | [InitProps])
710
714
  : [InitProps?]
711
- ): InferSchemaInstanceType<T> & InstanceType<P>;
712
- prototype: InferSchemaInstanceType<T> & InstanceType<P> & {
715
+ ): SchemaInstance<T, P>;
716
+ prototype: SchemaInstance<T, P> & {
713
717
  initialize(...args: [InitProps] extends [never] ? [] : InitProps extends readonly any[] ? InitProps : [InitProps]): void;
714
718
  };
715
719
  }
@@ -755,7 +759,7 @@ export function schema<
755
759
  T extends FieldsAndMethods,
756
760
  P extends typeof Schema = typeof Schema
757
761
  >(
758
- fieldsAndMethods: T & ThisType<InferSchemaInstanceType<T>>,
762
+ fieldsAndMethods: T & ThisType<SchemaInstance<T, P>>,
759
763
  name?: string,
760
764
  inherits: P = Schema as P,
761
765
  ): SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P> {
@@ -1,5 +1,5 @@
1
1
  import { Metadata } from "../../Metadata.js";
2
- import { Collection, NonFunctionPropNames } from "../../types/HelperTypes.js";
2
+ import { Collection, NonFunctionPropNames, CollectionLike } from "../../types/HelperTypes.js";
3
3
  import type { IRef, Ref } from "../../encoder/ChangeTree.js";
4
4
  import { Decoder } from "../Decoder.js";
5
5
  import { DataChange } from "../DecodeOperation.js";
@@ -25,25 +25,24 @@ type KeyValueCallback<K, V> = (key: K, value: V) => void;
25
25
  type ValueKeyCallback<V, K> = (value: V, key: K) => void;
26
26
  type InstanceChangeCallback = () => void;
27
27
 
28
- // Exclude internal properties from valid property names
29
- type PublicPropNames<T> = Exclude<NonFunctionPropNames<T>, typeof $refId> & string;
28
+ type PublicPropNames<T> = NonFunctionPropNames<T> & string;
30
29
 
31
30
  // Extract only properties that extend Collection
32
- type CollectionPropNames<T> = Exclude<{
33
- [K in keyof T]: T[K] extends Collection<any, any> ? K : never
34
- }[keyof T] & string, typeof $refId>;
31
+ type CollectionPropNames<T> = {
32
+ [K in keyof T]: T[K] extends CollectionLike<any, any> ? K : never
33
+ }[keyof T] & string;
35
34
 
36
35
  // Infer the value type of a collection property
37
36
  type CollectionValueType<T, K extends keyof T> =
38
37
  T[K] extends MapSchema<infer V, any> ? V :
39
38
  T[K] extends ArraySchema<infer V> ? V :
40
- T[K] extends Collection<any, infer V, any> ? V : never;
39
+ T[K] extends CollectionLike<any, infer V, any> ? V : never;
41
40
 
42
41
  // Infer the key type of a collection property
43
42
  type CollectionKeyType<T, K extends keyof T> =
44
43
  T[K] extends MapSchema<any, infer Key> ? Key :
45
44
  T[K] extends ArraySchema<any> ? number :
46
- T[K] extends Collection<infer Key, any, any> ? Key : never;
45
+ T[K] extends CollectionLike<infer Key, any, any> ? Key : never;
47
46
 
48
47
  export class StateCallbackStrategy<TState extends IRef> {
49
48
  protected decoder: Decoder<TState>;
@@ -1,5 +1,5 @@
1
1
  import { Metadata } from "../../Metadata.js";
2
- import { Collection, NonFunctionNonPrimitivePropNames, NonFunctionPropNames } from "../../types/HelperTypes.js";
2
+ import { Collection, NonFunctionNonPrimitivePropNames, NonFunctionPropNames, CollectionLike } from "../../types/HelperTypes.js";
3
3
  import { IRef, Ref } from "../../encoder/ChangeTree.js";
4
4
  import { Decoder } from "../Decoder.js";
5
5
  import { DataChange } from "../DecodeOperation.js";
@@ -30,7 +30,7 @@ export type GetCallbackProxy = SchemaCallbackProxy<any>; // workaround for compa
30
30
 
31
31
  export type CallbackProxy<T> = unknown extends T // is "any"?
32
32
  ? SchemaCallback<T> & CollectionCallback<any, any>
33
- : T extends Collection<infer K, infer V, infer _>
33
+ : T extends CollectionLike<infer K, infer V, infer _>
34
34
  ? CollectionCallback<K, V>
35
35
  : SchemaCallback<T>;
36
36
 
@@ -17,10 +17,18 @@ type PrimitiveStringToType<T> =
17
17
  : T extends "boolean" ? boolean
18
18
  : T;
19
19
 
20
- export interface Collection<K = any, V = any, IT = V> {
20
+ /**
21
+ * What the decoder callbacks accept as "a collection": the public shape, which
22
+ * a plain array satisfies too — `@type([X]) items: X[]` is a common way to
23
+ * declare a field. {@link Collection} is the runtime contract on top of it.
24
+ */
25
+ export interface CollectionLike<K = any, V = any, IT = V> {
21
26
  [Symbol.iterator](): IterableIterator<IT>;
22
27
  forEach(callback: Function): void;
23
28
  entries(): IterableIterator<[K, V]>;
29
+ }
30
+
31
+ export interface Collection<K = any, V = any, IT = V> extends CollectionLike<K, V, IT> {
24
32
  /** See {@link $resyncPrune} — every collection kind must declare its resync-sweep semantics. */
25
33
  [$resyncPrune](
26
34
  visited: Set<number | string>,
@@ -90,13 +98,13 @@ export type InferValueType<T> =
90
98
  // Keys whose builder carries the `.optional()` brand. Reads the brand rather
91
99
  // than `undefined extends V`: the latter is true for EVERY V when the consumer
92
100
  // compiles with `strictNullChecks: false`, flipping all fields optional.
93
- type OptionalBuilderKeys<T> = {
94
- [K in keyof T]: T[K] extends FieldBuilder<unknown, boolean, infer O extends boolean>
95
- ? (O extends true ? K : never)
96
- : never
97
- }[keyof T];
101
+ type IsOptionalBuilderKey<T, K extends keyof T> =
102
+ T[K] extends FieldBuilder<unknown, boolean, infer O extends boolean> ? O : false;
98
103
 
99
- type RequiredBuilderKeys<T> = Exclude<keyof T, OptionalBuilderKeys<T>>;
104
+ // Per key, like `DataKey` below — an `Exclude<keyof T, …>` split defers every
105
+ // key once one field is typed by a bare type parameter.
106
+ type OptionalBuilderKeys<T> = { [K in keyof T]-?: IsOptionalBuilderKey<T, K> extends true ? K : never }[keyof T];
107
+ type RequiredBuilderKeys<T> = { [K in keyof T]-?: IsOptionalBuilderKey<T, K> extends true ? never : K }[keyof T];
100
108
 
101
109
  export type InferSchemaInstanceType<T> = {
102
110
  [K in RequiredBuilderKeys<T>]: T[K] extends FieldBuilder<any>
@@ -110,20 +118,20 @@ export type InferSchemaInstanceType<T> = {
110
118
  : never
111
119
  } & Schema;
112
120
 
113
- export type NonFunctionProps<T> = Omit<T, {
114
- [K in keyof T]: T[K] extends Function ? K : never;
115
- }[keyof T]>;
121
+ // Per-key filter, never `Omit`/`Exclude` over the union of method names: with
122
+ // one field typed by a bare type parameter that union defers EVERY key, and a
123
+ // mapped type with no resolvable keys has no members to relate — which is what
124
+ // stopped `SpecialNode<E>` from satisfying `extends NodeBase`.
125
+ // `keyof Schema` is dropped so `restore({ ... })` takes a plain literal.
126
+ type DataKey<T, K extends keyof T> =
127
+ K extends keyof Schema ? never
128
+ : T[K] extends Function ? never
129
+ : K;
116
130
 
117
- export type NonFunctionPropNames<T> = {
118
- [K in keyof T]: T[K] extends Function ? never : K
119
- }[keyof T];
131
+ export type NonFunctionPropNames<T> = { [K in keyof T]-?: DataKey<T, K> }[keyof T];
120
132
 
121
133
  export type NonFunctionNonPrimitivePropNames<T> = {
122
- [K in keyof T]: T[K] extends Function
123
- ? never
124
- : T[K] extends number | string | boolean
125
- ? never
126
- : K
134
+ [K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : T[K] extends number | string | boolean ? never : K
127
135
  }[keyof T];
128
136
 
129
137
  // Helper to recursively convert Schema instances to their JSON representation
@@ -138,21 +146,23 @@ type ToJSONField<X> =
138
146
  : X extends Schema ? ToJSON<X>
139
147
  : X;
140
148
 
141
- // Keys whose value admits `undefined` runtime `toJSON()` omits those, so
142
- // they surface as `?:` on the JSON shape. Under `strictNullChecks: false`
143
- // (`undefined extends {}` detects it) `undefined extends T[K]` is true for
144
- // every key, so only the `?` modifier can signal optionality there.
145
- type ToJSONOptionalKeys<T> = {
146
- [K in keyof T]-?: undefined extends {}
147
- ? ({} extends Pick<T, K> ? K : never)
148
- : (undefined extends T[K] ? K : never)
149
- }[keyof T];
150
- type ToJSONRequiredKeys<T> = Exclude<keyof T, ToJSONOptionalKeys<T>>;
151
-
152
- export type ToJSON<T> = NonFunctionProps<
149
+ // Runtime `toJSON()` omits `undefined` values, so those keys surface as `?:`.
150
+ // Under `strictNullChecks: false` (`undefined extends {}` detects it)
151
+ // `undefined extends T[K]` is true for every key, so only the `?` modifier
152
+ // can signal optionality there.
153
+ type IsOptionalKey<T, K extends keyof T> = undefined extends {}
154
+ ? ({} extends Pick<T, K> ? true : false)
155
+ : (undefined extends T[K] ? true : false);
156
+
157
+ // `DataKey` first: machinery and method keys drop before the optionality probe.
158
+ type ToJSONRequiredKeys<T> = { [K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : IsOptionalKey<T, K> extends true ? never : K }[keyof T];
159
+ type ToJSONOptionalKeys<T> = { [K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : IsOptionalKey<T, K> extends true ? K : never }[keyof T];
160
+
161
+ // Keys are filtered before mapping: `ToJSONField` over the `this`-typed methods
162
+ // exceeds TypeScript 7's instantiation depth.
163
+ export type ToJSON<T> =
153
164
  & { [K in ToJSONRequiredKeys<T>]: ToJSONField<T[K]> }
154
- & { [K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>> }
155
- >;
165
+ & { [K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>> };
156
166
 
157
167
  /**
158
168
  * The plain DATA shape of a Schema instance type `T`: its synchronized fields
@@ -172,8 +182,7 @@ export type ToJSON<T> = NonFunctionProps<
172
182
  *
173
183
  * Unlike {@link ToJSON} (a recursive *serialization* shape), this is a flat
174
184
  * structural projection: nested Schema / collection fields keep their instance
175
- * types, and it does not retain the non-method `Schema` members that `ToJSON`'s
176
- * `NonFunctionProps` pass leaves behind.
185
+ * types.
177
186
  */
178
187
  export type Data<T> = Omit<T, keyof Schema>;
179
188