@colyseus/schema 3.0.0-alpha.40 → 3.0.0-alpha.42

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 (50) hide show
  1. package/README.md +19 -3
  2. package/build/cjs/index.js +1216 -1221
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +1216 -1222
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +1216 -1221
  7. package/lib/annotations.d.ts +1 -1
  8. package/lib/annotations.js.map +1 -1
  9. package/lib/codegen/languages/csharp.js +1 -44
  10. package/lib/codegen/languages/csharp.js.map +1 -1
  11. package/lib/decoder/DecodeOperation.d.ts +3 -3
  12. package/lib/decoder/DecodeOperation.js +9 -9
  13. package/lib/decoder/DecodeOperation.js.map +1 -1
  14. package/lib/decoder/Decoder.js +5 -5
  15. package/lib/decoder/Decoder.js.map +1 -1
  16. package/lib/encoder/EncodeOperation.js +7 -7
  17. package/lib/encoder/EncodeOperation.js.map +1 -1
  18. package/lib/encoder/Encoder.js +4 -4
  19. package/lib/encoder/Encoder.js.map +1 -1
  20. package/lib/encoding/assert.d.ts +5 -5
  21. package/lib/encoding/assert.js +8 -0
  22. package/lib/encoding/assert.js.map +1 -1
  23. package/lib/encoding/decode.d.ts +36 -19
  24. package/lib/encoding/decode.js +53 -82
  25. package/lib/encoding/decode.js.map +1 -1
  26. package/lib/encoding/encode.d.ts +36 -17
  27. package/lib/encoding/encode.js +49 -39
  28. package/lib/encoding/encode.js.map +1 -1
  29. package/lib/encoding/spec.d.ts +2 -4
  30. package/lib/encoding/spec.js +0 -2
  31. package/lib/encoding/spec.js.map +1 -1
  32. package/lib/index.d.ts +4 -6
  33. package/lib/index.js +6 -5
  34. package/lib/index.js.map +1 -1
  35. package/lib/types/registry.d.ts +8 -1
  36. package/lib/types/registry.js +20 -2
  37. package/lib/types/registry.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/annotations.ts +3 -1
  40. package/src/codegen/languages/csharp.ts +1 -47
  41. package/src/decoder/DecodeOperation.ts +6 -6
  42. package/src/decoder/Decoder.ts +3 -3
  43. package/src/encoder/EncodeOperation.ts +1 -1
  44. package/src/encoder/Encoder.ts +1 -1
  45. package/src/encoding/assert.ts +13 -5
  46. package/src/encoding/decode.ts +73 -93
  47. package/src/encoding/encode.ts +64 -36
  48. package/src/encoding/spec.ts +2 -5
  49. package/src/index.ts +4 -6
  50. package/src/types/registry.ts +22 -3
@@ -18,8 +18,6 @@ var OPERATION;
18
18
  /**
19
19
  * ArraySchema operations
20
20
  */
21
- OPERATION[OPERATION["PUSH"] = 11] = "PUSH";
22
- OPERATION[OPERATION["UNSHIFT"] = 12] = "UNSHIFT";
23
21
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
24
22
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
25
23
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
@@ -61,1055 +59,1296 @@ const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
61
59
  const $viewFieldIndexes = "$__viewFieldIndexes";
62
60
  const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
63
61
 
