@fluidframework/tool-utils 2.0.0-internal.3.0.2 → 2.0.0-internal.3.2.0

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.
Files changed (48) hide show
  1. package/.eslintrc.js +19 -23
  2. package/.mocharc.js +2 -2
  3. package/api-extractor.json +2 -2
  4. package/dist/fluidToolRC.d.ts.map +1 -1
  5. package/dist/fluidToolRC.js.map +1 -1
  6. package/dist/httpHelpers.d.ts.map +1 -1
  7. package/dist/httpHelpers.js +6 -2
  8. package/dist/httpHelpers.js.map +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/odspTokenManager.d.ts.map +1 -1
  13. package/dist/odspTokenManager.js +5 -2
  14. package/dist/odspTokenManager.js.map +1 -1
  15. package/dist/packageVersion.d.ts +1 -1
  16. package/dist/packageVersion.js +1 -1
  17. package/dist/packageVersion.js.map +1 -1
  18. package/dist/snapshotNormalizer.d.ts.map +1 -1
  19. package/dist/snapshotNormalizer.js +13 -10
  20. package/dist/snapshotNormalizer.js.map +1 -1
  21. package/lib/fluidToolRC.d.ts.map +1 -1
  22. package/lib/fluidToolRC.js.map +1 -1
  23. package/lib/httpHelpers.d.ts.map +1 -1
  24. package/lib/httpHelpers.js +6 -2
  25. package/lib/httpHelpers.js.map +1 -1
  26. package/lib/index.d.ts +1 -1
  27. package/lib/index.d.ts.map +1 -1
  28. package/lib/index.js +1 -1
  29. package/lib/index.js.map +1 -1
  30. package/lib/odspTokenManager.d.ts.map +1 -1
  31. package/lib/odspTokenManager.js +5 -2
  32. package/lib/odspTokenManager.js.map +1 -1
  33. package/lib/packageVersion.d.ts +1 -1
  34. package/lib/packageVersion.js +1 -1
  35. package/lib/packageVersion.js.map +1 -1
  36. package/lib/snapshotNormalizer.d.ts.map +1 -1
  37. package/lib/snapshotNormalizer.js +14 -11
  38. package/lib/snapshotNormalizer.js.map +1 -1
  39. package/package.json +40 -39
  40. package/prettier.config.cjs +1 -1
  41. package/src/fluidToolRC.ts +36 -36
  42. package/src/httpHelpers.ts +58 -44
  43. package/src/index.ts +5 -1
  44. package/src/odspTokenManager.ts +301 -309
  45. package/src/packageVersion.ts +1 -1
  46. package/src/snapshotNormalizer.ts +178 -171
  47. package/tsconfig.esnext.json +6 -6
  48. package/tsconfig.json +10 -16
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tool-utils";
9
- export const pkgVersion = "2.0.0-internal.3.0.2";
9
+ export const pkgVersion = "2.0.0-internal.3.2.0";
@@ -4,11 +4,7 @@
4
4
  */
5
5
 
6
6
  import { AttachmentTreeEntry, BlobTreeEntry, TreeTreeEntry } from "@fluidframework/protocol-base";
7
- import {
8
- ITree,
9
- TreeEntry,
10
- ITreeEntry,
11
- } from "@fluidframework/protocol-definitions";
7
+ import { ITree, TreeEntry, ITreeEntry } from "@fluidframework/protocol-definitions";
12
8
 
13
9
  /** The name of the metadata blob added to the root of the container runtime. */
14
10
  const metadataBlobName = ".metadata";
@@ -16,14 +12,14 @@ const metadataBlobName = ".metadata";
16
12
  export const gcBlobPrefix = "__gc";
17
13
 
18
14
  export interface ISnapshotNormalizerConfig {
19
- // The paths of blobs whose contents should be normalized.
20
- blobsToNormalize?: string[];
21
- /**
22
- * channel types who's content (non-attribute) blobs will be excluded.
23
- * this is used to exclude the content of channels who's content cannot be compared
24
- * as the content is non-deterministic between snapshot at the same sequence number.
25
- */
26
- excludedChannelContentTypes?: string[];
15
+ // The paths of blobs whose contents should be normalized.
16
+ blobsToNormalize?: string[];
17
+ /**
18
+ * channel types who's content (non-attribute) blobs will be excluded.
19
+ * this is used to exclude the content of channels who's content cannot be compared
20
+ * as the content is non-deterministic between snapshot at the same sequence number.
21
+ */
22
+ excludedChannelContentTypes?: string[];
27
23
  }
