@colyseus/schema 5.0.9 → 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
@@ -551,6 +551,11 @@ function inspectNode(node, context, decoratorName) {
551
551
  }
552
552
  let parsedFiles;
553
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
+ }
554
559
  /**
555
560
  * Re-set globalContext for each test case
556
561
  */
@@ -587,7 +592,10 @@ function parseFiles(fileNames, decoratorName = "type", context = new Context())
587
592
  break;
588
593
  }
589
594
  catch (e) {
590
- // console.log(`${fileNameAlternatives[i]} => ${e.message}`);
595
+ // only swallow fs errors (ENOENT/EISDIR) while probing alternatives
596
+ if (!e?.code) {
597
+ throw e;
598
+ }
591
599
  }
592
600
  }
593
601
  if (sourceFile) {
@@ -2365,7 +2373,7 @@ function recursiveFiles(dir) {
2365
2373
  return collect;
2366
2374
  }
2367
2375
 
2368
- function displayHelp() {
2376
+ function displayHelp(exitCode = 0) {
2369
2377
  console.log(`\nschema-codegen [path/to/Schema.ts]
2370
2378
 
2371
2379
  Usage (C#/Unity)
@@ -2384,7 +2392,7 @@ ${Object.
2384
2392
  Optional:
2385
2393
  --namespace: generate namespace on output code
2386
2394
  --decorator: custom name for @type decorator to scan for`);
2387
- process.exit();
2395
+ process.exit(exitCode);
2388
2396
  }
2389
2397
  const args = argv(process.argv.slice(2));
2390
2398
  if (args.help) {
@@ -2398,7 +2406,7 @@ for (let target in generators) {
2398
2406
  }
2399
2407
  if (!args.output) {
2400
2408
  console.error("You must provide a valid --output directory.");
2401
- displayHelp();
2409
+ displayHelp(1);
2402
2410
  }
2403
2411
  try {
2404
2412
  args.files = args._;
@@ -2413,6 +2421,6 @@ try {
2413
2421
  catch (e) {
2414
2422
  console.error(e.message);
2415
2423
  console.error(e.stack);
2416
- displayHelp();
2424
+ displayHelp(1);
2417
2425
  }
2418
2426
  //# sourceMappingURL=cli.cjs.map