@colyseus/schema 4.0.4 → 4.0.6

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.
@@ -1,4 +1,4 @@
1
- import type { Definition, DefinitionType, PrimitiveType } from "../annotations.js";
1
+ import type { Definition, DefinitionType, PrimitiveType, RawPrimitiveType } from "../annotations.js";
2
2
  import type { Schema } from "../Schema.js";
3
3
  import type { ArraySchema } from "./custom/ArraySchema.js";
4
4
  import type { CollectionSchema } from "./custom/CollectionSchema.js";
@@ -42,10 +42,14 @@ export type InferValueType<T extends DefinitionType> = T extends "string" ? stri
42
42
  } ? (ChildType extends Record<string | number, string | number> ? MapSchema<ChildType[keyof ChildType]> : MapSchema<PrimitiveStringToType<ChildType>>) : T extends {
43
43
  set: infer ChildType extends Constructor;
44
44
  } ? SetSchema<InstanceType<ChildType>> : T extends {
45
+ set: infer ChildType extends RawPrimitiveType;
46
+ } ? SetSchema<InferValueType<ChildType>> : T extends {
45
47
  set: infer ChildType;
46
48
  } ? (ChildType extends Record<string | number, string | number> ? SetSchema<ChildType[keyof ChildType]> : SetSchema<ChildType>) : T extends {
47
49
  collection: infer ChildType extends Constructor;
48
50
  } ? CollectionSchema<InstanceType<ChildType>> : T extends {
51
+ collection: infer ChildType extends RawPrimitiveType;
52
+ } ? CollectionSchema<InferValueType<ChildType>> : T extends {
49
53
  collection: infer ChildType;
50
54
  } ? (ChildType extends Record<string | number, string | number> ? CollectionSchema<ChildType[keyof ChildType]> : CollectionSchema<ChildType>) : T extends Constructor ? InstanceType<T> : T extends Record<string | number, string | number> ? T[keyof T] : T extends PrimitiveType ? T : never;
51
55
  export type InferSchemaInstanceType<T extends Definition> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "4.0.4",
3
+ "version": "4.0.6",
4
4
  "description": "Binary state serializer with delta encoding for games",
5
5
  "type": "module",
6
6
  "bin": {
package/src/Metadata.ts CHANGED
@@ -294,7 +294,11 @@ export const Metadata = {
294
294
  }
295
295
  }
296
296
 
297
- constructor[Symbol.metadata] = metadata;
297
+ Object.defineProperty(constructor, Symbol.metadata, {
298
+ value: metadata,
299
+ writable: false,
300
+ configurable: true
301
+ });
298
302
 
299
303
  return metadata;
300
304
  },
package/src/Schema.ts CHANGED
@@ -42,6 +42,16 @@ export class Schema<C = any> implements IRef {
42
42
  return typeof((type as typeof Schema)[Symbol.metadata]) === "object";
43
43
  }
44
44
 
45
+ /**
46
+ * Check if a value is an instance of Schema.
47
+ * This method uses duck-typing to avoid issues with multiple @colyseus/schema versions.
48
+ * @param obj Value to check
49
+ * @returns true if the value is a Schema instance
50
+ */
51
+ static isSchema(obj: any): obj is Schema {
52
+ return typeof obj?.assign === "function";
53
+ }
54
+
45
55
  /**
46
56
  * Track property changes
47
57
  */
@@ -600,7 +600,12 @@ export function schema<
600
600
 
601
601
  } else if (value['type'] !== undefined && Schema.is(value['type'])) {
602
602
  // Direct Schema type: Type → new Type()
603
- defaultValues[fieldName] = new value['type']();
603
+ if (!value['type'].prototype.initialize || value['type'].prototype.initialize.length === 0) {
604
+ // only auto-initialize Schema instances if:
605
+ // - they don't have an initialize method
606
+ // - or initialize method doesn't accept any parameters
607
+ defaultValues[fieldName] = new value['type']();
608
+ }
604
609
  }