28
24
 
29
25
  /**
@@ -31,27 +27,27 @@ export interface ISnapshotNormalizerConfig {
31
27
  * @returns the sorted array.
32
28
  */
33
29
  function getDeepSortedArray(array: any[]): any[] {
34
- const sortedArray: any[] = [];
35
- // Sort arrays and objects, if any, in the array.
36
- for (const element of array) {
37
- if (Array.isArray(element)) {
38
- sortedArray.push(getDeepSortedArray(element));
39
- } else if (element instanceof Object) {
40
- sortedArray.push(getDeepSortedObject(element));
41
- } else {
42
- sortedArray.push(element);
43
- }
44
- }
45
-
46
- // Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each
47
- // element's stringified version.
48
- const sortFn = (elem1: any, elem2: any) => {
49
- const serializedElem1 = JSON.stringify(elem1);
50
- const serializedElem2 = JSON.stringify(elem2);
51
- return serializedElem1.localeCompare(serializedElem2);
52
- };
53
-
54
- return sortedArray.sort(sortFn);
30
+ const sortedArray: any[] = [];
31
+ // Sort arrays and objects, if any, in the array.
32
+ for (const element of array) {
33
+ if (Array.isArray(element)) {
34
+ sortedArray.push(getDeepSortedArray(element));
35
+ } else if (element instanceof Object) {
36
+ sortedArray.push(getDeepSortedObject(element));
37
+ } else {
38
+ sortedArray.push(element);
39
+ }
40
+ }
41
+
42
+ // Now that all the arrays and objects in this array's elements have been sorted, sort it by comparing each
43
+ // element's stringified version.
44
+ const sortFn = (elem1: any, elem2: any) => {
45
+ const serializedElem1 = JSON.stringify(elem1);
46
+ const serializedElem2 = JSON.stringify(elem2);
47
+ return serializedElem1.localeCompare(serializedElem2);
48
+ };
49
+
50
+ return sortedArray.sort(sortFn);
55
51
  }
56
52
 
57
53
  /**
@@ -59,21 +55,21 @@ function getDeepSortedArray(array: any[]): any[] {
59
55
  * @returns the sorted object.
60
56
  */
61
57
  function getDeepSortedObject(obj: any): any {
62
- const sortedObj: any = {};
63
- // Sort the object keys first. Then sort arrays and objects, if any, in the object.
64
- const keys = Object.keys(obj).sort();
65
- for (const key of keys) {
66
- const value = obj[key];
67
- if (Array.isArray(value)) {
68
- sortedObj[key] = getDeepSortedArray(value);
69
- } else if (value instanceof Object) {
70
- sortedObj[key] = getDeepSortedObject(value);
71
- } else {
72
- sortedObj[key] = value;
73
- }
74
- }
75
-
76
- return sortedObj;
58
+ const sortedObj: any = {};
59
+ // Sort the object keys first. Then sort arrays and objects, if any, in the object.
60
+ const keys = Object.keys(obj).sort();
61
+ for (const key of keys) {
62
+ const value = obj[key];
63
+ if (Array.isArray(value)) {
64
+ sortedObj[key] = getDeepSortedArray(value);
65
+ } else if (value instanceof Object) {
66
+ sortedObj[key] = getDeepSortedObject(value);
67
+ } else {
68
+ sortedObj[key] = value;
69
+ }
70
+ }
71
+
72
+ return sortedObj;
77
73
  }
78
74
 
79
75
  /**
@@ -82,46 +78,46 @@ function getDeepSortedObject(obj: any): any {
82
78
  * @returns the normalized blob content.
83
79
  */
