@fluidframework/tool-utils 0.58.2001 → 0.59.2000-61729
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/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/snapshotNormalizer.d.ts +6 -0
- package/dist/snapshotNormalizer.d.ts.map +1 -1
- package/dist/snapshotNormalizer.js +34 -26
- package/dist/snapshotNormalizer.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/snapshotNormalizer.d.ts +6 -0
- package/lib/snapshotNormalizer.d.ts.map +1 -1
- package/lib/snapshotNormalizer.js +34 -26
- package/lib/snapshotNormalizer.js.map +1 -1
- package/package.json +12 -7
- package/src/packageVersion.ts +1 -1
- package/src/snapshotNormalizer.ts +47 -26
package/dist/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/tool-utils";
|
|
8
|
-
export declare const pkgVersion = "0.
|
|
8
|
+
export declare const pkgVersion = "0.59.2000-61729";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,oBAAoB,CAAC"}
|
package/dist/packageVersion.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.pkgVersion = exports.pkgName = void 0;
|
|
10
10
|
exports.pkgName = "@fluidframework/tool-utils";
|
|
11
|
-
exports.pkgVersion = "0.
|
|
11
|
+
exports.pkgVersion = "0.59.2000-61729";
|
|
12
12
|
//# sourceMappingURL=packageVersion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4BAA4B,CAAC;AACvC,QAAA,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4BAA4B,CAAC;AACvC,QAAA,UAAU,GAAG,iBAAiB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"0.59.2000-61729\";\n"]}
|
|
@@ -6,6 +6,12 @@ import { ITree } from "@fluidframework/protocol-definitions";
|
|
|
6
6
|
export declare const gcBlobPrefix = "__gc";
|
|
7
7
|
export interface ISnapshotNormalizerConfig {
|
|
8
8
|
blobsToNormalize?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* channel types who's content (non-attribute) blobs will be excluded.
|
|
11
|
+
* this is used to exclude the content of channels who's content cannot be compared
|
|
12
|
+
* as the content is non-deterministic between snapshot at the same sequence number.
|
|
13
|
+
*/
|
|
14
|
+
excludedChannelContentTypes?: string[];
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACH,KAAK,EAGR,MAAM,sCAAsC,CAAC;AAE9C,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,MAAM,WAAW,yBAAyB;IAEtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACH,KAAK,EAGR,MAAM,sCAAsC,CAAC;AAE9C,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,MAAM,WAAW,yBAAyB;IAEtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1C;AAmFD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAgBhG"}
|
|
@@ -33,7 +33,6 @@ function getDeepSortedArray(array) {
|
|
|
33
33
|
const serializedElem2 = JSON.stringify(elem2);
|
|
34
34
|
return serializedElem1.localeCompare(serializedElem2);
|
|
35
35
|
};
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
37
36
|
return sortedArray.sort(sortFn);
|
|
38
37
|
}
|
|
39
38
|
/**
|
|
@@ -56,7 +55,6 @@ function getDeepSortedObject(obj) {
|
|
|
56
55
|
sortedObj[key] = value;
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
60
58
|
return sortedObj;
|
|
61
59
|
}
|
|
62
60
|
/**
|
|
@@ -100,35 +98,13 @@ function getNormalizedBlobContent(blobContent, blobName) {
|
|
|
100
98
|
* @returns a copy of the normalized snapshot tree.
|
|
101
99
|
*/
|
|
102
100
|
function getNormalizedSnapshot(snapshot, config) {
|
|
103
|
-
var _a;
|
|
104
101
|
// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be
|
|
105
102
|
// parsed and deep sorted.
|
|
106
|
-
const blobsToNormalize = [...(_a = config === null || config === void 0 ? void 0 : config.blobsToNormalize) !== null && _a !== void 0 ? _a : []];
|
|
107
103
|
const normalizedEntries = [];
|
|
108
104
|
for (const entry of snapshot.entries) {
|
|
109
|
-
|
|
110
|
-
case protocol_definitions_1.TreeEntry.Blob: {
|
|
111
|
-
let contents = entry.value.contents;
|
|
112
|
-
// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
|
|
113
|
-
if (blobsToNormalize.includes(entry.path) || entry.path.startsWith(exports.gcBlobPrefix)) {
|
|
114
|
-
contents = getNormalizedBlobContent(contents, entry.path);
|
|
115
|
-
}
|
|
116
|
-
normalizedEntries.push(new protocol_base_1.BlobTreeEntry(entry.path, contents));
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
case protocol_definitions_1.TreeEntry.Tree: {
|
|
120
|
-
normalizedEntries.push(new protocol_base_1.TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config)));
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case protocol_definitions_1.TreeEntry.Attachment: {
|
|
124
|
-
normalizedEntries.push(new protocol_base_1.AttachmentTreeEntry(entry.path, (entry.value).id));
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
default:
|
|
128
|
-
throw new Error("Unknown entry type");
|
|
129
|
-
}
|
|
105
|
+
normalizedEntries.push(normalizeEntry(entry, config));
|
|
130
106
|
}
|
|
131
|
-
//
|
|
107
|
+
// Sort the tree entries based on their path.
|
|
132
108
|
normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));
|
|
133
109
|
return {
|
|
134
110
|
entries: normalizedEntries,
|
|
@@ -136,4 +112,36 @@ function getNormalizedSnapshot(snapshot, config) {
|
|
|
136
112
|
};
|
|
137
113
|
}
|
|
138
114
|
exports.getNormalizedSnapshot = getNormalizedSnapshot;
|
|
115
|
+
function normalizeEntry(entry, config) {
|
|
116
|
+
var _a;
|
|
117
|
+
switch (entry.type) {
|
|
118
|
+
case protocol_definitions_1.TreeEntry.Blob: {
|
|
119
|
+
let contents = entry.value.contents;
|
|
120
|
+
// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
|
|
121
|
+
if (((_a = config === null || config === void 0 ? void 0 : config.blobsToNormalize) === null || _a === void 0 ? void 0 : _a.includes(entry.path)) || entry.path.startsWith(exports.gcBlobPrefix)) {
|
|
122
|
+
contents = getNormalizedBlobContent(contents, entry.path);
|
|
123
|
+
}
|
|
124
|
+
return new protocol_base_1.BlobTreeEntry(entry.path, contents);
|
|
125
|
+
}
|
|
126
|
+
case protocol_definitions_1.TreeEntry.Tree: {
|
|
127
|
+
if ((config === null || config === void 0 ? void 0 : config.excludedChannelContentTypes) !== undefined) {
|
|
128
|
+
for (const maybeAttributes of entry.value.entries) {
|
|
129
|
+
if (maybeAttributes.type === protocol_definitions_1.TreeEntry.Blob && maybeAttributes.path === ".attributes") {
|
|
130
|
+
const parsed = JSON.parse(maybeAttributes.value.contents);
|
|
131
|
+
if (parsed.type !== undefined && config.excludedChannelContentTypes.includes(parsed.type)) {
|
|
132
|
+
// remove everything to match the unknown channel
|
|
133
|
+
return new protocol_base_1.TreeTreeEntry(entry.path, { entries: [maybeAttributes] });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return new protocol_base_1.TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));
|
|
139
|
+
}
|
|
140
|
+
case protocol_definitions_1.TreeEntry.Attachment: {
|
|
141
|
+
return new protocol_base_1.AttachmentTreeEntry(entry.path, (entry.value).id);
|
|
142
|
+
}
|
|
143
|
+
default:
|
|
144
|
+
throw new Error("Unknown entry type");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
139
147
|
//# sourceMappingURL=snapshotNormalizer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { AttachmentTreeEntry, BlobTreeEntry, TreeTreeEntry } from \"@fluidframework/protocol-base\";\nimport {\n ITree,\n TreeEntry,\n ITreeEntry,\n} from \"@fluidframework/protocol-definitions\";\n\nexport const gcBlobPrefix = \"__gc\";\n\nexport interface ISnapshotNormalizerConfig {\n // The paths of blobs whose contents should be normalized.\n blobsToNormalize?: string[];\n}\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: any[]): any[] {\n const sortedArray: any[] = [];\n // Sort arrays and objects, if any, in the array.\n for (const element of array) {\n if (Array.isArray(element)) {\n sortedArray.push(getDeepSortedArray(element));\n } else if (element instanceof Object) {\n sortedArray.push(getDeepSortedObject(element));\n } else {\n sortedArray.push(element);\n }\n }\n\n // Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n // element's stringified version.\n const sortFn = (elem1: any, elem2: any) => {\n const serializedElem1 = JSON.stringify(elem1);\n const serializedElem2 = JSON.stringify(elem2);\n return serializedElem1.localeCompare(serializedElem2);\n };\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return sortedArray.sort(sortFn);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject(obj: any): any {\n const sortedObj: any = {};\n // Sort the object keys first. Then sort arrays and objects, if any, in the object.\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n const value = obj[key];\n if (Array.isArray(value)) {\n sortedObj[key] = getDeepSortedArray(value);\n } else if (value instanceof Object) {\n sortedObj[key] = getDeepSortedObject(value);\n } else {\n sortedObj[key] = value;\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n let content = blobContent;\n if (blobName.startsWith(gcBlobPrefix)) {\n // GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n // of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n // So, remove it for the purposes of comparing snapshots.\n const gcState = JSON.parse(content);\n for (const [, data] of Object.entries(gcState.gcNodes)) {\n delete (data as any).unreferencedTimestampMs;\n }\n content = JSON.stringify(gcState);\n }\n\n // Deep sort the content if it's parseable.\n try {\n let contentObj = JSON.parse(content);\n if (Array.isArray(contentObj)) {\n contentObj = getDeepSortedArray(contentObj);\n } else if (contentObj instanceof Object) {\n contentObj = getDeepSortedObject(contentObj);\n }\n content = JSON.stringify(contentObj);\n } catch {}\n return content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n */\nexport function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormalizerConfig): ITree {\n // Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n // parsed and deep sorted.\n const blobsToNormalize = [ ...config?.blobsToNormalize ?? [] ];\n const normalizedEntries: ITreeEntry[] = [];\n\n for (const entry of snapshot.entries) {\n switch (entry.type) {\n case TreeEntry.Blob: {\n let contents = entry.value.contents;\n // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.\n if (blobsToNormalize.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {\n contents = getNormalizedBlobContent(contents, entry.path);\n }\n normalizedEntries.push(new BlobTreeEntry(entry.path, contents));\n break;\n }\n case TreeEntry.Tree: {\n normalizedEntries.push(new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config)));\n break;\n }\n case TreeEntry.Attachment: {\n normalizedEntries.push(new AttachmentTreeEntry(entry.path, (entry.value).id));\n break;\n }\n\n default:\n throw new Error(\"Unknown entry type\");\n }\n }\n\n // Sory the tree entries based on their path.\n normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n return {\n entries: normalizedEntries,\n id: snapshot.id,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { AttachmentTreeEntry, BlobTreeEntry, TreeTreeEntry } from \"@fluidframework/protocol-base\";\nimport {\n ITree,\n TreeEntry,\n ITreeEntry,\n} from \"@fluidframework/protocol-definitions\";\n\nexport const gcBlobPrefix = \"__gc\";\n\nexport interface ISnapshotNormalizerConfig {\n // The paths of blobs whose contents should be normalized.\n blobsToNormalize?: string[];\n /**\n * channel types who's content (non-attribute) blobs will be excluded.\n * this is used to exclude the content of channels who's content cannot be compared\n * as the content is non-deterministic between snapshot at the same sequence number.\n */\n excludedChannelContentTypes?: string[];\n}\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: any[]): any[] {\n const sortedArray: any[] = [];\n // Sort arrays and objects, if any, in the array.\n for (const element of array) {\n if (Array.isArray(element)) {\n sortedArray.push(getDeepSortedArray(element));\n } else if (element instanceof Object) {\n sortedArray.push(getDeepSortedObject(element));\n } else {\n sortedArray.push(element);\n }\n }\n\n // Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n // element's stringified version.\n const sortFn = (elem1: any, elem2: any) => {\n const serializedElem1 = JSON.stringify(elem1);\n const serializedElem2 = JSON.stringify(elem2);\n return serializedElem1.localeCompare(serializedElem2);\n };\n\n return sortedArray.sort(sortFn);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject(obj: any): any {\n const sortedObj: any = {};\n // Sort the object keys first. Then sort arrays and objects, if any, in the object.\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n const value = obj[key];\n if (Array.isArray(value)) {\n sortedObj[key] = getDeepSortedArray(value);\n } else if (value instanceof Object) {\n sortedObj[key] = getDeepSortedObject(value);\n } else {\n sortedObj[key] = value;\n }\n }\n\n return sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n let content = blobContent;\n if (blobName.startsWith(gcBlobPrefix)) {\n // GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n // of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n // So, remove it for the purposes of comparing snapshots.\n const gcState = JSON.parse(content);\n for (const [, data] of Object.entries(gcState.gcNodes)) {\n delete (data as any).unreferencedTimestampMs;\n }\n content = JSON.stringify(gcState);\n }\n\n // Deep sort the content if it's parseable.\n try {\n let contentObj = JSON.parse(content);\n if (Array.isArray(contentObj)) {\n contentObj = getDeepSortedArray(contentObj);\n } else if (contentObj instanceof Object) {\n contentObj = getDeepSortedObject(contentObj);\n }\n content = JSON.stringify(contentObj);\n } catch {}\n return content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n */\nexport function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormalizerConfig): ITree {\n // Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n // parsed and deep sorted.\n const normalizedEntries: ITreeEntry[] = [];\n\n for (const entry of snapshot.entries) {\n normalizedEntries.push(normalizeEntry(entry, config));\n }\n\n // Sort the tree entries based on their path.\n normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n return {\n entries: normalizedEntries,\n id: snapshot.id,\n };\n}\n\nfunction normalizeEntry(\n entry: ITreeEntry,\n config: ISnapshotNormalizerConfig | undefined,\n): ITreeEntry {\n switch (entry.type) {\n case TreeEntry.Blob: {\n let contents = entry.value.contents;\n // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.\n if (config?.blobsToNormalize?.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {\n contents = getNormalizedBlobContent(contents, entry.path);\n }\n return new BlobTreeEntry(entry.path, contents);\n }\n case TreeEntry.Tree: {\n if(config?.excludedChannelContentTypes !== undefined) {\n for(const maybeAttributes of entry.value.entries) {\n if(maybeAttributes.type === TreeEntry.Blob && maybeAttributes.path === \".attributes\") {\n const parsed: {type?: string} = JSON.parse(maybeAttributes.value.contents);\n if(parsed.type !== undefined && config.excludedChannelContentTypes.includes(parsed.type)) {\n // remove everything to match the unknown channel\n return new TreeTreeEntry(entry.path, {entries:[maybeAttributes]});\n }\n }\n }\n }\n\n return new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));\n }\n case TreeEntry.Attachment: {\n return new AttachmentTreeEntry(entry.path, (entry.value).id);\n }\n\n default:\n throw new Error(\"Unknown entry type\");\n }\n}\n"]}
|
package/lib/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/tool-utils";
|
|
8
|
-
export declare const pkgVersion = "0.
|
|
8
|
+
export declare const pkgVersion = "0.59.2000-61729";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,oBAAoB,CAAC"}
|
package/lib/packageVersion.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4BAA4B,CAAC;AACpD,MAAM,CAAC,MAAM,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4BAA4B,CAAC;AACpD,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"0.59.2000-61729\";\n"]}
|
|
@@ -6,6 +6,12 @@ import { ITree } from "@fluidframework/protocol-definitions";
|
|
|
6
6
|
export declare const gcBlobPrefix = "__gc";
|
|
7
7
|
export interface ISnapshotNormalizerConfig {
|
|
8
8
|
blobsToNormalize?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* channel types who's content (non-attribute) blobs will be excluded.
|
|
11
|
+
* this is used to exclude the content of channels who's content cannot be compared
|
|
12
|
+
* as the content is non-deterministic between snapshot at the same sequence number.
|
|
13
|
+
*/
|
|
14
|
+
excludedChannelContentTypes?: string[];
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACH,KAAK,EAGR,MAAM,sCAAsC,CAAC;AAE9C,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,MAAM,WAAW,yBAAyB;IAEtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshotNormalizer.d.ts","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACH,KAAK,EAGR,MAAM,sCAAsC,CAAC;AAE9C,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,MAAM,WAAW,yBAAyB;IAEtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1C;AAmFD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAgBhG"}
|
|
@@ -30,7 +30,6 @@ function getDeepSortedArray(array) {
|
|
|
30
30
|
const serializedElem2 = JSON.stringify(elem2);
|
|
31
31
|
return serializedElem1.localeCompare(serializedElem2);
|
|
32
32
|
};
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
34
33
|
return sortedArray.sort(sortFn);
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
@@ -53,7 +52,6 @@ function getDeepSortedObject(obj) {
|
|
|
53
52
|
sortedObj[key] = value;
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
57
55
|
return sortedObj;
|
|
58
56
|
}
|
|
59
57
|
/**
|
|
@@ -97,39 +95,49 @@ function getNormalizedBlobContent(blobContent, blobName) {
|
|
|
97
95
|
* @returns a copy of the normalized snapshot tree.
|
|
98
96
|
*/
|
|
99
97
|
export function getNormalizedSnapshot(snapshot, config) {
|
|
100
|
-
var _a;
|
|
101
98
|
// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be
|
|
102
99
|
// parsed and deep sorted.
|
|
103
|
-
const blobsToNormalize = [...(_a = config === null || config === void 0 ? void 0 : config.blobsToNormalize) !== null && _a !== void 0 ? _a : []];
|
|
104
100
|
const normalizedEntries = [];
|
|
105
101
|
for (const entry of snapshot.entries) {
|
|
106
|
-
|
|
107
|
-
case TreeEntry.Blob: {
|
|
108
|
-
let contents = entry.value.contents;
|
|
109
|
-
// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
|
|
110
|
-
if (blobsToNormalize.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {
|
|
111
|
-
contents = getNormalizedBlobContent(contents, entry.path);
|
|
112
|
-
}
|
|
113
|
-
normalizedEntries.push(new BlobTreeEntry(entry.path, contents));
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case TreeEntry.Tree: {
|
|
117
|
-
normalizedEntries.push(new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config)));
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
case TreeEntry.Attachment: {
|
|
121
|
-
normalizedEntries.push(new AttachmentTreeEntry(entry.path, (entry.value).id));
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
default:
|
|
125
|
-
throw new Error("Unknown entry type");
|
|
126
|
-
}
|
|
102
|
+
normalizedEntries.push(normalizeEntry(entry, config));
|
|
127
103
|
}
|
|
128
|
-
//
|
|
104
|
+
// Sort the tree entries based on their path.
|
|
129
105
|
normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));
|
|
130
106
|
return {
|
|
131
107
|
entries: normalizedEntries,
|
|
132
108
|
id: snapshot.id,
|
|
133
109
|
};
|
|
134
110
|
}
|
|
111
|
+
function normalizeEntry(entry, config) {
|
|
112
|
+
var _a;
|
|
113
|
+
switch (entry.type) {
|
|
114
|
+
case TreeEntry.Blob: {
|
|
115
|
+
let contents = entry.value.contents;
|
|
116
|
+
// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
|
|
117
|
+
if (((_a = config === null || config === void 0 ? void 0 : config.blobsToNormalize) === null || _a === void 0 ? void 0 : _a.includes(entry.path)) || entry.path.startsWith(gcBlobPrefix)) {
|
|
118
|
+
contents = getNormalizedBlobContent(contents, entry.path);
|
|
119
|
+
}
|
|
120
|
+
return new BlobTreeEntry(entry.path, contents);
|
|
121
|
+
}
|
|
122
|
+
case TreeEntry.Tree: {
|
|
123
|
+
if ((config === null || config === void 0 ? void 0 : config.excludedChannelContentTypes) !== undefined) {
|
|
124
|
+
for (const maybeAttributes of entry.value.entries) {
|
|
125
|
+
if (maybeAttributes.type === TreeEntry.Blob && maybeAttributes.path === ".attributes") {
|
|
126
|
+
const parsed = JSON.parse(maybeAttributes.value.contents);
|
|
127
|
+
if (parsed.type !== undefined && config.excludedChannelContentTypes.includes(parsed.type)) {
|
|
128
|
+
// remove everything to match the unknown channel
|
|
129
|
+
return new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));
|
|
135
|
+
}
|
|
136
|
+
case TreeEntry.Attachment: {
|
|
137
|
+
return new AttachmentTreeEntry(entry.path, (entry.value).id);
|
|
138
|
+
}
|
|
139
|
+
default:
|
|
140
|
+
throw new Error("Unknown entry type");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
135
143
|
//# sourceMappingURL=snapshotNormalizer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { AttachmentTreeEntry, BlobTreeEntry, TreeTreeEntry } from \"@fluidframework/protocol-base\";\nimport {\n ITree,\n TreeEntry,\n ITreeEntry,\n} from \"@fluidframework/protocol-definitions\";\n\nexport const gcBlobPrefix = \"__gc\";\n\nexport interface ISnapshotNormalizerConfig {\n // The paths of blobs whose contents should be normalized.\n blobsToNormalize?: string[];\n}\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: any[]): any[] {\n const sortedArray: any[] = [];\n // Sort arrays and objects, if any, in the array.\n for (const element of array) {\n if (Array.isArray(element)) {\n sortedArray.push(getDeepSortedArray(element));\n } else if (element instanceof Object) {\n sortedArray.push(getDeepSortedObject(element));\n } else {\n sortedArray.push(element);\n }\n }\n\n // Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n // element's stringified version.\n const sortFn = (elem1: any, elem2: any) => {\n const serializedElem1 = JSON.stringify(elem1);\n const serializedElem2 = JSON.stringify(elem2);\n return serializedElem1.localeCompare(serializedElem2);\n };\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return sortedArray.sort(sortFn);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject(obj: any): any {\n const sortedObj: any = {};\n // Sort the object keys first. Then sort arrays and objects, if any, in the object.\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n const value = obj[key];\n if (Array.isArray(value)) {\n sortedObj[key] = getDeepSortedArray(value);\n } else if (value instanceof Object) {\n sortedObj[key] = getDeepSortedObject(value);\n } else {\n sortedObj[key] = value;\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n let content = blobContent;\n if (blobName.startsWith(gcBlobPrefix)) {\n // GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n // of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n // So, remove it for the purposes of comparing snapshots.\n const gcState = JSON.parse(content);\n for (const [, data] of Object.entries(gcState.gcNodes)) {\n delete (data as any).unreferencedTimestampMs;\n }\n content = JSON.stringify(gcState);\n }\n\n // Deep sort the content if it's parseable.\n try {\n let contentObj = JSON.parse(content);\n if (Array.isArray(contentObj)) {\n contentObj = getDeepSortedArray(contentObj);\n } else if (contentObj instanceof Object) {\n contentObj = getDeepSortedObject(contentObj);\n }\n content = JSON.stringify(contentObj);\n } catch {}\n return content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n */\nexport function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormalizerConfig): ITree {\n // Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n // parsed and deep sorted.\n const blobsToNormalize = [ ...config?.blobsToNormalize ?? [] ];\n const normalizedEntries: ITreeEntry[] = [];\n\n for (const entry of snapshot.entries) {\n switch (entry.type) {\n case TreeEntry.Blob: {\n let contents = entry.value.contents;\n // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.\n if (blobsToNormalize.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {\n contents = getNormalizedBlobContent(contents, entry.path);\n }\n normalizedEntries.push(new BlobTreeEntry(entry.path, contents));\n break;\n }\n case TreeEntry.Tree: {\n normalizedEntries.push(new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config)));\n break;\n }\n case TreeEntry.Attachment: {\n normalizedEntries.push(new AttachmentTreeEntry(entry.path, (entry.value).id));\n break;\n }\n\n default:\n throw new Error(\"Unknown entry type\");\n }\n }\n\n // Sory the tree entries based on their path.\n normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n return {\n entries: normalizedEntries,\n id: snapshot.id,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"snapshotNormalizer.js","sourceRoot":"","sources":["../src/snapshotNormalizer.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { AttachmentTreeEntry, BlobTreeEntry, TreeTreeEntry } from \"@fluidframework/protocol-base\";\nimport {\n ITree,\n TreeEntry,\n ITreeEntry,\n} from \"@fluidframework/protocol-definitions\";\n\nexport const gcBlobPrefix = \"__gc\";\n\nexport interface ISnapshotNormalizerConfig {\n // The paths of blobs whose contents should be normalized.\n blobsToNormalize?: string[];\n /**\n * channel types who's content (non-attribute) blobs will be excluded.\n * this is used to exclude the content of channels who's content cannot be compared\n * as the content is non-deterministic between snapshot at the same sequence number.\n */\n excludedChannelContentTypes?: string[];\n}\n\n/**\n * Function that deep sorts an array. It handles cases where array elements are objects or arrays.\n * @returns the sorted array.\n */\nfunction getDeepSortedArray(array: any[]): any[] {\n const sortedArray: any[] = [];\n // Sort arrays and objects, if any, in the array.\n for (const element of array) {\n if (Array.isArray(element)) {\n sortedArray.push(getDeepSortedArray(element));\n } else if (element instanceof Object) {\n sortedArray.push(getDeepSortedObject(element));\n } else {\n sortedArray.push(element);\n }\n }\n\n // Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each\n // element's stringified version.\n const sortFn = (elem1: any, elem2: any) => {\n const serializedElem1 = JSON.stringify(elem1);\n const serializedElem2 = JSON.stringify(elem2);\n return serializedElem1.localeCompare(serializedElem2);\n };\n\n return sortedArray.sort(sortFn);\n}\n\n/**\n * Function that deep sorts an object. It handles cases where object properties are arrays or objects.\n * @returns the sorted object.\n */\nfunction getDeepSortedObject(obj: any): any {\n const sortedObj: any = {};\n // Sort the object keys first. Then sort arrays and objects, if any, in the object.\n const keys = Object.keys(obj).sort();\n for (const key of keys) {\n const value = obj[key];\n if (Array.isArray(value)) {\n sortedObj[key] = getDeepSortedArray(value);\n } else if (value instanceof Object) {\n sortedObj[key] = getDeepSortedObject(value);\n } else {\n sortedObj[key] = value;\n }\n }\n\n return sortedObj;\n}\n\n/**\n * Function that normalizes a blob's content. If the content is an object or an array, deep sorts them.\n * Special handling for certain runtime blobs, such as the \"gc\" blob.\n * @returns the normalized blob content.\n */\nfunction getNormalizedBlobContent(blobContent: string, blobName: string): string {\n let content = blobContent;\n if (blobName.startsWith(gcBlobPrefix)) {\n // GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp\n // of the last op processed or current timestamp and can differ between clients depending on when GC was run.\n // So, remove it for the purposes of comparing snapshots.\n const gcState = JSON.parse(content);\n for (const [, data] of Object.entries(gcState.gcNodes)) {\n delete (data as any).unreferencedTimestampMs;\n }\n content = JSON.stringify(gcState);\n }\n\n // Deep sort the content if it's parseable.\n try {\n let contentObj = JSON.parse(content);\n if (Array.isArray(contentObj)) {\n contentObj = getDeepSortedArray(contentObj);\n } else if (contentObj instanceof Object) {\n contentObj = getDeepSortedObject(contentObj);\n }\n content = JSON.stringify(contentObj);\n } catch {}\n return content;\n}\n\n/**\n * Helper function that normalizes the given snapshot tree. It sorts objects and arrays in the snapshot. It also\n * normalizes certain blob contents for which the order of content does not matter. For example, garbage collection\n * blobs contains objects / arrays whose element order do not matter.\n * @param snapshot - The snapshot tree to normalize.\n * @param config - Configs to use when normalizing snapshot. For example, it can contain paths of blobs whose contents\n * should be normalized as well.\n * @returns a copy of the normalized snapshot tree.\n */\nexport function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormalizerConfig): ITree {\n // Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be\n // parsed and deep sorted.\n const normalizedEntries: ITreeEntry[] = [];\n\n for (const entry of snapshot.entries) {\n normalizedEntries.push(normalizeEntry(entry, config));\n }\n\n // Sort the tree entries based on their path.\n normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));\n\n return {\n entries: normalizedEntries,\n id: snapshot.id,\n };\n}\n\nfunction normalizeEntry(\n entry: ITreeEntry,\n config: ISnapshotNormalizerConfig | undefined,\n): ITreeEntry {\n switch (entry.type) {\n case TreeEntry.Blob: {\n let contents = entry.value.contents;\n // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.\n if (config?.blobsToNormalize?.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {\n contents = getNormalizedBlobContent(contents, entry.path);\n }\n return new BlobTreeEntry(entry.path, contents);\n }\n case TreeEntry.Tree: {\n if(config?.excludedChannelContentTypes !== undefined) {\n for(const maybeAttributes of entry.value.entries) {\n if(maybeAttributes.type === TreeEntry.Blob && maybeAttributes.path === \".attributes\") {\n const parsed: {type?: string} = JSON.parse(maybeAttributes.value.contents);\n if(parsed.type !== undefined && config.excludedChannelContentTypes.includes(parsed.type)) {\n // remove everything to match the unknown channel\n return new TreeTreeEntry(entry.path, {entries:[maybeAttributes]});\n }\n }\n }\n }\n\n return new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));\n }\n case TreeEntry.Attachment: {\n return new AttachmentTreeEntry(entry.path, (entry.value).id);\n }\n\n default:\n throw new Error(\"Unknown entry type\");\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/tool-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.2000-61729",
|
|
4
4
|
"description": "Common utilities for Fluid tools",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"lint:fix": "npm run eslint:fix",
|
|
33
33
|
"test": "npm run test:mocha",
|
|
34
34
|
"test:coverage": "nyc npm run test:report",
|
|
35
|
-
"test:mocha": "mocha --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
|
|
35
|
+
"test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
|
|
36
36
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
37
37
|
"test:report": "npm test -- -- --reporter xunit --reporter-option output=nyc/mocha-junit-report.xml",
|
|
38
38
|
"tsc": "tsc",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@fluidframework/common-utils": "^0.32.1",
|
|
64
|
-
"@fluidframework/odsp-doclib-utils": "
|
|
65
|
-
"@fluidframework/protocol-base": "^0.
|
|
66
|
-
"@fluidframework/protocol-definitions": "^0.
|
|
64
|
+
"@fluidframework/odsp-doclib-utils": "0.59.2000-61729",
|
|
65
|
+
"@fluidframework/protocol-base": "^0.1036.1000-0",
|
|
66
|
+
"@fluidframework/protocol-definitions": "^0.1028.1000-0",
|
|
67
67
|
"async-mutex": "^0.3.1",
|
|
68
68
|
"debug": "^4.1.1",
|
|
69
69
|
"jwt-decode": "^2.2.0",
|
|
@@ -71,8 +71,9 @@
|
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@fluidframework/build-common": "^0.23.0",
|
|
74
|
-
"@fluidframework/eslint-config-fluid": "^0.
|
|
75
|
-
"@fluidframework/mocha-test-setup": "
|
|
74
|
+
"@fluidframework/eslint-config-fluid": "^0.28.1000-61189",
|
|
75
|
+
"@fluidframework/mocha-test-setup": "0.59.2000-61729",
|
|
76
|
+
"@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@^0.58.0",
|
|
76
77
|
"@microsoft/api-extractor": "^7.16.1",
|
|
77
78
|
"@rushstack/eslint-config": "^2.5.1",
|
|
78
79
|
"@types/debug": "^4.1.5",
|
|
@@ -96,5 +97,9 @@
|
|
|
96
97
|
"rimraf": "^2.6.2",
|
|
97
98
|
"typescript": "~4.1.3",
|
|
98
99
|
"typescript-formatter": "7.1.0"
|
|
100
|
+
},
|
|
101
|
+
"typeValidation": {
|
|
102
|
+
"version": "0.59.1000",
|
|
103
|
+
"broken": {}
|
|
99
104
|
}
|
|
100
105
|
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -15,6 +15,12 @@ export const gcBlobPrefix = "__gc";
|
|
|
15
15
|
export interface ISnapshotNormalizerConfig {
|
|
16
16
|
// The paths of blobs whose contents should be normalized.
|
|
17
17
|
blobsToNormalize?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* channel types who's content (non-attribute) blobs will be excluded.
|
|
20
|
+
* this is used to exclude the content of channels who's content cannot be compared
|
|
21
|
+
* as the content is non-deterministic between snapshot at the same sequence number.
|
|
22
|
+
*/
|
|
23
|
+
excludedChannelContentTypes?: string[];
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
/**
|
|
@@ -41,7 +47,7 @@ function getDeepSortedArray(array: any[]): any[] {
|
|
|
41
47
|
const serializedElem2 = JSON.stringify(elem2);
|
|
42
48
|
return serializedElem1.localeCompare(serializedElem2);
|
|
43
49
|
};
|
|
44
|
-
|
|
50
|
+
|
|
45
51
|
return sortedArray.sort(sortFn);
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -63,7 +69,7 @@ function getDeepSortedObject(obj: any): any {
|
|
|
63
69
|
sortedObj[key] = value;
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
|
-
|
|
72
|
+
|
|
67
73
|
return sortedObj;
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -110,35 +116,13 @@ function getNormalizedBlobContent(blobContent: string, blobName: string): string
|
|
|
110
116
|
export function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormalizerConfig): ITree {
|
|
111
117
|
// Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be
|
|
112
118
|
// parsed and deep sorted.
|
|
113
|
-
const blobsToNormalize = [ ...config?.blobsToNormalize ?? [] ];
|
|
114
119
|
const normalizedEntries: ITreeEntry[] = [];
|
|
115
120
|
|
|
116
121
|
for (const entry of snapshot.entries) {
|
|
117
|
-
|
|
118
|
-
case TreeEntry.Blob: {
|
|
119
|
-
let contents = entry.value.contents;
|
|
120
|
-
// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
|
|
121
|
-
if (blobsToNormalize.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {
|
|
122
|
-
contents = getNormalizedBlobContent(contents, entry.path);
|
|
123
|
-
}
|
|
124
|
-
normalizedEntries.push(new BlobTreeEntry(entry.path, contents));
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case TreeEntry.Tree: {
|
|
128
|
-
normalizedEntries.push(new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config)));
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case TreeEntry.Attachment: {
|
|
132
|
-
normalizedEntries.push(new AttachmentTreeEntry(entry.path, (entry.value).id));
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
default:
|
|
137
|
-
throw new Error("Unknown entry type");
|
|
138
|
-
}
|
|
122
|
+
normalizedEntries.push(normalizeEntry(entry, config));
|
|
139
123
|
}
|
|
140
124
|
|
|
141
|
-
//
|
|
125
|
+
// Sort the tree entries based on their path.
|
|
142
126
|
normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));
|
|
143
127
|
|
|
144
128
|
return {
|
|
@@ -146,3 +130,40 @@ export function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormali
|
|
|
146
130
|
id: snapshot.id,
|
|
147
131
|
};
|
|
148
132
|
}
|
|
133
|
+
|
|
134
|
+
function normalizeEntry(
|
|
135
|
+
entry: ITreeEntry,
|
|
136
|
+
config: ISnapshotNormalizerConfig | undefined,
|
|
137
|
+
): ITreeEntry {
|
|
138
|
+
switch (entry.type) {
|
|
139
|
+
case TreeEntry.Blob: {
|
|
140
|
+
let contents = entry.value.contents;
|
|
141
|
+
// If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
|
|
142
|
+
if (config?.blobsToNormalize?.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {
|
|
143
|
+
contents = getNormalizedBlobContent(contents, entry.path);
|
|
144
|
+
}
|
|
145
|
+
return new BlobTreeEntry(entry.path, contents);
|
|
146
|
+
}
|
|
147
|
+
case TreeEntry.Tree: {
|
|
148
|
+
if(config?.excludedChannelContentTypes !== undefined) {
|
|
149
|
+
for(const maybeAttributes of entry.value.entries) {
|
|
150
|
+
if(maybeAttributes.type === TreeEntry.Blob && maybeAttributes.path === ".attributes") {
|
|
151
|
+
const parsed: {type?: string} = JSON.parse(maybeAttributes.value.contents);
|
|
152
|
+
if(parsed.type !== undefined && config.excludedChannelContentTypes.includes(parsed.type)) {
|
|
153
|
+
// remove everything to match the unknown channel
|
|
154
|
+
return new TreeTreeEntry(entry.path, {entries:[maybeAttributes]});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));
|
|
161
|
+
}
|
|
162
|
+
case TreeEntry.Attachment: {
|
|
163
|
+
return new AttachmentTreeEntry(entry.path, (entry.value).id);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
default:
|
|
167
|
+
throw new Error("Unknown entry type");
|
|
168
|
+
}
|
|
169
|
+
}
|