@fluidframework/map 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.224419

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 (118) hide show
  1. package/.eslintrc.js +12 -11
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +162 -0
  4. package/README.md +24 -8
  5. package/api-extractor-lint.json +4 -0
  6. package/api-extractor.json +2 -2
  7. package/api-report/map.api.md +297 -0
  8. package/dist/{directory.js → directory.cjs} +749 -228
  9. package/dist/directory.cjs.map +1 -0
  10. package/dist/directory.d.ts +567 -34
  11. package/dist/directory.d.ts.map +1 -1
  12. package/dist/index.cjs +27 -0
  13. package/dist/index.cjs.map +1 -0
  14. package/dist/index.d.ts +5 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/{interfaces.js → interfaces.cjs} +1 -1
  17. package/dist/interfaces.cjs.map +1 -0
  18. package/dist/interfaces.d.ts +167 -184
  19. package/dist/interfaces.d.ts.map +1 -1
  20. package/dist/internalInterfaces.cjs +7 -0
  21. package/dist/internalInterfaces.cjs.map +1 -0
  22. package/dist/internalInterfaces.d.ts +101 -0
  23. package/dist/internalInterfaces.d.ts.map +1 -0
  24. package/dist/{localValues.js → localValues.cjs} +15 -3
  25. package/dist/localValues.cjs.map +1 -0
  26. package/dist/localValues.d.ts +17 -6
  27. package/dist/localValues.d.ts.map +1 -1
  28. package/dist/map-alpha.d.ts +982 -0
  29. package/dist/map-beta.d.ts +275 -0
  30. package/dist/map-public.d.ts +275 -0
  31. package/dist/map-untrimmed.d.ts +996 -0
  32. package/dist/{map.js → map.cjs} +39 -34
  33. package/dist/map.cjs.map +1 -0
  34. package/dist/map.d.ts +10 -17
  35. package/dist/map.d.ts.map +1 -1
  36. package/dist/{mapKernel.js → mapKernel.cjs} +122 -79
  37. package/dist/mapKernel.cjs.map +1 -0
  38. package/dist/mapKernel.d.ts +17 -48
  39. package/dist/mapKernel.d.ts.map +1 -1
  40. package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
  41. package/dist/packageVersion.cjs.map +1 -0
  42. package/dist/packageVersion.d.ts +1 -1
  43. package/dist/packageVersion.d.ts.map +1 -1
  44. package/dist/tsdoc-metadata.json +11 -0
  45. package/lib/directory.d.mts +902 -0
  46. package/lib/directory.d.mts.map +1 -0
  47. package/lib/{directory.js → directory.mjs} +736 -199
  48. package/lib/directory.mjs.map +1 -0
  49. package/lib/index.d.mts +9 -0
  50. package/lib/index.d.mts.map +1 -0
  51. package/lib/index.mjs +8 -0
  52. package/lib/index.mjs.map +1 -0
  53. package/lib/{interfaces.d.ts → interfaces.d.mts} +167 -184
  54. package/lib/interfaces.d.mts.map +1 -0
  55. package/lib/{interfaces.js → interfaces.mjs} +1 -1
  56. package/lib/interfaces.mjs.map +1 -0
  57. package/lib/internalInterfaces.d.mts +101 -0
  58. package/lib/internalInterfaces.d.mts.map +1 -0
  59. package/lib/internalInterfaces.mjs +6 -0
  60. package/lib/internalInterfaces.mjs.map +1 -0
  61. package/lib/{localValues.d.ts → localValues.d.mts} +18 -7
  62. package/lib/localValues.d.mts.map +1 -0
  63. package/lib/{localValues.js → localValues.mjs} +15 -3
  64. package/lib/localValues.mjs.map +1 -0
  65. package/lib/map-alpha.d.mts +982 -0
  66. package/lib/map-beta.d.mts +275 -0
  67. package/lib/map-public.d.mts +275 -0
  68. package/lib/map-untrimmed.d.mts +996 -0
  69. package/lib/{map.d.ts → map.d.mts} +11 -18
  70. package/lib/map.d.mts.map +1 -0
  71. package/lib/{map.js → map.mjs} +40 -35
  72. package/lib/map.mjs.map +1 -0
  73. package/lib/{mapKernel.d.ts → mapKernel.d.mts} +18 -49
  74. package/lib/mapKernel.d.mts.map +1 -0
  75. package/lib/{mapKernel.js → mapKernel.mjs} +116 -73
  76. package/lib/mapKernel.mjs.map +1 -0
  77. package/lib/{packageVersion.d.ts → packageVersion.d.mts} +1 -1
  78. package/lib/{packageVersion.d.ts.map → packageVersion.d.mts.map} +1 -1
  79. package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
  80. package/lib/packageVersion.mjs.map +1 -0
  81. package/map.test-files.tar +0 -0
  82. package/package.json +105 -65
  83. package/prettier.config.cjs +8 -0
  84. package/src/directory.ts +2544 -1727
  85. package/src/index.ts +31 -5
  86. package/src/interfaces.ts +346 -345
  87. package/src/internalInterfaces.ts +119 -0
  88. package/src/localValues.ts +103 -96
  89. package/src/map.ts +362 -351
  90. package/src/mapKernel.ts +755 -722
  91. package/src/packageVersion.ts +1 -1
  92. package/tsc-multi.test.json +4 -0
  93. package/tsconfig.json +10 -15
  94. package/dist/directory.js.map +0 -1
  95. package/dist/index.js +0 -34
  96. package/dist/index.js.map +0 -1
  97. package/dist/interfaces.js.map +0 -1
  98. package/dist/localValues.js.map +0 -1
  99. package/dist/map.js.map +0 -1
  100. package/dist/mapKernel.js.map +0 -1
  101. package/dist/packageVersion.js.map +0 -1
  102. package/lib/directory.d.ts +0 -369
  103. package/lib/directory.d.ts.map +0 -1
  104. package/lib/directory.js.map +0 -1
  105. package/lib/index.d.ts +0 -20
  106. package/lib/index.d.ts.map +0 -1
  107. package/lib/index.js +0 -20
  108. package/lib/index.js.map +0 -1
  109. package/lib/interfaces.d.ts.map +0 -1
  110. package/lib/interfaces.js.map +0 -1
  111. package/lib/localValues.d.ts.map +0 -1
  112. package/lib/localValues.js.map +0 -1
  113. package/lib/map.d.ts.map +0 -1
  114. package/lib/map.js.map +0 -1
  115. package/lib/mapKernel.d.ts.map +0 -1
  116. package/lib/mapKernel.js.map +0 -1
  117. package/lib/packageVersion.js.map +0 -1
  118. package/tsconfig.esnext.json +0 -7