605
610
  } else {
606
611
  defaultValues[fieldName] = value['default'];
@@ -610,7 +615,12 @@ export function schema<
610
615
  } else if (typeof (value) === "function") {
611
616
  if (Schema.is(value)) {
612
617
  // Direct Schema type: Type → new Type()
613
- defaultValues[fieldName] = new value();
618
+ 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
+ defaultValues[fieldName] = new value();
623
+ }
614
624
  fields[fieldName] = getNormalizedType(value);
615
625
  } else {
616
626
  methods[fieldName] = value;
@@ -638,14 +648,33 @@ export function schema<
638
648
  return defaults;
639
649
  };
640
650
 
651
+ const getParentProps = (props: any) => {
652
+ const fieldNames = Object.keys(fields);
653
+ const parentProps: any = {};
654
+ for (const key in props) {
655
+ if (!fieldNames.includes(key)) {
656
+ parentProps[key] = props[key];
657
+ }
658
+ }
659
+ return parentProps;
660
+ }
661
+
641
662
  /** @codegen-ignore */
642
663
  const klass = Metadata.setFields<any>(class extends (inherits as any) {
643
664
  constructor(...args: any[]) {
644
- super(Object.assign({}, getDefaultValues(), args[0] || {}));
645
-
646
665
  // call initialize method
647
666
  if (methods.initialize && typeof methods.initialize === 'function') {
648
- methods.initialize.apply(this, args);
667
+ 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
+ */
672
+ if (new.target === klass) {
673
+ methods.initialize.apply(this, args);
674
+ }
675
+
676
+ } else {
677
+ super(Object.assign({}, getDefaultValues(), args[0] || {}));
649
678
  }
650
679
  }
651
680
  }, fields) as SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P>;
@@ -1,20 +1,18 @@
1
1
  import { OPERATION } from "../encoding/spec.js";
2
2
  import { Metadata } from "../Metadata.js";
3
3
  import { Schema } from "../Schema.js";
4
- import type { Ref } from "../encoder/ChangeTree.js";
4
+ import type { IRef, Ref } from "../encoder/ChangeTree.js";
5
5
  import type { Decoder } from "./Decoder.js";
6
6
  import { Iterator, decode } from "../encoding/decode.js";
7
7
  import { $childType, $deleteByIndex, $getByIndex, $refId } from "../types/symbols.js";
8
8
 
9
- import type { MapSchema } from "../types/custom/MapSchema.js";
10
9
  import type { ArraySchema } from "../types/custom/ArraySchema.js";
11
- import type { CollectionSchema } from "../types/custom/CollectionSchema.js";
12
10
 
13
11
  import { getType } from "../types/registry.js";
14
12
  import { Collection } from "../types/HelperTypes.js";
15
13
 
