@colyseus/schema 3.0.0-alpha.37 → 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 +142 -32
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +142 -33
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +142 -32
  6. package/lib/Metadata.d.ts +3 -2
  7. package/lib/Metadata.js +57 -15
  8. package/lib/Metadata.js.map +1 -1
  9. package/lib/Reflection.d.ts +4 -3
  10. package/lib/Reflection.js +36 -11
  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 +36 -3
  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 +62 -16
  35. package/src/Reflection.ts +43 -16
  36. package/src/Schema.ts +2 -3
  37. package/src/annotations.ts +68 -18
  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
@@ -1 +1 @@
1
- {"version":3,"file":"HelperTypes.js","sourceRoot":"","sources":["../../src/types/HelperTypes.ts"],"names":[],"mappings":"","sourcesContent":["import { ArraySchema } from \"./custom/ArraySchema\";\nimport { MapSchema } from \"./custom/MapSchema\";\n\nexport interface Collection<K = any, V = any, IT = V> {\n [Symbol.iterator](): IterableIterator<IT>;\n forEach(callback: Function);\n entries(): IterableIterator<[K, V]>;\n}\n\nexport type NonFunctionProps<T> = Omit<T, {\n [K in keyof T]: T[K] extends Function ? K : never;\n}[keyof T]>;\n\nexport type NonFunctionPropNames<T> = {\n [K in keyof T]: T[K] extends Function ? never : K\n}[keyof T];\n\nexport type NonFunctionNonPrimitivePropNames<T> = {\n [K in keyof T]: T[K] extends Function\n ? never\n : T[K] extends number | string | boolean\n ? never\n : K\n}[keyof T];\n\nexport type ToJSON<T> = NonFunctionProps<{\n [K in keyof T]: T[K] extends MapSchema<infer U>\n ? Record<string, U>\n : T[K] extends Map<string, infer U>\n ? Record<string, U>\n : T[K] extends ArraySchema<infer U>\n ? U[]\n : T[K]\n}>;"]}
1
+ {"version":3,"file":"HelperTypes.js","sourceRoot":"","sources":["../../src/types/HelperTypes.ts"],"names":[],"mappings":"","sourcesContent":["import type { Definition, DefinitionType, PrimitiveType, RawPrimitiveType } from \"../annotations\";\nimport type { Schema } from \"../Schema\";\nimport type { ArraySchema } from \"./custom/ArraySchema\";\nimport type { CollectionSchema } from \"./custom/CollectionSchema\";\nimport type { MapSchema } from \"./custom/MapSchema\";\nimport type { SetSchema } from \"./custom/SetSchema\";\n\nexport type Constructor<T = {}> = new (...args: any[]) => T;\n\nexport interface Collection<K = any, V = any, IT = V> {\n [Symbol.iterator](): IterableIterator<IT>;\n forEach(callback: Function);\n entries(): IterableIterator<[K, V]>;\n}\n\nexport type InferValueType<T extends DefinitionType> =\n T extends \"string\" ? string\n : T extends \"number\" ? number\n : T extends \"int8\" ? number\n : T extends \"uint8\" ? number\n : T extends \"int16\" ? number\n : T extends \"uint16\" ? number\n : T extends \"int32\" ? number\n : T extends \"uint32\" ? number\n : T extends \"int64\" ? number\n : T extends \"uint64\" ? number\n : T extends \"float32\" ? number\n : T extends \"float64\" ? number\n : T extends \"boolean\" ? boolean\n\n : T extends { type: infer ChildType extends Constructor } ? InstanceType<ChildType>\n : T extends { type: infer ChildType extends PrimitiveType } ? ChildType\n\n : T extends Array<infer ChildType extends Constructor> ? InstanceType<ChildType>[]\n : T extends Array<infer ChildType extends RawPrimitiveType> ? ChildType[]\n\n : T extends { array: infer ChildType extends Constructor } ? InstanceType<ChildType>[]\n : T extends { array: infer ChildType extends PrimitiveType } ? ChildType[]\n\n : T extends { map: infer ChildType extends Constructor } ? MapSchema<InstanceType<ChildType>>\n : T extends { map: infer ChildType extends PrimitiveType } ? MapSchema<ChildType>\n\n : T extends { set: infer ChildType extends Constructor } ? SetSchema<InstanceType<ChildType>>\n : T extends { set: infer ChildType extends PrimitiveType } ? SetSchema<ChildType>\n\n : T extends { collection: infer ChildType extends Constructor } ? CollectionSchema<InstanceType<ChildType>>\n : T extends { collection: infer ChildType extends PrimitiveType } ? CollectionSchema<ChildType>\n\n : T extends Constructor ? InstanceType<T>\n : T extends PrimitiveType ? T\n\n : never;\n\nexport type InferSchemaInstanceType<T extends Definition> = {\n [K in keyof T]: InferValueType<T[K]>\n} & Schema;\n\nexport type DefinedSchemaType<T extends Definition, P extends typeof Schema> = {\n new (): InferSchemaInstanceType<T> & InstanceType<P>;\n} & typeof Schema;\n\nexport type NonFunctionProps<T> = Omit<T, {\n [K in keyof T]: T[K] extends Function ? K : never;\n}[keyof T]>;\n\nexport type NonFunctionPropNames<T> = {\n [K in keyof T]: T[K] extends Function ? never : K\n}[keyof T];\n\nexport type NonFunctionNonPrimitivePropNames<T> = {\n [K in keyof T]: T[K] extends Function\n ? never\n : T[K] extends number | string | boolean\n ? never\n : K\n}[keyof T];\n\nexport type ToJSON<T> = NonFunctionProps<{\n [K in keyof T]: T[K] extends MapSchema<infer U>\n ? Record<string, U>\n : T[K] extends Map<string, infer U>\n ? Record<string, U>\n : T[K] extends ArraySchema<infer U>\n ? U[]\n : T[K]\n}>;"]}
@@ -46,7 +46,7 @@ class TypeContext {
46
46
  // Workaround to allow using an empty Schema (with no `@type()` fields)
47
47
  //
48
48
  if (schema[Symbol.metadata] === undefined) {
49
- Metadata_1.Metadata.init(schema);
49
+ Metadata_1.Metadata.initialize(schema);
50
50
  }
51
51
  this.schemas.set(schema, typeid);
52
52
  return true;
@@ -62,6 +62,14 @@ class TypeContext {
62
62
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
63
63
  this.discoverTypes(child, parentIndex, parentFieldViewTag);
64
64
  });
