@fluidframework/datastore 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.225277

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 (146) hide show
  1. package/.eslintrc.js +5 -7
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +273 -0
  4. package/README.md +41 -0
  5. package/api-extractor-esm.json +4 -0
  6. package/api-extractor-lint.json +4 -0
  7. package/api-extractor.json +2 -2
  8. package/api-report/datastore.api.md +168 -0
  9. package/dist/channelContext.cjs +86 -0
  10. package/dist/channelContext.cjs.map +1 -0
  11. package/dist/channelContext.d.ts +15 -9
  12. package/dist/channelContext.d.ts.map +1 -1
  13. package/dist/{channelDeltaConnection.js → channelDeltaConnection.cjs} +14 -15
  14. package/dist/channelDeltaConnection.cjs.map +1 -0
  15. package/dist/channelDeltaConnection.d.ts +4 -5
  16. package/dist/channelDeltaConnection.d.ts.map +1 -1
  17. package/dist/{channelStorageService.js → channelStorageService.cjs} +13 -16
  18. package/dist/channelStorageService.cjs.map +1 -0
  19. package/dist/channelStorageService.d.ts +2 -2
  20. package/dist/channelStorageService.d.ts.map +1 -1
  21. package/dist/{dataStoreRuntime.js → dataStoreRuntime.cjs} +302 -225
  22. package/dist/dataStoreRuntime.cjs.map +1 -0
  23. package/dist/dataStoreRuntime.d.ts +81 -37
  24. package/dist/dataStoreRuntime.d.ts.map +1 -1
  25. package/dist/datastore-alpha.d.ts +317 -0
  26. package/dist/datastore-beta.d.ts +47 -0
  27. package/dist/datastore-public.d.ts +47 -0
  28. package/dist/datastore-untrimmed.d.ts +324 -0
  29. package/dist/{fluidHandle.js → fluidHandle.cjs} +44 -16
  30. package/dist/fluidHandle.cjs.map +1 -0
  31. package/dist/fluidHandle.d.ts +33 -6
  32. package/dist/fluidHandle.d.ts.map +1 -1
  33. package/dist/index.cjs +15 -0
  34. package/dist/index.cjs.map +1 -0
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/localChannelContext.cjs +190 -0
  38. package/dist/localChannelContext.cjs.map +1 -0
  39. package/dist/localChannelContext.d.ts +12 -21
  40. package/dist/localChannelContext.d.ts.map +1 -1
  41. package/dist/{localChannelStorageService.js → localChannelStorageService.cjs} +3 -3
  42. package/dist/localChannelStorageService.cjs.map +1 -0
  43. package/dist/localChannelStorageService.d.ts.map +1 -1
  44. package/dist/remoteChannelContext.cjs +124 -0
  45. package/dist/remoteChannelContext.cjs.map +1 -0
  46. package/dist/remoteChannelContext.d.ts +5 -10
  47. package/dist/remoteChannelContext.d.ts.map +1 -1
  48. package/dist/tsdoc-metadata.json +11 -0
  49. package/lib/{channelContext.d.ts → channelContext.d.mts} +17 -11
  50. package/lib/channelContext.d.mts.map +1 -0
  51. package/lib/channelContext.mjs +78 -0
  52. package/lib/channelContext.mjs.map +1 -0
  53. package/lib/{channelDeltaConnection.d.ts → channelDeltaConnection.d.mts} +5 -6
  54. package/lib/channelDeltaConnection.d.mts.map +1 -0
  55. package/lib/{channelDeltaConnection.js → channelDeltaConnection.mjs} +11 -12
  56. package/lib/channelDeltaConnection.mjs.map +1 -0
  57. package/lib/{channelStorageService.d.ts → channelStorageService.d.mts} +3 -3
  58. package/lib/channelStorageService.d.mts.map +1 -0
  59. package/lib/{channelStorageService.js → channelStorageService.mjs} +13 -16
  60. package/lib/channelStorageService.mjs.map +1 -0
  61. package/lib/{dataStoreRuntime.d.ts → dataStoreRuntime.d.mts} +82 -38
  62. package/lib/dataStoreRuntime.d.mts.map +1 -0
  63. package/lib/{dataStoreRuntime.js → dataStoreRuntime.mjs} +286 -209
  64. package/lib/dataStoreRuntime.mjs.map +1 -0
  65. package/lib/datastore-alpha.d.mts +317 -0
  66. package/lib/datastore-beta.d.mts +47 -0
  67. package/lib/datastore-public.d.mts +47 -0
  68. package/lib/datastore-untrimmed.d.mts +324 -0
  69. package/lib/fluidHandle.d.mts +57 -0
  70. package/lib/fluidHandle.d.mts.map +1 -0
  71. package/lib/{fluidHandle.js → fluidHandle.mjs} +44 -16
  72. package/lib/fluidHandle.mjs.map +1 -0
  73. package/lib/index.d.mts +7 -0
  74. package/lib/index.d.mts.map +1 -0
  75. package/lib/index.mjs +7 -0
  76. package/lib/index.mjs.map +1 -0
  77. package/lib/{localChannelContext.d.ts → localChannelContext.d.mts} +14 -23
  78. package/lib/localChannelContext.d.mts.map +1 -0
  79. package/lib/{localChannelContext.js → localChannelContext.mjs} +73 -85
  80. package/lib/localChannelContext.mjs.map +1 -0
  81. package/lib/{localChannelStorageService.d.ts → localChannelStorageService.d.mts} +1 -1
  82. package/lib/localChannelStorageService.d.mts.map +1 -0
  83. package/lib/{localChannelStorageService.js → localChannelStorageService.mjs} +2 -2
  84. package/lib/localChannelStorageService.mjs.map +1 -0
  85. package/lib/{remoteChannelContext.d.ts → remoteChannelContext.d.mts} +8 -13
  86. package/lib/remoteChannelContext.d.mts.map +1 -0
  87. package/lib/remoteChannelContext.mjs +120 -0
  88. package/lib/remoteChannelContext.mjs.map +1 -0
  89. package/package.json +139 -71
  90. package/{lib/index.js → prettier.config.cjs} +4 -3
  91. package/src/channelContext.ts +168 -71
  92. package/src/channelDeltaConnection.ts +52 -47
  93. package/src/channelStorageService.ts +59 -55
  94. package/src/dataStoreRuntime.ts +1158 -983
  95. package/src/fluidHandle.ts +92 -64
  96. package/src/index.ts +8 -2
  97. package/src/localChannelContext.ts +278 -272
  98. package/src/localChannelStorageService.ts +48 -46
  99. package/src/remoteChannelContext.ts +237 -300
  100. package/tsc-multi.test.json +4 -0
  101. package/tsconfig.json +11 -13
  102. package/dist/channelContext.js +0 -35
  103. package/dist/channelContext.js.map +0 -1
  104. package/dist/channelDeltaConnection.js.map +0 -1
  105. package/dist/channelStorageService.js.map +0 -1
  106. package/dist/dataStoreRuntime.js.map +0 -1
  107. package/dist/fluidHandle.js.map +0 -1
  108. package/dist/index.js +0 -19
  109. package/dist/index.js.map +0 -1
  110. package/dist/localChannelContext.js +0 -202
  111. package/dist/localChannelContext.js.map +0 -1
  112. package/dist/localChannelStorageService.js.map +0 -1
  113. package/dist/packageVersion.d.ts +0 -9
  114. package/dist/packageVersion.d.ts.map +0 -1
  115. package/dist/packageVersion.js +0 -12
  116. package/dist/packageVersion.js.map +0 -1
  117. package/dist/remoteChannelContext.js +0 -207
  118. package/dist/remoteChannelContext.js.map +0 -1
  119. package/lib/channelContext.d.ts.map +0 -1
  120. package/lib/channelContext.js +0 -29
  121. package/lib/channelContext.js.map +0 -1
  122. package/lib/channelDeltaConnection.d.ts.map +0 -1
  123. package/lib/channelDeltaConnection.js.map +0 -1
  124. package/lib/channelStorageService.d.ts.map +0 -1
  125. package/lib/channelStorageService.js.map +0 -1
  126. package/lib/dataStoreRuntime.d.ts.map +0 -1
  127. package/lib/dataStoreRuntime.js.map +0 -1
  128. package/lib/fluidHandle.d.ts +0 -30
  129. package/lib/fluidHandle.d.ts.map +0 -1
  130. package/lib/fluidHandle.js.map +0 -1
  131. package/lib/index.d.ts +0 -7
  132. package/lib/index.d.ts.map +0 -1
  133. package/lib/index.js.map +0 -1
  134. package/lib/localChannelContext.d.ts.map +0 -1
  135. package/lib/localChannelContext.js.map +0 -1
  136. package/lib/localChannelStorageService.d.ts.map +0 -1
  137. package/lib/localChannelStorageService.js.map +0 -1
  138. package/lib/packageVersion.d.ts +0 -9
  139. package/lib/packageVersion.d.ts.map +0 -1
  140. package/lib/packageVersion.js +0 -9
  141. package/lib/packageVersion.js.map +0 -1
  142. package/lib/remoteChannelContext.d.ts.map +0 -1
  143. package/lib/remoteChannelContext.js +0 -203
  144. package/lib/remoteChannelContext.js.map +0 -1
  145. package/src/packageVersion.ts +0 -9
  146. package/tsconfig.esnext.json +0 -7
