@colyseus/schema 5.0.12 → 5.0.14

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/index.js CHANGED
@@ -1918,6 +1918,34 @@
1918
1918
  tree._parentIndex = index;
1919
1919
  }
1920
1920
  }
1921
+ /**
1922
+ * Move `parent`'s existing chain entry to `index`, skipping the attachment
1923
+ * work `addParent` does. `parent` must already be a parent of `tree`.
1924
+ *
1925
+ * Called by collections whose wire slots shift (ArraySchema): StateView
1926
+ * addresses per-view ADD/DELETE by that index, so it has to follow the
1927
+ * element it names.
1928
+ */
1929
+ function setParentIndex(tree, parent, index) {
1930
+ if (tree.extraParents === undefined) {
1931
+ tree._parentIndex = index; // sole parent, so it is `parent`
1932
+ return;
1933
+ }
1934
+ // Shared instance — move only the entry `parent` owns. Matching goes
1935
+ // through `$changes` because ArraySchema arrives proxied (see removeParent
1936
+ // below), and `extraParents` only ever fills by demoting `parentRef`, so
1937
+ // the inline parent is set here.
1938
+ if (tree.parentRef[$changes] === parent[$changes]) {
1939
+ tree._parentIndex = index;
1940
+ return;
1941
+ }
1942
+ for (let entry = tree.extraParents; entry !== undefined; entry = entry.next) {
1943
+ if (entry.ref[$changes] === parent[$changes]) {
1944
+ entry.index = index;
1945
+ return;
1946
+ }
1947
+ }
1948
+ }
1921
1949
  /**
1922
1950
  * Remove a parent from the chain.
1923
1951
  * @returns true if parent was found and removed (Root.remove relies on this).
@@ -3162,6 +3190,8 @@
3162
3190
  get parent() { return this.parentRef; }
3163
3191
  get parentIndex() { return this._parentIndex; }
3164
3192
  addParent(parent, index) { addParent(this, parent, index); }
3193
+ /** Re-point an existing parent's cached index after the parent reindexed. */
3194
+ setParentIndex(parent, index) { setParentIndex(this, parent, index); }
3165
3195
  /** @returns true if parent was found and removed */
3166
3196
  removeParent(parent = this.parent) { return removeParent(this, parent); }
3167
3197
  findParent(predicate) {
@@ -3206,6 +3236,7 @@
3206
3236
  operation() { }
3207
3237
  setParent() { }
3208
3238
  addParent() { }
3239
+ setParentIndex() { }
3209
3240
  removeParent() { return false; }
3210
3241
  getChange() { return 0; }
3211
3242
  discard() { }
@@ -4261,6 +4292,40 @@
4261
4292
  // beyond the live range: appends land after the staged tmpItems tail
4262
4293
  return tmpItems.length + (index - live);
4263
4294
  }
4295
+ /**
4296
+ * Re-point children at their wire slot. `ChangeTree._parentIndex` caches
4297
+ * the slot a child holds in `tmpItems`, and StateView addresses per-view
4298
+ * ADD/DELETE with it — so a reorder that leaves it behind aims those ops
4299
+ * at whichever element inherited the slot (issue #231).
4300
+ *
4301
+ * The filter check is a correctness boundary, not a tunable: StateView is
4302
+ * the only reader and reaches the index only through a filtered array
4303
+ * (`addParentOf` bails on `hasFilteredFields`, `remove` on the child's
4304
+ * `isFiltered`). Everything else stops at the flag read instead of walking
4305
+ * its children every tick.
4306
+ *
4307
+ * Callers name the lowest slot that moved as `from`. Compaction cannot, so
4308
+ * it hands over the pre-compaction layout as `staged` and the unchanged
4309
+ * prefix is skipped instead. Either way tail churn walks nothing.
4310
+ */
4311
+ $reindexChildren(from, staged) {
4312
+ if (!this[$changes].hasFilteredFields) {
4313
+ return;
4314
+ } // nothing will read the cache
4315
+ if (typeof this[$childType] === "string") {
4316
+ return;
4317
+ } // primitives have no child tree
4318
+ const tmpItems = this.tmpItems;
4319
+ const length = tmpItems.length;
4320
+ if (staged !== undefined) {
4321
+ while (from < length && tmpItems[from] === staged[from]) {
4322
+ from++;
4323
+ }
4324
+ }
4325
+ for (let i = from; i < length; i++) {
4326
+ tmpItems[i]?.[$changes]?.setParentIndex(this, i);
4327
+ }
4328
+ }
4264
4329
  // encoding only. Returns the wire index the change was recorded at
