@colyseus/schema 5.0.6 → 5.0.8

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 (69) hide show
  1. package/build/Reflection.d.ts +53 -0
  2. package/build/Schema.d.ts +26 -1
  3. package/build/annotations.d.ts +8 -1
  4. package/build/codegen/cli.cjs +74 -64
  5. package/build/codegen/cli.cjs.map +1 -1
  6. package/build/codegen/types.d.ts +0 -1
  7. package/build/decoder/Decoder.d.ts +28 -0
  8. package/build/decoder/Resync.d.ts +61 -0
  9. package/build/encoder/ChangeTree.d.ts +15 -0
  10. package/build/encoder/Encoder.d.ts +13 -0
  11. package/build/encoder/Pool.d.ts +62 -0
  12. package/build/encoder/Root.d.ts +5 -4
  13. package/build/encoder/StateView.d.ts +12 -3
  14. package/build/index.cjs +1192 -284
  15. package/build/index.cjs.map +1 -1
  16. package/build/index.d.ts +3 -0
  17. package/build/index.js +1192 -284
  18. package/build/index.mjs +1189 -285
  19. package/build/index.mjs.map +1 -1
  20. package/build/input/InputDecoder.d.ts +9 -4
  21. package/build/input/InputEncoder.d.ts +44 -53
  22. package/build/input/index.cjs +102 -7321
  23. package/build/input/index.cjs.map +1 -1
  24. package/build/input/index.mjs +97 -7316
  25. package/build/input/index.mjs.map +1 -1
  26. package/build/types/HelperTypes.d.ts +3 -0
  27. package/build/types/builder.d.ts +57 -4
  28. package/build/types/custom/ArraySchema.d.ts +11 -1
  29. package/build/types/custom/CollectionSchema.d.ts +9 -1
  30. package/build/types/custom/MapSchema.d.ts +9 -1
  31. package/build/types/custom/SetSchema.d.ts +9 -1
  32. package/build/types/custom/StreamSchema.d.ts +2 -1
  33. package/build/types/quantize.d.ts +102 -0
  34. package/build/types/symbols.d.ts +15 -0
  35. package/package.json +8 -1
  36. package/src/Metadata.ts +34 -9
  37. package/src/Reflection.ts +49 -11
  38. package/src/Schema.ts +64 -2
  39. package/src/annotations.ts +133 -51
  40. package/src/bench_churn.ts +121 -0
  41. package/src/codegen/languages/haxe.ts +0 -16
  42. package/src/codegen/parser.ts +29 -11
  43. package/src/codegen/types.ts +45 -63
  44. package/src/decoder/DecodeOperation.ts +46 -4
  45. package/src/decoder/Decoder.ts +48 -0
  46. package/src/decoder/ReferenceTracker.ts +9 -5
  47. package/src/decoder/Resync.ts +170 -0
  48. package/src/encoder/ChangeTree.ts +59 -0
  49. package/src/encoder/Encoder.ts +53 -12
  50. package/src/encoder/Pool.ts +90 -0
  51. package/src/encoder/Root.ts +22 -32
  52. package/src/encoder/StateView.ts +101 -50
  53. package/src/encoder/changeTree/liveIteration.ts +4 -0
  54. package/src/encoder/changeTree/treeAttachment.ts +28 -24
  55. package/src/index.ts +11 -1
  56. package/src/input/InputDecoder.ts +11 -5
  57. package/src/input/InputEncoder.ts +85 -126
  58. package/src/types/HelperTypes.ts +7 -0
  59. package/src/types/TypeContext.ts +7 -0
  60. package/src/types/builder.ts +62 -5
  61. package/src/types/custom/ArraySchema.ts +70 -12
  62. package/src/types/custom/CollectionSchema.ts +36 -1
  63. package/src/types/custom/MapSchema.ts +50 -1
  64. package/src/types/custom/SetSchema.ts +36 -1
  65. package/src/types/custom/StreamSchema.ts +7 -0
  66. package/src/types/quantize.ts +173 -0
  67. package/src/types/symbols.ts +16 -0
  68. package/build/encoder/RefIdAllocator.d.ts +0 -35
  69. package/src/encoder/RefIdAllocator.ts +0 -68
