@colyseus/schema 3.0.51 → 3.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +1014 -968
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +1014 -968
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +1014 -968
- package/lib/Schema.js +0 -1
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +2 -2
- package/lib/annotations.js +52 -5
- package/lib/annotations.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +0 -2
- package/lib/encoder/ChangeTree.js +18 -45
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/Root.d.ts +4 -2
- package/lib/encoder/Root.js +24 -1
- package/lib/encoder/Root.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +3 -3
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/MapSchema.js +3 -0
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/package.json +1 -1
- package/src/Schema.ts +0 -1
- package/src/annotations.ts +52 -7
- package/src/encoder/ChangeTree.ts +20 -50
- package/src/encoder/Root.ts +34 -3
- package/src/types/custom/ArraySchema.ts +4 -4
- package/src/types/custom/MapSchema.ts +4 -0
package/build/cjs/index.js
CHANGED
|
@@ -963,27 +963,13 @@ const Metadata = {
|
|
|
963
963
|
}
|
|
964
964
|
};
|
|
965
965
|
|
|
966
|
-
function createChangeSet() {
|
|
967
|
-
return { indexes: {}, operations: [] };
|
|
966
|
+
function createChangeSet(queueRootNode) {
|
|
967
|
+
return { indexes: {}, operations: [], queueRootNode };
|
|
968
968
|
}
|
|
969
969
|
// Linked list helper functions
|
|
970
970
|
function createChangeTreeList() {
|
|
971
971
|
return { next: undefined, tail: undefined, length: 0 };
|
|
972
972
|
}
|
|
973
|
-
function addToChangeTreeList(list, changeTree) {
|
|
974
|
-
const node = { changeTree, next: undefined, prev: undefined };
|
|
975
|
-
if (!list.next) {
|
|
976
|
-
list.next = node;
|
|
977
|
-
list.tail = node;
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
node.prev = list.tail;
|
|
981
|
-
list.tail.next = node;
|
|
982
|
-
list.tail = node;
|
|
983
|
-
}
|
|
984
|
-
list.length++;
|
|
985
|
-
return node;
|
|
986
|
-
}
|
|
987
973
|
function setOperationAtIndex(changeSet, index) {
|
|
988
974
|
const operationsIndex = changeSet.indexes[index];
|
|
989
975
|
if (operationsIndex === undefined) {
|
|
@@ -1008,17 +994,6 @@ function deleteOperationAtIndex(changeSet, index) {
|
|
|
1008
994
|
changeSet.operations[operationsIndex] = undefined;
|
|
1009
995
|
delete changeSet.indexes[index];
|
|
1010
996
|
}
|
|
1011
|
-
function enqueueChangeTree(root, changeTree, changeSet, queueRootNode = changeTree[changeSet].queueRootNode) {
|
|
1012
|
-
// skip
|
|
1013
|
-
if (!root) {
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
if (queueRootNode) ;
|
|
1017
|
-
else {
|
|
1018
|
-
// Add to linked list if not already present
|
|
1019
|
-
changeTree[changeSet].queueRootNode = addToChangeTreeList(root[changeSet], changeTree);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
997
|
class ChangeTree {
|
|
1023
998
|
constructor(ref) {
|
|
1024
999
|
/**
|
|
@@ -1124,11 +1099,11 @@ class ChangeTree {
|
|
|
1124
1099
|
// this is checked during .encode() time.
|
|
1125
1100
|
if (this.filteredChanges !== undefined) {
|
|
1126
1101
|
this.filteredChanges.operations.push(-op);
|
|
1127
|
-
|
|
1102
|
+
this.root?.enqueueChangeTree(this, 'filteredChanges');
|
|
1128
1103
|
}
|
|
1129
1104
|
else {
|
|
1130
1105
|
this.changes.operations.push(-op);
|
|
1131
|
-
|
|
1106
|
+
this.root?.enqueueChangeTree(this, 'changes');
|
|
1132
1107
|
}
|
|
1133
1108
|
}
|
|
1134
1109
|
change(index, operation = exports.OPERATION.ADD) {
|
|
@@ -1152,13 +1127,13 @@ class ChangeTree {
|
|
|
1152
1127
|
if (isFiltered) {
|
|
1153
1128
|
setOperationAtIndex(this.allFilteredChanges, index);
|
|
1154
1129
|
if (this.root) {
|
|
1155
|
-
|
|
1156
|
-
|
|
1130
|
+
this.root.enqueueChangeTree(this, 'filteredChanges');
|
|
1131
|
+
this.root.enqueueChangeTree(this, 'allFilteredChanges');
|
|
1157
1132
|
}
|
|
1158
1133
|
}
|
|
1159
1134
|
else {
|
|
1160
1135
|
setOperationAtIndex(this.allChanges, index);
|
|
1161
|
-
|
|
1136
|
+
this.root?.enqueueChangeTree(this, 'changes');
|
|
1162
1137
|
}
|
|
1163
1138
|
}
|
|
1164
1139
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -1213,12 +1188,12 @@ class ChangeTree {
|
|
|
1213
1188
|
if (this.filteredChanges !== undefined) {
|
|
1214
1189
|
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
1215
1190
|
setOperationAtIndex(this.filteredChanges, index);
|
|
1216
|
-
|
|
1191
|
+
this.root?.enqueueChangeTree(this, 'filteredChanges');
|
|
1217
1192
|
}
|
|
1218
1193
|
else {
|
|
1219
1194
|
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
1220
1195
|
setOperationAtIndex(this.changes, index);
|
|
1221
|
-
|
|
1196
|
+
this.root?.enqueueChangeTree(this, 'changes');
|
|
1222
1197
|
}
|
|
1223
1198
|
}
|
|
1224
1199
|
getType(index) {
|
|
@@ -1281,10 +1256,10 @@ class ChangeTree {
|
|
|
1281
1256
|
//
|
|
1282
1257
|
if (this.filteredChanges !== undefined) {
|
|
1283
1258
|
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
1284
|
-
|
|
1259
|
+
this.root?.enqueueChangeTree(this, 'filteredChanges');
|
|
1285
1260
|
}
|
|
1286
1261
|
else {
|
|
1287
|
-
|
|
1262
|
+
this.root?.enqueueChangeTree(this, 'changes');
|
|
1288
1263
|
}
|
|
1289
1264
|
return previousValue;
|
|
1290
1265
|
}
|
|
@@ -1310,9 +1285,10 @@ class ChangeTree {
|
|
|
1310
1285
|
this.filteredChanges = createChangeSet();
|
|
1311
1286
|
}
|
|
1312
1287
|
if (discardAll) {
|
|
1313
|
-
|
|
1288
|
+
// preserve queueRootNode references
|
|
1289
|
+
this.allChanges = createChangeSet(this.allChanges.queueRootNode);
|
|
1314
1290
|
if (this.allFilteredChanges !== undefined) {
|
|
1315
|
-
this.allFilteredChanges = createChangeSet();
|
|
1291
|
+
this.allFilteredChanges = createChangeSet(this.allFilteredChanges.queueRootNode);
|
|
1316
1292
|
}
|
|
1317
1293
|
}
|
|
1318
1294
|
}
|
|
@@ -1343,16 +1319,16 @@ class ChangeTree {
|
|
|
1343
1319
|
//
|
|
1344
1320
|
this._checkFilteredByParent(parent, parentIndex);
|
|
1345
1321
|
if (this.filteredChanges !== undefined) {
|
|
1346
|
-
|
|
1322
|
+
this.root?.enqueueChangeTree(this, 'filteredChanges');
|
|
1347
1323
|
if (isNewChangeTree) {
|
|
1348
|
-
|
|
1324
|
+
this.root?.enqueueChangeTree(this, 'allFilteredChanges');
|
|
1349
1325
|
}
|
|
1350
1326
|
}
|
|
1351
1327
|
}
|
|
1352
1328
|
if (!this.isFiltered) {
|
|
1353
|
-
|
|
1329
|
+
this.root?.enqueueChangeTree(this, 'changes');
|
|
1354
1330
|
if (isNewChangeTree) {
|
|
1355
|
-
|
|
1331
|
+
this.root?.enqueueChangeTree(this, 'allChanges');
|
|
1356
1332
|
}
|
|
1357
1333
|
}
|
|
1358
1334
|
}
|
|
@@ -2209,7 +2185,7 @@ class ArraySchema {
|
|
|
2209
2185
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
2210
2186
|
changeTree.delete(index, exports.OPERATION.DELETE, allChangesIndex);
|
|
2211
2187
|
changeTree.shiftAllChangeIndexes(-1, allChangesIndex);
|
|
2212
|
-
|
|
2188
|
+
this.deletedIndexes[index] = true;
|
|
2213
2189
|
return this.items.shift();
|
|
2214
2190
|
}
|
|
2215
2191
|
/**
|
|
@@ -2304,10 +2280,10 @@ class ArraySchema {
|
|
|
2304
2280
|
// FIXME: this code block is duplicated on ChangeTree
|
|
2305
2281
|
//
|
|
2306
2282
|
if (changeTree.filteredChanges !== undefined) {
|
|
2307
|
-
|
|
2283
|
+
changeTree.root?.enqueueChangeTree(changeTree, 'filteredChanges');
|
|
2308
2284
|
}
|
|
2309
2285
|
else {
|
|
2310
|
-
|
|
2286
|
+
changeTree.root?.enqueueChangeTree(changeTree, 'changes');
|
|
2311
2287
|
}
|
|
2312
2288
|
return this.items.splice(start, deleteCount, ...insertItems);
|
|
2313
2289
|
}
|
|
@@ -2722,6 +2698,9 @@ class MapSchema {
|
|
|
2722
2698
|
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
2723
2699
|
}
|
|
2724
2700
|
}
|
|
2701
|
+
if (this.deletedItems[index]) {
|
|
2702
|
+
delete this.deletedItems[index];
|
|
2703
|
+
}
|
|
2725
2704
|
}
|
|
2726
2705
|
else {
|
|
2727
2706
|
index = changeTree.indexes[$numFields] ?? 0;
|
|
@@ -2841,448 +2820,174 @@ class MapSchema {
|
|
|
2841
2820
|
}
|
|
2842
2821
|
registerType("map", { constructor: MapSchema });
|
|
2843
2822
|
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2875
|
-
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2876
|
-
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2877
|
-
// ?? -1; // no fields defined
|
|
2878
|
-
// fieldIndex++;
|
|
2879
|
-
// if (
|
|
2880
|
-
// !parent && // the parent already initializes the `$changes` property
|
|
2881
|
-
// !Metadata.hasFields(context.metadata)
|
|
2882
|
-
// ) {
|
|
2883
|
-
// context.addInitializer(function (this: Ref) {
|
|
2884
|
-
// Object.defineProperty(this, $changes, {
|
|
2885
|
-
// value: new ChangeTree(this),
|
|
2886
|
-
// enumerable: false,
|
|
2887
|
-
// writable: true
|
|
2888
|
-
// });
|
|
2889
|
-
// });
|
|
2890
|
-
// }
|
|
2891
|
-
// Metadata.addField(context.metadata, fieldIndex, field, type);
|
|
2892
|
-
// const isArray = ArraySchema.is(type);
|
|
2893
|
-
// const isMap = !isArray && MapSchema.is(type);
|
|
2894
|
-
// // if (options && options.manual) {
|
|
2895
|
-
// // // do not declare getter/setter descriptor
|
|
2896
|
-
// // definition.descriptors[field] = {
|
|
2897
|
-
// // enumerable: true,
|
|
2898
|
-
// // configurable: true,
|
|
2899
|
-
// // writable: true,
|
|
2900
|
-
// // };
|
|
2901
|
-
// // return;
|
|
2902
|
-
// // }
|
|
2903
|
-
// return {
|
|
2904
|
-
// init(value) {
|
|
2905
|
-
// // TODO: may need to convert ArraySchema/MapSchema here
|
|
2906
|
-
// // do not flag change if value is undefined.
|
|
2907
|
-
// if (value !== undefined) {
|
|
2908
|
-
// this[$changes].change(fieldIndex);
|
|
2909
|
-
// // automaticallty transform Array into ArraySchema
|
|
2910
|
-
// if (isArray) {
|
|
2911
|
-
// if (!(value instanceof ArraySchema)) {
|
|
2912
|
-
// value = new ArraySchema(...value);
|
|
2913
|
-
// }
|
|
2914
|
-
// value[$childType] = Object.values(type)[0];
|
|
2915
|
-
// }
|
|
2916
|
-
// // automaticallty transform Map into MapSchema
|
|
2917
|
-
// if (isMap) {
|
|
2918
|
-
// if (!(value instanceof MapSchema)) {
|
|
2919
|
-
// value = new MapSchema(value);
|
|
2920
|
-
// }
|
|
2921
|
-
// value[$childType] = Object.values(type)[0];
|
|
2922
|
-
// }
|
|
2923
|
-
// // try to turn provided structure into a Proxy
|
|
2924
|
-
// if (value['$proxy'] === undefined) {
|
|
2925
|
-
// if (isMap) {
|
|
2926
|
-
// value = getMapProxy(value);
|
|
2927
|
-
// }
|
|
2928
|
-
// }
|
|
2929
|
-
// }
|
|
2930
|
-
// return value;
|
|
2931
|
-
// },
|
|
2932
|
-
// get() {
|
|
2933
|
-
// return get.call(this);
|
|
2934
|
-
// },
|
|
2935
|
-
// set(value: any) {
|
|
2936
|
-
// /**
|
|
2937
|
-
// * Create Proxy for array or map items
|
|
2938
|
-
// */
|
|
2939
|
-
// // skip if value is the same as cached.
|
|
2940
|
-
// if (value === get.call(this)) {
|
|
2941
|
-
// return;
|
|
2942
|
-
// }
|
|
2943
|
-
// if (
|
|
2944
|
-
// value !== undefined &&
|
|
2945
|
-
// value !== null
|
|
2946
|
-
// ) {
|
|
2947
|
-
// // automaticallty transform Array into ArraySchema
|
|
2948
|
-
// if (isArray) {
|
|
2949
|
-
// if (!(value instanceof ArraySchema)) {
|
|
2950
|
-
// value = new ArraySchema(...value);
|
|
2951
|
-
// }
|
|
2952
|
-
// value[$childType] = Object.values(type)[0];
|
|
2953
|
-
// }
|
|
2954
|
-
// // automaticallty transform Map into MapSchema
|
|
2955
|
-
// if (isMap) {
|
|
2956
|
-
// if (!(value instanceof MapSchema)) {
|
|
2957
|
-
// value = new MapSchema(value);
|
|
2958
|
-
// }
|
|
2959
|
-
// value[$childType] = Object.values(type)[0];
|
|
2960
|
-
// }
|
|
2961
|
-
// // try to turn provided structure into a Proxy
|
|
2962
|
-
// if (value['$proxy'] === undefined) {
|
|
2963
|
-
// if (isMap) {
|
|
2964
|
-
// value = getMapProxy(value);
|
|
2965
|
-
// }
|
|
2966
|
-
// }
|
|
2967
|
-
// // flag the change for encoding.
|
|
2968
|
-
// this[$changes].change(fieldIndex);
|
|
2969
|
-
// //
|
|
2970
|
-
// // call setParent() recursively for this and its child
|
|
2971
|
-
// // structures.
|
|
2972
|
-
// //
|
|
2973
|
-
// if (value[$changes]) {
|
|
2974
|
-
// value[$changes].setParent(
|
|
2975
|
-
// this,
|
|
2976
|
-
// this[$changes].root,
|
|
2977
|
-
// Metadata.getIndex(context.metadata, field),
|
|
2978
|
-
// );
|
|
2979
|
-
// }
|
|
2980
|
-
// } else if (get.call(this)) {
|
|
2981
|
-
// //
|
|
2982
|
-
// // Setting a field to `null` or `undefined` will delete it.
|
|
2983
|
-
// //
|
|
2984
|
-
// this[$changes].delete(field);
|
|
2985
|
-
// }
|
|
2986
|
-
// set.call(this, value);
|
|
2987
|
-
// },
|
|
2988
|
-
// };
|
|
2989
|
-
// }
|
|
2990
|
-
// }
|
|
2991
|
-
function view(tag = DEFAULT_VIEW_TAG) {
|
|
2992
|
-
return function (target, fieldName) {
|
|
2993
|
-
const constructor = target.constructor;
|
|
2994
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
2995
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
2996
|
-
// TODO: use Metadata.initialize()
|
|
2997
|
-
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2998
|
-
// const fieldIndex = metadata[fieldName];
|
|
2999
|
-
// if (!metadata[fieldIndex]) {
|
|
3000
|
-
// //
|
|
3001
|
-
// // detect index for this field, considering inheritance
|
|
3002
|
-
// //
|
|
3003
|
-
// metadata[fieldIndex] = {
|
|
3004
|
-
// type: undefined,
|
|
3005
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3006
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3007
|
-
// ?? -1) + 1 // no fields defined
|
|
3008
|
-
// }
|
|
3009
|
-
// }
|
|
3010
|
-
Metadata.setTag(metadata, fieldName, tag);
|
|
3011
|
-
};
|
|
3012
|
-
}
|
|
3013
|
-
function type(type, options) {
|
|
3014
|
-
return function (target, field) {
|
|
3015
|
-
const constructor = target.constructor;
|
|
3016
|
-
if (!type) {
|
|
3017
|
-
throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
|
|
2823
|
+
var _a$2, _b$2;
|
|
2824
|
+
class CollectionSchema {
|
|
2825
|
+
static { this[_a$2] = encodeKeyValueOperation; }
|
|
2826
|
+
static { this[_b$2] = decodeKeyValueOperation; }
|
|
2827
|
+
/**
|
|
2828
|
+
* Determine if a property must be filtered.
|
|
2829
|
+
* - If returns false, the property is NOT going to be encoded.
|
|
2830
|
+
* - If returns true, the property is going to be encoded.
|
|
2831
|
+
*
|
|
2832
|
+
* Encoding with "filters" happens in two steps:
|
|
2833
|
+
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2834
|
+
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2835
|
+
*/
|
|
2836
|
+
static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
|
|
2837
|
+
return (!view ||
|
|
2838
|
+
typeof (ref[$childType]) === "string" ||
|
|
2839
|
+
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
2840
|
+
}
|
|
2841
|
+
static is(type) {
|
|
2842
|
+
return type['collection'] !== undefined;
|
|
2843
|
+
}
|
|
2844
|
+
constructor(initialValues) {
|
|
2845
|
+
this.$items = new Map();
|
|
2846
|
+
this.$indexes = new Map();
|
|
2847
|
+
this.deletedItems = {};
|
|
2848
|
+
this.$refId = 0;
|
|
2849
|
+
this[$changes] = new ChangeTree(this);
|
|
2850
|
+
this[$changes].indexes = {};
|
|
2851
|
+
if (initialValues) {
|
|
2852
|
+
initialValues.forEach((v) => this.add(v));
|
|
3018
2853
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
}
|
|
3033
|
-
else if (metadata[fieldIndex].type !== undefined) {
|
|
3034
|
-
// trying to define same property multiple times across inheritance.
|
|
3035
|
-
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
3036
|
-
try {
|
|
3037
|
-
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name}'.\nCheck @type() annotation`);
|
|
3038
|
-
}
|
|
3039
|
-
catch (e) {
|
|
3040
|
-
const definitionAtLine = e.stack.split("\n")[4].trim();
|
|
3041
|
-
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
2854
|
+
Object.defineProperty(this, $childType, {
|
|
2855
|
+
value: undefined,
|
|
2856
|
+
enumerable: false,
|
|
2857
|
+
writable: true,
|
|
2858
|
+
configurable: true,
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
add(value) {
|
|
2862
|
+
// set "index" for reference.
|
|
2863
|
+
const index = this.$refId++;
|
|
2864
|
+
const isRef = (value[$changes]) !== undefined;
|
|
2865
|
+
if (isRef) {
|
|
2866
|
+
value[$changes].setParent(this, this[$changes].root, index);
|
|
3044
2867
|
}
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3051
|
-
?? -1; // no fields defined
|
|
3052
|
-
fieldIndex++;
|
|
3053
|
-
}
|
|
3054
|
-
if (options && options.manual) {
|
|
3055
|
-
Metadata.addField(metadata, fieldIndex, field, type, {
|
|
3056
|
-
// do not declare getter/setter descriptor
|
|
3057
|
-
enumerable: true,
|
|
3058
|
-
configurable: true,
|
|
3059
|
-
writable: true,
|
|
3060
|
-
});
|
|
3061
|
-
}
|
|
3062
|
-
else {
|
|
3063
|
-
const complexTypeKlass = (Array.isArray(type))
|
|
3064
|
-
? getType("array")
|
|
3065
|
-
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
3066
|
-
const childType = (complexTypeKlass)
|
|
3067
|
-
? Object.values(type)[0]
|
|
3068
|
-
: type;
|
|
3069
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
3070
|
-
}
|
|
3071
|
-
};
|
|
3072
|
-
}
|
|
3073
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
3074
|
-
return {
|
|
3075
|
-
get: function () { return this[fieldCached]; },
|
|
3076
|
-
set: function (value) {
|
|
3077
|
-
const previousValue = this[fieldCached] ?? undefined;
|
|
3078
|
-
// skip if value is the same as cached.
|
|
3079
|
-
if (value === previousValue) {
|
|
3080
|
-
return;
|
|
3081
|
-
}
|
|
3082
|
-
if (value !== undefined &&
|
|
3083
|
-
value !== null) {
|
|
3084
|
-
if (complexTypeKlass) {
|
|
3085
|
-
// automaticallty transform Array into ArraySchema
|
|
3086
|
-
if (complexTypeKlass.constructor === ArraySchema && !(value instanceof ArraySchema)) {
|
|
3087
|
-
value = new ArraySchema(...value);
|
|
3088
|
-
}
|
|
3089
|
-
// automaticallty transform Map into MapSchema
|
|
3090
|
-
if (complexTypeKlass.constructor === MapSchema && !(value instanceof MapSchema)) {
|
|
3091
|
-
value = new MapSchema(value);
|
|
3092
|
-
}
|
|
3093
|
-
value[$childType] = type;
|
|
3094
|
-
}
|
|
3095
|
-
else if (typeof (type) !== "string") {
|
|
3096
|
-
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
3097
|
-
}
|
|
3098
|
-
else {
|
|
3099
|
-
assertType(value, type, this, fieldCached.substring(1));
|
|
3100
|
-
}
|
|
3101
|
-
const changeTree = this[$changes];
|
|
3102
|
-
//
|
|
3103
|
-
// Replacing existing "ref", remove it from root.
|
|
3104
|
-
//
|
|
3105
|
-
if (previousValue !== undefined && previousValue[$changes]) {
|
|
3106
|
-
changeTree.root?.remove(previousValue[$changes]);
|
|
3107
|
-
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.DELETE_AND_ADD);
|
|
3108
|
-
}
|
|
3109
|
-
else {
|
|
3110
|
-
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
3111
|
-
}
|
|
3112
|
-
//
|
|
3113
|
-
// call setParent() recursively for this and its child
|
|
3114
|
-
// structures.
|
|
3115
|
-
//
|
|
3116
|
-
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
3117
|
-
}
|
|
3118
|
-
else if (previousValue !== undefined) {
|
|
3119
|
-
//
|
|
3120
|
-
// Setting a field to `null` or `undefined` will delete it.
|
|
3121
|
-
//
|
|
3122
|
-
this[$changes].delete(fieldIndex);
|
|
3123
|
-
}
|
|
3124
|
-
this[fieldCached] = value;
|
|
3125
|
-
},
|
|
3126
|
-
enumerable: true,
|
|
3127
|
-
configurable: true
|
|
3128
|
-
};
|
|
3129
|
-
}
|
|
3130
|
-
/**
|
|
3131
|
-
* `@deprecated()` flag a field as deprecated.
|
|
3132
|
-
* The previous `@type()` annotation should remain along with this one.
|
|
3133
|
-
*/
|
|
3134
|
-
function deprecated(throws = true) {
|
|
3135
|
-
return function (klass, field) {
|
|
3136
|
-
//
|
|
3137
|
-
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
3138
|
-
//
|
|
3139
|
-
const constructor = klass.constructor;
|
|
3140
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
3141
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
3142
|
-
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3143
|
-
const fieldIndex = metadata[field];
|
|
3144
|
-
// if (!metadata[field]) {
|
|
3145
|
-
// //
|
|
3146
|
-
// // detect index for this field, considering inheritance
|
|
3147
|
-
// //
|
|
3148
|
-
// metadata[field] = {
|
|
3149
|
-
// type: undefined,
|
|
3150
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3151
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3152
|
-
// ?? -1) + 1 // no fields defined
|
|
3153
|
-
// }
|
|
3154
|
-
// }
|
|
3155
|
-
metadata[fieldIndex].deprecated = true;
|
|
3156
|
-
if (throws) {
|
|
3157
|
-
metadata[$descriptors] ??= {};
|
|
3158
|
-
metadata[$descriptors][field] = {
|
|
3159
|
-
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
3160
|
-
set: function (value) { },
|
|
3161
|
-
enumerable: false,
|
|
3162
|
-
configurable: true
|
|
3163
|
-
};
|
|
3164
|
-
}
|
|
3165
|
-
// flag metadata[field] as non-enumerable
|
|
3166
|
-
Object.defineProperty(metadata, fieldIndex, {
|
|
3167
|
-
value: metadata[fieldIndex],
|
|
3168
|
-
enumerable: false,
|
|
3169
|
-
configurable: true
|
|
3170
|
-
});
|
|
3171
|
-
};
|
|
3172
|
-
}
|
|
3173
|
-
function defineTypes(target, fields, options) {
|
|
3174
|
-
for (let field in fields) {
|
|
3175
|
-
type(fields[field], options)(target.prototype, field);
|
|
2868
|
+
this[$changes].indexes[index] = index;
|
|
2869
|
+
this.$indexes.set(index, index);
|
|
2870
|
+
this.$items.set(index, value);
|
|
2871
|
+
this[$changes].change(index);
|
|
2872
|
+
return index;
|
|
3176
2873
|
}
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
const
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
2874
|
+
at(index) {
|
|
2875
|
+
const key = Array.from(this.$items.keys())[index];
|
|
2876
|
+
return this.$items.get(key);
|
|
2877
|
+
}
|
|
2878
|
+
entries() {
|
|
2879
|
+
return this.$items.entries();
|
|
2880
|
+
}
|
|
2881
|
+
delete(item) {
|
|
2882
|
+
const entries = this.$items.entries();
|
|
2883
|
+
let index;
|
|
2884
|
+
let entry;
|
|
2885
|
+
while (entry = entries.next()) {
|
|
2886
|
+
if (entry.done) {
|
|
2887
|
+
break;
|
|
3189
2888
|
}
|
|
3190
|
-
if (value[
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
: value['view'];
|
|
2889
|
+
if (item === entry.value[1]) {
|
|
2890
|
+
index = entry.value[0];
|
|
2891
|
+
break;
|
|
3194
2892
|
}
|
|
3195
|
-
fields[fieldName] = value;
|
|
3196
|
-
}
|
|
3197
|
-
else if (typeof (value) === "function") {
|
|
3198
|
-
methods[fieldName] = value;
|
|
3199
2893
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
2894
|
+
if (index === undefined) {
|
|
2895
|
+
return false;
|
|
3202
2896
|
}
|
|
2897
|
+
this.deletedItems[index] = this[$changes].delete(index);
|
|
2898
|
+
this.$indexes.delete(index);
|
|
2899
|
+
return this.$items.delete(index);
|
|
3203
2900
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
}
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
2901
|
+
clear() {
|
|
2902
|
+
const changeTree = this[$changes];
|
|
2903
|
+
// discard previous operations.
|
|
2904
|
+
changeTree.discard(true);
|
|
2905
|
+
changeTree.indexes = {};
|
|
2906
|
+
// remove children references
|
|
2907
|
+
changeTree.forEachChild((childChangeTree, _) => {
|
|
2908
|
+
changeTree.root?.remove(childChangeTree);
|
|
2909
|
+
});
|
|
2910
|
+
// clear previous indexes
|
|
2911
|
+
this.$indexes.clear();
|
|
2912
|
+
// clear items
|
|
2913
|
+
this.$items.clear();
|
|
2914
|
+
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3212
2915
|
}
|
|
3213
|
-
|
|
3214
|
-
|
|
2916
|
+
has(value) {
|
|
2917
|
+
return Array.from(this.$items.values()).some((v) => v === value);
|
|
3215
2918
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
2919
|
+
forEach(callbackfn) {
|
|
2920
|
+
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3218
2921
|
}
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
function getIndent(level) {
|
|
3224
|
-
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
3225
|
-
}
|
|
3226
|
-
function dumpChanges(schema) {
|
|
3227
|
-
const $root = schema[$changes].root;
|
|
3228
|
-
const dump = {
|
|
3229
|
-
ops: {},
|
|
3230
|
-
refs: []
|
|
3231
|
-
};
|
|
3232
|
-
// for (const refId in $root.changes) {
|
|
3233
|
-
let current = $root.changes.next;
|
|
3234
|
-
while (current) {
|
|
3235
|
-
const changeTree = current.changeTree;
|
|
3236
|
-
// skip if ChangeTree is undefined
|
|
3237
|
-
if (changeTree === undefined) {
|
|
3238
|
-
current = current.next;
|
|
3239
|
-
continue;
|
|
3240
|
-
}
|
|
3241
|
-
const changes = changeTree.indexedOperations;
|
|
3242
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3243
|
-
for (const index in changes) {
|
|
3244
|
-
const op = changes[index];
|
|
3245
|
-
const opName = exports.OPERATION[op];
|
|
3246
|
-
if (!dump.ops[opName]) {
|
|
3247
|
-
dump.ops[opName] = 0;
|
|
3248
|
-
}
|
|
3249
|
-
dump.ops[exports.OPERATION[op]]++;
|
|
3250
|
-
}
|
|
3251
|
-
current = current.next;
|
|
2922
|
+
values() {
|
|
2923
|
+
return this.$items.values();
|
|
3252
2924
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
var _a$2, _b$2;
|
|
3257
|
-
/**
|
|
3258
|
-
* Schema encoder / decoder
|
|
3259
|
-
*/
|
|
3260
|
-
class Schema {
|
|
3261
|
-
static { this[_a$2] = encodeSchemaOperation; }
|
|
3262
|
-
static { this[_b$2] = decodeSchemaOperation; }
|
|
3263
|
-
/**
|
|
3264
|
-
* Assign the property descriptors required to track changes on this instance.
|
|
3265
|
-
* @param instance
|
|
3266
|
-
*/
|
|
3267
|
-
static initialize(instance) {
|
|
3268
|
-
Object.defineProperty(instance, $changes, {
|
|
3269
|
-
value: new ChangeTree(instance),
|
|
3270
|
-
enumerable: false,
|
|
3271
|
-
writable: true
|
|
3272
|
-
});
|
|
3273
|
-
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2925
|
+
get size() {
|
|
2926
|
+
return this.$items.size;
|
|
3274
2927
|
}
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
2928
|
+
/** Iterator */
|
|
2929
|
+
[Symbol.iterator]() {
|
|
2930
|
+
return this.$items.values();
|
|
3279
2931
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
2932
|
+
setIndex(index, key) {
|
|
2933
|
+
this.$indexes.set(index, key);
|
|
2934
|
+
}
|
|
2935
|
+
getIndex(index) {
|
|
2936
|
+
return this.$indexes.get(index);
|
|
2937
|
+
}
|
|
2938
|
+
[$getByIndex](index) {
|
|
2939
|
+
return this.$items.get(this.$indexes.get(index));
|
|
2940
|
+
}
|
|
2941
|
+
[$deleteByIndex](index) {
|
|
2942
|
+
const key = this.$indexes.get(index);
|
|
2943
|
+
this.$items.delete(key);
|
|
2944
|
+
this.$indexes.delete(index);
|
|
2945
|
+
}
|
|
2946
|
+
[$onEncodeEnd]() {
|
|
2947
|
+
this.deletedItems = {};
|
|
2948
|
+
}
|
|
2949
|
+
toArray() {
|
|
2950
|
+
return Array.from(this.$items.values());
|
|
2951
|
+
}
|
|
2952
|
+
toJSON() {
|
|
2953
|
+
const values = [];
|
|
2954
|
+
this.forEach((value, key) => {
|
|
2955
|
+
values.push((typeof (value['toJSON']) === "function")
|
|
2956
|
+
? value['toJSON']()
|
|
2957
|
+
: value);
|
|
2958
|
+
});
|
|
2959
|
+
return values;
|
|
2960
|
+
}
|
|
2961
|
+
//
|
|
2962
|
+
// Decoding utilities
|
|
2963
|
+
//
|
|
2964
|
+
clone(isDecoding) {
|
|
2965
|
+
let cloned;
|
|
2966
|
+
if (isDecoding) {
|
|
2967
|
+
// client-side
|
|
2968
|
+
cloned = Object.assign(new CollectionSchema(), this);
|
|
2969
|
+
}
|
|
2970
|
+
else {
|
|
2971
|
+
// server-side
|
|
2972
|
+
cloned = new CollectionSchema();
|
|
2973
|
+
this.forEach((value) => {
|
|
2974
|
+
if (value[$changes]) {
|
|
2975
|
+
cloned.add(value['clone']());
|
|
2976
|
+
}
|
|
2977
|
+
else {
|
|
2978
|
+
cloned.add(value);
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
return cloned;
|
|
3285
2983
|
}
|
|
2984
|
+
}
|
|
2985
|
+
registerType("collection", { constructor: CollectionSchema, });
|
|
2986
|
+
|
|
2987
|
+
var _a$1, _b$1;
|
|
2988
|
+
class SetSchema {
|
|
2989
|
+
static { this[_a$1] = encodeKeyValueOperation; }
|
|
2990
|
+
static { this[_b$1] = decodeKeyValueOperation; }
|
|
3286
2991
|
/**
|
|
3287
2992
|
* Determine if a property must be filtered.
|
|
3288
2993
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3292,418 +2997,652 @@ class Schema {
|
|
|
3292
2997
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3293
2998
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3294
2999
|
*/
|
|
3295
|
-
static [$filter](ref, index, view) {
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
// shared pass/encode: encode if doesn't have a tag
|
|
3300
|
-
return tag === undefined;
|
|
3301
|
-
}
|
|
3302
|
-
else if (tag === undefined) {
|
|
3303
|
-
// view pass: no tag
|
|
3304
|
-
return true;
|
|
3305
|
-
}
|
|
3306
|
-
else if (tag === DEFAULT_VIEW_TAG) {
|
|
3307
|
-
// view pass: default tag
|
|
3308
|
-
return view.isChangeTreeVisible(ref[$changes]);
|
|
3309
|
-
}
|
|
3310
|
-
else {
|
|
3311
|
-
// view pass: custom tag
|
|
3312
|
-
const tags = view.tags?.get(ref[$changes]);
|
|
3313
|
-
return tags && tags.has(tag);
|
|
3314
|
-
}
|
|
3000
|
+
static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
|
|
3001
|
+
return (!view ||
|
|
3002
|
+
typeof (ref[$childType]) === "string" ||
|
|
3003
|
+
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
3315
3004
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
if (
|
|
3327
|
-
|
|
3005
|
+
static is(type) {
|
|
3006
|
+
return type['set'] !== undefined;
|
|
3007
|
+
}
|
|
3008
|
+
constructor(initialValues) {
|
|
3009
|
+
this.$items = new Map();
|
|
3010
|
+
this.$indexes = new Map();
|
|
3011
|
+
this.deletedItems = {};
|
|
3012
|
+
this.$refId = 0;
|
|
3013
|
+
this[$changes] = new ChangeTree(this);
|
|
3014
|
+
this[$changes].indexes = {};
|
|
3015
|
+
if (initialValues) {
|
|
3016
|
+
initialValues.forEach((v) => this.add(v));
|
|
3328
3017
|
}
|
|
3018
|
+
Object.defineProperty(this, $childType, {
|
|
3019
|
+
value: undefined,
|
|
3020
|
+
enumerable: false,
|
|
3021
|
+
writable: true,
|
|
3022
|
+
configurable: true,
|
|
3023
|
+
});
|
|
3329
3024
|
}
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3025
|
+
add(value) {
|
|
3026
|
+
// immediatelly return false if value already added.
|
|
3027
|
+
if (this.has(value)) {
|
|
3028
|
+
return false;
|
|
3029
|
+
}
|
|
3030
|
+
// set "index" for reference.
|
|
3031
|
+
const index = this.$refId++;
|
|
3032
|
+
if ((value[$changes]) !== undefined) {
|
|
3033
|
+
value[$changes].setParent(this, this[$changes].root, index);
|
|
3034
|
+
}
|
|
3035
|
+
const operation = this[$changes].indexes[index]?.op ?? exports.OPERATION.ADD;
|
|
3036
|
+
this[$changes].indexes[index] = index;
|
|
3037
|
+
this.$indexes.set(index, index);
|
|
3038
|
+
this.$items.set(index, value);
|
|
3039
|
+
this[$changes].change(index, operation);
|
|
3040
|
+
return index;
|
|
3333
3041
|
}
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
* @param instance Schema instance
|
|
3337
|
-
* @param property string representing the property name, or number representing the index of the property.
|
|
3338
|
-
* @param operation OPERATION to perform (detected automatically)
|
|
3339
|
-
*/
|
|
3340
|
-
setDirty(property, operation) {
|
|
3341
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
3342
|
-
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
3042
|
+
entries() {
|
|
3043
|
+
return this.$items.entries();
|
|
3343
3044
|
}
|
|
3344
|
-
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
for (const fieldIndex in metadata) {
|
|
3352
|
-
// const field = metadata[metadata[fieldIndex]].name;
|
|
3353
|
-
const field = metadata[fieldIndex].name;
|
|
3354
|
-
if (typeof (this[field]) === "object" &&
|
|
3355
|
-
typeof (this[field]?.clone) === "function") {
|
|
3356
|
-
// deep clone
|
|
3357
|
-
cloned[field] = this[field].clone();
|
|
3045
|
+
delete(item) {
|
|
3046
|
+
const entries = this.$items.entries();
|
|
3047
|
+
let index;
|
|
3048
|
+
let entry;
|
|
3049
|
+
while (entry = entries.next()) {
|
|
3050
|
+
if (entry.done) {
|
|
3051
|
+
break;
|
|
3358
3052
|
}
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3053
|
+
if (item === entry.value[1]) {
|
|
3054
|
+
index = entry.value[0];
|
|
3055
|
+
break;
|
|
3362
3056
|
}
|
|
3363
3057
|
}
|
|
3364
|
-
|
|
3058
|
+
if (index === undefined) {
|
|
3059
|
+
return false;
|
|
3060
|
+
}
|
|
3061
|
+
this.deletedItems[index] = this[$changes].delete(index);
|
|
3062
|
+
this.$indexes.delete(index);
|
|
3063
|
+
return this.$items.delete(index);
|
|
3365
3064
|
}
|
|
3366
|
-
|
|
3367
|
-
const
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3065
|
+
clear() {
|
|
3066
|
+
const changeTree = this[$changes];
|
|
3067
|
+
// discard previous operations.
|
|
3068
|
+
changeTree.discard(true);
|
|
3069
|
+
changeTree.indexes = {};
|
|
3070
|
+
// clear previous indexes
|
|
3071
|
+
this.$indexes.clear();
|
|
3072
|
+
// clear items
|
|
3073
|
+
this.$items.clear();
|
|
3074
|
+
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3075
|
+
}
|
|
3076
|
+
has(value) {
|
|
3077
|
+
const values = this.$items.values();
|
|
3078
|
+
let has = false;
|
|
3079
|
+
let entry;
|
|
3080
|
+
while (entry = values.next()) {
|
|
3081
|
+
if (entry.done) {
|
|
3082
|
+
break;
|
|
3083
|
+
}
|
|
3084
|
+
if (value === entry.value) {
|
|
3085
|
+
has = true;
|
|
3086
|
+
break;
|
|
3376
3087
|
}
|
|
3377
3088
|
}
|
|
3378
|
-
return
|
|
3089
|
+
return has;
|
|
3379
3090
|
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3091
|
+
forEach(callbackfn) {
|
|
3092
|
+
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3093
|
+
}
|
|
3094
|
+
values() {
|
|
3095
|
+
return this.$items.values();
|
|
3096
|
+
}
|
|
3097
|
+
get size() {
|
|
3098
|
+
return this.$items.size;
|
|
3099
|
+
}
|
|
3100
|
+
/** Iterator */
|
|
3101
|
+
[Symbol.iterator]() {
|
|
3102
|
+
return this.$items.values();
|
|
3103
|
+
}
|
|
3104
|
+
setIndex(index, key) {
|
|
3105
|
+
this.$indexes.set(index, key);
|
|
3106
|
+
}
|
|
3107
|
+
getIndex(index) {
|
|
3108
|
+
return this.$indexes.get(index);
|
|
3386
3109
|
}
|
|
3387
3110
|
[$getByIndex](index) {
|
|
3388
|
-
|
|
3389
|
-
return this[metadata[index].name];
|
|
3111
|
+
return this.$items.get(this.$indexes.get(index));
|
|
3390
3112
|
}
|
|
3391
3113
|
[$deleteByIndex](index) {
|
|
3392
|
-
const
|
|
3393
|
-
this
|
|
3114
|
+
const key = this.$indexes.get(index);
|
|
3115
|
+
this.$items.delete(key);
|
|
3116
|
+
this.$indexes.delete(index);
|
|
3394
3117
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
*
|
|
3398
|
-
* @param ref Schema instance
|
|
3399
|
-
* @param showContents display JSON contents of the instance
|
|
3400
|
-
* @returns
|
|
3401
|
-
*/
|
|
3402
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3403
|
-
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3404
|
-
const changeTree = ref[$changes];
|
|
3405
|
-
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
3406
|
-
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3407
|
-
// log reference count if > 1
|
|
3408
|
-
const refCount = (root?.refCount?.[refId] > 1)
|
|
3409
|
-
? ` [×${root.refCount[refId]}]`
|
|
3410
|
-
: '';
|
|
3411
|
-
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3412
|
-
changeTree.forEachChild((childChangeTree, indexOrKey) => {
|
|
3413
|
-
let key = indexOrKey;
|
|
3414
|
-
if (typeof indexOrKey === 'number' && ref['$indexes']) {
|
|
3415
|
-
// MapSchema
|
|
3416
|
-
key = ref['$indexes'].get(indexOrKey) ?? indexOrKey;
|
|
3417
|
-
}
|
|
3418
|
-
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3419
|
-
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3420
|
-
});
|
|
3421
|
-
return output;
|
|
3118
|
+
[$onEncodeEnd]() {
|
|
3119
|
+
this.deletedItems = {};
|
|
3422
3120
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
let current = ref[$changes].root[changeSet].next;
|
|
3426
|
-
while (current) {
|
|
3427
|
-
if (current.changeTree) {
|
|
3428
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3429
|
-
}
|
|
3430
|
-
current = current.next;
|
|
3431
|
-
}
|
|
3432
|
-
return encodeOrder;
|
|
3121
|
+
toArray() {
|
|
3122
|
+
return Array.from(this.$items.values());
|
|
3433
3123
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3124
|
+
toJSON() {
|
|
3125
|
+
const values = [];
|
|
3126
|
+
this.forEach((value, key) => {
|
|
3127
|
+
values.push((typeof (value['toJSON']) === "function")
|
|
3128
|
+
? value['toJSON']()
|
|
3129
|
+
: value);
|
|
3130
|
+
});
|
|
3131
|
+
return values;
|
|
3436
3132
|
}
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3133
|
+
//
|
|
3134
|
+
// Decoding utilities
|
|
3135
|
+
//
|
|
3136
|
+
clone(isDecoding) {
|
|
3137
|
+
let cloned;
|
|
3138
|
+
if (isDecoding) {
|
|
3139
|
+
// client-side
|
|
3140
|
+
cloned = Object.assign(new SetSchema(), this);
|
|
3141
|
+
}
|
|
3142
|
+
else {
|
|
3143
|
+
// server-side
|
|
3144
|
+
cloned = new SetSchema();
|
|
3145
|
+
this.forEach((value) => {
|
|
3146
|
+
if (value[$changes]) {
|
|
3147
|
+
cloned.add(value['clone']());
|
|
3148
|
+
}
|
|
3149
|
+
else {
|
|
3150
|
+
cloned.add(value);
|
|
3151
|
+
}
|
|
3457
3152
|
});
|
|
3458
3153
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3154
|
+
return cloned;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
registerType("set", { constructor: SetSchema });
|
|
3158
|
+
|
|
3159
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
3160
|
+
function entity(constructor) {
|
|
3161
|
+
TypeContext.register(constructor);
|
|
3162
|
+
return constructor;
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
3166
|
+
*
|
|
3167
|
+
* Annotate a Schema property to be serializeable.
|
|
3168
|
+
* \@type()'d fields are automatically flagged as "dirty" for the next patch.
|
|
3169
|
+
*
|
|
3170
|
+
* @example Standard usage, with automatic change tracking.
|
|
3171
|
+
* ```
|
|
3172
|
+
* \@type("string") propertyName: string;
|
|
3173
|
+
* ```
|
|
3174
|
+
*
|
|
3175
|
+
* @example You can provide the "manual" option if you'd like to manually control your patches via .setDirty().
|
|
3176
|
+
* ```
|
|
3177
|
+
* \@type("string", { manual: true })
|
|
3178
|
+
* ```
|
|
3179
|
+
*/
|
|
3180
|
+
// export function type(type: DefinitionType, options?: TypeOptions) {
|
|
3181
|
+
// return function ({ get, set }, context: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<Schema, any> {
|
|
3182
|
+
// if (context.kind !== "accessor") {
|
|
3183
|
+
// throw new Error("@type() is only supported for class accessor properties");
|
|
3184
|
+
// }
|
|
3185
|
+
// const field = context.name.toString();
|
|
3186
|
+
// //
|
|
3187
|
+
// // detect index for this field, considering inheritance
|
|
3188
|
+
// //
|
|
3189
|
+
// const parent = Object.getPrototypeOf(context.metadata);
|
|
3190
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
3191
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
3192
|
+
// ?? -1; // no fields defined
|
|
3193
|
+
// fieldIndex++;
|
|
3194
|
+
// if (
|
|
3195
|
+
// !parent && // the parent already initializes the `$changes` property
|
|
3196
|
+
// !Metadata.hasFields(context.metadata)
|
|
3197
|
+
// ) {
|
|
3198
|
+
// context.addInitializer(function (this: Ref) {
|
|
3199
|
+
// Object.defineProperty(this, $changes, {
|
|
3200
|
+
// value: new ChangeTree(this),
|
|
3201
|
+
// enumerable: false,
|
|
3202
|
+
// writable: true
|
|
3203
|
+
// });
|
|
3204
|
+
// });
|
|
3205
|
+
// }
|
|
3206
|
+
// Metadata.addField(context.metadata, fieldIndex, field, type);
|
|
3207
|
+
// const isArray = ArraySchema.is(type);
|
|
3208
|
+
// const isMap = !isArray && MapSchema.is(type);
|
|
3209
|
+
// // if (options && options.manual) {
|
|
3210
|
+
// // // do not declare getter/setter descriptor
|
|
3211
|
+
// // definition.descriptors[field] = {
|
|
3212
|
+
// // enumerable: true,
|
|
3213
|
+
// // configurable: true,
|
|
3214
|
+
// // writable: true,
|
|
3215
|
+
// // };
|
|
3216
|
+
// // return;
|
|
3217
|
+
// // }
|
|
3218
|
+
// return {
|
|
3219
|
+
// init(value) {
|
|
3220
|
+
// // TODO: may need to convert ArraySchema/MapSchema here
|
|
3221
|
+
// // do not flag change if value is undefined.
|
|
3222
|
+
// if (value !== undefined) {
|
|
3223
|
+
// this[$changes].change(fieldIndex);
|
|
3224
|
+
// // automaticallty transform Array into ArraySchema
|
|
3225
|
+
// if (isArray) {
|
|
3226
|
+
// if (!(value instanceof ArraySchema)) {
|
|
3227
|
+
// value = new ArraySchema(...value);
|
|
3228
|
+
// }
|
|
3229
|
+
// value[$childType] = Object.values(type)[0];
|
|
3230
|
+
// }
|
|
3231
|
+
// // automaticallty transform Map into MapSchema
|
|
3232
|
+
// if (isMap) {
|
|
3233
|
+
// if (!(value instanceof MapSchema)) {
|
|
3234
|
+
// value = new MapSchema(value);
|
|
3235
|
+
// }
|
|
3236
|
+
// value[$childType] = Object.values(type)[0];
|
|
3237
|
+
// }
|
|
3238
|
+
// // try to turn provided structure into a Proxy
|
|
3239
|
+
// if (value['$proxy'] === undefined) {
|
|
3240
|
+
// if (isMap) {
|
|
3241
|
+
// value = getMapProxy(value);
|
|
3242
|
+
// }
|
|
3243
|
+
// }
|
|
3244
|
+
// }
|
|
3245
|
+
// return value;
|
|
3246
|
+
// },
|
|
3247
|
+
// get() {
|
|
3248
|
+
// return get.call(this);
|
|
3249
|
+
// },
|
|
3250
|
+
// set(value: any) {
|
|
3251
|
+
// /**
|
|
3252
|
+
// * Create Proxy for array or map items
|
|
3253
|
+
// */
|
|
3254
|
+
// // skip if value is the same as cached.
|
|
3255
|
+
// if (value === get.call(this)) {
|
|
3256
|
+
// return;
|
|
3257
|
+
// }
|
|
3258
|
+
// if (
|
|
3259
|
+
// value !== undefined &&
|
|
3260
|
+
// value !== null
|
|
3261
|
+
// ) {
|
|
3262
|
+
// // automaticallty transform Array into ArraySchema
|
|
3263
|
+
// if (isArray) {
|
|
3264
|
+
// if (!(value instanceof ArraySchema)) {
|
|
3265
|
+
// value = new ArraySchema(...value);
|
|
3266
|
+
// }
|
|
3267
|
+
// value[$childType] = Object.values(type)[0];
|
|
3268
|
+
// }
|
|
3269
|
+
// // automaticallty transform Map into MapSchema
|
|
3270
|
+
// if (isMap) {
|
|
3271
|
+
// if (!(value instanceof MapSchema)) {
|
|
3272
|
+
// value = new MapSchema(value);
|
|
3273
|
+
// }
|
|
3274
|
+
// value[$childType] = Object.values(type)[0];
|
|
3275
|
+
// }
|
|
3276
|
+
// // try to turn provided structure into a Proxy
|
|
3277
|
+
// if (value['$proxy'] === undefined) {
|
|
3278
|
+
// if (isMap) {
|
|
3279
|
+
// value = getMapProxy(value);
|
|
3280
|
+
// }
|
|
3281
|
+
// }
|
|
3282
|
+
// // flag the change for encoding.
|
|
3283
|
+
// this[$changes].change(fieldIndex);
|
|
3284
|
+
// //
|
|
3285
|
+
// // call setParent() recursively for this and its child
|
|
3286
|
+
// // structures.
|
|
3287
|
+
// //
|
|
3288
|
+
// if (value[$changes]) {
|
|
3289
|
+
// value[$changes].setParent(
|
|
3290
|
+
// this,
|
|
3291
|
+
// this[$changes].root,
|
|
3292
|
+
// Metadata.getIndex(context.metadata, field),
|
|
3293
|
+
// );
|
|
3294
|
+
// }
|
|
3295
|
+
// } else if (get.call(this)) {
|
|
3296
|
+
// //
|
|
3297
|
+
// // Setting a field to `null` or `undefined` will delete it.
|
|
3298
|
+
// //
|
|
3299
|
+
// this[$changes].delete(field);
|
|
3300
|
+
// }
|
|
3301
|
+
// set.call(this, value);
|
|
3302
|
+
// },
|
|
3303
|
+
// };
|
|
3304
|
+
// }
|
|
3305
|
+
// }
|
|
3306
|
+
function view(tag = DEFAULT_VIEW_TAG) {
|
|
3307
|
+
return function (target, fieldName) {
|
|
3308
|
+
const constructor = target.constructor;
|
|
3309
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3310
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3311
|
+
// TODO: use Metadata.initialize()
|
|
3312
|
+
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3313
|
+
// const fieldIndex = metadata[fieldName];
|
|
3314
|
+
// if (!metadata[fieldIndex]) {
|
|
3315
|
+
// //
|
|
3316
|
+
// // detect index for this field, considering inheritance
|
|
3317
|
+
// //
|
|
3318
|
+
// metadata[fieldIndex] = {
|
|
3319
|
+
// type: undefined,
|
|
3320
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3321
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3322
|
+
// ?? -1) + 1 // no fields defined
|
|
3323
|
+
// }
|
|
3324
|
+
// }
|
|
3325
|
+
Metadata.setTag(metadata, fieldName, tag);
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
function type(type, options) {
|
|
3329
|
+
return function (target, field) {
|
|
3330
|
+
const constructor = target.constructor;
|
|
3331
|
+
if (!type) {
|
|
3332
|
+
throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
|
|
3466
3333
|
}
|
|
3467
|
-
//
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3334
|
+
// for inheritance support
|
|
3335
|
+
TypeContext.register(constructor);
|
|
3336
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3337
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3338
|
+
const metadata = Metadata.initialize(constructor);
|
|
3339
|
+
let fieldIndex = metadata[field];
|
|
3340
|
+
/**
|
|
3341
|
+
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
3342
|
+
*/
|
|
3343
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
3344
|
+
if (metadata[fieldIndex].deprecated) {
|
|
3345
|
+
// do not create accessors for deprecated properties.
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
3349
|
+
// trying to define same property multiple times across inheritance.
|
|
3350
|
+
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
3351
|
+
try {
|
|
3352
|
+
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name}'.\nCheck @type() annotation`);
|
|
3353
|
+
}
|
|
3354
|
+
catch (e) {
|
|
3355
|
+
const definitionAtLine = e.stack.split("\n")[4].trim();
|
|
3356
|
+
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
else {
|
|
3361
|
+
//
|
|
3362
|
+
// detect index for this field, considering inheritance
|
|
3363
|
+
//
|
|
3364
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
3365
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3366
|
+
?? -1; // no fields defined
|
|
3367
|
+
fieldIndex++;
|
|
3368
|
+
}
|
|
3369
|
+
if (options && options.manual) {
|
|
3370
|
+
Metadata.addField(metadata, fieldIndex, field, type, {
|
|
3371
|
+
// do not declare getter/setter descriptor
|
|
3372
|
+
enumerable: true,
|
|
3373
|
+
configurable: true,
|
|
3374
|
+
writable: true,
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
else {
|
|
3378
|
+
const complexTypeKlass = (Array.isArray(type))
|
|
3379
|
+
? getType("array")
|
|
3380
|
+
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
3381
|
+
const childType = (complexTypeKlass)
|
|
3382
|
+
? Object.values(type)[0]
|
|
3383
|
+
: type;
|
|
3384
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
3473
3385
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3485
|
-
const changeTree = root.changeTrees[refId];
|
|
3486
|
-
if (!changeTree) {
|
|
3487
|
-
continue;
|
|
3488
|
-
}
|
|
3489
|
-
let includeChangeTree = false;
|
|
3490
|
-
let parentChangeTrees = [];
|
|
3491
|
-
let parentChangeTree = changeTree.parent?.[$changes];
|
|
3492
|
-
if (changeTree === rootChangeTree) {
|
|
3493
|
-
includeChangeTree = true;
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
3389
|
+
return {
|
|
3390
|
+
get: function () { return this[fieldCached]; },
|
|
3391
|
+
set: function (value) {
|
|
3392
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
3393
|
+
// skip if value is the same as cached.
|
|
3394
|
+
if (value === previousValue) {
|
|
3395
|
+
return;
|
|
3494
3396
|
}
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3397
|
+
if (value !== undefined &&
|
|
3398
|
+
value !== null) {
|
|
3399
|
+
if (complexTypeKlass) {
|
|
3400
|
+
// automaticallty transform Array into ArraySchema
|
|
3401
|
+
if (complexTypeKlass.constructor === ArraySchema && !(value instanceof ArraySchema)) {
|
|
3402
|
+
value = new ArraySchema(...value);
|
|
3501
3403
|
}
|
|
3502
|
-
|
|
3404
|
+
// automaticallty transform Map into MapSchema
|
|
3405
|
+
if (complexTypeKlass.constructor === MapSchema && !(value instanceof MapSchema)) {
|
|
3406
|
+
value = new MapSchema(value);
|
|
3407
|
+
}
|
|
3408
|
+
value[$childType] = type;
|
|
3503
3409
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
instanceRefIds.push(changeTree.refId);
|
|
3507
|
-
totalOperations += Object.keys(changes).length;
|
|
3508
|
-
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
output += "---\n";
|
|
3512
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3513
|
-
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3514
|
-
output += `Total changes: ${totalOperations}\n`;
|
|
3515
|
-
output += "---\n";
|
|
3516
|
-
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
3517
|
-
const visitedParents = new WeakSet();
|
|
3518
|
-
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3519
|
-
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3520
|
-
if (!visitedParents.has(parentChangeTree)) {
|
|
3521
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3522
|
-
visitedParents.add(parentChangeTree);
|
|
3410
|
+
else if (typeof (type) !== "string") {
|
|
3411
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
3523
3412
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3413
|
+
else {
|
|
3414
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
3415
|
+
}
|
|
3416
|
+
const changeTree = this[$changes];
|
|
3417
|
+
//
|
|
3418
|
+
// Replacing existing "ref", remove it from root.
|
|
3419
|
+
//
|
|
3420
|
+
if (previousValue !== undefined && previousValue[$changes]) {
|
|
3421
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
3422
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.DELETE_AND_ADD);
|
|
3423
|
+
}
|
|
3424
|
+
else {
|
|
3425
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
3426
|
+
}
|
|
3427
|
+
//
|
|
3428
|
+
// call setParent() recursively for this and its child
|
|
3429
|
+
// structures.
|
|
3430
|
+
//
|
|
3431
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
3533
3432
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3433
|
+
else if (previousValue !== undefined) {
|
|
3434
|
+
//
|
|
3435
|
+
// Setting a field to `null` or `undefined` will delete it.
|
|
3436
|
+
//
|
|
3437
|
+
this[$changes].delete(fieldIndex);
|
|
3438
|
+
}
|
|
3439
|
+
this[fieldCached] = value;
|
|
3440
|
+
},
|
|
3441
|
+
enumerable: true,
|
|
3442
|
+
configurable: true
|
|
3443
|
+
};
|
|
3537
3444
|
}
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3445
|
+
/**
|
|
3446
|
+
* `@deprecated()` flag a field as deprecated.
|
|
3447
|
+
* The previous `@type()` annotation should remain along with this one.
|
|
3448
|
+
*/
|
|
3449
|
+
function deprecated(throws = true) {
|
|
3450
|
+
return function (klass, field) {
|
|
3451
|
+
//
|
|
3452
|
+
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
3453
|
+
//
|
|
3454
|
+
const constructor = klass.constructor;
|
|
3455
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3456
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3457
|
+
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3458
|
+
const fieldIndex = metadata[field];
|
|
3459
|
+
// if (!metadata[field]) {
|
|
3460
|
+
// //
|
|
3461
|
+
// // detect index for this field, considering inheritance
|
|
3462
|
+
// //
|
|
3463
|
+
// metadata[field] = {
|
|
3464
|
+
// type: undefined,
|
|
3465
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3466
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3467
|
+
// ?? -1) + 1 // no fields defined
|
|
3468
|
+
// }
|
|
3469
|
+
// }
|
|
3470
|
+
metadata[fieldIndex].deprecated = true;
|
|
3471
|
+
if (throws) {
|
|
3472
|
+
metadata[$descriptors] ??= {};
|
|
3473
|
+
metadata[$descriptors][field] = {
|
|
3474
|
+
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
3475
|
+
set: function (value) { },
|
|
3476
|
+
enumerable: false,
|
|
3477
|
+
configurable: true
|
|
3478
|
+
};
|
|
3569
3479
|
}
|
|
3570
|
-
|
|
3571
|
-
|
|
3480
|
+
// flag metadata[field] as non-enumerable
|
|
3481
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
3482
|
+
value: metadata[fieldIndex],
|
|
3572
3483
|
enumerable: false,
|
|
3573
|
-
|
|
3574
|
-
configurable: true,
|
|
3484
|
+
configurable: true
|
|
3575
3485
|
});
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
function defineTypes(target, fields, options) {
|
|
3489
|
+
for (let field in fields) {
|
|
3490
|
+
type(fields[field], options)(target.prototype, field);
|
|
3576
3491
|
}
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3492
|
+
return target;
|
|
3493
|
+
}
|
|
3494
|
+
function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
3495
|
+
const fields = {};
|
|
3496
|
+
const methods = {};
|
|
3497
|
+
const defaultValues = {};
|
|
3498
|
+
const viewTagFields = {};
|
|
3499
|
+
for (let fieldName in fieldsAndMethods) {
|
|
3500
|
+
const value = fieldsAndMethods[fieldName];
|
|
3501
|
+
if (typeof (value) === "object") {
|
|
3502
|
+
if (value['view'] !== undefined) {
|
|
3503
|
+
viewTagFields[fieldName] = (typeof (value['view']) === "boolean")
|
|
3504
|
+
? DEFAULT_VIEW_TAG
|
|
3505
|
+
: value['view'];
|
|
3506
|
+
}
|
|
3507
|
+
fields[fieldName] = value;
|
|
3508
|
+
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3509
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3510
|
+
if (Array.isArray(value) || value['array'] !== undefined) {
|
|
3511
|
+
// Collection: Array → new ArraySchema()
|
|
3512
|
+
defaultValues[fieldName] = new ArraySchema();
|
|
3513
|
+
}
|
|
3514
|
+
else if (value['map'] !== undefined) {
|
|
3515
|
+
// Collection: Map → new MapSchema()
|
|
3516
|
+
defaultValues[fieldName] = new MapSchema();
|
|
3517
|
+
}
|
|
3518
|
+
else if (value['collection'] !== undefined) {
|
|
3519
|
+
// Collection: Collection → new CollectionSchema()
|
|
3520
|
+
defaultValues[fieldName] = new CollectionSchema();
|
|
3521
|
+
}
|
|
3522
|
+
else if (value['set'] !== undefined) {
|
|
3523
|
+
// Collection: Set → new SetSchema()
|
|
3524
|
+
defaultValues[fieldName] = new SetSchema();
|
|
3525
|
+
}
|
|
3526
|
+
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3527
|
+
// Direct Schema type: Type → new Type()
|
|
3528
|
+
defaultValues[fieldName] = new value['type']();
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
else {
|
|
3532
|
+
defaultValues[fieldName] = value['default'];
|
|
3533
|
+
}
|
|
3583
3534
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3535
|
+
else if (typeof (value) === "function") {
|
|
3536
|
+
if (Schema.is(value)) {
|
|
3537
|
+
// Direct Schema type: Type → new Type()
|
|
3538
|
+
defaultValues[fieldName] = new value();
|
|
3539
|
+
fields[fieldName] = value;
|
|
3540
|
+
}
|
|
3541
|
+
else {
|
|
3542
|
+
methods[fieldName] = value;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
else {
|
|
3546
|
+
fields[fieldName] = value;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
const getDefaultValues = () => {
|
|
3550
|
+
const defaults = {};
|
|
3551
|
+
for (const fieldName in defaultValues) {
|
|
3552
|
+
const defaultValue = defaultValues[fieldName];
|
|
3553
|
+
// If the default value has a clone method, use it to get a fresh instance
|
|
3554
|
+
if (defaultValue && typeof defaultValue.clone === 'function') {
|
|
3555
|
+
defaults[fieldName] = defaultValue.clone();
|
|
3604
3556
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3557
|
+
else {
|
|
3558
|
+
// Otherwise, use the value as-is (for primitives and non-cloneable objects)
|
|
3559
|
+
defaults[fieldName] = defaultValue;
|
|
3608
3560
|
}
|
|
3609
3561
|
}
|
|
3610
|
-
|
|
3611
|
-
|
|
3562
|
+
return defaults;
|
|
3563
|
+
};
|
|
3564
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
3565
|
+
constructor(...args) {
|
|
3566
|
+
args[0] = Object.assign({}, getDefaultValues(), args[0]);
|
|
3567
|
+
super(...args);
|
|
3612
3568
|
}
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
}
|
|
3617
|
-
clear() {
|
|
3618
|
-
const changeTree = this[$changes];
|
|
3619
|
-
// discard previous operations.
|
|
3620
|
-
changeTree.discard(true);
|
|
3621
|
-
changeTree.indexes = {};
|
|
3622
|
-
// remove children references
|
|
3623
|
-
changeTree.forEachChild((childChangeTree, _) => {
|
|
3624
|
-
changeTree.root?.remove(childChangeTree);
|
|
3625
|
-
});
|
|
3626
|
-
// clear previous indexes
|
|
3627
|
-
this.$indexes.clear();
|
|
3628
|
-
// clear items
|
|
3629
|
-
this.$items.clear();
|
|
3630
|
-
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3631
|
-
}
|
|
3632
|
-
has(value) {
|
|
3633
|
-
return Array.from(this.$items.values()).some((v) => v === value);
|
|
3634
|
-
}
|
|
3635
|
-
forEach(callbackfn) {
|
|
3636
|
-
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3637
|
-
}
|
|
3638
|
-
values() {
|
|
3639
|
-
return this.$items.values();
|
|
3640
|
-
}
|
|
3641
|
-
get size() {
|
|
3642
|
-
return this.$items.size;
|
|
3643
|
-
}
|
|
3644
|
-
/** Iterator */
|
|
3645
|
-
[Symbol.iterator]() {
|
|
3646
|
-
return this.$items.values();
|
|
3647
|
-
}
|
|
3648
|
-
setIndex(index, key) {
|
|
3649
|
-
this.$indexes.set(index, key);
|
|
3650
|
-
}
|
|
3651
|
-
getIndex(index) {
|
|
3652
|
-
return this.$indexes.get(index);
|
|
3653
|
-
}
|
|
3654
|
-
[$getByIndex](index) {
|
|
3655
|
-
return this.$items.get(this.$indexes.get(index));
|
|
3656
|
-
}
|
|
3657
|
-
[$deleteByIndex](index) {
|
|
3658
|
-
const key = this.$indexes.get(index);
|
|
3659
|
-
this.$items.delete(key);
|
|
3660
|
-
this.$indexes.delete(index);
|
|
3661
|
-
}
|
|
3662
|
-
[$onEncodeEnd]() {
|
|
3663
|
-
this.deletedItems = {};
|
|
3569
|
+
}, fields);
|
|
3570
|
+
for (let fieldName in viewTagFields) {
|
|
3571
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
3664
3572
|
}
|
|
3665
|
-
|
|
3666
|
-
|
|
3573
|
+
for (let methodName in methods) {
|
|
3574
|
+
klass.prototype[methodName] = methods[methodName];
|
|
3667
3575
|
}
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
this.forEach((value, key) => {
|
|
3671
|
-
values.push((typeof (value['toJSON']) === "function")
|
|
3672
|
-
? value['toJSON']()
|
|
3673
|
-
: value);
|
|
3674
|
-
});
|
|
3675
|
-
return values;
|
|
3576
|
+
if (name) {
|
|
3577
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
3676
3578
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3579
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
3580
|
+
return klass;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
function getIndent(level) {
|
|
3584
|
+
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
3585
|
+
}
|
|
3586
|
+
function dumpChanges(schema) {
|
|
3587
|
+
const $root = schema[$changes].root;
|
|
3588
|
+
const dump = {
|
|
3589
|
+
ops: {},
|
|
3590
|
+
refs: []
|
|
3591
|
+
};
|
|
3592
|
+
// for (const refId in $root.changes) {
|
|
3593
|
+
let current = $root.changes.next;
|
|
3594
|
+
while (current) {
|
|
3595
|
+
const changeTree = current.changeTree;
|
|
3596
|
+
// skip if ChangeTree is undefined
|
|
3597
|
+
if (changeTree === undefined) {
|
|
3598
|
+
current = current.next;
|
|
3599
|
+
continue;
|
|
3685
3600
|
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
}
|
|
3696
|
-
});
|
|
3601
|
+
const changes = changeTree.indexedOperations;
|
|
3602
|
+
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3603
|
+
for (const index in changes) {
|
|
3604
|
+
const op = changes[index];
|
|
3605
|
+
const opName = exports.OPERATION[op];
|
|
3606
|
+
if (!dump.ops[opName]) {
|
|
3607
|
+
dump.ops[opName] = 0;
|
|
3608
|
+
}
|
|
3609
|
+
dump.ops[exports.OPERATION[op]]++;
|
|
3697
3610
|
}
|
|
3698
|
-
|
|
3611
|
+
current = current.next;
|
|
3699
3612
|
}
|
|
3613
|
+
return dump;
|
|
3700
3614
|
}
|
|
3701
|
-
registerType("collection", { constructor: CollectionSchema, });
|
|
3702
3615
|
|
|
3703
3616
|
var _a, _b;
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3617
|
+
/**
|
|
3618
|
+
* Schema encoder / decoder
|
|
3619
|
+
*/
|
|
3620
|
+
class Schema {
|
|
3621
|
+
static { this[_a] = encodeSchemaOperation; }
|
|
3622
|
+
static { this[_b] = decodeSchemaOperation; }
|
|
3623
|
+
/**
|
|
3624
|
+
* Assign the property descriptors required to track changes on this instance.
|
|
3625
|
+
* @param instance
|
|
3626
|
+
*/
|
|
3627
|
+
static initialize(instance) {
|
|
3628
|
+
Object.defineProperty(instance, $changes, {
|
|
3629
|
+
value: new ChangeTree(instance),
|
|
3630
|
+
enumerable: false,
|
|
3631
|
+
writable: true
|
|
3632
|
+
});
|
|
3633
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3634
|
+
}
|
|
3635
|
+
static is(type) {
|
|
3636
|
+
return typeof (type[Symbol.metadata]) === "object";
|
|
3637
|
+
// const metadata = type[Symbol.metadata];
|
|
3638
|
+
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
3639
|
+
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Track property changes
|
|
3642
|
+
*/
|
|
3643
|
+
static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = exports.OPERATION.ADD) {
|
|
3644
|
+
changeTree.change(index, operation);
|
|
3645
|
+
}
|
|
3707
3646
|
/**
|
|
3708
3647
|
* Determine if a property must be filtered.
|
|
3709
3648
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3713,164 +3652,248 @@ class SetSchema {
|
|
|
3713
3652
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3714
3653
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3715
3654
|
*/
|
|
3716
|
-
static [
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
return type['set'] !== undefined;
|
|
3723
|
-
}
|
|
3724
|
-
constructor(initialValues) {
|
|
3725
|
-
this.$items = new Map();
|
|
3726
|
-
this.$indexes = new Map();
|
|
3727
|
-
this.deletedItems = {};
|
|
3728
|
-
this.$refId = 0;
|
|
3729
|
-
this[$changes] = new ChangeTree(this);
|
|
3730
|
-
this[$changes].indexes = {};
|
|
3731
|
-
if (initialValues) {
|
|
3732
|
-
initialValues.forEach((v) => this.add(v));
|
|
3655
|
+
static [$filter](ref, index, view) {
|
|
3656
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
3657
|
+
const tag = metadata[index]?.tag;
|
|
3658
|
+
if (view === undefined) {
|
|
3659
|
+
// shared pass/encode: encode if doesn't have a tag
|
|
3660
|
+
return tag === undefined;
|
|
3733
3661
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
writable: true,
|
|
3738
|
-
configurable: true,
|
|
3739
|
-
});
|
|
3740
|
-
}
|
|
3741
|
-
add(value) {
|
|
3742
|
-
// immediatelly return false if value already added.
|
|
3743
|
-
if (this.has(value)) {
|
|
3744
|
-
return false;
|
|
3662
|
+
else if (tag === undefined) {
|
|
3663
|
+
// view pass: no tag
|
|
3664
|
+
return true;
|
|
3745
3665
|
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3666
|
+
else if (tag === DEFAULT_VIEW_TAG) {
|
|
3667
|
+
// view pass: default tag
|
|
3668
|
+
return view.isChangeTreeVisible(ref[$changes]);
|
|
3669
|
+
}
|
|
3670
|
+
else {
|
|
3671
|
+
// view pass: custom tag
|
|
3672
|
+
const tags = view.tags?.get(ref[$changes]);
|
|
3673
|
+
return tags && tags.has(tag);
|
|
3750
3674
|
}
|
|
3751
|
-
const operation = this[$changes].indexes[index]?.op ?? exports.OPERATION.ADD;
|
|
3752
|
-
this[$changes].indexes[index] = index;
|
|
3753
|
-
this.$indexes.set(index, index);
|
|
3754
|
-
this.$items.set(index, value);
|
|
3755
|
-
this[$changes].change(index, operation);
|
|
3756
|
-
return index;
|
|
3757
3675
|
}
|
|
3758
|
-
|
|
3759
|
-
|
|
3676
|
+
// allow inherited classes to have a constructor
|
|
3677
|
+
constructor(...args) {
|
|
3678
|
+
//
|
|
3679
|
+
// inline
|
|
3680
|
+
// Schema.initialize(this);
|
|
3681
|
+
//
|
|
3682
|
+
Schema.initialize(this);
|
|
3683
|
+
//
|
|
3684
|
+
// Assign initial values
|
|
3685
|
+
//
|
|
3686
|
+
if (args[0]) {
|
|
3687
|
+
Object.assign(this, args[0]);
|
|
3688
|
+
}
|
|
3760
3689
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3690
|
+
assign(props) {
|
|
3691
|
+
Object.assign(this, props);
|
|
3692
|
+
return this;
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3695
|
+
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3696
|
+
* @param instance Schema instance
|
|
3697
|
+
* @param property string representing the property name, or number representing the index of the property.
|
|
3698
|
+
* @param operation OPERATION to perform (detected automatically)
|
|
3699
|
+
*/
|
|
3700
|
+
setDirty(property, operation) {
|
|
3701
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3702
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
3703
|
+
}
|
|
3704
|
+
clone() {
|
|
3705
|
+
const cloned = new (this.constructor);
|
|
3706
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3707
|
+
//
|
|
3708
|
+
// TODO: clone all properties, not only annotated ones
|
|
3709
|
+
//
|
|
3710
|
+
// for (const field in this) {
|
|
3711
|
+
for (const fieldIndex in metadata) {
|
|
3712
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3713
|
+
const field = metadata[fieldIndex].name;
|
|
3714
|
+
if (typeof (this[field]) === "object" &&
|
|
3715
|
+
typeof (this[field]?.clone) === "function") {
|
|
3716
|
+
// deep clone
|
|
3717
|
+
cloned[field] = this[field].clone();
|
|
3768
3718
|
}
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3719
|
+
else {
|
|
3720
|
+
// primitive values
|
|
3721
|
+
cloned[field] = this[field];
|
|
3772
3722
|
}
|
|
3773
3723
|
}
|
|
3774
|
-
|
|
3775
|
-
return false;
|
|
3776
|
-
}
|
|
3777
|
-
this.deletedItems[index] = this[$changes].delete(index);
|
|
3778
|
-
this.$indexes.delete(index);
|
|
3779
|
-
return this.$items.delete(index);
|
|
3780
|
-
}
|
|
3781
|
-
clear() {
|
|
3782
|
-
const changeTree = this[$changes];
|
|
3783
|
-
// discard previous operations.
|
|
3784
|
-
changeTree.discard(true);
|
|
3785
|
-
changeTree.indexes = {};
|
|
3786
|
-
// clear previous indexes
|
|
3787
|
-
this.$indexes.clear();
|
|
3788
|
-
// clear items
|
|
3789
|
-
this.$items.clear();
|
|
3790
|
-
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3724
|
+
return cloned;
|
|
3791
3725
|
}
|
|
3792
|
-
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
break;
|
|
3726
|
+
toJSON() {
|
|
3727
|
+
const obj = {};
|
|
3728
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3729
|
+
for (const index in metadata) {
|
|
3730
|
+
const field = metadata[index];
|
|
3731
|
+
const fieldName = field.name;
|
|
3732
|
+
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
3733
|
+
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
3734
|
+
? this[fieldName]['toJSON']()
|
|
3735
|
+
: this[fieldName];
|
|
3803
3736
|
}
|
|
3804
3737
|
}
|
|
3805
|
-
return
|
|
3806
|
-
}
|
|
3807
|
-
forEach(callbackfn) {
|
|
3808
|
-
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3809
|
-
}
|
|
3810
|
-
values() {
|
|
3811
|
-
return this.$items.values();
|
|
3812
|
-
}
|
|
3813
|
-
get size() {
|
|
3814
|
-
return this.$items.size;
|
|
3815
|
-
}
|
|
3816
|
-
/** Iterator */
|
|
3817
|
-
[Symbol.iterator]() {
|
|
3818
|
-
return this.$items.values();
|
|
3819
|
-
}
|
|
3820
|
-
setIndex(index, key) {
|
|
3821
|
-
this.$indexes.set(index, key);
|
|
3738
|
+
return obj;
|
|
3822
3739
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3740
|
+
/**
|
|
3741
|
+
* Used in tests only
|
|
3742
|
+
* @internal
|
|
3743
|
+
*/
|
|
3744
|
+
discardAllChanges() {
|
|
3745
|
+
this[$changes].discardAll();
|
|
3825
3746
|
}
|
|
3826
3747
|
[$getByIndex](index) {
|
|
3827
|
-
|
|
3748
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3749
|
+
return this[metadata[index].name];
|
|
3828
3750
|
}
|
|
3829
3751
|
[$deleteByIndex](index) {
|
|
3830
|
-
const
|
|
3831
|
-
this
|
|
3832
|
-
this.$indexes.delete(index);
|
|
3752
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3753
|
+
this[metadata[index].name] = undefined;
|
|
3833
3754
|
}
|
|
3834
|
-
|
|
3835
|
-
|
|
3755
|
+
/**
|
|
3756
|
+
* Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
|
|
3757
|
+
*
|
|
3758
|
+
* @param ref Schema instance
|
|
3759
|
+
* @param showContents display JSON contents of the instance
|
|
3760
|
+
* @returns
|
|
3761
|
+
*/
|
|
3762
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3763
|
+
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3764
|
+
const changeTree = ref[$changes];
|
|
3765
|
+
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
3766
|
+
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3767
|
+
// log reference count if > 1
|
|
3768
|
+
const refCount = (root?.refCount?.[refId] > 1)
|
|
3769
|
+
? ` [×${root.refCount[refId]}]`
|
|
3770
|
+
: '';
|
|
3771
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3772
|
+
changeTree.forEachChild((childChangeTree, indexOrKey) => {
|
|
3773
|
+
let key = indexOrKey;
|
|
3774
|
+
if (typeof indexOrKey === 'number' && ref['$indexes']) {
|
|
3775
|
+
// MapSchema
|
|
3776
|
+
key = ref['$indexes'].get(indexOrKey) ?? indexOrKey;
|
|
3777
|
+
}
|
|
3778
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3779
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3780
|
+
});
|
|
3781
|
+
return output;
|
|
3836
3782
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
3783
|
+
static debugRefIdEncodingOrder(ref, changeSet = 'allChanges') {
|
|
3784
|
+
let encodeOrder = [];
|
|
3785
|
+
let current = ref[$changes].root[changeSet].next;
|
|
3786
|
+
while (current) {
|
|
3787
|
+
if (current.changeTree) {
|
|
3788
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3789
|
+
}
|
|
3790
|
+
current = current.next;
|
|
3791
|
+
}
|
|
3792
|
+
return encodeOrder;
|
|
3839
3793
|
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
this.forEach((value, key) => {
|
|
3843
|
-
values.push((typeof (value['toJSON']) === "function")
|
|
3844
|
-
? value['toJSON']()
|
|
3845
|
-
: value);
|
|
3846
|
-
});
|
|
3847
|
-
return values;
|
|
3794
|
+
static debugRefIdsFromDecoder(decoder) {
|
|
3795
|
+
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3848
3796
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3797
|
+
/**
|
|
3798
|
+
* Return a string representation of the changes on a Schema instance.
|
|
3799
|
+
* The list of changes is cleared after each encode.
|
|
3800
|
+
*
|
|
3801
|
+
* @param instance Schema instance
|
|
3802
|
+
* @param isEncodeAll Return "full encode" instead of current change set.
|
|
3803
|
+
* @returns
|
|
3804
|
+
*/
|
|
3805
|
+
static debugChanges(instance, isEncodeAll = false) {
|
|
3806
|
+
const changeTree = instance[$changes];
|
|
3807
|
+
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3808
|
+
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3809
|
+
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3810
|
+
function dumpChangeSet(changeSet) {
|
|
3811
|
+
changeSet.operations
|
|
3812
|
+
.filter(op => op)
|
|
3813
|
+
.forEach((index) => {
|
|
3814
|
+
const operation = changeTree.indexedOperations[index];
|
|
3815
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3816
|
+
});
|
|
3857
3817
|
}
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3818
|
+
dumpChangeSet(changeSet);
|
|
3819
|
+
// display filtered changes
|
|
3820
|
+
if (!isEncodeAll &&
|
|
3821
|
+
changeTree.filteredChanges &&
|
|
3822
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3823
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3824
|
+
dumpChangeSet(changeTree.filteredChanges);
|
|
3825
|
+
}
|
|
3826
|
+
// display filtered changes
|
|
3827
|
+
if (isEncodeAll &&
|
|
3828
|
+
changeTree.allFilteredChanges &&
|
|
3829
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3830
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3831
|
+
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3832
|
+
}
|
|
3833
|
+
return output;
|
|
3834
|
+
}
|
|
3835
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3836
|
+
let output = "";
|
|
3837
|
+
const rootChangeTree = ref[$changes];
|
|
3838
|
+
const root = rootChangeTree.root;
|
|
3839
|
+
const changeTrees = new Map();
|
|
3840
|
+
const instanceRefIds = [];
|
|
3841
|
+
let totalOperations = 0;
|
|
3842
|
+
// TODO: FIXME: this method is not working as expected
|
|
3843
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3844
|
+
const changeTree = root.changeTrees[refId];
|
|
3845
|
+
if (!changeTree) {
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
let includeChangeTree = false;
|
|
3849
|
+
let parentChangeTrees = [];
|
|
3850
|
+
let parentChangeTree = changeTree.parent?.[$changes];
|
|
3851
|
+
if (changeTree === rootChangeTree) {
|
|
3852
|
+
includeChangeTree = true;
|
|
3853
|
+
}
|
|
3854
|
+
else {
|
|
3855
|
+
while (parentChangeTree !== undefined) {
|
|
3856
|
+
parentChangeTrees.push(parentChangeTree);
|
|
3857
|
+
if (parentChangeTree.ref === ref) {
|
|
3858
|
+
includeChangeTree = true;
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
parentChangeTree = parentChangeTree.parent?.[$changes];
|
|
3864
3862
|
}
|
|
3865
|
-
|
|
3866
|
-
|
|
3863
|
+
}
|
|
3864
|
+
if (includeChangeTree) {
|
|
3865
|
+
instanceRefIds.push(changeTree.refId);
|
|
3866
|
+
totalOperations += Object.keys(changes).length;
|
|
3867
|
+
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
output += "---\n";
|
|
3871
|
+
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3872
|
+
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3873
|
+
output += `Total changes: ${totalOperations}\n`;
|
|
3874
|
+
output += "---\n";
|
|
3875
|
+
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
3876
|
+
const visitedParents = new WeakSet();
|
|
3877
|
+
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3878
|
+
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3879
|
+
if (!visitedParents.has(parentChangeTree)) {
|
|
3880
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3881
|
+
visitedParents.add(parentChangeTree);
|
|
3867
3882
|
}
|
|
3868
3883
|
});
|
|
3884
|
+
const changes = changeTree.indexedOperations;
|
|
3885
|
+
const level = parentChangeTrees.length;
|
|
3886
|
+
const indent = getIndent(level);
|
|
3887
|
+
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3888
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3889
|
+
for (const index in changes) {
|
|
3890
|
+
const operation = changes[index];
|
|
3891
|
+
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3892
|
+
}
|
|
3869
3893
|
}
|
|
3870
|
-
return
|
|
3894
|
+
return `${output}`;
|
|
3871
3895
|
}
|
|
3872
3896
|
}
|
|
3873
|
-
registerType("set", { constructor: SetSchema });
|
|
3874
3897
|
|
|
3875
3898
|
/******************************************************************************
|
|
3876
3899
|
Copyright (c) Microsoft Corporation.
|
|
@@ -3940,11 +3963,12 @@ class Root {
|
|
|
3940
3963
|
}
|
|
3941
3964
|
}
|
|
3942
3965
|
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3943
|
-
// console.log("ADD", { refId: changeTree.refId, refCount: this.refCount[changeTree.refId] });
|
|
3966
|
+
// console.log("ADD", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount: this.refCount[changeTree.refId], isNewChangeTree });
|
|
3944
3967
|
return isNewChangeTree;
|
|
3945
3968
|
}
|
|
3946
3969
|
remove(changeTree) {
|
|
3947
3970
|
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3971
|
+
// console.log("REMOVE", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
|
|
3948
3972
|
if (refCount <= 0) {
|
|
3949
3973
|
//
|
|
3950
3974
|
// Only remove "root" reference if it's the last reference
|
|
@@ -4027,6 +4051,28 @@ class Root {
|
|
|
4027
4051
|
}
|
|
4028
4052
|
changeSet.tail = node;
|
|
4029
4053
|
}
|
|
4054
|
+
enqueueChangeTree(changeTree, changeSet, queueRootNode = changeTree[changeSet].queueRootNode) {
|
|
4055
|
+
// skip
|
|
4056
|
+
if (queueRootNode) {
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
// Add to linked list if not already present
|
|
4060
|
+
changeTree[changeSet].queueRootNode = this.addToChangeTreeList(this[changeSet], changeTree);
|
|
4061
|
+
}
|
|
4062
|
+
addToChangeTreeList(list, changeTree) {
|
|
4063
|
+
const node = { changeTree, next: undefined, prev: undefined };
|
|
4064
|
+
if (!list.next) {
|
|
4065
|
+
list.next = node;
|
|
4066
|
+
list.tail = node;
|
|
4067
|
+
}
|
|
4068
|
+
else {
|
|
4069
|
+
node.prev = list.tail;
|
|
4070
|
+
list.tail.next = node;
|
|
4071
|
+
list.tail = node;
|
|
4072
|
+
}
|
|
4073
|
+
list.length++;
|
|
4074
|
+
return node;
|
|
4075
|
+
}
|
|
4030
4076
|
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
4031
4077
|
const changeSet = this[changeSetName];
|
|
4032
4078
|
const node = changeTree[changeSetName].queueRootNode;
|