@@ -2,19 +2,22 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { BindState, AttachState, } from "@fluidframework/container-definitions";
6
- import { DataProcessingError, UsageError } from "@fluidframework/container-utils";
7
- import { assert, Deferred, LazyPromise, TypedEventEmitter, unreachableCase, } from "@fluidframework/common-utils";
8
- import { ChildLogger, LoggingError, raiseConnectedEvent, } from "@fluidframework/telemetry-utils";
5
+ import { TypedEventEmitter } from "@fluid-internal/client-utils";
6
+ import { DataProcessingError, generateStack, LoggingError, raiseConnectedEvent, createChildMonitoringContext, tagCodeArtifacts, UsageError, } from "@fluidframework/telemetry-utils";
7
+ import { assert, Deferred, LazyPromise, unreachableCase } from "@fluidframework/core-utils";
8
+ import { AttachState, } from "@fluidframework/container-definitions";
9
9
  import { buildSnapshotTree } from "@fluidframework/driver-utils";
10
10
  import { SummaryType, } from "@fluidframework/protocol-definitions";
11
11
  import { CreateSummarizerNodeSource, VisibilityState, } from "@fluidframework/runtime-definitions";
12
- import { convertSnapshotTreeToSummaryTree, convertSummaryTreeToITree, generateHandleContextPath, RequestParser, SummaryTreeBuilder, create404Response, createResponseError, exceptionToResponse, } from "@fluidframework/runtime-utils";
13
- import { GCDataBuilder, removeRouteFromAllNodes, unpackChildNodesGCDetails, unpackChildNodesUsedRoutes, } from "@fluidframework/garbage-collector";
12
+ import { convertSnapshotTreeToSummaryTree, convertSummaryTreeToITree, generateHandleContextPath, RequestParser, SummaryTreeBuilder, create404Response, createResponseError, exceptionToResponse, GCDataBuilder, unpackChildNodesUsedRoutes, } from "@fluidframework/runtime-utils";
14
13
  import { v4 as uuid } from "uuid";
15
- import { summarizeChannel } from "./channelContext";
16
- import { LocalChannelContext, LocalChannelContextBase, RehydratedLocalChannelContext } from "./localChannelContext";
17
- import { RemoteChannelContext } from "./remoteChannelContext";
14
+ import { summarizeChannel } from "./channelContext.mjs";
15
+ import { LocalChannelContext, LocalChannelContextBase, RehydratedLocalChannelContext, } from "./localChannelContext.mjs";
16
+ import { RemoteChannelContext } from "./remoteChannelContext.mjs";
17
+ import { FluidObjectHandle } from "./fluidHandle.mjs";
18
+ /**
19
+ * @alpha
20
+ */
18
21
  export var DataStoreMessageType;
19
22
  (function (DataStoreMessageType) {
20
23
  // Creates a new channel
@@ -23,16 +26,83 @@ export var DataStoreMessageType;
23
26
  })(DataStoreMessageType || (DataStoreMessageType = {}));
24
27
  /**
25
28
  * Base data store class
29
+ * @alpha
26
30
  */
