@colyseus/schema 5.0.2 → 5.0.4

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.
@@ -1237,11 +1237,24 @@ function checkInheritedFlags(tree, parent, parentIndex) {
1237
1237
  const refType = Metadata.isValidInstance(tree.ref)
1238
1238
  ? tree.ref.constructor
1239
1239
  : tree.ref[$childType];
1240
+ // #218: nested Schema fields inherit visibility from a @view-gated
1241
+ // parent regardless of whether the parent is a collection. The
1242
+ // `parentIsCollection` constraint that used to live here blocked
1243
+ // nested-Schema-field-of-@view-tagged-Schema from sharing visibility,
1244
+ // forcing users to wrap the child in an ArraySchema as a workaround.
1245
+ //
1246
+ // #226 (4.0.25): items inside a non-default-tag `@view(N)` collection
1247
+ // also inherit visibility from the parent collection, so items
1248
+ // pushed/set after `view.add(state, N)` show up automatically.
1249
+ // Default-tag `@view()` collections keep per-item gating —
1250
+ // `view.add(item)` is still required to opt each one in.
1251
+ // The `parentMetadata[parentIndex].tag` access is safe inside the
1252
+ // `fieldHasViewTag` short-circuit (the metadata entry and its `tag`
1253
+ // are guaranteed to exist when that flag is set).
1240
1254
  tree.isVisibilitySharedWithParent = (parentChangeTree.isFiltered
1241
1255
  && typeof refType !== "string"
1242
- && !fieldHasViewTag
1243
1256
  && !fieldHasStream
1244
- && parentIsCollection);
1257
+ && (!fieldHasViewTag || (parentIsCollection && parentMetadata[parentIndex].tag !== DEFAULT_VIEW_TAG)));
1245
1258
  }
1246
1259
  }
1247
1260
 
@@ -5554,6 +5567,33 @@ const Metadata = {
5554
5567
  getStreamPriority(metadata, index) {
5555
5568
  return metadata?.[$streamPriorities]?.[index];
5556
5569
  },
5570
+ /**
5571
+ * Install a single field with full encoder wiring: accessor descriptor
5572
+ * on the prototype + `metadata[$encoders]` slot for primitives. Shared
5573
+ * between `Metadata.setFields` (build path) and
5574
+ * `Reflection.makeEncodable` (Reflection upgrade path).
5575
+ */
5576
+ defineField(target, metadata, fieldIndex, fieldName, type) {
5577
+ const normalized = getNormalizedType(type);
5578
+ const { complexTypeKlass, childType } = resolveFieldType(normalized);
5579
+ Metadata.addField(metadata, fieldIndex, fieldName, normalized, getPropertyDescriptor(fieldName, fieldIndex, childType, complexTypeKlass));
5580
+ // Install accessor descriptor on the prototype (once per class field).
5581
+ if (metadata[$descriptors][fieldName]) {
5582
+ Object.defineProperty(target.prototype, fieldName, metadata[$descriptors][fieldName]);
5583
+ }
5584
+ // Pre-compute encoder function for primitive types.
5585
+ if (typeof normalized === "string") {
5586
+ if (!metadata[$encoders]) {
5587
+ Object.defineProperty(metadata, $encoders, {
5588
+ value: [],
5589
+ enumerable: false,
5590
+ configurable: true,
5591
+ writable: true,
5592
+ });
5593
+ }
5594
+ metadata[$encoders][fieldIndex] = encode[normalized];
5595
+ }
5596
+ },
5557
5597
  setFields(target, fields) {
5558
5598
  // for inheritance support
5559
5599
  const constructor = target.prototype.constructor;
@@ -5591,17 +5631,7 @@ const Metadata = {
5591
5631
  });
5592
5632
  }
5593
5633
  for (const field in fields) {
5594
- const type = getNormalizedType(fields[field]);
5595
- const { complexTypeKlass, childType } = resolveFieldType(type);
5596
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(field, fieldIndex, childType, complexTypeKlass));
5597
- // Install accessor descriptor on the prototype (once per class field).
5598
- if (metadata[$descriptors][field]) {
5599
- Object.defineProperty(target.prototype, field, metadata[$descriptors][field]);
5600
- }
5601
- // Pre-compute encoder function for primitive types.
5602
- if (typeof type === "string") {
5603
- metadata[$encoders][fieldIndex] = encode[type];
5604
- }
5634
+ Metadata.defineField(constructor, metadata, fieldIndex, field, fields[field]);
5605
5635
  fieldIndex++;
5606
5636
  }
5607
5637
  return target;
@@ -6584,8 +6614,19 @@ class Encoder {
6584
6614
  // selected element is passed to `view.add()` which populates
6585
6615
  // view.changes with the stream-link ADD + element-field ADDs.
6586
6616
  this._emitStreamPriority(view);
6587
- // encode visibility-triggered changes collected by view.add()
6588
- for (const [refId, changes] of view.changes) {
6617
+ //
6618
+ // `view.changes` Map insertion order IS topological order:
6619
+ // - `view.add` walks the parent chain to root via `addParentOf`
6620
+ // (depth-first ancestor-first), inserting every ancestor's
6621
+ // entry before the descendant's.
6622
+ // - `view.remove` calls `_touchAncestorsOf` before its own
6623
+ // write to insert any missing ancestors at the front of the
6624
+ // chain — empty entries that get skipped by the size==0
6625
+ // check below but establish Map position.
6626
+ // No per-encode topo sort needed.
6627
+ //
6628
+ for (const refId of view.changes.keys()) {
6629
+ const changes = view.changes.get(refId);
6589
6630
  const changeTree = this.root.changeTrees[refId];
6590
6631
  if (changeTree === undefined) {
6591
6632
  // detached instance, remove from view and skip.