@colyseus/schema 5.0.19 → 5.0.21
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 +4 -3
- package/build/Reflection.d.ts +2 -2
- package/build/annotations.d.ts +9 -7
- package/build/decoder/strategy/Callbacks.d.ts +7 -8
- package/build/decoder/strategy/getDecoderStateCallbacks.d.ts +2 -2
- package/build/index.cjs +23 -9
- package/build/index.cjs.map +1 -1
- package/build/index.js +23 -9
- package/build/index.mjs +23 -9
- package/build/index.mjs.map +1 -1
- package/build/types/HelperTypes.d.ts +24 -14
- package/package.json +9 -8
- package/src/Metadata.ts +4 -0
- package/src/annotations.ts +33 -21
- package/src/decoder/strategy/Callbacks.ts +7 -8
- package/src/decoder/strategy/getDecoderStateCallbacks.ts +2 -2
- package/src/types/HelperTypes.ts +43 -34
|
@@ -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
|
-
|
|
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]
|
|
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
|
-
|
|
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]
|
|
93
|
+
[K in keyof T]-?: DataKey<T, K>;
|
|
86
94
|
}[keyof T];
|
|
87
95
|
export type NonFunctionNonPrimitivePropNames<T> = {
|
|
88
|
-
[K in keyof T]
|
|
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]-?:
|
|
105
|
+
[K in keyof T]-?: [DataKey<T, K>] extends [never] ? never : IsOptionalKey<T, K> extends true ? K : never;
|
|
94
106
|
}[keyof T];
|
|
95
|
-
type
|
|
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
|
|
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.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.0.21",
|
|
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 &&
|
|
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"
|
package/src/Metadata.ts
CHANGED
|
@@ -488,6 +488,10 @@ export const Metadata = {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
for (const field in fields) {
|
|
491
|
+
// metadata inherits the parent's, so this also catches a redeclared parent field
|
|
492
|
+
if (metadata[field] !== undefined) {
|
|
493
|
+
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name || "(anonymous)"}'.`);
|
|
494
|
+
}
|
|
491
495
|
Metadata.defineField(constructor, metadata, fieldIndex, field, fields[field] as DefinitionType);
|
|
492
496
|
fieldIndex++;
|
|
493
497
|
}
|
package/src/annotations.ts
CHANGED
|
@@ -626,11 +626,11 @@ export function defineTypes(
|
|
|
626
626
|
|
|
627
627
|
// Helper type to extract InitProps from initialize method.
|
|
628
628
|
// - Non-empty initialize params: use them directly.
|
|
629
|
-
// - Zero-arg initialize: no args accepted (`never`) —
|
|
630
|
-
// values would be dropped at runtime
|
|
631
|
-
// during child construction via the `new.target === klass` guard, and
|
|
632
|
-
// own-field auto-assignment happens only inside initialize).
|
|
629
|
+
// - Zero-arg initialize: no args accepted (`never`) — an initialize() owns the
|
|
630
|
+
// fields, so user-supplied values would be dropped at runtime.
|
|
633
631
|
// - No initialize at all: derive from fields map.
|
|
632
|
+
// `T & T2` from `.extend()` carries the parent's initialize unless the child
|
|
633
|
+
// redefines it, matching the runtime (the most-derived one runs).
|
|
634
634
|
type ExtractInitProps<T> = T extends { initialize: (...args: infer P) => void }
|
|
635
635
|
? P extends readonly []
|
|
636
636
|
? never
|
|
@@ -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<
|
|
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':
|
|
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,10 +712,8 @@ export interface SchemaWithExtendsConstructor<
|
|
|
708
712
|
: HasExplicitInit<T> extends true ? [InitProps]
|
|
709
713
|
: IsInitPropsRequired<T> extends true ? ([] | [InitProps])
|
|
710
714
|
: [InitProps?]
|
|
711
|
-
):
|
|
712
|
-
prototype:
|
|
713
|
-
initialize(...args: [InitProps] extends [never] ? [] : InitProps extends readonly any[] ? InitProps : [InitProps]): void;
|
|
714
|
-
};
|
|
715
|
+
): SchemaInstance<T, P>;
|
|
716
|
+
prototype: SchemaInstance<T, P>;
|
|
715
717
|
}
|
|
716
718
|
|
|
717
719
|
/**
|
|
@@ -738,6 +740,9 @@ function makeAutoDefaultFactory(rawType: any): (() => any) | undefined {
|
|
|
738
740
|
/**
|
|
739
741
|
* Define a Schema class declaratively.
|
|
740
742
|
*
|
|
743
|
+
* `initialize()` acts as the constructor: a class created with `.extend()`
|
|
744
|
+
* inherits the parent's unless it defines its own.
|
|
745
|
+
*
|
|
741
746
|
* @example
|
|
742
747
|
* import { schema, t } from '@colyseus/schema';
|
|
743
748
|
*
|
|
@@ -755,7 +760,7 @@ export function schema<
|
|
|
755
760
|
T extends FieldsAndMethods,
|
|
756
761
|
P extends typeof Schema = typeof Schema
|
|
757
762
|
>(
|
|
758
|
-
fieldsAndMethods: T & ThisType<
|
|
763
|
+
fieldsAndMethods: T & ThisType<SchemaInstance<T, P>>,
|
|
759
764
|
name?: string,
|
|
760
765
|
inherits: P = Schema as P,
|
|
761
766
|
): SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P> {
|
|
@@ -906,9 +911,12 @@ export function schema<
|
|
|
906
911
|
};
|
|
907
912
|
|
|
908
913
|
const hasInitialize = typeof methods.initialize === "function";
|
|
914
|
+
// Like a constructor: the most-derived initialize() runs once, own or
|
|
915
|
+
// inherited from a schema() parent. A custom base's initialize() is not
|
|
916
|
+
// picked up — the constructor type only sees the fields map.
|
|
917
|
+
const initialize = methods.initialize ?? (inherits as any)._initialize;
|
|
909
918
|
|
|
910
|
-
|
|
911
|
-
const klass = Metadata.setFields<any>(class extends (inherits as any) {
|
|
919
|
+
const klass = class extends (inherits as any) {
|
|
912
920
|
constructor(...args: any[]) {
|
|
913
921
|
const props = args[0];
|
|
914
922
|
if (props === undefined) {
|
|
@@ -922,14 +930,22 @@ export function schema<
|
|
|
922
930
|
// `initialize()` owns the schema fields, so only parent props flow up.
|
|
923
931
|
super(Object.assign(getDefaultValues(), hasInitialize ? getParentProps(props) : props));
|
|
924
932
|
}
|
|
925
|
-
// Only
|
|
926
|
-
if (
|
|
927
|
-
|
|
933
|
+
// Only on the exact target class — parents' constructors skip it.
|
|
934
|
+
if (initialize && new.target === klass) {
|
|
935
|
+
initialize.apply(this, args);
|
|
928
936
|
}
|
|
929
937
|
}
|
|
930
|
-
}
|
|
938
|
+
} as unknown as SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P>;
|
|
939
|
+
|
|
940
|
+
// named before setFields so a duplicate-field error can say which class
|
|
941
|
+
if (name) {
|
|
942
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
943
|
+
}
|
|
944
|
+
/** @codegen-ignore */
|
|
945
|
+
Metadata.setFields<any>(klass, fields);
|
|
931
946
|
|
|
932
947
|
(klass as any)._getDefaultValues = getDefaultValues;
|
|
948
|
+
(klass as any)._initialize = initialize;
|
|
933
949
|
|
|
934
950
|
Object.assign(klass.prototype, methods);
|
|
935
951
|
|
|
@@ -966,10 +982,6 @@ export function schema<
|
|
|
966
982
|
}
|
|
967
983
|
}
|
|
968
984
|
|
|
969
|
-
if (name) {
|
|
970
|
-
Object.defineProperty(klass, "name", { value: name });
|
|
971
|
-
}
|
|
972
|
-
|
|
973
985
|
(klass as any).extend = <T2 extends FieldsAndMethods = FieldsAndMethods>(
|
|
974
986
|
childFields: T2,
|
|
975
987
|
childName?: string,
|
|
@@ -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
|
-
|
|
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> =
|
|
33
|
-
[K in keyof T]: T[K] extends
|
|
34
|
-
}[keyof T] & string
|
|
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
|
|
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
|
|
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
|
|
33
|
+
: T extends CollectionLike<infer K, infer V, infer _>
|
|
34
34
|
? CollectionCallback<K, V>
|
|
35
35
|
: SchemaCallback<T>;
|
|
36
36
|
|
package/src/types/HelperTypes.ts
CHANGED
|
@@ -17,10 +17,18 @@ type PrimitiveStringToType<T> =
|
|
|
17
17
|
: T extends "boolean" ? boolean
|
|
18
18
|
: T;
|
|
19
19
|
|
|
20
|
-
|
|
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
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
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
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
type
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
type ToJSONRequiredKeys<T> =
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
176
|
-
* `NonFunctionProps` pass leaves behind.
|
|
185
|
+
* types.
|
|
177
186
|
*/
|
|
178
187
|
export type Data<T> = Omit<T, keyof Schema>;
|
|
179
188
|
|