@colyseus/schema 5.0.13 → 5.0.19

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.
Files changed (50) hide show
  1. package/README.md +7 -2
  2. package/build/Metadata.d.ts +10 -1
  3. package/build/Reflection.d.ts +36 -34
  4. package/build/codegen/api.d.ts +2 -0
  5. package/build/codegen/cli.cjs +322 -31
  6. package/build/codegen/cli.cjs.map +1 -1
  7. package/build/codegen/parser.d.ts +6 -1
  8. package/build/codegen/resolve.d.ts +25 -0
  9. package/build/codegen/types.d.ts +2 -0
  10. package/build/encoder/ChangeTree.d.ts +40 -12
  11. package/build/encoder/Encoder.d.ts +1 -1
  12. package/build/encoder/Root.d.ts +9 -0
  13. package/build/encoder/StateView.d.ts +38 -1
  14. package/build/encoder/changeTree/inheritedFlags.d.ts +13 -19
  15. package/build/encoder/changeTree/liveIteration.d.ts +8 -0
  16. package/build/encoder/changeTree/parentChain.d.ts +30 -8
  17. package/build/encoder/streaming.d.ts +1 -1
  18. package/build/index.cjs +3237 -2834
  19. package/build/index.cjs.map +1 -1
  20. package/build/index.js +3233 -2830
  21. package/build/index.mjs +3237 -2834
  22. package/build/index.mjs.map +1 -1
  23. package/build/types/HelperTypes.d.ts +3 -5
  24. package/build/types/TypeContext.d.ts +0 -17
  25. package/build/types/builder.d.ts +8 -11
  26. package/build/types/symbols.d.ts +1 -0
  27. package/package.json +2 -2
  28. package/src/Metadata.ts +59 -77
  29. package/src/Reflection.ts +9 -5
  30. package/src/annotations.ts +19 -13
  31. package/src/codegen/api.ts +3 -1
  32. package/src/codegen/cli.ts +5 -2
  33. package/src/codegen/parser.ts +69 -31
  34. package/src/codegen/resolve.ts +322 -0
  35. package/src/codegen/types.ts +4 -1
  36. package/src/decoder/DecodeOperation.ts +13 -2
  37. package/src/encoder/ChangeTree.ts +76 -25
  38. package/src/encoder/EncodeOperation.ts +10 -1
  39. package/src/encoder/Encoder.ts +52 -2
  40. package/src/encoder/Root.ts +28 -8
  41. package/src/encoder/StateView.ts +150 -66
  42. package/src/encoder/changeTree/inheritedFlags.ts +164 -45
  43. package/src/encoder/changeTree/liveIteration.ts +24 -3
  44. package/src/encoder/changeTree/parentChain.ts +72 -15
  45. package/src/encoder/streaming.ts +2 -1
  46. package/src/types/HelperTypes.ts +16 -13
  47. package/src/types/TypeContext.ts +5 -52
  48. package/src/types/builder.ts +21 -16
  49. package/src/types/custom/ArraySchema.ts +57 -14
  50. package/src/types/symbols.ts +3 -0
@@ -51,10 +51,11 @@ export type BuilderOf<T> = FieldBuilder<T>;
51
51
  * Schema ref whose `initialize` takes zero args.
52
52
  * - `IsOptional` is a compile-time brand for `.optional()`. Both
53
53
  * `HasDefault` and `IsOptional` make the field omittable in
54
- * `BuilderInitProps<T>`. A separate brand (rather than reading
55
- * `undefined extends V`) sidesteps a TypeScript quirk where
56
- * class-generic-inferred `V` resolves `undefined extends V` as `true`
57
- * even for non-undefined types.
54
+ * `BuilderInitProps<T>`; `IsOptional` alone marks the instance property
55
+ * `?:`. A separate brand (rather than reading `undefined extends V`)
56
+ * keeps both correct for consumers compiling with
57
+ * `strictNullChecks: false`, where `undefined extends V` is true for
58
+ * every V.
58
59
  *
59
60
  * schema() reads the internal configuration via `toDefinition()` and wires
60
61
  * up metadata through the existing pipeline.
@@ -289,8 +290,8 @@ export function isBuilder(value: any): value is FieldBuilder<any> {
289
290
  * Two call signatures, NOT a defaulted generic `<T extends TBase = TBase>`: the
290
291
  * bare form must return a CONCRETE `FieldBuilder<TBase>` so `schema({ x:
291
292
  * t.number() })` still infers `x: number`. A defaulted free type parameter gets
292
- * captured as `any` during `schema()`'s self-referential field inference (and
293
- * `undefined extends any` then flips every field optional).
293
+ * captured as `any` during `schema()`'s self-referential field inference,
294
+ * degrading every field's value type to `any`.
294
295
  *
295
296
  * NOTE: the refinement is a TYPE-LEVEL assertion, not a runtime guarantee — the
296
297
  * wire still carries the codec's full range and the DECODER writes whatever