65
+ // add parent classes
66
+ let parent = klass;
67
+ while ((parent = Object.getPrototypeOf(parent)) &&
68
+ parent !== Schema_1.Schema && // stop at root (Schema)
69
+ parent !== Function.prototype // stop at root (non-Schema)
70
+ ) {
71
+ this.discoverTypes(parent);
72
+ }
65
73
  const metadata = (klass[Symbol.metadata] ??= {});
66
74
  // if any schema/field has filters, mark "context" as having filters.
67
75
  if (metadata[symbols_1.$viewFieldIndexes]) {
@@ -1 +1 @@
1
- {"version":3,"file":"TypeContext.js","sourceRoot":"","sources":["../../src/types/TypeContext.ts"],"names":[],"mappings":"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","sourcesContent":["import { Metadata } from \"../Metadata\";\nimport { Schema } from \"../Schema\";\nimport { $viewFieldIndexes } from \"./symbols\";\n\nexport class TypeContext {\n types: { [id: number]: typeof Schema; } = {};\n schemas = new Map<typeof Schema, number>();\n\n hasFilters: boolean = false;\n parentFiltered: {[typeIdAndParentIndex: string]: boolean} = {};\n\n /**\n * For inheritance support\n * Keeps track of which classes extends which. (parent -> children)\n */\n static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();\n\n static register(target: typeof Schema) {\n const parent = Object.getPrototypeOf(target);\n if (parent !== Schema) {\n let inherits = TypeContext.inheritedTypes.get(parent);\n if (!inherits) {\n inherits = new Set<typeof Schema>();\n TypeContext.inheritedTypes.set(parent, inherits);\n }\n inherits.add(target);\n }\n }\n\n constructor(rootClass?: typeof Schema) {\n if (rootClass) {\n this.discoverTypes(rootClass);\n }\n }\n\n has(schema: typeof Schema) {\n return this.schemas.has(schema);\n }\n\n get(typeid: number) {\n return this.types[typeid];\n }\n\n add(schema: typeof Schema, typeid: number = this.schemas.size) {\n // skip if already registered\n if (this.schemas.has(schema)) {\n return false;\n }\n\n this.types[typeid] = schema;\n\n //\n // Workaround to allow using an empty Schema (with no `@type()` fields)\n //\n if (schema[Symbol.metadata] === undefined) {\n Metadata.init(schema);\n }\n\n this.schemas.set(schema, typeid);\n return true;\n }\n\n getTypeId(klass: typeof Schema) {\n return this.schemas.get(klass);\n }\n\n private discoverTypes(klass: typeof Schema, parentIndex?: number, parentFieldViewTag?: number) {\n if (!this.add(klass)) {\n return;\n }\n\n // add classes inherited from this base class\n TypeContext.inheritedTypes.get(klass)?.forEach((child) => {\n this.discoverTypes(child, parentIndex, parentFieldViewTag);\n });\n\n const metadata: Metadata = (klass[Symbol.metadata] ??= {});\n\n // if any schema/field has filters, mark \"context\" as having filters.\n if (metadata[$viewFieldIndexes]) {\n this.hasFilters = true;\n }\n\n if (parentFieldViewTag !== undefined) {\n this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;\n }\n\n for (const fieldIndex in metadata) {\n const index = fieldIndex as any as number;\n\n const fieldType = metadata[index].type;\n const viewTag = metadata[index].tag;\n\n if (typeof (fieldType) === \"string\") {\n continue;\n }\n\n if (Array.isArray(fieldType)) {\n const type = fieldType[0];\n\n // skip primitive types\n if (type === \"string\") {\n continue;\n }\n\n this.discoverTypes(type as typeof Schema, index, viewTag);\n\n } else if (typeof (fieldType) === \"function\") {\n this.discoverTypes(fieldType as typeof Schema, viewTag);\n\n } else {\n const type = Object.values(fieldType)[0];\n\n // skip primitive types\n if (typeof (type) === \"string\") {\n continue;\n }\n\n this.discoverTypes(type as typeof Schema, index, viewTag);\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"TypeContext.js","sourceRoot":"","sources":["../../src/types/TypeContext.ts"],"names":[],"mappings":"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","sourcesContent":["import { Metadata } from \"../Metadata\";\nimport { Schema } from \"../Schema\";\nimport { $viewFieldIndexes } from \"./symbols\";\n\nexport class TypeContext {\n types: { [id: number]: typeof Schema; } = {};\n schemas = new Map<typeof Schema, number>();\n\n hasFilters: boolean = false;\n parentFiltered: {[typeIdAndParentIndex: string]: boolean} = {};\n\n /**\n * For inheritance support\n * Keeps track of which classes extends which. (parent -> children)\n */\n static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();\n\n static register(target: typeof Schema) {\n const parent = Object.getPrototypeOf(target);\n if (parent !== Schema) {\n let inherits = TypeContext.inheritedTypes.get(parent);\n if (!inherits) {\n inherits = new Set<typeof Schema>();\n TypeContext.inheritedTypes.set(parent, inherits);\n }\n inherits.add(target);\n }\n }\n\n constructor(rootClass?: typeof Schema) {\n if (rootClass) {\n this.discoverTypes(rootClass);\n }\n }\n\n has(schema: typeof Schema) {\n return this.schemas.has(schema);\n }\n\n get(typeid: number) {\n return this.types[typeid];\n }\n\n add(schema: typeof Schema, typeid: number = this.schemas.size) {\n // skip if already registered\n if (this.schemas.has(schema)) {\n return false;\n }\n\n this.types[typeid] = schema;\n\n //\n // Workaround to allow using an empty Schema (with no `@type()` fields)\n //\n if (schema[Symbol.metadata] === undefined) {\n Metadata.initialize(schema);\n }\n\n this.schemas.set(schema, typeid);\n return true;\n }\n\n getTypeId(klass: typeof Schema) {\n return this.schemas.get(klass);\n }\n\n private discoverTypes(klass: typeof Schema, parentIndex?: number, parentFieldViewTag?: number) {\n if (!this.add(klass)) {\n return;\n }\n\n // add classes inherited from this base class\n TypeContext.inheritedTypes.get(klass)?.forEach((child) => {\n this.discoverTypes(child, parentIndex, parentFieldViewTag);\n });\n\n // add parent classes\n let parent: any = klass;\n while (\n (parent = Object.getPrototypeOf(parent)) &&\n parent !== Schema && // stop at root (Schema)\n parent !== Function.prototype // stop at root (non-Schema)\n ) {\n this.discoverTypes(parent);\n }\n\n const metadata: Metadata = (klass[Symbol.metadata] ??= {});\n\n // if any schema/field has filters, mark \"context\" as having filters.\n if (metadata[$viewFieldIndexes]) {\n this.hasFilters = true;\n }\n\n if (parentFieldViewTag !== undefined) {\n this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;\n }\n\n for (const fieldIndex in metadata) {\n const index = fieldIndex as any as number;\n\n const fieldType = metadata[index].type;\n const viewTag = metadata[index].tag;\n\n if (typeof (fieldType) === \"string\") {\n continue;\n }\n\n if (Array.isArray(fieldType)) {\n const type = fieldType[0];\n\n // skip primitive types\n if (type === \"string\") {\n continue;\n }\n\n this.discoverTypes(type as typeof Schema, index, viewTag);\n\n } else if (typeof (fieldType) === \"function\") {\n this.discoverTypes(fieldType as typeof Schema, viewTag);\n\n } else {\n const type = Object.values(fieldType)[0];\n\n // skip primitive types\n if (typeof (type) === \"string\") {\n continue;\n }\n\n this.discoverTypes(type as typeof Schema, index, viewTag);\n }\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "3.0.0-alpha.37",
3
+ "version": "3.0.0-alpha.39",
4
4
  "description": "Binary state serializer with delta encoding for games",
5
5
  "bin": {
6
6
  "schema-codegen": "./bin/schema-codegen",
package/src/Metadata.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { getPropertyDescriptor, type DefinitionType } from "./annotations";
1
+ import { Definition, DefinitionType, getPropertyDescriptor } from "./annotations";
2
2
  import { Schema } from "./Schema";
3
3
  import { getType } from "./types/registry";
4
4
  import { $decoder, $descriptors, $encoder, $fieldIndexesByViewTag, $numFields, $refTypeFieldIndexes, $track, $viewFieldIndexes } from "./types/symbols";
@@ -22,6 +22,14 @@ export type Metadata =
22
22
  { [field: string]: number; } & // field name => field metadata
23
23
  { [$descriptors]: { [field: string]: PropertyDescriptor } } // property descriptors
24
24
 
25
+ export function getNormalizedType(type: DefinitionType): DefinitionType {
26
+ return (Array.isArray(type))
27
+ ? { array: type[0] }
28
+ : (typeof(type['type']) !== "undefined")
29
+ ? type['type']
30
+ : type;
31
+ }
32
+
25
33
  export const Metadata = {
26
34
 
27
35
  addField(metadata: any, index: number, name: string, type: DefinitionType, descriptor?: PropertyDescriptor) {
@@ -32,16 +40,18 @@ export const Metadata = {
32
40
  metadata[index] = Object.assign(
33
41
  metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
34
42
  {
35
- type: (Array.isArray(type))
36
- ? { array: type[0] }
37
- : type,
43
+ type: getNormalizedType(type),
38
44
  index,
39
45
  name,
40
46
  }
41
47
  );
42
48
 
43
49
  // create "descriptors" map
44
- metadata[$descriptors] ??= {};
50
+ Object.defineProperty(metadata, $descriptors, {
51
+ value: metadata[$descriptors] || {},
52
+ enumerable: false,
53
+ configurable: true,
54
+ });
45
55
 
46
56
  if (descriptor) {
47
57
  // for encoder
@@ -128,7 +138,7 @@ export const Metadata = {
128
138
 
129
139
  const parentClass = Object.getPrototypeOf(constructor);
130
140
  const parentMetadata = parentClass && parentClass[Symbol.metadata];
131
- const metadata = Metadata.initialize(constructor, parentMetadata);
141
+ const metadata = Metadata.initialize(constructor);
132
142
 
133
143
  // Use Schema's methods if not defined in the class
134
144
  if (!constructor[$track]) { constructor[$track] = Schema[$track]; }
@@ -147,18 +157,23 @@ export const Metadata = {
147
157
 
148
158
  for (const field in fields) {
149
159
  const type = fields[field];
160
+ const normalizedType = getNormalizedType(type);
150
161
 
151
162
  // FIXME: this code is duplicated from @type() annotation
152
163
  const complexTypeKlass = (Array.isArray(type))
153
164
  ? getType("array")
154
165
  : (typeof(Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
155
166
 
167
+ const childType = (complexTypeKlass)
168
+ ? Object.values(type)[0]
169
+ : normalizedType;
170
+
156
171
  Metadata.addField(
157
172
  metadata,
158
173
  fieldIndex,
159
174
  field,
160
175
  type,
161
- getPropertyDescriptor(`_${field}`, fieldIndex, type, complexTypeKlass)
176
+ getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass)
162
177
  );
163
178
 
164
179
  fieldIndex++;
@@ -185,28 +200,59 @@ export const Metadata = {
185
200
  });
186
201
  },
187
202
 
188
- initialize(constructor: any, parentMetadata?: Metadata) {
203
+ initialize(constructor: any) {
204
+ const parentClass = Object.getPrototypeOf(constructor);
205
+ const parentMetadata: Metadata = parentClass[Symbol.metadata];
206
+
189
207
  let metadata: Metadata = constructor[Symbol.metadata] ?? Object.create(null);
190
208
 
191
209
  // make sure inherited classes have their own metadata object.
192
- if (constructor[Symbol.metadata] === parentMetadata) {
210
+ if (parentClass !== Schema && metadata === parentMetadata) {
193
211
  metadata = Object.create(null);
194
212
 
195
213
  if (parentMetadata) {
214
+ //
196
215
  // assign parent metadata to current
197
- Object.assign(metadata, parentMetadata);
216
+ //
217
+ Object.setPrototypeOf(metadata, parentMetadata);
218
+
219
+ // $numFields
220
+ Object.defineProperty(metadata, $numFields, {
221
+ value: parentMetadata[$numFields],
222
+ enumerable: false,
223
+ configurable: true,
224
+ writable: true,
225
+ });
198
226
 
199
- for (let i = 0; i <= parentMetadata[$numFields]; i++) {
200
- const fieldName = parentMetadata[i].name;
201
- Object.defineProperty(metadata, fieldName, {
202
- value: parentMetadata[fieldName],
227
+ // $viewFieldIndexes / $fieldIndexesByViewTag
228
+ if (parentMetadata[$viewFieldIndexes] !== undefined) {
229
+ Object.defineProperty(metadata, $viewFieldIndexes, {
230
+ value: [...parentMetadata[$viewFieldIndexes]],
231
+ enumerable: false,
232
+ configurable: true,
233
+ writable: true,
234
+ });
235
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
236
+ value: { ...parentMetadata[$fieldIndexesByViewTag] },
203
237
  enumerable: false,
204
238
  configurable: true,
239
+ writable: true,
205
240
  });
206
241
  }
207
242
 
208
- Object.defineProperty(metadata, $numFields, {
209
- value: parentMetadata[$numFields],
243
+ // $refTypeFieldIndexes
244
+ if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
245
+ Object.defineProperty(metadata, $refTypeFieldIndexes, {
246
+ value: [...parentMetadata[$refTypeFieldIndexes]],
247
+ enumerable: false,
248
+ configurable: true,
249
+ writable: true,
250
+ });
251
+ }
252
+
253
+ // $descriptors
254
+ Object.defineProperty(metadata, $descriptors, {
255
+ value: { ...parentMetadata[$descriptors] },
210
256
  enumerable: false,
211
257
  configurable: true,
212
258
  writable: true,
package/src/Reflection.ts CHANGED
@@ -6,6 +6,7 @@ import { Iterator } from "./encoding/decode";
6
6
  import { Encoder } from "./encoder/Encoder";
7
7
  import { Decoder } from "./decoder/Decoder";
8
8
  import { Schema } from "./Schema";
9
+ import { $numFields } from "./types/symbols";
9
10
 
10
11
  /**
11
12
  * Reflection
@@ -23,19 +24,27 @@ export class ReflectionType extends Schema {
23
24
  }
24
25
 
25
26
  export class Reflection extends Schema {
26
- @type([ ReflectionType ]) types: ArraySchema<ReflectionType> = new ArraySchema<ReflectionType>();
27
+ @type([ReflectionType]) types: ArraySchema<ReflectionType> = new ArraySchema<ReflectionType>();
28
+ @type("number") rootType: number;
27
29
 
28
30
  /**
29
31
  * Encodes the TypeContext of an Encoder into a buffer.
30
32
  *
31
- * @param context TypeContext instance
33
+ * @param encoder Encoder instance
32
34
  * @param it
33
35
  * @returns
34
36
  */
35
- static encode(context: TypeContext, it: Iterator = { offset: 0 }) {
37
+ static encode(encoder: Encoder, it: Iterator = { offset: 0 }) {
38
+ const context = encoder.context;
39
+
36
40
  const reflection = new Reflection();
37
41
  const reflectionEncoder = new Encoder(reflection);
38
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
+
39
48
  const buildType = (currentType: ReflectionType, metadata: Metadata) => {
40
49
  for (const fieldIndex in metadata) {
41
50
  const index = Number(fieldIndex);
@@ -127,24 +136,17 @@ export class Reflection extends Schema {
127
136
  const parentClass: typeof Schema = typeContext.get(reflectionType.extendsId) ?? Schema;
128
137
  const schema: typeof Schema = class _ extends parentClass {};
129
138
 
130
- const parentMetadata = parentClass[Symbol.metadata];
131
-
132
139
  // register for inheritance support
133
140
  TypeContext.register(schema);
134
141
 
135
- // for inheritance support
136
- Metadata.initialize(schema, parentMetadata);
142
+ // // for inheritance support
143
+ // Metadata.initialize(schema);
137
144
 
138
145
  typeContext.add(schema, reflectionType.id);
139
146
  }, {});
140
147
 
141
- // 2nd pass, set fields
142
- reflection.types.forEach((reflectionType) => {
143
- const schemaType = typeContext.get(reflectionType.id);
144
- const metadata = schemaType[Symbol.metadata];
145
-
146
- const parentFieldIndex = 0;
147
-
148
+ // define fields
149
+ const addFields = (metadata: Metadata, reflectionType: ReflectionType, parentFieldIndex: number) => {
148
150
  reflectionType.fields.forEach((field, i) => {
149
151
  const fieldIndex = parentFieldIndex + i;
150
152
 
@@ -163,16 +165,41 @@ export class Reflection extends Schema {
163
165
  Metadata.addField(metadata, fieldIndex, field.name, refType);
164
166
 
165
167
  } else {
166
- Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType } as DefinitionType);
168
+ Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
167
169
  }
168
170
 
169
171
  } else {
170
172
  Metadata.addField(metadata, fieldIndex, field.name, field.type as PrimitiveType);
171
173
  }
172
174
  });
175
+ };
176
+
177
+ // 2nd pass, set fields
178
+ reflection.types.forEach((reflectionType) => {
179
+ const schema = typeContext.get(reflectionType.id);
180
+
181
+ // for inheritance support
182
+ const metadata = Metadata.initialize(schema);
183
+
184
+ const inheritedTypes: ReflectionType[] = [];
185
+
186
+ let parentType: ReflectionType = reflectionType;
187
+ do {
188
+ inheritedTypes.push(parentType);
189
+ parentType = reflection.types.find((t) => t.id === parentType.extendsId);
190
+ } while (parentType);
191
+
192
+ let parentFieldIndex = 0;
193
+
194
+ inheritedTypes.reverse().forEach((reflectionType) => {
195
+ // add fields from all inherited classes
196
+ // TODO: refactor this to avoid adding fields from parent classes
197
+ addFields(metadata, reflectionType, parentFieldIndex);
198
+ parentFieldIndex += reflectionType.fields.length;
199
+ });
173
200
  });
174
201
 
175
- 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)();
176
203
 
177
204
  return new Decoder<T>(state, typeContext);
178
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
 
@@ -286,15 +287,15 @@ export function type (
286
287
  TypeContext.register(constructor);
287
288
 
288
289
  const parentClass = Object.getPrototypeOf(constructor);
289
- const parentMetadata = parentClass && parentClass[Symbol.metadata];
290
- const metadata = Metadata.initialize(constructor, parentMetadata);
290
+ const parentMetadata = parentClass[Symbol.metadata];
291
+ const metadata = Metadata.initialize(constructor);
291
292
 
292
293
  let fieldIndex: number = metadata[field];
293
294
 
294
295
  /**
295
296
  * skip if descriptor already exists for this field (`@deprecated()`)
296
297
  */
297
- if (metadata[fieldIndex]) {
298
+ if (metadata[fieldIndex] !== undefined) {
298
299
  if (metadata[fieldIndex].deprecated) {
299
300
  // do not create accessors for deprecated properties.
300
301
  return;
@@ -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