package/src/map.ts CHANGED
@@ -5,375 +5,386 @@
5
5
 
6
6
  import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
7
7
  import {
8
- IChannelAttributes,
9
- IFluidDataStoreRuntime,
10
- IChannelStorageService,
11
- IChannelServices,
12
- IChannelFactory,
8
+ IChannelAttributes,
9
+ IFluidDataStoreRuntime,
10
+ IChannelStorageService,
11
+ IChannelServices,
12
+ IChannelFactory,
13
13
  } from "@fluidframework/datastore-definitions";
14
14
  import { ISummaryTreeWithStats, ITelemetryContext } from "@fluidframework/runtime-definitions";
15
15
  import { readAndParse } from "@fluidframework/driver-utils";
16
- import {
17
- IFluidSerializer,
18
- SharedObject,
19
- } from "@fluidframework/shared-object-base";
16
+ import { IFluidSerializer, SharedObject } from "@fluidframework/shared-object-base";
20
17
  import { SummaryTreeBuilder } from "@fluidframework/runtime-utils";
21
- import {
22
- ISharedMap,
23
- ISharedMapEvents,
24
- } from "./interfaces";
25
- import { IMapDataObjectSerializable, MapKernel } from "./mapKernel";
18
+ import { ISharedMap, ISharedMapEvents } from "./interfaces";
19
+ import { IMapDataObjectSerializable, IMapOperation, MapKernel } from "./mapKernel";
26
20
  import { pkgVersion } from "./packageVersion";
