@colyseus/schema 5.0.0 → 5.0.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/build/Metadata.d.ts +1 -0
- package/build/Reflection.d.ts +26 -26
- package/build/annotations.d.ts +11 -7
- package/build/index.cjs +71 -12
- package/build/index.cjs.map +1 -1
- package/build/index.js +71 -12
- package/build/index.mjs +71 -12
- package/build/index.mjs.map +1 -1
- package/build/input/index.cjs +29 -5
- package/build/input/index.cjs.map +1 -1
- package/build/input/index.mjs +29 -5
- package/build/input/index.mjs.map +1 -1
- package/build/types/HelperTypes.d.ts +47 -3
- package/build/types/builder.d.ts +65 -35
- package/package.json +1 -1
- package/src/Metadata.ts +1 -0
- package/src/Reflection.ts +2 -2
- package/src/annotations.ts +58 -17
- package/src/types/HelperTypes.ts +104 -19
- package/src/types/builder.ts +67 -21
- package/src/types/symbols.ts +45 -6
|
@@ -66,8 +66,14 @@ export type InferValueType<T> = T extends FieldBuilder<infer V> ? V : T extends
|
|
|
66
66
|
} ? StreamSchema<InstanceType<ChildType>> : T extends {
|
|
67
67
|
stream: infer ChildType;
|
|
68
68
|
} ? StreamSchema<ChildType> : T extends Constructor ? InstanceType<T> : T extends Record<string | number, string | number> ? T[keyof T] : T extends PrimitiveType ? T : never;
|
|
69
|
+
type OptionalBuilderKeys<T> = {
|
|
70
|
+
[K in keyof T]: T[K] extends FieldBuilder<infer V> ? (undefined extends V ? K : never) : never;
|
|
71
|
+
}[keyof T];
|
|
72
|
+
type RequiredBuilderKeys<T> = Exclude<keyof T, OptionalBuilderKeys<T>>;
|
|
69
73
|
export type InferSchemaInstanceType<T> = {
|
|
70
|
-
[K in
|
|
74
|
+
[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]>;
|
|
75
|
+
} & {
|
|
76
|
+
[K in OptionalBuilderKeys<T>]?: T[K] extends FieldBuilder<infer V> ? V : never;
|
|
71
77
|
} & Schema;
|
|
72
78
|
export type NonFunctionProps<T> = Omit<T, {
|
|
73
79
|
[K in keyof T]: T[K] extends Function ? K : never;
|
|
@@ -79,8 +85,17 @@ export type NonFunctionNonPrimitivePropNames<T> = {
|
|
|
79
85
|
[K in keyof T]: T[K] extends Function ? never : T[K] extends number | string | boolean ? never : K;
|
|
80
86
|
}[keyof T];
|
|
81
87
|
type ToJSONValue<U> = U extends Schema ? ToJSON<U> : PrimitiveStringToType<U>;
|
|
88
|
+
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;
|
|
89
|
+
type ToJSONRequiredKeys<T> = {
|
|
90
|
+
[K in keyof T]-?: undefined extends T[K] ? never : K;
|
|
91
|
+
}[keyof T];
|
|
92
|
+
type ToJSONOptionalKeys<T> = {
|
|
93
|
+
[K in keyof T]-?: undefined extends T[K] ? K : never;
|
|
94
|
+
}[keyof T];
|
|
82
95
|
export type ToJSON<T> = NonFunctionProps<{
|
|
83
|
-
[K in
|
|
96
|
+
[K in ToJSONRequiredKeys<T>]: ToJSONField<T[K]>;
|
|
97
|
+
} & {
|
|
98
|
+
[K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>>;
|
|
84
99
|
}>;
|
|
85
100
|
export type IsNever<T> = [T] extends [never] ? true : false;
|
|
86
101
|
/**
|
|
@@ -90,6 +105,35 @@ export type IsNever<T> = [T] extends [never] ? true : false;
|
|
|
90
105
|
* - Collections can be assigned from their JSON representations
|
|
91
106
|
*/
|
|
92
107
|
export type AssignableProps<T> = {
|
|
93
|
-
[K in NonFunctionPropNames<T>]?:
|
|
108
|
+
[K in NonFunctionPropNames<T>]?: AssignableValue<T[K]>;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Value-level assignment shape shared by `AssignableProps` and
|
|
112
|
+
* `BuilderInitProps`. Captures the "you can pass the real instance, or the
|
|
113
|
+
* plain-object / array shape" pattern.
|
|
114
|
+
*/
|
|
115
|
+
export type AssignableValue<V> = V extends MapSchema<infer U> ? MapSchema<U> | Record<string, U extends Schema ? (U | AssignableProps<U>) : U> : V extends ArraySchema<infer U> ? ArraySchema<U> | (U extends Schema ? (U | AssignableProps<U>)[] : U[]) : V extends SetSchema<infer U> ? SetSchema<U> | Set<U> | (U extends Schema ? (U | AssignableProps<U>)[] : U[]) : V extends CollectionSchema<infer U> ? CollectionSchema<U> | (U extends Schema ? (U | AssignableProps<U>)[] : U[]) : V extends Schema ? V | AssignableProps<V> : V;
|
|
116
|
+
export type RefHasDefault<C> = C extends {
|
|
117
|
+
prototype: {
|
|
118
|
+
initialize(...args: infer P): any;
|
|
119
|
+
};
|
|
120
|
+
} ? (P extends readonly [] ? true : false) : true;
|
|
121
|
+
type FieldValue<F> = F extends FieldBuilder<infer V, boolean, boolean> ? V : F extends new (...args: any[]) => infer I ? (I extends Schema ? I : never) : never;
|
|
122
|
+
type KeyClass<T, K extends keyof T> = T[K] extends FieldBuilder<unknown, infer D extends boolean, infer O extends boolean> ? (D extends true ? "optional" : O extends true ? "optional" : "required") : T[K] extends new (...args: any[]) => Schema ? (RefHasDefault<T[K]> extends true ? "optional" : "required") : "none";
|
|
123
|
+
export type BuilderRequiredKeys<T> = {
|
|
124
|
+
[K in keyof T]-?: KeyClass<T, K> extends "required" ? K : never;
|
|
125
|
+
}[keyof T];
|
|
126
|
+
export type BuilderOptionalKeys<T> = {
|
|
127
|
+
[K in keyof T]-?: KeyClass<T, K> extends "optional" ? K : never;
|
|
128
|
+
}[keyof T];
|
|
129
|
+
/**
|
|
130
|
+
* Constructor/init-props type for a schema() fields map. Required fields
|
|
131
|
+
* (primitives without `.default()` or `.optional()`, and Schema refs with
|
|
132
|
+
* non-zero-arg `initialize()`) are `:`; everything else is `?:`.
|
|
133
|
+
*/
|
|
134
|
+
export type BuilderInitProps<T> = {
|
|
135
|
+
[K in BuilderRequiredKeys<T>]: AssignableValue<FieldValue<T[K]>>;
|
|
136
|
+
} & {
|
|
137
|
+
[K in BuilderOptionalKeys<T>]?: AssignableValue<Exclude<FieldValue<T[K]>, undefined>>;
|
|
94
138
|
};
|
|
95
139
|
export {};
|
package/build/types/builder.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface BuilderDefinition {
|
|
|
22
22
|
deprecatedThrows?: boolean;
|
|
23
23
|
static?: boolean;
|
|
24
24
|
stream?: boolean;
|
|
25
|
+
optional?: boolean;
|
|
25
26
|
/** Declaration-scope priority callback for `.stream()` fields. */
|
|
26
27
|
streamPriority?: (view: any, element: any) => number;
|
|
27
28
|
}
|
|
@@ -32,11 +33,25 @@ export type BuilderOf<T> = FieldBuilder<T>;
|
|
|
32
33
|
/**
|
|
33
34
|
* Chainable field builder. Instances are produced by `t.*()` factories.
|
|
34
35
|
*
|
|
35
|
-
*
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
36
|
+
* Generics:
|
|
37
|
+
* - `T` is the runtime/JS type of the field (e.g. `number`, `string`,
|
|
38
|
+
* `ArraySchema<Item>`). `.optional()` widens it to `T | undefined`
|
|
39
|
+
* so the inferred instance/toJSON shapes reflect absence.
|
|
40
|
+
* - `HasDefault` is a compile-time flag that the field carries a
|
|
41
|
+
* construction-time default — either an explicit `.default(v)` or an
|
|
42
|
+
* auto-default from a collection factory (`t.array`, `t.map`, …) or a
|
|
43
|
+
* Schema ref whose `initialize` takes zero args.
|
|
44
|
+
* - `IsOptional` is a compile-time brand for `.optional()`. Both
|
|
45
|
+
* `HasDefault` and `IsOptional` make the field omittable in
|
|
46
|
+
* `BuilderInitProps<T>`. A separate brand (rather than reading
|
|
47
|
+
* `undefined extends V`) sidesteps a TypeScript quirk where
|
|
48
|
+
* class-generic-inferred `V` resolves `undefined extends V` as `true`
|
|
49
|
+
* even for non-undefined types.
|
|
50
|
+
*
|
|
51
|
+
* schema() reads the internal configuration via `toDefinition()` and wires
|
|
52
|
+
* up metadata through the existing pipeline.
|
|
38
53
|
*/
|
|
39
|
-
export declare class FieldBuilder<T = unknown> {
|
|
54
|
+
export declare class FieldBuilder<T = unknown, HasDefault extends boolean = false, IsOptional extends boolean = false> {
|
|
40
55
|
readonly [$builder]: true;
|
|
41
56
|
_type: DefinitionType;
|
|
42
57
|
_default: any;
|
|
@@ -49,10 +64,11 @@ export declare class FieldBuilder<T = unknown> {
|
|
|
49
64
|
_deprecatedThrows: boolean;
|
|
50
65
|
_static: boolean;
|
|
51
66
|
_stream: boolean;
|
|
67
|
+
_optional: boolean;
|
|
52
68
|
_streamPriority: ((view: any, element: any) => number) | undefined;
|
|
53
69
|
constructor(type: DefinitionType);
|
|
54
70
|
/** Provide a default value for this field. */
|
|
55
|
-
default(value: T):
|
|
71
|
+
default(value: T): FieldBuilder<T, true, IsOptional>;
|
|
56
72
|
/** Tag this field with a view tag (DEFAULT_VIEW_TAG when called without arg). */
|
|
57
73
|
view(tag?: number): this;
|
|
58
74
|
/** Mark this field as owned (encoder-side ownership filtering). */
|
|
@@ -107,52 +123,66 @@ export declare class FieldBuilder<T = unknown> {
|
|
|
107
123
|
priority<V = any>(fn: (view: any, element: V) => number): this;
|
|
108
124
|
/** Mark this field as deprecated. Pass `false` to silence the access error. */
|
|
109
125
|
deprecated(throws?: boolean): this;
|
|
126
|
+
/**
|
|
127
|
+
* Mark this field as optional — inferred instance type becomes
|
|
128
|
+
* `T | undefined` and the property becomes omittable in initialization
|
|
129
|
+
* props. Skips the auto-instantiation of collection / Schema-ref
|
|
130
|
+
* defaults, so the field starts as `undefined` at runtime.
|
|
131
|
+
*/
|
|
132
|
+
optional(): FieldBuilder<T | undefined, HasDefault, true>;
|
|
110
133
|
toDefinition(): BuilderDefinition;
|
|
111
134
|
}
|
|
112
135
|
export declare function isBuilder(value: any): value is FieldBuilder<any>;
|
|
113
136
|
export type ChildType = RawPrimitiveType | Constructor<Schema> | FieldBuilder<any>;
|
|
114
137
|
interface ArrayFactory {
|
|
115
|
-
<C extends Constructor<Schema>>(child: C): FieldBuilder<ArraySchema<InstanceType<C
|
|
116
|
-
<P extends RawPrimitiveType>(child: P): FieldBuilder<ArraySchema<InferValueType<P
|
|
117
|
-
<V>(child: FieldBuilder<V>): FieldBuilder<ArraySchema<V
|
|
138
|
+
<C extends Constructor<Schema>>(child: C): FieldBuilder<ArraySchema<InstanceType<C>>, true, false>;
|
|
139
|
+
<P extends RawPrimitiveType>(child: P): FieldBuilder<ArraySchema<InferValueType<P>>, true, false>;
|
|
140
|
+
<V>(child: FieldBuilder<V>): FieldBuilder<ArraySchema<V>, true, false>;
|
|
118
141
|
}
|
|
119
142
|
interface MapFactory {
|
|
120
|
-
<C extends Constructor<Schema>>(child: C): FieldBuilder<MapSchema<InstanceType<C
|
|
121
|
-
<P extends RawPrimitiveType>(child: P): FieldBuilder<MapSchema<InferValueType<P
|
|
122
|
-
<V>(child: FieldBuilder<V>): FieldBuilder<MapSchema<V
|
|
143
|
+
<C extends Constructor<Schema>>(child: C): FieldBuilder<MapSchema<InstanceType<C>>, true, false>;
|
|
144
|
+
<P extends RawPrimitiveType>(child: P): FieldBuilder<MapSchema<InferValueType<P>>, true, false>;
|
|
145
|
+
<V>(child: FieldBuilder<V>): FieldBuilder<MapSchema<V>, true, false>;
|
|
123
146
|
}
|
|
124
147
|
interface SetFactory {
|
|
125
|
-
<C extends Constructor<Schema>>(child: C): FieldBuilder<SetSchema<InstanceType<C
|
|
126
|
-
<P extends RawPrimitiveType>(child: P): FieldBuilder<SetSchema<InferValueType<P
|
|
127
|
-
<V>(child: FieldBuilder<V>): FieldBuilder<SetSchema<V
|
|
148
|
+
<C extends Constructor<Schema>>(child: C): FieldBuilder<SetSchema<InstanceType<C>>, true, false>;
|
|
149
|
+
<P extends RawPrimitiveType>(child: P): FieldBuilder<SetSchema<InferValueType<P>>, true, false>;
|
|
150
|
+
<V>(child: FieldBuilder<V>): FieldBuilder<SetSchema<V>, true, false>;
|
|
128
151
|
}
|
|
129
152
|
interface CollectionFactory {
|
|
130
|
-
<C extends Constructor<Schema>>(child: C): FieldBuilder<CollectionSchema<InstanceType<C
|
|
131
|
-
<P extends RawPrimitiveType>(child: P): FieldBuilder<CollectionSchema<InferValueType<P
|
|
132
|
-
<V>(child: FieldBuilder<V>): FieldBuilder<CollectionSchema<V
|
|
153
|
+
<C extends Constructor<Schema>>(child: C): FieldBuilder<CollectionSchema<InstanceType<C>>, true, false>;
|
|
154
|
+
<P extends RawPrimitiveType>(child: P): FieldBuilder<CollectionSchema<InferValueType<P>>, true, false>;
|
|
155
|
+
<V>(child: FieldBuilder<V>): FieldBuilder<CollectionSchema<V>, true, false>;
|
|
133
156
|
}
|
|
134
157
|
interface StreamFactory {
|
|
135
|
-
<C extends Constructor<Schema>>(child: C): FieldBuilder<StreamSchema<InstanceType<C
|
|
158
|
+
<C extends Constructor<Schema>>(child: C): FieldBuilder<StreamSchema<InstanceType<C>>, true, false>;
|
|
159
|
+
}
|
|
160
|
+
type RefHasDefault<C> = C extends {
|
|
161
|
+
prototype: {
|
|
162
|
+
initialize(...args: infer P): any;
|
|
163
|
+
};
|
|
164
|
+
} ? (P extends readonly [] ? true : false) : true;
|
|
165
|
+
interface RefFactory {
|
|
166
|
+
<C extends Constructor<Schema>>(ctor: C): FieldBuilder<InstanceType<C>, RefHasDefault<C>, false>;
|
|
136
167
|
}
|
|
137
|
-
declare function refFactory<C extends Constructor<Schema>>(ctor: C): FieldBuilder<InstanceType<C>>;
|
|
138
168
|
export declare const t: Readonly<{
|
|
139
|
-
string: () => FieldBuilder<string>;
|
|
140
|
-
number: () => FieldBuilder<number>;
|
|
141
|
-
boolean: () => FieldBuilder<boolean>;
|
|
142
|
-
int8: () => FieldBuilder<number>;
|
|
143
|
-
uint8: () => FieldBuilder<number>;
|
|
144
|
-
int16: () => FieldBuilder<number>;
|
|
145
|
-
uint16: () => FieldBuilder<number>;
|
|
146
|
-
int32: () => FieldBuilder<number>;
|
|
147
|
-
uint32: () => FieldBuilder<number>;
|
|
148
|
-
int64: () => FieldBuilder<number>;
|
|
149
|
-
uint64: () => FieldBuilder<number>;
|
|
150
|
-
float32: () => FieldBuilder<number>;
|
|
151
|
-
float64: () => FieldBuilder<number>;
|
|
152
|
-
bigint64: () => FieldBuilder<bigint>;
|
|
153
|
-
biguint64: () => FieldBuilder<bigint>;
|
|
169
|
+
string: () => FieldBuilder<string, false, false>;
|
|
170
|
+
number: () => FieldBuilder<number, false, false>;
|
|
171
|
+
boolean: () => FieldBuilder<boolean, false, false>;
|
|
172
|
+
int8: () => FieldBuilder<number, false, false>;
|
|
173
|
+
uint8: () => FieldBuilder<number, false, false>;
|
|
174
|
+
int16: () => FieldBuilder<number, false, false>;
|
|
175
|
+
uint16: () => FieldBuilder<number, false, false>;
|
|
176
|
+
int32: () => FieldBuilder<number, false, false>;
|
|
177
|
+
uint32: () => FieldBuilder<number, false, false>;
|
|
178
|
+
int64: () => FieldBuilder<number, false, false>;
|
|
179
|
+
uint64: () => FieldBuilder<number, false, false>;
|
|
180
|
+
float32: () => FieldBuilder<number, false, false>;
|
|
181
|
+
float64: () => FieldBuilder<number, false, false>;
|
|
182
|
+
bigint64: () => FieldBuilder<bigint, false, false>;
|
|
183
|
+
biguint64: () => FieldBuilder<bigint, false, false>;
|
|
154
184
|
/** Reference to a Schema subtype. `t.array(Item)` usually reads better, but this is available when a plain ref is needed. */
|
|
155
|
-
ref:
|
|
185
|
+
ref: RefFactory;
|
|
156
186
|
array: ArrayFactory;
|
|
157
187
|
map: MapFactory;
|
|
158
188
|
set: SetFactory;
|
package/package.json
CHANGED
package/src/Metadata.ts
CHANGED
package/src/Reflection.ts
CHANGED
|
@@ -52,8 +52,8 @@ export const Reflection = schema({
|
|
|
52
52
|
types: t.array(ReflectionType),
|
|
53
53
|
rootType: t.number(),
|
|
54
54
|
}, "Reflection") as ReturnType<typeof schema<{
|
|
55
|
-
types: FieldBuilder<ArraySchema<ReflectionType
|
|
56
|
-
rootType: FieldBuilder<number>;
|
|
55
|
+
types: FieldBuilder<ArraySchema<ReflectionType>, true, false>;
|
|
56
|
+
rootType: FieldBuilder<number, false, false>;
|
|
57
57
|
}>> & ReflectionStatic;
|
|
58
58
|
|
|
59
59
|
export type Reflection = SchemaType<typeof Reflection>;
|
package/src/annotations.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { TypeDefinition, getType } from "./types/registry.js";
|
|
|
9
9
|
import { OPERATION } from "./encoding/spec.js";
|
|
10
10
|
import { TypeContext } from "./types/TypeContext.js";
|
|
11
11
|
import { assertInstanceType, assertType, EncodeSchemaError } from "./encoding/assert.js";
|
|
12
|
-
import type { InferValueType, InferSchemaInstanceType, AssignableProps, IsNever } from "./types/HelperTypes.js";
|
|
12
|
+
import type { InferValueType, InferSchemaInstanceType, AssignableProps, BuilderInitProps, IsNever } from "./types/HelperTypes.js";
|
|
13
13
|
import { CollectionSchema } from "./types/custom/CollectionSchema.js";
|
|
14
14
|
import { SetSchema } from "./types/custom/SetSchema.js";
|
|
15
15
|
import { StreamSchema } from "./types/custom/StreamSchema.js";
|
|
@@ -529,9 +529,13 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
|
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
-
// Helper type to extract InitProps from initialize method
|
|
533
|
-
//
|
|
534
|
-
//
|
|
532
|
+
// Helper type to extract InitProps from initialize method.
|
|
533
|
+
// - Non-empty initialize params: use them directly.
|
|
534
|
+
// - Zero-arg initialize: no args accepted (`never`) — user-supplied field
|
|
535
|
+
// values would be dropped at runtime (parent's initialize is skipped
|
|
536
|
+
// during child construction via the `new.target === klass` guard, and
|
|
537
|
+
// own-field auto-assignment happens only inside initialize).
|
|
538
|
+
// - No initialize at all: derive from fields map.
|
|
535
539
|
type ExtractInitProps<T> = T extends { initialize: (...args: infer P) => void }
|
|
536
540
|
? P extends readonly []
|
|
537
541
|
? never
|
|
@@ -540,28 +544,40 @@ type ExtractInitProps<T> = T extends { initialize: (...args: infer P) => void }
|
|
|
540
544
|
? First
|
|
541
545
|
: P
|
|
542
546
|
: P
|
|
543
|
-
:
|
|
547
|
+
: BuilderInitProps<T>;
|
|
544
548
|
|
|
545
|
-
//
|
|
546
|
-
type
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
549
|
+
// Does the init-props shape have at least one required property?
|
|
550
|
+
type HasRequiredKeys<X> = {} extends X ? false : true;
|
|
551
|
+
|
|
552
|
+
// Whether the constructor's init-props argument must be supplied.
|
|
553
|
+
// Mirrors the cases inside ExtractInitProps: non-empty initialize params
|
|
554
|
+
// are required; zero-arg initialize accepts nothing; no initialize
|
|
555
|
+
// depends on whether the derived BuilderInitProps has any required keys.
|
|
556
|
+
type IsInitPropsRequired<T> = T extends { initialize: (...args: infer P) => void }
|
|
557
|
+
? P extends readonly []
|
|
558
|
+
? false
|
|
559
|
+
: true
|
|
560
|
+
: HasRequiredKeys<BuilderInitProps<T>>;
|
|
561
|
+
|
|
562
|
+
// Whether T declares any non-empty `initialize` method. Used to tighten
|
|
563
|
+
// the constructor signature: authors who write an explicit `initialize()`
|
|
564
|
+
// with args opt into strict required args. Without an initialize the sig
|
|
565
|
+
// also allows `[]` so the common `new X(); x.field = ...` pattern works.
|
|
566
|
+
type HasExplicitInit<T> = T extends { initialize: (...args: infer P) => void }
|
|
567
|
+
? P extends readonly [] ? false : true
|
|
568
|
+
: false;
|
|
553
569
|
|
|
554
570
|
/**
|
|
555
571
|
* A `schema()` field definition accepts a FieldBuilder, a Schema subclass
|
|
556
572
|
* (shorthand for `t.ref(Class)`), or a method (attached to the prototype).
|
|
557
573
|
*/
|
|
558
|
-
export type FieldsAndMethods = Record<string, FieldBuilder<any> | (new (...args: any[]) => Schema) | Function>;
|
|
574
|
+
export type FieldsAndMethods = Record<string, FieldBuilder<any, boolean, boolean> | (new (...args: any[]) => Schema) | Function>;
|
|
559
575
|
|
|
560
576
|
export interface SchemaWithExtends<T, P extends typeof Schema> {
|
|
561
577
|
extend: <T2 extends FieldsAndMethods = FieldsAndMethods>(
|
|
562
578
|
fields: T2 & ThisType<InferSchemaInstanceType<T & T2>>,
|
|
563
579
|
name?: string,
|
|
564
|
-
) => SchemaWithExtendsConstructor<T & T2, ExtractInitProps<T2>, P>;
|
|
580
|
+
) => SchemaWithExtendsConstructor<T & T2, ExtractInitProps<T & T2>, P>;
|
|
565
581
|
}
|
|
566
582
|
|
|
567
583
|
/**
|
|
@@ -582,7 +598,22 @@ export interface SchemaWithExtendsConstructor<
|
|
|
582
598
|
P extends typeof Schema
|
|
583
599
|
> extends SchemaWithExtends<T, P> {
|
|
584
600
|
'~type': InferSchemaInstanceType<T>;
|
|
585
|
-
|
|
601
|
+
// Constructor signature:
|
|
602
|
+
// - InitProps = never (zero-arg initialize): no args.
|
|
603
|
+
// - InitProps is a tuple (multi-arg initialize): spread it.
|
|
604
|
+
// - Explicit `initialize(arg)` with required args: strict [InitProps]
|
|
605
|
+
// — the author opted into requiring them.
|
|
606
|
+
// - No initialize, but required builder fields: allow `[]` or
|
|
607
|
+
// `[InitProps]`. Preserves `new X(); x.field = ...` while still
|
|
608
|
+
// flagging incomplete-object mistakes like `new X({ hp: 1 })`.
|
|
609
|
+
// - Otherwise: optional single-arg.
|
|
610
|
+
new (...args:
|
|
611
|
+
[InitProps] extends [never] ? []
|
|
612
|
+
: InitProps extends readonly any[] ? InitProps
|
|
613
|
+
: HasExplicitInit<T> extends true ? [InitProps]
|
|
614
|
+
: IsInitPropsRequired<T> extends true ? ([] | [InitProps])
|
|
615
|
+
: [InitProps?]
|
|
616
|
+
): InferSchemaInstanceType<T> & InstanceType<P>;
|
|
586
617
|
prototype: InferSchemaInstanceType<T> & InstanceType<P> & {
|
|
587
618
|
initialize(...args: [InitProps] extends [never] ? [] : InitProps extends readonly any[] ? InitProps : [InitProps]): void;
|
|
588
619
|
};
|
|
@@ -627,6 +658,7 @@ export function schema<
|
|
|
627
658
|
const staticFields: string[] = [];
|
|
628
659
|
const streamFields: string[] = [];
|
|
629
660
|
const streamPriorityFields: { [field: string]: (view: any, element: any) => number } = {};
|
|
661
|
+
const optionalFields: string[] = [];
|
|
630
662
|
|
|
631
663
|
for (const fieldName in fieldsAndMethods) {
|
|
632
664
|
const value: any = (fieldsAndMethods as any)[fieldName];
|
|
@@ -643,11 +675,13 @@ export function schema<
|
|
|
643
675
|
if (def.static) { staticFields.push(fieldName); }
|
|
644
676
|
if (def.stream) { streamFields.push(fieldName); }
|
|
645
677
|
if (def.streamPriority !== undefined) { streamPriorityFields[fieldName] = def.streamPriority; }
|
|
678
|
+
if (def.optional) { optionalFields.push(fieldName); }
|
|
646
679
|
|
|
647
680
|
if (def.hasDefault) {
|
|
648
681
|
defaultValues[fieldName] = def.default;
|
|
649
|
-
} else {
|
|
682
|
+
} else if (!def.optional) {
|
|
650
683
|
// Auto-instantiate collection/Schema defaults when none is provided.
|
|
684
|
+
// `.optional()` opts out — field starts as undefined.
|
|
651
685
|
const rawType: any = def.type;
|
|
652
686
|
if (rawType && typeof rawType === "object") {
|
|
653
687
|
if (rawType.array !== undefined) {
|
|
@@ -759,6 +793,13 @@ export function schema<
|
|
|
759
793
|
}
|
|
760
794
|
}
|
|
761
795
|
|
|
796
|
+
if (optionalFields.length > 0) {
|
|
797
|
+
const metadata = (klass as any)[Symbol.metadata] as Metadata;
|
|
798
|
+
for (const fieldName of optionalFields) {
|
|
799
|
+
metadata[metadata[fieldName]].optional = true;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
762
803
|
if (name) {
|
|
763
804
|
Object.defineProperty(klass, "name", { value: name });
|
|
764
805
|
}
|
package/src/types/HelperTypes.ts
CHANGED
|
@@ -80,12 +80,25 @@ export type InferValueType<T> =
|
|
|
80
80
|
|
|
81
81
|
: never;
|
|
82
82
|
|
|
83
|
+
// Keys whose FieldBuilder generic admits `undefined` (i.e. `.optional()` was chained).
|
|
84
|
+
type OptionalBuilderKeys<T> = {
|
|
85
|
+
[K in keyof T]: T[K] extends FieldBuilder<infer V>
|
|
86
|
+
? (undefined extends V ? K : never)
|
|
87
|
+
: never
|
|
88
|
+
}[keyof T];
|
|
89
|
+
|
|
90
|
+
type RequiredBuilderKeys<T> = Exclude<keyof T, OptionalBuilderKeys<T>>;
|
|
91
|
+
|
|
83
92
|
export type InferSchemaInstanceType<T> = {
|
|
84
|
-
[K in
|
|
93
|
+
[K in RequiredBuilderKeys<T>]: T[K] extends FieldBuilder<any>
|
|
85
94
|
? InferValueType<T[K]>
|
|
86
95
|
: T[K] extends (...args: any[]) => any
|
|
87
96
|
? (T[K] extends new (...args: any[]) => any ? InferValueType<T[K]> : T[K])
|
|
88
97
|
: InferValueType<T[K]>
|
|
98
|
+
} & {
|
|
99
|
+
[K in OptionalBuilderKeys<T>]?: T[K] extends FieldBuilder<infer V>
|
|
100
|
+
? V
|
|
101
|
+
: never
|
|
89
102
|
} & Schema;
|
|
90
103
|
|
|
91
104
|
export type NonFunctionProps<T> = Omit<T, {
|
|
@@ -107,16 +120,28 @@ export type NonFunctionNonPrimitivePropNames<T> = {
|
|
|
107
120
|
// Helper to recursively convert Schema instances to their JSON representation
|
|
108
121
|
type ToJSONValue<U> = U extends Schema ? ToJSON<U> : PrimitiveStringToType<U>;
|
|
109
122
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
type ToJSONField<X> =
|
|
124
|
+
X extends MapSchema<infer U> ? Record<string, ToJSONValue<U>>
|
|
125
|
+
: X extends Map<string, infer U> ? Record<string, ToJSONValue<U>>
|
|
126
|
+
: X extends ArraySchema<infer U> ? ToJSONValue<U>[]
|
|
127
|
+
: X extends SetSchema<infer U> ? ToJSONValue<U>[]
|
|
128
|
+
: X extends CollectionSchema<infer U> ? ToJSONValue<U>[]
|
|
129
|
+
: X extends Schema ? ToJSON<X>
|
|
130
|
+
: X;
|
|
131
|
+
|
|
132
|
+
// Keys whose value type admits `undefined` — runtime `toJSON()` omits those,
|
|
133
|
+
// so they surface as `?:` on the JSON shape.
|
|
134
|
+
type ToJSONRequiredKeys<T> = {
|
|
135
|
+
[K in keyof T]-?: undefined extends T[K] ? never : K
|
|
136
|
+
}[keyof T];
|
|
137
|
+
type ToJSONOptionalKeys<T> = {
|
|
138
|
+
[K in keyof T]-?: undefined extends T[K] ? K : never
|
|
139
|
+
}[keyof T];
|
|
140
|
+
|
|
141
|
+
export type ToJSON<T> = NonFunctionProps<
|
|
142
|
+
& { [K in ToJSONRequiredKeys<T>]: ToJSONField<T[K]> }
|
|
143
|
+
& { [K in ToJSONOptionalKeys<T>]?: ToJSONField<Exclude<T[K], undefined>> }
|
|
144
|
+
>;
|
|
120
145
|
|
|
121
146
|
// Helper type to check if T is exactly 'never' (meaning no InitProps was provided)
|
|
122
147
|
export type IsNever<T> = [T] extends [never] ? true : false;
|
|
@@ -128,15 +153,75 @@ export type IsNever<T> = [T] extends [never] ? true : false;
|
|
|
128
153
|
* - Collections can be assigned from their JSON representations
|
|
129
154
|
*/
|
|
130
155
|
export type AssignableProps<T> = {
|
|
131
|
-
[K in NonFunctionPropNames<T>]?: T[K]
|
|
156
|
+
[K in NonFunctionPropNames<T>]?: AssignableValue<T[K]>
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Value-level assignment shape shared by `AssignableProps` and
|
|
161
|
+
* `BuilderInitProps`. Captures the "you can pass the real instance, or the
|
|
162
|
+
* plain-object / array shape" pattern.
|
|
163
|
+
*/
|
|
164
|
+
export type AssignableValue<V> =
|
|
165
|
+
V extends MapSchema<infer U>
|
|
132
166
|
? MapSchema<U> | Record<string, U extends Schema ? (U | AssignableProps<U>) : U>
|
|
133
|
-
:
|
|
167
|
+
: V extends ArraySchema<infer U>
|
|
134
168
|
? ArraySchema<U> | (U extends Schema ? (U | AssignableProps<U>)[] : U[])
|
|
135
|
-
:
|
|
169
|
+
: V extends SetSchema<infer U>
|
|
136
170
|
? SetSchema<U> | Set<U> | (U extends Schema ? (U | AssignableProps<U>)[] : U[])
|
|
137
|
-
:
|
|
171
|
+
: V extends CollectionSchema<infer U>
|
|
138
172
|
? CollectionSchema<U> | (U extends Schema ? (U | AssignableProps<U>)[] : U[])
|
|
139
|
-
:
|
|
140
|
-
?
|
|
141
|
-
:
|
|
142
|
-
|
|
173
|
+
: V extends Schema
|
|
174
|
+
? V | AssignableProps<V>
|
|
175
|
+
: V;
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// BuilderInitProps<T> — init-props shape derived from a schema() fields map.
|
|
179
|
+
// Unlike AssignableProps (fully partial, for `.assign()` updates), this type
|
|
180
|
+
// enforces required vs optional based on per-field `HasDefault` + `undefined`.
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
// Compile-time analogue of schema()'s Schema-ref auto-default rule:
|
|
184
|
+
// if the ref has no `initialize`, or a zero-arg `initialize`, schema()
|
|
185
|
+
// auto-instantiates it — so the field is omittable at construction.
|
|
186
|
+
export type RefHasDefault<C> =
|
|
187
|
+
C extends { prototype: { initialize(...args: infer P): any } }
|
|
188
|
+
? (P extends readonly [] ? true : false)
|
|
189
|
+
: true;
|
|
190
|
+
|
|
191
|
+
// Resolve a fields-map entry to its runtime value type.
|
|
192
|
+
type FieldValue<F> =
|
|
193
|
+
F extends FieldBuilder<infer V, boolean, boolean> ? V
|
|
194
|
+
: F extends new (...args: any[]) => infer I ? (I extends Schema ? I : never)
|
|
195
|
+
: never;
|
|
196
|
+
|
|
197
|
+
// Classify each key of a fields map as "required" / "optional" / "none"
|
|
198
|
+
// (methods). Both `HasDefault = true` and the explicit `.optional()` brand
|
|
199
|
+
// `IsOptional = true` mark the field omittable at construction. The brand
|
|
200
|
+
// sidesteps a TypeScript quirk where `undefined extends V` returned `true`
|
|
201
|
+
// for non-undefined V when V was inferred from a class with T in
|
|
202
|
+
// contravariant + covariant positions.
|
|
203
|
+
type KeyClass<T, K extends keyof T> =
|
|
204
|
+
T[K] extends FieldBuilder<unknown, infer D extends boolean, infer O extends boolean>
|
|
205
|
+
? (D extends true
|
|
206
|
+
? "optional"
|
|
207
|
+
: O extends true ? "optional" : "required")
|
|
208
|
+
: T[K] extends new (...args: any[]) => Schema
|
|
209
|
+
? (RefHasDefault<T[K]> extends true ? "optional" : "required")
|
|
210
|
+
: "none";
|
|
211
|
+
|
|
212
|
+
export type BuilderRequiredKeys<T> = {
|
|
213
|
+
[K in keyof T]-?: KeyClass<T, K> extends "required" ? K : never
|
|
214
|
+
}[keyof T];
|
|
215
|
+
|
|
216
|
+
export type BuilderOptionalKeys<T> = {
|
|
217
|
+
[K in keyof T]-?: KeyClass<T, K> extends "optional" ? K : never
|
|
218
|
+
}[keyof T];
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Constructor/init-props type for a schema() fields map. Required fields
|
|
222
|
+
* (primitives without `.default()` or `.optional()`, and Schema refs with
|
|
223
|
+
* non-zero-arg `initialize()`) are `:`; everything else is `?:`.
|
|
224
|
+
*/
|
|
225
|
+
export type BuilderInitProps<T> =
|
|
226
|
+
& { [K in BuilderRequiredKeys<T>]: AssignableValue<FieldValue<T[K]>> }
|
|
227
|
+
& { [K in BuilderOptionalKeys<T>]?: AssignableValue<Exclude<FieldValue<T[K]>, undefined>> };
|