84
80
  function getNormalizedBlobContent(blobContent: string, blobName: string): string {
85
- let content = blobContent;
86
- if (blobName.startsWith(gcBlobPrefix)) {
87
- // GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp
88
- // of the last op processed or current timestamp and can differ between clients depending on when GC was run.
89
- // So, remove it for the purposes of comparing snapshots.
90
- const gcState = JSON.parse(content);
91
- for (const [, data] of Object.entries(gcState.gcNodes)) {
92
- delete (data as any).unreferencedTimestampMs;
93
- }
94
- content = JSON.stringify(gcState);
95
- }
96
-
97
- /**
98
- * The metadata blob has "summaryNumber" or "summaryCount" that tells which summary this is for a container. It can
99
- * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#
100
- * 1000, if one client has summarized 10 times and other has summarizer 15 times, summaryNumber will be different
101
- * for them. So, update "summaryNumber" to 0 for purposes of comparing snapshots.
102
- */
103
- if (blobName === metadataBlobName) {
104
- const metadata = JSON.parse(content);
105
- if (metadata.summaryNumber !== undefined) {
106
- metadata.summaryNumber = 0;
107
- }
108
- if (metadata.summaryCount !== undefined) {
109
- metadata.summaryCount = 0;
110
- }
111
- content = JSON.stringify(metadata);
112
- }
113
-
114
- // Deep sort the content if it's parseable.
115
- try {
116
- let contentObj = JSON.parse(content);
117
- if (Array.isArray(contentObj)) {
118
- contentObj = getDeepSortedArray(contentObj);
119
- } else if (contentObj instanceof Object) {
120
- contentObj = getDeepSortedObject(contentObj);
121
- }
122
- content = JSON.stringify(contentObj);
123
- } catch {}
124
- return content;
81
+ let content = blobContent;
82
+ if (blobName.startsWith(gcBlobPrefix)) {
83
+ // GC blobs may contain `unreferencedTimestampMs` for node that became unreferenced. This is the timestamp
84
+ // of the last op processed or current timestamp and can differ between clients depending on when GC was run.
85
+ // So, remove it for the purposes of comparing snapshots.
86
+ const gcState = JSON.parse(content);
87
+ for (const [, data] of Object.entries(gcState.gcNodes)) {
88
+ delete (data as any).unreferencedTimestampMs;
89
+ }
90
+ content = JSON.stringify(gcState);
91
+ }
92
+
93
+ /**
94
+ * The metadata blob has "summaryNumber" or "summaryCount" that tells which summary this is for a container. It can
95
+ * be different in summaries of two clients even if they are generated at the same sequence#. For instance, at seq#
96
+ * 1000, if one client has summarized 10 times and other has summarizer 15 times, summaryNumber will be different
97
+ * for them. So, update "summaryNumber" to 0 for purposes of comparing snapshots.
98
+ */
99
+ if (blobName === metadataBlobName) {
100
+ const metadata = JSON.parse(content);
101
+ if (metadata.summaryNumber !== undefined) {
102
+ metadata.summaryNumber = 0;
103
+ }
104
+ if (metadata.summaryCount !== undefined) {
105
+ metadata.summaryCount = 0;
106
+ }
107
+ content = JSON.stringify(metadata);
108
+ }
109
+
110
+ // Deep sort the content if it's parseable.
111
+ try {
112
+ let contentObj = JSON.parse(content);
113
+ if (Array.isArray(contentObj)) {
114
+ contentObj = getDeepSortedArray(contentObj);
115
+ } else if (contentObj instanceof Object) {
116
+ contentObj = getDeepSortedObject(contentObj);
117
+ }
118
+ content = JSON.stringify(contentObj);
119
+ } catch {}
120
+ return content;
125
121
  }
126
122
 
127
123
  /**
@@ -134,104 +130,115 @@ function getNormalizedBlobContent(blobContent: string, blobName: string): string
134
130
  * @returns a copy of the normalized snapshot tree.
135
131
  */