@@ -306,16 +307,24 @@ function primitive<TBase>(name: RawPrimitiveType): PrimitiveFactory<TBase> {
306
307
  return (() => new FieldBuilder<TBase>(name)) as PrimitiveFactory<TBase>;
307
308
  }
308
309
 
309
- // Accepts a Schema class, a primitive string, or another FieldBuilder as a child type.
310
+ // Collection element: a Schema class or a primitive type NAME (`"string"`).
310
311
  export type ChildType =
311
312
  | RawPrimitiveType
312
- | Constructor<Schema>
313
- | FieldBuilder<any>;
313
+ | Constructor<Schema>;
314
314
 
315
+ /**
316
+ * Guard against `t.array(t.string())`. A builder child looks like it should
317
+ * work — and its bare `_type` would — but every modifier on it (`.view()`,
318
+ * `.default()`, quantize options) would be silently dropped, since modifiers
319
+ * describe the FIELD, not the elements. Fail loudly instead.
320
+ */
315
321
  function resolveChild(child: ChildType): DefinitionType {
316
322
  if (isBuilder(child)) {
317
- // `_type` is private; element access bypasses the visibility check.
318
- return child['_type'];
323
+ const inner = child['_type']; // private; element access bypasses the check
324
+ const hint = (typeof inner === "string")
325
+ ? `use the type name instead: t.array("${inner}")`
326
+ : `collections accept a Schema class or a primitive type name ("string", "number", …)`;
327
+ throw new Error(`t.array/map/set/collection(): a t.* builder is not a valid element type — ${hint}.`);
319
328
  }
320
329
  return child as DefinitionType;
321
330
  }