27
31
  export class FluidDataStoreRuntime extends TypedEventEmitter {
28
- constructor(dataStoreContext, sharedObjectRegistry, existing) {
32
+ get connected() {
33
+ return this.dataStoreContext.connected;
34
+ }
35
+ get clientId() {
36
+ return this.dataStoreContext.clientId;
37
+ }
38
+ get clientDetails() {
39
+ return this.dataStoreContext.clientDetails;
40
+ }
41
+ get isAttached() {
42
+ return this.attachState !== AttachState.Detached;
43
+ }
44
+ get attachState() {
45
+ return this._attachState;
46
+ }
47
+ get absolutePath() {
48
+ return generateHandleContextPath(this.id, this.routeContext);
49
+ }
50
+ get routeContext() {
51
+ return this.dataStoreContext.IFluidHandleContext;
52
+ }
53
+ get idCompressor() {
54
+ return this.dataStoreContext.idCompressor;
55
+ }
56
+ get IFluidHandleContext() {
57
+ return this;
58
+ }
59
+ get rootRoutingContext() {
60
+ return this;
61
+ }
62
+ get channelsRoutingContext() {
63
+ return this;
64
+ }
65
+ get objectsRoutingContext() {
66
+ return this;
67
+ }
68
+ get disposed() {
69
+ return this._disposed;
70
+ }
71
+ get logger() {
72
+ return this.mc.logger;
73
+ }
74
+ /**
75
+ * Invokes the given callback and expects that no ops are submitted
76
+ * until execution finishes. If an op is submitted, an error will be raised.
77
+ *
78
+ * Can be disabled by feature gate `Fluid.ContainerRuntime.DisableOpReentryCheck`
79
+ *
80
+ * @param callback - the callback to be invoked
81
+ */
82
+ ensureNoDataModelChanges(callback) {
83
+ // back-compat ADO:2309
84
+ return this.dataStoreContext.ensureNoDataModelChanges === undefined
85
+ ? callback()
86
+ : this.dataStoreContext.ensureNoDataModelChanges(callback);
87
+ }
88
+ /**
89
+ * Create an instance of a DataStore runtime.
90
+ *
91
+ * @param dataStoreContext - Context object for the runtime.
92
+ * @param sharedObjectRegistry - The registry of shared objects that this data store will be able to instantiate.
93
+ * @param existing - Pass 'true' if loading this datastore from an existing file; pass 'false' otherwise.
94
+ * @param provideEntryPoint - Function to initialize the entryPoint object for the data store runtime. The
95
+ * handle to this data store runtime will point to the object returned by this function. If this function is not
96
+ * provided, the handle will be left undefined. This is here so we can start making handles a first-class citizen
97
+ * and the primary way of interacting with some Fluid objects, and should be used if possible.
98
+ */
99
+ constructor(dataStoreContext, sharedObjectRegistry, existing, provideEntryPoint) {
29
100
  super();
30
101
  this.dataStoreContext = dataStoreContext;
31
102
  this.sharedObjectRegistry = sharedObjectRegistry;
32
103
  this._disposed = false;
33
104
  this.contexts = new Map();
34
- this.contextsDeferred = new Map();
35
- this.pendingAttach = new Map();
105
+ this.pendingAttach = new Set();
36
106
  this.deferredAttached = new Deferred();
37
107
  this.localChannelContextQueue = new Map();
38
108
  this.notBoundedChannelContextSet = new Set();
@@ -40,20 +110,21 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
40
110
  // store becomes visible.
41
111
  this.pendingHandlesToMakeVisible = new Set();
42
112
  assert(!dataStoreContext.id.includes("/"), 0x30e /* Id cannot contain slashes. DataStoreContext should have validated this. */);
43
- this.logger = ChildLogger.create(dataStoreContext.logger, "FluidDataStoreRuntime", { all: { dataStoreId: uuid() } });
113
+ this.mc = createChildMonitoringContext({
114
+ logger: dataStoreContext.logger,
115
+ namespace: "FluidDataStoreRuntime",
116
+ properties: {
117
+ all: { dataStoreId: uuid() },
118
+ },
119
+ });
44
120
  this.id = dataStoreContext.id;
45
121
  this.options = dataStoreContext.options;
46
122
  this.deltaManager = dataStoreContext.deltaManager;
47
123
  this.quorum = dataStoreContext.getQuorum();
48
124
  this.audience = dataStoreContext.getAudience();
49
125
  const tree = dataStoreContext.baseSnapshot;
50
- this.channelsBaseGCDetails = new LazyPromise(async () => {
51
- var _a, _b, _c;
52
- const baseGCDetails = await ((_c = (_b = (_a = this.dataStoreContext).getBaseGCDetails) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : this.dataStoreContext.getInitialGCSummaryDetails());
53
- return unpackChildNodesGCDetails(baseGCDetails);
54
- });
55
126
  // Must always receive the data store type inside of the attributes
56
- if ((tree === null || tree === void 0 ? void 0 : tree.trees) !== undefined) {
127
+ if (tree?.trees !== undefined) {
57
128
  Object.keys(tree.trees).forEach((path) => {
58
129
  // Issue #4414
59
130
  if (path === "_search") {
@@ -77,29 +148,31 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
77
148
  }
78
149
  }
79
150
  else {
80
- channelContext = new RemoteChannelContext(this, dataStoreContext, dataStoreContext.storage, (content, localOpMetadata) => this.submitChannelOp(path, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), path, tree.trees[path], this.sharedObjectRegistry, undefined /* extraBlobs */, this.dataStoreContext.getCreateChildSummarizerNodeFn(path, { type: CreateSummarizerNodeSource.FromSummary }), async () => this.getChannelBaseGCDetails(path));
151
+ channelContext = new RemoteChannelContext(this, dataStoreContext, dataStoreContext.storage, (content, localOpMetadata) => this.submitChannelOp(path, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), path, tree.trees[path], this.sharedObjectRegistry, undefined /* extraBlobs */, this.dataStoreContext.getCreateChildSummarizerNodeFn(path, {
152
+ type: CreateSummarizerNodeSource.FromSummary,
153
+ }));
81
154
  }
82
- const deferred = new Deferred();
83
- deferred.resolve(channelContext);
84
155
  this.contexts.set(path, channelContext);
85
- this.contextsDeferred.set(path, deferred);
86
156
  });
87
157
  }
158
+ this.entryPoint = new FluidObjectHandle(new LazyPromise(async () => provideEntryPoint(this)), "", this.objectsRoutingContext);
88
159
  this.attachListener();
89
- // If exists on storage or loaded from a snapshot, it should already be bound.
90
- this.bindState = existing ? BindState.Bound : BindState.NotBound;
91
160
  this._attachState = dataStoreContext.attachState;
92
161
  /**
93
162
  * If existing flag is false, this is a new data store and is not visible. The existing flag can be true in two
94
163
  * conditions:
164
+ *
95
165
  * 1. It's a local data store that is created when a detached container is rehydrated. In this case, the data
96
- * store is locally visible because the snapshot it is loaded from contains locally visible data stores only.
166
+ * store is locally visible because the snapshot it is loaded from contains locally visible data stores only.
167
+ *
97
168
  * 2. It's a remote data store that is created when an attached container is loaded is loaded from snapshot or
98
- * when an attach op comes in. In both these cases, the data store is already globally visible.
169
+ * when an attach op comes in. In both these cases, the data store is already globally visible.
99
170
  */
100
171
  if (existing) {
101
- this.visibilityState = dataStoreContext.attachState === AttachState.Detached
102
- ? VisibilityState.LocallyVisible : VisibilityState.GloballyVisible;
172
+ this.visibilityState =
173
+ dataStoreContext.attachState === AttachState.Detached
174
+ ? VisibilityState.LocallyVisible
175
+ : VisibilityState.GloballyVisible;
103
176
  }
104
177
  else {
105
178
  this.visibilityState = VisibilityState.NotVisible;
@@ -108,43 +181,10 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
108
181
  if (existing) {
109
182
  this.deferredAttached.resolve();
110
183
  }
184
+ // By default, a data store can log maximum 10 local changes telemetry in summarizer.
185
+ this.localChangesTelemetryCount =
186
+ this.mc.config.getNumber("Fluid.Telemetry.LocalChangesTelemetryCount") ?? 10;
111
187
  }
112
- /**
113
- * Loads the data store runtime
114
- * @param context - The data store context
115
- * @param sharedObjectRegistry - The registry of shared objects used by this data store
116
- * @param existing - If loading from an existing file.
117
- */
118
- static load(context, sharedObjectRegistry, existing) {
119
- return new FluidDataStoreRuntime(context, sharedObjectRegistry, existing);
120
- }
121
- get IFluidRouter() { return this; }
122
- get connected() {
123
- return this.dataStoreContext.connected;
124
- }
125
- get clientId() {
126
- return this.dataStoreContext.clientId;
127
- }
128
- get clientDetails() {
129
- return this.dataStoreContext.clientDetails;
130
- }
131
- get isAttached() {
132
- return this.attachState !== AttachState.Detached;
133
- }
134
- get attachState() {
135
- return this._attachState;
136
- }
137
- get absolutePath() {
138
- return generateHandleContextPath(this.id, this.routeContext);
139
- }
140
- get routeContext() {
141
- return this.dataStoreContext.IFluidHandleContext;
142
- }
143
- get IFluidHandleContext() { return this; }
144
- get rootRoutingContext() { return this; }
145
- get channelsRoutingContext() { return this; }
146
- get objectsRoutingContext() { return this; }
147
- get disposed() { return this._disposed; }
148
188
  dispose() {
149
189
  if (this._disposed) {
150
190
  return;
@@ -161,18 +201,17 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
161
201
  const parser = RequestParser.create(request);
162
202
  const id = parser.pathParts[0];
163
203
  if (id === "_channels" || id === "_custom") {
164
- return this.request(parser.createSubRequest(1));
204
+ return await this.request(parser.createSubRequest(1));
165
205
  }
166
206
  // Check for a data type reference first
167
- if (this.contextsDeferred.has(id) && parser.isLeaf(1)) {
207
+ const context = this.contexts.get(id);
208
+ if (context !== undefined && parser.isLeaf(1)) {
168
209
  try {
169
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170
- const value = await this.contextsDeferred.get(id).promise;
171
- const channel = await value.getChannel();
210
+ const channel = await context.getChannel();
172
211
  return { mimeType: "fluid/object", status: 200, value: channel };
173
212
  }
174
213
  catch (error) {
175
- this.logger.sendErrorEvent({ eventName: "GetChannelFailedInRequest" }, error);
214
+ this.mc.logger.sendErrorEvent({ eventName: "GetChannelFailedInRequest" }, error);
176
215
  return createResponseError(500, `Failed to get Channel: ${error}`, request);
177
216
  }
178
217
  }
@@ -185,37 +224,56 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
185
224
  }
186
225
  async getChannel(id) {
187
226
  this.verifyNotClosed();
188
- // TODO we don't assume any channels (even root) in the runtime. If you request a channel that doesn't exist
189
- // we will never resolve the promise. May want a flag to getChannel that doesn't wait for the promise if
190
- // it doesn't exist
191
- if (!this.contextsDeferred.has(id)) {
192
- this.contextsDeferred.set(id, new Deferred());
193
- }
194
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
195
- const context = await this.contextsDeferred.get(id).promise;
196
- const channel = await context.getChannel();
197
- return channel;
227
+ const context = this.contexts.get(id);
228
+ if (context === undefined) {
229
+ throw new LoggingError("Channel does not exist");
230
+ }
231
+ return context.getChannel();
198
232
  }
199
- createChannel(id = uuid(), type) {
233
+ /**
234
+ * Api which allows caller to create the channel first and then add it to the runtime.
235
+ * The channel type should be present in the registry, otherwise the runtime would reject
236
+ * the channel. Also the runtime used to create the channel object should be same to which
237
+ * it is added.
238
+ * @param channel - channel which needs to be added to the runtime.
239
+ */
240
+ addChannel(channel) {
241
+ const id = channel.id;
200
242
  if (id.includes("/")) {
201
243
  throw new UsageError(`Id cannot contain slashes: ${id}`);
202
244
  }
203
245
  this.verifyNotClosed();
204
- assert(!this.contexts.has(id), 0x179 /* "createChannel() with existing ID" */);
205
- this.notBoundedChannelContextSet.add(id);
206
- const context = new LocalChannelContext(id, this.sharedObjectRegistry, type, this, this.dataStoreContext, this.dataStoreContext.storage, this.logger, (content, localOpMetadata) => this.submitChannelOp(id, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle));
207
- this.contexts.set(id, context);
208
- if (this.contextsDeferred.has(id)) {
209
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
210
- this.contextsDeferred.get(id).resolve(context);
246
+ assert(!this.contexts.has(id), 0x865 /* addChannel() with existing ID */);
247
+ const type = channel.attributes.type;
248
+ const factory = this.sharedObjectRegistry.get(channel.attributes.type);
249
+ if (factory === undefined) {
250
+ throw new Error(`Channel Factory ${type} not registered`);
211
251
  }
212
- else {
213
- const deferred = new Deferred();
214
- deferred.resolve(context);
215
- this.contextsDeferred.set(id, deferred);
252
+ this.createChannelContext(channel);
253
+ // Channels (DDS) should not be created in summarizer client.
254
+ this.identifyLocalChangeInSummarizer("DDSCreatedInSummarizer", id, type);
255
+ }
256
+ createChannel(id = uuid(), type) {
257
+ if (id.includes("/")) {
258
+ throw new UsageError(`Id cannot contain slashes: ${id}`);
216
259
  }
217
- assert(!!context.channel, 0x17a /* "Channel should be loaded when created!!" */);
218
- return context.channel;
260
+ this.verifyNotClosed();
261
+ assert(!this.contexts.has(id), 0x179 /* "createChannel() with existing ID" */);
262
+ assert(type !== undefined, 0x209 /* "Factory Type should be defined" */);
263
+ const factory = this.sharedObjectRegistry.get(type);
264
+ if (factory === undefined) {
265
+ throw new Error(`Channel Factory ${type} not registered`);
266
+ }
267
+ const channel = factory.create(this, id);
268
+ this.createChannelContext(channel);
269
+ // Channels (DDS) should not be created in summarizer client.
270
+ this.identifyLocalChangeInSummarizer("DDSCreatedInSummarizer", id, type);
271
+ return channel;
272
+ }
273
+ createChannelContext(channel) {
274
+ this.notBoundedChannelContextSet.add(channel.id);
275
+ const context = new LocalChannelContext(channel, this, this.dataStoreContext, this.dataStoreContext.storage, this.logger, (content, localOpMetadata) => this.submitChannelOp(channel.id, content, localOpMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle));
276
+ this.contexts.set(channel.id, context);
219
277
  }
220
278
  /**
221
279
  * Binds a channel with the runtime. If the runtime is attached we will attach the channel right away.
@@ -223,7 +281,7 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
223
281
  * @param channel - channel to be registered.
224
282
  */
225
283
  bindChannel(channel) {
226
- assert(this.notBoundedChannelContextSet.has(channel.id), 0x17b /* "Channel to be binded should be in not bounded set" */);
284
+ assert(this.notBoundedChannelContextSet.has(channel.id), 0x17b /* "Channel to be bound should be in not bounded set" */);
227
285
  this.notBoundedChannelContextSet.delete(channel.id);
228
286
  // If our data store is attached, then attach the channel.
229
287
  if (this.isAttached) {
@@ -245,11 +303,14 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
245
303
  }
246
304
  /**
247
305
  * This function is called when a data store becomes root. It does the following:
306
+ *
248
307
  * 1. Marks the data store locally visible in the container.
308
+ *
249
309
  * 2. Attaches the graph of all the handles bound to it.
310
+ *
250
311
  * 3. Calls into the data store context to mark it visible in the container too. If the container is globally
251
- * visible, it will mark us globally visible. Otherwise, it will mark us globally visible when it becomes
252
- * globally visible.
312
+ * visible, it will mark us globally visible. Otherwise, it will mark us globally visible when it becomes
313
+ * globally visible.
253
314
  */
254
315
  makeVisibleAndAttachGraph() {
255
316
  if (this.visibilityState !== VisibilityState.NotVisible) {
@@ -260,7 +321,7 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
260
321
  handle.attachGraph();
261
322
  });
262
323
  this.pendingHandlesToMakeVisible.clear();
263
- this.bindToContext();
324
+ this.dataStoreContext.makeLocallyVisible();
264
325
  }
265
326
  /**
266
327
  * This function is called when a handle to this data store is added to a visible DDS.
@@ -268,20 +329,6 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
268
329
  attachGraph() {
269
330
  this.makeVisibleAndAttachGraph();
270
331
  }
271
- /**
272
- * Binds this runtime to the container
273
- * This includes the following:
274
- * 1. Sending an Attach op that includes all existing state
275
- * 2. Attaching the graph if the data store becomes attached.
276
- */
277
- bindToContext() {
278
- if (this.bindState !== BindState.NotBound) {
279
- return;
280
- }
281
- this.bindState = BindState.Binding;
282
- this.dataStoreContext.bindToContext();
283
- this.bindState = BindState.Bound;
284
- }
285
332
  bind(handle) {
286
333
  // If visible, attach the incoming handle's graph. Else, this will be done when we become visible.
287
334
  if (this.visibilityState !== VisibilityState.NotVisible) {
@@ -303,9 +350,14 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
303
350
  getAudience() {
304
351
  return this.audience;
305
352
  }
306
- async uploadBlob(blob) {
353
+ async uploadBlob(blob, signal) {
307
354
  this.verifyNotClosed();
308
- return this.dataStoreContext.uploadBlob(blob);
355
+ return this.dataStoreContext.uploadBlob(blob, signal);
356
+ }
357
+ createRemoteChannelContext(attachMessage, summarizerNodeParams) {
358
+ const flatBlobs = new Map();
359
+ const snapshotTree = buildSnapshotTree(attachMessage.snapshot.entries, flatBlobs);
360
+ return new RemoteChannelContext(this, this.dataStoreContext, this.dataStoreContext.storage, (content, localContentMetadata) => this.submitChannelOp(attachMessage.id, content, localContentMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), attachMessage.id, snapshotTree, this.sharedObjectRegistry, flatBlobs, this.dataStoreContext.getCreateChildSummarizerNodeFn(attachMessage.id, summarizerNodeParams), attachMessage.type);
309
361
  }
310
362
  process(message, local, localOpMetadata) {
311
363
  this.verifyNotClosed();
@@ -318,28 +370,17 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
318
370
  // If a non-local operation then go and create the object
319
371
  // Otherwise mark it as officially attached.
320
372
  if (local) {
321
- assert(this.pendingAttach.has(id), 0x17c /* "Unexpected attach (local) channel OP" */);
322
- this.pendingAttach.delete(id);
373
+ assert(this.pendingAttach.delete(id), 0x17c /* "Unexpected attach (local) channel OP" */);
323
374
  }
324
375
  else {
325
- assert(!this.contexts.has(id), 0x17d);
326
- const flatBlobs = new Map();
327
- const snapshotTree = buildSnapshotTree(attachMessage.snapshot.entries, flatBlobs);
328
- const remoteChannelContext = new RemoteChannelContext(this, this.dataStoreContext, this.dataStoreContext.storage, (content, localContentMetadata) => this.submitChannelOp(id, content, localContentMetadata), (address) => this.setChannelDirty(address), (srcHandle, outboundHandle) => this.addedGCOutboundReference(srcHandle, outboundHandle), id, snapshotTree, this.sharedObjectRegistry, flatBlobs, this.dataStoreContext.getCreateChildSummarizerNodeFn(id, {
376
+ assert(!this.contexts.has(id), 0x17d /* "Unexpected attach channel OP" */);
377
+ const summarizerNodeParams = {
329
378
  type: CreateSummarizerNodeSource.FromAttach,
330
379
  sequenceNumber: message.sequenceNumber,
331
380
  snapshot: attachMessage.snapshot,
332
- }), async () => this.getChannelBaseGCDetails(id), attachMessage.type);
381
+ };
382
+ const remoteChannelContext = this.createRemoteChannelContext(attachMessage, summarizerNodeParams);
333
383
  this.contexts.set(id, remoteChannelContext);
334
- if (this.contextsDeferred.has(id)) {
335
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
336
- this.contextsDeferred.get(id).resolve(remoteChannelContext);
337
- }
338
- else {
339
- const deferred = new Deferred();
340
- deferred.resolve(remoteChannelContext);
341
- this.contextsDeferred.set(id, deferred);
342
- }
343
384
  }
344
385
  break;
345
386
  }
@@ -361,13 +402,13 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
361
402
  return (
362
403
  // Added in createChannel
363
404
  // Removed when bindChannel is called
364
- !this.notBoundedChannelContextSet.has(id)
405
+ !this.notBoundedChannelContextSet.has(id) &&
365
406
  // Added in bindChannel only if this is not attached yet
366
407
  // Removed when this is attached by calling attachGraph
367
- && !this.localChannelContextQueue.has(id)
408
+ !this.localChannelContextQueue.has(id) &&
368
409
  // Added in attachChannel called by bindChannel
369
410
  // Removed when attach op is broadcast
370
- && !this.pendingAttach.has(id));
411
+ !this.pendingAttach.has(id));
371
412
  }
372
413
  /**
373
414
  * Returns the outbound routes of this channel. Currently, all contexts in this channel are considered
@@ -398,11 +439,15 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
398
439
  * Generates data used for garbage collection. This includes a list of GC nodes that represent this channel
399
440
  * including any of its child channel contexts. Each node has a set of outbound routes to other GC nodes in the
400
441
  * document. It does the following:
442
+ *
401
443
  * 1. Calls into each child context to get its GC data.
444
+ *
402
445
  * 2. Prefixes the child context's id to the GC nodes in the child's GC data. This makes sure that the node can be
403
- * identified as belonging to the child.
446
+ * identified as belonging to the child.
447
+ *
404
448
  * 3. Adds a GC node for this channel to the nodes received from the children. All these nodes together represent
405
- * the GC data of this channel.
449
+ * the GC data of this channel.
450
+ *
406
451
  * @param fullGC - true to bypass optimizations and force full generation of GC data.
407
452
  */
408
453
  async getGCData(fullGC = false) {
@@ -413,7 +458,8 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
413
458
  // Get GC data only for attached contexts. Detached contexts are not connected in the GC reference
414
459
  // graph so any references they might have won't be connected as well.
415
460
  return this.isChannelAttached(contextId);
416
- }).map(async ([contextId, context]) => {
461
+ })
462
+ .map(async ([contextId, context]) => {
417
463
  const contextGCData = await context.getGCData(fullGC);
418
464
  // Prefix the child's id to the ids of its GC nodes so they can be identified as belonging to the child.
419
465
  // This also gradually builds the id of each node to be a path from the root.
@@ -426,11 +472,8 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
426
472
  * After GC has run, called to notify this channel of routes that are used in it. It calls the child contexts to
427
473
  * update their used routes.
428
474
  * @param usedRoutes - The routes that are used in all contexts in this channel.
429
- * @param gcTimestamp - The time when GC was run that generated these used routes. If any node becomes unreferenced
430
- * as part of this GC run, this should be used to update the time when it happens.
431
475
  */
432
- updateUsedRoutes(usedRoutes, gcTimestamp) {
433
- var _a;
476
+ updateUsedRoutes(usedRoutes) {
434
477
  // Get a map of channel ids to routes used in it.
435
478
  const usedContextRoutes = unpackChildNodesUsedRoutes(usedRoutes);
436
479
  // Verify that the used routes are correct.
@@ -439,7 +482,7 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
439
482
  }
440
483
  // Update the used routes in each context. Used routes is empty for unused context.
441
484
  for (const [contextId, context] of this.contexts) {
442
- context.updateUsedRoutes((_a = usedContextRoutes.get(contextId)) !== null && _a !== void 0 ? _a : [], gcTimestamp);
485
+ context.updateUsedRoutes(usedContextRoutes.get(contextId) ?? []);
443
486
  }
444
487
  }
445
488
  /**
@@ -449,31 +492,7 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
449
492
  * @param outboundHandle - The handle of the outbound node that is referenced.
450
493
  */
451
494
  addedGCOutboundReference(srcHandle, outboundHandle) {
452
- var _a, _b;
453
- (_b = (_a = this.dataStoreContext).addedGCOutboundReference) === null || _b === void 0 ? void 0 : _b.call(_a, srcHandle, outboundHandle);
454
- }
455
- /**
456
- * Returns the base GC details for the channel with the given id. This is used to initialize its GC state.
457
- * @param channelId - The id of the channel context that is asked for the initial GC details.
458
- * @returns the requested channel's base GC details.
459
- */
460
- async getChannelBaseGCDetails(channelId) {
461
- var _a;
462
- let channelBaseGCDetails = (await this.channelsBaseGCDetails).get(channelId);
463
- if (channelBaseGCDetails === undefined) {
464
- channelBaseGCDetails = {};
465
- }
466
- else if (((_a = channelBaseGCDetails.gcData) === null || _a === void 0 ? void 0 : _a.gcNodes) !== undefined) {
467
- // Note: if the child channel has an explicit handle route to its parent, it will be removed here and
468
- // expected to be added back by the parent when getGCData is called.
469
- removeRouteFromAllNodes(channelBaseGCDetails.gcData.gcNodes, this.absolutePath);
470
- }
471
- // Currently, channel context's are always considered used. So, it there are no used routes for it, we still
472
- // need to mark it as used. Add self-route (empty string) to the channel context's used routes.
473
- if (channelBaseGCDetails.usedRoutes === undefined || channelBaseGCDetails.usedRoutes.length === 0) {
474
- channelBaseGCDetails.usedRoutes = [""];
475
- }
476
- return channelBaseGCDetails;
495
+ this.dataStoreContext.addedGCOutboundReference?.(srcHandle, outboundHandle);
477
496
  }
478
497
  /**
479
498
  * Returns a summary at the current sequence number.
@@ -492,7 +511,8 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
492
511
  // If the object is registered - and we have received the sequenced op creating the object
493
512
  // (i.e. it has a base mapping) - then we go ahead and summarize
494
513
  return isAttached;
495
- }).map(async ([contextId, context]) => {
514
+ })
515
+ .map(async ([contextId, context]) => {
496
516
  const contextSummary = await context.summarize(fullTree, trackState, telemetryContext);
497
517
  summaryBuilder.addWithStats(contextId, contextSummary);
498
518
  }));
@@ -503,20 +523,20 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
503
523
  * back-compat 0.59.1000 - getAttachSummary() is called when making a data store globally visible (previously
504
524
  * attaching state). Ideally, attachGraph() should have already be called making it locally visible. However,
505
525
  * before visibility state was added, this may not have been the case and getAttachSummary() could be called:
506
- * 1) Before attaching the data store - When a detached container is attached.
507
- * 2) After attaching the data store - When a data store is created and bound in an attached container.
526
+ *
527
+ * 1. Before attaching the data store - When a detached container is attached.
528
+ *
529
+ * 2. After attaching the data store - When a data store is created and bound in an attached container.
508
530
  *
509
531
  * The basic idea is that all local object should become locally visible before they are globally visible.
510
532
  */
511
533
  this.attachGraph();
512
- /**
513
- * This assert cannot be added now due to back-compat. To be uncommented when the following issue is fixed -
514
- * https://github.com/microsoft/FluidFramework/issues/9688.
515
- *
516
- * assert(this.visibilityState === VisibilityState.LocallyVisible,
517
- * "The data store should be locally visible when generating attach summary",
518
- * );
519
- */
534
+ // This assert cannot be added now due to back-compat. To be uncommented when the following issue is fixed -
535
+ // https://github.com/microsoft/FluidFramework/issues/9688.
536
+ //
537
+ // assert(this.visibilityState === VisibilityState.LocallyVisible,
538
+ // "The data store should be locally visible when generating attach summary",
539
+ // );
520
540
  const summaryBuilder = new SummaryTreeBuilder();
521
541
  // Craft the .attributes file for each shared object
522
542
  for (const [contextId, context] of this.contexts) {
@@ -544,9 +564,15 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
544
564
  submitMessage(type, content, localOpMetadata) {
545
565
  this.submit(type, content, localOpMetadata);
546
566
  }
547
- submitSignal(type, content) {
567
+ /**
568
+ * Submits the signal to be sent to other clients.
569
+ * @param type - Type of the signal.
570
+ * @param content - Content of the signal.
571
+ * @param targetClientId - When specified, the signal is only sent to the provided client id.
572
+ */
573
+ submitSignal(type, content, targetClientId) {
548
574
  this.verifyNotClosed();
549
- return this.dataStoreContext.submitSignal(type, content);
575
+ return this.dataStoreContext.submitSignal(type, content, targetClientId);
550
576
  }
551
577
  /**
552
578
  * Will return when the data store is attached.
@@ -574,7 +600,7 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
574
600
  snapshot,
575
601
  type: channel.attributes.type,
576
602
  };
577
- this.pendingAttach.set(channel.id, message);
603
+ this.pendingAttach.add(channel.id);
578
604
  this.submit(DataStoreMessageType.Attach, message);
579
605
  const context = this.contexts.get(channel.id);
580
606
  context.makeVisible();
@@ -597,15 +623,14 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
597
623
  reSubmit(type, content, localOpMetadata) {
598
624
  this.verifyNotClosed();
599
625
  switch (type) {
600
- case DataStoreMessageType.ChannelOp:
601
- {
602
- // For Operations, find the right channel and trigger resubmission on it.
603
- const envelope = content;
604
- const channelContext = this.contexts.get(envelope.address);
605
- assert(!!channelContext, 0x183 /* "There should be a channel context for the op" */);
606
- channelContext.reSubmit(envelope.contents, localOpMetadata);
607
- break;
608
- }
626
+ case DataStoreMessageType.ChannelOp: {
627
+ // For Operations, find the right channel and trigger resubmission on it.
628
+ const envelope = content;
629
+ const channelContext = this.contexts.get(envelope.address);
630
+ assert(!!channelContext, 0x183 /* "There should be a channel context for the op" */);
631
+ channelContext.reSubmit(envelope.contents, localOpMetadata);
632
+ break;
633
+ }
609
634
  case DataStoreMessageType.Attach:
610
635
  // For Attach messages, just submit them again.
611
636
  this.submit(type, content, localOpMetadata);
@@ -622,25 +647,42 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
622
647
  rollback(type, content, localOpMetadata) {
623
648
  this.verifyNotClosed();
624
649
  switch (type) {
625
- case DataStoreMessageType.ChannelOp:
626
- {
627
- // For Operations, find the right channel and trigger resubmission on it.
628
- const envelope = content;
629
- const channelContext = this.contexts.get(envelope.address);
630
- assert(!!channelContext, 0x2ed /* "There should be a channel context for the op" */);
631
- channelContext.rollback(envelope.contents, localOpMetadata);
632
- break;
633
- }
650
+ case DataStoreMessageType.ChannelOp: {
651
+ // For Operations, find the right channel and trigger resubmission on it.
652
+ const envelope = content;
653
+ const channelContext = this.contexts.get(envelope.address);
654
+ assert(!!channelContext, 0x2ed /* "There should be a channel context for the op" */);
655
+ channelContext.rollback(envelope.contents, localOpMetadata);
656
+ break;
657
+ }
634
658
  default:
635
659
  throw new LoggingError(`Can't rollback ${type} message`);
636
660
  }
637
661
  }
638
662
  async applyStashedOp(content) {
639
- const envelope = content;
640
- const channelContext = this.contexts.get(envelope.address);
641
- assert(!!channelContext, 0x184 /* "There should be a channel context for the op" */);
642
- await channelContext.getChannel();
643
- return channelContext.applyStashedOp(envelope.contents);
663
+ const type = content?.type;
664
+ switch (type) {
665
+ case DataStoreMessageType.Attach: {
666
+ const attachMessage = content.content;
667
+ // local means this node will throw if summarized; this is fine because only interactive clients will have stashed ops
668
+ const summarizerNodeParams = {
669
+ type: CreateSummarizerNodeSource.Local,
670
+ };
671
+ const context = this.createRemoteChannelContext(attachMessage, summarizerNodeParams);
672
+ this.pendingAttach.add(attachMessage.id);
673
+ this.contexts.set(attachMessage.id, context);
674
+ return;
675
+ }
676
+ case DataStoreMessageType.ChannelOp: {
677
+ const envelope = content.content;
678
+ const channelContext = this.contexts.get(envelope.address);
679
+ assert(!!channelContext, 0x184 /* "There should be a channel context for the op" */);
680
+ await channelContext.getChannel();
681
+ return channelContext.applyStashedOp(envelope.contents);
682
+ }
683
+ default:
684
+ unreachableCase(type);
685
+ }
644
686
  }
645
687
  setChannelDirty(address) {
646
688
  this.verifyNotClosed();
@@ -649,7 +691,10 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
649
691
  processChannelOp(message, local, localOpMetadata) {
650
692
  this.verifyNotClosed();
651
693
  const envelope = message.contents;
652
- const transformed = Object.assign(Object.assign({}, message), { contents: envelope.contents });
694
+ const transformed = {
695
+ ...message,
696
+ contents: envelope.contents,
697
+ };
653
698
  const channelContext = this.contexts.get(envelope.address);
654
699
  assert(!!channelContext, 0x185 /* "Channel not found" */);
655
700
  channelContext.processOp(transformed, local, localOpMetadata);
@@ -691,12 +736,37 @@ export class FluidDataStoreRuntime extends TypedEventEmitter {
691
736
  throw new LoggingError("Runtime is closed");
692
737
  }
693
738
  }
739
+ /**
740
+ * Summarizer client should not have local changes. These changes can become part of the summary and can break
741
+ * eventual consistency. For example, the next summary (say at ref seq# 100) may contain these changes whereas
742
+ * other clients that are up-to-date till seq# 100 may not have them yet.
743
+ */
744
+ identifyLocalChangeInSummarizer(eventName, channelId, channelType) {
745
+ if (this.clientDetails.type !== "summarizer" || this.localChangesTelemetryCount <= 0) {
746
+ return;
747
+ }
748
+ // Log a telemetry if there are local changes in the summarizer. This will give us data on how often
749
+ // this is happening and which data stores do this. The eventual goal is to disallow local changes
750
+ // in the summarizer and the data will help us plan this.
751
+ this.mc.logger.sendTelemetryEvent({
752
+ eventName,
753
+ ...tagCodeArtifacts({
754
+ channelType,
755
+ channelId,
756
+ fluidDataStoreId: this.id,
757
+ fluidDataStorePackagePath: this.dataStoreContext.packagePath.join("/"),
758
+ }),
759
+ stack: generateStack(),
760
+ });
761
+ this.localChangesTelemetryCount--;
762
+ }
694
763
  }
695
764
  /**
696
765
  * Mixin class that adds request handler to FluidDataStoreRuntime
697
766
  * Request handler is only called when data store can't resolve request, i.e. for custom requests.
698
767
  * @param Base - base class, inherits from FluidDataStoreRuntime
699
768
  * @param requestHandler - request handler to mix in
769
+ * @internal
700
770
  */
701
771
  export const mixinRequestHandler = (requestHandler, Base = FluidDataStoreRuntime) => class RuntimeWithRequestHandler extends Base {
702
772
  async request(request) {
@@ -712,6 +782,7 @@ export const mixinRequestHandler = (requestHandler, Base = FluidDataStoreRuntime
712
782
  * @param handler - handler that returns info about blob to be added to summary.
713
783
  * Or undefined not to add anything to summary.
714
784
  * @param Base - base class, inherits from FluidDataStoreRuntime
785
+ * @alpha
715
786
  */
716
787
  export const mixinSummaryHandler = (handler, Base = FluidDataStoreRuntime) => class RuntimeWithSummarizerHandler extends Base {
717
788
  addBlob(summary, path, content) {
@@ -736,11 +807,17 @@ export const mixinSummaryHandler = (handler, Base = FluidDataStoreRuntime) => cl
736
807
  }
737
808
  async summarize(...args) {
738
809
  const summary = await super.summarize(...args);
739
- const content = await handler(this);
740
- if (content !== undefined) {
741
- this.addBlob(summary, content.path, content.content);
810
+ try {
811
+ const content = await handler(this);
812
+ if (content !== undefined) {
813
+ this.addBlob(summary, content.path, content.content);
814
+ }
815
+ }
816
+ catch (e) {
817
+ // Any error coming from app-provided handler should be marked as DataProcessingError
818
+ throw DataProcessingError.wrapIfUnrecognized(e, "mixinSummaryHandler");
742
819
  }
743
820
  return summary;
744
821
  }
745
822
  };
746
- //# sourceMappingURL=dataStoreRuntime.js.map
823
+ //# sourceMappingURL=dataStoreRuntime.mjs.map