@colyseus/schema 3.0.0-alpha.38 → 3.0.0-alpha.39

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 (45) hide show
  1. package/build/cjs/index.js +73 -20
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +73 -21
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +73 -20
  6. package/lib/Metadata.d.ts +2 -1
  7. package/lib/Metadata.js +14 -14
  8. package/lib/Metadata.js.map +1 -1
  9. package/lib/Reflection.d.ts +4 -3
  10. package/lib/Reflection.js +13 -3
  11. package/lib/Reflection.js.map +1 -1
  12. package/lib/Schema.d.ts +2 -2
  13. package/lib/Schema.js.map +1 -1
  14. package/lib/annotations.d.ts +29 -12
  15. package/lib/annotations.js +33 -0
  16. package/lib/annotations.js.map +1 -1
  17. package/lib/codegen/parser.js +83 -0
  18. package/lib/codegen/parser.js.map +1 -1
  19. package/lib/codegen/types.js +3 -0
  20. package/lib/codegen/types.js.map +1 -1
  21. package/lib/decoder/DecodeOperation.js +4 -0
  22. package/lib/decoder/DecodeOperation.js.map +1 -1
  23. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  24. package/lib/encoder/EncodeOperation.js +2 -2
  25. package/lib/encoder/EncodeOperation.js.map +1 -1
  26. package/lib/index.d.ts +1 -1
  27. package/lib/index.js +2 -1
  28. package/lib/index.js.map +1 -1
  29. package/lib/types/HelperTypes.d.ts +34 -2
  30. package/lib/types/HelperTypes.js.map +1 -1
  31. package/lib/types/TypeContext.js +9 -1
  32. package/lib/types/TypeContext.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/Metadata.ts +16 -17
  35. package/src/Reflection.ts +13 -5
  36. package/src/Schema.ts +2 -3
  37. package/src/annotations.ts +65 -15
  38. package/src/codegen/parser.ts +107 -0
  39. package/src/codegen/types.ts +1 -0
  40. package/src/decoder/DecodeOperation.ts +4 -0
  41. package/src/decoder/strategy/StateCallbacks.ts +1 -1
  42. package/src/encoder/EncodeOperation.ts +4 -2
  43. package/src/index.ts +1 -1
  44. package/src/types/HelperTypes.ts +54 -2
  45. package/src/types/TypeContext.ts +11 -1
package/src/Reflection.ts CHANGED
@@ -24,19 +24,27 @@ export class ReflectionType extends Schema {
24
24
  }
25
25
 
26
26
  export class Reflection extends Schema {
27
- @type([ ReflectionType ]) types: ArraySchema<ReflectionType> = new ArraySchema<ReflectionType>();
27
+ @type([ReflectionType]) types: ArraySchema<ReflectionType> = new ArraySchema<ReflectionType>();
28
+ @type("number") rootType: number;
28
29
 
29
30
  /**
30
31
  * Encodes the TypeContext of an Encoder into a buffer.
31
32
  *
32
- * @param context TypeContext instance
33
+ * @param encoder Encoder instance
33
34
  * @param it
34
35
  * @returns
35
36
  */
36
- static encode(context: TypeContext, it: Iterator = { offset: 0 }) {
37
+ static encode(encoder: Encoder, it: Iterator = { offset: 0 }) {
38
+ const context = encoder.context;
39
+
37
40
  const reflection = new Reflection();
38
41
  const reflectionEncoder = new Encoder(reflection);
39
42
 
43
+ // rootType is usually the first schema passed to the Encoder
44
+ // (unless it inherits from another schema)
45
+ const rootType = context.schemas.get(encoder.state.constructor);
46
+ if (rootType > 0) { reflection.rootType = rootType; }
47
+
40
48
  const buildType = (currentType: ReflectionType, metadata: Metadata) => {
41
49
  for (const fieldIndex in metadata) {
42
50
  const index = Number(fieldIndex);
@@ -157,7 +165,7 @@ export class Reflection extends Schema {
157
165
  Metadata.addField(metadata, fieldIndex, field.name, refType);
158
166
 
159
167
  } else {
160
- Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType } as DefinitionType);
168
+ Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
161
169
  }
162
170
 
163
171
  } else {
@@ -191,7 +199,7 @@ export class Reflection extends Schema {
191
199
  });
192
200
  });
193
201
 
194
- const state: T = new (typeContext.get(0) as unknown as any)();
202
+ const state: T = new (typeContext.get(reflection.rootType || 0) as unknown as any)();
195
203
 
