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