@@ -325,28 +334,24 @@ function resolveChild(child: ChildType): DefinitionType {
325
334
  // ---------------------------------------------------------------------------
326
335
 
327
336
  // Overloaded factories for collections. Implementation lives in a single function;
328
- // overloads narrow the return type for Schema/primitive/builder children.
337
+ // overloads narrow the return type for Schema/primitive children.
329
338
  // All collection factories tag `HasDefault = true` because schema() auto-
330
339
  // instantiates an empty collection when no explicit default is given.
331
340
  interface ArrayFactory {
332
341
  <C extends Constructor<Schema>>(child: C): FieldBuilder<ArraySchema<InstanceType<C>>, true, false>;
333
342
  <P extends RawPrimitiveType>(child: P): FieldBuilder<ArraySchema<InferValueType<P>>, true, false>;
334
- <V>(child: FieldBuilder<V>): FieldBuilder<ArraySchema<V>, true, false>;
335
343
  }
336
344
  interface MapFactory {
337
345
  <C extends Constructor<Schema>>(child: C): FieldBuilder<MapSchema<InstanceType<C>>, true, false>;
338
346
  <P extends RawPrimitiveType>(child: P): FieldBuilder<MapSchema<InferValueType<P>>, true, false>;
339
- <V>(child: FieldBuilder<V>): FieldBuilder<MapSchema<V>, true, false>;
340
347
  }
341
348
  interface SetFactory {
342
349
  <C extends Constructor<Schema>>(child: C): FieldBuilder<SetSchema<InstanceType<C>>, true, false>;
343
350
  <P extends RawPrimitiveType>(child: P): FieldBuilder<SetSchema<InferValueType<P>>, true, false>;
344
- <V>(child: FieldBuilder<V>): FieldBuilder<SetSchema<V>, true, false>;
345
351
  }
346
352
  interface CollectionFactory {
347
353
  <C extends Constructor<Schema>>(child: C): FieldBuilder<CollectionSchema<InstanceType<C>>, true, false>;
348
354
  <P extends RawPrimitiveType>(child: P): FieldBuilder<CollectionSchema<InferValueType<P>>, true, false>;
349
- <V>(child: FieldBuilder<V>): FieldBuilder<CollectionSchema<V>, true, false>;
350
355
  }
351
356
  // t.stream(Entity) — priority-batched collection of Schema instances.
352
357
  // Element type is restricted to Schema subclasses (no primitives) because
@@ -178,7 +178,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
178
178
  const proxy = new Proxy(this, ARRAY_PROXY_HANDLER);
179
179
 
180
180
  Object.defineProperty(this, $changes, {
181
- value: new ChangeTree(proxy),
181
+ value: new ChangeTree(proxy, this),
182
182
  enumerable: false,
183
183
  writable: true,
184
184
  });
@@ -486,7 +486,22 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
486
486
  // @ts-ignore
487
487
  reverse(): ArraySchema<V> {
488
488
  const self = this[$proxyTarget];
489
- self[$changes].operation(OPERATION.REVERSE);
489
+ const changeTree = self[$changes];
490
+
491
+ if (changeTree.has() || self.deletedIndexes.length > 0) {
492
+ //
493
+ // Ops recorded earlier this tick address the staged (pre-reverse)
494
+ // layout, and the encoder only resolves their values at encode
495
+ // time — a pure REVERSE would move that layout under them.
496
+ // Degrade to a full re-state: CLEAR + re-ADD in reversed order.
497
+ //
498
+ const reversed = self.items.slice().reverse();
499
+ this.clear(); // also drops staged holes (discard → $onEncodeEnd)
500
+ this.push(...reversed);
501
+ return this;
502
+ }
503
+
504
+ changeTree.operation(OPERATION.REVERSE);
490
505
  self.items.reverse();
491
506
  self.tmpItems.reverse();
492
507
  self.$reindexChildren(0);
@@ -602,13 +617,13 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
602
617
 
603
618
  // insert operations
604
619
  if (insertCount > 0) {
605
- if (insertCount > deleteCount) {
606
- console.error("Inserting more elements than deleting during ArraySchema#splice()");
607
- throw new Error("ArraySchema#splice(): insertCount must be equal or lower than deleteCount.");
608
- }
620
+ const base = indexes[start] ?? itemsLength;
609
621
 
610
- for (let i = 0; i < insertCount; i++) {
611
- const addIndex = (indexes[start] ?? itemsLength) + i;
622
+ // the first `reuse` items take over the wire slots just deleted
623
+ const reuse = Math.min(insertCount, deleteCount);
624
+
625
+ for (let i = 0; i < reuse; i++) {
626
+ const addIndex = base + i;
612
627
 
613
628
  changeTree.indexedOperation(
614
629
  addIndex,
@@ -617,9 +632,36 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
617
632
  : OPERATION.ADD
618
633
  );
619
634
 
635
+ // the slot is live again — the staged snapshot must carry the
636
+ // new value, or `$getByIndex` falls back to `items[addIndex]`
637
+ // and resolves an unrelated element once tmp/items diverge.
638
+ tmpItems[addIndex] = insertItems[i];
639
+ deletedIndexes[addIndex] = false;
640
+
620
641
  // set value's parent/root — use `this` (Proxy) as parent.
621
642
  insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
622
643
  }
644
+
645
+ // ...the rest have no slot to take: widen the wire layout, same as
646
+ // unshift() but at `at` instead of 0.
647
+ const extra = insertCount - reuse;
648
+ if (extra > 0) {
649
+ const at = base + reuse;
650
+
651
+ changeTree.insertAt(at, extra);
652
+
653
+ for (let i = 0; i < extra; i++) {
654
+ insertItems[reuse + i][$changes]?.setParent(this, changeTree.root, at + i);
655
+ }
656
+
657
+ // keep staged-delete flags aligned with the inserted tmp slots
658
+ if (deletedIndexes.length > 0) {
659
+ deletedIndexes.splice(at, 0, ...new Array(extra).fill(false));
660
+ }
661
+
662
+ tmpItems.splice(at, 0, ...insertItems.slice(reuse));
663
+ self.$reindexChildren(at + extra); // survivors only — the loop above placed the new items
664
+ }
623
665
  }
624
666
 
625
667
  changeTree.root?.enqueueChangeTree(changeTree);
@@ -969,15 +1011,16 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
969
1011
  }
970
1012
 
971
1013
  protected [$onEncodeEnd]() {
972
- const self = this[$proxyTarget] ?? this;
973
- const staged = self.tmpItems;
974
- self.tmpItems = self.items.slice();
1014
+ // No unwrap: ChangeTree's gated sites are the only callers and they
1015
+ // invoke on `refTarget` (the raw target) already.
1016
+ const staged = this.tmpItems;
1017
+ this.tmpItems = this.items.slice();
975
1018
 
976
- if (self.deletedIndexes.length > 0) {
1019
+ if (this.deletedIndexes.length > 0) {
977
1020
  // compaction just closed the staged holes — everything above the
978
1021
  // lowest one slid down a slot
979
- self.$reindexChildren(0, staged);
980
- self.deletedIndexes.length = 0;
1022
+ this.$reindexChildren(0, staged);
1023
+ this.deletedIndexes.length = 0;
981
1024
  }
982
1025
  }
983
1026
 
@@ -131,6 +131,9 @@ export const $viewFieldIndexes = "~__viewFieldIndexes";
131
131
  export const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
132
132
  export const $unreliableFieldIndexes = "~__unreliableFieldIndexes";
133
133
  export const $patchOnlyFieldIndexes = "~__patchOnlyFieldIndexes";
134
+ // @patchOnly ∪ @deprecated() — indexes the full-sync walk must not read (the
135
+ // deprecated accessor may throw). Maintained at decoration time.
136
+ export const $fullSyncSkipIndexes = "~__fullSyncSkipIndexes";
134
137
  export const $fullStateOnlyFieldIndexes = "~__fullStateOnlyFieldIndexes";
135
138
  export const $streamFieldIndexes = "~__streamFieldIndexes";
136
139
  export const $streamPriorities = "~__streamPriorities";