64
- const registeredTypes = {};
65
- const identifiers = new Map();
66
- function registerType(identifier, definition) {
67
- identifiers.set(definition.constructor, identifier);
68
- registeredTypes[identifier] = definition;
69
- }
70
- function getType(identifier) {
71
- return registeredTypes[identifier];
62
+ /**
63
+ * Copyright (c) 2018 Endel Dreyer
64
+ * Copyright (c) 2014 Ion Drive Software Ltd.
65
+ *
66
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
67
+ * of this software and associated documentation files (the "Software"), to deal
68
+ * in the Software without restriction, including without limitation the rights
69
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
70
+ * copies of the Software, and to permit persons to whom the Software is
71
+ * furnished to do so, subject to the following conditions:
72
+ *
73
+ * The above copyright notice and this permission notice shall be included in all
74
+ * copies or substantial portions of the Software.
75
+ *
76
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
77
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
78
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
79
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
80
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
81
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
82
+ * SOFTWARE
83
+ */
84
+ /**
85
+ * msgpack implementation highly based on notepack.io
86
+ * https://github.com/darrachequesne/notepack
87
+ */
88
+ let textEncoder;
89
+ // @ts-ignore
90
+ try {
91
+ textEncoder = new TextEncoder();
72
92
  }
73
-
74
- class TypeContext {
75
- /**
76
- * For inheritance support
77
- * Keeps track of which classes extends which. (parent -> children)
78
- */
79
- static { this.inheritedTypes = new Map(); }
80
- static register(target) {
81
- const parent = Object.getPrototypeOf(target);
82
- if (parent !== Schema) {
83
- let inherits = TypeContext.inheritedTypes.get(parent);
84
- if (!inherits) {
85
- inherits = new Set();
86
- TypeContext.inheritedTypes.set(parent, inherits);
87
- }
88
- inherits.add(target);
89
- }
90
- }
91
- constructor(rootClass) {
92
- this.types = {};
93
- this.schemas = new Map();
94
- this.hasFilters = false;
95
- this.parentFiltered = {};
96
- if (rootClass) {
97
- this.discoverTypes(rootClass);
98
- }
99
- }
100
- has(schema) {
101
- return this.schemas.has(schema);
102
- }
103
- get(typeid) {
104
- return this.types[typeid];
105
- }
106
- add(schema, typeid = this.schemas.size) {
107
- // skip if already registered
108
- if (this.schemas.has(schema)) {
109
- return false;
110
- }
111
- this.types[typeid] = schema;
112
- //
113
- // Workaround to allow using an empty Schema (with no `@type()` fields)
114
- //
115
- if (schema[Symbol.metadata] === undefined) {
116
- Metadata.initialize(schema);
117
- }
118
- this.schemas.set(schema, typeid);
119
- return true;
120
- }
121
- getTypeId(klass) {
122
- return this.schemas.get(klass);
123
- }
124
- discoverTypes(klass, parentIndex, parentFieldViewTag) {
125
- if (!this.add(klass)) {
126
- return;
127
- }
128
- // add classes inherited from this base class
129
- TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
130
- this.discoverTypes(child, parentIndex, parentFieldViewTag);
131
- });
132
- // add parent classes
133
- let parent = klass;
134
- while ((parent = Object.getPrototypeOf(parent)) &&
135
- parent !== Schema && // stop at root (Schema)
136
- parent !== Function.prototype // stop at root (non-Schema)
137
- ) {
138
- this.discoverTypes(parent);
139
- }
140
- const metadata = (klass[Symbol.metadata] ??= {});
141
- // if any schema/field has filters, mark "context" as having filters.
142
- if (metadata[$viewFieldIndexes]) {
143
- this.hasFilters = true;
144
- }
145
- if (parentFieldViewTag !== undefined) {
146
- this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
147
- }
148
- for (const fieldIndex in metadata) {
149
- const index = fieldIndex;
150
- const fieldType = metadata[index].type;
151
- const viewTag = metadata[index].tag;
152
- if (typeof (fieldType) === "string") {
153
- continue;
93
+ catch (e) { }
94
+ const _convoBuffer$1 = new ArrayBuffer(8);
95
+ const _int32$1 = new Int32Array(_convoBuffer$1);
96
+ const _float32$1 = new Float32Array(_convoBuffer$1);
97
+ const _float64$1 = new Float64Array(_convoBuffer$1);
98
+ const _int64$1 = new BigInt64Array(_convoBuffer$1);
99
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
100
+ const utf8Length = (hasBufferByteLength)
101
+ ? Buffer.byteLength // node
102
+ : function (str, _) {
103
+ var c = 0, length = 0;
104
+ for (var i = 0, l = str.length; i < l; i++) {
105
+ c = str.charCodeAt(i);
106
+ if (c < 0x80) {
107
+ length += 1;
154
108
  }
155
- if (Array.isArray(fieldType)) {
156
- const type = fieldType[0];
157
- // skip primitive types
158
- if (type === "string") {
159
- continue;
160
- }
161
- this.discoverTypes(type, index, viewTag);
109
+ else if (c < 0x800) {
110
+ length += 2;
162
111
  }
163
- else if (typeof (fieldType) === "function") {
164
- this.discoverTypes(fieldType, viewTag);
112
+ else if (c < 0xd800 || c >= 0xe000) {
113
+ length += 3;
165
114
  }
166
115
  else {
167
- const type = Object.values(fieldType)[0];
168
- // skip primitive types
169
- if (typeof (type) === "string") {
170
- continue;
171
- }
172
- this.discoverTypes(type, index, viewTag);
116
+ i++;
117
+ length += 4;
173
118
  }
174
119
  }
175
- }
176
- }
177
-
178
- function getNormalizedType(type) {
179
- return (Array.isArray(type))
180
- ? { array: type[0] }
181
- : (typeof (type['type']) !== "undefined")
182
- ? type['type']
183
- : type;
184
- }
185
- const Metadata = {
186
- addField(metadata, index, name, type, descriptor) {
187
- if (index > 64) {
188
- throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
120
+ return length;
121
+ };
122
+ function utf8Write(view, str, it) {
123
+ var c = 0;
124
+ for (var i = 0, l = str.length; i < l; i++) {
125
+ c = str.charCodeAt(i);
126
+ if (c < 0x80) {
127
+ view[it.offset++] = c;
189
128
  }
190
- metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
191
- {
192
- type: getNormalizedType(type),
193
- index,
194
- name,
195
- });
196
- // create "descriptors" map
197
- Object.defineProperty(metadata, $descriptors, {
198
- value: metadata[$descriptors] || {},
199
- enumerable: false,
200
- configurable: true,
201
- });
202
- if (descriptor) {
203
- // for encoder
204
- metadata[$descriptors][name] = descriptor;
205
- metadata[$descriptors][`_${name}`] = {
206
- value: undefined,
207
- writable: true,
208
- enumerable: false,
209
- configurable: true,
210
- };
129
+ else if (c < 0x800) {
130
+ view[it.offset] = 0xc0 | (c >> 6);
131
+ view[it.offset + 1] = 0x80 | (c & 0x3f);
132
+ it.offset += 2;
211
133
  }
212
- else {
213
- // for decoder
214
- metadata[$descriptors][name] = {
215
- value: undefined,
216
- writable: true,
217
- enumerable: true,
218
- configurable: true,
219
- };
134
+ else if (c < 0xd800 || c >= 0xe000) {
135
+ view[it.offset] = 0xe0 | (c >> 12);
136
+ view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
137
+ view[it.offset + 2] = 0x80 | (c & 0x3f);
138
+ it.offset += 3;
220
139
  }
221
- // map -1 as last field index
222
- Object.defineProperty(metadata, $numFields, {
223
- value: index,
224
- enumerable: false,
225
- configurable: true
226
- });
227
- // map field name => index (non enumerable)
228
- Object.defineProperty(metadata, name, {
229
- value: index,
230
- enumerable: false,
231
- configurable: true,
232
- });
233
- // if child Ref/complex type, add to -4
234
- if (typeof (metadata[index].type) !== "string") {
235
- if (metadata[$refTypeFieldIndexes] === undefined) {
236
- Object.defineProperty(metadata, $refTypeFieldIndexes, {
237
- value: [],
238
- enumerable: false,
239
- configurable: true,
240
- });
241
- }
242
- metadata[$refTypeFieldIndexes].push(index);
140
+ else {
141
+ i++;
142
+ c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
143
+ view[it.offset] = 0xf0 | (c >> 18);
144
+ view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
145
+ view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
146
+ view[it.offset + 3] = 0x80 | (c & 0x3f);
147
+ it.offset += 4;
243
148
  }
244
- },
245
- setTag(metadata, fieldName, tag) {
246
- const index = metadata[fieldName];
247
- const field = metadata[index];
248
- // add 'tag' to the field
249
- field.tag = tag;
250
- if (!metadata[$viewFieldIndexes]) {
251
- // -2: all field indexes with "view" tag
252
- Object.defineProperty(metadata, $viewFieldIndexes, {
253
- value: [],
254
- enumerable: false,
255
- configurable: true
256
- });
257
- // -3: field indexes by "view" tag
258
- Object.defineProperty(metadata, $fieldIndexesByViewTag, {
259
- value: {},
260
- enumerable: false,
261
- configurable: true
262
- });
149
+ }
150
+ }
151
+ function int8$1(bytes, value, it) {
152
+ bytes[it.offset++] = value & 255;
153
+ }
154
+ function uint8$1(bytes, value, it) {
155
+ bytes[it.offset++] = value & 255;
156
+ }
157
+ function int16$1(bytes, value, it) {
158
+ bytes[it.offset++] = value & 255;
159
+ bytes[it.offset++] = (value >> 8) & 255;
160
+ }
161
+ function uint16$1(bytes, value, it) {
162
+ bytes[it.offset++] = value & 255;
163
+ bytes[it.offset++] = (value >> 8) & 255;
164
+ }
165
+ function int32$1(bytes, value, it) {
166
+ bytes[it.offset++] = value & 255;
167
+ bytes[it.offset++] = (value >> 8) & 255;
168
+ bytes[it.offset++] = (value >> 16) & 255;
169
+ bytes[it.offset++] = (value >> 24) & 255;
170
+ }
171
+ function uint32$1(bytes, value, it) {
172
+ const b4 = value >> 24;
173
+ const b3 = value >> 16;
174
+ const b2 = value >> 8;
175
+ const b1 = value;
176
+ bytes[it.offset++] = b1 & 255;
177
+ bytes[it.offset++] = b2 & 255;
178
+ bytes[it.offset++] = b3 & 255;
179
+ bytes[it.offset++] = b4 & 255;
180
+ }
181
+ function int64$1(bytes, value, it) {
182
+ const high = Math.floor(value / Math.pow(2, 32));
183
+ const low = value >>> 0;
184
+ uint32$1(bytes, low, it);
185
+ uint32$1(bytes, high, it);
186
+ }
187
+ function uint64$1(bytes, value, it) {
188
+ const high = (value / Math.pow(2, 32)) >> 0;
189
+ const low = value >>> 0;
190
+ uint32$1(bytes, low, it);
191
+ uint32$1(bytes, high, it);
192
+ }
193
+ function bigint64$1(bytes, value, it) {
194
+ _int64$1[0] = BigInt.asIntN(64, value);
195
+ int32$1(bytes, _int32$1[0], it);
196
+ int32$1(bytes, _int32$1[1], it);
197
+ }
198
+ function biguint64$1(bytes, value, it) {
199
+ _int64$1[0] = BigInt.asIntN(64, value);
200
+ int32$1(bytes, _int32$1[0], it);
201
+ int32$1(bytes, _int32$1[1], it);
202
+ }
203
+ function float32$1(bytes, value, it) {
204
+ _float32$1[0] = value;
205
+ int32$1(bytes, _int32$1[0], it);
206
+ }
207
+ function float64$1(bytes, value, it) {
208
+ _float64$1[0] = value;
209
+ int32$1(bytes, _int32$1[0 ], it);
210
+ int32$1(bytes, _int32$1[1 ], it);
211
+ }
212
+ function boolean$1(bytes, value, it) {
213
+ bytes[it.offset++] = value ? 1 : 0; // uint8
214
+ }
215
+ function string$1(bytes, value, it) {
216
+ // encode `null` strings as empty.
217
+ if (!value) {
218
+ value = "";
219
+ }
220
+ let length = utf8Length(value, "utf8");
221
+ let size = 0;
222
+ // fixstr
223
+ if (length < 0x20) {
224
+ bytes[it.offset++] = length | 0xa0;
225
+ size = 1;
226
+ }
227
+ // str 8
228
+ else if (length < 0x100) {
229
+ bytes[it.offset++] = 0xd9;
230
+ bytes[it.offset++] = length % 255;
231
+ size = 2;
232
+ }
233
+ // str 16
234
+ else if (length < 0x10000) {
235
+ bytes[it.offset++] = 0xda;
236
+ uint16$1(bytes, length, it);
237
+ size = 3;
238
+ }
239
+ // str 32
240
+ else if (length < 0x100000000) {
241
+ bytes[it.offset++] = 0xdb;
242
+ uint32$1(bytes, length, it);
243
+ size = 5;
244
+ }
245
+ else {
246
+ throw new Error('String too long');
247
+ }
248
+ utf8Write(bytes, value, it);
249
+ return size + length;
250
+ }
251
+ function number$1(bytes, value, it) {
252
+ if (isNaN(value)) {
253
+ return number$1(bytes, 0, it);
254
+ }
255
+ else if (!isFinite(value)) {
256
+ return number$1(bytes, (value > 0) ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER, it);
257
+ }
258
+ else if (value !== (value | 0)) {
259
+ if (Math.abs(value) <= 3.4028235e+38) { // range check
260
+ _float32$1[0] = value;
261
+ if (Math.abs(Math.abs(_float32$1[0]) - Math.abs(value)) < 1e-4) { // precision check; adjust 1e-n (n = precision) to in-/decrease acceptable precision loss
262
+ // now we know value is in range for f32 and has acceptable precision for f32
263
+ bytes[it.offset++] = 0xca;
264
+ float32$1(bytes, value, it);
265
+ return 5;
266
+ }
263
267
  }
264
- metadata[$viewFieldIndexes].push(index);
265
- if (!metadata[$fieldIndexesByViewTag][tag]) {
266
- metadata[$fieldIndexesByViewTag][tag] = [];
268
+ bytes[it.offset++] = 0xcb;
269
+ float64$1(bytes, value, it);
270
+ return 9;
271
+ }
272
+ if (value >= 0) {
273
+ // positive fixnum
274
+ if (value < 0x80) {
275
+ bytes[it.offset++] = value & 255; // uint8
276
+ return 1;
267
277
  }
268
- metadata[$fieldIndexesByViewTag][tag].push(index);
269
- },
270
- setFields(target, fields) {
271
- // for inheritance support
272
- const constructor = target.prototype.constructor;
273
- TypeContext.register(constructor);
274
- const parentClass = Object.getPrototypeOf(constructor);
275
- const parentMetadata = parentClass && parentClass[Symbol.metadata];
276
- const metadata = Metadata.initialize(constructor);
277
- // Use Schema's methods if not defined in the class
278
- if (!constructor[$track]) {
279
- constructor[$track] = Schema[$track];
278
+ // uint 8
279
+ if (value < 0x100) {
280
+ bytes[it.offset++] = 0xcc;
281
+ bytes[it.offset++] = value & 255; // uint8
282
+ return 2;
280
283
  }
281
- if (!constructor[$encoder]) {
282
- constructor[$encoder] = Schema[$encoder];
284
+ // uint 16
285
+ if (value < 0x10000) {
286
+ bytes[it.offset++] = 0xcd;
287
+ uint16$1(bytes, value, it);
288
+ return 3;
283
289
  }
284
- if (!constructor[$decoder]) {
285
- constructor[$decoder] = Schema[$decoder];
290
+ // uint 32
291
+ if (value < 0x100000000) {
292
+ bytes[it.offset++] = 0xce;
293
+ uint32$1(bytes, value, it);
294
+ return 5;
286
295
  }
287
- if (!constructor.prototype.toJSON) {
288
- constructor.prototype.toJSON = Schema.prototype.toJSON;
296
+ // uint 64
297
+ bytes[it.offset++] = 0xcf;
298
+ uint64$1(bytes, value, it);
299
+ return 9;
300
+ }
301
+ else {
302
+ // negative fixnum
303
+ if (value >= -0x20) {
304
+ bytes[it.offset++] = 0xe0 | (value + 0x20);
305
+ return 1;
289
306
  }
290
- //
291
- // detect index for this field, considering inheritance
292
- //
293
- let fieldIndex = metadata[$numFields] // current structure already has fields defined
294
- ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
295
- ?? -1; // no fields defined
296
- fieldIndex++;
297
- for (const field in fields) {
298
- const type = fields[field];
299
- const normalizedType = getNormalizedType(type);
300
- // FIXME: this code is duplicated from @type() annotation
301
- const complexTypeKlass = (Array.isArray(type))
302
- ? getType("array")
303
- : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
304
- const childType = (complexTypeKlass)
305
- ? Object.values(type)[0]
306
- : normalizedType;
307
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
308
- fieldIndex++;
307
+ // int 8
308
+ if (value >= -0x80) {
309
+ bytes[it.offset++] = 0xd0;
310
+ int8$1(bytes, value, it);
311
+ return 2;
309
312
  }
310
- return target;
311
- },
312
- isDeprecated(metadata, field) {
313
- return metadata[field].deprecated === true;
314
- },
315
- init(klass) {
316
- //
317
- // Used only to initialize an empty Schema (Encoder#constructor)
318
- // TODO: remove/refactor this...
319
- //
320
- const metadata = {};
321
- klass[Symbol.metadata] = metadata;
322
- Object.defineProperty(metadata, $numFields, {
323
- value: 0,
324
- enumerable: false,
325
- configurable: true,
326
- });
327
- },
328
- initialize(constructor) {
329
- const parentClass = Object.getPrototypeOf(constructor);
330
- const parentMetadata = parentClass[Symbol.metadata];
331
- let metadata = constructor[Symbol.metadata] ?? Object.create(null);
332
- // make sure inherited classes have their own metadata object.
333
- if (parentClass !== Schema && metadata === parentMetadata) {
334
- metadata = Object.create(null);
335
- if (parentMetadata) {
336
- //
337
- // assign parent metadata to current
338
- //
339
- Object.setPrototypeOf(metadata, parentMetadata);
340
- // $numFields
341
- Object.defineProperty(metadata, $numFields, {
342
- value: parentMetadata[$numFields],
343
- enumerable: false,
344
- configurable: true,
345
- writable: true,
346
- });
347
- // $viewFieldIndexes / $fieldIndexesByViewTag
348
- if (parentMetadata[$viewFieldIndexes] !== undefined) {
349
- Object.defineProperty(metadata, $viewFieldIndexes, {
350
- value: [...parentMetadata[$viewFieldIndexes]],
351
- enumerable: false,
352
- configurable: true,
353
- writable: true,
354
- });
355
- Object.defineProperty(metadata, $fieldIndexesByViewTag, {
356
- value: { ...parentMetadata[$fieldIndexesByViewTag] },
357
- enumerable: false,
358
- configurable: true,
359
- writable: true,
360
- });
361
- }
362
- // $refTypeFieldIndexes
363
- if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
364
- Object.defineProperty(metadata, $refTypeFieldIndexes, {
365
- value: [...parentMetadata[$refTypeFieldIndexes]],
366
- enumerable: false,
367
- configurable: true,
368
- writable: true,
369
- });
370
- }
371
- // $descriptors
372
- Object.defineProperty(metadata, $descriptors, {
373
- value: { ...parentMetadata[$descriptors] },
374
- enumerable: false,
375
- configurable: true,
376
- writable: true,
377
- });
378
- }
313
+ // int 16
314
+ if (value >= -0x8000) {
315
+ bytes[it.offset++] = 0xd1;
316
+ int16$1(bytes, value, it);
317
+ return 3;
379
318
  }
380
- constructor[Symbol.metadata] = metadata;
381
- return metadata;
382
- },
383
- isValidInstance(klass) {
384
- return (klass.constructor[Symbol.metadata] &&
385
- Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
386
- },
387
- getFields(klass) {
388
- const metadata = klass[Symbol.metadata];
389
- const fields = {};
390
- for (let i = 0; i <= metadata[$numFields]; i++) {
391
- fields[metadata[i].name] = metadata[i].type;
319
+ // int 32
320
+ if (value >= -0x80000000) {
321
+ bytes[it.offset++] = 0xd2;
322
+ int32$1(bytes, value, it);
323
+ return 5;
392
324
  }
393
- return fields;
325
+ // int 64
326
+ bytes[it.offset++] = 0xd3;
327
+ int64$1(bytes, value, it);
328
+ return 9;
329
+ }
330
+ }
331
+ const encode = {
332
+ int8: int8$1,
333
+ uint8: uint8$1,
334
+ int16: int16$1,
335
+ uint16: uint16$1,
336
+ int32: int32$1,
337
+ uint32: uint32$1,
338
+ int64: int64$1,
339
+ uint64: uint64$1,
340
+ bigint64: bigint64$1,
341
+ biguint64: biguint64$1,
342
+ float32: float32$1,
343
+ float64: float64$1,
344
+ boolean: boolean$1,
345
+ string: string$1,
346
+ number: number$1,
347
+ utf8Write,
348
+ utf8Length,
349
+ };
350
+
351
+ /**
352
+ * Copyright (c) 2018 Endel Dreyer
353
+ * Copyright (c) 2014 Ion Drive Software Ltd.
354
+ *
355
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
356
+ * of this software and associated documentation files (the "Software"), to deal
357
+ * in the Software without restriction, including without limitation the rights
358
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
359
+ * copies of the Software, and to permit persons to whom the Software is
360
+ * furnished to do so, subject to the following conditions:
361
+ *
362
+ * The above copyright notice and this permission notice shall be included in all
363
+ * copies or substantial portions of the Software.
364
+ *
365
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
366
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
367
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
368
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
369
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
370
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
371
+ * SOFTWARE
372
+ */
373
+ // force little endian to facilitate decoding on multiple implementations
374
+ const _convoBuffer = new ArrayBuffer(8);
375
+ const _int32 = new Int32Array(_convoBuffer);
376
+ const _float32 = new Float32Array(_convoBuffer);
377
+ const _float64 = new Float64Array(_convoBuffer);
378
+ const _uint64 = new BigUint64Array(_convoBuffer);
379
+ const _int64 = new BigInt64Array(_convoBuffer);
380
+ function utf8Read(bytes, it, length) {
381
+ var string = '', chr = 0;
382
+ for (var i = it.offset, end = it.offset + length; i < end; i++) {
383
+ var byte = bytes[i];
384
+ if ((byte & 0x80) === 0x00) {
385
+ string += String.fromCharCode(byte);
386
+ continue;
387
+ }
388
+ if ((byte & 0xe0) === 0xc0) {
389
+ string += String.fromCharCode(((byte & 0x1f) << 6) |
390
+ (bytes[++i] & 0x3f));
391
+ continue;
392
+ }
393
+ if ((byte & 0xf0) === 0xe0) {
394
+ string += String.fromCharCode(((byte & 0x0f) << 12) |
395
+ ((bytes[++i] & 0x3f) << 6) |
396
+ ((bytes[++i] & 0x3f) << 0));
397
+ continue;
398
+ }
399
+ if ((byte & 0xf8) === 0xf0) {
400
+ chr = ((byte & 0x07) << 18) |
401
+ ((bytes[++i] & 0x3f) << 12) |
402
+ ((bytes[++i] & 0x3f) << 6) |
403
+ ((bytes[++i] & 0x3f) << 0);
404
+ if (chr >= 0x010000) { // surrogate pair
405
+ chr -= 0x010000;
406
+ string += String.fromCharCode((chr >>> 10) + 0xD800, (chr & 0x3FF) + 0xDC00);
407
+ }
408
+ else {
409
+ string += String.fromCharCode(chr);
410
+ }
411
+ continue;
412
+ }
413
+ console.error('Invalid byte ' + byte.toString(16));
414
+ // (do not throw error to avoid server/client from crashing due to hack attemps)
415
+ // throw new Error('Invalid byte ' + byte.toString(16));
416
+ }
417
+ it.offset += length;
418
+ return string;
419
+ }
420
+ function int8(bytes, it) {
421
+ return uint8(bytes, it) << 24 >> 24;
422
+ }
423
+ function uint8(bytes, it) {
424
+ return bytes[it.offset++];
425
+ }
426
+ function int16(bytes, it) {
427
+ return uint16(bytes, it) << 16 >> 16;
428
+ }
429
+ function uint16(bytes, it) {
430
+ return bytes[it.offset++] | bytes[it.offset++] << 8;
431
+ }
432
+ function int32(bytes, it) {
433
+ return bytes[it.offset++] | bytes[it.offset++] << 8 | bytes[it.offset++] << 16 | bytes[it.offset++] << 24;
434
+ }
435
+ function uint32(bytes, it) {
436
+ return int32(bytes, it) >>> 0;
437
+ }
438
+ function float32(bytes, it) {
439
+ _int32[0] = int32(bytes, it);
440
+ return _float32[0];
441
+ }
442
+ function float64(bytes, it) {
443
+ _int32[0 ] = int32(bytes, it);
444
+ _int32[1 ] = int32(bytes, it);
445
+ return _float64[0];
446
+ }
447
+ function int64(bytes, it) {
448
+ const low = uint32(bytes, it);
449
+ const high = int32(bytes, it) * Math.pow(2, 32);
450
+ return high + low;
451
+ }
452
+ function uint64(bytes, it) {
453
+ const low = uint32(bytes, it);
454
+ const high = uint32(bytes, it) * Math.pow(2, 32);
455
+ return high + low;
456
+ }
457
+ function bigint64(bytes, it) {
458
+ _int32[0] = int32(bytes, it);
459
+ _int32[1] = int32(bytes, it);
460
+ return _int64[0];
461
+ }
462
+ function biguint64(bytes, it) {
463
+ _int32[0] = int32(bytes, it);
464
+ _int32[1] = int32(bytes, it);
465
+ return _uint64[0];
466
+ }
467
+ function boolean(bytes, it) {
468
+ return uint8(bytes, it) > 0;
469
+ }
470
+ function string(bytes, it) {
471
+ const prefix = bytes[it.offset++];
472
+ let length;
473
+ if (prefix < 0xc0) {
474
+ // fixstr
475
+ length = prefix & 0x1f;
476
+ }
477
+ else if (prefix === 0xd9) {
478
+ length = uint8(bytes, it);
479
+ }
480
+ else if (prefix === 0xda) {
481
+ length = uint16(bytes, it);
482
+ }
483
+ else if (prefix === 0xdb) {
484
+ length = uint32(bytes, it);
485
+ }
486
+ return utf8Read(bytes, it, length);
487
+ }
488
+ function number(bytes, it) {
489
+ const prefix = bytes[it.offset++];
490
+ if (prefix < 0x80) {
491
+ // positive fixint
492
+ return prefix;
493
+ }
494
+ else if (prefix === 0xca) {
495
+ // float 32
496
+ return float32(bytes, it);
497
+ }
498
+ else if (prefix === 0xcb) {
499
+ // float 64
500
+ return float64(bytes, it);
501
+ }
502
+ else if (prefix === 0xcc) {
503
+ // uint 8
504
+ return uint8(bytes, it);
505
+ }
506
+ else if (prefix === 0xcd) {
507
+ // uint 16
508
+ return uint16(bytes, it);
509
+ }
510
+ else if (prefix === 0xce) {
511
+ // uint 32
512
+ return uint32(bytes, it);
513
+ }
514
+ else if (prefix === 0xcf) {
515
+ // uint 64
516
+ return uint64(bytes, it);
517
+ }
518
+ else if (prefix === 0xd0) {
519
+ // int 8
520
+ return int8(bytes, it);
521
+ }
522
+ else if (prefix === 0xd1) {
523
+ // int 16
524
+ return int16(bytes, it);
525
+ }
526
+ else if (prefix === 0xd2) {
527
+ // int 32
528
+ return int32(bytes, it);
529
+ }
530
+ else if (prefix === 0xd3) {
531
+ // int 64
532
+ return int64(bytes, it);
533
+ }
534
+ else if (prefix > 0xdf) {
535
+ // negative fixint
536
+ return (0xff - prefix + 1) * -1;
394
537
  }
538
+ }
539
+ function stringCheck(bytes, it) {
540
+ const prefix = bytes[it.offset];
541
+ return (
542
+ // fixstr
543
+ (prefix < 0xc0 && prefix > 0xa0) ||
544
+ // str 8
545
+ prefix === 0xd9 ||
546
+ // str 16
547
+ prefix === 0xda ||
548
+ // str 32
549
+ prefix === 0xdb);
550
+ }
551
+ const decode = {
552
+ utf8Read,
553
+ int8,
554
+ uint8,
555
+ int16,
556
+ uint16,
557
+ int32,
558
+ uint32,
559
+ float32,
560
+ float64,
561
+ int64,
562
+ uint64,
563
+ bigint64,
564
+ biguint64,
565
+ boolean,
566
+ string,
567
+ number,
568
+ stringCheck,
395
569
  };
396
570
 
397
- function setOperationAtIndex(changeSet, index) {
398
- const operationsIndex = changeSet.indexes[index];
399
- if (operationsIndex === undefined) {
400
- changeSet.indexes[index] = changeSet.operations.push(index) - 1;
571
+ const registeredTypes = {};
572
+ const identifiers = new Map();
573
+ function registerType(identifier, definition) {
574
+ if (definition.constructor) {
575
+ identifiers.set(definition.constructor, identifier);
576
+ registeredTypes[identifier] = definition;
401
577
  }
402
- else {
403
- changeSet.operations[operationsIndex] = index;
578
+ if (definition.encode) {
579
+ encode[identifier] = definition.encode;
404
580
  }
405
- }
406
- function deleteOperationAtIndex(changeSet, index) {
407
- const operationsIndex = changeSet.indexes[index];
408
- if (operationsIndex !== undefined) {
409
- changeSet.operations[operationsIndex] = undefined;
581
+ if (definition.decode) {
582
+ decode[identifier] = definition.decode;
410
583
  }
411
- delete changeSet.indexes[index];
412
584
  }
413
- function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
414
- if (root && root[changeSet][queueRootIndex] !== changeTree) {
415
- changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
416
- }
585
+ function getType(identifier) {
586
+ return registeredTypes[identifier];
417
587
  }
418
- class ChangeTree {
419
- constructor(ref) {
420
- this.isFiltered = false;
421
- this.isPartiallyFiltered = false;
422
- this.indexedOperations = {};
423
- //
424
- // TODO:
425
- // try storing the index + operation per item.
426
- // example: 1024 & 1025 => ADD, 1026 => DELETE
427
- //
428
- // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
429
- //
430
- this.changes = { indexes: {}, operations: [] };
431
- this.allChanges = { indexes: {}, operations: [] };
432
- /**
433
- * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
434
- */
435
- this.isNew = true;
436
- this.ref = ref;
437
- //
438
- // Does this structure have "filters" declared?
439
- //
440
- if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
441
- this.allFilteredChanges = { indexes: {}, operations: [] };
442
- this.filteredChanges = { indexes: {}, operations: [] };
443
- }
588
+ function defineCustomTypes(types) {
589
+ for (const identifier in types) {
590
+ registerType(identifier, types[identifier]);
444
591
  }
445
- setRoot(root) {
446
- this.root = root;
447
- const isNewChangeTree = this.root.add(this);
448
- const metadata = this.ref.constructor[Symbol.metadata];
449
- if (this.root.types.hasFilters) {
450
- //
451
- // At Schema initialization, the "root" structure might not be available
452
- // yet, as it only does once the "Encoder" has been set up.
453
- //
454
- // So the "parent" may be already set without a "root".
455
- //
456
- this.checkIsFiltered(metadata, this.parent, this.parentIndex);
457
- if (this.isFiltered || this.isPartiallyFiltered) {
458
- enqueueChangeTree(root, this, 'filteredChanges');
459
- if (isNewChangeTree) {
460
- this.root.allFilteredChanges.push(this);
461
- }
462
- }
463
- }
464
- if (!this.isFiltered) {
465
- enqueueChangeTree(root, this, 'changes');
466
- if (isNewChangeTree) {
467
- this.root.allChanges.push(this);
592
+ return (t) => type(t);
593
+ }
594
+
595
+ class TypeContext {
596
+ /**
597
+ * For inheritance support
598
+ * Keeps track of which classes extends which. (parent -> children)
599
+ */
600
+ static { this.inheritedTypes = new Map(); }
601
+ static register(target) {
602
+ const parent = Object.getPrototypeOf(target);
603
+ if (parent !== Schema) {
604
+ let inherits = TypeContext.inheritedTypes.get(parent);
605
+ if (!inherits) {
606
+ inherits = new Set();
607
+ TypeContext.inheritedTypes.set(parent, inherits);
468
608
  }
469
- }
470
- // Recursively set root on child structures
471
- if (metadata) {
472
- metadata[$refTypeFieldIndexes]?.forEach((index) => {
473
- const field = metadata[index];
474
- const value = this.ref[field.name];
475
- value?.[$changes].setRoot(root);
476
- });
477
- }
478
- else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
479
- // MapSchema / ArraySchema, etc.
480
- this.ref.forEach((value, key) => {
481
- value[$changes].setRoot(root);
482
- });
609
+ inherits.add(target);
483
610
  }
484
611
  }
485
- setParent(parent, root, parentIndex) {
486
- this.parent = parent;
487
- this.parentIndex = parentIndex;
488
- // avoid setting parents with empty `root`
489
- if (!root) {
490
- return;
491
- }
492
- const metadata = this.ref.constructor[Symbol.metadata];
493
- // skip if parent is already set
494
- if (root !== this.root) {
495
- this.root = root;
496
- const isNewChangeTree = root.add(this);
497
- if (root.types.hasFilters) {
498
- this.checkIsFiltered(metadata, parent, parentIndex);
499
- if (this.isFiltered || this.isPartiallyFiltered) {
500
- enqueueChangeTree(root, this, 'filteredChanges');
501
- if (isNewChangeTree) {
502
- this.root.allFilteredChanges.push(this);
503
- }
504
- }
505
- }
506
- if (!this.isFiltered) {
507
- enqueueChangeTree(root, this, 'changes');
508
- if (isNewChangeTree) {
509
- this.root.allChanges.push(this);
510
- }
511
- }
512
- }
513
- else {
514
- root.add(this);
515
- }
516
- // assign same parent on child structures
517
- if (metadata) {
518
- metadata[$refTypeFieldIndexes]?.forEach((index) => {
519
- const field = metadata[index];
520
- const value = this.ref[field.name];
521
- value?.[$changes].setParent(this.ref, root, index);
522
- // try { throw new Error(); } catch (e) {
523
- // console.log(e.stack);
524
- // }
525
- });
526
- }
527
- else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
528
- // MapSchema / ArraySchema, etc.
529
- this.ref.forEach((value, key) => {
530
- value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
531
- });
612
+ constructor(rootClass) {
613
+ this.types = {};
614
+ this.schemas = new Map();
615
+ this.hasFilters = false;
616
+ this.parentFiltered = {};
617
+ if (rootClass) {
618
+ this.discoverTypes(rootClass);
532
619
  }
533
620
  }
534
- forEachChild(callback) {
535
- //
536
- // assign same parent on child structures
537
- //
538
- const metadata = this.ref.constructor[Symbol.metadata];
539
- if (metadata) {
540
- metadata[$refTypeFieldIndexes]?.forEach((index) => {
541
- const field = metadata[index];
542
- const value = this.ref[field.name];
543
- if (value) {
544
- callback(value[$changes], index);
545
- }
546
- });
547
- }
548
- else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
549
- // MapSchema / ArraySchema, etc.
550
- this.ref.forEach((value, key) => {
551
- callback(value[$changes], this.indexes[key] ?? key);
552
- });
553
- }
621
+ has(schema) {
622
+ return this.schemas.has(schema);
554
623
  }
555
- operation(op) {
556
- // operations without index use negative values to represent them
557
- // this is checked during .encode() time.
558
- this.changes.operations.push(-op);
559
- enqueueChangeTree(this.root, this, 'changes');
624
+ get(typeid) {
625
+ return this.types[typeid];
560
626
  }
561
- change(index, operation = OPERATION.ADD) {
562
- const metadata = this.ref.constructor[Symbol.metadata];
563
- const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
564
- const changeSet = (isFiltered)
565
- ? this.filteredChanges
566
- : this.changes;
567
- const previousOperation = this.indexedOperations[index];
568
- if (!previousOperation || previousOperation === OPERATION.DELETE) {
569
- const op = (!previousOperation)
570
- ? operation
571
- : (previousOperation === OPERATION.DELETE)
572
- ? OPERATION.DELETE_AND_ADD
573
- : operation;
574
- //
575
- // TODO: are DELETE operations being encoded as ADD here ??
576
- //
577
- this.indexedOperations[index] = op;
578
- }
579
- setOperationAtIndex(changeSet, index);
580
- if (isFiltered) {
581
- setOperationAtIndex(this.allFilteredChanges, index);
582
- if (this.root) {
583
- enqueueChangeTree(this.root, this, 'filteredChanges');
584
- enqueueChangeTree(this.root, this, 'allFilteredChanges');
585
- }
586
- }
587
- else {
588
- setOperationAtIndex(this.allChanges, index);
589
- enqueueChangeTree(this.root, this, 'changes');
627
+ add(schema, typeid = this.schemas.size) {
628
+ // skip if already registered
629
+ if (this.schemas.has(schema)) {
630
+ return false;
590
631
  }
591
- }
592
- shiftChangeIndexes(shiftIndex) {
593
- //
594
- // Used only during:
632
+ this.types[typeid] = schema;
595
633
  //
596
- // - ArraySchema#unshift()
634
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
597
635
  //
598
- const changeSet = (this.isFiltered)
599
- ? this.filteredChanges
600
- : this.changes;
601
- const newIndexedOperations = {};
602
- const newIndexes = {};
603
- for (const index in this.indexedOperations) {
604
- newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
605
- newIndexes[Number(index) + shiftIndex] = changeSet[index];
636
+ if (schema[Symbol.metadata] === undefined) {
637
+ Metadata.initialize(schema);
606
638
  }
607
- this.indexedOperations = newIndexedOperations;
608
- changeSet.indexes = newIndexes;
609
- changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
639
+ this.schemas.set(schema, typeid);
640
+ return true;
610
641
  }
611
- shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
612
- //
613
- // Used only during:
614
- //
615
- // - ArraySchema#splice()
616
- //
617
- if (this.isFiltered || this.isPartiallyFiltered) {
618
- this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
619
- this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
642
+ getTypeId(klass) {
643
+ return this.schemas.get(klass);
644
+ }
645
+ discoverTypes(klass, parentIndex, parentFieldViewTag) {
646
+ if (!this.add(klass)) {
647
+ return;
620
648
  }
621
- else {
622
- this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
649
+ // add classes inherited from this base class
650
+ TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
651
+ this.discoverTypes(child, parentIndex, parentFieldViewTag);
652
+ });
653
+ // add parent classes
654
+ let parent = klass;
655
+ while ((parent = Object.getPrototypeOf(parent)) &&
656
+ parent !== Schema && // stop at root (Schema)
657
+ parent !== Function.prototype // stop at root (non-Schema)
658
+ ) {
659
+ this.discoverTypes(parent);
623
660
  }
624
- }
625
- _shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
626
- const newIndexes = {};
627
- for (const key in changeSet.indexes) {
628
- const index = changeSet.indexes[key];
629
- if (index > startIndex) {
630
- newIndexes[Number(key) + shiftIndex] = index;
661
+ const metadata = (klass[Symbol.metadata] ??= {});
662
+ // if any schema/field has filters, mark "context" as having filters.
663
+ if (metadata[$viewFieldIndexes]) {
664
+ this.hasFilters = true;
665
+ }
666
+ if (parentFieldViewTag !== undefined) {
667
+ this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
668
+ }
669
+ for (const fieldIndex in metadata) {
670
+ const index = fieldIndex;
671
+ const fieldType = metadata[index].type;
672
+ const viewTag = metadata[index].tag;
673
+ if (typeof (fieldType) === "string") {
674
+ continue;
631
675
  }
632
- else {
633
- newIndexes[key] = index;
676
+ if (Array.isArray(fieldType)) {
677
+ const type = fieldType[0];
678
+ // skip primitive types
679
+ if (type === "string") {
680
+ continue;
681
+ }
682
+ this.discoverTypes(type, index, viewTag);
634
683
  }
635
- }
636
- changeSet.indexes = newIndexes;
637
- for (let i = 0; i < changeSet.operations.length; i++) {
638
- const index = changeSet.operations[i];
639
- if (index > startIndex) {
640
- changeSet.operations[i] = index + shiftIndex;
684
+ else if (typeof (fieldType) === "function") {
685
+ this.discoverTypes(fieldType, viewTag);
686
+ }
687
+ else {
688
+ const type = Object.values(fieldType)[0];
689
+ // skip primitive types
690
+ if (typeof (type) === "string") {
691
+ continue;
692
+ }
693
+ this.discoverTypes(type, index, viewTag);
641
694
  }
642
695
  }
643
696
  }
644
- indexedOperation(index, operation, allChangesIndex = index) {
645
- this.indexedOperations[index] = operation;
646
- if (this.filteredChanges) {
647
- setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
648
- setOperationAtIndex(this.filteredChanges, index);
649
- enqueueChangeTree(this.root, this, 'filteredChanges');
650
- }
651
- else {
652
- setOperationAtIndex(this.allChanges, allChangesIndex);
653
- setOperationAtIndex(this.changes, index);
654
- enqueueChangeTree(this.root, this, 'changes');
697
+ }
698
+
699
+ function getNormalizedType(type) {
700
+ return (Array.isArray(type))
701
+ ? { array: type[0] }
702
+ : (typeof (type['type']) !== "undefined")
703
+ ? type['type']
704
+ : type;
705
+ }
706
+ const Metadata = {
707
+ addField(metadata, index, name, type, descriptor) {
708
+ if (index > 64) {
709
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
655
710
  }
656
- }
657
- getType(index) {
658
- if (Metadata.isValidInstance(this.ref)) {
659
- const metadata = this.ref.constructor[Symbol.metadata];
660
- return metadata[index].type;
711
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
712
+ {
713
+ type: getNormalizedType(type),
714
+ index,
715
+ name,
716
+ });
717
+ // create "descriptors" map
718
+ Object.defineProperty(metadata, $descriptors, {
719
+ value: metadata[$descriptors] || {},
720
+ enumerable: false,
721
+ configurable: true,
722
+ });
723
+ if (descriptor) {
724
+ // for encoder
725
+ metadata[$descriptors][name] = descriptor;
726
+ metadata[$descriptors][`_${name}`] = {
727
+ value: undefined,
728
+ writable: true,
729
+ enumerable: false,
730
+ configurable: true,
731
+ };
661
732
  }
662
733
  else {
663
- //
664
- // Get the child type from parent structure.
665
- // - ["string"] => "string"
666
- // - { map: "string" } => "string"
667
- // - { set: "string" } => "string"
668
- //
669
- return this.ref[$childType];
734
+ // for decoder
735
+ metadata[$descriptors][name] = {
736
+ value: undefined,
737
+ writable: true,
738
+ enumerable: true,
739
+ configurable: true,
740
+ };
670
741
  }
671
- }
672
- getChange(index) {
673
- return this.indexedOperations[index];
674
- }
675
- //
676
- // used during `.encode()`
677
- //
678
- getValue(index, isEncodeAll = false) {
679
- //
680
- // `isEncodeAll` param is only used by ArraySchema
681
- //
682
- return this.ref[$getByIndex](index, isEncodeAll);
683
- }
684
- delete(index, operation, allChangesIndex = index) {
685
- if (index === undefined) {
686
- try {
687
- throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
688
- }
689
- catch (e) {
690
- console.warn(e);
742
+ // map -1 as last field index
743
+ Object.defineProperty(metadata, $numFields, {
744
+ value: index,
745
+ enumerable: false,
746
+ configurable: true
747
+ });
748
+ // map field name => index (non enumerable)
749
+ Object.defineProperty(metadata, name, {
750
+ value: index,
751
+ enumerable: false,
752
+ configurable: true,
753
+ });
754
+ // if child Ref/complex type, add to -4
755
+ if (typeof (metadata[index].type) !== "string") {
756
+ if (metadata[$refTypeFieldIndexes] === undefined) {
757
+ Object.defineProperty(metadata, $refTypeFieldIndexes, {
758
+ value: [],
759
+ enumerable: false,
760
+ configurable: true,
761
+ });
691
762
  }
692
- return;
763
+ metadata[$refTypeFieldIndexes].push(index);
693
764
  }
694
- const changeSet = (this.filteredChanges)
695
- ? this.filteredChanges
696
- : this.changes;
697
- this.indexedOperations[index] = operation ?? OPERATION.DELETE;
698
- setOperationAtIndex(changeSet, index);
699
- const previousValue = this.getValue(index);
700
- // remove `root` reference
701
- if (previousValue && previousValue[$changes]) {
702
- //
703
- // FIXME: this.root is "undefined"
704
- //
705
- // This method is being called at decoding time when a DELETE operation is found.
706
- //
707
- // - This is due to using the concrete Schema class at decoding time.
708
- // - "Reflected" structures do not have this problem.
709
- //
710
- // (the property descriptors should NOT be used at decoding time. only at encoding time.)
711
- //
712
- this.root?.remove(previousValue[$changes]);
765
+ },
766
+ setTag(metadata, fieldName, tag) {
767
+ const index = metadata[fieldName];
768
+ const field = metadata[index];
769
+ // add 'tag' to the field
770
+ field.tag = tag;
771
+ if (!metadata[$viewFieldIndexes]) {
772
+ // -2: all field indexes with "view" tag
773
+ Object.defineProperty(metadata, $viewFieldIndexes, {
774
+ value: [],
775
+ enumerable: false,
776
+ configurable: true
777
+ });
778
+ // -3: field indexes by "view" tag
779
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
780
+ value: {},
781
+ enumerable: false,
782
+ configurable: true
783
+ });
784
+ }
785
+ metadata[$viewFieldIndexes].push(index);
786
+ if (!metadata[$fieldIndexesByViewTag][tag]) {
787
+ metadata[$fieldIndexesByViewTag][tag] = [];
788
+ }
789
+ metadata[$fieldIndexesByViewTag][tag].push(index);
790
+ },
791
+ setFields(target, fields) {
792
+ // for inheritance support
793
+ const constructor = target.prototype.constructor;
794
+ TypeContext.register(constructor);
795
+ const parentClass = Object.getPrototypeOf(constructor);
796
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
797
+ const metadata = Metadata.initialize(constructor);
798
+ // Use Schema's methods if not defined in the class
799
+ if (!constructor[$track]) {
800
+ constructor[$track] = Schema[$track];
801
+ }
802
+ if (!constructor[$encoder]) {
803
+ constructor[$encoder] = Schema[$encoder];
713
804
  }
714
- //
715
- // FIXME: this is looking a ugly and repeated
716
- //
717
- if (this.filteredChanges) {
718
- deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
719
- enqueueChangeTree(this.root, this, 'filteredChanges');
805
+ if (!constructor[$decoder]) {
806
+ constructor[$decoder] = Schema[$decoder];
720
807
  }
721
- else {
722
- deleteOperationAtIndex(this.allChanges, allChangesIndex);
723
- enqueueChangeTree(this.root, this, 'changes');
808
+ if (!constructor.prototype.toJSON) {
809
+ constructor.prototype.toJSON = Schema.prototype.toJSON;
724
810
  }
725
- }
726
- endEncode() {
727
- this.indexedOperations = {};
728
- // // clear changes
729
- // this.changes.indexes = {};
730
- // this.changes.operations.length = 0;
731
- // ArraySchema and MapSchema have a custom "encode end" method
732
- this.ref[$onEncodeEnd]?.();
733
- // Not a new instance anymore
734
- this.isNew = false;
735
- }
736
- discard(discardAll = false) {
737
811
  //
738
- // > MapSchema:
739
- // Remove cached key to ensure ADD operations is unsed instead of
740
- // REPLACE in case same key is used on next patches.
812
+ // detect index for this field, considering inheritance
741
813
  //
742
- this.ref[$onEncodeEnd]?.();
743
- this.indexedOperations = {};
744
- this.changes.indexes = {};
745
- this.changes.operations.length = 0;
746
- this.changes.queueRootIndex = undefined;
747
- if (this.filteredChanges !== undefined) {
748
- this.filteredChanges.indexes = {};
749
- this.filteredChanges.operations.length = 0;
750
- this.filteredChanges.queueRootIndex = undefined;
814
+ let fieldIndex = metadata[$numFields] // current structure already has fields defined
815
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
816
+ ?? -1; // no fields defined
817
+ fieldIndex++;
818
+ for (const field in fields) {
819
+ const type = fields[field];
820
+ const normalizedType = getNormalizedType(type);
821
+ // FIXME: this code is duplicated from @type() annotation
822
+ const complexTypeKlass = (Array.isArray(type))
823
+ ? getType("array")
824
+ : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
825
+ const childType = (complexTypeKlass)
826
+ ? Object.values(type)[0]
827
+ : normalizedType;
828
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
829
+ fieldIndex++;
751
830
  }
752
- if (discardAll) {
753
- this.allChanges.indexes = {};
754
- this.allChanges.operations.length = 0;
755
- if (this.allFilteredChanges !== undefined) {
756
- this.allFilteredChanges.indexes = {};
757
- this.allFilteredChanges.operations.length = 0;
831
+ return target;
832
+ },
833
+ isDeprecated(metadata, field) {
834
+ return metadata[field].deprecated === true;
835
+ },
836
+ init(klass) {
837
+ //
838
+ // Used only to initialize an empty Schema (Encoder#constructor)
839
+ // TODO: remove/refactor this...
840
+ //
841
+ const metadata = {};
842
+ klass[Symbol.metadata] = metadata;
843
+ Object.defineProperty(metadata, $numFields, {
844
+ value: 0,
845
+ enumerable: false,
846
+ configurable: true,
847
+ });
848
+ },
849
+ initialize(constructor) {
850
+ const parentClass = Object.getPrototypeOf(constructor);
851
+ const parentMetadata = parentClass[Symbol.metadata];
852
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
853
+ // make sure inherited classes have their own metadata object.
854
+ if (parentClass !== Schema && metadata === parentMetadata) {
855
+ metadata = Object.create(null);
856
+ if (parentMetadata) {
857
+ //
858
+ // assign parent metadata to current
859
+ //
860
+ Object.setPrototypeOf(metadata, parentMetadata);
861
+ // $numFields
862
+ Object.defineProperty(metadata, $numFields, {
863
+ value: parentMetadata[$numFields],
864
+ enumerable: false,
865
+ configurable: true,
866
+ writable: true,
867
+ });
868
+ // $viewFieldIndexes / $fieldIndexesByViewTag
869
+ if (parentMetadata[$viewFieldIndexes] !== undefined) {
870
+ Object.defineProperty(metadata, $viewFieldIndexes, {
871
+ value: [...parentMetadata[$viewFieldIndexes]],
872
+ enumerable: false,
873
+ configurable: true,
874
+ writable: true,
875
+ });
876
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
877
+ value: { ...parentMetadata[$fieldIndexesByViewTag] },
878
+ enumerable: false,
879
+ configurable: true,
880
+ writable: true,
881
+ });
882
+ }
883
+ // $refTypeFieldIndexes
884
+ if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
885
+ Object.defineProperty(metadata, $refTypeFieldIndexes, {
886
+ value: [...parentMetadata[$refTypeFieldIndexes]],
887
+ enumerable: false,
888
+ configurable: true,
889
+ writable: true,
890
+ });
891
+ }
892
+ // $descriptors
893
+ Object.defineProperty(metadata, $descriptors, {
894
+ value: { ...parentMetadata[$descriptors] },
895
+ enumerable: false,
896
+ configurable: true,
897
+ writable: true,
898
+ });
758
899
  }
759
- // remove children references
760
- this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
761
900
  }
762
- }
763
- /**
764
- * Recursively discard all changes from this, and child structures.
765
- */
766
- discardAll() {
767
- const keys = Object.keys(this.indexedOperations);
768
- for (let i = 0, len = keys.length; i < len; i++) {
769
- const value = this.getValue(Number(keys[i]));
770
- if (value && value[$changes]) {
771
- value[$changes].discardAll();
772
- }
901
+ constructor[Symbol.metadata] = metadata;
902
+ return metadata;
903
+ },
904
+ isValidInstance(klass) {
905
+ return (klass.constructor[Symbol.metadata] &&
906
+ Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
907
+ },
908
+ getFields(klass) {
909
+ const metadata = klass[Symbol.metadata];
910
+ const fields = {};
911
+ for (let i = 0; i <= metadata[$numFields]; i++) {
912
+ fields[metadata[i].name] = metadata[i].type;
773
913
  }
774
- this.discard();
914
+ return fields;
775
915
  }
776
- ensureRefId() {
777
- // skip if refId is already set.
778
- if (this.refId !== undefined) {
779
- return;
780
- }
781
- this.refId = this.root.getNextUniqueId();
916
+ };
917
+
918
+ function setOperationAtIndex(changeSet, index) {
919
+ const operationsIndex = changeSet.indexes[index];
920
+ if (operationsIndex === undefined) {
921
+ changeSet.indexes[index] = changeSet.operations.push(index) - 1;
782
922
  }
783
- get changed() {
784
- return (Object.entries(this.indexedOperations).length > 0);
923
+ else {
924
+ changeSet.operations[operationsIndex] = index;
785
925
  }
786
- checkIsFiltered(metadata, parent, parentIndex) {
787
- // Detect if current structure has "filters" declared
788
- this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
789
- if (this.isPartiallyFiltered) {
790
- this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
791
- this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
792
- }
793
- // skip if parent is not set
794
- if (!parent) {
795
- return;
796
- }
797
- if (!Metadata.isValidInstance(parent)) {
798
- const parentChangeTree = parent[$changes];
799
- parent = parentChangeTree.parent;
800
- parentIndex = parentChangeTree.parentIndex;
801
- }
802
- const parentMetadata = parent.constructor?.[Symbol.metadata];
803
- this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
926
+ }
927
+ function deleteOperationAtIndex(changeSet, index) {
928
+ const operationsIndex = changeSet.indexes[index];
929
+ if (operationsIndex !== undefined) {
930
+ changeSet.operations[operationsIndex] = undefined;
931
+ }
932
+ delete changeSet.indexes[index];
933
+ }
934
+ function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
935
+ if (root && root[changeSet][queueRootIndex] !== changeTree) {
936
+ changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
937
+ }
938
+ }
939
+ class ChangeTree {
940
+ constructor(ref) {
941
+ this.isFiltered = false;
942
+ this.isPartiallyFiltered = false;
943
+ this.indexedOperations = {};
804
944
  //
805
- // TODO: refactor this!
945
+ // TODO:
946
+ // try storing the index + operation per item.
947
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
806
948
  //
807
- // swapping `changes` and `filteredChanges` is required here
808
- // because "isFiltered" may not be imedialely available on `change()`
949
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
809
950
  //
810
- if (this.isFiltered) {
811
- this.filteredChanges = { indexes: {}, operations: [] };
951
+ this.changes = { indexes: {}, operations: [] };
952
+ this.allChanges = { indexes: {}, operations: [] };
953
+ /**
954
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
955
+ */
956
+ this.isNew = true;
957
+ this.ref = ref;
958
+ //
959
+ // Does this structure have "filters" declared?
960
+ //
961
+ if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
812
962
  this.allFilteredChanges = { indexes: {}, operations: [] };
813
- if (this.changes.operations.length > 0) {
814
- // swap changes reference
815
- const changes = this.changes;
816
- this.changes = this.filteredChanges;
817
- this.filteredChanges = changes;
818
- // swap "all changes" reference
819
- const allFilteredChanges = this.allFilteredChanges;
820
- this.allFilteredChanges = this.allChanges;
821
- this.allChanges = allFilteredChanges;
822
- // console.log("SWAP =>", {
823
- // "this.allFilteredChanges": this.allFilteredChanges,
824
- // "this.allChanges": this.allChanges
825
- // })
963
+ this.filteredChanges = { indexes: {}, operations: [] };
964
+ }
965
+ }
966
+ setRoot(root) {
967
+ this.root = root;
968
+ const isNewChangeTree = this.root.add(this);
969
+ const metadata = this.ref.constructor[Symbol.metadata];
970
+ if (this.root.types.hasFilters) {
971
+ //
972
+ // At Schema initialization, the "root" structure might not be available
973
+ // yet, as it only does once the "Encoder" has been set up.
974
+ //
975
+ // So the "parent" may be already set without a "root".
976
+ //
977
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
978
+ if (this.isFiltered || this.isPartiallyFiltered) {
979
+ enqueueChangeTree(root, this, 'filteredChanges');
980
+ if (isNewChangeTree) {
981
+ this.root.allFilteredChanges.push(this);
982
+ }
826
983
  }
827
984
  }
828
- }
829
- }
830
-
831
- /**
832
- * Copyright (c) 2018 Endel Dreyer
833
- * Copyright (c) 2014 Ion Drive Software Ltd.
834
- *
835
- * Permission is hereby granted, free of charge, to any person obtaining a copy
836
- * of this software and associated documentation files (the "Software"), to deal
837
- * in the Software without restriction, including without limitation the rights
838
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
839
- * copies of the Software, and to permit persons to whom the Software is
840
- * furnished to do so, subject to the following conditions:
841
- *
842
- * The above copyright notice and this permission notice shall be included in all
843
- * copies or substantial portions of the Software.
844
- *
845
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
846
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
847
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
848
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
849
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
850
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
851
- * SOFTWARE
852
- */
853
- /**
854
- * msgpack implementation highly based on notepack.io
855
- * https://github.com/darrachequesne/notepack
856
- */
857
- let textEncoder;
858
- // @ts-ignore
859
- try {
860
- textEncoder = new TextEncoder();
861
- }
862
- catch (e) { }
863
- const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
864
- const utf8Length = (hasBufferByteLength)
865
- ? Buffer.byteLength // node
866
- : function (str, _) {
867
- var c = 0, length = 0;
868
- for (var i = 0, l = str.length; i < l; i++) {
869
- c = str.charCodeAt(i);
870
- if (c < 0x80) {
871
- length += 1;
872
- }
873
- else if (c < 0x800) {
874
- length += 2;
985
+ if (!this.isFiltered) {
986
+ enqueueChangeTree(root, this, 'changes');
987
+ if (isNewChangeTree) {
988
+ this.root.allChanges.push(this);
875
989
  }
876
- else if (c < 0xd800 || c >= 0xe000) {
877
- length += 3;
990
+ }
991
+ // Recursively set root on child structures
992
+ if (metadata) {
993
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
994
+ const field = metadata[index];
995
+ const value = this.ref[field.name];
996
+ value?.[$changes].setRoot(root);
997
+ });
998
+ }
999
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1000
+ // MapSchema / ArraySchema, etc.
1001
+ this.ref.forEach((value, key) => {
1002
+ value[$changes].setRoot(root);
1003
+ });
1004
+ }
1005
+ }
1006
+ setParent(parent, root, parentIndex) {
1007
+ this.parent = parent;
1008
+ this.parentIndex = parentIndex;
1009
+ // avoid setting parents with empty `root`
1010
+ if (!root) {
1011
+ return;
1012
+ }
1013
+ const metadata = this.ref.constructor[Symbol.metadata];
1014
+ // skip if parent is already set
1015
+ if (root !== this.root) {
1016
+ this.root = root;
1017
+ const isNewChangeTree = root.add(this);
1018
+ if (root.types.hasFilters) {
1019
+ this.checkIsFiltered(metadata, parent, parentIndex);
1020
+ if (this.isFiltered || this.isPartiallyFiltered) {
1021
+ enqueueChangeTree(root, this, 'filteredChanges');
1022
+ if (isNewChangeTree) {
1023
+ this.root.allFilteredChanges.push(this);
1024
+ }
1025
+ }
878
1026
  }
879
- else {
880
- i++;
881
- length += 4;
1027
+ if (!this.isFiltered) {
1028
+ enqueueChangeTree(root, this, 'changes');
1029
+ if (isNewChangeTree) {
1030
+ this.root.allChanges.push(this);
1031
+ }
882
1032
  }
883
1033
  }
884
- return length;
885
- };
886
- function utf8Write(view, str, it) {
887
- var c = 0;
888
- for (var i = 0, l = str.length; i < l; i++) {
889
- c = str.charCodeAt(i);
890
- if (c < 0x80) {
891
- view[it.offset++] = c;
1034
+ else {
1035
+ root.add(this);
892
1036
  }
893
- else if (c < 0x800) {
894
- view[it.offset] = 0xc0 | (c >> 6);
895
- view[it.offset + 1] = 0x80 | (c & 0x3f);
896
- it.offset += 2;
1037
+ // assign same parent on child structures
1038
+ if (metadata) {
1039
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
1040
+ const field = metadata[index];
1041
+ const value = this.ref[field.name];
1042
+ value?.[$changes].setParent(this.ref, root, index);
1043
+ // try { throw new Error(); } catch (e) {
1044
+ // console.log(e.stack);
1045
+ // }
1046
+ });
897
1047
  }
898
- else if (c < 0xd800 || c >= 0xe000) {
899
- view[it.offset] = 0xe0 | (c >> 12);
900
- view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
901
- view[it.offset + 2] = 0x80 | (c & 0x3f);
902
- it.offset += 3;
1048
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1049
+ // MapSchema / ArraySchema, etc.
1050
+ this.ref.forEach((value, key) => {
1051
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
1052
+ });
903
1053
  }
904
- else {
905
- i++;
906
- c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
907
- view[it.offset] = 0xf0 | (c >> 18);
908
- view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
909
- view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
910
- view[it.offset + 3] = 0x80 | (c & 0x3f);
911
- it.offset += 4;
1054
+ }
1055
+ forEachChild(callback) {
1056
+ //
1057
+ // assign same parent on child structures
1058
+ //
1059
+ const metadata = this.ref.constructor[Symbol.metadata];
1060
+ if (metadata) {
1061
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
1062
+ const field = metadata[index];
1063
+ const value = this.ref[field.name];
1064
+ if (value) {
1065
+ callback(value[$changes], index);
1066
+ }
1067
+ });
1068
+ }
1069
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1070
+ // MapSchema / ArraySchema, etc.
1071
+ this.ref.forEach((value, key) => {
1072
+ callback(value[$changes], this.indexes[key] ?? key);
1073
+ });
912
1074
  }
913
1075
  }
914
- }
915
- function int8$1(bytes, value, it) {
916
- bytes[it.offset++] = value & 255;
917
- }
918
- function uint8$1(bytes, value, it) {
919
- bytes[it.offset++] = value & 255;
920
- }
921
- function int16$1(bytes, value, it) {
922
- bytes[it.offset++] = value & 255;
923
- bytes[it.offset++] = (value >> 8) & 255;
924
- }
925
- function uint16$1(bytes, value, it) {
926
- bytes[it.offset++] = value & 255;
927
- bytes[it.offset++] = (value >> 8) & 255;
928
- }
929
- function int32$1(bytes, value, it) {
930
- bytes[it.offset++] = value & 255;
931
- bytes[it.offset++] = (value >> 8) & 255;
932
- bytes[it.offset++] = (value >> 16) & 255;
933
- bytes[it.offset++] = (value >> 24) & 255;
934
- }
935
- function uint32$1(bytes, value, it) {
936
- const b4 = value >> 24;
937
- const b3 = value >> 16;
938
- const b2 = value >> 8;
939
- const b1 = value;
940
- bytes[it.offset++] = b1 & 255;
941
- bytes[it.offset++] = b2 & 255;
942
- bytes[it.offset++] = b3 & 255;
943
- bytes[it.offset++] = b4 & 255;
944
- }
945
- function int64$1(bytes, value, it) {
946
- const high = Math.floor(value / Math.pow(2, 32));
947
- const low = value >>> 0;
948
- uint32$1(bytes, low, it);
949
- uint32$1(bytes, high, it);
950
- }
951
- function uint64$1(bytes, value, it) {
952
- const high = (value / Math.pow(2, 32)) >> 0;
953
- const low = value >>> 0;
954
- uint32$1(bytes, low, it);
955
- uint32$1(bytes, high, it);
956
- }
957
- function float32$1(bytes, value, it) {
958
- writeFloat32(bytes, value, it);
959
- }
960
- function float64$1(bytes, value, it) {
961
- writeFloat64(bytes, value, it);
962
- }
963
- const _int32$1 = new Int32Array(2);
964
- const _float32$1 = new Float32Array(_int32$1.buffer);
965
- const _float64$1 = new Float64Array(_int32$1.buffer);
966
- function writeFloat32(bytes, value, it) {
967
- _float32$1[0] = value;
968
- int32$1(bytes, _int32$1[0], it);
969
- }
970
- function writeFloat64(bytes, value, it) {
971
- _float64$1[0] = value;
972
- int32$1(bytes, _int32$1[0 ], it);
973
- int32$1(bytes, _int32$1[1 ], it);
974
- }
975
- function boolean$1(bytes, value, it) {
976
- bytes[it.offset++] = value ? 1 : 0; // uint8
977
- }
978
- function string$1(bytes, value, it) {
979
- // encode `null` strings as empty.
980
- if (!value) {
981
- value = "";
1076
+ operation(op) {
1077
+ // operations without index use negative values to represent them
1078
+ // this is checked during .encode() time.
1079
+ this.changes.operations.push(-op);
1080
+ enqueueChangeTree(this.root, this, 'changes');
982
1081
  }
983
- let length = utf8Length(value, "utf8");
984
- let size = 0;
985
- // fixstr
986
- if (length < 0x20) {
987
- bytes[it.offset++] = length | 0xa0;
988
- size = 1;
1082
+ change(index, operation = OPERATION.ADD) {
1083
+ const metadata = this.ref.constructor[Symbol.metadata];
1084
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
1085
+ const changeSet = (isFiltered)
1086
+ ? this.filteredChanges
1087
+ : this.changes;
1088
+ const previousOperation = this.indexedOperations[index];
1089
+ if (!previousOperation || previousOperation === OPERATION.DELETE) {
1090
+ const op = (!previousOperation)
1091
+ ? operation
1092
+ : (previousOperation === OPERATION.DELETE)
1093
+ ? OPERATION.DELETE_AND_ADD
1094
+ : operation;
1095
+ //
1096
+ // TODO: are DELETE operations being encoded as ADD here ??
1097
+ //
1098
+ this.indexedOperations[index] = op;
1099
+ }
1100
+ setOperationAtIndex(changeSet, index);
1101
+ if (isFiltered) {
1102
+ setOperationAtIndex(this.allFilteredChanges, index);
1103
+ if (this.root) {
1104
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1105
+ enqueueChangeTree(this.root, this, 'allFilteredChanges');
1106
+ }
1107
+ }
1108
+ else {
1109
+ setOperationAtIndex(this.allChanges, index);
1110
+ enqueueChangeTree(this.root, this, 'changes');
1111
+ }
989
1112
  }
990
- // str 8
991
- else if (length < 0x100) {
992
- bytes[it.offset++] = 0xd9;
993
- bytes[it.offset++] = length % 255;
994
- size = 2;
1113
+ shiftChangeIndexes(shiftIndex) {
1114
+ //
1115
+ // Used only during:
1116
+ //
1117
+ // - ArraySchema#unshift()
1118
+ //
1119
+ const changeSet = (this.isFiltered)
1120
+ ? this.filteredChanges
1121
+ : this.changes;
1122
+ const newIndexedOperations = {};
1123
+ const newIndexes = {};
1124
+ for (const index in this.indexedOperations) {
1125
+ newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
1126
+ newIndexes[Number(index) + shiftIndex] = changeSet[index];
1127
+ }
1128
+ this.indexedOperations = newIndexedOperations;
1129
+ changeSet.indexes = newIndexes;
1130
+ changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
995
1131
  }
996
- // str 16
997
- else if (length < 0x10000) {
998
- bytes[it.offset++] = 0xda;
999
- uint16$1(bytes, length, it);
1000
- size = 3;
1132
+ shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
1133
+ //
1134
+ // Used only during:
1135
+ //
1136
+ // - ArraySchema#splice()
1137
+ //
1138
+ if (this.isFiltered || this.isPartiallyFiltered) {
1139
+ this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
1140
+ this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
1141
+ }
1142
+ else {
1143
+ this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
1144
+ }
1001
1145
  }
1002
- // str 32
1003
- else if (length < 0x100000000) {
1004
- bytes[it.offset++] = 0xdb;
1005
- uint32$1(bytes, length, it);
1006
- size = 5;
1146
+ _shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
1147
+ const newIndexes = {};
1148
+ for (const key in changeSet.indexes) {
1149
+ const index = changeSet.indexes[key];
1150
+ if (index > startIndex) {
1151
+ newIndexes[Number(key) + shiftIndex] = index;
1152
+ }
1153
+ else {
1154
+ newIndexes[key] = index;
1155
+ }
1156
+ }
1157
+ changeSet.indexes = newIndexes;
1158
+ for (let i = 0; i < changeSet.operations.length; i++) {
1159
+ const index = changeSet.operations[i];
1160
+ if (index > startIndex) {
1161
+ changeSet.operations[i] = index + shiftIndex;
1162
+ }
1163
+ }
1007
1164
  }
1008
- else {
1009
- throw new Error('String too long');
1165
+ indexedOperation(index, operation, allChangesIndex = index) {
1166
+ this.indexedOperations[index] = operation;
1167
+ if (this.filteredChanges) {
1168
+ setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
1169
+ setOperationAtIndex(this.filteredChanges, index);
1170
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1171
+ }
1172
+ else {
1173
+ setOperationAtIndex(this.allChanges, allChangesIndex);
1174
+ setOperationAtIndex(this.changes, index);
1175
+ enqueueChangeTree(this.root, this, 'changes');
1176
+ }
1010
1177
  }
1011
- utf8Write(bytes, value, it);
1012
- return size + length;
1013
- }
1014
- function number$1(bytes, value, it) {
1015
- if (isNaN(value)) {
1016
- return number$1(bytes, 0, it);
1178
+ getType(index) {
1179
+ if (Metadata.isValidInstance(this.ref)) {
1180
+ const metadata = this.ref.constructor[Symbol.metadata];
1181
+ return metadata[index].type;
1182
+ }
1183
+ else {
1184
+ //
1185
+ // Get the child type from parent structure.
1186
+ // - ["string"] => "string"
1187
+ // - { map: "string" } => "string"
1188
+ // - { set: "string" } => "string"
1189
+ //
1190
+ return this.ref[$childType];
1191
+ }
1017
1192
  }
1018
- else if (!isFinite(value)) {
1019
- return number$1(bytes, (value > 0) ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER, it);
1193
+ getChange(index) {
1194
+ return this.indexedOperations[index];
1020
1195
  }
1021
- else if (value !== (value | 0)) {
1022
- bytes[it.offset++] = 0xcb;
1023
- writeFloat64(bytes, value, it);
1024
- return 9;
1025
- // TODO: encode float 32?
1026
- // is it possible to differentiate between float32 / float64 here?
1027
- // // float 32
1028
- // bytes.push(0xca);
1029
- // writeFloat32(bytes, value);
1030
- // return 5;
1196
+ //
1197
+ // used during `.encode()`
1198
+ //
1199
+ getValue(index, isEncodeAll = false) {
1200
+ //
1201
+ // `isEncodeAll` param is only used by ArraySchema
1202
+ //
1203
+ return this.ref[$getByIndex](index, isEncodeAll);
1031
1204
  }
1032
- if (value >= 0) {
1033
- // positive fixnum
1034
- if (value < 0x80) {
1035
- bytes[it.offset++] = value & 255; // uint8
1036
- return 1;
1205
+ delete(index, operation, allChangesIndex = index) {
1206
+ if (index === undefined) {
1207
+ try {
1208
+ throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
1209
+ }
1210
+ catch (e) {
1211
+ console.warn(e);
1212
+ }
1213
+ return;
1037
1214
  }
1038
- // uint 8
1039
- if (value < 0x100) {
1040
- bytes[it.offset++] = 0xcc;
1041
- bytes[it.offset++] = value & 255; // uint8
1042
- return 2;
1215
+ const changeSet = (this.filteredChanges)
1216
+ ? this.filteredChanges
1217
+ : this.changes;
1218
+ this.indexedOperations[index] = operation ?? OPERATION.DELETE;
1219
+ setOperationAtIndex(changeSet, index);
1220
+ const previousValue = this.getValue(index);
1221
+ // remove `root` reference
1222
+ if (previousValue && previousValue[$changes]) {
1223
+ //
1224
+ // FIXME: this.root is "undefined"
1225
+ //
1226
+ // This method is being called at decoding time when a DELETE operation is found.
1227
+ //
1228
+ // - This is due to using the concrete Schema class at decoding time.
1229
+ // - "Reflected" structures do not have this problem.
1230
+ //
1231
+ // (the property descriptors should NOT be used at decoding time. only at encoding time.)
1232
+ //
1233
+ this.root?.remove(previousValue[$changes]);
1043
1234
  }
1044
- // uint 16
1045
- if (value < 0x10000) {
1046
- bytes[it.offset++] = 0xcd;
1047
- uint16$1(bytes, value, it);
1048
- return 3;
1235
+ //
1236
+ // FIXME: this is looking a ugly and repeated
1237
+ //
1238
+ if (this.filteredChanges) {
1239
+ deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
1240
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1049
1241
  }
1050
- // uint 32
1051
- if (value < 0x100000000) {
1052
- bytes[it.offset++] = 0xce;
1053
- uint32$1(bytes, value, it);
1054
- return 5;
1242
+ else {
1243
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
1244
+ enqueueChangeTree(this.root, this, 'changes');
1055
1245
  }
1056
- // uint 64
1057
- bytes[it.offset++] = 0xcf;
1058
- uint64$1(bytes, value, it);
1059
- return 9;
1060
1246
  }
1061
- else {
1062
- // negative fixnum
1063
- if (value >= -0x20) {
1064
- bytes[it.offset++] = 0xe0 | (value + 0x20);
1065
- return 1;
1247
+ endEncode() {
1248
+ this.indexedOperations = {};
1249
+ // // clear changes
1250
+ // this.changes.indexes = {};
1251
+ // this.changes.operations.length = 0;
1252
+ // ArraySchema and MapSchema have a custom "encode end" method
1253
+ this.ref[$onEncodeEnd]?.();
1254
+ // Not a new instance anymore
1255
+ this.isNew = false;
1256
+ }
1257
+ discard(discardAll = false) {
1258
+ //
1259
+ // > MapSchema:
1260
+ // Remove cached key to ensure ADD operations is unsed instead of
1261
+ // REPLACE in case same key is used on next patches.
1262
+ //
1263
+ this.ref[$onEncodeEnd]?.();
1264
+ this.indexedOperations = {};
1265
+ this.changes.indexes = {};
1266
+ this.changes.operations.length = 0;
1267
+ this.changes.queueRootIndex = undefined;
1268
+ if (this.filteredChanges !== undefined) {
1269
+ this.filteredChanges.indexes = {};
1270
+ this.filteredChanges.operations.length = 0;
1271
+ this.filteredChanges.queueRootIndex = undefined;
1066
1272
  }
1067
- // int 8
1068
- if (value >= -0x80) {
1069
- bytes[it.offset++] = 0xd0;
1070
- int8$1(bytes, value, it);
1071
- return 2;
1273
+ if (discardAll) {
1274
+ this.allChanges.indexes = {};
1275
+ this.allChanges.operations.length = 0;
1276
+ if (this.allFilteredChanges !== undefined) {
1277
+ this.allFilteredChanges.indexes = {};
1278
+ this.allFilteredChanges.operations.length = 0;
1279
+ }
1280
+ // remove children references
1281
+ this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
1282
+ }
1283
+ }
1284
+ /**
1285
+ * Recursively discard all changes from this, and child structures.
1286
+ */
1287
+ discardAll() {
1288
+ const keys = Object.keys(this.indexedOperations);
1289
+ for (let i = 0, len = keys.length; i < len; i++) {
1290
+ const value = this.getValue(Number(keys[i]));
1291
+ if (value && value[$changes]) {
1292
+ value[$changes].discardAll();
1293
+ }
1294
+ }
1295
+ this.discard();
1296
+ }
1297
+ ensureRefId() {
1298
+ // skip if refId is already set.
1299
+ if (this.refId !== undefined) {
1300
+ return;
1301
+ }
1302
+ this.refId = this.root.getNextUniqueId();
1303
+ }
1304
+ get changed() {
1305
+ return (Object.entries(this.indexedOperations).length > 0);
1306
+ }
1307
+ checkIsFiltered(metadata, parent, parentIndex) {
1308
+ // Detect if current structure has "filters" declared
1309
+ this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
1310
+ if (this.isPartiallyFiltered) {
1311
+ this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
1312
+ this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
1072
1313
  }
1073
- // int 16
1074
- if (value >= -0x8000) {
1075
- bytes[it.offset++] = 0xd1;
1076
- int16$1(bytes, value, it);
1077
- return 3;
1314
+ // skip if parent is not set
1315
+ if (!parent) {
1316
+ return;
1078
1317
  }
1079
- // int 32
1080
- if (value >= -0x80000000) {
1081
- bytes[it.offset++] = 0xd2;
1082
- int32$1(bytes, value, it);
1083
- return 5;
1318
+ if (!Metadata.isValidInstance(parent)) {
1319
+ const parentChangeTree = parent[$changes];
1320
+ parent = parentChangeTree.parent;
1321
+ parentIndex = parentChangeTree.parentIndex;
1322
+ }
1323
+ const parentMetadata = parent.constructor?.[Symbol.metadata];
1324
+ this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
1325
+ //
1326
+ // TODO: refactor this!
1327
+ //
1328
+ // swapping `changes` and `filteredChanges` is required here
1329
+ // because "isFiltered" may not be imedialely available on `change()`
1330
+ //
1331
+ if (this.isFiltered) {
1332
+ this.filteredChanges = { indexes: {}, operations: [] };
1333
+ this.allFilteredChanges = { indexes: {}, operations: [] };
1334
+ if (this.changes.operations.length > 0) {
1335
+ // swap changes reference
1336
+ const changes = this.changes;
1337
+ this.changes = this.filteredChanges;
1338
+ this.filteredChanges = changes;
1339
+ // swap "all changes" reference
1340
+ const allFilteredChanges = this.allFilteredChanges;
1341
+ this.allFilteredChanges = this.allChanges;
1342
+ this.allChanges = allFilteredChanges;
1343
+ // console.log("SWAP =>", {
1344
+ // "this.allFilteredChanges": this.allFilteredChanges,
1345
+ // "this.allChanges": this.allChanges
1346
+ // })
1347
+ }
1084
1348
  }
1085
- // int 64
1086
- bytes[it.offset++] = 0xd3;
1087
- int64$1(bytes, value, it);
1088
- return 9;
1089
1349
  }
1090
1350
  }
1091
1351
 
1092
- var encode = /*#__PURE__*/Object.freeze({
1093
- __proto__: null,
1094
- boolean: boolean$1,
1095
- float32: float32$1,
1096
- float64: float64$1,
1097
- int16: int16$1,
1098
- int32: int32$1,
1099
- int64: int64$1,
1100
- int8: int8$1,
1101
- number: number$1,
1102
- string: string$1,
1103
- uint16: uint16$1,
1104
- uint32: uint32$1,
1105
- uint64: uint64$1,
1106
- uint8: uint8$1,
1107
- utf8Length: utf8Length,
1108
- utf8Write: utf8Write,
1109
- writeFloat32: writeFloat32,
1110
- writeFloat64: writeFloat64
1111
- });
1112
-
1113
1352
  function encodeValue(encoder, bytes, type, value, operation, it) {
1114
1353
  if (typeof (type) === "string") {
1115
1354
  encode[type]?.(bytes, value, it);
@@ -1119,7 +1358,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
1119
1358
  // Encode refId for this instance.
1120
1359
  // The actual instance is going to be encoded on next `changeTree` iteration.
1121
1360
  //
1122
- number$1(bytes, value[$changes].refId, it);
1361
+ encode.number(bytes, value[$changes].refId, it);
1123
1362
  // Try to encode inherited TYPE_ID if it's an ADD operation.
1124
1363
  if ((operation & OPERATION.ADD) === OPERATION.ADD) {
1125
1364
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
@@ -1130,7 +1369,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
1130
1369
  // Encode refId for this instance.
1131
1370
  // The actual instance is going to be encoded on next `changeTree` iteration.
1132
1371
  //
1133
- number$1(bytes, value[$changes].refId, it);
1372
+ encode.number(bytes, value[$changes].refId, it);
1134
1373
  }
1135
1374
  }
1136
1375
  /**
@@ -1161,7 +1400,7 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, ope
1161
1400
  return;
1162
1401
  }
1163
1402
  // encode index
1164
- number$1(bytes, index, it);
1403
+ encode.number(bytes, index, it);
1165
1404
  // Do not encode value for DELETE operations
1166
1405
  if (operation === OPERATION.DELETE) {
1167
1406
  return;
@@ -1176,7 +1415,7 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, ope
1176
1415
  // MapSchema dynamic key
1177
1416
  //
1178
1417
  const dynamicIndex = changeTree.ref['$indexes'].get(index);
1179
- string$1(bytes, dynamicIndex, it);
1418
+ encode.string(bytes, dynamicIndex, it);
1180
1419
  }
1181
1420
  }
1182
1421
  const type = ref[$childType];
@@ -1224,7 +1463,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1224
1463
  return;
1225
1464
  }
1226
1465
  // encode index
1227
- number$1(bytes, refOrIndex, it);
1466
+ encode.number(bytes, refOrIndex, it);
1228
1467
  // Do not encode value for DELETE operations
1229
1468
  if (operation === OPERATION.DELETE) {
1230
1469
  return;
@@ -1242,259 +1481,6 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1242
1481
  encodeValue(encoder, bytes, type, value, operation, it);
1243
1482
  };
1244
1483
 
1245
- /**
1246
- * Copyright (c) 2018 Endel Dreyer
1247
- * Copyright (c) 2014 Ion Drive Software Ltd.
1248
- *
1249
- * Permission is hereby granted, free of charge, to any person obtaining a copy
1250
- * of this software and associated documentation files (the "Software"), to deal
1251
- * in the Software without restriction, including without limitation the rights
1252
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1253
- * copies of the Software, and to permit persons to whom the Software is
1254
- * furnished to do so, subject to the following conditions:
1255
- *
1256
- * The above copyright notice and this permission notice shall be included in all
1257
- * copies or substantial portions of the Software.
1258
- *
1259
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1260
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1261
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1262
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1263
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1264
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1265
- * SOFTWARE
1266
- */
1267
- function utf8Read(bytes, it, length) {
1268
- var string = '', chr = 0;
1269
- for (var i = it.offset, end = it.offset + length; i < end; i++) {
1270
- var byte = bytes[i];
1271
- if ((byte & 0x80) === 0x00) {
1272
- string += String.fromCharCode(byte);
1273
- continue;
1274
- }
1275
- if ((byte & 0xe0) === 0xc0) {
1276
- string += String.fromCharCode(((byte & 0x1f) << 6) |
1277
- (bytes[++i] & 0x3f));
1278
- continue;
1279
- }
1280
- if ((byte & 0xf0) === 0xe0) {
1281
- string += String.fromCharCode(((byte & 0x0f) << 12) |
1282
- ((bytes[++i] & 0x3f) << 6) |
1283
- ((bytes[++i] & 0x3f) << 0));
1284
- continue;
1285
- }
1286
- if ((byte & 0xf8) === 0xf0) {
1287
- chr = ((byte & 0x07) << 18) |
1288
- ((bytes[++i] & 0x3f) << 12) |
1289
- ((bytes[++i] & 0x3f) << 6) |
1290
- ((bytes[++i] & 0x3f) << 0);
1291
- if (chr >= 0x010000) { // surrogate pair
1292
- chr -= 0x010000;
1293
- string += String.fromCharCode((chr >>> 10) + 0xD800, (chr & 0x3FF) + 0xDC00);
1294
- }
1295
- else {
1296
- string += String.fromCharCode(chr);
1297
- }
1298
- continue;
1299
- }
1300
- console.error('Invalid byte ' + byte.toString(16));
1301
- // (do not throw error to avoid server/client from crashing due to hack attemps)
1302
- // throw new Error('Invalid byte ' + byte.toString(16));
1303
- }
1304
- it.offset += length;
1305
- return string;
1306
- }
1307
- function int8(bytes, it) {
1308
- return uint8(bytes, it) << 24 >> 24;
1309
- }
1310
- function uint8(bytes, it) {
1311
- return bytes[it.offset++];
1312
- }
1313
- function int16(bytes, it) {
1314
- return uint16(bytes, it) << 16 >> 16;
1315
- }
1316
- function uint16(bytes, it) {
1317
- return bytes[it.offset++] | bytes[it.offset++] << 8;
1318
- }
1319
- function int32(bytes, it) {
1320
- return bytes[it.offset++] | bytes[it.offset++] << 8 | bytes[it.offset++] << 16 | bytes[it.offset++] << 24;
1321
- }
1322
- function uint32(bytes, it) {
1323
- return int32(bytes, it) >>> 0;
1324
- }
1325
- function float32(bytes, it) {
1326
- return readFloat32(bytes, it);
1327
- }
1328
- function float64(bytes, it) {
1329
- return readFloat64(bytes, it);
1330
- }
1331
- function int64(bytes, it) {
1332
- const low = uint32(bytes, it);
1333
- const high = int32(bytes, it) * Math.pow(2, 32);
1334
- return high + low;
1335
- }
1336
- function uint64(bytes, it) {
1337
- const low = uint32(bytes, it);
1338
- const high = uint32(bytes, it) * Math.pow(2, 32);
1339
- return high + low;
1340
- }
1341
- const _int32 = new Int32Array(2);
1342
- const _float32 = new Float32Array(_int32.buffer);
1343
- const _float64 = new Float64Array(_int32.buffer);
1344
- function readFloat32(bytes, it) {
1345
- _int32[0] = int32(bytes, it);
1346
- return _float32[0];
1347
- }
1348
- function readFloat64(bytes, it) {
1349
- _int32[0 ] = int32(bytes, it);
1350
- _int32[1 ] = int32(bytes, it);
1351
- return _float64[0];
1352
- }
1353
- function boolean(bytes, it) {
1354
- return uint8(bytes, it) > 0;
1355
- }
1356
- function string(bytes, it) {
1357
- const prefix = bytes[it.offset++];
1358
- let length;
1359
- if (prefix < 0xc0) {
1360
- // fixstr
1361
- length = prefix & 0x1f;
1362
- }
1363
- else if (prefix === 0xd9) {
1364
- length = uint8(bytes, it);
1365
- }
1366
- else if (prefix === 0xda) {
1367
- length = uint16(bytes, it);
1368
- }
1369
- else if (prefix === 0xdb) {
1370
- length = uint32(bytes, it);
1371
- }
1372
- return utf8Read(bytes, it, length);
1373
- }
1374
- function stringCheck(bytes, it) {
1375
- const prefix = bytes[it.offset];
1376
- return (
1377
- // fixstr
1378
- (prefix < 0xc0 && prefix > 0xa0) ||
1379
- // str 8
1380
- prefix === 0xd9 ||
1381
- // str 16
1382
- prefix === 0xda ||
1383
- // str 32
1384
- prefix === 0xdb);
1385
- }
1386
- function number(bytes, it) {
1387
- const prefix = bytes[it.offset++];
1388
- if (prefix < 0x80) {
1389
- // positive fixint
1390
- return prefix;
1391
- }
1392
- else if (prefix === 0xca) {
1393
- // float 32
1394
- return readFloat32(bytes, it);
1395
- }
1396
- else if (prefix === 0xcb) {
1397
- // float 64
1398
- return readFloat64(bytes, it);
1399
- }
1400
- else if (prefix === 0xcc) {
1401
- // uint 8
1402
- return uint8(bytes, it);
1403
- }
1404
- else if (prefix === 0xcd) {
1405
- // uint 16
1406
- return uint16(bytes, it);
1407
- }
1408
- else if (prefix === 0xce) {
1409
- // uint 32
1410
- return uint32(bytes, it);
1411
- }
1412
- else if (prefix === 0xcf) {
1413
- // uint 64
1414
- return uint64(bytes, it);
1415
- }
1416
- else if (prefix === 0xd0) {
1417
- // int 8
1418
- return int8(bytes, it);
1419
- }
1420
- else if (prefix === 0xd1) {
1421
- // int 16
1422
- return int16(bytes, it);
1423
- }
1424
- else if (prefix === 0xd2) {
1425
- // int 32
1426
- return int32(bytes, it);
1427
- }
1428
- else if (prefix === 0xd3) {
1429
- // int 64
1430
- return int64(bytes, it);
1431
- }
1432
- else if (prefix > 0xdf) {
1433
- // negative fixint
1434
- return (0xff - prefix + 1) * -1;
1435
- }
1436
- }
1437
- function numberCheck(bytes, it) {
1438
- const prefix = bytes[it.offset];
1439
- // positive fixint - 0x00 - 0x7f
1440
- // float 32 - 0xca
1441
- // float 64 - 0xcb
1442
- // uint 8 - 0xcc
1443
- // uint 16 - 0xcd
1444
- // uint 32 - 0xce
1445
- // uint 64 - 0xcf
1446
- // int 8 - 0xd0
1447
- // int 16 - 0xd1
1448
- // int 32 - 0xd2
1449
- // int 64 - 0xd3
1450
- return (prefix < 0x80 ||
1451
- (prefix >= 0xca && prefix <= 0xd3));
1452
- }
1453
- function arrayCheck(bytes, it) {
1454
- return bytes[it.offset] < 0xa0;
1455
- // const prefix = bytes[it.offset] ;
1456
- // if (prefix < 0xa0) {
1457
- // return prefix;
1458
- // // array
1459
- // } else if (prefix === 0xdc) {
1460
- // it.offset += 2;
1461
- // } else if (0xdd) {
1462
- // it.offset += 4;
1463
- // }
1464
- // return prefix;
1465
- }
1466
- function switchStructureCheck(bytes, it) {
1467
- return (
1468
- // previous byte should be `SWITCH_TO_STRUCTURE`
1469
- bytes[it.offset - 1] === SWITCH_TO_STRUCTURE &&
1470
- // next byte should be a number
1471
- (bytes[it.offset] < 0x80 || (bytes[it.offset] >= 0xca && bytes[it.offset] <= 0xd3)));
1472
- }
1473
-
1474
- var decode = /*#__PURE__*/Object.freeze({
1475
- __proto__: null,
1476
- arrayCheck: arrayCheck,
1477
- boolean: boolean,
1478
- float32: float32,
1479
- float64: float64,
1480
- int16: int16,
1481
- int32: int32,
1482
- int64: int64,
1483
- int8: int8,
1484
- number: number,
1485
- numberCheck: numberCheck,
1486
- readFloat32: readFloat32,
1487
- readFloat64: readFloat64,
1488
- string: string,
1489
- stringCheck: stringCheck,
1490
- switchStructureCheck: switchStructureCheck,
1491
- uint16: uint16,
1492
- uint32: uint32,
1493
- uint64: uint64,
1494
- uint8: uint8,
1495
- utf8Read: utf8Read
1496
- });
1497
-
1498
1484
  const DEFINITION_MISMATCH = -1;
1499
1485
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1500
1486
  const $root = decoder.root;
@@ -1529,7 +1515,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1529
1515
  }
1530
1516
  if (operation === OPERATION.DELETE) ;
1531
1517
  else if (Schema.is(type)) {
1532
- const refId = number(bytes, it);
1518
+ const refId = decode.number(bytes, it);
1533
1519
  value = $root.refs.get(refId);
1534
1520
  if (previousValue) {
1535
1521
  const previousRefId = $root.refIds.get(previousValue);
@@ -1558,7 +1544,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1558
1544
  }
1559
1545
  else {
1560
1546
  const typeDef = getType(Object.keys(type)[0]);
1561
- const refId = number(bytes, it);
1547
+ const refId = decode.number(bytes, it);
1562
1548
  const valueRef = ($root.refs.has(refId))
1563
1549
  ? previousValue || $root.refs.get(refId)
1564
1550
  : new typeDef.constructor();
@@ -1637,12 +1623,12 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
1637
1623
  ref.clear();
1638
1624
  return;
1639
1625
  }
1640
- const index = number(bytes, it);
1626
+ const index = decode.number(bytes, it);
1641
1627
  const type = ref[$childType];
1642
1628
  let dynamicIndex;
1643
1629
  if ((operation & OPERATION.ADD) === OPERATION.ADD) { // ADD or DELETE_AND_ADD
1644
1630
  if (typeof (ref['set']) === "function") {
1645
- dynamicIndex = string(bytes, it); // MapSchema
1631
+ dynamicIndex = decode.string(bytes, it); // MapSchema
1646
1632
  ref['setIndex'](index, dynamicIndex);
1647
1633
  }
1648
1634
  else {
@@ -1704,7 +1690,7 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1704
1690
  }
1705
1691
  else if (operation === OPERATION.DELETE_BY_REFID) {
1706
1692
  // TODO: refactor here, try to follow same flow as below
1707
- const refId = number(bytes, it);
1693
+ const refId = decode.number(bytes, it);
1708
1694
  const previousValue = decoder.root.refs.get(refId);
1709
1695
  index = ref.findIndex((value) => value === previousValue);
1710
1696
  ref[$deleteByIndex](index);
@@ -1720,7 +1706,7 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1720
1706
  return;
1721
1707
  }
1722
1708
  else if (operation === OPERATION.ADD_BY_REFID) {
1723
- const refId = number(bytes, it);
1709
+ const refId = decode.number(bytes, it);
1724
1710
  const itemByRefId = decoder.root.refs.get(refId);
1725
1711
  // use existing index, or push new value
1726
1712
  index = (itemByRefId)
@@ -1728,7 +1714,7 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1728
1714
  : ref.length;
1729
1715
  }
1730
1716
  else {
1731
- index = number(bytes, it);
1717
+ index = decode.number(bytes, it);
1732
1718
  }
1733
1719
  const type = ref[$childType];
1734
1720
  let dynamicIndex = index;
@@ -1775,6 +1761,10 @@ function assertType(value, type, klass, field) {
1775
1761
  console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1776
1762
  }
1777
1763
  break;
1764
+ case "bigint64":
1765
+ case "biguint64":
1766
+ typeofTarget = "bigint";
1767
+ break;
1778
1768
  case "string":
1779
1769
  typeofTarget = "string";
1780
1770
  allowNull = true;
@@ -1782,6 +1772,10 @@ function assertType(value, type, klass, field) {
1782
1772
  case "boolean":
1783
1773
  // boolean is always encoded as true/false based on truthiness
1784
1774
  return;
1775
+ default:
1776
+ // skip assertion for custom types
1777
+ // TODO: allow custom types to define their own assertions
1778
+ return;
1785
1779
  }
1786
1780
  if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1787
1781
  let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
@@ -3786,7 +3780,7 @@ class Encoder {
3786
3780
  // (unless it "hasView", which will need to revisit the root)
3787
3781
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3788
3782
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3789
- number$1(buffer, changeTree.refId, it);
3783
+ encode.number(buffer, changeTree.refId, it);
3790
3784
  }
3791
3785
  for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3792
3786
  const fieldIndex = operations.operations[j];
@@ -3915,7 +3909,7 @@ class Encoder {
3915
3909
  const encoder = ctor[$encoder];
3916
3910
  const metadata = ctor[Symbol.metadata];
3917
3911
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3918
- number$1(bytes, changeTree.refId, it);
3912
+ encode.number(bytes, changeTree.refId, it);
3919
3913
  const keys = Object.keys(changes);
3920
3914
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3921
3915
  const key = keys[i];
@@ -3981,7 +3975,7 @@ class Encoder {
3981
3975
  }
3982
3976
  if (baseTypeId !== targetTypeId) {
3983
3977
  bytes[it.offset++] = TYPE_ID & 255;
3984
- number$1(bytes, targetTypeId, it);
3978
+ encode.number(bytes, targetTypeId, it);
3985
3979
  }
3986
3980
  }
3987
3981
  get hasChanges() {
@@ -4141,7 +4135,7 @@ class Decoder {
4141
4135
  //
4142
4136
  if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
4143
4137
  it.offset++;
4144
- this.currentRefId = number(bytes, it);
4138
+ this.currentRefId = decode.number(bytes, it);
4145
4139
  const nextRef = $root.refs.get(this.currentRefId);
4146
4140
  //
4147
4141
  // Trying to access a reference that haven't been decoded yet.
@@ -4163,9 +4157,9 @@ class Decoder {
4163
4157
  //
4164
4158
  const nextIterator = { offset: it.offset };
4165
4159
  while (it.offset < totalBytes) {
4166
- if (switchStructureCheck(bytes, it)) {
4160
+ if (bytes[it.offset] === SWITCH_TO_STRUCTURE) {
4167
4161
  nextIterator.offset = it.offset + 1;
4168
- if ($root.refs.has(number(bytes, nextIterator))) {
4162
+ if ($root.refs.has(decode.number(bytes, nextIterator))) {
4169
4163
  break;
4170
4164
  }
4171
4165
  }
@@ -4186,7 +4180,7 @@ class Decoder {
4186
4180
  let type;
4187
4181
  if (bytes[it.offset] === TYPE_ID) {
4188
4182
  it.offset++;
4189
- const type_id = number(bytes, it);
4183
+ const type_id = decode.number(bytes, it);
4190
4184
  type = this.context.get(type_id);
4191
4185
  }
4192
4186
  return type || defaultType;
@@ -4840,5 +4834,5 @@ registerType("array", { constructor: ArraySchema });
4840
4834
  registerType("set", { constructor: SetSchema });
4841
4835
  registerType("collection", { constructor: CollectionSchema, });
4842
4836
 
4843
- export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, getDecoderStateCallbacks, getRawChangesCallback, registerType, schema, type, view };
4837
+ export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineCustomTypes, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, getDecoderStateCallbacks, getRawChangesCallback, registerType, schema, type, view };
4844
4838
  //# sourceMappingURL=index.mjs.map