@colyseus/schema 5.0.8 → 5.0.10

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.
package/README.md CHANGED
@@ -20,7 +20,33 @@
20
20
 
21
21
  ## Schema definition
22
22
 
23
- `@colyseus/schema` uses type annotations to define types of synchronized properties.
23
+ Define synchronizable structures with `schema()` and `t.*` field builders:
24
+
25
+ ```typescript
26
+ import { schema, t, type SchemaType } from '@colyseus/schema';
27
+
28
+ export const Player = schema({
29
+ name: t.string(),
30
+ x: t.number(),
31
+ y: t.number(),
32
+ }, "Player");
33
+ export type Player = SchemaType<typeof Player>;
34
+
35
+ export const MyState = schema({
36
+ fieldString: t.string(),
37
+ fieldNumber: t.number(),
38
+ player: Player,
39
+ arrayOfPlayers: t.array(Player),
40
+ mapOfPlayers: t.map(Player),
41
+ }, "MyState");
42
+ export type MyState = SchemaType<typeof MyState>;
43
+ ```
44
+
45
+ `schema()` returns a real class (`instanceof` works) and runs in plain JavaScript and TypeScript alike, with no compiler configuration. The `type` aliases are TypeScript-only sugar — omit them in plain JS.
46
+
47
+ ### Decorators (still supported)
48
+
49
+ The classic `@type()` decorator style remains fully supported, and both styles produce the identical wire format:
24
50
 
25
51
  ```typescript
26
52
  import { Schema, type, ArraySchema, MapSchema } from '@colyseus/schema';
@@ -30,16 +56,30 @@ export class Player extends Schema {
30
56
  @type("number") x: number;
31
57
  @type("number") y: number;
32
58
  }
59
+ ```
60
+
61
+ We are moving away from decorators due to ecosystem compatibility issues: they depend on the legacy `experimentalDecorators` implementation and `useDefineForClassFields: false`, which conflict with modern toolchain defaults (esbuild, SWC, Vite), diverge from the TC39 decorators specification, and aren't available in plain JavaScript. See the [Decorators reference](https://docs.colyseus.io/state/schema/decorators) for setup and the full decorator documentation.
62
+
63
+ ## TypeScript support
64
+
65
+ Compatible with TypeScript **5.x**, **6.x** and **7.x**.
33
66
 
