@colyseus/schema 4.0.19 → 5.0.0

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 (95) hide show
  1. package/README.md +2 -0
  2. package/build/Metadata.d.ts +55 -2
  3. package/build/Reflection.d.ts +24 -30
  4. package/build/Schema.d.ts +70 -9
  5. package/build/annotations.d.ts +56 -13
  6. package/build/codegen/cli.cjs +84 -44
  7. package/build/codegen/cli.cjs.map +1 -1
  8. package/build/decoder/DecodeOperation.d.ts +48 -5
  9. package/build/decoder/Decoder.d.ts +2 -2
  10. package/build/decoder/strategy/Callbacks.d.ts +1 -1
  11. package/build/encoder/ChangeRecorder.d.ts +107 -0
  12. package/build/encoder/ChangeTree.d.ts +218 -69
  13. package/build/encoder/EncodeDescriptor.d.ts +63 -0
  14. package/build/encoder/EncodeOperation.d.ts +25 -2
  15. package/build/encoder/Encoder.d.ts +59 -3
  16. package/build/encoder/MapJournal.d.ts +62 -0
  17. package/build/encoder/RefIdAllocator.d.ts +35 -0
  18. package/build/encoder/Root.d.ts +94 -13
  19. package/build/encoder/StateView.d.ts +116 -8
  20. package/build/encoder/changeTree/inheritedFlags.d.ts +34 -0
  21. package/build/encoder/changeTree/liveIteration.d.ts +3 -0
  22. package/build/encoder/changeTree/parentChain.d.ts +24 -0
  23. package/build/encoder/changeTree/treeAttachment.d.ts +13 -0
  24. package/build/encoder/streaming.d.ts +73 -0
  25. package/build/encoder/subscriptions.d.ts +25 -0
  26. package/build/index.cjs +5202 -1552
  27. package/build/index.cjs.map +1 -1
  28. package/build/index.d.ts +7 -3
  29. package/build/index.js +5202 -1552
  30. package/build/index.mjs +5193 -1552
  31. package/build/index.mjs.map +1 -1
  32. package/build/input/InputDecoder.d.ts +32 -0
  33. package/build/input/InputEncoder.d.ts +117 -0
  34. package/build/input/index.cjs +7429 -0
  35. package/build/input/index.cjs.map +1 -0
  36. package/build/input/index.d.ts +3 -0
  37. package/build/input/index.mjs +7426 -0
  38. package/build/input/index.mjs.map +1 -0
  39. package/build/types/HelperTypes.d.ts +22 -8
  40. package/build/types/TypeContext.d.ts +9 -0
  41. package/build/types/builder.d.ts +162 -0
  42. package/build/types/custom/ArraySchema.d.ts +25 -4
  43. package/build/types/custom/CollectionSchema.d.ts +30 -2
  44. package/build/types/custom/MapSchema.d.ts +52 -3
  45. package/build/types/custom/SetSchema.d.ts +32 -2
  46. package/build/types/custom/StreamSchema.d.ts +114 -0
  47. package/build/types/symbols.d.ts +48 -5
  48. package/package.json +8 -2
  49. package/src/Metadata.ts +258 -31
  50. package/src/Reflection.ts +15 -13
  51. package/src/Schema.ts +176 -134
  52. package/src/annotations.ts +308 -236
  53. package/src/bench_bloat.ts +173 -0
  54. package/src/bench_decode.ts +221 -0
  55. package/src/bench_decode_mem.ts +165 -0
  56. package/src/bench_encode.ts +108 -0
  57. package/src/bench_init.ts +150 -0
  58. package/src/bench_static.ts +109 -0
  59. package/src/bench_stream.ts +295 -0
  60. package/src/bench_view_cmp.ts +142 -0
  61. package/src/codegen/parser.ts +83 -61
  62. package/src/decoder/DecodeOperation.ts +168 -63
  63. package/src/decoder/Decoder.ts +20 -10
  64. package/src/decoder/ReferenceTracker.ts +4 -0
  65. package/src/decoder/strategy/Callbacks.ts +30 -26
  66. package/src/decoder/strategy/getDecoderStateCallbacks.ts +16 -13
  67. package/src/encoder/ChangeRecorder.ts +276 -0
  68. package/src/encoder/ChangeTree.ts +674 -519
  69. package/src/encoder/EncodeDescriptor.ts +213 -0
  70. package/src/encoder/EncodeOperation.ts +107 -65
  71. package/src/encoder/Encoder.ts +630 -119
  72. package/src/encoder/MapJournal.ts +124 -0
  73. package/src/encoder/RefIdAllocator.ts +68 -0
  74. package/src/encoder/Root.ts +247 -120
  75. package/src/encoder/StateView.ts +592 -121
  76. package/src/encoder/changeTree/inheritedFlags.ts +217 -0
  77. package/src/encoder/changeTree/liveIteration.ts +74 -0
  78. package/src/encoder/changeTree/parentChain.ts +131 -0
  79. package/src/encoder/changeTree/treeAttachment.ts +171 -0
  80. package/src/encoder/streaming.ts +232 -0
  81. package/src/encoder/subscriptions.ts +71 -0
  82. package/src/index.ts +15 -3
  83. package/src/input/InputDecoder.ts +57 -0
  84. package/src/input/InputEncoder.ts +303 -0
  85. package/src/input/index.ts +3 -0
  86. package/src/types/HelperTypes.ts +21 -9
  87. package/src/types/TypeContext.ts +14 -2
  88. package/src/types/builder.ts +285 -0
  89. package/src/types/custom/ArraySchema.ts +210 -197
  90. package/src/types/custom/CollectionSchema.ts +115 -35
  91. package/src/types/custom/MapSchema.ts +162 -58
  92. package/src/types/custom/SetSchema.ts +128 -39
  93. package/src/types/custom/StreamSchema.ts +310 -0
  94. package/src/types/symbols.ts +54 -6
  95. package/src/utils.ts +4 -6