196
204
  return new Decoder<T>(state, typeContext);
197
205
  }
package/src/Schema.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { OPERATION } from './encoding/spec';
2
- import { DEFAULT_VIEW_TAG, DefinitionType } from "./annotations";
2
+ import { DEFAULT_VIEW_TAG, type DefinitionType } from "./annotations";
3
3
 
4
4
  import { NonFunctionPropNames, ToJSON } from './types/HelperTypes';
5
5
 
@@ -15,7 +15,7 @@ import { getIndent } from './utils';
15
15
  /**
16
16
  * Schema encoder / decoder
17
17
  */
18
- export abstract class Schema {
18
+ export class Schema {
19
19
  static [$encoder] = encodeSchemaOperation;
20
20
  static [$decoder] = decodeSchemaOperation;
21
21
 
@@ -147,7 +147,6 @@ export abstract class Schema {
147
147
 
148
148
  toJSON () {
149
149
  const obj: unknown = {};
150
-
151
150
  const metadata = this.constructor[Symbol.metadata];
152
151
  for (const index in metadata) {
153
152
  const field = metadata[index];
@@ -9,12 +9,11 @@ import { OPERATION } from "./encoding/spec";
9
9
  import { TypeContext } from "./types/TypeContext";
10
10
  import { assertInstanceType, assertType } from "./encoding/assert";
11
11
  import type { Ref } from "./encoder/ChangeTree";
12
+ import type { DefinedSchemaType, InferValueType } from "./types/HelperTypes";
13
+ import type { CollectionSchema } from "./types/custom/CollectionSchema";
14
+ import type { SetSchema } from "./types/custom/SetSchema";
12
15
 
13
- /**
14
- * Data types
15
- */
16
- export type PrimitiveType =
17
- "string" |
16
+ export type RawPrimitiveType = "string" |
18
17
  "number" |
19
18
  "boolean" |
20
19
  "int8" |
@@ -26,16 +25,18 @@ export type PrimitiveType =
26
25
  "int64" |
27
26
  "uint64" |
28
27
  "float32" |
29
- "float64" |
30
- typeof Schema |
31
- object;
28
+ "float64";
29
+
30
+ export type PrimitiveType = RawPrimitiveType | typeof Schema | object;
32
31
 
33
- export type DefinitionType = PrimitiveType
34
- | PrimitiveType[]
35
- | { array: PrimitiveType }
36
- | { map: PrimitiveType }
37
- | { collection: PrimitiveType }
38
- | { set: PrimitiveType };
32
+ // TODO: infer "default" value type correctly.
33
+ export type DefinitionType<T extends PrimitiveType = PrimitiveType> = T
34
+ | T[]
35
+ | { type: T, default?: InferValueType<T>, view?: boolean | number }
36
+ | { array: T, default?: ArraySchema<InferValueType<T>>, view?: boolean | number }
37
+ | { map: T, default?: MapSchema<InferValueType<T>>, view?: boolean | number }
38
+ | { collection: T, default?: CollectionSchema<InferValueType<T>>, view?: boolean | number }
39
+ | { set: T, default?: SetSchema<InferValueType<T>>, view?: boolean | number };
39
40
 
40
41
  export type Definition = { [field: string]: DefinitionType };
41
42
 
@@ -479,7 +480,7 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
479
480
 
480
481
  export function defineTypes(
481
482
  target: typeof Schema,
482
- fields: { [property: string]: DefinitionType },
483
+ fields: Definition,
483
484
  options?: TypeOptions
484
485
  ) {
485
486
  for (let field in fields) {
@@ -487,3 +488,52 @@ export function defineTypes(
487
488
  }
488
489
  return target;
489
490
  }
491
+
492
+ export interface SchemaWithExtends<T extends Definition, P extends typeof Schema> extends DefinedSchemaType<T, P> {
493
+ extends: <T2 extends Definition>(
494
+ fields: T2,
495
+ name?: string
496
+ ) => SchemaWithExtends<T & T2, typeof this>;
497
+ }
498
+
499
+ export function schema<T extends Definition, P extends typeof Schema = typeof Schema>(
500
+ fields: T,
501
+ name?: string,
502
+ inherits: P = Schema as P
503
+ ): SchemaWithExtends<T, P> {
504
+ const defaultValues: any = {};
505
+ const viewTagFields: any = {};
506
+
507
+ for (let fieldName in fields) {
508
+ const field = fields[fieldName] as DefinitionType;
509
+ if (typeof (field) === "object") {
510
+ if (field['default'] !== undefined) {
511
+ defaultValues[fieldName] = field['default'];
512
+ }
513
+ if (field['view'] !== undefined) {
514
+ viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
515
+ ? DEFAULT_VIEW_TAG
516
+ : field['view'];
517
+ }
518
+ }
519
+ }
520
+
521
+ const klass = Metadata.setFields(class extends inherits {
522
+ constructor (...args: any[]) {
523
+ args[0] = Object.assign({}, defaultValues, args[0]);
524
+ super(...args);
525
+ }
526
+ }, fields) as SchemaWithExtends<T, P>;
527
+
528
+ for (let fieldName in viewTagFields) {
529
+ view(viewTagFields[fieldName])(klass.prototype, fieldName);
530
+ }
531
+
532
+ if (name) {
533
+ Object.defineProperty(klass, "name", { value: name });
534
+ }
535
+
536
+ klass.extends = (fields, name) => schema(fields, name, klass);
537
+
538
+ return klass;
539
+ }
@@ -148,6 +148,49 @@ function inspectNode(node: ts.Node, context: Context, decoratorName: string) {
148
148
  defineProperty(property, typeArgument);
149
149
  }
150
150
 
151
+ } else if (
152
+ node.getText() === "setFields" &&
153
+ (
154
+ node.parent.kind === ts.SyntaxKind.CallExpression ||
155
+ node.parent.kind === ts.SyntaxKind.PropertyAccessExpression
156
+ )
157
+ ) {
158
+ /**
159
+ * Metadata.setFields(klassName, { ... })
160
+ */
161
+ const callExpression = (node.parent.kind === ts.SyntaxKind.PropertyAccessExpression)
162
+ ? node.parent.parent as ts.CallExpression
163
+ : node.parent as ts.CallExpression;
164
+
165
+ if (callExpression.kind !== ts.SyntaxKind.CallExpression) {
166
+ break;
167
+ }
168
+
169
+ const classNameNode = callExpression.arguments[0];
170
+ const className = ts.isClassExpression(classNameNode)
171
+ ? classNameNode.name?.escapedText.toString()
172
+ : classNameNode.getText();
173
+
174
+ // skip if no className is provided
175
+ if (!className) { break; }
176
+
177
+ if (currentStructure.name !== className) {
178
+ currentStructure = new Class();
179
+ }
180
+ context.addStructure(currentStructure);
181
+ (currentStructure as Class).extends = "Schema"; // force extends to Schema
182
+ currentStructure.name = className;
183
+
184
+ const types = callExpression.arguments[1] as any;
185
+ for (let i = 0; i < types.properties.length; i++) {
186
+ const prop = types.properties[i];
187
+
188
+ const property = currentProperty || new Property();
189
+ property.name = prop.name.escapedText;
190
+
191
+ currentStructure.addProperty(property);
192
+ defineProperty(property, prop.initializer);
193
+ }
151
194
 
152
195
  } else if (
153
196
  node.getText() === "defineTypes" &&
@@ -192,6 +235,70 @@ function inspectNode(node: ts.Node, context: Context, decoratorName: string) {
192
235
 
193
236
  break;
194
237
 
238
+ case ts.SyntaxKind.CallExpression:
239
+ /**
240
+ * Defining schema via `schema.schema({ ... })`
241
+ * - schema.schema({})
242
+ * - schema({})
243
+ * - ClassName.extends({})
244
+ */
245
+ if (
246
+ (
247
+ (
248
+ (node as ts.CallExpression).expression?.getText() === "schema.schema" ||
249
+ (node as ts.CallExpression).expression?.getText() === "schema"
250
+ ) ||
251
+ (
252
+ (node as ts.CallExpression).expression?.getText().indexOf(".extends") !== -1
253
+ )
254
+ ) &&
255
+ (node as ts.CallExpression).arguments[0].kind === ts.SyntaxKind.ObjectLiteralExpression
256
+ ) {
257
+ const callExpression = node as ts.CallExpression;
258
+
259
+ let className = callExpression.arguments[1]?.getText();
260
+
261
+ if (!className && callExpression.parent.kind === ts.SyntaxKind.VariableDeclaration) {
262
+ className = (callExpression.parent as ts.VariableDeclaration).name?.getText();
263
+ }
264
+
265
+ // skip if no className is provided
266
+ if (!className) { break; }
267
+
268
+ if (currentStructure.name !== className) {
269
+ currentStructure = new Class();
270
+ context.addStructure(currentStructure);
271
+ }
272
+
273
+ if ((node as ts.CallExpression).expression?.getText().indexOf(".extends") !== -1) {
274
+ // if it's using `.extends({})`
275
+ const extendsClass = (node as any).expression?.expression?.escapedText;
276
+
277
+ // skip if no extendsClass is provided
278
+ if (!extendsClass) { break; }
279
+ (currentStructure as Class).extends = extendsClass;
280
+
281
+ } else {
282
+ // if it's using `schema({})`
283
+ (currentStructure as Class).extends = "Schema"; // force extends to Schema
284
+ }
285
+
286
+ currentStructure.name = className;
287
+
288
+ const types = callExpression.arguments[0] as any;
289
+ for (let i = 0; i < types.properties.length; i++) {
290
+ const prop = types.properties[i];
291
+
292
+ const property = currentProperty || new Property();
293
+ property.name = prop.name.escapedText;
294
+
295
+ currentStructure.addProperty(property);
296
+ defineProperty(property, prop.initializer);
297
+ }
298
+ }
299
+
300
+ break;
301
+
195
302
  case ts.SyntaxKind.EnumMember:
196
303
  if (currentStructure instanceof Enum) {
197
304
  const initializer = (node as any).initializer?.text;
@@ -39,6 +39,7 @@ export class Context {
39
39
  }
40
40
 
41
41
  addStructure(structure: IStructure) {
42
+ if (structure.context === this) { return; } // skip if already added.
42
43
  structure.context = this;
43
44
 
44
45
  if (structure instanceof Class) {
@@ -327,6 +327,10 @@ export const decodeArray: DecodeOperation = function (
327
327
  (ref as ArraySchema).clear();
328
328
  return;
329
329
 
330
+ } else if (operation === OPERATION.REVERSE) {
331
+ (ref as ArraySchema).reverse();
332
+ return;
333
+
330
334
  } else if (operation === OPERATION.DELETE_BY_REFID) {
331
335
  // TODO: refactor here, try to follow same flow as below
332
336
  const refId = decode.number(bytes, it);
@@ -4,8 +4,8 @@ import { Ref } from "../../encoder/ChangeTree";
4
4
  import { Decoder } from "../Decoder";
5
5
  import { DataChange } from "../DecodeOperation";
6
6
  import { OPERATION } from "../../encoding/spec";
7
- import { DefinitionType } from "../../annotations";
8
7
  import { Schema } from "../../Schema";
8
+ import type { DefinitionType } from "../../annotations";
9
9
  import type { CollectionSchema } from "../../types/custom/CollectionSchema";
10
10
 
11
11
  //
@@ -79,7 +79,6 @@ export const encodeSchemaOperation: EncodeOperation = function (
79
79
  }
80
80
 
81
81
  const ref = changeTree.ref;
82
- // const metadata: Metadata = ref.constructor[Symbol.metadata];
83
82
  const field = metadata[index];
84
83
 
85
84
  // TODO: inline this function call small performance gain
@@ -200,7 +199,10 @@ export const encodeArray: EncodeOperation = function (
200
199
  bytes[it.offset++] = operation & 255;
201
200
 
202
201
  // custom operations
203
- if (operation === OPERATION.CLEAR) {
202
+ if (
203
+ operation === OPERATION.CLEAR ||
204
+ operation === OPERATION.REVERSE
205
+ ) {
204
206
  return;
205
207
  }
206
208
 
package/src/index.ts CHANGED
@@ -41,7 +41,7 @@ export {
41
41
 
42
42
  // Annotations, Metadata and TypeContext
43
43
  export { Metadata } from "./Metadata";
44
- export { type, deprecated, defineTypes, view, } from "./annotations";
44
+ export { type, deprecated, defineTypes, view, schema, type SchemaWithExtends, } from "./annotations";
45
45
  export { TypeContext } from "./types/TypeContext";
46
46
 
47
47
  // Annotation types
@@ -1,5 +1,11 @@
1
- import { ArraySchema } from "./custom/ArraySchema";
2
- import { MapSchema } from "./custom/MapSchema";
1
+ import type { Definition, DefinitionType, PrimitiveType, RawPrimitiveType } from "../annotations";
2
+ import type { Schema } from "../Schema";
3
+ import type { ArraySchema } from "./custom/ArraySchema";
4
+ import type { CollectionSchema } from "./custom/CollectionSchema";
5
+ import type { MapSchema } from "./custom/MapSchema";
6
+ import type { SetSchema } from "./custom/SetSchema";
7
+
8
+ export type Constructor<T = {}> = new (...args: any[]) => T;
3
9
 
4
10
  export interface Collection<K = any, V = any, IT = V> {
5
11
  [Symbol.iterator](): IterableIterator<IT>;
@@ -7,6 +13,52 @@ export interface Collection<K = any, V = any, IT = V> {
7
13
  entries(): IterableIterator<[K, V]>;
8
14
  }
9
15
 
16
+ export type InferValueType<T extends DefinitionType> =
17
+ T extends "string" ? string
18
+ : T extends "number" ? number
19
+ : T extends "int8" ? number
20
+ : T extends "uint8" ? number
21
+ : T extends "int16" ? number
22
+ : T extends "uint16" ? number
23
+ : T extends "int32" ? number
24
+ : T extends "uint32" ? number
25
+ : T extends "int64" ? number
26
+ : T extends "uint64" ? number
27
+ : T extends "float32" ? number
28
+ : T extends "float64" ? number
29
+ : T extends "boolean" ? boolean
30
+
31
+ : T extends { type: infer ChildType extends Constructor } ? InstanceType<ChildType>
32
+ : T extends { type: infer ChildType extends PrimitiveType } ? ChildType
33
+
34
+ : T extends Array<infer ChildType extends Constructor> ? InstanceType<ChildType>[]
35
+ : T extends Array<infer ChildType extends RawPrimitiveType> ? ChildType[]
36
+
37
+ : T extends { array: infer ChildType extends Constructor } ? InstanceType<ChildType>[]
38
+ : T extends { array: infer ChildType extends PrimitiveType } ? ChildType[]
39
+
40
+ : T extends { map: infer ChildType extends Constructor } ? MapSchema<InstanceType<ChildType>>
41
+ : T extends { map: infer ChildType extends PrimitiveType } ? MapSchema<ChildType>
42
+
43
+ : T extends { set: infer ChildType extends Constructor } ? SetSchema<InstanceType<ChildType>>
44
+ : T extends { set: infer ChildType extends PrimitiveType } ? SetSchema<ChildType>
45
+
46
+ : T extends { collection: infer ChildType extends Constructor } ? CollectionSchema<InstanceType<ChildType>>
47
+ : T extends { collection: infer ChildType extends PrimitiveType } ? CollectionSchema<ChildType>
48
+
49
+ : T extends Constructor ? InstanceType<T>
50
+ : T extends PrimitiveType ? T
51
+
52
+ : never;
53
+
54
+ export type InferSchemaInstanceType<T extends Definition> = {
55
+ [K in keyof T]: InferValueType<T[K]>
56
+ } & Schema;
57
+
58
+ export type DefinedSchemaType<T extends Definition, P extends typeof Schema> = {
59
+ new (): InferSchemaInstanceType<T> & InstanceType<P>;
60
+ } & typeof Schema;
61
+
10
62
  export type NonFunctionProps<T> = Omit<T, {
11
63
  [K in keyof T]: T[K] extends Function ? K : never;
12
64
  }[keyof T]>;
@@ -53,7 +53,7 @@ export class TypeContext {
53
53
  // Workaround to allow using an empty Schema (with no `@type()` fields)
54
54
  //
55
55
  if (schema[Symbol.metadata] === undefined) {
56
- Metadata.init(schema);
56
+ Metadata.initialize(schema);
57
57
  }
58
58
 
59
59
  this.schemas.set(schema, typeid);
@@ -74,6 +74,16 @@ export class TypeContext {
74
74
  this.discoverTypes(child, parentIndex, parentFieldViewTag);
75
75
  });
76
76
 
77
+ // add parent classes
78
+ let parent: any = klass;
79
+ while (
80
+ (parent = Object.getPrototypeOf(parent)) &&
81
+ parent !== Schema && // stop at root (Schema)
82
+ parent !== Function.prototype // stop at root (non-Schema)
83
+ ) {
84
+ this.discoverTypes(parent);
85
+ }
86
+
77
87
  const metadata: Metadata = (klass[Symbol.metadata] ??= {});
78
88
 
79
89
  // if any schema/field has filters, mark "context" as having filters.