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