34
- export class MyState extends Schema {
35
- @type('string') fieldString: string;
36
- @type('number') fieldNumber: number;
37
- @type(Player) player: Player;
38
- @type([ Player ]) arrayOfPlayers: ArraySchema<Player>;
39
- @type({ map: Player }) mapOfPlayers: MapSchema<Player>;
67
+ The `@type()` decorator uses legacy decorators — enable them in your `tsconfig.json`:
68
+
69
+ ```json
70
+ {
71
+ "compilerOptions": {
72
+ "experimentalDecorators": true,
73
+ "useDefineForClassFields": false
74
+ }
40
75
  }
41
76
  ```
42
77
 
78
+ > **Note:** `schema-codegen` requires TypeScript 5.x or 6.x installed in your
79
+ > project — TypeScript 7's native compiler no longer ships the JS compiler API
80
+ > that codegen uses to parse your schema files. The runtime and your build are
81
+ > not affected by this.
82
+
43
83
  ## Supported types
44
84
 
45
85
  ### Primitive Types
@@ -62,28 +102,25 @@ export class MyState extends Schema {
62
102
 
63
103
  ### Declaration:
64
104
 
105
+ Each primitive type is declared through its `t.*` factory (`t.string()`, `t.uint8()`, …). The string names above remain valid as collection child types (`t.array("string")`).
106
+
65
107
  #### Primitive types (`string`, `number`, `boolean`, etc)
66
108
 
67
109
  ```typescript
68
- @type("string")
69
- name: string;
70
-
71
- @type("int32")
72
- name: number;
110
+ name: t.string(),
111
+ health: t.int32(),
73
112
  ```
74
113
 
75
114
  #### Child `Schema` structures
76
115
 
77
116
  ```typescript
78
- @type(Player)
79
- player: Player;
117
+ player: Player, // shorthand for t.ref(Player)
80
118
  ```
81
119
 
82
120
  #### Array of `Schema` structure
83
121
 
84
122
  ```typescript
85
- @type([ Player ])
86
- arrayOfPlayers: ArraySchema<Player>;
123
+ arrayOfPlayers: t.array(Player),
87
124
  ```
88
125
 
89
126
  #### Array of a primitive type
@@ -91,18 +128,14 @@ arrayOfPlayers: ArraySchema<Player>;
91
128
  You can't mix types inside arrays.
92
129
 
93
130
  ```typescript
94
- @type([ "number" ])
95
- arrayOfNumbers: ArraySchema<number>;
96
-
97
- @type([ "string" ])
98
- arrayOfStrings: ArraySchema<string>;
131
+ arrayOfNumbers: t.array("number"),
132
+ arrayOfStrings: t.array("string"),
99
133
  ```
100
134
 
101
135
  #### Map of `Schema` structure
102
136
 
103
137
  ```typescript
104
- @type({ map: Player })
105
- mapOfPlayers: MapSchema<Player>;
138
+ mapOfPlayers: t.map(Player),
106
139
  ```
107
140
 
108
141
  #### Map of a primitive type
@@ -110,11 +143,8 @@ mapOfPlayers: MapSchema<Player>;
110
143
  You can't mix primitive types inside maps.
111
144
 
112
145
  ```typescript
113
- @type({ map: "number" })
114
- mapOfNumbers: MapSchema<number>;
115
-
116
- @type({ map: "string" })
117
- mapOfStrings: MapSchema<string>;
146
+ mapOfNumbers: t.map("number"),
147
+ mapOfStrings: t.map("string"),
118
148
  ```
119
149
 
120
150
  ### Reflection
@@ -124,35 +154,38 @@ definition implementation in the server-side, and just send the encoded
124
154
  reflection to the client-side, for example:
125
155
 
126
156
  ```typescript
127
- import { Schema, type, Reflection } from "@colyseus/schema";
157
+ import { schema, t, Encoder, Reflection } from "@colyseus/schema";
128
158
 
129
- class MyState extends Schema {
130
- @type("string") currentTurn: string;
159
+ const MyState = schema({
160
+ currentTurn: t.string(),
131
161
  // ... more definitions
132
- }
162
+ }, "MyState");
133
163
 
134
- // send `encodedStateSchema` across the network
135
- const encodedStateSchema = Reflection.encode(new MyState());
164
+ // server-side: encode the schema definition itself
165
+ const encoder = new Encoder(new MyState());
166
+ const encodedStateSchema = Reflection.encode(encoder);
167
+ // ... send `encodedStateSchema` across the network
136
168
 
137
- // instantiate `MyState` in the client-side, without having its definition:
138
- const myState = Reflection.decode(encodedStateSchema);
169
+ // client-side: rebuild the state without having its definition
170
+ const decoder = Reflection.decode(encodedStateSchema);
171
+ const myState = decoder.state;
139
172
  ```
140
173
 
141
- ### `StateView` / `@view()`
174
+ ### `StateView` / `.view()`
142
175
 
143
- You can use `@view()` to filter properties that should be sent only to `StateView`'s that have access to it.
176
+ You can use the `.view()` field modifier to filter properties that should be sent only to `StateView`'s that have access to it.
144
177
 
145
178
  ```typescript
146
- import { Schema, type, view } from "@colyseus/schema";
179
+ import { schema, t } from "@colyseus/schema";
147
180
 
148
- class Player extends Schema {
149
- @view() @type("string") secret: string;
150
- @type("string") notSecret: string;
151
- }
181
+ const Player = schema({
182
+ secret: t.string().view(),
183
+ notSecret: t.string(),
184
+ }, "Player");
152
185
 
153
- class MyState extends Schema {
154
- @type({ map: Player }) players = new MapSchema<Player>();
155
- }
186
+ const MyState = schema({
187
+ players: t.map(Player),
188
+ }, "MyState");
156
189
  ```
157
190
 
158
191
  Using the `StateView`
@@ -168,7 +201,7 @@ There are 3 major features of the `Encoder` class:
168
201
 
169
202
  - Encoding the full state
170
203
  - Encoding the state changes
171
- - Encoding state with filters (properties using `@view()` tag)
204
+ - Encoding state with filters (properties tagged with `.view()`)
172
205
 
173
206
  ```typescript
174
207
  import { Encoder } from "@colyseus/schema";
@@ -193,7 +226,7 @@ const changesBuffer = encoder.encode();
193
226
 
194
227
  ### Encoding with views
195
228
 
196
- When using `@view()` and `StateView`'s, a single "full encode" must be used for multiple views. Each view also must add its own changes.
229
+ When using `.view()` and `StateView`'s, a single "full encode" must be used for multiple views. Each view also must add its own changes.
197
230
 
198
231
  ```typescript
199
232
  // shared buffer iterator
@@ -250,7 +283,7 @@ decoder.decode(encodedBytes);
250
283
 
251
284
  Backwards/forwards compatibility is possible by declaring new fields at the
252
285
  end of existing structures, and earlier declarations to not be removed, but
253
- be marked `@deprecated()` when needed.
286
+ be marked `.deprecated()` when needed.
254
287
 
255
288
  This is particularly useful for native-compiled targets, such as C#, C++,
256
289
  Haxe, etc - where the client-side can potentially not have the most
@@ -260,7 +293,7 @@ up-to-date version of the schema definitions.
260
293
  ## Limitations and best practices
261
294
 
262
295
  - 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.
296
+ - 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
297
  - 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.
265
298
  - `NaN` or `null` numbers are encoded as `0`
266
299
  - `null` strings are encoded as `""`
@@ -276,7 +309,10 @@ up-to-date version of the schema definitions.
276
309
  > If you're using JavaScript or LUA, there's no need to bother about this.
277
310
  > Interpreted programming languages are able to re-build the Schema locally through the use of `Reflection`.
278
311
 
279
- You can generate the client-side schema files based on the TypeScript schema definitions automatically.
312
+ You can generate the client-side schema files based on your server-side schema definitions automatically — both `schema()` and decorator styles are supported.
313
+
314
+ > `schema-codegen` requires TypeScript 5.x or 6.x installed in your project
315
+ > (TypeScript 7+ no longer ships the JS compiler API it uses for parsing).
280
316
 
281
317
  ```
282
318
  # C#/Unity
@@ -103,6 +103,14 @@ export declare function getPropertyDescriptor(fieldName: string, fieldIndex: num
103
103
  * The previous `@type()` annotation should remain along with this one.
104
104
  */
105
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;
106
114
  type ExtractInitProps<T> = T extends {
107
115
  initialize: (...args: infer P) => void;
108
116
  } ? P extends readonly [] ? never : P extends readonly [infer First] ? First extends object ? First : P : P : BuilderInitProps<T>;
@@ -209,6 +209,7 @@ function getInheritanceTree(klass, allClasses, includeSelf = true) {
209
209
  let currentStructure;
210
210
  let currentProperty;
211
211
  let globalContext;
212
+ let defineTypesWarned = false;
212
213
  const BUILDER_COLLECTION_KINDS = new Set(["array", "map", "set", "collection"]);
213
214
  /**
214
215
  * For a t.*().chain().calls() expression, walk down to the base `t.X(...)`
@@ -419,6 +420,34 @@ function inspectNode(node, context, decoratorName) {
419
420
  defineProperty(property, prop.initializer);
420
421
  }
421
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
+ }
422
451
  if (node.parent.kind === ts__namespace.SyntaxKind.ClassDeclaration) {
423
452
  currentStructure.name = node.getText();
424
453
  }
@@ -522,6 +551,11 @@ function inspectNode(node, context, decoratorName) {
522
551
  }
523
552
  let parsedFiles;
524
553
  function parseFiles(fileNames, decoratorName = "type", context = new Context()) {
554
+ if (typeof ts__namespace.createSourceFile !== "function") {
555
+ // typescript@7+ (native) no longer ships the JS compiler API
556
+ throw new Error(`schema-codegen requires the TypeScript compiler API, which the installed "typescript@${ts__namespace.version}" package does not provide.\n` +
557
+ `TypeScript 7+ no longer ships the JS compiler API — install typescript 5.x or 6.x (e.g. \`npm install --save-dev typescript@6\`) to use schema-codegen.`);
558
+ }
525
559
  /**
526
560
  * Re-set globalContext for each test case
527
561
  */
@@ -558,7 +592,10 @@ function parseFiles(fileNames, decoratorName = "type", context = new Context())
558
592
  break;
559
593
  }
