@colyseus/schema 3.0.53 → 3.0.55

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.
@@ -685,15 +685,7 @@ class TypeContext {
685
685
  if (typeof (fieldType) === "string") {
686
686
  continue;
687
687
  }
688
- if (Array.isArray(fieldType)) {
689
- const type = fieldType[0];
690
- // skip primitive types
691
- if (type === "string") {
692
- continue;
693
- }
694
- this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
695
- }
696
- else if (typeof (fieldType) === "function") {
688
+ if (typeof (fieldType) === "function") {
697
689
  this.discoverTypes(fieldType, klass, index, parentHasViewTag || fieldHasViewTag);
698
690
  }
699
691
  else {
@@ -743,11 +735,43 @@ class TypeContext {
743
735
  }
744
736
 
745
737
  function getNormalizedType(type) {
746
- return (Array.isArray(type))
747
- ? { array: type[0] }
748
- : (typeof (type['type']) !== "undefined")
749
- ? type['type']
750
- : type;
738
+ if (Array.isArray(type)) {
739
+ return { array: getNormalizedType(type[0]) };
740
+ }
741
+ else if (typeof (type['type']) !== "undefined") {
742
+ return type['type'];
743
+ }
744
+ else if (isTSEnum(type)) {
745
+ // Detect TS Enum type (either string or number)
746
+ return Object.keys(type).every(key => typeof type[key] === "string")
747
+ ? "string"
748
+ : "number";
749
+ }
750
+ else if (typeof type === "object" && type !== null) {
751
+ // Handle collection types
752
+ const collectionType = Object.keys(type).find(k => registeredTypes[k] !== undefined);
753
+ if (collectionType) {
754
+ type[collectionType] = getNormalizedType(type[collectionType]);
755
+ return type;
756
+ }
757
+ }
758
+ return type;
759
+ }
760
+ function isTSEnum(_enum) {
761
+ if (typeof _enum === 'function' && _enum[Symbol.metadata]) {
762
+ return false;
763
+ }
764
+ const keys = Object.keys(_enum);
765
+ const numericFields = keys.filter(k => /\d+/.test(k));
766
+ // Check for number enum (has numeric keys and reverse mapping)
767
+ if (numericFields.length > 0 && numericFields.length === (keys.length / 2) && _enum[_enum[numericFields[0]]] == numericFields[0]) {
768
+ return true;
769
+ }
770
+ // Check for string enum (all values are strings and keys match values)
771
+ if (keys.length > 0 && keys.every(key => typeof _enum[key] === 'string' && _enum[key] === key)) {
772
+ return true;
773
+ }
774
+ return false;
751
775
  }
752
776
  const Metadata = {
753
777
  addField(metadata, index, name, type, descriptor) {
@@ -862,14 +886,12 @@ const Metadata = {
862
886
  ?? -1; // no fields defined
863
887
  fieldIndex++;
864
888
  for (const field in fields) {
865
- const type = fields[field];
889
+ const type = getNormalizedType(fields[field]);
866
890
  // FIXME: this code is duplicated from @type() annotation
867
- const complexTypeKlass = (Array.isArray(type))
868
- ? getType("array")
869
- : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
891
+ const complexTypeKlass = typeof (Object.keys(type)[0]) === "string" && getType(Object.keys(type)[0]);
870
892
  const childType = (complexTypeKlass)
871
893
  ? Object.values(type)[0]
872
- : getNormalizedType(type);
894
+ : type;
873
895
  Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
874
896
  fieldIndex++;
875
897
  }
@@ -3331,6 +3353,8 @@ function type(type, options) {
3331
3353
  if (!type) {
3332
3354
  throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
3333
3355
  }
3356
+ // Normalize type (enum/collection/etc)
3357
+ type = getNormalizedType(type);
3334
3358
  // for inheritance support
3335
3359
  TypeContext.register(constructor);
3336
3360
  const parentClass = Object.getPrototypeOf(constructor);
@@ -3375,9 +3399,7 @@ function type(type, options) {
3375
3399
  });
3376
3400
  }
3377
3401
  else {
3378
- const complexTypeKlass = (Array.isArray(type))
3379
- ? getType("array")
3380
- : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
3402
+ const complexTypeKlass = typeof (Object.keys(type)[0]) === "string" && getType(Object.keys(type)[0]);
3381
3403
  const childType = (complexTypeKlass)
3382
3404
  ? Object.values(type)[0]
3383
3405
  : type;
@@ -3504,9 +3526,10 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3504
3526
  ? DEFAULT_VIEW_TAG
3505
3527
  : value['view'];
3506
3528
  }
3507
- fields[fieldName] = value;
3529
+ fields[fieldName] = getNormalizedType(value);
3508
3530
  // If no explicit default provided, handle automatic instantiation for collection types
3509
3531
  if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
3532
+ // TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
3510
3533
  if (Array.isArray(value) || value['array'] !== undefined) {
3511
3534
  // Collection: Array → new ArraySchema()
3512
3535
  defaultValues[fieldName] = new ArraySchema();
@@ -3536,14 +3559,14 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3536
3559
  if (Schema.is(value)) {
3537
3560
  // Direct Schema type: Type → new Type()
3538
3561
  defaultValues[fieldName] = new value();
3539
- fields[fieldName] = value;
3562
+ fields[fieldName] = getNormalizedType(value);
3540
3563
  }
3541
3564
  else {
3542
3565
  methods[fieldName] = value;
3543
3566
  }
3544
3567
  }
3545
3568
  else {
3546
- fields[fieldName] = value;
3569
+ fields[fieldName] = getNormalizedType(value);
3547
3570
  }
3548
3571
  }
3549
3572
  const getDefaultValues = () => {
@@ -3985,7 +4008,7 @@ class Root {
3985
4008
  changeTree.forEachChild((child, _) => {
3986
4009
  if (child.removeParent(changeTree.ref)) {
3987
4010
  if ((child.parentChain === undefined || // no parent, remove it
3988
- (child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
4011
+ (child.parentChain && this.refCount[child.refId] > 0) // parent is still in use, but has more than one reference, remove it
3989
4012
  )) {
3990
4013
  this.remove(child);
3991
4014
  }