@colyseus/schema 4.0.20 → 5.0.0
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 +2 -0
- package/build/Metadata.d.ts +55 -2
- package/build/Reflection.d.ts +24 -30
- package/build/Schema.d.ts +70 -9
- package/build/annotations.d.ts +56 -13
- package/build/codegen/cli.cjs +84 -67
- package/build/codegen/cli.cjs.map +1 -1
- package/build/decoder/DecodeOperation.d.ts +48 -5
- package/build/decoder/Decoder.d.ts +2 -2
- package/build/decoder/strategy/Callbacks.d.ts +1 -1
- package/build/encoder/ChangeRecorder.d.ts +107 -0
- package/build/encoder/ChangeTree.d.ts +218 -69
- package/build/encoder/EncodeDescriptor.d.ts +63 -0
- package/build/encoder/EncodeOperation.d.ts +25 -2
- package/build/encoder/Encoder.d.ts +59 -3
- package/build/encoder/MapJournal.d.ts +62 -0
- package/build/encoder/RefIdAllocator.d.ts +35 -0
- package/build/encoder/Root.d.ts +94 -13
- package/build/encoder/StateView.d.ts +116 -8
- package/build/encoder/changeTree/inheritedFlags.d.ts +34 -0
- package/build/encoder/changeTree/liveIteration.d.ts +3 -0
- package/build/encoder/changeTree/parentChain.d.ts +24 -0
- package/build/encoder/changeTree/treeAttachment.d.ts +13 -0
- package/build/encoder/streaming.d.ts +73 -0
- package/build/encoder/subscriptions.d.ts +25 -0
- package/build/index.cjs +5202 -1552
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -3
- package/build/index.js +5202 -1552
- package/build/index.mjs +5193 -1552
- package/build/index.mjs.map +1 -1
- package/build/input/InputDecoder.d.ts +32 -0
- package/build/input/InputEncoder.d.ts +117 -0
- package/build/input/index.cjs +7429 -0
- package/build/input/index.cjs.map +1 -0
- package/build/input/index.d.ts +3 -0
- package/build/input/index.mjs +7426 -0
- package/build/input/index.mjs.map +1 -0
- package/build/types/HelperTypes.d.ts +22 -8
- package/build/types/TypeContext.d.ts +9 -0
- package/build/types/builder.d.ts +162 -0
- package/build/types/custom/ArraySchema.d.ts +25 -4
- package/build/types/custom/CollectionSchema.d.ts +30 -2
- package/build/types/custom/MapSchema.d.ts +52 -3
- package/build/types/custom/SetSchema.d.ts +32 -2
- package/build/types/custom/StreamSchema.d.ts +114 -0
- package/build/types/symbols.d.ts +48 -5
- package/package.json +9 -3
- package/src/Metadata.ts +258 -31
- package/src/Reflection.ts +15 -13
- package/src/Schema.ts +176 -134
- package/src/annotations.ts +308 -236
- package/src/bench_bloat.ts +173 -0
- package/src/bench_decode.ts +221 -0
- package/src/bench_decode_mem.ts +165 -0
- package/src/bench_encode.ts +108 -0
- package/src/bench_init.ts +150 -0
- package/src/bench_static.ts +109 -0
- package/src/bench_stream.ts +295 -0
- package/src/bench_view_cmp.ts +142 -0
- package/src/codegen/languages/csharp.ts +0 -24
- package/src/codegen/parser.ts +83 -61
- package/src/decoder/DecodeOperation.ts +168 -63
- package/src/decoder/Decoder.ts +20 -10
- package/src/decoder/ReferenceTracker.ts +4 -0
- package/src/decoder/strategy/Callbacks.ts +30 -26
- package/src/decoder/strategy/getDecoderStateCallbacks.ts +16 -13
- package/src/encoder/ChangeRecorder.ts +276 -0
- package/src/encoder/ChangeTree.ts +674 -519
- package/src/encoder/EncodeDescriptor.ts +213 -0
- package/src/encoder/EncodeOperation.ts +107 -65
- package/src/encoder/Encoder.ts +630 -119
- package/src/encoder/MapJournal.ts +124 -0
- package/src/encoder/RefIdAllocator.ts +68 -0
- package/src/encoder/Root.ts +247 -120
- package/src/encoder/StateView.ts +592 -121
- package/src/encoder/changeTree/inheritedFlags.ts +217 -0
- package/src/encoder/changeTree/liveIteration.ts +74 -0
- package/src/encoder/changeTree/parentChain.ts +131 -0
- package/src/encoder/changeTree/treeAttachment.ts +171 -0
- package/src/encoder/streaming.ts +232 -0
- package/src/encoder/subscriptions.ts +71 -0
- package/src/index.ts +15 -3
- package/src/input/InputDecoder.ts +57 -0
- package/src/input/InputEncoder.ts +303 -0
- package/src/input/index.ts +3 -0
- package/src/types/HelperTypes.ts +21 -9
- package/src/types/TypeContext.ts +14 -2
- package/src/types/builder.ts +285 -0
- package/src/types/custom/ArraySchema.ts +210 -197
- package/src/types/custom/CollectionSchema.ts +115 -35
- package/src/types/custom/MapSchema.ts +162 -58
- package/src/types/custom/SetSchema.ts +128 -39
- package/src/types/custom/StreamSchema.ts +310 -0
- package/src/types/symbols.ts +54 -6
- package/src/utils.ts +4 -6
package/src/annotations.ts
CHANGED
|
@@ -2,15 +2,18 @@ import "./symbol.shim.js";
|
|
|
2
2
|
import { Schema } from './Schema.js';
|
|
3
3
|
import { ArraySchema } from './types/custom/ArraySchema.js';
|
|
4
4
|
import { MapSchema } from './types/custom/MapSchema.js';
|
|
5
|
-
import { getNormalizedType, Metadata } from "./Metadata.js";
|
|
6
|
-
import { $changes, $childType, $descriptors, $numFields, $track } from "./types/symbols.js";
|
|
5
|
+
import { getNormalizedType, Metadata, resolveFieldType } from "./Metadata.js";
|
|
6
|
+
import { $changes, $childType, $descriptors, $encoders, $numFields, $track, $values } from "./types/symbols.js";
|
|
7
|
+
import { encode } from "./encoding/encode.js";
|
|
7
8
|
import { TypeDefinition, getType } from "./types/registry.js";
|
|
8
9
|
import { OPERATION } from "./encoding/spec.js";
|
|
9
10
|
import { TypeContext } from "./types/TypeContext.js";
|
|
10
|
-
import { assertInstanceType, assertType } from "./encoding/assert.js";
|
|
11
|
+
import { assertInstanceType, assertType, EncodeSchemaError } from "./encoding/assert.js";
|
|
11
12
|
import type { InferValueType, InferSchemaInstanceType, AssignableProps, IsNever } from "./types/HelperTypes.js";
|
|
12
13
|
import { CollectionSchema } from "./types/custom/CollectionSchema.js";
|
|
13
14
|
import { SetSchema } from "./types/custom/SetSchema.js";
|
|
15
|
+
import { StreamSchema } from "./types/custom/StreamSchema.js";
|
|
16
|
+
import { FieldBuilder, isBuilder } from "./types/builder.js";
|
|
14
17
|
|
|
15
18
|
export type RawPrimitiveType = "string" |
|
|
16
19
|
"number" |
|
|
@@ -33,11 +36,12 @@ export type PrimitiveType = RawPrimitiveType | typeof Schema | object;
|
|
|
33
36
|
// TODO: infer "default" value type correctly.
|
|
34
37
|
export type DefinitionType<T extends PrimitiveType = PrimitiveType> = T
|
|
35
38
|
| T[]
|
|
36
|
-
| { type: T, default?: InferValueType<T>, view?: boolean | number, sync?: boolean }
|
|
37
|
-
| { array: T, default?: ArraySchema<InferValueType<T>>, view?: boolean | number, sync?: boolean }
|
|
38
|
-
| { map: T, default?: MapSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean }
|
|
39
|
-
| { collection: T, default?: CollectionSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean }
|
|
40
|
-
| { set: T, default?: SetSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean }
|
|
39
|
+
| { type: T, default?: InferValueType<T>, view?: boolean | number, sync?: boolean, owned?: boolean }
|
|
40
|
+
| { array: T, default?: ArraySchema<InferValueType<T>>, view?: boolean | number, sync?: boolean, owned?: boolean }
|
|
41
|
+
| { map: T, default?: MapSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean, owned?: boolean }
|
|
42
|
+
| { collection: T, default?: CollectionSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean, owned?: boolean }
|
|
43
|
+
| { set: T, default?: SetSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean, owned?: boolean }
|
|
44
|
+
| { stream: T, default?: StreamSchema<InferValueType<T>>, view?: boolean | number, sync?: boolean, owned?: boolean, priority?: (view: any, element: InferValueType<T>) => number };
|
|
41
45
|
|
|
42
46
|
export type Definition = { [field: string]: DefinitionType };
|
|
43
47
|
|
|
@@ -220,57 +224,32 @@ export function entity(constructor: any): any {
|
|
|
220
224
|
|
|
221
225
|
export function view<T> (tag: number = DEFAULT_VIEW_TAG) {
|
|
222
226
|
return function(target: T, fieldName: string) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
226
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
227
|
-
|
|
228
|
-
// TODO: use Metadata.initialize()
|
|
229
|
-
const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
230
|
-
// const fieldIndex = metadata[fieldName];
|
|
231
|
-
|
|
232
|
-
// if (!metadata[fieldIndex]) {
|
|
233
|
-
// //
|
|
234
|
-
// // detect index for this field, considering inheritance
|
|
235
|
-
// //
|
|
236
|
-
// metadata[fieldIndex] = {
|
|
237
|
-
// type: undefined,
|
|
238
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
239
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
240
|
-
// ?? -1) + 1 // no fields defined
|
|
241
|
-
// }
|
|
242
|
-
// }
|
|
243
|
-
|
|
227
|
+
const metadata = Metadata.initialize(target.constructor as typeof Schema);
|
|
244
228
|
Metadata.setTag(metadata, fieldName, tag);
|
|
245
229
|
}
|
|
246
230
|
}
|
|
247
231
|
|
|
232
|
+
export function owned<T> (target: T, field: string) {
|
|
233
|
+
const metadata = Metadata.initialize(target.constructor as typeof Schema);
|
|
234
|
+
metadata[metadata[field]].owned = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
248
237
|
export function unreliable<T> (target: T, field: string) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// metadata[field] = {
|
|
265
|
-
// type: undefined,
|
|
266
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
267
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
268
|
-
// ?? -1) + 1 // no fields defined
|
|
269
|
-
// }
|
|
270
|
-
// }
|
|
271
|
-
|
|
272
|
-
// add owned flag to the field
|
|
273
|
-
metadata[metadata[field]].unreliable = true;
|
|
238
|
+
const metadata = Metadata.initialize(target.constructor as typeof Schema);
|
|
239
|
+
Metadata.setUnreliable(metadata, field);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @transient — mark a field as not persisted to snapshots (encodeAll /
|
|
244
|
+
* encodeAllView). Transient fields are still emitted on per-tick patches
|
|
245
|
+
* (reliable or unreliable), but late-joining clients won't see them until
|
|
246
|
+
* the next mutation.
|
|
247
|
+
*
|
|
248
|
+
* Orthogonal to @unreliable: a field can be either, both, or neither.
|
|
249
|
+
*/
|
|
250
|
+
export function transient<T> (target: T, field: string) {
|
|
251
|
+
const metadata = Metadata.initialize(target.constructor as typeof Schema);
|
|
252
|
+
Metadata.setTransient(metadata, field);
|
|
274
253
|
}
|
|
275
254
|
|
|
276
255
|
export function type (
|
|
@@ -341,97 +320,179 @@ export function type (
|
|
|
341
320
|
);
|
|
342
321
|
|
|
343
322
|
} else {
|
|
344
|
-
const complexTypeKlass
|
|
345
|
-
|
|
346
|
-
const childType = (complexTypeKlass)
|
|
347
|
-
? Object.values(type)[0]
|
|
348
|
-
: type;
|
|
323
|
+
const { complexTypeKlass, childType } = resolveFieldType(type);
|
|
349
324
|
|
|
350
325
|
Metadata.addField(
|
|
351
326
|
metadata,
|
|
352
327
|
fieldIndex,
|
|
353
328
|
field,
|
|
354
329
|
type,
|
|
355
|
-
getPropertyDescriptor(
|
|
330
|
+
getPropertyDescriptor(field, fieldIndex, childType, complexTypeKlass)
|
|
356
331
|
);
|
|
357
332
|
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
333
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
complexTypeKlass: TypeDefinition,
|
|
366
|
-
) {
|
|
367
|
-
return {
|
|
368
|
-
get: function (this: Schema) { return this[fieldCached as keyof Schema]; },
|
|
369
|
-
set: function (this: Schema, value: any) {
|
|
370
|
-
const previousValue = this[fieldCached as keyof Schema] ?? undefined;
|
|
334
|
+
// Install accessor descriptor on the prototype (once per class field).
|
|
335
|
+
if (metadata[$descriptors][field]) {
|
|
336
|
+
Object.defineProperty(target, field, metadata[$descriptors][field]);
|
|
337
|
+
}
|
|
371
338
|
|
|
372
|
-
|
|
373
|
-
|
|
339
|
+
// Pre-compute encoder function for primitive types.
|
|
340
|
+
if (typeof type === "string") {
|
|
341
|
+
if (!metadata[$encoders]) {
|
|
342
|
+
Object.defineProperty(metadata, $encoders, {
|
|
343
|
+
value: [],
|
|
344
|
+
enumerable: false,
|
|
345
|
+
configurable: true,
|
|
346
|
+
writable: true,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
metadata[$encoders][fieldIndex] = (encode as any)[type];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
374
353
|
|
|
354
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
355
|
+
// Per-field-shape specialized setters.
|
|
356
|
+
//
|
|
357
|
+
// Single shared closure used to handle all three shapes (primitive /
|
|
358
|
+
// schema-ref / collection) in one body with many branches. V8's inliner
|
|
359
|
+
// gave up on it because of the size + polymorphism. Splitting into three
|
|
360
|
+
// dedicated factories yields smaller, monomorphic bodies that the JIT can
|
|
361
|
+
// inline into hot setters like `position.x = 100`.
|
|
362
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
/** typeof target per primitive type. Cached once, looked up O(1) at decoration. */
|
|
365
|
+
const PRIMITIVE_TYPEOF: Record<string, "number" | "string" | "boolean" | "bigint"> = {
|
|
366
|
+
number: "number",
|
|
367
|
+
int8: "number", uint8: "number",
|
|
368
|
+
int16: "number", uint16: "number",
|
|
369
|
+
int32: "number", uint32: "number",
|
|
370
|
+
int64: "number", uint64: "number",
|
|
371
|
+
float32: "number", float64: "number",
|
|
372
|
+
bigint64: "bigint", biguint64: "bigint",
|
|
373
|
+
string: "string",
|
|
374
|
+
boolean: "boolean",
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
function makePrimitiveSetter(fieldName: string, fieldIndex: number, type: string) {
|
|
378
|
+
const typeofTarget = PRIMITIVE_TYPEOF[type]; // undefined for custom types
|
|
379
|
+
const allowNull = type === "string";
|
|
380
|
+
const isBool = type === "boolean";
|
|
381
|
+
return function (this: Schema, value: any) {
|
|
382
|
+
const values = this[$values];
|
|
383
|
+
const previousValue = values[fieldIndex];
|
|
384
|
+
if (value === previousValue) return;
|
|
385
|
+
|
|
386
|
+
if (value !== undefined && value !== null) {
|
|
387
|
+
// Inlined assertType primitive check.
|
|
375
388
|
if (
|
|
376
|
-
|
|
377
|
-
|
|
389
|
+
!isBool &&
|
|
390
|
+
typeofTarget !== undefined &&
|
|
391
|
+
typeof value !== typeofTarget &&
|
|
392
|
+
!(allowNull && value === null)
|
|
378
393
|
) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
394
|
+
const ctorSuffix = (value && value.constructor) ? ` (${value.constructor.name})` : '';
|
|
395
|
+
throw new EncodeSchemaError(
|
|
396
|
+
`a '${typeofTarget}' was expected, but '${JSON.stringify(value)}'${ctorSuffix} was provided in ${this.constructor.name}#${fieldName}`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
(this.constructor as typeof Schema)[$track](this[$changes], fieldIndex, OPERATION.ADD);
|
|
400
|
+
} else if (previousValue !== undefined && previousValue !== null) {
|
|
401
|
+
this[$changes].delete(fieldIndex);
|
|
402
|
+
}
|
|
403
|
+
values[fieldIndex] = value;
|
|
404
|
+
};
|
|
405
|
+
}
|
|
384
406
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
407
|
+
function makeSchemaRefSetter(fieldName: string, fieldIndex: number, type: typeof Schema) {
|
|
408
|
+
return function (this: Schema, value: any) {
|
|
409
|
+
const values = this[$values];
|
|
410
|
+
const previousValue = values[fieldIndex];
|
|
411
|
+
if (value === previousValue) return;
|
|
389
412
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
// value = new SetSchema(value);
|
|
393
|
-
// }
|
|
413
|
+
if (value !== undefined && value !== null) {
|
|
414
|
+
assertInstanceType(value, type, this, fieldName);
|
|
394
415
|
|
|
395
|
-
|
|
416
|
+
const changeTree = this[$changes];
|
|
417
|
+
const ctor = this.constructor as typeof Schema;
|
|
396
418
|
|
|
397
|
-
|
|
398
|
-
|
|
419
|
+
if (previousValue !== undefined && previousValue !== null && previousValue[$changes]) {
|
|
420
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
421
|
+
ctor[$track](changeTree, fieldIndex, OPERATION.DELETE_AND_ADD);
|
|
422
|
+
} else {
|
|
423
|
+
ctor[$track](changeTree, fieldIndex, OPERATION.ADD);
|
|
424
|
+
}
|
|
399
425
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
426
|
+
// External Schema-like instances may not carry a ChangeTree.
|
|
427
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
403
428
|
|
|
404
|
-
|
|
429
|
+
} else if (previousValue !== undefined && previousValue !== null) {
|
|
430
|
+
this[$changes].delete(fieldIndex);
|
|
431
|
+
}
|
|
432
|
+
values[fieldIndex] = value;
|
|
433
|
+
};
|
|
434
|
+
}
|
|
405
435
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
436
|
+
function makeCollectionSetter(
|
|
437
|
+
_fieldName: string,
|
|
438
|
+
fieldIndex: number,
|
|
439
|
+
type: DefinitionType,
|
|
440
|
+
complexTypeKlass: TypeDefinition,
|
|
441
|
+
) {
|
|
442
|
+
const isArrayKlass = complexTypeKlass.constructor === ArraySchema;
|
|
443
|
+
const isMapKlass = complexTypeKlass.constructor === MapSchema;
|
|
444
|
+
return function (this: Schema, value: any) {
|
|
445
|
+
const values = this[$values];
|
|
446
|
+
const previousValue = values[fieldIndex];
|
|
447
|
+
if (value === previousValue) return;
|
|
448
|
+
|
|
449
|
+
if (value !== undefined && value !== null) {
|
|
450
|
+
// automatic Array → ArraySchema / Map → MapSchema conversion.
|
|
451
|
+
if (isArrayKlass && !(value instanceof ArraySchema)) {
|
|
452
|
+
value = new ArraySchema(...value);
|
|
453
|
+
} else if (isMapKlass && !(value instanceof MapSchema)) {
|
|
454
|
+
value = new MapSchema(value);
|
|
455
|
+
}
|
|
456
|
+
value[$childType] = type;
|
|
412
457
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
458
|
+
const changeTree = this[$changes];
|
|
459
|
+
const ctor = this.constructor as typeof Schema;
|
|
416
460
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
} else if (previousValue !== undefined) {
|
|
424
|
-
//
|
|
425
|
-
// Setting a field to `null` or `undefined` will delete it.
|
|
426
|
-
//
|
|
427
|
-
this[$changes].delete(fieldIndex);
|
|
461
|
+
if (previousValue !== undefined && previousValue !== null && previousValue[$changes]) {
|
|
462
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
463
|
+
ctor[$track](changeTree, fieldIndex, OPERATION.DELETE_AND_ADD);
|
|
464
|
+
} else {
|
|
465
|
+
ctor[$track](changeTree, fieldIndex, OPERATION.ADD);
|
|
428
466
|
}
|
|
429
467
|
|
|
430
|
-
|
|
431
|
-
|
|
468
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
469
|
+
|
|
470
|
+
} else if (previousValue !== undefined && previousValue !== null) {
|
|
471
|
+
this[$changes].delete(fieldIndex);
|
|
472
|
+
}
|
|
473
|
+
values[fieldIndex] = value;
|
|
474
|
+
};
|
|
475
|
+
}
|
|
432
476
|
|
|
477
|
+
export function getPropertyDescriptor(
|
|
478
|
+
fieldName: string,
|
|
479
|
+
fieldIndex: number,
|
|
480
|
+
type: DefinitionType,
|
|
481
|
+
complexTypeKlass: TypeDefinition | false,
|
|
482
|
+
) {
|
|
483
|
+
let setter: (this: Schema, value: any) => void;
|
|
484
|
+
if (complexTypeKlass) {
|
|
485
|
+
setter = makeCollectionSetter(fieldName, fieldIndex, type, complexTypeKlass);
|
|
486
|
+
} else if (typeof type === "string") {
|
|
487
|
+
setter = makePrimitiveSetter(fieldName, fieldIndex, type);
|
|
488
|
+
} else {
|
|
489
|
+
setter = makeSchemaRefSetter(fieldName, fieldIndex, type as typeof Schema);
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
get: function (this: Schema) { return this[$values][fieldIndex]; },
|
|
493
|
+
set: setter,
|
|
433
494
|
enumerable: true,
|
|
434
|
-
configurable: true
|
|
495
|
+
configurable: true,
|
|
435
496
|
};
|
|
436
497
|
}
|
|
437
498
|
|
|
@@ -442,38 +503,21 @@ export function getPropertyDescriptor(
|
|
|
442
503
|
|
|
443
504
|
export function deprecated(throws: boolean = true): PropertyDecorator {
|
|
444
505
|
return function (klass: typeof Schema, field: string) {
|
|
445
|
-
|
|
446
|
-
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
447
|
-
//
|
|
448
|
-
const constructor = klass.constructor as typeof Schema;
|
|
449
|
-
|
|
450
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
451
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
452
|
-
const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
506
|
+
const metadata = Metadata.initialize(klass.constructor as typeof Schema);
|
|
453
507
|
const fieldIndex = metadata[field];
|
|
454
508
|
|
|
455
|
-
// if (!metadata[field]) {
|
|
456
|
-
// //
|
|
457
|
-
// // detect index for this field, considering inheritance
|
|
458
|
-
// //
|
|
459
|
-
// metadata[field] = {
|
|
460
|
-
// type: undefined,
|
|
461
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
462
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
463
|
-
// ?? -1) + 1 // no fields defined
|
|
464
|
-
// }
|
|
465
|
-
// }
|
|
466
|
-
|
|
467
509
|
metadata[fieldIndex].deprecated = true;
|
|
468
510
|
|
|
469
511
|
if (throws) {
|
|
470
512
|
metadata[$descriptors] ??= {};
|
|
471
513
|
metadata[$descriptors][field] = {
|
|
472
514
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
473
|
-
set: function (this: Schema,
|
|
515
|
+
set: function (this: Schema, _value: any) { /* throw new Error(`${field} is deprecated.`); */ },
|
|
474
516
|
enumerable: false,
|
|
475
517
|
configurable: true
|
|
476
518
|
};
|
|
519
|
+
// Override accessor on the prototype so deprecated throws at access.
|
|
520
|
+
Object.defineProperty(klass, field, metadata[$descriptors][field]);
|
|
477
521
|
}
|
|
478
522
|
|
|
479
523
|
// flag metadata[field] as non-enumerable
|
|
@@ -485,17 +529,6 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
|
|
|
485
529
|
}
|
|
486
530
|
}
|
|
487
531
|
|
|
488
|
-
export function defineTypes(
|
|
489
|
-
target: typeof Schema,
|
|
490
|
-
fields: Definition,
|
|
491
|
-
options?: TypeOptions
|
|
492
|
-
) {
|
|
493
|
-
for (let field in fields) {
|
|
494
|
-
type(fields[field], options)(target.prototype, field);
|
|
495
|
-
}
|
|
496
|
-
return target;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
532
|
// Helper type to extract InitProps from initialize method
|
|
500
533
|
// Supports both single object parameter and multiple parameters
|
|
501
534
|
// If no initialize method is specified, use AssignableProps for field initialization
|
|
@@ -507,9 +540,7 @@ type ExtractInitProps<T> = T extends { initialize: (...args: infer P) => void }
|
|
|
507
540
|
? First
|
|
508
541
|
: P
|
|
509
542
|
: P
|
|
510
|
-
: T
|
|
511
|
-
? AssignableProps<InferSchemaInstanceType<T>>
|
|
512
|
-
: never;
|
|
543
|
+
: AssignableProps<InferSchemaInstanceType<T>>;
|
|
513
544
|
|
|
514
545
|
// Helper type to determine if InitProps should be required
|
|
515
546
|
type IsInitPropsRequired<T> = T extends { initialize: (props: any) => void }
|
|
@@ -520,27 +551,33 @@ type IsInitPropsRequired<T> = T extends { initialize: (props: any) => void }
|
|
|
520
551
|
: true
|
|
521
552
|
: false;
|
|
522
553
|
|
|
523
|
-
|
|
524
|
-
|
|
554
|
+
/**
|
|
555
|
+
* A `schema()` field definition accepts a FieldBuilder, a Schema subclass
|
|
556
|
+
* (shorthand for `t.ref(Class)`), or a method (attached to the prototype).
|
|
557
|
+
*/
|
|
558
|
+
export type FieldsAndMethods = Record<string, FieldBuilder<any> | (new (...args: any[]) => Schema) | Function>;
|
|
559
|
+
|
|
560
|
+
export interface SchemaWithExtends<T, P extends typeof Schema> {
|
|
561
|
+
extend: <T2 extends FieldsAndMethods = FieldsAndMethods>(
|
|
525
562
|
fields: T2 & ThisType<InferSchemaInstanceType<T & T2>>,
|
|
526
|
-
name?: string
|
|
563
|
+
name?: string,
|
|
527
564
|
) => SchemaWithExtendsConstructor<T & T2, ExtractInitProps<T2>, P>;
|
|
528
565
|
}
|
|
529
566
|
|
|
530
567
|
/**
|
|
531
|
-
* Get the type of the schema defined via `schema({...})` method.
|
|
568
|
+
* Get the type of the schema defined via `schema('Name', {...})` method.
|
|
532
569
|
*
|
|
533
570
|
* @example
|
|
534
|
-
* const Entity = schema({
|
|
535
|
-
* x:
|
|
536
|
-
* y:
|
|
571
|
+
* const Entity = schema('Entity', {
|
|
572
|
+
* x: t.number(),
|
|
573
|
+
* y: t.number(),
|
|
537
574
|
* });
|
|
538
575
|
* type Entity = SchemaType<typeof Entity>;
|
|
539
576
|
*/
|
|
540
577
|
export type SchemaType<T extends {'~type': any}> = T['~type'];
|
|
541
578
|
|
|
542
579
|
export interface SchemaWithExtendsConstructor<
|
|
543
|
-
T
|
|
580
|
+
T,
|
|
544
581
|
InitProps,
|
|
545
582
|
P extends typeof Schema
|
|
546
583
|
> extends SchemaWithExtends<T, P> {
|
|
@@ -551,97 +588,112 @@ export interface SchemaWithExtendsConstructor<
|
|
|
551
588
|
};
|
|
552
589
|
}
|
|
553
590
|
|
|
591
|
+
/**
|
|
592
|
+
* Define a Schema class declaratively.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* import { schema, t } from '@colyseus/schema';
|
|
596
|
+
*
|
|
597
|
+
* const Player = schema({
|
|
598
|
+
* hp: t.uint8().default(100),
|
|
599
|
+
* name: t.string().view(),
|
|
600
|
+
* takeDamage(n: number) { this.hp -= n; },
|
|
601
|
+
* }, 'Player');
|
|
602
|
+
*
|
|
603
|
+
* const Warrior = Player.extend({
|
|
604
|
+
* weapon: t.string(),
|
|
605
|
+
* }, 'Warrior');
|
|
606
|
+
*/
|
|
554
607
|
export function schema<
|
|
555
|
-
T extends
|
|
608
|
+
T extends FieldsAndMethods,
|
|
556
609
|
P extends typeof Schema = typeof Schema
|
|
557
610
|
>(
|
|
558
611
|
fieldsAndMethods: T & ThisType<InferSchemaInstanceType<T>>,
|
|
559
612
|
name?: string,
|
|
560
|
-
inherits: P = Schema as P
|
|
613
|
+
inherits: P = Schema as P,
|
|
561
614
|
): SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P> {
|
|
615
|
+
if (fieldsAndMethods == null || typeof fieldsAndMethods !== "object") {
|
|
616
|
+
throw new Error(`schema(): first argument must be a fields object (got ${typeof fieldsAndMethods}).`);
|
|
617
|
+
}
|
|
618
|
+
|
|
562
619
|
const fields: any = {};
|
|
563
620
|
const methods: any = {};
|
|
564
|
-
|
|
565
621
|
const defaultValues: any = {};
|
|
566
|
-
const viewTagFields:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
622
|
+
const viewTagFields: { [field: string]: number } = {};
|
|
623
|
+
const ownedFields: string[] = [];
|
|
624
|
+
const unreliableFields: string[] = [];
|
|
625
|
+
const transientFields: string[] = [];
|
|
626
|
+
const deprecatedFields: { [field: string]: boolean } = {};
|
|
627
|
+
const staticFields: string[] = [];
|
|
628
|
+
const streamFields: string[] = [];
|
|
629
|
+
const streamPriorityFields: { [field: string]: (view: any, element: any) => number } = {};
|
|
630
|
+
|
|
631
|
+
for (const fieldName in fieldsAndMethods) {
|
|
632
|
+
const value: any = (fieldsAndMethods as any)[fieldName];
|
|
633
|
+
|
|
634
|
+
if (isBuilder(value)) {
|
|
635
|
+
const def = value.toDefinition();
|
|
636
|
+
fields[fieldName] = getNormalizedType(def.type);
|
|
637
|
+
|
|
638
|
+
if (def.view !== undefined) { viewTagFields[fieldName] = def.view; }
|
|
639
|
+
if (def.owned) { ownedFields.push(fieldName); }
|
|
640
|
+
if (def.unreliable) { unreliableFields.push(fieldName); }
|
|
641
|
+
if (def.transient) { transientFields.push(fieldName); }
|
|
642
|
+
if (def.deprecated) { deprecatedFields[fieldName] = def.deprecatedThrows; }
|
|
643
|
+
if (def.static) { staticFields.push(fieldName); }
|
|
644
|
+
if (def.stream) { streamFields.push(fieldName); }
|
|
645
|
+
if (def.streamPriority !== undefined) { streamPriorityFields[fieldName] = def.streamPriority; }
|
|
646
|
+
|
|
647
|
+
if (def.hasDefault) {
|
|
648
|
+
defaultValues[fieldName] = def.default;
|
|
649
|
+
} else {
|
|
650
|
+
// Auto-instantiate collection/Schema defaults when none is provided.
|
|
651
|
+
const rawType: any = def.type;
|
|
652
|
+
if (rawType && typeof rawType === "object") {
|
|
653
|
+
if (rawType.array !== undefined) {
|
|
654
|
+
defaultValues[fieldName] = new ArraySchema();
|
|
655
|
+
} else if (rawType.map !== undefined) {
|
|
656
|
+
defaultValues[fieldName] = new MapSchema();
|
|
657
|
+
} else if (rawType.set !== undefined) {
|
|
658
|
+
defaultValues[fieldName] = new SetSchema();
|
|
659
|
+
} else if (rawType.collection !== undefined) {
|
|
660
|
+
defaultValues[fieldName] = new CollectionSchema();
|
|
661
|
+
} else if (rawType.stream !== undefined) {
|
|
662
|
+
defaultValues[fieldName] = new StreamSchema();
|
|
663
|
+
}
|
|
664
|
+
} else if (typeof rawType === "function" && Schema.is(rawType)) {
|
|
665
|
+
if (!rawType.prototype.initialize || rawType.prototype.initialize.length === 0) {
|
|
666
|
+
defaultValues[fieldName] = new rawType();
|
|
608
667
|
}
|
|
609
668
|
}
|
|
610
|
-
} else {
|
|
611
|
-
defaultValues[fieldName] = value['default'];
|
|
612
669
|
}
|
|
613
670
|
|
|
614
|
-
|
|
615
|
-
} else if (typeof (value) === "function") {
|
|
671
|
+
} else if (typeof value === "function") {
|
|
616
672
|
if (Schema.is(value)) {
|
|
617
|
-
//
|
|
673
|
+
// Convenience: allow a bare Schema subclass (equivalent to `t.ref(Class)`).
|
|
674
|
+
fields[fieldName] = getNormalizedType(value);
|
|
618
675
|
if (!value.prototype.initialize || value.prototype.initialize.length === 0) {
|
|
619
|
-
// only auto-initialize Schema instances if:
|
|
620
|
-
// - they don't have an initialize method
|
|
621
|
-
// - or initialize method doesn't accept any parameters
|
|
622
676
|
defaultValues[fieldName] = new value();
|
|
623
677
|
}
|
|
624
|
-
fields[fieldName] = getNormalizedType(value);
|
|
625
678
|
} else {
|
|
626
679
|
methods[fieldName] = value;
|
|
627
680
|
}
|
|
628
681
|
|
|
629
682
|
} else {
|
|
630
|
-
|
|
683
|
+
throw new Error(
|
|
684
|
+
`schema(${name ? `'${name}'` : ""}): field '${fieldName}' must be a t.* builder, ` +
|
|
685
|
+
`Schema subclass, or method (got ${typeof value}).`
|
|
686
|
+
);
|
|
631
687
|
}
|
|
632
688
|
}
|
|
633
689
|
|
|
634
690
|
const getDefaultValues = () => {
|
|
635
691
|
const defaults: any = {};
|
|
636
|
-
|
|
637
|
-
// use current class default values
|
|
638
692
|
for (const fieldName in defaultValues) {
|
|
639
693
|
const defaultValue = defaultValues[fieldName];
|
|
640
|
-
if (defaultValue && typeof defaultValue.clone ===
|
|
641
|
-
// complex, cloneable values, e.g. Schema, ArraySchema, MapSchema, CollectionSchema, SetSchema
|
|
694
|
+
if (defaultValue && typeof defaultValue.clone === "function") {
|
|
642
695
|
defaults[fieldName] = defaultValue.clone();
|
|
643
696
|
} else {
|
|
644
|
-
// primitives and non-cloneable values
|
|
645
697
|
defaults[fieldName] = defaultValue;
|
|
646
698
|
}
|
|
647
699
|
}
|
|
@@ -657,44 +709,64 @@ export function schema<
|
|
|
657
709
|
}
|
|
658
710
|
}
|
|
659
711
|
return parentProps;
|
|
660
|
-
}
|
|
712
|
+
};
|
|
661
713
|
|
|
662
714
|
/** @codegen-ignore */
|
|
663
715
|
const klass = Metadata.setFields<any>(class extends (inherits as any) {
|
|
664
716
|
constructor(...args: any[]) {
|
|
665
|
-
|
|
666
|
-
if (methods.initialize && typeof methods.initialize === 'function') {
|
|
717
|
+
if (methods.initialize && typeof methods.initialize === "function") {
|
|
667
718
|
super(Object.assign({}, getDefaultValues(), getParentProps(args[0] || {})));
|
|
668
|
-
|
|
669
|
-
* only call initialize() in the current class, not the parent ones.
|
|
670
|
-
* see "should not call initialize automatically when creating an instance of inherited Schema"
|
|
671
|
-
*/
|
|
719
|
+
// Only call initialize() on the exact target class, not parents.
|
|
672
720
|
if (new.target === klass) {
|
|
673
721
|
methods.initialize.apply(this, args);
|
|
674
722
|
}
|
|
675
|
-
|
|
676
723
|
} else {
|
|
677
724
|
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
678
725
|
}
|
|
679
726
|
}
|
|
680
|
-
}, fields) as SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P>;
|
|
727
|
+
}, fields) as unknown as SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P>;
|
|
681
728
|
|
|
682
|
-
// Store the getDefaultValues function on the class for inheritance
|
|
683
729
|
(klass as any)._getDefaultValues = getDefaultValues;
|
|
684
730
|
|
|
685
|
-
// Add methods to the prototype
|
|
686
731
|
Object.assign(klass.prototype, methods);
|
|
687
732
|
|
|
688
|
-
for (
|
|
733
|
+
for (const fieldName in viewTagFields) {
|
|
689
734
|
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
690
735
|
}
|
|
736
|
+
for (const fieldName of ownedFields) {
|
|
737
|
+
owned(klass.prototype, fieldName);
|
|
738
|
+
}
|
|
739
|
+
for (const fieldName of unreliableFields) {
|
|
740
|
+
unreliable(klass.prototype, fieldName);
|
|
741
|
+
}
|
|
742
|
+
for (const fieldName of transientFields) {
|
|
743
|
+
transient(klass.prototype, fieldName);
|
|
744
|
+
}
|
|
745
|
+
for (const fieldName in deprecatedFields) {
|
|
746
|
+
deprecated(deprecatedFields[fieldName])(klass.prototype, fieldName);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (staticFields.length > 0 || streamFields.length > 0) {
|
|
750
|
+
const metadata = (klass as any)[Symbol.metadata] as Metadata;
|
|
751
|
+
for (const fieldName of staticFields) {
|
|
752
|
+
Metadata.setStatic(metadata, fieldName);
|
|
753
|
+
}
|
|
754
|
+
for (const fieldName of streamFields) {
|
|
755
|
+
Metadata.setStream(metadata, fieldName);
|
|
756
|
+
}
|
|
757
|
+
for (const fieldName in streamPriorityFields) {
|
|
758
|
+
Metadata.setStreamPriority(metadata, fieldName, streamPriorityFields[fieldName]);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
691
761
|
|
|
692
762
|
if (name) {
|
|
693
763
|
Object.defineProperty(klass, "name", { value: name });
|
|
694
764
|
}
|
|
695
765
|
|
|
696
|
-
klass.
|
|
697
|
-
|
|
766
|
+
(klass as any).extend = <T2 extends FieldsAndMethods = FieldsAndMethods>(
|
|
767
|
+
childFields: T2,
|
|
768
|
+
childName?: string,
|
|
769
|
+
) => schema<T2>(childFields, childName, klass as any);
|
|
698
770
|
|
|
699
771
|
return klass;
|
|
700
772
|
}
|