560
594
  catch (e) {
561
- // console.log(`${fileNameAlternatives[i]} => ${e.message}`);
595
+ // only swallow fs errors (ENOENT/EISDIR) while probing alternatives
596
+ if (!e?.code) {
597
+ throw e;
598
+ }
562
599
  }
563
600
  }
564
601
  if (sourceFile) {
@@ -2336,7 +2373,7 @@ function recursiveFiles(dir) {
2336
2373
  return collect;
2337
2374
  }
2338
2375
 
2339
- function displayHelp() {
2376
+ function displayHelp(exitCode = 0) {
2340
2377
  console.log(`\nschema-codegen [path/to/Schema.ts]
2341
2378
 
2342
2379
  Usage (C#/Unity)
@@ -2355,7 +2392,7 @@ ${Object.
2355
2392
  Optional:
2356
2393
  --namespace: generate namespace on output code
2357
2394
  --decorator: custom name for @type decorator to scan for`);
2358
- process.exit();
2395
+ process.exit(exitCode);
2359
2396
  }
2360
2397
  const args = argv(process.argv.slice(2));
2361
2398
  if (args.help) {
@@ -2369,7 +2406,7 @@ for (let target in generators) {
2369
2406
  }
2370
2407
  if (!args.output) {
2371
2408
  console.error("You must provide a valid --output directory.");
2372
- displayHelp();
2409
+ displayHelp(1);
2373
2410
  }
2374
2411
  try {
2375
2412
  args.files = args._;
@@ -2384,6 +2421,6 @@ try {
2384
2421
  catch (e) {
2385
2422
  console.error(e.message);
2386
2423
  console.error(e.stack);
2387
- displayHelp();
2424
+ displayHelp(1);
2388
2425
  }
2389
2426
  //# sourceMappingURL=cli.cjs.map