4265
4330
  // (undefined when nothing was recorded).
4266
4331
  $changeAt(index, value) {
@@ -4372,6 +4437,7 @@
4372
4437
  self[$changes].operation(exports.OPERATION.REVERSE);
4373
4438
  self.items.reverse();
4374
4439
  self.tmpItems.reverse();
4440
+ self.$reindexChildren(0);
4375
4441
  return this;
4376
4442
  }
4377
4443
  /**
@@ -4423,6 +4489,7 @@
4423
4489
  // wouldn't OPERATION.MOVE make more sense here?
4424
4490
  sortedItems.forEach((_, i) => changeTree.change(i, exports.OPERATION.REPLACE));
4425
4491
  self.tmpItems.sort(compareFn);
4492
+ self.$reindexChildren(0);
4426
4493
  self.isMovingItems = false;
4427
4494
  return this;
4428
4495
  }
@@ -4504,6 +4571,7 @@
4504
4571
  deletedIndexes.unshift(...new Array(items.length).fill(false));
4505
4572
  }
4506
4573
  self.tmpItems.unshift(...items);
4574
+ self.$reindexChildren(items.length); // survivors only — the loop above placed the new items
4507
4575
  return self.items.unshift(...items);
4508
4576
  }
4509
4577
  /**
@@ -4772,8 +4840,14 @@
4772
4840
  }
4773
4841
  [$onEncodeEnd]() {
4774
4842
  const self = this[$proxyTarget] ?? this;
4843
+ const staged = self.tmpItems;
4775
4844
  self.tmpItems = self.items.slice();
4776
- self.deletedIndexes.length = 0;
4845
+ if (self.deletedIndexes.length > 0) {
4846
+ // compaction just closed the staged holes — everything above the
4847
+ // lowest one slid down a slot
4848
+ self.$reindexChildren(0, staged);
4849
+ self.deletedIndexes.length = 0;
4850
+ }
4777
4851
  }
4778
4852
  [$onDecodeEnd]() {
4779
4853
  const self = this[$proxyTarget] ?? this;
@@ -6152,10 +6226,11 @@
6152
6226
  * Schema ref whose `initialize` takes zero args.
6153
6227
  * - `IsOptional` is a compile-time brand for `.optional()`. Both
6154
6228
  * `HasDefault` and `IsOptional` make the field omittable in
6155
- * `BuilderInitProps<T>`. A separate brand (rather than reading
6156
- * `undefined extends V`) sidesteps a TypeScript quirk where
6157
- * class-generic-inferred `V` resolves `undefined extends V` as `true`
6158
- * even for non-undefined types.
6229
+ * `BuilderInitProps<T>`; `IsOptional` alone marks the instance property
6230
+ * `?:`. A separate brand (rather than reading `undefined extends V`)
6231
+ * keeps both correct for consumers compiling with
6232
+ * `strictNullChecks: false`, where `undefined extends V` is true for
6233
+ * every V.
6159
6234
  *
6160
6235
  * schema() reads the internal configuration via `toDefinition()` and wires
6161
6236
  * up metadata through the existing pipeline.
package/build/index.mjs CHANGED
@@ -1912,6 +1912,34 @@ function addParent(tree, parent, index) {
1912
1912
  tree._parentIndex = index;
1913
1913
  }
1914
1914
  }
1915
+ /**
1916
+ * Move `parent`'s existing chain entry to `index`, skipping the attachment
1917
+ * work `addParent` does. `parent` must already be a parent of `tree`.
1918
+ *
1919
+ * Called by collections whose wire slots shift (ArraySchema): StateView
1920
+ * addresses per-view ADD/DELETE by that index, so it has to follow the
1921
+ * element it names.
1922
+ */
1923
+ function setParentIndex(tree, parent, index) {
1924
+ if (tree.extraParents === undefined) {
1925
+ tree._parentIndex = index; // sole parent, so it is `parent`
1926
+ return;
1927
+ }
1928
+ // Shared instance — move only the entry `parent` owns. Matching goes
1929
+ // through `$changes` because ArraySchema arrives proxied (see removeParent
1930
+ // below), and `extraParents` only ever fills by demoting `parentRef`, so
1931
+ // the inline parent is set here.
1932
+ if (tree.parentRef[$changes] === parent[$changes]) {
1933
+ tree._parentIndex = index;
1934
+ return;
1935
+ }
1936
+ for (let entry = tree.extraParents; entry !== undefined; entry = entry.next) {
1937
+ if (entry.ref[$changes] === parent[$changes]) {
1938
+ entry.index = index;
1939
+ return;
1940
+ }
1941
+ }
1942
+ }
1915
1943
  /**
1916
1944
  * Remove a parent from the chain.
1917
1945
  * @returns true if parent was found and removed (Root.remove relies on this).
@@ -3156,6 +3184,8 @@ class ChangeTree {
3156
3184
  get parent() { return this.parentRef; }
3157
3185
  get parentIndex() { return this._parentIndex; }
3158
3186
  addParent(parent, index) { addParent(this, parent, index); }
3187
+ /** Re-point an existing parent's cached index after the parent reindexed. */
3188
+ setParentIndex(parent, index) { setParentIndex(this, parent, index); }
3159
3189
  /** @returns true if parent was found and removed */