@@ -45,14 +45,53 @@ interface ReflectionStatic {
45
45
  /**
46
46
  * Reflection
47
47
  */
48
+ /**
49
+ * `t.quantized()` field descriptor as it rides the reflection handshake —
50
+ * schema-typed (bit-exact float64 bounds), NOT a string grammar, so every
51
+ * language port decodes it with the schema decoder it already has.
52
+ */
53
+ export declare const QuantizedDescriptor: import("./annotations.js").SchemaWithExtendsConstructor<{
54
+ min: FieldBuilder<number, false, false>;
55
+ max: FieldBuilder<number, false, false>;
56
+ bits: FieldBuilder<number, false, false>;
57
+ mode: FieldBuilder<number, false, false>;
58
+ }, import("./index.js").BuilderInitProps<{
59
+ min: FieldBuilder<number, false, false>;
60
+ max: FieldBuilder<number, false, false>;
61
+ bits: FieldBuilder<number, false, false>;
62
+ mode: FieldBuilder<number, false, false>;
63
+ }>, typeof Schema>;
64
+ export type QuantizedDescriptor = SchemaType<typeof QuantizedDescriptor>;
48
65
  export declare const ReflectionField: import("./annotations.js").SchemaWithExtendsConstructor<{
49
66
  name: FieldBuilder<string, false, false>;
50
67
  type: FieldBuilder<string, false, false>;
51
68
  referencedType: FieldBuilder<number, false, false>;
69
+ /** Primitive child of a collection (`array`/`map`/... of "string" etc.) —
70
+ * its own slot, replacing the legacy `"array:string"` colon packing. */
71
+ childPrimitive: FieldBuilder<string, false, false>;
72
+ /** Set only on `t.quantized()` fields (`.optional()` — no auto-instantiated
73
+ * default; its absence is the "not quantized" signal on decode). */
74
+ quantized: FieldBuilder<{} & {
75
+ min?: number;
76
+ max?: number;
77
+ bits?: number;
78
+ mode?: number;
79
+ } & Schema<any> & Schema<unknown>, false, true>;
52
80
  }, import("./index.js").BuilderInitProps<{
53
81
  name: FieldBuilder<string, false, false>;
54
82
  type: FieldBuilder<string, false, false>;
55
83
  referencedType: FieldBuilder<number, false, false>;
84
+ /** Primitive child of a collection (`array`/`map`/... of "string" etc.) —
85
+ * its own slot, replacing the legacy `"array:string"` colon packing. */
86
+ childPrimitive: FieldBuilder<string, false, false>;
87
+ /** Set only on `t.quantized()` fields (`.optional()` — no auto-instantiated
88
+ * default; its absence is the "not quantized" signal on decode). */
89
+ quantized: FieldBuilder<{} & {
90
+ min?: number;
91
+ max?: number;
92
+ bits?: number;
93
+ mode?: number;
94
+ } & Schema<any> & Schema<unknown>, false, true>;
56
95
  }>, typeof Schema>;
57
96
  export type ReflectionField = SchemaType<typeof ReflectionField>;
58
97
  export declare const ReflectionType: import("./annotations.js").SchemaWithExtendsConstructor<{
@@ -61,7 +100,14 @@ export declare const ReflectionType: import("./annotations.js").SchemaWithExtend
61
100
  fields: FieldBuilder<ArraySchema<{} & {
62
101
  name?: string;
63
102
  type?: string;
103
+ quantized?: {} & {
104
+ min?: number;
105
+ max?: number;
106
+ bits?: number;
107
+ mode?: number;
108
+ } & Schema<any> & Schema<unknown>;
64
109
  referencedType?: number;
110
+ childPrimitive?: string;
65
111
  } & Schema<any> & Schema<unknown>>, true, false>;
66
112
  }, import("./index.js").BuilderInitProps<{
67
113
  id: FieldBuilder<number, false, false>;
@@ -69,7 +115,14 @@ export declare const ReflectionType: import("./annotations.js").SchemaWithExtend
69
115
  fields: FieldBuilder<ArraySchema<{} & {
70
116
  name?: string;
71
117
  type?: string;
118
+ quantized?: {} & {
119
+ min?: number;
120
+ max?: number;
121
+ bits?: number;
122
+ mode?: number;
123
+ } & Schema<any> & Schema<unknown>;
72
124
  referencedType?: number;
125
+ childPrimitive?: string;
73
126
  } & Schema<any> & Schema<unknown>>, true, false>;
74
127
  }>, typeof Schema>;
75
128
  export type ReflectionType = SchemaType<typeof ReflectionType>;
package/build/Schema.d.ts CHANGED
@@ -2,7 +2,7 @@ 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
4
  import { ChangeTree, IRef, Ref } from './encoder/ChangeTree.js';
5
- import { $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $refId, $track, $values } from './types/symbols.js';
5
+ import { $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $refId, $reset, $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';
@@ -37,6 +37,31 @@ export declare class Schema<C = any> implements IRef {
37
37
  static initializeForDecoder<T extends Schema = Schema>(this: {
38
38
  prototype: T;
39
39
  } & typeof Schema): T;
40
+ /**
41
+ * Reset a DETACHED instance to construction defaults so it can be returned
42
+ * to a {@link SchemaPool} and reused, avoiding the cost of `new`. Recurses
43
+ * into ref-type fields (child Schemas / collections).
44
+ *
45
+ * Preconditions (enforced):
46
+ * - The instance must be tracked (encoder-side), not a decoder mirror.
47
+ * - The instance must NOT be shared across multiple parents.
48
+ * - The instance must already be removed from its parent collection/field
49
+ * (so the encoder detached it: `root === undefined`).
50
+ *
51
+ * NOTE: primitive field values are NOT reset to class defaults — re-assign
52
+ * the fields you care about when you reuse the instance (standard
53
+ * object-pool discipline).
54
+ */
55
+ static reset(instance: Schema): void;
56
+ /**
57
+ * Per-instance reset primitive (the recursive worker behind
58
+ * {@link Schema.reset}). Resets ref-type children first (depth-first),
59
+ * then recycles this instance's ChangeTree and drops its `$refId` so a
60
+ * re-add is assigned a fresh refId exactly like a freshly constructed
61
+ * instance. Dropping `$refId` is what makes instance reuse
62
+ * wire-format-identical to `new T()`.
63
+ */
64
+ [$reset](): void;
40
65
  /**
41
66
  * Check whether `type` describes a Schema *class* (a subclass
42
67
  * constructor carrying `Symbol.metadata`, as installed by `@type`).
@@ -55,7 +55,14 @@ export interface TypeOptions {
55
55
  manual?: boolean;
56
56
  }
57
57
  export declare const DEFAULT_VIEW_TAG = -1;
58
- export declare function entity(constructor: any): any;
58
+ /**
59
+ * Class decorator that registers a `@type`-style Schema class with the
60
+ * TypeContext (required for reflection / cross-language codegen).
61
+ *
62
+ * @entity
63
+ * class Player extends Schema { ... }
64
+ */
65
+ export declare function entity<T extends Function>(constructor: T): T;
59
66
  /**
60
67
  * [See documentation](https://docs.colyseus.io/state/schema/)
61
68
  *
@@ -84,21 +84,11 @@ class Context {
84
84
  interfaces = [];
85
85
  enums = [];
86
86
  getStructures() {
87
+ // `isSchemaClass` already walks the full ancestor chain, so a class is
88
+ // emitted iff it (or any ancestor) descends from Schema — no need to
89
+ // re-walk parents here.
87
90
  return {
88
- classes: this.classes.filter(klass => {
89
- if (this.isSchemaClass(klass)) {
90
- return true;
91
- }
92
- else {
93
- let parentClass = klass;
94
- while (parentClass = this.getParentClass(parentClass)) {
95
- if (this.isSchemaClass(parentClass)) {
96
- return true;
97
- }
98
- }
99
- }
100
- return false;
101
- }),
91
+ classes: this.classes.filter(klass => this.isSchemaClass(klass)),
102
92
  interfaces: this.interfaces,
103
93
  enums: this.enums,
104
94
  };
@@ -118,30 +108,25 @@ class Context {
118
108
  this.enums.push(structure);
119
109
  }
120
110
  }
121
- getParentClass(klass) {
122
- return this.classes.find(c => c.name === klass.extends);
123
- }
124
111
  isSchemaClass(klass) {
125
- let isSchema = false;
126
- let currentClass = klass;
127
- while (!isSchema && currentClass) {
128
- //
129
- // TODO: ideally we should check for actual @colyseus/schema module
130
- // reference rather than arbitrary strings.
131
- //
132
- isSchema = (currentClass.extends === "Schema" ||
133
- currentClass.extends === "schema.Schema" ||
134
- currentClass.extends === "Schema.Schema");
135
- //
136
- // When extending from `schema.Schema`, it is required to
137
- // normalize as "Schema" for code generation.
138
- //
139
- if (currentClass === klass && isSchema) {
140
- klass.extends = "Schema";
112
+ // True if `klass` or any ancestor extends Schema (directly or via the
113
+ // `schema.Schema` / `Schema.Schema` aliases).
114
+ //
115
+ // TODO: ideally we should check for the actual @colyseus/schema module
116
+ // reference rather than arbitrary strings.
117
+ for (const current of [klass, ...eachAncestor(klass, this.classes)]) {
118
+ const isSchema = (current.extends === "Schema" ||
119
+ current.extends === "schema.Schema" ||
120
+ current.extends === "Schema.Schema");
121
+ if (isSchema) {
122
+ // Normalize a `schema.Schema`-style base on the queried class itself.
123
+ if (current === klass) {
124
+ klass.extends = "Schema";
125
+ }
126
+ return true;
141
127
  }
142
- currentClass = this.getParentClass(currentClass);
143
128
  }
144
- return isSchema;
129
+ return false;
145
130
  }
146
131
  }
147
132
  class Interface {
@@ -170,14 +155,11 @@ class Class {
170
155
  this.properties.push(property);
171
156
  }
172
157
  postProcessing() {
173
- /**
174
- * Ensure the proprierties `index` are correct using inheritance
175
- */
176
- let parentKlass = this;
177
- while (parentKlass &&
178
- (parentKlass = this.context.classes.find(k => k.name === parentKlass.extends))) {
158
+ // Offset each property's `index` by the field count of every ancestor,
159
+ // so indexes stay correct across inheritance.
160
+ for (const parent of eachAncestor(this, this.context.classes)) {
179
161
  this.properties.forEach(prop => {
180
- prop.index += parentKlass.properties.length;
162
+ prop.index += parent.properties.length;
181
163
  });
182
164
  }
183
165
  }
@@ -197,17 +179,31 @@ class Property {
197
179
  childType;
198
180
  deprecated;
199
181
  }
200
- function getInheritanceTree(klass, allClasses, includeSelf = true) {
201
- let currentClass = klass;
202
- let inheritanceTree = [];
203
- if (includeSelf) {
204
- inheritanceTree.push(currentClass);
205
- }
206
- while (currentClass.extends !== "Schema") {
207
- currentClass = allClasses.find(klass => klass.name == currentClass.extends);
208
- inheritanceTree.push(currentClass);
182
+ /**
183
+ * Walk `klass`'s `extends` chain, parent-first, yielding each ancestor class
184
+ * (not `klass` itself). The single safe ancestor traversal every inheritance
185
+ * query is built on: it stops at the Schema root, an unresolved base, or a
186
+ * cycle — so a malformed class graph can never spin forever. This is why the
187
+ * `seen` cycle-guard lives here and nowhere else.
188
+ */
189
+ function* eachAncestor(klass, allClasses) {
190
+ const seen = new Set([klass]);
191
+ let current = klass;
192
+ while (current.extends && current.extends !== "Schema") {
193
+ const parent = allClasses.find(c => c.name === current.extends);
194
+ if (!parent || seen.has(parent)) {
195
+ return;
196
+ }
197
+ seen.add(parent);
198
+ yield parent;
199
+ current = parent;
209
200
  }
210
- return inheritanceTree;
201
+ }
202
+ function getInheritanceTree(klass, allClasses, includeSelf = true) {
203
+ return [
204
+ ...(includeSelf ? [klass] : []),
205
+ ...eachAncestor(klass, allClasses),
206
+ ];
211
207
  }
212
208
 
213
209
  let currentStructure;
@@ -442,7 +438,7 @@ function inspectNode(node, context, decoratorName) {
442
438
  if (!callee)
443
439
  break;
444
440
  const isSchemaCall = callee === "schema" || callee === "schema.schema";
445
- const isExtendCall = callee.indexOf(".extend") !== -1 && !callee.endsWith(".extends");
441
+ const isExtendCall = callee.endsWith(".extend");
446
442
  if (!isSchemaCall && !isExtendCall)
447
443
  break;
448
444
  // Signature: (fields, name?)
@@ -460,24 +456,38 @@ function inspectNode(node, context, decoratorName) {
460
456
  className = nameArg.getText();
461
457
  }
462
458
  }
463
- if (!className && callExpression.parent.kind === ts__namespace.SyntaxKind.VariableDeclaration) {
464
- className = callExpression.parent.name?.getText();
459
+ if (!className) {
460
+ // No explicit name arg — infer it from the variable the
461
+ // result is assigned to (`const Foo = schema({...})`).
462
+ let p = callExpression.parent;
463
+ while (p !== undefined && (p.kind === ts__namespace.SyntaxKind.PropertyAccessExpression ||
464
+ p.kind === ts__namespace.SyntaxKind.CallExpression)) {
465
+ p = p.parent;
466
+ }
467
+ if (p?.kind === ts__namespace.SyntaxKind.VariableDeclaration) {
468
+ className = p.name?.getText();
469
+ }
465
470
  }
466
471
  if (!className)
467
472
  break;
468
- if (currentStructure?.name !== className) {
469
- currentStructure = new Class();
470
- context.addStructure(currentStructure);
471
- }
473
+ // Resolve the base class BEFORE registering a structure. A
474
+ // chained `schema({...}).extend({...})` has a call expression
475
+ // (not an identifier) as its `.extend` base, which can't be
476
+ // statically named — bail here rather than leave a half-formed,
477
+ // nameless Class in the context (which corrupts inheritance walks).
478
+ let extendsClass = "Schema";
472
479
  if (isExtendCall) {
473
- const extendsClass = node.expression?.expression?.escapedText;
474
- if (!extendsClass)
480
+ extendsClass = node.expression?.expression?.escapedText;
481
+ if (!extendsClass) {
482
+ console.warn(`schema-codegen: cannot resolve the base class of a chained .extend() for '${className}' — fields from that .extend({...}) are omitted.`);
475
483
  break;
476
- currentStructure.extends = extendsClass;
484
+ }
477
485
  }
478
- else {
479
- currentStructure.extends = "Schema";
486
+ if (currentStructure?.name !== className) {
487
+ currentStructure = new Class();
488
+ context.addStructure(currentStructure);
480
489
  }
490
+ currentStructure.extends = extendsClass;
481
491
  currentStructure.name = className;
482
492
  const types = fieldsArg;
483
493
  for (let i = 0; i < types.properties.length; i++) {