136
132
  export function getNormalizedSnapshot(snapshot: ITree, config?: ISnapshotNormalizerConfig): ITree {
137
- // Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be
138
- // parsed and deep sorted.
139
- const normalizedEntries: ITreeEntry[] = [];
140
-
141
- // The metadata blob in the root of the summary tree needs to be normalized.
142
- const blobsToNormalize = [metadataBlobName, ...config?.blobsToNormalize ?? []];
143
- for (const entry of snapshot.entries) {
144
- normalizedEntries.push(normalizeEntry(entry, { ...config, blobsToNormalize }));
145
- }
146
-
147
- // Sort the tree entries based on their path.
148
- normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));
149
-
150
- return {
151
- entries: normalizedEntries,
152
- id: snapshot.id,
153
- };
133
+ // Merge blobs to normalize in the config with runtime blobs to normalize. The contents of these blobs will be
134
+ // parsed and deep sorted.
135
+ const normalizedEntries: ITreeEntry[] = [];
136
+
137
+ // The metadata blob in the root of the summary tree needs to be normalized.
138
+ const blobsToNormalize = [metadataBlobName, ...(config?.blobsToNormalize ?? [])];
139
+ for (const entry of snapshot.entries) {
140
+ normalizedEntries.push(normalizeEntry(entry, { ...config, blobsToNormalize }));
141
+ }
142
+
143
+ // Sort the tree entries based on their path.
144
+ normalizedEntries.sort((a, b) => a.path.localeCompare(b.path));
145
+
146
+ return {
147
+ entries: normalizedEntries,
148
+ id: snapshot.id,
149
+ };
154
150
  }
155
151
 
156
152
  function normalizeMatrix(value: ITree): ITree {
157
- const rows = value.entries.find((e) => e.path === "rows");
153
+ const rows = value.entries.find((e) => e.path === "rows");
158
154
 
159
- if (!rows || !("entries" in rows.value)) {
160
- return value;
161
- }
155
+ if (!rows || !("entries" in rows.value)) {
156
+ return value;
157
+ }
162
158
 
163
- const segments = rows.value.entries.find((e) => e.path === "segments");
159
+ const segments = rows.value.entries.find((e) => e.path === "segments");
164
160
 
165
- if (!segments || !("entries" in segments.value)) {
166
- return value;
167
- }
161
+ if (!segments || !("entries" in segments.value)) {
162
+ return value;
163
+ }
168
164
 
169
- const header = segments.value.entries.find((e) => e.path === "header");
165
+ const header = segments.value.entries.find((e) => e.path === "header");
170
166
 
171
- if (!header || !("contents" in header.value)) {
172
- return value;
173
- }
167
+ if (!header || !("contents" in header.value)) {
168
+ return value;
169
+ }
174
170
 
175
- if (!header?.value.contents.includes("removedClientId")) {
176
- return value;
177
- }
171
+ if (!header?.value.contents.includes("removedClientId")) {
172
+ return value;
173
+ }
178
174
 
179
- const contents = JSON.parse(header?.value.contents);
175
+ const contents = JSON.parse(header?.value.contents);
180
176
 
181
- for (const segment of contents.segments) {
182
- if ("removedClientId" in segment) {
183
- segment.removedClientId = undefined;
184
- }
177
+ for (const segment of contents.segments) {
178
+ if ("removedClientId" in segment) {
179
+ segment.removedClientId = undefined;
180
+ }
185
181
 
186
- if ("removedClientIds" in segment) {
187
- segment.removedClientIds = undefined;
188
- }
189
- }
182
+ if ("removedClientIds" in segment) {
183
+ segment.removedClientIds = undefined;
184
+ }
185
+ }
190
186
 
191
- header.value.contents = JSON.stringify(contents);
187
+ header.value.contents = JSON.stringify(contents);
192
188
 
193
- return value;
189
+ return value;
194
190
  }
195
191
 
