@colyseus/schema 5.0.2 → 5.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "description": "Binary state serializer with delta encoding for games",
5
5
  "type": "module",
6
6
  "bin": {
package/src/Metadata.ts CHANGED
@@ -329,6 +329,49 @@ export const Metadata = {
329
329
  return metadata?.[$streamPriorities]?.[index];
330
330
  },
331
331
 
332
+ /**
333
+ * Install a single field with full encoder wiring: accessor descriptor
334
+ * on the prototype + `metadata[$encoders]` slot for primitives. Shared
335
+ * between `Metadata.setFields` (build path) and
336
+ * `Reflection.makeEncodable` (Reflection upgrade path).
337
+ */
338
+ defineField(
339
+ target: any,
340
+ metadata: any,
341
+ fieldIndex: number,
342
+ fieldName: string,
343
+ type: DefinitionType,
344
+ ) {
345
+ const normalized = getNormalizedType(type);
346
+ const { complexTypeKlass, childType } = resolveFieldType(normalized);
347
+
348
+ Metadata.addField(
349
+ metadata,
350
+ fieldIndex,
351
+ fieldName,
352
+ normalized,
353
+ getPropertyDescriptor(fieldName, fieldIndex, childType, complexTypeKlass),
354
+ );
355
+
356
+ // Install accessor descriptor on the prototype (once per class field).
357
+ if (metadata[$descriptors][fieldName]) {
358
+ Object.defineProperty(target.prototype, fieldName, metadata[$descriptors][fieldName]);
359
+ }
360
+
361
+ // Pre-compute encoder function for primitive types.
362
+ if (typeof normalized === "string") {
363
+ if (!metadata[$encoders]) {
364
+ Object.defineProperty(metadata, $encoders, {
365
+ value: [],
366
+ enumerable: false,
367
+ configurable: true,
368
+ writable: true,
369
+ });
370
+ }
371
+ metadata[$encoders][fieldIndex] = (encode as any)[normalized];
372
+ }
373
+ },
374
+
332
375
  setFields<T extends { new (...args: any[]): InstanceType<T> } = any>(target: T, fields: { [field in keyof InstanceType<T>]?: DefinitionType }) {
333
376
  // for inheritance support
334
377
  const constructor = target.prototype.constructor;
@@ -364,28 +407,7 @@ export const Metadata = {
364
407
  }
365
408
 
366
409
  for (const field in fields) {
367
- const type = getNormalizedType(fields[field]);
368
-
369
- const { complexTypeKlass, childType } = resolveFieldType(type);
370
-
371
- Metadata.addField(
372
- metadata,
373
- fieldIndex,
374
- field,
375
- type,
376
- getPropertyDescriptor(field, fieldIndex, childType, complexTypeKlass)
377
- );
378
-
379
- // Install accessor descriptor on the prototype (once per class field).
380
- if (metadata[$descriptors][field]) {
381
- Object.defineProperty(target.prototype, field, metadata[$descriptors][field]);
382
- }
383
-
384
- // Pre-compute encoder function for primitive types.
385
- if (typeof type === "string") {
386
- metadata[$encoders][fieldIndex] = (encode as any)[type];
387
- }
388
-
410
+ Metadata.defineField(constructor, metadata, fieldIndex, field, fields[field] as DefinitionType);
389
411
  fieldIndex++;
390
412
  }
391
413
 
package/src/Reflection.ts CHANGED
@@ -7,6 +7,7 @@ import { Decoder } from "./decoder/Decoder.js";
7
7
  import { Schema } from "./Schema.js";
8
8
  import { t, FieldBuilder } from "./types/builder.js";
9
9
  import { ArraySchema } from "./types/custom/ArraySchema.js";
10
+ import { $encodeDescriptor, $numFields } from "./types/symbols.js";
10
11
 
11
12
  /**
12
13
  * Static methods available on Reflection
@@ -29,6 +30,23 @@ interface ReflectionStatic {
29
30
  * @returns Decoder instance
30
31
  */
31
32
  decode: <T extends Schema = Schema>(bytes: Uint8Array, it?: Iterator) => Decoder<T>;
33
+
34
+ /**
35
+ * Upgrade a class produced by `Reflection.decode` so its instances
36
+ * can be used as encode sources (for `InputEncoder` or `Encoder`).
37
+ *
38
+ * `Reflection.decode` reconstructs classes with decoder-only field
39
+ * slots — `inst.x = 7` lands as a direct own property and bypasses
40
+ * the change-tracking + `$values` plumbing that encoders rely on.
41
+ * Calling `makeEncodable(ctor)` installs the same prototype accessor
42
+ * descriptors and `metadata[$encoders]` lookup table that the
43
+ * `schema(...)` / `@type` builders install at class-definition time.
44
+ *
45
+ * Idempotent. Pay-as-you-go: callers that only decode never invoke
46
+ * this and pay nothing extra. Must be called BEFORE any instance of
47
+ * the class is constructed and assigned to.
48
+ */
49
+ makeEncodable: (ctor: typeof Schema) => typeof Schema;
32
50
  }
33
51
 
34
52
  /**
@@ -249,4 +267,31 @@ Reflection.decode = function <T extends Schema = Schema>(bytes: Uint8Array, it?:
249
267
  const state: T = new (typeContext.get(reflection.rootType || 0) as unknown as any)();
250
268
 
251
269
  return new Decoder<T>(state, typeContext);
252
- }
270
+ }
271
+
272
+ Reflection.makeEncodable = function (ctor: typeof Schema): typeof Schema {
273
+ const metadata: any = (ctor as any)[Symbol.metadata];
274
+ if (!metadata) return ctor;
275
+
276
+ const numFields = metadata[$numFields];
277
+ if (numFields === undefined) return ctor;
278
+
279
+ // Walk every field index across the inheritance chain. Repeat calls
280
+ // are cheap: defineField overwrites the same descriptor and re-stamps
281
+ // the same `metadata[$encoders]` slot (idempotent).
282
+ for (let i = 0; i <= numFields; i++) {
283
+ const field = metadata[i];
284
+ if (!field) continue;
285
+ Metadata.defineField(ctor, metadata, i, field.name, field.type);
286
+ }
287
+
288
+ // Invalidate any cached encode descriptor — `getEncodeDescriptor`
289
+ // memoizes on the constructor. If something already constructed it
290
+ // (e.g. a prior `InputEncoder(...)` call that threw), drop the stale
291
+ // entry so the next read sees the upgraded metadata.
292
+ if (Object.prototype.hasOwnProperty.call(ctor, $encodeDescriptor)) {
293
+ delete (ctor as any)[$encodeDescriptor];
294
+ }
295
+
296
+ return ctor;
297
+ };