3160
3190
  removeParent(parent = this.parent) { return removeParent(this, parent); }
3161
3191
  findParent(predicate) {
@@ -3200,6 +3230,7 @@ class UntrackedChangeTree {
3200
3230
  operation() { }
3201
3231
  setParent() { }
3202
3232
  addParent() { }
3233
+ setParentIndex() { }
3203
3234
  removeParent() { return false; }
3204
3235
  getChange() { return 0; }
3205
3236
  discard() { }
@@ -4255,6 +4286,40 @@ class ArraySchema {
4255
4286
  // beyond the live range: appends land after the staged tmpItems tail
4256
4287
  return tmpItems.length + (index - live);
4257
4288
  }
4289
+ /**
4290
+ * Re-point children at their wire slot. `ChangeTree._parentIndex` caches
4291
+ * the slot a child holds in `tmpItems`, and StateView addresses per-view
4292
+ * ADD/DELETE with it — so a reorder that leaves it behind aims those ops
4293
+ * at whichever element inherited the slot (issue #231).
4294
+ *
4295
+ * The filter check is a correctness boundary, not a tunable: StateView is
4296
+ * the only reader and reaches the index only through a filtered array
4297
+ * (`addParentOf` bails on `hasFilteredFields`, `remove` on the child's
4298
+ * `isFiltered`). Everything else stops at the flag read instead of walking
4299
+ * its children every tick.
4300
+ *
4301
+ * Callers name the lowest slot that moved as `from`. Compaction cannot, so
4302
+ * it hands over the pre-compaction layout as `staged` and the unchanged
4303
+ * prefix is skipped instead. Either way tail churn walks nothing.
4304
+ */
4305
+ $reindexChildren(from, staged) {
4306
+ if (!this[$changes].hasFilteredFields) {
4307
+ return;
4308
+ } // nothing will read the cache
4309
+ if (typeof this[$childType] === "string") {
4310
+ return;
4311
+ } // primitives have no child tree
4312
+ const tmpItems = this.tmpItems;
4313
+ const length = tmpItems.length;
4314
+ if (staged !== undefined) {
4315
+ while (from < length && tmpItems[from] === staged[from]) {
4316
+ from++;
4317
+ }
4318
+ }
4319
+ for (let i = from; i < length; i++) {
4320
+ tmpItems[i]?.[$changes]?.setParentIndex(this, i);
4321
+ }
4322
+ }
4258
4323
  // encoding only. Returns the wire index the change was recorded at
4259
4324
  // (undefined when nothing was recorded).
4260
4325
  $changeAt(index, value) {
@@ -4366,6 +4431,7 @@ class ArraySchema {
4366
4431
  self[$changes].operation(OPERATION.REVERSE);
4367
4432
  self.items.reverse();
4368
4433
  self.tmpItems.reverse();
4434
+ self.$reindexChildren(0);
4369
4435
  return this;
4370
4436
  }
4371
4437
  /**
@@ -4417,6 +4483,7 @@ class ArraySchema {
4417
4483
  // wouldn't OPERATION.MOVE make more sense here?
4418
4484
  sortedItems.forEach((_, i) => changeTree.change(i, OPERATION.REPLACE));
4419
4485
  self.tmpItems.sort(compareFn);
4486
+ self.$reindexChildren(0);
4420
4487
  self.isMovingItems = false;
4421
4488
  return this;
4422
4489
  }
@@ -4498,6 +4565,7 @@ class ArraySchema {
4498
4565
  deletedIndexes.unshift(...new Array(items.length).fill(false));
4499
4566
  }
4500
4567
  self.tmpItems.unshift(...items);
4568
+ self.$reindexChildren(items.length); // survivors only — the loop above placed the new items
4501
4569
  return self.items.unshift(...items);
4502
4570
  }
4503
4571
  /**
@@ -4766,8 +4834,14 @@ class ArraySchema {
4766
4834
  }
4767
4835
  [$onEncodeEnd]() {
4768
4836
  const self = this[$proxyTarget] ?? this;
4837
+ const staged = self.tmpItems;
4769
4838
  self.tmpItems = self.items.slice();
4770
- self.deletedIndexes.length = 0;
4839
+ if (self.deletedIndexes.length > 0) {
4840
+ // compaction just closed the staged holes — everything above the
4841
+ // lowest one slid down a slot
4842
+ self.$reindexChildren(0, staged);
4843
+ self.deletedIndexes.length = 0;
4844
+ }
4771
4845
  }
4772
4846
  [$onDecodeEnd]() {
4773
4847
  const self = this[$proxyTarget] ?? this;
@@ -6146,10 +6220,11 @@ registerType("stream", { constructor: StreamSchema });
6146
6220
  * Schema ref whose `initialize` takes zero args.
6147
6221
  * - `IsOptional` is a compile-time brand for `.optional()`. Both
6148
6222
  * `HasDefault` and `IsOptional` make the field omittable in
6149
- * `BuilderInitProps<T>`. A separate brand (rather than reading
6150
- * `undefined extends V`) sidesteps a TypeScript quirk where
6151
- * class-generic-inferred `V` resolves `undefined extends V` as `true`
6152
- * even for non-undefined types.
6223
+ * `BuilderInitProps<T>`; `IsOptional` alone marks the instance property
6224
+ * `?:`. A separate brand (rather than reading `undefined extends V`)
6225
+ * keeps both correct for consumers compiling with
6226
+ * `strictNullChecks: false`, where `undefined extends V` is true for
6227
+ * every V.
6153
6228
  *
6154
6229
  * schema() reads the internal configuration via `toDefinition()` and wires
6155
6230
  * up metadata through the existing pipeline.