196
192
  function normalizeEntry(
197
- entry: ITreeEntry,
198
- config: ISnapshotNormalizerConfig | undefined,
193
+ entry: ITreeEntry,
194
+ config: ISnapshotNormalizerConfig | undefined,
199
195
  ): ITreeEntry {
200
- switch (entry.type) {
201
- case TreeEntry.Blob: {
202
- let contents = entry.value.contents;
203
- // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
204
- if (config?.blobsToNormalize?.includes(entry.path) || entry.path.startsWith(gcBlobPrefix)) {
205
- contents = getNormalizedBlobContent(contents, entry.path);
206
- }
207
- return new BlobTreeEntry(entry.path, contents);
208
- }
209
- case TreeEntry.Tree: {
210
- if (config?.excludedChannelContentTypes !== undefined) {
211
- for (const maybeAttributes of entry.value.entries) {
212
- if (maybeAttributes.type === TreeEntry.Blob && maybeAttributes.path === ".attributes") {
213
- const parsed: { type?: string; } = JSON.parse(maybeAttributes.value.contents);
214
- if (parsed.type === "https://graph.microsoft.com/types/sharedmatrix") {
215
- return new TreeTreeEntry(
216
- entry.path,
217
- normalizeMatrix(getNormalizedSnapshot(entry.value, config)),
218
- );
219
- }
220
- if (parsed.type !== undefined && config.excludedChannelContentTypes.includes(parsed.type)) {
221
- // remove everything to match the unknown channel
222
- return new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });
223
- }
224
- }
225
- }
226
- }
227
-
228
- return new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));
229
- }
230
- case TreeEntry.Attachment: {
231
- return new AttachmentTreeEntry(entry.path, (entry.value).id);
232
- }
233
-
234
- default:
235
- throw new Error("Unknown entry type");
236
- }
196
+ switch (entry.type) {
197
+ case TreeEntry.Blob: {
198
+ let contents = entry.value.contents;
199
+ // If this blob has to be normalized or it's a GC blob, parse and sort the blob contents first.
200
+ if (
201
+ config?.blobsToNormalize?.includes(entry.path) ||
202
+ entry.path.startsWith(gcBlobPrefix)
203
+ ) {
204
+ contents = getNormalizedBlobContent(contents, entry.path);
205
+ }
206
+ return new BlobTreeEntry(entry.path, contents);
207
+ }
208
+ case TreeEntry.Tree: {
209
+ if (config?.excludedChannelContentTypes !== undefined) {
210
+ for (const maybeAttributes of entry.value.entries) {
211
+ if (
212
+ maybeAttributes.type === TreeEntry.Blob &&
213
+ maybeAttributes.path === ".attributes"
214
+ ) {
215
+ const parsed: { type?: string } = JSON.parse(
216
+ maybeAttributes.value.contents,
217
+ );
218
+ if (parsed.type === "https://graph.microsoft.com/types/sharedmatrix") {
219
+ return new TreeTreeEntry(
220
+ entry.path,
221
+ normalizeMatrix(getNormalizedSnapshot(entry.value, config)),
222
+ );
223
+ }
224
+ if (
225
+ parsed.type !== undefined &&
226
+ config.excludedChannelContentTypes.includes(parsed.type)
227
+ ) {
228
+ // remove everything to match the unknown channel
229
+ return new TreeTreeEntry(entry.path, { entries: [maybeAttributes] });
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ return new TreeTreeEntry(entry.path, getNormalizedSnapshot(entry.value, config));
236
+ }
237
+ case TreeEntry.Attachment: {
238
+ return new AttachmentTreeEntry(entry.path, entry.value.id);
239
+ }
240
+
241
+ default:
242
+ throw new Error("Unknown entry type");
243
+ }
237
244
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./lib",
5
- "module": "esnext"
6
- },
7
- }
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./lib",
5
+ "module": "esnext",
6
+ },
7
+ }
package/tsconfig.json CHANGED
@@ -1,17 +1,11 @@
1
1
  {
2
- "extends": "@fluidframework/build-common/ts-common-config.json",
3
- "exclude": [
4
- "src/test/**/*"
5
- ],
6
- "compilerOptions": {
7
- "rootDir": "./src",
8
- "outDir": "./dist",
9
- "composite": true,
10
- "types": [
11
- "node"
12
- ]
13
- },
14
- "include": [
15
- "src/**/*"
16
- ]
17
- }
2
+ "extends": "@fluidframework/build-common/ts-common-config.json",
3
+ "exclude": ["src/test/**/*"],
4
+ "compilerOptions": {
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ "composite": true,
8
+ "types": ["node"],
9
+ },
10
+ "include": ["src/**/*"],
11
+ }