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