27
21
 
28
22
  interface IMapSerializationFormat {
29
- blobs?: string[];
30
- content: IMapDataObjectSerializable;
23
+ blobs?: string[];
24
+ content: IMapDataObjectSerializable;
31
25
  }
32
26
 
33
27
  const snapshotFileName = "header";
34
28
 
35
29
  /**
36
- * The factory that defines the map.
30
+ * {@link @fluidframework/datastore-definitions#IChannelFactory} for {@link SharedMap}.
31
+ *
37
32
  * @sealed
33
+ * @alpha
38
34
  */
39
35
  export class MapFactory implements IChannelFactory {
40
- /**
41
- * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory."type"}
42
- */
43
- public static readonly Type = "https://graph.microsoft.com/types/map";
44
-
45
- /**
46
- * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.attributes}
47
- */
48
- public static readonly Attributes: IChannelAttributes = {
49
- type: MapFactory.Type,
50
- snapshotFormatVersion: "0.2",
51
- packageVersion: pkgVersion,
52
- };
53
-
54
- /**
55
- * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory."type"}
56
- */
57
- public get type() {
58
- return MapFactory.Type;
59
- }
60
-
61
- /**
62
- * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.attributes}
63
- */
64
- public get attributes() {
65
- return MapFactory.Attributes;
66
- }
67
-
68
- /**
69
- * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.load}
70
- */
71
- public async load(
72
- runtime: IFluidDataStoreRuntime,
73
- id: string,
74
- services: IChannelServices,
75
- attributes: IChannelAttributes): Promise<ISharedMap> {
76
- const map = new SharedMap(id, runtime, attributes);
77
- await map.load(services);
78
-
79
- return map;
80
- }
81
-
82
- /**
83
- * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.create}
84
- */
85
- public create(runtime: IFluidDataStoreRuntime, id: string): ISharedMap {
86
- const map = new SharedMap(id, runtime, MapFactory.Attributes);
87
- map.initializeLocal();
88
-
89
- return map;
90
- }
36
+ /**
37
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory."type"}
38
+ */
39
+ public static readonly Type = "https://graph.microsoft.com/types/map";
40
+
41
+ /**
42
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.attributes}
43
+ */
44
+ public static readonly Attributes: IChannelAttributes = {
45
+ type: MapFactory.Type,
46
+ snapshotFormatVersion: "0.2",
47
+ packageVersion: pkgVersion,
48
+ };
49
+
50
+ /**
51
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory."type"}
52
+ */
53
+ public get type(): string {
54
+ return MapFactory.Type;
55
+ }
56
+
57
+ /**
58
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.attributes}
59
+ */
60
+ public get attributes(): IChannelAttributes {
61
+ return MapFactory.Attributes;
62
+ }
63
+
64
+ /**
65
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.load}
66
+ */
67
+ public async load(
68
+ runtime: IFluidDataStoreRuntime,
69
+ id: string,
70
+ services: IChannelServices,
71
+ attributes: IChannelAttributes,
72
+ ): Promise<ISharedMap> {
73
+ const map = new SharedMap(id, runtime, attributes);
74
+ await map.load(services);
75
+
76
+ return map;
77
+ }
78
+
79
+ /**
80
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.create}
81
+ */
82
+ public create(runtime: IFluidDataStoreRuntime, id: string): ISharedMap {
83
+ const map = new SharedMap(id, runtime, MapFactory.Attributes);
84
+ map.initializeLocal();
85
+
86
+ return map;
87
+ }
91
88
  }
92
89
 
93
90
  /**
94
- * The SharedMap distributed data structure can be used to store key-value pairs. It provides the same API for setting
95
- * and retrieving values that JavaScript developers are accustomed to with the
96
- * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map | Map} built-in object.
97
- * However, the keys of a SharedMap must be strings.
91
+ * {@inheritDoc ISharedMap}
92
+ * @public
98
93
  */