package/README.md CHANGED
@@ -260,6 +260,8 @@ up-to-date version of the schema definitions.
260
260
  ## Limitations and best practices
261
261
 
262
262
  - Each `Schema` structure can hold up to `64` fields. If you need more fields, use nested structures.
263
+ - Fields tagged with `@view`, `@unreliable`, or `@static` at field indexes `≥ 32` use a slower per-mutation classification path (linear scan over the tagged-field list instead of a single bitwise op). For schemas with more than 32 fields, declare frequently-mutated tagged fields earlier so they fall in the bitmask fast path.
264
+ - Schemas with `≤ 8` fields store per-field operation bytes inline in two numbers (no allocation per instance). Schemas with `> 8` fields allocate a small `Uint8Array` per instance for op storage. The difference is only material when allocating thousands of instances per tick — prefer narrower nested structures in that regime.
263
265
  - `NaN` or `null` numbers are encoded as `0`
264
266
  - `null` strings are encoded as `""`
265
267
  - `Infinity` numbers are encoded as `Number.MAX_SAFE_INTEGER`
@@ -1,12 +1,17 @@
1
1
  import { DefinitionType } from "./annotations.js";
2
- import { $descriptors, $fieldIndexesByViewTag, $numFields, $refTypeFieldIndexes, $viewFieldIndexes } from "./types/symbols.js";
2
+ import { TypeDefinition } from "./types/registry.js";
3
+ import { $descriptors, $encoders, $fieldIndexesByViewTag, $numFields, $refTypeFieldIndexes, $staticFieldIndexes, $streamFieldIndexes, $streamPriorities, $transientFieldIndexes, $unreliableFieldIndexes, $viewFieldIndexes } from "./types/symbols.js";
3
4
  export type MetadataField = {
4
5
  type: DefinitionType;
5
6
  name: string;
6
7
  index: number;
7
8
  tag?: number;
8
9
  unreliable?: boolean;
10
+ transient?: boolean;
9
11
  deprecated?: boolean;
12
+ owned?: boolean;
13
+ static?: boolean;
14
+ stream?: boolean;
10
15
  };