16
14
  export interface DataChange<T = any, F = string> {
17
- ref: Ref,
15
+ ref: IRef,
18
16
  refId: number,
19
17
  op: OPERATION,
20
18
  field: F;
@@ -29,7 +27,7 @@ export type DecodeOperation<T extends Schema = any> = (
29
27
  decoder: Decoder<T>,
30
28
  bytes: Uint8Array,
31
29
  it: Iterator,
32
- ref: Ref,
30
+ ref: IRef,
33
31
  allChanges: DataChange[],
34
32
  ) => number | void;
35
33
 
@@ -4,13 +4,13 @@ import { Schema } from "../Schema.js";
4
4
 
5
5
  import { decode } from "../encoding/decode.js";
6
6
  import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec.js';
7
- import type { Ref } from "../encoder/ChangeTree.js";
7
+ import type { IRef, Ref } from "../encoder/ChangeTree.js";
8
8
  import type { Iterator } from "../encoding/decode.js";
9
9
  import { ReferenceTracker } from "./ReferenceTracker.js";
10
10
  import { DEFINITION_MISMATCH, type DataChange, type DecodeOperation } from "./DecodeOperation.js";
11
11
  import { Collection } from "../types/HelperTypes.js";
12
12
 
13
- export class Decoder<T extends Schema = any> {
13
+ export class Decoder<T extends IRef = any> {
14
14
  context: TypeContext;
15
15
 
16
16
  state: T;
@@ -40,7 +40,7 @@ export class Decoder<T extends Schema = any> {
40
40
  decode(
41
41
  bytes: Uint8Array,
42
42
  it: Iterator = { offset: 0 },
43
- ref: Ref = this.state,
43
+ ref: IRef = this.state,
44
44
  ) {
45
45
  const allChanges: DataChange[] = [];
46
46
 
@@ -1,6 +1,6 @@
1
1
  import { Metadata } from "../Metadata.js";
2
2
  import { $childType, $refId } from "../types/symbols.js";
3
- import { Ref } from "../encoder/ChangeTree.js";
3
+ import type { IRef } from "../encoder/ChangeTree.js";
4
4
  import { spliceOne } from "../types/utils.js";
5
5
  import { OPERATION } from "../encoding/spec.js";
6
6
 
@@ -25,7 +25,7 @@ export class ReferenceTracker {
25
25
  // Relation of refId => Schema structure
26
26
  // For direct access of structures during decoding time.
27
27
  //
28
- public refs = new Map<number, Ref>();
28
+ public refs = new Map<number, IRef>();
29
29
 
30
30
  public refCount: { [refId: number]: number; } = {};
31
31
  public deletedRefs = new Set<number>();
@@ -38,7 +38,7 @@ export class ReferenceTracker {
38
38
  }
39
39
 
40
40
  // for decoding
41
- addRef(refId: number, ref: Ref, incrementCount: boolean = true) {
41
+ addRef(refId: number, ref: IRef, incrementCount: boolean = true) {
42
42
  this.refs.set(refId, ref);
43
43
  ref[$refId] = refId;
44
44
 
@@ -103,7 +103,7 @@ export class ReferenceTracker {
103
103
  const metadata: Metadata = (ref.constructor as typeof Schema)[Symbol.metadata];
104
104
  for (const index in metadata) {
105
105
  const field = metadata[index as any as number].name;
106
- const child = ref[field as keyof Ref];
106
+ const child = ref[field as keyof IRef];
107
107
  if (typeof(child) === "object" && child) {
108
108
  const childRefId = (child as any)[$refId];
109
109
  if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
@@ -1,6 +1,6 @@
1
1
  import { Metadata } from "../../Metadata.js";
2
2
  import { Collection, NonFunctionPropNames } from "../../types/HelperTypes.js";
3
- import { Ref } from "../../encoder/ChangeTree.js";
3
+ import type { IRef, Ref } from "../../encoder/ChangeTree.js";
4
4
  import { Decoder } from "../Decoder.js";
5
5
  import { DataChange } from "../DecodeOperation.js";
6
6
  import { OPERATION } from "../../encoding/spec.js";
@@ -45,7 +45,7 @@ type CollectionKeyType<T, K extends keyof T> =
45
45
  T[K] extends ArraySchema<any> ? number :
46
46
  T[K] extends Collection<infer Key, any, any> ? Key : never;
47
47
 
48
- export class StateCallbackStrategy<TState extends Schema> {
48
+ export class StateCallbackStrategy<TState extends IRef> {
49
49
  protected decoder: Decoder<TState>;
50
50
  protected uniqueRefIds: Set<number> = new Set();
51
51
  protected isTriggering: boolean = false;
@@ -72,7 +72,7 @@ export class StateCallbackStrategy<TState extends Schema> {
72
72
  return $root.addCallback(refId, operationOrProperty, handler);
73
73
  }
74
74
 
75
- protected addCallbackOrWaitCollectionAvailable<TInstance extends Schema, TReturn extends Ref>(
75
+ protected addCallbackOrWaitCollectionAvailable<TInstance extends IRef, TReturn extends Ref>(
76
76
  instance: TInstance,
77
77
  propertyName: string,
78
78
  operation: OPERATION,
@@ -142,7 +142,7 @@ export class StateCallbackStrategy<TState extends Schema> {
142
142
  }
143
143
  }
144
144
 
145
- protected listenInstance<TInstance extends Schema>(
145
+ protected listenInstance<TInstance extends IRef>(
146
146
  instance: TInstance,
147
147
  propertyName: string,
148
148
  handler: PropertyChangeCallback<any>,
@@ -344,7 +344,7 @@ export class StateCallbackStrategy<TState extends Schema> {
344
344
  //
345
345
  if (
346
346
  (change.op & OPERATION.DELETE) === OPERATION.DELETE &&
347
- change.previousValue instanceof Schema
347
+ Schema.isSchema(change.previousValue)
348
348
  ) {
349
349
  const childRefId = (change.previousValue as Ref)[$refId];
350
350
  const deleteCallbacks = this.callbacks[childRefId]?.[OPERATION.DELETE];
@@ -355,7 +355,7 @@ export class StateCallbackStrategy<TState extends Schema> {
355
355
  }
356
356
  }
357
357
 
358
- if (ref instanceof Schema) {
358
+ if (Schema.isSchema(ref)) {
359
359
  //
360
360
  // Handle Schema instance
361
361
  //
@@ -485,7 +485,7 @@ export const Callbacks = {
485
485
  * @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
486
486
  * @returns the new callbacks standard API.
487
487
  */
488
- get<T extends Schema>(roomOrDecoder: Decoder<T> | { serializer: { decoder: Decoder<T> } }): StateCallbackStrategy<T> {
488
+ get<T extends IRef>(roomOrDecoder: Decoder<T> | { serializer: { decoder: Decoder<T> } }): StateCallbackStrategy<T> {
489
489
  if (roomOrDecoder instanceof Decoder) {
490
490
  return new StateCallbackStrategy<T>(roomOrDecoder);
491
491
 
@@ -137,7 +137,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
137
137
  //
138
138
  if (
139
139
  (change.op & OPERATION.DELETE) === OPERATION.DELETE &&
140
- change.previousValue instanceof Schema
140
+ Schema.isSchema(change.previousValue)
141
141
  ) {
142
142
  const deleteCallbacks = callbacks[(change.previousValue as Ref)[$refId]]?.[OPERATION.DELETE];
143
143
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
@@ -145,7 +145,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
145
145
  }
146
146
  }
147
147
 
148
- if (ref instanceof Schema) {
148
+ if (Schema.isSchema(ref)) {
149
149
  //
150
150
  // Handle schema instance
151
151
  //
@@ -56,9 +56,11 @@ export type InferValueType<T extends DefinitionType> =
56
56
  : T extends { map: infer ChildType } ? (ChildType extends Record<string | number, string | number> ? MapSchema<ChildType[keyof ChildType]> : MapSchema<PrimitiveStringToType<ChildType>>) // TS ENUM
57
57
 
58
58
  : T extends { set: infer ChildType extends Constructor } ? SetSchema<InstanceType<ChildType>>
59
+ : T extends { set: infer ChildType extends RawPrimitiveType } ? SetSchema<InferValueType<ChildType>> // primitive types
59
60
  : T extends { set: infer ChildType } ? (ChildType extends Record<string | number, string | number> ? SetSchema<ChildType[keyof ChildType]> : SetSchema<ChildType>) // TS ENUM
60
61
 
61
62
  : T extends { collection: infer ChildType extends Constructor } ? CollectionSchema<InstanceType<ChildType>>
63
+ : T extends { collection: infer ChildType extends RawPrimitiveType } ? CollectionSchema<InferValueType<ChildType>> // primitive types
62
64
  : T extends { collection: infer ChildType } ? (ChildType extends Record<string | number, string | number> ? CollectionSchema<ChildType[keyof ChildType]> : CollectionSchema<ChildType>) // TS ENUM
63
65
 
64
66
  // Handle direct types