@colyseus/schema 5.0.6 → 5.0.9

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 +16 -1
  4. package/build/codegen/cli.cjs +103 -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 +1211 -284
  15. package/build/index.cjs.map +1 -1
  16. package/build/index.d.ts +4 -1
  17. package/build/index.js +1211 -284
  18. package/build/index.mjs +1207 -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 +157 -51
  40. package/src/bench_churn.ts +121 -0
  41. package/src/codegen/languages/haxe.ts +0 -16
  42. package/src/codegen/parser.ts +69 -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 +12 -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
  *
@@ -96,6 +103,14 @@ export declare function getPropertyDescriptor(fieldName: string, fieldIndex: num
96
103
  * The previous `@type()` annotation should remain along with this one.
97
104
  */
98
105
  export declare function deprecated(throws?: boolean): PropertyDecorator;
106
+ /**
107
+ * Adds synchronizable fields to an existing `Schema` subclass — the pre-5.0
108
+ * helper for plain JavaScript users.
109
+ *
110
+ * @deprecated Use `schema()` with `t.*` field builders instead:
111
+ * https://docs.colyseus.io/state/schema
112
+ */
113
+ export declare function defineTypes(target: typeof Schema, fields: Definition, options?: TypeOptions): typeof Schema;
99
114
  type ExtractInitProps<T> = T extends {
100
115
  initialize: (...args: infer P) => void;
101
116
  } ? P extends readonly [] ? never : P extends readonly [infer First] ? First extends object ? First : P : P : BuilderInitProps<T>;
@@ -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,22 +179,37 @@ 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;
214
210
  let currentProperty;
215
211
  let globalContext;
212
+ let defineTypesWarned = false;
216
213
  const BUILDER_COLLECTION_KINDS = new Set(["array", "map", "set", "collection"]);
217
214
  /**
218
215
  * For a t.*().chain().calls() expression, walk down to the base `t.X(...)`
@@ -423,6 +420,34 @@ function inspectNode(node, context, decoratorName) {
423
420
  defineProperty(property, prop.initializer);
424
421
  }
425
422
  }
423
+ else if (node.getText() === "defineTypes" &&
424
+ (node.parent.kind === ts__namespace.SyntaxKind.CallExpression ||
425
+ node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)) {
426
+ /**
427
+ * JavaScript source file (`.js`)
428
+ * Using `defineTypes()` (deprecated)
429
+ */
430
+ const callExpression = (node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)
431
+ ? node.parent.parent
432
+ : node.parent;
433
+ if (callExpression.kind !== ts__namespace.SyntaxKind.CallExpression) {
434
+ break;
435
+ }
436
+ if (!defineTypesWarned) {
437
+ defineTypesWarned = true;
438
+ console.warn("schema-codegen: defineTypes() is deprecated and will be removed in a future release. Use schema() with t.* field builders instead → https://docs.colyseus.io/state/schema");
439
+ }
440
+ const className = callExpression.arguments[0].getText();
441
+ currentStructure.name = className;
442
+ const types = callExpression.arguments[1];
443
+ for (let i = 0; i < types.properties.length; i++) {
444
+ const prop = types.properties[i];
445
+ const property = currentProperty || new Property();
446
+ property.name = prop.name.escapedText;
447
+ currentStructure.addProperty(property);
448
+ defineProperty(property, prop.initializer);
449
+ }
450
+ }
426
451
  if (node.parent.kind === ts__namespace.SyntaxKind.ClassDeclaration) {
427
452
  currentStructure.name = node.getText();
428
453
  }
@@ -442,7 +467,7 @@ function inspectNode(node, context, decoratorName) {
442
467
  if (!callee)
443
468
  break;
444
469
  const isSchemaCall = callee === "schema" || callee === "schema.schema";
445
- const isExtendCall = callee.indexOf(".extend") !== -1 && !callee.endsWith(".extends");
470
+ const isExtendCall = callee.endsWith(".extend");
446
471
  if (!isSchemaCall && !isExtendCall)
447
472
  break;
448
473
  // Signature: (fields, name?)
@@ -460,24 +485,38 @@ function inspectNode(node, context, decoratorName) {
460
485
  className = nameArg.getText();
461
486
  }
462
487
  }
463
- if (!className && callExpression.parent.kind === ts__namespace.SyntaxKind.VariableDeclaration) {
464
- className = callExpression.parent.name?.getText();
488
+ if (!className) {
489
+ // No explicit name arg — infer it from the variable the
490
+ // result is assigned to (`const Foo = schema({...})`).
491
+ let p = callExpression.parent;
492
+ while (p !== undefined && (p.kind === ts__namespace.SyntaxKind.PropertyAccessExpression ||
493
+ p.kind === ts__namespace.SyntaxKind.CallExpression)) {
494
+ p = p.parent;
495
+ }
496
+ if (p?.kind === ts__namespace.SyntaxKind.VariableDeclaration) {
497
+ className = p.name?.getText();
498
+ }
465
499
  }
466
500
  if (!className)
467
501
  break;
468
- if (currentStructure?.name !== className) {
469
- currentStructure = new Class();
470
- context.addStructure(currentStructure);
471
- }
502
+ // Resolve the base class BEFORE registering a structure. A
503
+ // chained `schema({...}).extend({...})` has a call expression
504
+ // (not an identifier) as its `.extend` base, which can't be
505
+ // statically named — bail here rather than leave a half-formed,
506
+ // nameless Class in the context (which corrupts inheritance walks).
507
+ let extendsClass = "Schema";
472
508
  if (isExtendCall) {
473
- const extendsClass = node.expression?.expression?.escapedText;
474
- if (!extendsClass)
509
+ extendsClass = node.expression?.expression?.escapedText;
510
+ if (!extendsClass) {
511
+ console.warn(`schema-codegen: cannot resolve the base class of a chained .extend() for '${className}' — fields from that .extend({...}) are omitted.`);
475
512
  break;
476
- currentStructure.extends = extendsClass;
513
+ }
477
514
  }
478
- else {
479
- currentStructure.extends = "Schema";
515
+ if (currentStructure?.name !== className) {
516
+ currentStructure = new Class();
517
+ context.addStructure(currentStructure);
480
518
  }
519
+ currentStructure.extends = extendsClass;
481
520
  currentStructure.name = className;
482
521
  const types = fieldsArg;
483
522
  for (let i = 0; i < types.properties.length; i++) {