99
94
  export class SharedMap extends SharedObject<ISharedMapEvents> implements ISharedMap {
100
- /**
101
- * Create a new shared map.
102
- * @param runtime - The data store runtime that the new shared map belongs to.
103
- * @param id - Optional name of the shared map.
104
- * @returns Newly created shared map.
105
- *
106
- * @example
107
- * To create a `SharedMap`, call the static create method:
108
- *
109
- * ```typescript
110
- * const myMap = SharedMap.create(this.runtime, id);
111
- * ```
112
- */
113
- public static create(runtime: IFluidDataStoreRuntime, id?: string): SharedMap {
114
- return runtime.createChannel(id, MapFactory.Type) as SharedMap;
115
- }
116
-
117
- /**
118
- * Get a factory for SharedMap to register with the data store.
119
- * @returns A factory that creates SharedMaps and loads them from storage.
120
- */
121
- public static getFactory(): IChannelFactory {
122
- return new MapFactory();
123
- }
124
-
125
- /**
126
- * String representation for the class.
127
- */
128
- public readonly [Symbol.toStringTag]: string = "SharedMap";
129
-
130
- /**
131
- * MapKernel which manages actual map operations.
132
- */
133
- private readonly kernel: MapKernel;
134
-
135
- /**
136
- * Do not call the constructor. Instead, you should use the {@link SharedMap.create | create method}.
137
- *
138
- * @param id - String identifier.
139
- * @param runtime - Data store runtime.
140
- * @param attributes - The attributes for the map.
141
- */
142
- constructor(
143
- id: string,
144
- runtime: IFluidDataStoreRuntime,
145
- attributes: IChannelAttributes,
146
- ) {
147
- super(id, runtime, attributes, "fluid_map_");
148
- this.kernel = new MapKernel(
149
- this.serializer,
150
- this.handle,
151
- (op, localOpMetadata) => this.submitLocalMessage(op, localOpMetadata),
152
- () => this.isAttached(),
153
- this,
154
- );
155
- }
156
-
157
- /**
158
- * Get an iterator over the keys in this map.
159
- * @returns The iterator
160
- */
161
- public keys(): IterableIterator<string> {
162
- return this.kernel.keys();
163
- }
164
-
165
- /**
166
- * Get an iterator over the entries in this map.
167
- * @returns The iterator
168
- */
169
- public entries(): IterableIterator<[string, any]> {
170
- return this.kernel.entries();
171
- }
172
-
173
- /**
174
- * Get an iterator over the values in this map.
175
- * @returns The iterator
176
- */
177
- public values(): IterableIterator<any> {
178
- return this.kernel.values();
179
- }
180
-
181
- /**
182
- * Get an iterator over the entries in this map.
183
- * @returns The iterator
184
- */
185
- public [Symbol.iterator](): IterableIterator<[string, any]> {
186
- return this.kernel.entries();
187
- }
188
-
189
- /**
190
- * The number of key/value pairs stored in the map.
191
- */
192
- public get size() {
193
- return this.kernel.size;
194
- }
195
-
196
- /**
197
- * Executes the given callback on each entry in the map.
198
- * @param callbackFn - Callback function
199
- */
200
- public forEach(callbackFn: (value: any, key: string, map: Map<string, any>) => void): void {
201
- this.kernel.forEach(callbackFn);
202
- }
203
-
204
- /**
205
- * {@inheritDoc ISharedMap.get}
206
- */
207
- public get<T = any>(key: string): T | undefined {
208
- return this.kernel.get<T>(key);
209
- }
210
-
211
- /**
212
- * Check if a key exists in the map.
213
- * @param key - The key to check
214
- * @returns True if the key exists, false otherwise
215
- */
216
- public has(key: string): boolean {
217
- return this.kernel.has(key);
218
- }
219
-
220
- /**
221
- * {@inheritDoc ISharedMap.set}
222
- */
223
- public set(key: string, value: any): this {
224
- this.kernel.set(key, value);
225
- return this;
226
- }
227
-
228
- /**
229
- * Delete a key from the map.
230
- * @param key - Key to delete
231
- * @returns True if the key existed and was deleted, false if it did not exist
232
- */
233
- public delete(key: string): boolean {
234
- return this.kernel.delete(key);
235
- }
236
-
237
- /**
238
- * Clear all data from the map.
239
- */
240
- public clear(): void {
241
- this.kernel.clear();
242
- }
243
-
244
- /**
245
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.summarizeCore}
246
- * @internal
247
- */
248
- protected summarizeCore(
249
- serializer: IFluidSerializer,
250
- telemetryContext?: ITelemetryContext,
251
- ): ISummaryTreeWithStats {
252
- let currentSize = 0;
253
- let counter = 0;
254
- let headerBlob: IMapDataObjectSerializable = {};
255
- const blobs: string[] = [];
256
-
257
- const builder = new SummaryTreeBuilder();
258
-
259
- const data = this.kernel.getSerializedStorage(serializer);
260
-
261
- // If single property exceeds this size, it goes into its own blob
262
- const MinValueSizeSeparateSnapshotBlob = 8 * 1024;
263
-
264
- // Maximum blob size for multiple map properties
265
- // Should be bigger than MinValueSizeSeparateSnapshotBlob
266
- const MaxSnapshotBlobSize = 16 * 1024;
267
-
268
- // Partitioning algorithm:
269
- // 1) Split large (over MinValueSizeSeparateSnapshotBlob = 8K) properties into their own blobs.
270
- // Naming (across snapshots) of such blob does not have to be stable across snapshots,
271
- // As de-duping process (in driver) should not care about paths, only content.
272
- // 2) Split remaining properties into blobs of MaxSnapshotBlobSize (16K) size.
273
- // This process does not produce stable partitioning. This means
274
- // modification (including addition / deletion) of property can shift properties across blobs
275
- // and result in non-incremental snapshot.
276
- // This can be improved in the future, without being format breaking change, as loading sequence
277
- // loads all blobs at once and partitioning schema has no impact on that process.
278
- for (const key of Object.keys(data)) {
279
- const value = data[key];
280
- if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
281
- const blobName = `blob${counter}`;
282
- counter++;
283
- blobs.push(blobName);
284
- const content: IMapDataObjectSerializable = {
285
- [key]: {
286
- type: value.type,
287
- value: JSON.parse(value.value),
288
- },
289
- };
290
- builder.addBlob(blobName, JSON.stringify(content));
291
- } else {
292
- currentSize += value.type.length + 21; // Approximation cost of property header
293
- if (value.value) {
294
- currentSize += value.value.length;
295
- }
296
-
297
- if (currentSize > MaxSnapshotBlobSize) {
298
- const blobName = `blob${counter}`;
299
- counter++;
300
- blobs.push(blobName);
301
- builder.addBlob(blobName, JSON.stringify(headerBlob));
302
- headerBlob = {};
303
- currentSize = 0;
304
- }
305
- headerBlob[key] = {
306
- type: value.type,
307
- value: value.value === undefined ? undefined : JSON.parse(value.value),
308
- };
309
- }
310
- }
311
-
312
- const header: IMapSerializationFormat = {
313
- blobs,
314
- content: headerBlob,
315
- };
316
- builder.addBlob(snapshotFileName, JSON.stringify(header));
317
-
318
- return builder.getSummaryTree();
319
- }
320
-
321
- /**
322
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
323
- * @internal
324
- */
325
- protected async loadCore(storage: IChannelStorageService) {
326
- const json = await readAndParse<object>(storage, snapshotFileName);
327
- const newFormat = json as IMapSerializationFormat;
328
- if (Array.isArray(newFormat.blobs)) {
329
- this.kernel.populateFromSerializable(newFormat.content);
330
- await Promise.all(newFormat.blobs.map(async (value) => {
331
- const content = await readAndParse<IMapDataObjectSerializable>(storage, value);
332
- this.kernel.populateFromSerializable(content);
333
- }));
334
- } else {
335
- this.kernel.populateFromSerializable(json as IMapDataObjectSerializable);
336
- }
337
- }
338
-
339
- /**
340
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.onDisconnect}
341
- * @internal
342
- */
343
- protected onDisconnect() {}
344
-
345
- /**
346
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.reSubmitCore}
347
- * @internal
348
- */
349
- protected reSubmitCore(content: any, localOpMetadata: unknown) {
350
- this.kernel.trySubmitMessage(content, localOpMetadata);
351
- }
352
-
353
- /**
354
- * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
355
- * @internal
356
- */
357
- protected applyStashedOp(content: any): unknown {
358
- this.kernel.tryProcessMessage(content, false, undefined);
359
- return this.kernel.tryGetStashedOpLocalMetadata(content);
360
- }
361
-
362
- /**
363
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.processCore}
364
- * @internal
365
- */
366
- protected processCore(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown) {
367
- if (message.type === MessageType.Operation) {
368
- this.kernel.tryProcessMessage(message.contents, local, localOpMetadata);
369
- }
370
- }
371
-
372
- /**
373
- * {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
374
- * @internal
375
- */
376
- protected rollback(content: any, localOpMetadata: unknown) {
377
- this.kernel.rollback(content, localOpMetadata);
378
- }
95
+ /**
96
+ * Create a new shared map.
97
+ * @param runtime - The data store runtime that the new shared map belongs to.
98
+ * @param id - Optional name of the shared map.
99
+ * @returns Newly created shared map.
100
+ *
101
+ * @example
102
+ * To create a `SharedMap`, call the static create method:
103
+ *
104
+ * ```typescript
105
+ * const myMap = SharedMap.create(this.runtime, id);
106
+ * ```
107
+ */
108
+ public static create(runtime: IFluidDataStoreRuntime, id?: string): SharedMap {
109
+ return runtime.createChannel(id, MapFactory.Type) as SharedMap;
110
+ }
111
+
112
+ /**
113
+ * Get a factory for SharedMap to register with the data store.
114
+ * @returns A factory that creates SharedMaps and loads them from storage.
115
+ */
116
+ public static getFactory(): IChannelFactory {
117
+ return new MapFactory();
118
+ }
119
+
120
+ /**
121
+ * String representation for the class.
122
+ */
123
+ public readonly [Symbol.toStringTag]: string = "SharedMap";
124
+
125
+ /**
126
+ * MapKernel which manages actual map operations.
127
+ */
128
+ private readonly kernel: MapKernel;
129
+
130
+ /**
131
+ * Do not call the constructor. Instead, you should use the {@link SharedMap.create | create method}.
132
+ *
133
+ * @param id - String identifier.
134
+ * @param runtime - Data store runtime.
135
+ * @param attributes - The attributes for the map.
136
+ */
137
+ public constructor(
138
+ id: string,
139
+ runtime: IFluidDataStoreRuntime,
140
+ attributes: IChannelAttributes,
141
+ ) {
142
+ super(id, runtime, attributes, "fluid_map_");
143
+ this.kernel = new MapKernel(
144
+ this.serializer,
145
+ this.handle,
146
+ (op, localOpMetadata) => this.submitLocalMessage(op, localOpMetadata),
147
+ () => this.isAttached(),
148
+ this,
149
+ );
150
+ }
151
+
152
+ /**
153
+ * Get an iterator over the keys in this map.
154
+ * @returns The iterator
155
+ */
156
+ public keys(): IterableIterator<string> {
157
+ return this.kernel.keys();
158
+ }
159
+
160
+ /**
161
+ * Get an iterator over the entries in this map.
162
+ * @returns The iterator
163
+ */
164
+ // TODO: Use `unknown` instead (breaking change).
165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
+ public entries(): IterableIterator<[string, any]> {
167
+ return this.kernel.entries();
168
+ }
169
+
170
+ /**
171
+ * Get an iterator over the values in this map.
172
+ * @returns The iterator
173
+ */
174
+ // TODO: Use `unknown` instead (breaking change).
175
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
176
+ public values(): IterableIterator<any> {
177
+ return this.kernel.values();
178
+ }
179
+
180
+ /**
181
+ * Get an iterator over the entries in this map.
182
+ * @returns The iterator
183
+ */
184
+ // TODO: Use `unknown` instead (breaking change).
185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
+ public [Symbol.iterator](): IterableIterator<[string, any]> {
187
+ return this.kernel.entries();
188
+ }
189
+
190
+ /**
191
+ * The number of key/value pairs stored in the map.
192
+ */
193
+ public get size(): number {
194
+ return this.kernel.size;
195
+ }
196
+
197
+ /**
198
+ * Executes the given callback on each entry in the map.
199
+ * @param callbackFn - Callback function
200
+ */
201
+ // TODO: Use `unknown` instead (breaking change).
202
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
+ public forEach(callbackFn: (value: any, key: string, map: Map<string, any>) => void): void {
204
+ // eslint-disable-next-line unicorn/no-array-for-each, unicorn/no-array-callback-reference
205
+ this.kernel.forEach(callbackFn);
206
+ }
207
+
208
+ /**
209
+ * {@inheritDoc ISharedMap.get}
210
+ */
211
+ // TODO: Use `unknown` instead (breaking change).
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ public get<T = any>(key: string): T | undefined {
214
+ return this.kernel.get<T>(key);
215
+ }
216
+
217
+ /**
218
+ * Check if a key exists in the map.
219
+ * @param key - The key to check
220
+ * @returns True if the key exists, false otherwise
221
+ */
222
+ public has(key: string): boolean {
223
+ return this.kernel.has(key);
224
+ }
225
+
226
+ /**
227
+ * {@inheritDoc ISharedMap.set}
228
+ */
229
+ public set(key: string, value: unknown): this {
230
+ this.kernel.set(key, value);
231
+ return this;
232
+ }
233
+
234
+ /**
235
+ * Delete a key from the map.
236
+ * @param key - Key to delete
237
+ * @returns True if the key existed and was deleted, false if it did not exist
238
+ */
239
+ public delete(key: string): boolean {
240
+ return this.kernel.delete(key);
241
+ }
242
+
243
+ /**
244
+ * Clear all data from the map.
245
+ */
246
+ public clear(): void {
247
+ this.kernel.clear();
248
+ }
249
+
250
+ /**
251
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.summarizeCore}
252
+ */
253
+ protected summarizeCore(
254
+ serializer: IFluidSerializer,
255
+ telemetryContext?: ITelemetryContext,
256
+ ): ISummaryTreeWithStats {
257
+ let currentSize = 0;
258
+ let counter = 0;
259
+ let headerBlob: IMapDataObjectSerializable = {};
260
+ const blobs: string[] = [];
261
+
262
+ const builder = new SummaryTreeBuilder();
263
+
264
+ const data = this.kernel.getSerializedStorage(serializer);
265
+
266
+ // If single property exceeds this size, it goes into its own blob
267
+ const MinValueSizeSeparateSnapshotBlob = 8 * 1024;
268
+
269
+ // Maximum blob size for multiple map properties
270
+ // Should be bigger than MinValueSizeSeparateSnapshotBlob
271
+ const MaxSnapshotBlobSize = 16 * 1024;
272
+
273
+ // Partitioning algorithm:
274
+ // 1) Split large (over MinValueSizeSeparateSnapshotBlob = 8K) properties into their own blobs.
275
+ // Naming (across snapshots) of such blob does not have to be stable across snapshots,
276
+ // As de-duping process (in driver) should not care about paths, only content.
277
+ // 2) Split remaining properties into blobs of MaxSnapshotBlobSize (16K) size.
278
+ // This process does not produce stable partitioning. This means
279
+ // modification (including addition / deletion) of property can shift properties across blobs
280
+ // and result in non-incremental snapshot.
281
+ // This can be improved in the future, without being format breaking change, as loading sequence
282
+ // loads all blobs at once and partitioning schema has no impact on that process.
283
+ for (const key of Object.keys(data)) {
284
+ const value = data[key];
285
+ if (value.value && value.value.length >= MinValueSizeSeparateSnapshotBlob) {
286
+ const blobName = `blob${counter}`;
287
+ counter++;
288
+ blobs.push(blobName);
289
+ const content: IMapDataObjectSerializable = {
290
+ [key]: {
291
+ type: value.type,
292
+ value: JSON.parse(value.value) as unknown,
293
+ },
294
+ };
295
+ builder.addBlob(blobName, JSON.stringify(content));
296
+ } else {
297
+ currentSize += value.type.length + 21; // Approximation cost of property header
298
+ if (value.value) {
299
+ currentSize += value.value.length;
300
+ }
301
+
302
+ if (currentSize > MaxSnapshotBlobSize) {
303
+ const blobName = `blob${counter}`;
304
+ counter++;
305
+ blobs.push(blobName);
306
+ builder.addBlob(blobName, JSON.stringify(headerBlob));
307
+ headerBlob = {};
308
+ currentSize = 0;
309
+ }
310
+ headerBlob[key] = {
311
+ type: value.type,
312
+ value:
313
+ value.value === undefined
314
+ ? undefined
315
+ : (JSON.parse(value.value) as unknown),
316
+ };
317
+ }
318
+ }
319
+
320
+ const header: IMapSerializationFormat = {
321
+ blobs,
322
+ content: headerBlob,
323
+ };
324
+ builder.addBlob(snapshotFileName, JSON.stringify(header));
325
+
326
+ return builder.getSummaryTree();
327
+ }
328
+
329
+ /**
330
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
331
+ */
332
+ protected async loadCore(storage: IChannelStorageService): Promise<void> {
333
+ const json = await readAndParse<object>(storage, snapshotFileName);
334
+ const newFormat = json as IMapSerializationFormat;
335
+ if (Array.isArray(newFormat.blobs)) {
336
+ this.kernel.populateFromSerializable(newFormat.content);
337
+ await Promise.all(
338
+ newFormat.blobs.map(async (value) => {
339
+ const content = await readAndParse<IMapDataObjectSerializable>(storage, value);
340
+ this.kernel.populateFromSerializable(content);
341
+ }),
342
+ );
343
+ } else {
344
+ this.kernel.populateFromSerializable(json as IMapDataObjectSerializable);
345
+ }
346
+ }
347
+
348
+ /**
349
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.onDisconnect}
350
+ */
351
+ protected onDisconnect(): void {}
352
+
353
+ /**
354
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.reSubmitCore}
355
+ */
356
+ protected reSubmitCore(content: unknown, localOpMetadata: unknown): void {
357
+ this.kernel.trySubmitMessage(content as IMapOperation, localOpMetadata);
358
+ }
359
+
360
+ /**
361
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
362
+ */
363
+ protected applyStashedOp(content: unknown): unknown {
364
+ return this.kernel.tryApplyStashedOp(content as IMapOperation);
365
+ }
366
+
367
+ /**
368
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.processCore}
369
+ */
370
+ protected processCore(
371
+ message: ISequencedDocumentMessage,
372
+ local: boolean,
373
+ localOpMetadata: unknown,
374
+ ): void {
375
+ if (message.type === MessageType.Operation) {
376
+ this.kernel.tryProcessMessage(
377
+ message.contents as IMapOperation,
378
+ local,
379
+ localOpMetadata,
380
+ );
381
+ }
382
+ }
383
+
384
+ /**
385
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.rollback}
386
+ */
387
+ protected rollback(content: unknown, localOpMetadata: unknown): void {
388
+ this.kernel.rollback(content, localOpMetadata);
389
+ }
379
390
  }