@colyseus/schema 3.0.52 → 3.0.54
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 +1013 -946
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +1013 -946
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +1013 -946
- package/lib/Metadata.js +36 -12
- package/lib/Metadata.js.map +1 -1
- package/lib/Schema.js +0 -1
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +2 -2
- package/lib/annotations.js +58 -10
- package/lib/annotations.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +25 -11
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/TypeContext.js +1 -9
- package/lib/types/TypeContext.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +1 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/registry.d.ts +3 -0
- package/lib/types/registry.js +4 -3
- package/lib/types/registry.js.map +1 -1
- package/package.json +1 -1
- package/src/Metadata.ts +41 -13
- package/src/Schema.ts +0 -1
- package/src/annotations.ts +61 -13
- package/src/types/HelperTypes.ts +15 -6
- package/src/types/TypeContext.ts +1 -11
- package/src/types/custom/ArraySchema.ts +1 -1
- package/src/types/registry.ts +2 -1
package/build/esm/index.mjs
CHANGED
|
@@ -683,15 +683,7 @@ class TypeContext {
|
|
|
683
683
|
if (typeof (fieldType) === "string") {
|
|
684
684
|
continue;
|
|
685
685
|
}
|
|
686
|
-
if (
|
|
687
|
-
const type = fieldType[0];
|
|
688
|
-
// skip primitive types
|
|
689
|
-
if (type === "string") {
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
|
|
693
|
-
}
|
|
694
|
-
else if (typeof (fieldType) === "function") {
|
|
686
|
+
if (typeof (fieldType) === "function") {
|
|
695
687
|
this.discoverTypes(fieldType, klass, index, parentHasViewTag || fieldHasViewTag);
|
|
696
688
|
}
|
|
697
689
|
else {
|
|
@@ -741,11 +733,43 @@ class TypeContext {
|
|
|
741
733
|
}
|
|
742
734
|
|
|
743
735
|
function getNormalizedType(type) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
736
|
+
if (Array.isArray(type)) {
|
|
737
|
+
return { array: getNormalizedType(type[0]) };
|
|
738
|
+
}
|
|
739
|
+
else if (typeof (type['type']) !== "undefined") {
|
|
740
|
+
return type['type'];
|
|
741
|
+
}
|
|
742
|
+
else if (isTSEnum(type)) {
|
|
743
|
+
// Detect TS Enum type (either string or number)
|
|
744
|
+
return Object.keys(type).every(key => typeof type[key] === "string")
|
|
745
|
+
? "string"
|
|
746
|
+
: "number";
|
|
747
|
+
}
|
|
748
|
+
else if (typeof type === "object" && type !== null) {
|
|
749
|
+
// Handle collection types
|
|
750
|
+
const collectionType = Object.keys(type).find(k => registeredTypes[k] !== undefined);
|
|
751
|
+
if (collectionType) {
|
|
752
|
+
type[collectionType] = getNormalizedType(type[collectionType]);
|
|
753
|
+
return type;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return type;
|
|
757
|
+
}
|
|
758
|
+
function isTSEnum(_enum) {
|
|
759
|
+
if (typeof _enum === 'function' && _enum[Symbol.metadata]) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
const keys = Object.keys(_enum);
|
|
763
|
+
const numericFields = keys.filter(k => /\d+/.test(k));
|
|
764
|
+
// Check for number enum (has numeric keys and reverse mapping)
|
|
765
|
+
if (numericFields.length > 0 && numericFields.length === (keys.length / 2) && _enum[_enum[numericFields[0]]] == numericFields[0]) {
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
// Check for string enum (all values are strings and keys match values)
|
|
769
|
+
if (keys.length > 0 && keys.every(key => typeof _enum[key] === 'string' && _enum[key] === key)) {
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
return false;
|
|
749
773
|
}
|
|
750
774
|
const Metadata = {
|
|
751
775
|
addField(metadata, index, name, type, descriptor) {
|
|
@@ -860,14 +884,12 @@ const Metadata = {
|
|
|
860
884
|
?? -1; // no fields defined
|
|
861
885
|
fieldIndex++;
|
|
862
886
|
for (const field in fields) {
|
|
863
|
-
const type = fields[field];
|
|
887
|
+
const type = getNormalizedType(fields[field]);
|
|
864
888
|
// FIXME: this code is duplicated from @type() annotation
|
|
865
|
-
const complexTypeKlass = (
|
|
866
|
-
? getType("array")
|
|
867
|
-
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
889
|
+
const complexTypeKlass = typeof (Object.keys(type)[0]) === "string" && getType(Object.keys(type)[0]);
|
|
868
890
|
const childType = (complexTypeKlass)
|
|
869
891
|
? Object.values(type)[0]
|
|
870
|
-
:
|
|
892
|
+
: type;
|
|
871
893
|
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
872
894
|
fieldIndex++;
|
|
873
895
|
}
|
|
@@ -2183,7 +2205,7 @@ class ArraySchema {
|
|
|
2183
2205
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
2184
2206
|
changeTree.delete(index, OPERATION.DELETE, allChangesIndex);
|
|
2185
2207
|
changeTree.shiftAllChangeIndexes(-1, allChangesIndex);
|
|
2186
|
-
|
|
2208
|
+
this.deletedIndexes[index] = true;
|
|
2187
2209
|
return this.items.shift();
|
|
2188
2210
|
}
|
|
2189
2211
|
/**
|
|
@@ -2818,448 +2840,174 @@ class MapSchema {
|
|
|
2818
2840
|
}
|
|
2819
2841
|
registerType("map", { constructor: MapSchema });
|
|
2820
2842
|
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2852
|
-
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2853
|
-
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2854
|
-
// ?? -1; // no fields defined
|
|
2855
|
-
// fieldIndex++;
|
|
2856
|
-
// if (
|
|
2857
|
-
// !parent && // the parent already initializes the `$changes` property
|
|
2858
|
-
// !Metadata.hasFields(context.metadata)
|
|
2859
|
-
// ) {
|
|
2860
|
-
// context.addInitializer(function (this: Ref) {
|
|
2861
|
-
// Object.defineProperty(this, $changes, {
|
|
2862
|
-
// value: new ChangeTree(this),
|
|
2863
|
-
// enumerable: false,
|
|
2864
|
-
// writable: true
|
|
2865
|
-
// });
|
|
2866
|
-
// });
|
|
2867
|
-
// }
|
|
2868
|
-
// Metadata.addField(context.metadata, fieldIndex, field, type);
|
|
2869
|
-
// const isArray = ArraySchema.is(type);
|
|
2870
|
-
// const isMap = !isArray && MapSchema.is(type);
|
|
2871
|
-
// // if (options && options.manual) {
|
|
2872
|
-
// // // do not declare getter/setter descriptor
|
|
2873
|
-
// // definition.descriptors[field] = {
|
|
2874
|
-
// // enumerable: true,
|
|
2875
|
-
// // configurable: true,
|
|
2876
|
-
// // writable: true,
|
|
2877
|
-
// // };
|
|
2878
|
-
// // return;
|
|
2879
|
-
// // }
|
|
2880
|
-
// return {
|
|
2881
|
-
// init(value) {
|
|
2882
|
-
// // TODO: may need to convert ArraySchema/MapSchema here
|
|
2883
|
-
// // do not flag change if value is undefined.
|
|
2884
|
-
// if (value !== undefined) {
|
|
2885
|
-
// this[$changes].change(fieldIndex);
|
|
2886
|
-
// // automaticallty transform Array into ArraySchema
|
|
2887
|
-
// if (isArray) {
|
|
2888
|
-
// if (!(value instanceof ArraySchema)) {
|
|
2889
|
-
// value = new ArraySchema(...value);
|
|
2890
|
-
// }
|
|
2891
|
-
// value[$childType] = Object.values(type)[0];
|
|
2892
|
-
// }
|
|
2893
|
-
// // automaticallty transform Map into MapSchema
|
|
2894
|
-
// if (isMap) {
|
|
2895
|
-
// if (!(value instanceof MapSchema)) {
|
|
2896
|
-
// value = new MapSchema(value);
|
|
2897
|
-
// }
|
|
2898
|
-
// value[$childType] = Object.values(type)[0];
|
|
2899
|
-
// }
|
|
2900
|
-
// // try to turn provided structure into a Proxy
|
|
2901
|
-
// if (value['$proxy'] === undefined) {
|
|
2902
|
-
// if (isMap) {
|
|
2903
|
-
// value = getMapProxy(value);
|
|
2904
|
-
// }
|
|
2905
|
-
// }
|
|
2906
|
-
// }
|
|
2907
|
-
// return value;
|
|
2908
|
-
// },
|
|
2909
|
-
// get() {
|
|
2910
|
-
// return get.call(this);
|
|
2911
|
-
// },
|
|
2912
|
-
// set(value: any) {
|
|
2913
|
-
// /**
|
|
2914
|
-
// * Create Proxy for array or map items
|
|
2915
|
-
// */
|
|
2916
|
-
// // skip if value is the same as cached.
|
|
2917
|
-
// if (value === get.call(this)) {
|
|
2918
|
-
// return;
|
|
2919
|
-
// }
|
|
2920
|
-
// if (
|
|
2921
|
-
// value !== undefined &&
|
|
2922
|
-
// value !== null
|
|
2923
|
-
// ) {
|
|
2924
|
-
// // automaticallty transform Array into ArraySchema
|
|
2925
|
-
// if (isArray) {
|
|
2926
|
-
// if (!(value instanceof ArraySchema)) {
|
|
2927
|
-
// value = new ArraySchema(...value);
|
|
2928
|
-
// }
|
|
2929
|
-
// value[$childType] = Object.values(type)[0];
|
|
2930
|
-
// }
|
|
2931
|
-
// // automaticallty transform Map into MapSchema
|
|
2932
|
-
// if (isMap) {
|
|
2933
|
-
// if (!(value instanceof MapSchema)) {
|
|
2934
|
-
// value = new MapSchema(value);
|
|
2935
|
-
// }
|
|
2936
|
-
// value[$childType] = Object.values(type)[0];
|
|
2937
|
-
// }
|
|
2938
|
-
// // try to turn provided structure into a Proxy
|
|
2939
|
-
// if (value['$proxy'] === undefined) {
|
|
2940
|
-
// if (isMap) {
|
|
2941
|
-
// value = getMapProxy(value);
|
|
2942
|
-
// }
|
|
2943
|
-
// }
|
|
2944
|
-
// // flag the change for encoding.
|
|
2945
|
-
// this[$changes].change(fieldIndex);
|
|
2946
|
-
// //
|
|
2947
|
-
// // call setParent() recursively for this and its child
|
|
2948
|
-
// // structures.
|
|
2949
|
-
// //
|
|
2950
|
-
// if (value[$changes]) {
|
|
2951
|
-
// value[$changes].setParent(
|
|
2952
|
-
// this,
|
|
2953
|
-
// this[$changes].root,
|
|
2954
|
-
// Metadata.getIndex(context.metadata, field),
|
|
2955
|
-
// );
|
|
2956
|
-
// }
|
|
2957
|
-
// } else if (get.call(this)) {
|
|
2958
|
-
// //
|
|
2959
|
-
// // Setting a field to `null` or `undefined` will delete it.
|
|
2960
|
-
// //
|
|
2961
|
-
// this[$changes].delete(field);
|
|
2962
|
-
// }
|
|
2963
|
-
// set.call(this, value);
|
|
2964
|
-
// },
|
|
2965
|
-
// };
|
|
2966
|
-
// }
|
|
2967
|
-
// }
|
|
2968
|
-
function view(tag = DEFAULT_VIEW_TAG) {
|
|
2969
|
-
return function (target, fieldName) {
|
|
2970
|
-
const constructor = target.constructor;
|
|
2971
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
2972
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
2973
|
-
// TODO: use Metadata.initialize()
|
|
2974
|
-
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2975
|
-
// const fieldIndex = metadata[fieldName];
|
|
2976
|
-
// if (!metadata[fieldIndex]) {
|
|
2977
|
-
// //
|
|
2978
|
-
// // detect index for this field, considering inheritance
|
|
2979
|
-
// //
|
|
2980
|
-
// metadata[fieldIndex] = {
|
|
2981
|
-
// type: undefined,
|
|
2982
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2983
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2984
|
-
// ?? -1) + 1 // no fields defined
|
|
2985
|
-
// }
|
|
2986
|
-
// }
|
|
2987
|
-
Metadata.setTag(metadata, fieldName, tag);
|
|
2988
|
-
};
|
|
2989
|
-
}
|
|
2990
|
-
function type(type, options) {
|
|
2991
|
-
return function (target, field) {
|
|
2992
|
-
const constructor = target.constructor;
|
|
2993
|
-
if (!type) {
|
|
2994
|
-
throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
|
|
2995
|
-
}
|
|
2996
|
-
// for inheritance support
|
|
2997
|
-
TypeContext.register(constructor);
|
|
2998
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
2999
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
3000
|
-
const metadata = Metadata.initialize(constructor);
|
|
3001
|
-
let fieldIndex = metadata[field];
|
|
3002
|
-
/**
|
|
3003
|
-
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
3004
|
-
*/
|
|
3005
|
-
if (metadata[fieldIndex] !== undefined) {
|
|
3006
|
-
if (metadata[fieldIndex].deprecated) {
|
|
3007
|
-
// do not create accessors for deprecated properties.
|
|
3008
|
-
return;
|
|
3009
|
-
}
|
|
3010
|
-
else if (metadata[fieldIndex].type !== undefined) {
|
|
3011
|
-
// trying to define same property multiple times across inheritance.
|
|
3012
|
-
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
3013
|
-
try {
|
|
3014
|
-
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name}'.\nCheck @type() annotation`);
|
|
3015
|
-
}
|
|
3016
|
-
catch (e) {
|
|
3017
|
-
const definitionAtLine = e.stack.split("\n")[4].trim();
|
|
3018
|
-
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
else {
|
|
3023
|
-
//
|
|
3024
|
-
// detect index for this field, considering inheritance
|
|
3025
|
-
//
|
|
3026
|
-
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
3027
|
-
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3028
|
-
?? -1; // no fields defined
|
|
3029
|
-
fieldIndex++;
|
|
2843
|
+
var _a$2, _b$2;
|
|
2844
|
+
class CollectionSchema {
|
|
2845
|
+
static { this[_a$2] = encodeKeyValueOperation; }
|
|
2846
|
+
static { this[_b$2] = decodeKeyValueOperation; }
|
|
2847
|
+
/**
|
|
2848
|
+
* Determine if a property must be filtered.
|
|
2849
|
+
* - If returns false, the property is NOT going to be encoded.
|
|
2850
|
+
* - If returns true, the property is going to be encoded.
|
|
2851
|
+
*
|
|
2852
|
+
* Encoding with "filters" happens in two steps:
|
|
2853
|
+
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2854
|
+
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2855
|
+
*/
|
|
2856
|
+
static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
|
|
2857
|
+
return (!view ||
|
|
2858
|
+
typeof (ref[$childType]) === "string" ||
|
|
2859
|
+
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
2860
|
+
}
|
|
2861
|
+
static is(type) {
|
|
2862
|
+
return type['collection'] !== undefined;
|
|
2863
|
+
}
|
|
2864
|
+
constructor(initialValues) {
|
|
2865
|
+
this.$items = new Map();
|
|
2866
|
+
this.$indexes = new Map();
|
|
2867
|
+
this.deletedItems = {};
|
|
2868
|
+
this.$refId = 0;
|
|
2869
|
+
this[$changes] = new ChangeTree(this);
|
|
2870
|
+
this[$changes].indexes = {};
|
|
2871
|
+
if (initialValues) {
|
|
2872
|
+
initialValues.forEach((v) => this.add(v));
|
|
3030
2873
|
}
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
// do not declare getter/setter descriptor
|
|
3034
|
-
enumerable: true,
|
|
3035
|
-
configurable: true,
|
|
3036
|
-
writable: true,
|
|
3037
|
-
});
|
|
3038
|
-
}
|
|
3039
|
-
else {
|
|
3040
|
-
const complexTypeKlass = (Array.isArray(type))
|
|
3041
|
-
? getType("array")
|
|
3042
|
-
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
3043
|
-
const childType = (complexTypeKlass)
|
|
3044
|
-
? Object.values(type)[0]
|
|
3045
|
-
: type;
|
|
3046
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
3047
|
-
}
|
|
3048
|
-
};
|
|
3049
|
-
}
|
|
3050
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
3051
|
-
return {
|
|
3052
|
-
get: function () { return this[fieldCached]; },
|
|
3053
|
-
set: function (value) {
|
|
3054
|
-
const previousValue = this[fieldCached] ?? undefined;
|
|
3055
|
-
// skip if value is the same as cached.
|
|
3056
|
-
if (value === previousValue) {
|
|
3057
|
-
return;
|
|
3058
|
-
}
|
|
3059
|
-
if (value !== undefined &&
|
|
3060
|
-
value !== null) {
|
|
3061
|
-
if (complexTypeKlass) {
|
|
3062
|
-
// automaticallty transform Array into ArraySchema
|
|
3063
|
-
if (complexTypeKlass.constructor === ArraySchema && !(value instanceof ArraySchema)) {
|
|
3064
|
-
value = new ArraySchema(...value);
|
|
3065
|
-
}
|
|
3066
|
-
// automaticallty transform Map into MapSchema
|
|
3067
|
-
if (complexTypeKlass.constructor === MapSchema && !(value instanceof MapSchema)) {
|
|
3068
|
-
value = new MapSchema(value);
|
|
3069
|
-
}
|
|
3070
|
-
value[$childType] = type;
|
|
3071
|
-
}
|
|
3072
|
-
else if (typeof (type) !== "string") {
|
|
3073
|
-
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
3074
|
-
}
|
|
3075
|
-
else {
|
|
3076
|
-
assertType(value, type, this, fieldCached.substring(1));
|
|
3077
|
-
}
|
|
3078
|
-
const changeTree = this[$changes];
|
|
3079
|
-
//
|
|
3080
|
-
// Replacing existing "ref", remove it from root.
|
|
3081
|
-
//
|
|
3082
|
-
if (previousValue !== undefined && previousValue[$changes]) {
|
|
3083
|
-
changeTree.root?.remove(previousValue[$changes]);
|
|
3084
|
-
this.constructor[$track](changeTree, fieldIndex, OPERATION.DELETE_AND_ADD);
|
|
3085
|
-
}
|
|
3086
|
-
else {
|
|
3087
|
-
this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
|
|
3088
|
-
}
|
|
3089
|
-
//
|
|
3090
|
-
// call setParent() recursively for this and its child
|
|
3091
|
-
// structures.
|
|
3092
|
-
//
|
|
3093
|
-
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
3094
|
-
}
|
|
3095
|
-
else if (previousValue !== undefined) {
|
|
3096
|
-
//
|
|
3097
|
-
// Setting a field to `null` or `undefined` will delete it.
|
|
3098
|
-
//
|
|
3099
|
-
this[$changes].delete(fieldIndex);
|
|
3100
|
-
}
|
|
3101
|
-
this[fieldCached] = value;
|
|
3102
|
-
},
|
|
3103
|
-
enumerable: true,
|
|
3104
|
-
configurable: true
|
|
3105
|
-
};
|
|
3106
|
-
}
|
|
3107
|
-
/**
|
|
3108
|
-
* `@deprecated()` flag a field as deprecated.
|
|
3109
|
-
* The previous `@type()` annotation should remain along with this one.
|
|
3110
|
-
*/
|
|
3111
|
-
function deprecated(throws = true) {
|
|
3112
|
-
return function (klass, field) {
|
|
3113
|
-
//
|
|
3114
|
-
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
3115
|
-
//
|
|
3116
|
-
const constructor = klass.constructor;
|
|
3117
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
3118
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
3119
|
-
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3120
|
-
const fieldIndex = metadata[field];
|
|
3121
|
-
// if (!metadata[field]) {
|
|
3122
|
-
// //
|
|
3123
|
-
// // detect index for this field, considering inheritance
|
|
3124
|
-
// //
|
|
3125
|
-
// metadata[field] = {
|
|
3126
|
-
// type: undefined,
|
|
3127
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3128
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3129
|
-
// ?? -1) + 1 // no fields defined
|
|
3130
|
-
// }
|
|
3131
|
-
// }
|
|
3132
|
-
metadata[fieldIndex].deprecated = true;
|
|
3133
|
-
if (throws) {
|
|
3134
|
-
metadata[$descriptors] ??= {};
|
|
3135
|
-
metadata[$descriptors][field] = {
|
|
3136
|
-
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
3137
|
-
set: function (value) { },
|
|
3138
|
-
enumerable: false,
|
|
3139
|
-
configurable: true
|
|
3140
|
-
};
|
|
3141
|
-
}
|
|
3142
|
-
// flag metadata[field] as non-enumerable
|
|
3143
|
-
Object.defineProperty(metadata, fieldIndex, {
|
|
3144
|
-
value: metadata[fieldIndex],
|
|
2874
|
+
Object.defineProperty(this, $childType, {
|
|
2875
|
+
value: undefined,
|
|
3145
2876
|
enumerable: false,
|
|
3146
|
-
|
|
2877
|
+
writable: true,
|
|
2878
|
+
configurable: true,
|
|
3147
2879
|
});
|
|
3148
|
-
};
|
|
3149
|
-
}
|
|
3150
|
-
function defineTypes(target, fields, options) {
|
|
3151
|
-
for (let field in fields) {
|
|
3152
|
-
type(fields[field], options)(target.prototype, field);
|
|
3153
|
-
}
|
|
3154
|
-
return target;
|
|
3155
|
-
}
|
|
3156
|
-
function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
3157
|
-
const fields = {};
|
|
3158
|
-
const methods = {};
|
|
3159
|
-
const defaultValues = {};
|
|
3160
|
-
const viewTagFields = {};
|
|
3161
|
-
for (let fieldName in fieldsAndMethods) {
|
|
3162
|
-
const value = fieldsAndMethods[fieldName];
|
|
3163
|
-
if (typeof (value) === "object") {
|
|
3164
|
-
if (value['default'] !== undefined) {
|
|
3165
|
-
defaultValues[fieldName] = value['default'];
|
|
3166
|
-
}
|
|
3167
|
-
if (value['view'] !== undefined) {
|
|
3168
|
-
viewTagFields[fieldName] = (typeof (value['view']) === "boolean")
|
|
3169
|
-
? DEFAULT_VIEW_TAG
|
|
3170
|
-
: value['view'];
|
|
3171
|
-
}
|
|
3172
|
-
fields[fieldName] = value;
|
|
3173
|
-
}
|
|
3174
|
-
else if (typeof (value) === "function") {
|
|
3175
|
-
methods[fieldName] = value;
|
|
3176
|
-
}
|
|
3177
|
-
else {
|
|
3178
|
-
fields[fieldName] = value;
|
|
3179
|
-
}
|
|
3180
2880
|
}
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
2881
|
+
add(value) {
|
|
2882
|
+
// set "index" for reference.
|
|
2883
|
+
const index = this.$refId++;
|
|
2884
|
+
const isRef = (value[$changes]) !== undefined;
|
|
2885
|
+
if (isRef) {
|
|
2886
|
+
value[$changes].setParent(this, this[$changes].root, index);
|
|
3185
2887
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
2888
|
+
this[$changes].indexes[index] = index;
|
|
2889
|
+
this.$indexes.set(index, index);
|
|
2890
|
+
this.$items.set(index, value);
|
|
2891
|
+
this[$changes].change(index);
|
|
2892
|
+
return index;
|
|
3189
2893
|
}
|
|
3190
|
-
|
|
3191
|
-
|
|
2894
|
+
at(index) {
|
|
2895
|
+
const key = Array.from(this.$items.keys())[index];
|
|
2896
|
+
return this.$items.get(key);
|
|
3192
2897
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
2898
|
+
entries() {
|
|
2899
|
+
return this.$items.entries();
|
|
3195
2900
|
}
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
refs: []
|
|
3208
|
-
};
|
|
3209
|
-
// for (const refId in $root.changes) {
|
|
3210
|
-
let current = $root.changes.next;
|
|
3211
|
-
while (current) {
|
|
3212
|
-
const changeTree = current.changeTree;
|
|
3213
|
-
// skip if ChangeTree is undefined
|
|
3214
|
-
if (changeTree === undefined) {
|
|
3215
|
-
current = current.next;
|
|
3216
|
-
continue;
|
|
3217
|
-
}
|
|
3218
|
-
const changes = changeTree.indexedOperations;
|
|
3219
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3220
|
-
for (const index in changes) {
|
|
3221
|
-
const op = changes[index];
|
|
3222
|
-
const opName = OPERATION[op];
|
|
3223
|
-
if (!dump.ops[opName]) {
|
|
3224
|
-
dump.ops[opName] = 0;
|
|
2901
|
+
delete(item) {
|
|
2902
|
+
const entries = this.$items.entries();
|
|
2903
|
+
let index;
|
|
2904
|
+
let entry;
|
|
2905
|
+
while (entry = entries.next()) {
|
|
2906
|
+
if (entry.done) {
|
|
2907
|
+
break;
|
|
2908
|
+
}
|
|
2909
|
+
if (item === entry.value[1]) {
|
|
2910
|
+
index = entry.value[0];
|
|
2911
|
+
break;
|
|
3225
2912
|
}
|
|
3226
|
-
dump.ops[OPERATION[op]]++;
|
|
3227
2913
|
}
|
|
3228
|
-
|
|
2914
|
+
if (index === undefined) {
|
|
2915
|
+
return false;
|
|
2916
|
+
}
|
|
2917
|
+
this.deletedItems[index] = this[$changes].delete(index);
|
|
2918
|
+
this.$indexes.delete(index);
|
|
2919
|
+
return this.$items.delete(index);
|
|
3229
2920
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
static { this[_a$2] = encodeSchemaOperation; }
|
|
3239
|
-
static { this[_b$2] = decodeSchemaOperation; }
|
|
3240
|
-
/**
|
|
3241
|
-
* Assign the property descriptors required to track changes on this instance.
|
|
3242
|
-
* @param instance
|
|
3243
|
-
*/
|
|
3244
|
-
static initialize(instance) {
|
|
3245
|
-
Object.defineProperty(instance, $changes, {
|
|
3246
|
-
value: new ChangeTree(instance),
|
|
3247
|
-
enumerable: false,
|
|
3248
|
-
writable: true
|
|
2921
|
+
clear() {
|
|
2922
|
+
const changeTree = this[$changes];
|
|
2923
|
+
// discard previous operations.
|
|
2924
|
+
changeTree.discard(true);
|
|
2925
|
+
changeTree.indexes = {};
|
|
2926
|
+
// remove children references
|
|
2927
|
+
changeTree.forEachChild((childChangeTree, _) => {
|
|
2928
|
+
changeTree.root?.remove(childChangeTree);
|
|
3249
2929
|
});
|
|
3250
|
-
|
|
2930
|
+
// clear previous indexes
|
|
2931
|
+
this.$indexes.clear();
|
|
2932
|
+
// clear items
|
|
2933
|
+
this.$items.clear();
|
|
2934
|
+
changeTree.operation(OPERATION.CLEAR);
|
|
3251
2935
|
}
|
|
3252
|
-
|
|
3253
|
-
return
|
|
3254
|
-
// const metadata = type[Symbol.metadata];
|
|
3255
|
-
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
2936
|
+
has(value) {
|
|
2937
|
+
return Array.from(this.$items.values()).some((v) => v === value);
|
|
3256
2938
|
}
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
2939
|
+
forEach(callbackfn) {
|
|
2940
|
+
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
2941
|
+
}
|
|
2942
|
+
values() {
|
|
2943
|
+
return this.$items.values();
|
|
2944
|
+
}
|
|
2945
|
+
get size() {
|
|
2946
|
+
return this.$items.size;
|
|
2947
|
+
}
|
|
2948
|
+
/** Iterator */
|
|
2949
|
+
[Symbol.iterator]() {
|
|
2950
|
+
return this.$items.values();
|
|
2951
|
+
}
|
|
2952
|
+
setIndex(index, key) {
|
|
2953
|
+
this.$indexes.set(index, key);
|
|
2954
|
+
}
|
|
2955
|
+
getIndex(index) {
|
|
2956
|
+
return this.$indexes.get(index);
|
|
2957
|
+
}
|
|
2958
|
+
[$getByIndex](index) {
|
|
2959
|
+
return this.$items.get(this.$indexes.get(index));
|
|
2960
|
+
}
|
|
2961
|
+
[$deleteByIndex](index) {
|
|
2962
|
+
const key = this.$indexes.get(index);
|
|
2963
|
+
this.$items.delete(key);
|
|
2964
|
+
this.$indexes.delete(index);
|
|
2965
|
+
}
|
|
2966
|
+
[$onEncodeEnd]() {
|
|
2967
|
+
this.deletedItems = {};
|
|
2968
|
+
}
|
|
2969
|
+
toArray() {
|
|
2970
|
+
return Array.from(this.$items.values());
|
|
2971
|
+
}
|
|
2972
|
+
toJSON() {
|
|
2973
|
+
const values = [];
|
|
2974
|
+
this.forEach((value, key) => {
|
|
2975
|
+
values.push((typeof (value['toJSON']) === "function")
|
|
2976
|
+
? value['toJSON']()
|
|
2977
|
+
: value);
|
|
2978
|
+
});
|
|
2979
|
+
return values;
|
|
2980
|
+
}
|
|
2981
|
+
//
|
|
2982
|
+
// Decoding utilities
|
|
2983
|
+
//
|
|
2984
|
+
clone(isDecoding) {
|
|
2985
|
+
let cloned;
|
|
2986
|
+
if (isDecoding) {
|
|
2987
|
+
// client-side
|
|
2988
|
+
cloned = Object.assign(new CollectionSchema(), this);
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
// server-side
|
|
2992
|
+
cloned = new CollectionSchema();
|
|
2993
|
+
this.forEach((value) => {
|
|
2994
|
+
if (value[$changes]) {
|
|
2995
|
+
cloned.add(value['clone']());
|
|
2996
|
+
}
|
|
2997
|
+
else {
|
|
2998
|
+
cloned.add(value);
|
|
2999
|
+
}
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
return cloned;
|
|
3262
3003
|
}
|
|
3004
|
+
}
|
|
3005
|
+
registerType("collection", { constructor: CollectionSchema, });
|
|
3006
|
+
|
|
3007
|
+
var _a$1, _b$1;
|
|
3008
|
+
class SetSchema {
|
|
3009
|
+
static { this[_a$1] = encodeKeyValueOperation; }
|
|
3010
|
+
static { this[_b$1] = decodeKeyValueOperation; }
|
|
3263
3011
|
/**
|
|
3264
3012
|
* Determine if a property must be filtered.
|
|
3265
3013
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3269,418 +3017,653 @@ class Schema {
|
|
|
3269
3017
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3270
3018
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3271
3019
|
*/
|
|
3272
|
-
static [$filter](ref, index, view) {
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
// shared pass/encode: encode if doesn't have a tag
|
|
3277
|
-
return tag === undefined;
|
|
3278
|
-
}
|
|
3279
|
-
else if (tag === undefined) {
|
|
3280
|
-
// view pass: no tag
|
|
3281
|
-
return true;
|
|
3282
|
-
}
|
|
3283
|
-
else if (tag === DEFAULT_VIEW_TAG) {
|
|
3284
|
-
// view pass: default tag
|
|
3285
|
-
return view.isChangeTreeVisible(ref[$changes]);
|
|
3286
|
-
}
|
|
3287
|
-
else {
|
|
3288
|
-
// view pass: custom tag
|
|
3289
|
-
const tags = view.tags?.get(ref[$changes]);
|
|
3290
|
-
return tags && tags.has(tag);
|
|
3291
|
-
}
|
|
3020
|
+
static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
|
|
3021
|
+
return (!view ||
|
|
3022
|
+
typeof (ref[$childType]) === "string" ||
|
|
3023
|
+
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
3292
3024
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
if (
|
|
3304
|
-
|
|
3025
|
+
static is(type) {
|
|
3026
|
+
return type['set'] !== undefined;
|
|
3027
|
+
}
|
|
3028
|
+
constructor(initialValues) {
|
|
3029
|
+
this.$items = new Map();
|
|
3030
|
+
this.$indexes = new Map();
|
|
3031
|
+
this.deletedItems = {};
|
|
3032
|
+
this.$refId = 0;
|
|
3033
|
+
this[$changes] = new ChangeTree(this);
|
|
3034
|
+
this[$changes].indexes = {};
|
|
3035
|
+
if (initialValues) {
|
|
3036
|
+
initialValues.forEach((v) => this.add(v));
|
|
3305
3037
|
}
|
|
3038
|
+
Object.defineProperty(this, $childType, {
|
|
3039
|
+
value: undefined,
|
|
3040
|
+
enumerable: false,
|
|
3041
|
+
writable: true,
|
|
3042
|
+
configurable: true,
|
|
3043
|
+
});
|
|
3306
3044
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3045
|
+
add(value) {
|
|
3046
|
+
// immediatelly return false if value already added.
|
|
3047
|
+
if (this.has(value)) {
|
|
3048
|
+
return false;
|
|
3049
|
+
}
|
|
3050
|
+
// set "index" for reference.
|
|
3051
|
+
const index = this.$refId++;
|
|
3052
|
+
if ((value[$changes]) !== undefined) {
|
|
3053
|
+
value[$changes].setParent(this, this[$changes].root, index);
|
|
3054
|
+
}
|
|
3055
|
+
const operation = this[$changes].indexes[index]?.op ?? OPERATION.ADD;
|
|
3056
|
+
this[$changes].indexes[index] = index;
|
|
3057
|
+
this.$indexes.set(index, index);
|
|
3058
|
+
this.$items.set(index, value);
|
|
3059
|
+
this[$changes].change(index, operation);
|
|
3060
|
+
return index;
|
|
3310
3061
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
* @param instance Schema instance
|
|
3314
|
-
* @param property string representing the property name, or number representing the index of the property.
|
|
3315
|
-
* @param operation OPERATION to perform (detected automatically)
|
|
3316
|
-
*/
|
|
3317
|
-
setDirty(property, operation) {
|
|
3318
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
3319
|
-
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
3062
|
+
entries() {
|
|
3063
|
+
return this.$items.entries();
|
|
3320
3064
|
}
|
|
3321
|
-
|
|
3322
|
-
const
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
for (const fieldIndex in metadata) {
|
|
3329
|
-
// const field = metadata[metadata[fieldIndex]].name;
|
|
3330
|
-
const field = metadata[fieldIndex].name;
|
|
3331
|
-
if (typeof (this[field]) === "object" &&
|
|
3332
|
-
typeof (this[field]?.clone) === "function") {
|
|
3333
|
-
// deep clone
|
|
3334
|
-
cloned[field] = this[field].clone();
|
|
3065
|
+
delete(item) {
|
|
3066
|
+
const entries = this.$items.entries();
|
|
3067
|
+
let index;
|
|
3068
|
+
let entry;
|
|
3069
|
+
while (entry = entries.next()) {
|
|
3070
|
+
if (entry.done) {
|
|
3071
|
+
break;
|
|
3335
3072
|
}
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3073
|
+
if (item === entry.value[1]) {
|
|
3074
|
+
index = entry.value[0];
|
|
3075
|
+
break;
|
|
3339
3076
|
}
|
|
3340
3077
|
}
|
|
3341
|
-
|
|
3078
|
+
if (index === undefined) {
|
|
3079
|
+
return false;
|
|
3080
|
+
}
|
|
3081
|
+
this.deletedItems[index] = this[$changes].delete(index);
|
|
3082
|
+
this.$indexes.delete(index);
|
|
3083
|
+
return this.$items.delete(index);
|
|
3342
3084
|
}
|
|
3343
|
-
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3085
|
+
clear() {
|
|
3086
|
+
const changeTree = this[$changes];
|
|
3087
|
+
// discard previous operations.
|
|
3088
|
+
changeTree.discard(true);
|
|
3089
|
+
changeTree.indexes = {};
|
|
3090
|
+
// clear previous indexes
|
|
3091
|
+
this.$indexes.clear();
|
|
3092
|
+
// clear items
|
|
3093
|
+
this.$items.clear();
|
|
3094
|
+
changeTree.operation(OPERATION.CLEAR);
|
|
3095
|
+
}
|
|
3096
|
+
has(value) {
|
|
3097
|
+
const values = this.$items.values();
|
|
3098
|
+
let has = false;
|
|
3099
|
+
let entry;
|
|
3100
|
+
while (entry = values.next()) {
|
|
3101
|
+
if (entry.done) {
|
|
3102
|
+
break;
|
|
3103
|
+
}
|
|
3104
|
+
if (value === entry.value) {
|
|
3105
|
+
has = true;
|
|
3106
|
+
break;
|
|
3353
3107
|
}
|
|
3354
3108
|
}
|
|
3355
|
-
return
|
|
3109
|
+
return has;
|
|
3356
3110
|
}
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3111
|
+
forEach(callbackfn) {
|
|
3112
|
+
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3113
|
+
}
|
|
3114
|
+
values() {
|
|
3115
|
+
return this.$items.values();
|
|
3116
|
+
}
|
|
3117
|
+
get size() {
|
|
3118
|
+
return this.$items.size;
|
|
3119
|
+
}
|
|
3120
|
+
/** Iterator */
|
|
3121
|
+
[Symbol.iterator]() {
|
|
3122
|
+
return this.$items.values();
|
|
3123
|
+
}
|
|
3124
|
+
setIndex(index, key) {
|
|
3125
|
+
this.$indexes.set(index, key);
|
|
3126
|
+
}
|
|
3127
|
+
getIndex(index) {
|
|
3128
|
+
return this.$indexes.get(index);
|
|
3363
3129
|
}
|
|
3364
3130
|
[$getByIndex](index) {
|
|
3365
|
-
|
|
3366
|
-
return this[metadata[index].name];
|
|
3131
|
+
return this.$items.get(this.$indexes.get(index));
|
|
3367
3132
|
}
|
|
3368
3133
|
[$deleteByIndex](index) {
|
|
3369
|
-
const
|
|
3370
|
-
this
|
|
3371
|
-
|
|
3372
|
-
/**
|
|
3373
|
-
* Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
|
|
3374
|
-
*
|
|
3375
|
-
* @param ref Schema instance
|
|
3376
|
-
* @param showContents display JSON contents of the instance
|
|
3377
|
-
* @returns
|
|
3378
|
-
*/
|
|
3379
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3380
|
-
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3381
|
-
const changeTree = ref[$changes];
|
|
3382
|
-
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
3383
|
-
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3384
|
-
// log reference count if > 1
|
|
3385
|
-
const refCount = (root?.refCount?.[refId] > 1)
|
|
3386
|
-
? ` [×${root.refCount[refId]}]`
|
|
3387
|
-
: '';
|
|
3388
|
-
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3389
|
-
changeTree.forEachChild((childChangeTree, indexOrKey) => {
|
|
3390
|
-
let key = indexOrKey;
|
|
3391
|
-
if (typeof indexOrKey === 'number' && ref['$indexes']) {
|
|
3392
|
-
// MapSchema
|
|
3393
|
-
key = ref['$indexes'].get(indexOrKey) ?? indexOrKey;
|
|
3394
|
-
}
|
|
3395
|
-
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3396
|
-
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3397
|
-
});
|
|
3398
|
-
return output;
|
|
3134
|
+
const key = this.$indexes.get(index);
|
|
3135
|
+
this.$items.delete(key);
|
|
3136
|
+
this.$indexes.delete(index);
|
|
3399
3137
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
let current = ref[$changes].root[changeSet].next;
|
|
3403
|
-
while (current) {
|
|
3404
|
-
if (current.changeTree) {
|
|
3405
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3406
|
-
}
|
|
3407
|
-
current = current.next;
|
|
3408
|
-
}
|
|
3409
|
-
return encodeOrder;
|
|
3138
|
+
[$onEncodeEnd]() {
|
|
3139
|
+
this.deletedItems = {};
|
|
3410
3140
|
}
|
|
3411
|
-
|
|
3412
|
-
return this.
|
|
3141
|
+
toArray() {
|
|
3142
|
+
return Array.from(this.$items.values());
|
|
3413
3143
|
}
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3144
|
+
toJSON() {
|
|
3145
|
+
const values = [];
|
|
3146
|
+
this.forEach((value, key) => {
|
|
3147
|
+
values.push((typeof (value['toJSON']) === "function")
|
|
3148
|
+
? value['toJSON']()
|
|
3149
|
+
: value);
|
|
3150
|
+
});
|
|
3151
|
+
return values;
|
|
3152
|
+
}
|
|
3153
|
+
//
|
|
3154
|
+
// Decoding utilities
|
|
3155
|
+
//
|
|
3156
|
+
clone(isDecoding) {
|
|
3157
|
+
let cloned;
|
|
3158
|
+
if (isDecoding) {
|
|
3159
|
+
// client-side
|
|
3160
|
+
cloned = Object.assign(new SetSchema(), this);
|
|
3161
|
+
}
|
|
3162
|
+
else {
|
|
3163
|
+
// server-side
|
|
3164
|
+
cloned = new SetSchema();
|
|
3165
|
+
this.forEach((value) => {
|
|
3166
|
+
if (value[$changes]) {
|
|
3167
|
+
cloned.add(value['clone']());
|
|
3168
|
+
}
|
|
3169
|
+
else {
|
|
3170
|
+
cloned.add(value);
|
|
3171
|
+
}
|
|
3434
3172
|
});
|
|
3435
3173
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3174
|
+
return cloned;
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
registerType("set", { constructor: SetSchema });
|
|
3178
|
+
|
|
3179
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
3180
|
+
function entity(constructor) {
|
|
3181
|
+
TypeContext.register(constructor);
|
|
3182
|
+
return constructor;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
3186
|
+
*
|
|
3187
|
+
* Annotate a Schema property to be serializeable.
|
|
3188
|
+
* \@type()'d fields are automatically flagged as "dirty" for the next patch.
|
|
3189
|
+
*
|
|
3190
|
+
* @example Standard usage, with automatic change tracking.
|
|
3191
|
+
* ```
|
|
3192
|
+
* \@type("string") propertyName: string;
|
|
3193
|
+
* ```
|
|
3194
|
+
*
|
|
3195
|
+
* @example You can provide the "manual" option if you'd like to manually control your patches via .setDirty().
|
|
3196
|
+
* ```
|
|
3197
|
+
* \@type("string", { manual: true })
|
|
3198
|
+
* ```
|
|
3199
|
+
*/
|
|
3200
|
+
// export function type(type: DefinitionType, options?: TypeOptions) {
|
|
3201
|
+
// return function ({ get, set }, context: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<Schema, any> {
|
|
3202
|
+
// if (context.kind !== "accessor") {
|
|
3203
|
+
// throw new Error("@type() is only supported for class accessor properties");
|
|
3204
|
+
// }
|
|
3205
|
+
// const field = context.name.toString();
|
|
3206
|
+
// //
|
|
3207
|
+
// // detect index for this field, considering inheritance
|
|
3208
|
+
// //
|
|
3209
|
+
// const parent = Object.getPrototypeOf(context.metadata);
|
|
3210
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
3211
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
3212
|
+
// ?? -1; // no fields defined
|
|
3213
|
+
// fieldIndex++;
|
|
3214
|
+
// if (
|
|
3215
|
+
// !parent && // the parent already initializes the `$changes` property
|
|
3216
|
+
// !Metadata.hasFields(context.metadata)
|
|
3217
|
+
// ) {
|
|
3218
|
+
// context.addInitializer(function (this: Ref) {
|
|
3219
|
+
// Object.defineProperty(this, $changes, {
|
|
3220
|
+
// value: new ChangeTree(this),
|
|
3221
|
+
// enumerable: false,
|
|
3222
|
+
// writable: true
|
|
3223
|
+
// });
|
|
3224
|
+
// });
|
|
3225
|
+
// }
|
|
3226
|
+
// Metadata.addField(context.metadata, fieldIndex, field, type);
|
|
3227
|
+
// const isArray = ArraySchema.is(type);
|
|
3228
|
+
// const isMap = !isArray && MapSchema.is(type);
|
|
3229
|
+
// // if (options && options.manual) {
|
|
3230
|
+
// // // do not declare getter/setter descriptor
|
|
3231
|
+
// // definition.descriptors[field] = {
|
|
3232
|
+
// // enumerable: true,
|
|
3233
|
+
// // configurable: true,
|
|
3234
|
+
// // writable: true,
|
|
3235
|
+
// // };
|
|
3236
|
+
// // return;
|
|
3237
|
+
// // }
|
|
3238
|
+
// return {
|
|
3239
|
+
// init(value) {
|
|
3240
|
+
// // TODO: may need to convert ArraySchema/MapSchema here
|
|
3241
|
+
// // do not flag change if value is undefined.
|
|
3242
|
+
// if (value !== undefined) {
|
|
3243
|
+
// this[$changes].change(fieldIndex);
|
|
3244
|
+
// // automaticallty transform Array into ArraySchema
|
|
3245
|
+
// if (isArray) {
|
|
3246
|
+
// if (!(value instanceof ArraySchema)) {
|
|
3247
|
+
// value = new ArraySchema(...value);
|
|
3248
|
+
// }
|
|
3249
|
+
// value[$childType] = Object.values(type)[0];
|
|
3250
|
+
// }
|
|
3251
|
+
// // automaticallty transform Map into MapSchema
|
|
3252
|
+
// if (isMap) {
|
|
3253
|
+
// if (!(value instanceof MapSchema)) {
|
|
3254
|
+
// value = new MapSchema(value);
|
|
3255
|
+
// }
|
|
3256
|
+
// value[$childType] = Object.values(type)[0];
|
|
3257
|
+
// }
|
|
3258
|
+
// // try to turn provided structure into a Proxy
|
|
3259
|
+
// if (value['$proxy'] === undefined) {
|
|
3260
|
+
// if (isMap) {
|
|
3261
|
+
// value = getMapProxy(value);
|
|
3262
|
+
// }
|
|
3263
|
+
// }
|
|
3264
|
+
// }
|
|
3265
|
+
// return value;
|
|
3266
|
+
// },
|
|
3267
|
+
// get() {
|
|
3268
|
+
// return get.call(this);
|
|
3269
|
+
// },
|
|
3270
|
+
// set(value: any) {
|
|
3271
|
+
// /**
|
|
3272
|
+
// * Create Proxy for array or map items
|
|
3273
|
+
// */
|
|
3274
|
+
// // skip if value is the same as cached.
|
|
3275
|
+
// if (value === get.call(this)) {
|
|
3276
|
+
// return;
|
|
3277
|
+
// }
|
|
3278
|
+
// if (
|
|
3279
|
+
// value !== undefined &&
|
|
3280
|
+
// value !== null
|
|
3281
|
+
// ) {
|
|
3282
|
+
// // automaticallty transform Array into ArraySchema
|
|
3283
|
+
// if (isArray) {
|
|
3284
|
+
// if (!(value instanceof ArraySchema)) {
|
|
3285
|
+
// value = new ArraySchema(...value);
|
|
3286
|
+
// }
|
|
3287
|
+
// value[$childType] = Object.values(type)[0];
|
|
3288
|
+
// }
|
|
3289
|
+
// // automaticallty transform Map into MapSchema
|
|
3290
|
+
// if (isMap) {
|
|
3291
|
+
// if (!(value instanceof MapSchema)) {
|
|
3292
|
+
// value = new MapSchema(value);
|
|
3293
|
+
// }
|
|
3294
|
+
// value[$childType] = Object.values(type)[0];
|
|
3295
|
+
// }
|
|
3296
|
+
// // try to turn provided structure into a Proxy
|
|
3297
|
+
// if (value['$proxy'] === undefined) {
|
|
3298
|
+
// if (isMap) {
|
|
3299
|
+
// value = getMapProxy(value);
|
|
3300
|
+
// }
|
|
3301
|
+
// }
|
|
3302
|
+
// // flag the change for encoding.
|
|
3303
|
+
// this[$changes].change(fieldIndex);
|
|
3304
|
+
// //
|
|
3305
|
+
// // call setParent() recursively for this and its child
|
|
3306
|
+
// // structures.
|
|
3307
|
+
// //
|
|
3308
|
+
// if (value[$changes]) {
|
|
3309
|
+
// value[$changes].setParent(
|
|
3310
|
+
// this,
|
|
3311
|
+
// this[$changes].root,
|
|
3312
|
+
// Metadata.getIndex(context.metadata, field),
|
|
3313
|
+
// );
|
|
3314
|
+
// }
|
|
3315
|
+
// } else if (get.call(this)) {
|
|
3316
|
+
// //
|
|
3317
|
+
// // Setting a field to `null` or `undefined` will delete it.
|
|
3318
|
+
// //
|
|
3319
|
+
// this[$changes].delete(field);
|
|
3320
|
+
// }
|
|
3321
|
+
// set.call(this, value);
|
|
3322
|
+
// },
|
|
3323
|
+
// };
|
|
3324
|
+
// }
|
|
3325
|
+
// }
|
|
3326
|
+
function view(tag = DEFAULT_VIEW_TAG) {
|
|
3327
|
+
return function (target, fieldName) {
|
|
3328
|
+
const constructor = target.constructor;
|
|
3329
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3330
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3331
|
+
// TODO: use Metadata.initialize()
|
|
3332
|
+
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3333
|
+
// const fieldIndex = metadata[fieldName];
|
|
3334
|
+
// if (!metadata[fieldIndex]) {
|
|
3335
|
+
// //
|
|
3336
|
+
// // detect index for this field, considering inheritance
|
|
3337
|
+
// //
|
|
3338
|
+
// metadata[fieldIndex] = {
|
|
3339
|
+
// type: undefined,
|
|
3340
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3341
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3342
|
+
// ?? -1) + 1 // no fields defined
|
|
3343
|
+
// }
|
|
3344
|
+
// }
|
|
3345
|
+
Metadata.setTag(metadata, fieldName, tag);
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
function type(type, options) {
|
|
3349
|
+
return function (target, field) {
|
|
3350
|
+
const constructor = target.constructor;
|
|
3351
|
+
if (!type) {
|
|
3352
|
+
throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
|
|
3353
|
+
}
|
|
3354
|
+
// Normalize type (enum/collection/etc)
|
|
3355
|
+
type = getNormalizedType(type);
|
|
3356
|
+
// for inheritance support
|
|
3357
|
+
TypeContext.register(constructor);
|
|
3358
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3359
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3360
|
+
const metadata = Metadata.initialize(constructor);
|
|
3361
|
+
let fieldIndex = metadata[field];
|
|
3362
|
+
/**
|
|
3363
|
+
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
3364
|
+
*/
|
|
3365
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
3366
|
+
if (metadata[fieldIndex].deprecated) {
|
|
3367
|
+
// do not create accessors for deprecated properties.
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
3371
|
+
// trying to define same property multiple times across inheritance.
|
|
3372
|
+
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
3373
|
+
try {
|
|
3374
|
+
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name}'.\nCheck @type() annotation`);
|
|
3375
|
+
}
|
|
3376
|
+
catch (e) {
|
|
3377
|
+
const definitionAtLine = e.stack.split("\n")[4].trim();
|
|
3378
|
+
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
else {
|
|
3383
|
+
//
|
|
3384
|
+
// detect index for this field, considering inheritance
|
|
3385
|
+
//
|
|
3386
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
3387
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3388
|
+
?? -1; // no fields defined
|
|
3389
|
+
fieldIndex++;
|
|
3390
|
+
}
|
|
3391
|
+
if (options && options.manual) {
|
|
3392
|
+
Metadata.addField(metadata, fieldIndex, field, type, {
|
|
3393
|
+
// do not declare getter/setter descriptor
|
|
3394
|
+
enumerable: true,
|
|
3395
|
+
configurable: true,
|
|
3396
|
+
writable: true,
|
|
3397
|
+
});
|
|
3443
3398
|
}
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3399
|
+
else {
|
|
3400
|
+
const complexTypeKlass = typeof (Object.keys(type)[0]) === "string" && getType(Object.keys(type)[0]);
|
|
3401
|
+
const childType = (complexTypeKlass)
|
|
3402
|
+
? Object.values(type)[0]
|
|
3403
|
+
: type;
|
|
3404
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
3450
3405
|
}
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3462
|
-
const changeTree = root.changeTrees[refId];
|
|
3463
|
-
if (!changeTree) {
|
|
3464
|
-
continue;
|
|
3465
|
-
}
|
|
3466
|
-
let includeChangeTree = false;
|
|
3467
|
-
let parentChangeTrees = [];
|
|
3468
|
-
let parentChangeTree = changeTree.parent?.[$changes];
|
|
3469
|
-
if (changeTree === rootChangeTree) {
|
|
3470
|
-
includeChangeTree = true;
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3408
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
3409
|
+
return {
|
|
3410
|
+
get: function () { return this[fieldCached]; },
|
|
3411
|
+
set: function (value) {
|
|
3412
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
3413
|
+
// skip if value is the same as cached.
|
|
3414
|
+
if (value === previousValue) {
|
|
3415
|
+
return;
|
|
3471
3416
|
}
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3417
|
+
if (value !== undefined &&
|
|
3418
|
+
value !== null) {
|
|
3419
|
+
if (complexTypeKlass) {
|
|
3420
|
+
// automaticallty transform Array into ArraySchema
|
|
3421
|
+
if (complexTypeKlass.constructor === ArraySchema && !(value instanceof ArraySchema)) {
|
|
3422
|
+
value = new ArraySchema(...value);
|
|
3478
3423
|
}
|
|
3479
|
-
|
|
3424
|
+
// automaticallty transform Map into MapSchema
|
|
3425
|
+
if (complexTypeKlass.constructor === MapSchema && !(value instanceof MapSchema)) {
|
|
3426
|
+
value = new MapSchema(value);
|
|
3427
|
+
}
|
|
3428
|
+
value[$childType] = type;
|
|
3480
3429
|
}
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
instanceRefIds.push(changeTree.refId);
|
|
3484
|
-
totalOperations += Object.keys(changes).length;
|
|
3485
|
-
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
|
-
output += "---\n";
|
|
3489
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3490
|
-
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3491
|
-
output += `Total changes: ${totalOperations}\n`;
|
|
3492
|
-
output += "---\n";
|
|
3493
|
-
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
3494
|
-
const visitedParents = new WeakSet();
|
|
3495
|
-
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3496
|
-
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3497
|
-
if (!visitedParents.has(parentChangeTree)) {
|
|
3498
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3499
|
-
visitedParents.add(parentChangeTree);
|
|
3430
|
+
else if (typeof (type) !== "string") {
|
|
3431
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
3500
3432
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3433
|
+
else {
|
|
3434
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
3435
|
+
}
|
|
3436
|
+
const changeTree = this[$changes];
|
|
3437
|
+
//
|
|
3438
|
+
// Replacing existing "ref", remove it from root.
|
|
3439
|
+
//
|
|
3440
|
+
if (previousValue !== undefined && previousValue[$changes]) {
|
|
3441
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
3442
|
+
this.constructor[$track](changeTree, fieldIndex, OPERATION.DELETE_AND_ADD);
|
|
3443
|
+
}
|
|
3444
|
+
else {
|
|
3445
|
+
this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
|
|
3446
|
+
}
|
|
3447
|
+
//
|
|
3448
|
+
// call setParent() recursively for this and its child
|
|
3449
|
+
// structures.
|
|
3450
|
+
//
|
|
3451
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
3510
3452
|
}
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3453
|
+
else if (previousValue !== undefined) {
|
|
3454
|
+
//
|
|
3455
|
+
// Setting a field to `null` or `undefined` will delete it.
|
|
3456
|
+
//
|
|
3457
|
+
this[$changes].delete(fieldIndex);
|
|
3458
|
+
}
|
|
3459
|
+
this[fieldCached] = value;
|
|
3460
|
+
},
|
|
3461
|
+
enumerable: true,
|
|
3462
|
+
configurable: true
|
|
3463
|
+
};
|
|
3514
3464
|
}
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3465
|
+
/**
|
|
3466
|
+
* `@deprecated()` flag a field as deprecated.
|
|
3467
|
+
* The previous `@type()` annotation should remain along with this one.
|
|
3468
|
+
*/
|
|
3469
|
+
function deprecated(throws = true) {
|
|
3470
|
+
return function (klass, field) {
|
|
3471
|
+
//
|
|
3472
|
+
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
3473
|
+
//
|
|
3474
|
+
const constructor = klass.constructor;
|
|
3475
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3476
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3477
|
+
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3478
|
+
const fieldIndex = metadata[field];
|
|
3479
|
+
// if (!metadata[field]) {
|
|
3480
|
+
// //
|
|
3481
|
+
// // detect index for this field, considering inheritance
|
|
3482
|
+
// //
|
|
3483
|
+
// metadata[field] = {
|
|
3484
|
+
// type: undefined,
|
|
3485
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3486
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3487
|
+
// ?? -1) + 1 // no fields defined
|
|
3488
|
+
// }
|
|
3489
|
+
// }
|
|
3490
|
+
metadata[fieldIndex].deprecated = true;
|
|
3491
|
+
if (throws) {
|
|
3492
|
+
metadata[$descriptors] ??= {};
|
|
3493
|
+
metadata[$descriptors][field] = {
|
|
3494
|
+
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
3495
|
+
set: function (value) { },
|
|
3496
|
+
enumerable: false,
|
|
3497
|
+
configurable: true
|
|
3498
|
+
};
|
|
3546
3499
|
}
|
|
3547
|
-
|
|
3548
|
-
|
|
3500
|
+
// flag metadata[field] as non-enumerable
|
|
3501
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
3502
|
+
value: metadata[fieldIndex],
|
|
3549
3503
|
enumerable: false,
|
|
3550
|
-
|
|
3551
|
-
configurable: true,
|
|
3504
|
+
configurable: true
|
|
3552
3505
|
});
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
function defineTypes(target, fields, options) {
|
|
3509
|
+
for (let field in fields) {
|
|
3510
|
+
type(fields[field], options)(target.prototype, field);
|
|
3553
3511
|
}
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
const key = Array.from(this.$items.keys())[index];
|
|
3569
|
-
return this.$items.get(key);
|
|
3570
|
-
}
|
|
3571
|
-
entries() {
|
|
3572
|
-
return this.$items.entries();
|
|
3573
|
-
}
|
|
3574
|
-
delete(item) {
|
|
3575
|
-
const entries = this.$items.entries();
|
|
3576
|
-
let index;
|
|
3577
|
-
let entry;
|
|
3578
|
-
while (entry = entries.next()) {
|
|
3579
|
-
if (entry.done) {
|
|
3580
|
-
break;
|
|
3512
|
+
return target;
|
|
3513
|
+
}
|
|
3514
|
+
function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
3515
|
+
const fields = {};
|
|
3516
|
+
const methods = {};
|
|
3517
|
+
const defaultValues = {};
|
|
3518
|
+
const viewTagFields = {};
|
|
3519
|
+
for (let fieldName in fieldsAndMethods) {
|
|
3520
|
+
const value = fieldsAndMethods[fieldName];
|
|
3521
|
+
if (typeof (value) === "object") {
|
|
3522
|
+
if (value['view'] !== undefined) {
|
|
3523
|
+
viewTagFields[fieldName] = (typeof (value['view']) === "boolean")
|
|
3524
|
+
? DEFAULT_VIEW_TAG
|
|
3525
|
+
: value['view'];
|
|
3581
3526
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3527
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3528
|
+
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3529
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3530
|
+
// TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
|
|
3531
|
+
if (Array.isArray(value) || value['array'] !== undefined) {
|
|
3532
|
+
// Collection: Array → new ArraySchema()
|
|
3533
|
+
defaultValues[fieldName] = new ArraySchema();
|
|
3534
|
+
}
|
|
3535
|
+
else if (value['map'] !== undefined) {
|
|
3536
|
+
// Collection: Map → new MapSchema()
|
|
3537
|
+
defaultValues[fieldName] = new MapSchema();
|
|
3538
|
+
}
|
|
3539
|
+
else if (value['collection'] !== undefined) {
|
|
3540
|
+
// Collection: Collection → new CollectionSchema()
|
|
3541
|
+
defaultValues[fieldName] = new CollectionSchema();
|
|
3542
|
+
}
|
|
3543
|
+
else if (value['set'] !== undefined) {
|
|
3544
|
+
// Collection: Set → new SetSchema()
|
|
3545
|
+
defaultValues[fieldName] = new SetSchema();
|
|
3546
|
+
}
|
|
3547
|
+
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3548
|
+
// Direct Schema type: Type → new Type()
|
|
3549
|
+
defaultValues[fieldName] = new value['type']();
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
else {
|
|
3553
|
+
defaultValues[fieldName] = value['default'];
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
else if (typeof (value) === "function") {
|
|
3557
|
+
if (Schema.is(value)) {
|
|
3558
|
+
// Direct Schema type: Type → new Type()
|
|
3559
|
+
defaultValues[fieldName] = new value();
|
|
3560
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3561
|
+
}
|
|
3562
|
+
else {
|
|
3563
|
+
methods[fieldName] = value;
|
|
3585
3564
|
}
|
|
3586
3565
|
}
|
|
3587
|
-
|
|
3588
|
-
|
|
3566
|
+
else {
|
|
3567
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3589
3568
|
}
|
|
3590
|
-
this.deletedItems[index] = this[$changes].delete(index);
|
|
3591
|
-
this.$indexes.delete(index);
|
|
3592
|
-
return this.$items.delete(index);
|
|
3593
|
-
}
|
|
3594
|
-
clear() {
|
|
3595
|
-
const changeTree = this[$changes];
|
|
3596
|
-
// discard previous operations.
|
|
3597
|
-
changeTree.discard(true);
|
|
3598
|
-
changeTree.indexes = {};
|
|
3599
|
-
// remove children references
|
|
3600
|
-
changeTree.forEachChild((childChangeTree, _) => {
|
|
3601
|
-
changeTree.root?.remove(childChangeTree);
|
|
3602
|
-
});
|
|
3603
|
-
// clear previous indexes
|
|
3604
|
-
this.$indexes.clear();
|
|
3605
|
-
// clear items
|
|
3606
|
-
this.$items.clear();
|
|
3607
|
-
changeTree.operation(OPERATION.CLEAR);
|
|
3608
|
-
}
|
|
3609
|
-
has(value) {
|
|
3610
|
-
return Array.from(this.$items.values()).some((v) => v === value);
|
|
3611
|
-
}
|
|
3612
|
-
forEach(callbackfn) {
|
|
3613
|
-
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3614
|
-
}
|
|
3615
|
-
values() {
|
|
3616
|
-
return this.$items.values();
|
|
3617
3569
|
}
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3570
|
+
const getDefaultValues = () => {
|
|
3571
|
+
const defaults = {};
|
|
3572
|
+
for (const fieldName in defaultValues) {
|
|
3573
|
+
const defaultValue = defaultValues[fieldName];
|
|
3574
|
+
// If the default value has a clone method, use it to get a fresh instance
|
|
3575
|
+
if (defaultValue && typeof defaultValue.clone === 'function') {
|
|
3576
|
+
defaults[fieldName] = defaultValue.clone();
|
|
3577
|
+
}
|
|
3578
|
+
else {
|
|
3579
|
+
// Otherwise, use the value as-is (for primitives and non-cloneable objects)
|
|
3580
|
+
defaults[fieldName] = defaultValue;
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
return defaults;
|
|
3584
|
+
};
|
|
3585
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
3586
|
+
constructor(...args) {
|
|
3587
|
+
args[0] = Object.assign({}, getDefaultValues(), args[0]);
|
|
3588
|
+
super(...args);
|
|
3589
|
+
}
|
|
3590
|
+
}, fields);
|
|
3591
|
+
for (let fieldName in viewTagFields) {
|
|
3592
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
3641
3593
|
}
|
|
3642
|
-
|
|
3643
|
-
|
|
3594
|
+
for (let methodName in methods) {
|
|
3595
|
+
klass.prototype[methodName] = methods[methodName];
|
|
3644
3596
|
}
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
this.forEach((value, key) => {
|
|
3648
|
-
values.push((typeof (value['toJSON']) === "function")
|
|
3649
|
-
? value['toJSON']()
|
|
3650
|
-
: value);
|
|
3651
|
-
});
|
|
3652
|
-
return values;
|
|
3597
|
+
if (name) {
|
|
3598
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
3653
3599
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3600
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
3601
|
+
return klass;
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
function getIndent(level) {
|
|
3605
|
+
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
3606
|
+
}
|
|
3607
|
+
function dumpChanges(schema) {
|
|
3608
|
+
const $root = schema[$changes].root;
|
|
3609
|
+
const dump = {
|
|
3610
|
+
ops: {},
|
|
3611
|
+
refs: []
|
|
3612
|
+
};
|
|
3613
|
+
// for (const refId in $root.changes) {
|
|
3614
|
+
let current = $root.changes.next;
|
|
3615
|
+
while (current) {
|
|
3616
|
+
const changeTree = current.changeTree;
|
|
3617
|
+
// skip if ChangeTree is undefined
|
|
3618
|
+
if (changeTree === undefined) {
|
|
3619
|
+
current = current.next;
|
|
3620
|
+
continue;
|
|
3662
3621
|
}
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
}
|
|
3673
|
-
});
|
|
3622
|
+
const changes = changeTree.indexedOperations;
|
|
3623
|
+
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3624
|
+
for (const index in changes) {
|
|
3625
|
+
const op = changes[index];
|
|
3626
|
+
const opName = OPERATION[op];
|
|
3627
|
+
if (!dump.ops[opName]) {
|
|
3628
|
+
dump.ops[opName] = 0;
|
|
3629
|
+
}
|
|
3630
|
+
dump.ops[OPERATION[op]]++;
|
|
3674
3631
|
}
|
|
3675
|
-
|
|
3632
|
+
current = current.next;
|
|
3676
3633
|
}
|
|
3634
|
+
return dump;
|
|
3677
3635
|
}
|
|
3678
|
-
registerType("collection", { constructor: CollectionSchema, });
|
|
3679
3636
|
|
|
3680
3637
|
var _a, _b;
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3638
|
+
/**
|
|
3639
|
+
* Schema encoder / decoder
|
|
3640
|
+
*/
|
|
3641
|
+
class Schema {
|
|
3642
|
+
static { this[_a] = encodeSchemaOperation; }
|
|
3643
|
+
static { this[_b] = decodeSchemaOperation; }
|
|
3644
|
+
/**
|
|
3645
|
+
* Assign the property descriptors required to track changes on this instance.
|
|
3646
|
+
* @param instance
|
|
3647
|
+
*/
|
|
3648
|
+
static initialize(instance) {
|
|
3649
|
+
Object.defineProperty(instance, $changes, {
|
|
3650
|
+
value: new ChangeTree(instance),
|
|
3651
|
+
enumerable: false,
|
|
3652
|
+
writable: true
|
|
3653
|
+
});
|
|
3654
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3655
|
+
}
|
|
3656
|
+
static is(type) {
|
|
3657
|
+
return typeof (type[Symbol.metadata]) === "object";
|
|
3658
|
+
// const metadata = type[Symbol.metadata];
|
|
3659
|
+
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
3660
|
+
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Track property changes
|
|
3663
|
+
*/
|
|
3664
|
+
static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = OPERATION.ADD) {
|
|
3665
|
+
changeTree.change(index, operation);
|
|
3666
|
+
}
|
|
3684
3667
|
/**
|
|
3685
3668
|
* Determine if a property must be filtered.
|
|
3686
3669
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3690,164 +3673,248 @@ class SetSchema {
|
|
|
3690
3673
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3691
3674
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3692
3675
|
*/
|
|
3693
|
-
static [
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
return type['set'] !== undefined;
|
|
3700
|
-
}
|
|
3701
|
-
constructor(initialValues) {
|
|
3702
|
-
this.$items = new Map();
|
|
3703
|
-
this.$indexes = new Map();
|
|
3704
|
-
this.deletedItems = {};
|
|
3705
|
-
this.$refId = 0;
|
|
3706
|
-
this[$changes] = new ChangeTree(this);
|
|
3707
|
-
this[$changes].indexes = {};
|
|
3708
|
-
if (initialValues) {
|
|
3709
|
-
initialValues.forEach((v) => this.add(v));
|
|
3710
|
-
}
|
|
3711
|
-
Object.defineProperty(this, $childType, {
|
|
3712
|
-
value: undefined,
|
|
3713
|
-
enumerable: false,
|
|
3714
|
-
writable: true,
|
|
3715
|
-
configurable: true,
|
|
3716
|
-
});
|
|
3717
|
-
}
|
|
3718
|
-
add(value) {
|
|
3719
|
-
// immediatelly return false if value already added.
|
|
3720
|
-
if (this.has(value)) {
|
|
3721
|
-
return false;
|
|
3722
|
-
}
|
|
3723
|
-
// set "index" for reference.
|
|
3724
|
-
const index = this.$refId++;
|
|
3725
|
-
if ((value[$changes]) !== undefined) {
|
|
3726
|
-
value[$changes].setParent(this, this[$changes].root, index);
|
|
3727
|
-
}
|
|
3728
|
-
const operation = this[$changes].indexes[index]?.op ?? OPERATION.ADD;
|
|
3729
|
-
this[$changes].indexes[index] = index;
|
|
3730
|
-
this.$indexes.set(index, index);
|
|
3731
|
-
this.$items.set(index, value);
|
|
3732
|
-
this[$changes].change(index, operation);
|
|
3733
|
-
return index;
|
|
3734
|
-
}
|
|
3735
|
-
entries() {
|
|
3736
|
-
return this.$items.entries();
|
|
3737
|
-
}
|
|
3738
|
-
delete(item) {
|
|
3739
|
-
const entries = this.$items.entries();
|
|
3740
|
-
let index;
|
|
3741
|
-
let entry;
|
|
3742
|
-
while (entry = entries.next()) {
|
|
3743
|
-
if (entry.done) {
|
|
3744
|
-
break;
|
|
3745
|
-
}
|
|
3746
|
-
if (item === entry.value[1]) {
|
|
3747
|
-
index = entry.value[0];
|
|
3748
|
-
break;
|
|
3749
|
-
}
|
|
3676
|
+
static [$filter](ref, index, view) {
|
|
3677
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
3678
|
+
const tag = metadata[index]?.tag;
|
|
3679
|
+
if (view === undefined) {
|
|
3680
|
+
// shared pass/encode: encode if doesn't have a tag
|
|
3681
|
+
return tag === undefined;
|
|
3750
3682
|
}
|
|
3751
|
-
if (
|
|
3752
|
-
|
|
3683
|
+
else if (tag === undefined) {
|
|
3684
|
+
// view pass: no tag
|
|
3685
|
+
return true;
|
|
3753
3686
|
}
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
changeTree.indexes = {};
|
|
3763
|
-
// clear previous indexes
|
|
3764
|
-
this.$indexes.clear();
|
|
3765
|
-
// clear items
|
|
3766
|
-
this.$items.clear();
|
|
3767
|
-
changeTree.operation(OPERATION.CLEAR);
|
|
3768
|
-
}
|
|
3769
|
-
has(value) {
|
|
3770
|
-
const values = this.$items.values();
|
|
3771
|
-
let has = false;
|
|
3772
|
-
let entry;
|
|
3773
|
-
while (entry = values.next()) {
|
|
3774
|
-
if (entry.done) {
|
|
3775
|
-
break;
|
|
3776
|
-
}
|
|
3777
|
-
if (value === entry.value) {
|
|
3778
|
-
has = true;
|
|
3779
|
-
break;
|
|
3780
|
-
}
|
|
3687
|
+
else if (tag === DEFAULT_VIEW_TAG) {
|
|
3688
|
+
// view pass: default tag
|
|
3689
|
+
return view.isChangeTreeVisible(ref[$changes]);
|
|
3690
|
+
}
|
|
3691
|
+
else {
|
|
3692
|
+
// view pass: custom tag
|
|
3693
|
+
const tags = view.tags?.get(ref[$changes]);
|
|
3694
|
+
return tags && tags.has(tag);
|
|
3781
3695
|
}
|
|
3782
|
-
return has;
|
|
3783
3696
|
}
|
|
3784
|
-
|
|
3785
|
-
|
|
3697
|
+
// allow inherited classes to have a constructor
|
|
3698
|
+
constructor(...args) {
|
|
3699
|
+
//
|
|
3700
|
+
// inline
|
|
3701
|
+
// Schema.initialize(this);
|
|
3702
|
+
//
|
|
3703
|
+
Schema.initialize(this);
|
|
3704
|
+
//
|
|
3705
|
+
// Assign initial values
|
|
3706
|
+
//
|
|
3707
|
+
if (args[0]) {
|
|
3708
|
+
Object.assign(this, args[0]);
|
|
3709
|
+
}
|
|
3786
3710
|
}
|
|
3787
|
-
|
|
3788
|
-
|
|
3711
|
+
assign(props) {
|
|
3712
|
+
Object.assign(this, props);
|
|
3713
|
+
return this;
|
|
3789
3714
|
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3715
|
+
/**
|
|
3716
|
+
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3717
|
+
* @param instance Schema instance
|
|
3718
|
+
* @param property string representing the property name, or number representing the index of the property.
|
|
3719
|
+
* @param operation OPERATION to perform (detected automatically)
|
|
3720
|
+
*/
|
|
3721
|
+
setDirty(property, operation) {
|
|
3722
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3723
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
3792
3724
|
}
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3725
|
+
clone() {
|
|
3726
|
+
const cloned = new (this.constructor);
|
|
3727
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3728
|
+
//
|
|
3729
|
+
// TODO: clone all properties, not only annotated ones
|
|
3730
|
+
//
|
|
3731
|
+
// for (const field in this) {
|
|
3732
|
+
for (const fieldIndex in metadata) {
|
|
3733
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3734
|
+
const field = metadata[fieldIndex].name;
|
|
3735
|
+
if (typeof (this[field]) === "object" &&
|
|
3736
|
+
typeof (this[field]?.clone) === "function") {
|
|
3737
|
+
// deep clone
|
|
3738
|
+
cloned[field] = this[field].clone();
|
|
3739
|
+
}
|
|
3740
|
+
else {
|
|
3741
|
+
// primitive values
|
|
3742
|
+
cloned[field] = this[field];
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
return cloned;
|
|
3796
3746
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
3747
|
+
toJSON() {
|
|
3748
|
+
const obj = {};
|
|
3749
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3750
|
+
for (const index in metadata) {
|
|
3751
|
+
const field = metadata[index];
|
|
3752
|
+
const fieldName = field.name;
|
|
3753
|
+
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
3754
|
+
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
3755
|
+
? this[fieldName]['toJSON']()
|
|
3756
|
+
: this[fieldName];
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
return obj;
|
|
3799
3760
|
}
|
|
3800
|
-
|
|
3801
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* Used in tests only
|
|
3763
|
+
* @internal
|
|
3764
|
+
*/
|
|
3765
|
+
discardAllChanges() {
|
|
3766
|
+
this[$changes].discardAll();
|
|
3802
3767
|
}
|
|
3803
3768
|
[$getByIndex](index) {
|
|
3804
|
-
|
|
3769
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3770
|
+
return this[metadata[index].name];
|
|
3805
3771
|
}
|
|
3806
3772
|
[$deleteByIndex](index) {
|
|
3807
|
-
const
|
|
3808
|
-
this
|
|
3809
|
-
this.$indexes.delete(index);
|
|
3773
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3774
|
+
this[metadata[index].name] = undefined;
|
|
3810
3775
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3776
|
+
/**
|
|
3777
|
+
* Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
|
|
3778
|
+
*
|
|
3779
|
+
* @param ref Schema instance
|
|
3780
|
+
* @param showContents display JSON contents of the instance
|
|
3781
|
+
* @returns
|
|
3782
|
+
*/
|
|
3783
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3784
|
+
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3785
|
+
const changeTree = ref[$changes];
|
|
3786
|
+
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
3787
|
+
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3788
|
+
// log reference count if > 1
|
|
3789
|
+
const refCount = (root?.refCount?.[refId] > 1)
|
|
3790
|
+
? ` [×${root.refCount[refId]}]`
|
|
3791
|
+
: '';
|
|
3792
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3793
|
+
changeTree.forEachChild((childChangeTree, indexOrKey) => {
|
|
3794
|
+
let key = indexOrKey;
|
|
3795
|
+
if (typeof indexOrKey === 'number' && ref['$indexes']) {
|
|
3796
|
+
// MapSchema
|
|
3797
|
+
key = ref['$indexes'].get(indexOrKey) ?? indexOrKey;
|
|
3798
|
+
}
|
|
3799
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3800
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3801
|
+
});
|
|
3802
|
+
return output;
|
|
3813
3803
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3804
|
+
static debugRefIdEncodingOrder(ref, changeSet = 'allChanges') {
|
|
3805
|
+
let encodeOrder = [];
|
|
3806
|
+
let current = ref[$changes].root[changeSet].next;
|
|
3807
|
+
while (current) {
|
|
3808
|
+
if (current.changeTree) {
|
|
3809
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3810
|
+
}
|
|
3811
|
+
current = current.next;
|
|
3812
|
+
}
|
|
3813
|
+
return encodeOrder;
|
|
3816
3814
|
}
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
this.forEach((value, key) => {
|
|
3820
|
-
values.push((typeof (value['toJSON']) === "function")
|
|
3821
|
-
? value['toJSON']()
|
|
3822
|
-
: value);
|
|
3823
|
-
});
|
|
3824
|
-
return values;
|
|
3815
|
+
static debugRefIdsFromDecoder(decoder) {
|
|
3816
|
+
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3825
3817
|
}
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3818
|
+
/**
|
|
3819
|
+
* Return a string representation of the changes on a Schema instance.
|
|
3820
|
+
* The list of changes is cleared after each encode.
|
|
3821
|
+
*
|
|
3822
|
+
* @param instance Schema instance
|
|
3823
|
+
* @param isEncodeAll Return "full encode" instead of current change set.
|
|
3824
|
+
* @returns
|
|
3825
|
+
*/
|
|
3826
|
+
static debugChanges(instance, isEncodeAll = false) {
|
|
3827
|
+
const changeTree = instance[$changes];
|
|
3828
|
+
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3829
|
+
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3830
|
+
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3831
|
+
function dumpChangeSet(changeSet) {
|
|
3832
|
+
changeSet.operations
|
|
3833
|
+
.filter(op => op)
|
|
3834
|
+
.forEach((index) => {
|
|
3835
|
+
const operation = changeTree.indexedOperations[index];
|
|
3836
|
+
output += `- [${index}]: ${OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3837
|
+
});
|
|
3834
3838
|
}
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3839
|
+
dumpChangeSet(changeSet);
|
|
3840
|
+
// display filtered changes
|
|
3841
|
+
if (!isEncodeAll &&
|
|
3842
|
+
changeTree.filteredChanges &&
|
|
3843
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3844
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3845
|
+
dumpChangeSet(changeTree.filteredChanges);
|
|
3846
|
+
}
|
|
3847
|
+
// display filtered changes
|
|
3848
|
+
if (isEncodeAll &&
|
|
3849
|
+
changeTree.allFilteredChanges &&
|
|
3850
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3851
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3852
|
+
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3853
|
+
}
|
|
3854
|
+
return output;
|
|
3855
|
+
}
|
|
3856
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3857
|
+
let output = "";
|
|
3858
|
+
const rootChangeTree = ref[$changes];
|
|
3859
|
+
const root = rootChangeTree.root;
|
|
3860
|
+
const changeTrees = new Map();
|
|
3861
|
+
const instanceRefIds = [];
|
|
3862
|
+
let totalOperations = 0;
|
|
3863
|
+
// TODO: FIXME: this method is not working as expected
|
|
3864
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3865
|
+
const changeTree = root.changeTrees[refId];
|
|
3866
|
+
if (!changeTree) {
|
|
3867
|
+
continue;
|
|
3868
|
+
}
|
|
3869
|
+
let includeChangeTree = false;
|
|
3870
|
+
let parentChangeTrees = [];
|
|
3871
|
+
let parentChangeTree = changeTree.parent?.[$changes];
|
|
3872
|
+
if (changeTree === rootChangeTree) {
|
|
3873
|
+
includeChangeTree = true;
|
|
3874
|
+
}
|
|
3875
|
+
else {
|
|
3876
|
+
while (parentChangeTree !== undefined) {
|
|
3877
|
+
parentChangeTrees.push(parentChangeTree);
|
|
3878
|
+
if (parentChangeTree.ref === ref) {
|
|
3879
|
+
includeChangeTree = true;
|
|
3880
|
+
break;
|
|
3881
|
+
}
|
|
3882
|
+
parentChangeTree = parentChangeTree.parent?.[$changes];
|
|
3841
3883
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3884
|
+
}
|
|
3885
|
+
if (includeChangeTree) {
|
|
3886
|
+
instanceRefIds.push(changeTree.refId);
|
|
3887
|
+
totalOperations += Object.keys(changes).length;
|
|
3888
|
+
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
output += "---\n";
|
|
3892
|
+
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3893
|
+
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3894
|
+
output += `Total changes: ${totalOperations}\n`;
|
|
3895
|
+
output += "---\n";
|
|
3896
|
+
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
3897
|
+
const visitedParents = new WeakSet();
|
|
3898
|
+
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3899
|
+
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3900
|
+
if (!visitedParents.has(parentChangeTree)) {
|
|
3901
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3902
|
+
visitedParents.add(parentChangeTree);
|
|
3844
3903
|
}
|
|
3845
3904
|
});
|
|
3905
|
+
const changes = changeTree.indexedOperations;
|
|
3906
|
+
const level = parentChangeTrees.length;
|
|
3907
|
+
const indent = getIndent(level);
|
|
3908
|
+
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3909
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3910
|
+
for (const index in changes) {
|
|
3911
|
+
const operation = changes[index];
|
|
3912
|
+
output += `${getIndent(level + 1)}${OPERATION[operation]}: ${index}\n`;
|
|
3913
|
+
}
|
|
3846
3914
|
}
|
|
3847
|
-
return
|
|
3915
|
+
return `${output}`;
|
|
3848
3916
|
}
|
|
3849
3917
|
}
|
|
3850
|
-
registerType("set", { constructor: SetSchema });
|
|
3851
3918
|
|
|
3852
3919
|
/******************************************************************************
|
|
3853
3920
|
Copyright (c) Microsoft Corporation.
|