11
16
  export type Metadata = {
12
17
  [$numFields]: number;
@@ -22,6 +27,26 @@ export type Metadata = {
22
27
  {
23
28
  [$refTypeFieldIndexes]: number[];
24
29
  } & // all field indexes containing Ref types (Schema, ArraySchema, MapSchema, etc)
30
+ {
31
+ [$unreliableFieldIndexes]: number[];
32
+ } & // all field indexes tagged with @unreliable
33
+ {
34
+ [$transientFieldIndexes]: number[];
35
+ } & // all field indexes tagged with @transient (not persisted to snapshots)
36
+ {
37
+ [$staticFieldIndexes]: number[];
38
+ } & // all field indexes tagged with @static (not tracked after assignment)
39
+ {
40
+ [$streamFieldIndexes]: number[];
41
+ } & // all field indexes holding a t.stream(...) collection
42
+ {
43
+ [$streamPriorities]: {
44
+ [field: number]: (view: any, element: any) => number;
45
+ };
46
+ } & // per-stream-field priority callback declared at schema definition time
47
+ {
48
+ [$encoders]: Array<(bytes: Uint8Array, value: any, it: any) => void>;
49
+ } & // pre-computed encoder fn per primitive field
25
50
  {
26
51
  [field: number]: MetadataField;
27
52
  } & // index => field name
@@ -33,17 +58,45 @@ export type Metadata = {
33
58
  [field: string]: PropertyDescriptor;
34
59
  };
35
60
  };
61
+ /**
62
+ * Given a normalized field type (`"number"`, `{ map: Foo }`, `Player`,
63
+ * etc.), split into the collection-type descriptor (`{ constructor:
64
+ * MapSchema, ... }`) if applicable and the inner child type. Shared by
65
+ * `@type()` decoration and `Metadata.setFields` — both need to build a
66
+ * property accessor that knows whether the slot holds a collection.
67
+ */
68
+ export declare function resolveFieldType(type: any): {
69
+ complexTypeKlass: TypeDefinition | false;
70
+ childType: any;
71
+ };
36
72
  export declare function getNormalizedType(type: any): DefinitionType;
37
73
  export declare const Metadata: {
38
74
  addField(metadata: any, index: number, name: string, type: DefinitionType, descriptor?: PropertyDescriptor): void;
39
75
  setTag(metadata: Metadata, fieldName: string, tag: number): void;
76
+ setUnreliable(metadata: Metadata, fieldName: string): void;
77
+ setTransient(metadata: Metadata, fieldName: string): void;
78
+ setStatic(metadata: Metadata, fieldName: string): void;
79
+ setStream(metadata: Metadata, fieldName: string): void;
80
+ /**
81
+ * Attach a declaration-scope priority callback to a stream field.
82
+ * Called at schema definition time (via `t.stream(X).priority(fn)` or
83
+ * `@type({ stream: X, priority: fn })`), looked up at stream-attach
84
+ * time to seed the instance's `_stream.priority` slot. The callback
85
+ * signature is `(view: StateView, element: V) => number` — only fires
86
+ * during `encodeView`, broadcast mode emits FIFO regardless.
87
+ */
88
+ setStreamPriority(metadata: Metadata, fieldName: string, fn: (view: any, element: any) => number): void;
89
+ getStreamPriority(metadata: Metadata | undefined, index: number): (view: any, element: any) => number;
40
90
  setFields<T extends {
41
91
  new (...args: any[]): InstanceType<T>;
42
92
  } = any>(target: T, fields: { [field in keyof InstanceType<T>]?: DefinitionType; }): T;
43
93
  isDeprecated(metadata: any, field: string): boolean;
44
- init(klass: any): void;
45
94
  initialize(constructor: any): Metadata;
46
95
  isValidInstance(klass: any): boolean;
47
96
  getFields(klass: any): any;
48
97
  hasViewTagAtIndex(metadata: Metadata, index: number): boolean;
98
+ hasUnreliableAtIndex(metadata: Metadata, index: number): boolean;
99
+ hasTransientAtIndex(metadata: Metadata, index: number): boolean;
100
+ hasStaticAtIndex(metadata: Metadata, index: number): boolean;
101
+ hasStreamAtIndex(metadata: Metadata, index: number): boolean;
49
102
  };
@@ -3,6 +3,8 @@ import { Iterator } from "./encoding/decode.js";
3
3
  import { Encoder } from "./encoder/Encoder.js";
4
4
  import { Decoder } from "./decoder/Decoder.js";
5
5
  import { Schema } from "./Schema.js";
6
+ import { FieldBuilder } from "./types/builder.js";
7
+ import { ArraySchema } from "./types/custom/ArraySchema.js";
6
8
  /**
7
9
  * Static methods available on Reflection
8
10
  */
@@ -28,44 +30,36 @@ interface ReflectionStatic {
28
30
  * Reflection
29
31
  */
30
32
  export declare const ReflectionField: import("./annotations.js").SchemaWithExtendsConstructor<{
31
- name: "string";
32
- type: "string";
33
- referencedType: "number";
33
+ name: FieldBuilder<string>;
34
+ type: FieldBuilder<string>;
35
+ referencedType: FieldBuilder<number>;
34
36
  }, import("./index.js").AssignableProps<import("./index.js").InferSchemaInstanceType<{
35
- name: "string";
36
- type: "string";
37
- referencedType: "number";
37
+ name: FieldBuilder<string>;
38
+ type: FieldBuilder<string>;
39
+ referencedType: FieldBuilder<number>;
38
40
  }>>, typeof Schema>;
39
41
  export type ReflectionField = SchemaType<typeof ReflectionField>;
40
42
  export declare const ReflectionType: import("./annotations.js").SchemaWithExtendsConstructor<{
41
- id: "number";
42
- extendsId: "number";
43
- fields: import("./annotations.js").SchemaWithExtendsConstructor<{
44
- name: "string";
45
- type: "string";
46
- referencedType: "number";
47
- }, import("./index.js").AssignableProps<import("./index.js").InferSchemaInstanceType<{
48
- name: "string";
49
- type: "string";
50
- referencedType: "number";
51
- }>>, typeof Schema>[];
43
+ id: FieldBuilder<number>;
44
+ extendsId: FieldBuilder<number>;
45
+ fields: FieldBuilder<ArraySchema<{
46
+ name: string;
47
+ type: string;
48
+ referencedType: number;
49
+ } & Schema<any> & Schema<unknown>>>;
52
50
  }, import("./index.js").AssignableProps<import("./index.js").InferSchemaInstanceType<{
53
- id: "number";
54
- extendsId: "number";
55
- fields: import("./annotations.js").SchemaWithExtendsConstructor<{
56
- name: "string";
57
- type: "string";
58
- referencedType: "number";
59
- }, import("./index.js").AssignableProps<import("./index.js").InferSchemaInstanceType<{
60
- name: "string";
61
- type: "string";
62
- referencedType: "number";
63
- }>>, typeof Schema>[];
51
+ id: FieldBuilder<number>;
52
+ extendsId: FieldBuilder<number>;
53
+ fields: FieldBuilder<ArraySchema<{
54
+ name: string;
55
+ type: string;
56
+ referencedType: number;
57
+ } & Schema<any> & Schema<unknown>>>;
64
58
  }>>, typeof Schema>;
65
59
  export type ReflectionType = SchemaType<typeof ReflectionType>;
66
60
  export declare const Reflection: ReturnType<typeof schema<{
67
- types: [typeof ReflectionType];
68
- rootType: "number";
61
+ types: FieldBuilder<ArraySchema<ReflectionType>>;
62
+ rootType: FieldBuilder<number>;
69
63
  }>> & ReflectionStatic;
70
64
  export type Reflection = SchemaType<typeof Reflection>;
71
65
  export {};
package/build/Schema.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { OPERATION } from './encoding/spec.js';
2
2
  import { type DefinitionType } from "./annotations.js";
3
3
  import { AssignableProps, NonFunctionPropNames, ToJSON } from './types/HelperTypes.js';
4
- import { ChangeSetName, ChangeTree, IRef, Ref } from './encoder/ChangeTree.js';
5
- import { $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $refId, $track } from './types/symbols.js';
4
+ import { ChangeTree, IRef, Ref } from './encoder/ChangeTree.js';
5
+ import { $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $refId, $track, $values } from './types/symbols.js';
6
6
  import { StateView } from './encoder/StateView.js';
7
7
  import type { Decoder } from './decoder/Decoder.js';
8
8
  import type { Metadata } from './Metadata.js';
@@ -14,21 +14,58 @@ export declare class Schema<C = any> implements IRef {
14
14
  static [$encoder]: import("./encoder/EncodeOperation.js").EncodeOperation<any>;
15
15
  static [$decoder]: import("./decoder/DecodeOperation.js").DecodeOperation<any>;
16
16
  [$refId]?: number;
17
+ [$values]: any[];
17
18
  /**
18
- * Assign the property descriptors required to track changes on this instance.
19
- * @param instance
19
+ * Initialize change tracking on this instance.
20
+ * Field accessor descriptors (getter/setter) live on the prototype,
21
+ * installed once at class-definition time. Per-instance work is limited
22
+ * to allocating a ChangeTree and a values array.
20
23
  */
21
24
  static initialize(instance: any): void;
25
+ /**
26
+ * Decoder-side factory. Skips the user subclass ctor entirely —
27
+ * decoder-built instances are passive mirrors of server state, so any
28
+ * field initializer / ctor body work would be overwritten by the
29
+ * decoded ADDs immediately after. Assignment order matches
30
+ * {@link Schema.initialize} so V8 assigns the same hidden class
31
+ * ($changes, then $values), keeping decode-path ICs monomorphic even
32
+ * when tracked and untracked instances coexist.
33
+ *
34
+ * The `this:` constraint pins the return type to the concrete subclass
35
+ * when called as `Player.initializeForDecoder()`, not the base Schema.
36
+ */
37
+ static initializeForDecoder<T extends Schema = Schema>(this: {
38
+ prototype: T;
39
+ } & typeof Schema): T;
40
+ /**
41
+ * Check whether `type` describes a Schema *class* (a subclass
42
+ * constructor carrying `Symbol.metadata`, as installed by `@type`).
43
+ * Returns false for primitive type strings like `"number"`, descriptor
44
+ * objects like `{ map: Player }`, and Schema *instances*.
45
+ *
46
+ * For the instance-level check — "is this value a Schema instance?" —
47
+ * see {@link Schema.isSchema}.
48
+ */
22
49
  static is(type: DefinitionType): boolean;
23
50
  /**
24
- * Check if a value is an instance of Schema.
25
- * This method uses duck-typing to avoid issues with multiple @colyseus/schema versions.
51
+ * Check if a value is an *instance* of Schema. Uses duck-typing on
52
+ * `.assign` to work across multiple `@colyseus/schema` versions that
53
+ * may be loaded in the same process (e.g. bundled server types vs.
54
+ * client types in a p2p setup).
55
+ *
56
+ * For the class-level check — "is this type a Schema subclass?" —
57
+ * see {@link Schema.is}.
58
+ *
26
59
  * @param obj Value to check
27
60
  * @returns true if the value is a Schema instance
28
61
  */
29
62
  static isSchema(obj: any): obj is Schema;
30
63
  /**
31
- * Track property changes
64
+ * Track property changes. Exposed as an override point so downstream
65
+ * tools (debuggers, transparent proxies, custom instrumentation) can
66
+ * intercept per-field writes. Hot-path code in `annotations.ts` calls
67
+ * `(this.constructor as typeof Schema)[$track](...)` rather than
68
+ * `changeTree.change(...)` directly so any subclass override wins.
32
69
  */
33
70
  static [$track](changeTree: ChangeTree, index: number, operation?: OPERATION): void;
34
71
  /**
@@ -48,6 +85,12 @@ export declare class Schema<C = any> implements IRef {
48
85
  * @returns
49
86
  */
50
87
  assign<T extends Partial<this>>(props: AssignableProps<T>): this;
88
+ /**
89
+ * Metadata-driven property assignment.
90
+ * Reads tracked fields via property access (works with prototype accessors),
91
+ * then copies any remaining own properties for non-tracked fields.
92
+ */
93
+ protected static assignProps(target: any, source: any): void;
51
94
  /**
52
95
  * Restore the instance from JSON data.
53
96
  * @param jsonData JSON data to restore the instance from
@@ -61,6 +104,17 @@ export declare class Schema<C = any> implements IRef {
61
104
  * @param operation OPERATION to perform (detected automatically)
62
105
  */
63
106
  setDirty<K extends NonFunctionPropNames<this>>(property: K | number, operation?: OPERATION): void;
107
+ /** Stop recording mutations until resumeTracking() is called. */
108
+ pauseTracking(): void;
109
+ /** Re-enable automatic change tracking. */
110
+ resumeTracking(): void;
111
+ /**
112
+ * Run `fn` with change tracking paused, then resume.
113
+ * Returns the function's return value. Safe to nest.
114
+ */
115
+ untracked<T>(fn: () => T): T;
116
+ /** True while tracking is paused. */
117
+ get isTrackingPaused(): boolean;
64
118
  clone(): this;
65
119
  toJSON(this: any): ToJSON<this>;
66
120
  /**
@@ -78,7 +132,15 @@ export declare class Schema<C = any> implements IRef {
78
132
  * @returns
79
133
  */
80
134
  static debugRefIds<T extends Schema>(ref: T, showContents?: boolean, level?: number, decoder?: Decoder, keyPrefix?: string): string;
81
- static debugRefIdEncodingOrder<T extends Ref>(ref: T, changeSet?: ChangeSetName): number[];
135
+ /**
136
+ * @param changeSet
137
+ * - "changes": iterate the current-tick dirty queue (per-tick encode order)
138
+ * - "allChanges" / "allFilteredChanges" (legacy): structurally walk the
139
+ * tree in DFS preorder (matches the order in which full-sync emits
140
+ * trees). The two legacy modes differ by which side of the filter
141
+ * split they include.
142
+ */
143
+ static debugRefIdEncodingOrder<T extends Ref>(ref: T, changeSet?: "changes" | "allChanges" | "allFilteredChanges"): number[];
82
144
  static debugRefIdsFromDecoder(decoder: Decoder): string;
83
145
  /**
84
146
  * Return a string representation of the changes on a Schema instance.
@@ -89,5 +151,4 @@ export declare class Schema<C = any> implements IRef {
89
151
  * @returns
90
152
  */
91
153
  static debugChanges<T extends Ref>(instance: T, isEncodeAll?: boolean): string;
92
- static debugChangesDeep<T extends Schema>(ref: T, changeSetName?: "changes" | "allChanges" | "allFilteredChanges" | "filteredChanges"): string;
93
154
  }
@@ -3,10 +3,11 @@ import { Schema } from './Schema.js';
3
3
  import { ArraySchema } from './types/custom/ArraySchema.js';
4
4
  import { MapSchema } from './types/custom/MapSchema.js';
5
5
  import { TypeDefinition } from "./types/registry.js";
6
- import { OPERATION } from "./encoding/spec.js";
7
6
  import type { InferValueType, InferSchemaInstanceType, AssignableProps } from "./types/HelperTypes.js";
8
7
  import { CollectionSchema } from "./types/custom/CollectionSchema.js";
9
8
  import { SetSchema } from "./types/custom/SetSchema.js";
9
+ import { StreamSchema } from "./types/custom/StreamSchema.js";
10
+ import { FieldBuilder } from "./types/builder.js";
10
11
  export type RawPrimitiveType = "string" | "number" | "boolean" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float32" | "float64" | "bigint64" | "biguint64";
11
12
  export type PrimitiveType = RawPrimitiveType | typeof Schema | object;
12
13
  export type DefinitionType<T extends PrimitiveType = PrimitiveType> = T | T[] | {
@@ -14,26 +15,38 @@ export type DefinitionType<T extends PrimitiveType = PrimitiveType> = T | T[] |
14
15
  default?: InferValueType<T>;
15
16
  view?: boolean | number;
16
17
  sync?: boolean;
18
+ owned?: boolean;
17
19
  } | {
18
20
  array: T;
19
21
  default?: ArraySchema<InferValueType<T>>;
20
22
  view?: boolean | number;
21
23
  sync?: boolean;
24
+ owned?: boolean;
22
25
  } | {
23
26
  map: T;
24
27
  default?: MapSchema<InferValueType<T>>;
25
28
  view?: boolean | number;
26
29
  sync?: boolean;
30
+ owned?: boolean;
27
31
  } | {
28
32
  collection: T;
29
33
  default?: CollectionSchema<InferValueType<T>>;
30
34
  view?: boolean | number;
31
35
  sync?: boolean;
36
+ owned?: boolean;
32
37
  } | {
33
38
  set: T;
34
39
  default?: SetSchema<InferValueType<T>>;
35
40
  view?: boolean | number;
36
41
  sync?: boolean;
42
+ owned?: boolean;
43
+ } | {
44
+ stream: T;
45
+ default?: StreamSchema<InferValueType<T>>;
46
+ view?: boolean | number;
47
+ sync?: boolean;
48
+ owned?: boolean;
49
+ priority?: (view: any, element: InferValueType<T>) => number;
37
50
  };
38
51
  export type Definition = {
39
52
  [field: string]: DefinitionType;
@@ -60,10 +73,20 @@ export declare function entity(constructor: any): any;
60
73
  * ```
61
74
  */
62
75
  export declare function view<T>(tag?: number): (target: T, fieldName: string) => void;
76
+ export declare function owned<T>(target: T, field: string): void;
63
77
  export declare function unreliable<T>(target: T, field: string): void;
78
+ /**
79
+ * @transient — mark a field as not persisted to snapshots (encodeAll /
80
+ * encodeAllView). Transient fields are still emitted on per-tick patches
81
+ * (reliable or unreliable), but late-joining clients won't see them until
82
+ * the next mutation.
83
+ *
84
+ * Orthogonal to @unreliable: a field can be either, both, or neither.
85
+ */
86
+ export declare function transient<T>(target: T, field: string): void;
64
87
  export declare function type(type: DefinitionType, options?: TypeOptions): PropertyDecorator;
65
- export declare function getPropertyDescriptor(fieldCached: string, fieldIndex: number, type: DefinitionType, complexTypeKlass: TypeDefinition): {
66
- get: (this: Schema) => number | ((index: number) => any) | ((index: number) => void) | (() => Schema<any>) | (<T extends Partial<Schema<any>>>(props: AssignableProps<T>) => Schema<any>) | ((jsonData: import("./index.js").ToJSON<Schema<any>>) => Schema<any>) | (<K extends "~refId">(property: number | K, operation?: OPERATION) => void) | ((this: any) => import("./index.js").ToJSON<Schema<any>>) | (() => void);
88
+ export declare function getPropertyDescriptor(fieldName: string, fieldIndex: number, type: DefinitionType, complexTypeKlass: TypeDefinition | false): {
89
+ get: (this: Schema) => any;
67
90
  set: (this: Schema, value: any) => void;
68
91
  enumerable: boolean;
69
92
  configurable: boolean;
@@ -73,37 +96,57 @@ export declare function getPropertyDescriptor(fieldCached: string, fieldIndex: n
73
96
  * The previous `@type()` annotation should remain along with this one.
74
97
  */
75
98
  export declare function deprecated(throws?: boolean): PropertyDecorator;
76
- export declare function defineTypes(target: typeof Schema, fields: Definition, options?: TypeOptions): typeof Schema;
77
99
  type ExtractInitProps<T> = T extends {
78
100
  initialize: (...args: infer P) => void;
79
- } ? P extends readonly [] ? never : P extends readonly [infer First] ? First extends object ? First : P : P : T extends Definition ? AssignableProps<InferSchemaInstanceType<T>> : never;
101
+ } ? P extends readonly [] ? never : P extends readonly [infer First] ? First extends object ? First : P : P : AssignableProps<InferSchemaInstanceType<T>>;
80
102
  type IsInitPropsRequired<T> = T extends {
81
103
  initialize: (props: any) => void;
82
104
  } ? true : T extends {
83
105
  initialize: (...args: infer P) => void;
84
106
  } ? P extends readonly [] ? false : true : false;
85
- export interface SchemaWithExtends<T extends Definition, P extends typeof Schema> {
86
- extends: <T2 extends Definition = Definition>(fields: T2 & ThisType<InferSchemaInstanceType<T & T2>>, name?: string) => SchemaWithExtendsConstructor<T & T2, ExtractInitProps<T2>, P>;
107
+ /**
108
+ * A `schema()` field definition accepts a FieldBuilder, a Schema subclass
109
+ * (shorthand for `t.ref(Class)`), or a method (attached to the prototype).
110
+ */
111
+ export type FieldsAndMethods = Record<string, FieldBuilder<any> | (new (...args: any[]) => Schema) | Function>;
112
+ export interface SchemaWithExtends<T, P extends typeof Schema> {
113
+ extend: <T2 extends FieldsAndMethods = FieldsAndMethods>(fields: T2 & ThisType<InferSchemaInstanceType<T & T2>>, name?: string) => SchemaWithExtendsConstructor<T & T2, ExtractInitProps<T2>, P>;
87
114
  }
88
115
  /**
89
- * Get the type of the schema defined via `schema({...})` method.
116
+ * Get the type of the schema defined via `schema('Name', {...})` method.
90
117
  *
91
118
  * @example
92
- * const Entity = schema({
93
- * x: "number",
94
- * y: "number",
119
+ * const Entity = schema('Entity', {
120
+ * x: t.number(),
121
+ * y: t.number(),
95
122
  * });
96
123
  * type Entity = SchemaType<typeof Entity>;
97
124
  */
98
125
  export type SchemaType<T extends {
99
126
  '~type': any;
100
127
  }> = T['~type'];
101
- export interface SchemaWithExtendsConstructor<T extends Definition, InitProps, P extends typeof Schema> extends SchemaWithExtends<T, P> {
128
+ export interface SchemaWithExtendsConstructor<T, InitProps, P extends typeof Schema> extends SchemaWithExtends<T, P> {
102
129
  '~type': InferSchemaInstanceType<T>;
103
130
  new (...args: [InitProps] extends [never] ? [] : InitProps extends readonly any[] ? InitProps : IsInitPropsRequired<T> extends true ? [InitProps] : [InitProps?]): InferSchemaInstanceType<T> & InstanceType<P>;
104
131
  prototype: InferSchemaInstanceType<T> & InstanceType<P> & {
105
132
  initialize(...args: [InitProps] extends [never] ? [] : InitProps extends readonly any[] ? InitProps : [InitProps]): void;
106
133
  };
107
134
  }
108
- export declare function schema<T extends Record<string, DefinitionType>, P extends typeof Schema = typeof Schema>(fieldsAndMethods: T & ThisType<InferSchemaInstanceType<T>>, name?: string, inherits?: P): SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P>;
135
+ /**
136
+ * Define a Schema class declaratively.
137
+ *
138
+ * @example
139
+ * import { schema, t } from '@colyseus/schema';
140
+ *
141
+ * const Player = schema({
142
+ * hp: t.uint8().default(100),
143
+ * name: t.string().view(),
144
+ * takeDamage(n: number) { this.hp -= n; },
145
+ * }, 'Player');
146
+ *
147
+ * const Warrior = Player.extend({
148
+ * weapon: t.string(),
149
+ * }, 'Warrior');
150
+ */
151
+ export declare function schema<T extends FieldsAndMethods, P extends typeof Schema = typeof Schema>(fieldsAndMethods: T & ThisType<InferSchemaInstanceType<T>>, name?: string, inherits?: P): SchemaWithExtendsConstructor<T, ExtractInitProps<T>, P>;
109
152
  export {};
@@ -213,7 +213,53 @@ function getInheritanceTree(klass, allClasses, includeSelf = true) {
213
213
  let currentStructure;
214
214
  let currentProperty;
215
215
  let globalContext;
216
+ const BUILDER_COLLECTION_KINDS = new Set(["array", "map", "set", "collection"]);
217
+ /**
218
+ * For a t.*().chain().calls() expression, walk down to the base `t.X(...)`
219
+ * call and return its method name and first argument. Returns null if the
220
+ * node does not look like a builder chain.
221
+ */
222
+ function extractBuilderBase(node) {
223
+ let current = node;
224
+ while (true) {
225
+ const expr = current.expression;
226
+ if (!ts__namespace.isPropertyAccessExpression(expr)) {
227
+ return null;
228
+ }
229
+ if (ts__namespace.isCallExpression(expr.expression)) {
230
+ // Chained modifier, e.g. .default() / .view() — walk deeper.
231
+ current = expr.expression;
232
+ continue;
233
+ }
234
+ return {
235
+ methodName: expr.name.text,
236
+ firstArg: current.arguments[0],
237
+ };
238
+ }
239
+ }
216
240
  function defineProperty(property, initializer) {
241
+ // Builder-style: t.number(), t.array(Item), t.map(Item).view(), etc.
242
+ if (ts__namespace.isCallExpression(initializer)) {
243
+ const base = extractBuilderBase(initializer);
244
+ if (base) {
245
+ if (BUILDER_COLLECTION_KINDS.has(base.methodName)) {
246
+ property.type = base.methodName;
247
+ if (base.firstArg) {
248
+ property.childType = base.firstArg.text ?? base.firstArg.getText();
249
+ }
250
+ }
251
+ else if (base.methodName === "ref") {
252
+ property.type = "ref";
253
+ if (base.firstArg) {
254
+ property.childType = base.firstArg.text ?? base.firstArg.getText();
255
+ }
256
+ }
257
+ else {
258
+ property.type = base.methodName;
259
+ }
260
+ return;
261
+ }
262
+ }
217
263
  if (ts__namespace.isIdentifier(initializer)) {
218
264
  property.type = "ref";
219
265
  property.childType = initializer.text;
@@ -377,30 +423,6 @@ function inspectNode(node, context, decoratorName) {
377
423
  defineProperty(property, prop.initializer);
378
424
  }
379
425
  }
380
- else if (node.getText() === "defineTypes" &&
381
- (node.parent.kind === ts__namespace.SyntaxKind.CallExpression ||
382
- node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)) {
383
- /**
384
- * JavaScript source file (`.js`)
385
- * Using `defineTypes()`
386
- */
387
- const callExpression = (node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)
388
- ? node.parent.parent
389
- : node.parent;
390
- if (callExpression.kind !== ts__namespace.SyntaxKind.CallExpression) {
391
- break;
392
- }
393
- const className = callExpression.arguments[0].getText();
394
- currentStructure.name = className;
395
- const types = callExpression.arguments[1];
396
- for (let i = 0; i < types.properties.length; i++) {
397
- const prop = types.properties[i];
398
- const property = currentProperty || new Property();
399
- property.name = prop.name.escapedText;
400
- currentStructure.addProperty(property);
401
- defineProperty(property, prop.initializer);
402
- }
403
- }
404
426
  if (node.parent.kind === ts__namespace.SyntaxKind.ClassDeclaration) {
405
427
  currentStructure.name = node.getText();
406
428
  }
@@ -408,45 +430,63 @@ function inspectNode(node, context, decoratorName) {
408
430
  break;
409
431
  case ts__namespace.SyntaxKind.CallExpression:
410
432
  /**
411
- * Defining schema via `schema.schema({ ... })`
412
- * - schema.schema({})
413
- * - schema({})
414
- * - ClassName.extends({})
433
+ * Defining schema via:
434
+ * - schema({ ... })
435
+ * - schema({ ... }, 'Name')
436
+ * - schema.schema({ ... }, 'Name')
437
+ * - ParentClass.extend({ ... }, 'Name')
415
438
  */
416
- if (((node.expression?.getText() === "schema.schema" ||
417
- node.expression?.getText() === "schema") ||
418
- (node.expression?.getText().indexOf(".extends") !== -1)) &&
419
- node.arguments[0].kind === ts__namespace.SyntaxKind.ObjectLiteralExpression) {
439
+ {
420
440
  const callExpression = node;
421
- let className = callExpression.arguments[1]?.getText();
441
+ const callee = callExpression.expression?.getText?.();
442
+ if (!callee)
443
+ break;
444
+ const isSchemaCall = callee === "schema" || callee === "schema.schema";
445
+ const isExtendCall = callee.indexOf(".extend") !== -1 && !callee.endsWith(".extends");
446
+ if (!isSchemaCall && !isExtendCall)
447
+ break;
448
+ // Signature: (fields, name?)
449
+ const fieldsArg = callExpression.arguments[0];
450
+ const nameArg = callExpression.arguments[1];
451
+ if (!fieldsArg || fieldsArg.kind !== ts__namespace.SyntaxKind.ObjectLiteralExpression) {
452
+ break;
453
+ }
454
+ let className;
455
+ if (nameArg) {
456
+ if (nameArg.kind === ts__namespace.SyntaxKind.StringLiteral) {
457
+ className = nameArg.text;
458
+ }
459
+ else {
460
+ className = nameArg.getText();
461
+ }
462
+ }
422
463
  if (!className && callExpression.parent.kind === ts__namespace.SyntaxKind.VariableDeclaration) {
423
464
  className = callExpression.parent.name?.getText();
424
465
  }
425
- // skip if no className is provided
426
- if (!className) {
466
+ if (!className)
427
467
  break;
428
- }
429
468
  if (currentStructure?.name !== className) {
430
469
  currentStructure = new Class();
431
470
  context.addStructure(currentStructure);
432
471
  }
433
- if (node.expression?.getText().indexOf(".extends") !== -1) {
434
- // if it's using `.extends({})`
472
+ if (isExtendCall) {
435
473
  const extendsClass = node.expression?.expression?.escapedText;
436
- // skip if no extendsClass is provided
437
- if (!extendsClass) {
474
+ if (!extendsClass)
438
475
  break;
439
- }
440
476
  currentStructure.extends = extendsClass;
441
477
  }
442
478
  else {
443
- // if it's using `schema({})`
444
- currentStructure.extends = "Schema"; // force extends to Schema
479
+ currentStructure.extends = "Schema";
445
480
  }
446
481
  currentStructure.name = className;
447
- const types = callExpression.arguments[0];
482
+ const types = fieldsArg;
448
483
  for (let i = 0; i < types.properties.length; i++) {
449
484
  const prop = types.properties[i];
485
+ // Skip methods declared inside the fields object.
486
+ if (prop.kind === ts__namespace.SyntaxKind.MethodDeclaration)
487
+ continue;
488
+ if (!prop.initializer)
489
+ continue;
450
490
  const property = currentProperty || new Property();
451
491
  property.name = prop.name.escapedText;
452
492
  currentStructure.addProperty(property);