@fluidframework/routerlicious-driver 2.0.0-dev.1.4.6.106135 → 2.0.0-dev.2.2.0.111723

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 (63) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/deltaStorageService.d.ts +2 -1
  3. package/dist/deltaStorageService.d.ts.map +1 -1
  4. package/dist/deltaStorageService.js +17 -6
  5. package/dist/deltaStorageService.js.map +1 -1
  6. package/dist/documentDeltaConnection.d.ts +1 -2
  7. package/dist/documentDeltaConnection.d.ts.map +1 -1
  8. package/dist/documentDeltaConnection.js.map +1 -1
  9. package/dist/documentService.d.ts.map +1 -1
  10. package/dist/documentService.js +27 -5
  11. package/dist/documentService.js.map +1 -1
  12. package/dist/documentServiceFactory.d.ts.map +1 -1
  13. package/dist/documentServiceFactory.js +39 -16
  14. package/dist/documentServiceFactory.js.map +1 -1
  15. package/dist/index.d.ts +4 -8
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +8 -18
  18. package/dist/index.js.map +1 -1
  19. package/dist/packageVersion.d.ts +1 -1
  20. package/dist/packageVersion.js +1 -1
  21. package/dist/packageVersion.js.map +1 -1
  22. package/dist/restWrapper.d.ts.map +1 -1
  23. package/dist/restWrapper.js +20 -9
  24. package/dist/restWrapper.js.map +1 -1
  25. package/dist/urlUtils.d.ts.map +1 -1
  26. package/dist/urlUtils.js +20 -24
  27. package/dist/urlUtils.js.map +1 -1
  28. package/lib/deltaStorageService.d.ts +2 -1
  29. package/lib/deltaStorageService.d.ts.map +1 -1
  30. package/lib/deltaStorageService.js +17 -6
  31. package/lib/deltaStorageService.js.map +1 -1
  32. package/lib/documentDeltaConnection.d.ts +1 -2
  33. package/lib/documentDeltaConnection.d.ts.map +1 -1
  34. package/lib/documentDeltaConnection.js.map +1 -1
  35. package/lib/documentService.d.ts.map +1 -1
  36. package/lib/documentService.js +27 -5
  37. package/lib/documentService.js.map +1 -1
  38. package/lib/documentServiceFactory.d.ts.map +1 -1
  39. package/lib/documentServiceFactory.js +40 -17
  40. package/lib/documentServiceFactory.js.map +1 -1
  41. package/lib/index.d.ts +4 -8
  42. package/lib/index.d.ts.map +1 -1
  43. package/lib/index.js +4 -8
  44. package/lib/index.js.map +1 -1
  45. package/lib/packageVersion.d.ts +1 -1
  46. package/lib/packageVersion.js +1 -1
  47. package/lib/packageVersion.js.map +1 -1
  48. package/lib/restWrapper.d.ts.map +1 -1
  49. package/lib/restWrapper.js +20 -9
  50. package/lib/restWrapper.js.map +1 -1
  51. package/lib/urlUtils.d.ts.map +1 -1
  52. package/lib/urlUtils.js +20 -24
  53. package/lib/urlUtils.js.map +1 -1
  54. package/package.json +26 -25
  55. package/prettier.config.cjs +8 -0
  56. package/src/deltaStorageService.ts +17 -8
  57. package/src/documentDeltaConnection.ts +1 -2
  58. package/src/documentService.ts +45 -13
  59. package/src/documentServiceFactory.ts +53 -19
  60. package/src/index.ts +9 -8
  61. package/src/packageVersion.ts +1 -1
  62. package/src/restWrapper.ts +34 -15
  63. package/src/urlUtils.ts +20 -24
@@ -11,9 +11,8 @@ import {
11
11
  } from "@fluidframework/driver-definitions";
12
12
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
13
13
  import { readAndParse, requestOps, emptyMessageStream } from "@fluidframework/driver-utils";
14
- import { TelemetryNullLogger } from "@fluidframework/common-utils";
15
14
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
16
- import { PerformanceEvent } from "@fluidframework/telemetry-utils";
15
+ import { PerformanceEvent, TelemetryNullLogger } from "@fluidframework/telemetry-utils";
17
16
  import { RestWrapper } from "@fluidframework/server-services-client";
18
17
  import { DocumentStorageService } from "./documentStorageService";
19
18
 
@@ -27,7 +26,8 @@ export class DocumentDeltaStorageService implements IDocumentDeltaStorageService
27
26
  private readonly tenantId: string,
28
27
  private readonly id: string,
29
28
  private readonly deltaStorageService: IDeltaStorageService,
30
- private readonly documentStorageService: DocumentStorageService) {
29
+ private readonly documentStorageService: DocumentStorageService,
30
+ private readonly logger: ITelemetryLogger) {
31
31
  }
32
32
 
33
33
  private logtailSha: string | undefined = this.documentStorageService.logTailSha;
@@ -62,11 +62,20 @@ export class DocumentDeltaStorageService implements IDocumentDeltaStorageService
62
62
 
63
63
  this.logtailSha = undefined;
64
64
  if (opsFromLogTail.length > 0) {
65
- const messages = opsFromLogTail.filter((op) =>
66
- op.sequenceNumber >= from,
67
- );
68
- if (messages.length > 0) {
69
- return { messages, partialResult: true };
65
+ try {
66
+ const messages = opsFromLogTail.filter((op, i) => {
67
+ // throw if the sequence numbers in logtail are not contiguous
68
+ if (i > 0 && op.sequenceNumber !== opsFromLogTail[i - 1].sequenceNumber + 1) {
69
+ throw new Error("Log tail ops are not contiguous");
70
+ }
71
+ return op.sequenceNumber >= from;
72
+ });
73
+
74
+ if (messages.length > 0 && messages[0].sequenceNumber === from) {
75
+ return { messages, partialResult: true };
76
+ }
77
+ } catch (error) {
78
+ this.logger.sendErrorEvent({ eventName: "LogTailReadError" }, error);
70
79
  }
71
80
  }
72
81
 
@@ -5,8 +5,7 @@
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { DocumentDeltaConnection } from "@fluidframework/driver-base";
8
- import { IDocumentDeltaConnection } from "@fluidframework/driver-definitions";
9
- import { IAnyDriverError } from "@fluidframework/driver-utils";
8
+ import { IAnyDriverError, IDocumentDeltaConnection } from "@fluidframework/driver-definitions";
10
9
  import { IClient, IConnect } from "@fluidframework/protocol-definitions";
11
10
  import type { io as SocketIOClientStatic } from "socket.io-client";
12
11
  import { errorObjectFromSocketError, IR11sSocketError } from "./errorUtils";
@@ -33,6 +33,7 @@ const RediscoverAfterTimeSinceDiscoveryMs = 5 * 60000; // 5 minute
33
33
  * The DocumentService manages the Socket.IO connection and manages routing requests to connected
34
34
  * clients.
35
35
  */
36
+ // eslint-disable-next-line import/namespace
36
37
  export class DocumentService implements api.IDocumentService {
37
38
  private lastDiscoveredAt: number = Date.now();
38
39
  private discoverP: Promise<void> | undefined;
@@ -175,6 +176,7 @@ export class DocumentService implements api.IDocumentService {
175
176
  this.documentId,
176
177
  deltaStorageService,
177
178
  this.documentStorageService,
179
+ this.logger,
178
180
  );
179
181
  }
180
182
 
@@ -188,20 +190,42 @@ export class DocumentService implements api.IDocumentService {
188
190
  if (this.shouldUpdateDiscoveredSessionInfo()) {
189
191
  await this.refreshDiscovery();
190
192
  }
191
- const ordererToken = await this.tokenProvider.fetchOrdererToken(
192
- this.tenantId,
193
- this.documentId,
194
- refreshToken,
193
+
194
+ const ordererToken = await PerformanceEvent.timedExecAsync(
195
+ this.logger,
196
+ {
197
+ eventName: "GetDeltaStreamToken",
198
+ docId: this.documentId,
199
+ details: JSON.stringify({
200
+ refreshToken,
201
+ }),
202
+ },
203
+ async () => {
204
+ return this.tokenProvider.fetchOrdererToken(
205
+ this.tenantId,
206
+ this.documentId,
207
+ refreshToken,
208
+ );
209
+ }
195
210
  );
196
211
 
197
- return R11sDocumentDeltaConnection.create(
198
- this.tenantId,
199
- this.documentId,
200
- ordererToken.jwt,
201
- io,
202
- client,
203
- this.deltaStreamUrl,
212
+ return PerformanceEvent.timedExecAsync(
204
213
  this.logger,
214
+ {
215
+ eventName: "ConnectToDeltaStream",
216
+ docId: this.documentId,
217
+ },
218
+ async () => {
219
+ return R11sDocumentDeltaConnection.create(
220
+ this.tenantId,
221
+ this.documentId,
222
+ ordererToken.jwt,
223
+ io,
224
+ client,
225
+ this.deltaStreamUrl,
226
+ this.logger,
227
+ );
228
+ }
205
229
  );
206
230
  };
207
231
 
@@ -228,7 +252,7 @@ export class DocumentService implements api.IDocumentService {
228
252
  this.discoverP = PerformanceEvent.timedExecAsync(
229
253
  this.logger,
230
254
  {
231
- eventName: "refreshSessionDiscovery",
255
+ eventName: "RefreshDiscovery",
232
256
  },
233
257
  async () => this.refreshDiscoveryCore(),
234
258
  );
@@ -243,7 +267,6 @@ export class DocumentService implements api.IDocumentService {
243
267
  this.ordererUrl = fluidResolvedUrl.endpoints.ordererUrl;
244
268
  this.deltaStorageUrl = fluidResolvedUrl.endpoints.deltaStorageUrl;
245
269
  this.deltaStreamUrl = fluidResolvedUrl.endpoints.deltaStreamUrl || this.ordererUrl;
246
- this.lastDiscoveredAt = Date.now();
247
270
  }
248
271
 
249
272
  /**
@@ -259,6 +282,15 @@ export class DocumentService implements api.IDocumentService {
259
282
  // Disconnect event is not so reliable in local testing. To ensure re-discovery when necessary,
260
283
  // re-discover if enough time has passed since last discovery.
261
284
  const pastLastDiscoveryTimeThreshold = (now - this.lastDiscoveredAt) > RediscoverAfterTimeSinceDiscoveryMs;
285
+ if (pastLastDiscoveryTimeThreshold) {
286
+ // Reset discover promise and refresh discovery.
287
+ this.lastDiscoveredAt = Date.now();
288
+ this.discoverP = undefined;
289
+ this.refreshDiscovery().catch(() => {
290
+ // Undo discovery time set on failure, so that next check refreshes.
291
+ this.lastDiscoveredAt = 0;
292
+ });
293
+ }
262
294
  return pastLastDiscoveryTimeThreshold;
263
295
  }
264
296
  }
@@ -18,7 +18,7 @@ import {
18
18
  getQuorumValuesFromProtocolSummary,
19
19
  RateLimiter,
20
20
  } from "@fluidframework/driver-utils";
21
- import { ChildLogger } from "@fluidframework/telemetry-utils";
21
+ import { ChildLogger, PerformanceEvent } from "@fluidframework/telemetry-utils";
22
22
  import { ISession } from "@fluidframework/server-services-client";
23
23
  import { DocumentService } from "./documentService";
24
24
  import { IRouterliciousDriverPolicies } from "./policies";
@@ -108,16 +108,34 @@ export class RouterliciousDocumentServiceFactory implements IDocumentServiceFact
108
108
  resolvedUrl.endpoints.ordererUrl,
109
109
  );
110
110
 
111
- // @TODO: Remove returned "string" type when removing back-compat code
112
- const res = await ordererRestWrapper.post<{ id: string; token?: string; session?: ISession; } | string>(
113
- `/documents/${tenantId}`,
111
+ const res = await PerformanceEvent.timedExecAsync(
112
+ logger2,
114
113
  {
115
- summary: convertSummaryToCreateNewSummary(appSummary),
116
- sequenceNumber: documentAttributes.sequenceNumber,
117
- values: quorumValues,
118
- enableDiscovery: this.driverPolicies.enableDiscovery,
119
- generateToken: this.tokenProvider.documentPostCreateCallback !== undefined,
114
+ eventName: "CreateNew",
115
+ details: JSON.stringify({
116
+ enableDiscovery: this.driverPolicies.enableDiscovery,
117
+ sequenceNumber: documentAttributes.sequenceNumber,
118
+ }),
120
119
  },
120
+ async (event) => {
121
+ // @TODO: Remove returned "string" type when removing back-compat code
122
+ const postRes = await ordererRestWrapper.post<
123
+ { id: string; token?: string; session?: ISession; } | string
124
+ >(`/documents/${tenantId}`, {
125
+ summary: convertSummaryToCreateNewSummary(appSummary),
126
+ sequenceNumber: documentAttributes.sequenceNumber,
127
+ values: quorumValues,
128
+ enableDiscovery: this.driverPolicies.enableDiscovery,
129
+ generateToken:
130
+ this.tokenProvider.documentPostCreateCallback !==
131
+ undefined,
132
+ });
133
+
134
+ event.end({
135
+ docId: typeof postRes === "string" ? postRes : postRes.id,
136
+ });
137
+ return postRes;
138
+ }
121
139
  );
122
140
 
123
141
  // For supporting backward compatibility, when the request has generateToken === true, it will return
@@ -138,12 +156,20 @@ export class RouterliciousDocumentServiceFactory implements IDocumentServiceFact
138
156
 
139
157
  // @TODO: Remove token from the condition, checking the documentPostCreateCallback !== undefined
140
158
  // is sufficient to determine if the token will be undefined or not.
141
- if (token && this.tokenProvider.documentPostCreateCallback !== undefined) {
142
- try {
143
- await this.tokenProvider.documentPostCreateCallback(documentId, token);
144
- } catch (error: any) {
145
- throw new DocumentPostCreateError(error);
146
- }
159
+ try {
160
+ await PerformanceEvent.timedExecAsync(
161
+ logger2,
162
+ {
163
+ eventName: "DocPostCreateCallback",
164
+ docId: documentId,
165
+ },
166
+ async () => {
167
+ if (token && this.tokenProvider.documentPostCreateCallback !== undefined) {
168
+ return this.tokenProvider.documentPostCreateCallback(documentId, token);
169
+ }
170
+ });
171
+ } catch (error: any) {
172
+ throw new DocumentPostCreateError(error);
147
173
  }
148
174
 
149
175
  parsedUrl.set("pathname", replaceDocumentIdInPath(parsedUrl.pathname, documentId));
@@ -206,10 +232,18 @@ export class RouterliciousDocumentServiceFactory implements IDocumentServiceFact
206
232
  this.driverPolicies.enableRestLess,
207
233
  resolvedUrl.endpoints.ordererUrl,
208
234
  );
209
- // The service responds with the current document session associated with the container.
210
- const discoveredSession = await ordererRestWrapper.get<ISession>(
211
- `/documents/${tenantId}/session/${documentId}`,
212
- );
235
+
236
+ const discoveredSession = await PerformanceEvent.timedExecAsync(
237
+ logger2,
238
+ {
239
+ eventName: "DiscoverSession",
240
+ docId: documentId,
241
+ },
242
+ async () => {
243
+ // The service responds with the current document session associated with the container.
244
+ return ordererRestWrapper.get<ISession>(
245
+ `/documents/${tenantId}/session/${documentId}`);
246
+ });
213
247
  return getDiscoveredFluidResolvedUrl(resolvedUrl, discoveredSession);
214
248
  };
215
249
  const fluidResolvedUrl: IFluidResolvedUrl = session !== undefined
package/src/index.ts CHANGED
@@ -3,11 +3,12 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- export * from "./defaultTokenProvider";
7
- export * from "./deltaStorageService";
8
- export * from "./documentService";
9
- export * from "./documentServiceFactory";
10
- export * from "./documentStorageService";
11
- export * from "./nullBlobStorageService";
12
- export * from "./policies";
13
- export * from "./tokens";
6
+ // Tokens
7
+ export { DefaultTokenProvider } from "./defaultTokenProvider";
8
+ export { ITokenProvider, ITokenResponse, ITokenService } from "./tokens";
9
+
10
+ // Factory
11
+ export { DocumentPostCreateError, RouterliciousDocumentServiceFactory } from "./documentServiceFactory";
12
+
13
+ // Configuration
14
+ export { IRouterliciousDriverPolicies } from "./policies";
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/routerlicious-driver";
9
- export const pkgVersion = "2.0.0-dev.1.4.6.106135";
9
+ export const pkgVersion = "2.0.0-dev.2.2.0.111723";
@@ -11,6 +11,7 @@ import {
11
11
  RestLessClient,
12
12
  RestWrapper,
13
13
  } from "@fluidframework/server-services-client";
14
+ import { PerformanceEvent } from "@fluidframework/telemetry-utils";
14
15
  import fetch from "cross-fetch";
15
16
  import type { AxiosRequestConfig, AxiosRequestHeaders } from "axios";
16
17
  import safeStringify from "json-stringify-safe";
@@ -145,17 +146,26 @@ export class RouterliciousStorageRestWrapper extends RouterliciousRestWrapper {
145
146
  token: `${fromUtf8ToBase64(tenantId)}`,
146
147
  };
147
148
  const getAuthorizationHeader: AuthorizationHeaderGetter = async (refreshToken?: boolean): Promise<string> => {
148
- // Craft credentials using tenant id and token
149
- const storageToken = await tokenProvider.fetchStorageToken(
150
- tenantId,
151
- documentId,
152
- refreshToken,
149
+ return PerformanceEvent.timedExecAsync(
150
+ logger,
151
+ {
152
+ eventName: "FetchStorageToken",
153
+ docId: documentId,
154
+ },
155
+ async () => {
156
+ // Craft credentials using tenant id and token
157
+ const storageToken = await tokenProvider.fetchStorageToken(
158
+ tenantId,
159
+ documentId,
160
+ refreshToken
161
+ );
162
+ const credentials = {
163
+ password: storageToken.jwt,
164
+ user: tenantId,
165
+ };
166
+ return getAuthorizationTokenFromCredentials(credentials);
167
+ }
153
168
  );
154
- const credentials = {
155
- password: storageToken.jwt,
156
- user: tenantId,
157
- };
158
- return getAuthorizationTokenFromCredentials(credentials);
159
169
  };
160
170
 
161
171
  const restWrapper = new RouterliciousStorageRestWrapper(
@@ -194,12 +204,21 @@ export class RouterliciousOrdererRestWrapper extends RouterliciousRestWrapper {
194
204
  baseurl?: string,
195
205
  ): Promise<RouterliciousOrdererRestWrapper> {
196
206
  const getAuthorizationHeader: AuthorizationHeaderGetter = async (refreshToken?: boolean): Promise<string> => {
197
- const ordererToken = await tokenProvider.fetchOrdererToken(
198
- tenantId,
199
- documentId,
200
- refreshToken,
207
+ return PerformanceEvent.timedExecAsync(
208
+ logger,
209
+ {
210
+ eventName: "FetchOrdererToken",
211
+ docId: documentId,
212
+ },
213
+ async () => {
214
+ const ordererToken = await tokenProvider.fetchOrdererToken(
215
+ tenantId,
216
+ documentId,
217
+ refreshToken,
218
+ );
219
+ return `Basic ${ordererToken.jwt}`;
220
+ }
201
221
  );
202
- return `Basic ${ordererToken.jwt}`;
203
222
  };
204
223
 
205
224
  const restWrapper = new RouterliciousOrdererRestWrapper(
package/src/urlUtils.ts CHANGED
@@ -21,30 +21,26 @@ export const replaceDocumentIdInPath = (urlPath: string, documentId: string): st
21
21
  urlPath.split("/").slice(0, -1).concat([documentId]).join("/");
22
22
 
23
23
  export const getDiscoveredFluidResolvedUrl = (resolvedUrl: IFluidResolvedUrl, session: ISession): IFluidResolvedUrl => {
24
- if (session) {
25
- const discoveredOrdererUrl = new URLParse(session.ordererUrl);
26
- const deltaStorageUrl = new URLParse(resolvedUrl.endpoints.deltaStorageUrl);
27
- deltaStorageUrl.set("host", discoveredOrdererUrl.host);
24
+ const discoveredOrdererUrl = new URLParse(session.ordererUrl);
25
+ const deltaStorageUrl = new URLParse(resolvedUrl.endpoints.deltaStorageUrl);
26
+ deltaStorageUrl.set("host", discoveredOrdererUrl.host);
28
27
 
29
- const discoveredStorageUrl = new URLParse(session.historianUrl);
30
- const storageUrl = new URLParse(resolvedUrl.endpoints.storageUrl);
31
- storageUrl.set("host", discoveredStorageUrl.host);
28
+ const discoveredStorageUrl = new URLParse(session.historianUrl);
29
+ const storageUrl = new URLParse(resolvedUrl.endpoints.storageUrl);
30
+ storageUrl.set("host", discoveredStorageUrl.host);
32
31
 
33
- const parsedUrl = parseFluidUrl(resolvedUrl.url);
34
- const discoveredResolvedUrl: IFluidResolvedUrl = {
35
- endpoints: {
36
- deltaStorageUrl: deltaStorageUrl.toString(),
37
- ordererUrl: session.ordererUrl,
38
- storageUrl: storageUrl.toString(),
39
- },
40
- id: resolvedUrl.id,
41
- tokens: resolvedUrl.tokens,
42
- type: resolvedUrl.type,
43
- url: new URLParse(`fluid://${discoveredOrdererUrl.host}${parsedUrl.pathname}`).toString(),
44
- };
45
-
46
- return discoveredResolvedUrl;
47
- } else {
48
- return resolvedUrl;
49
- }
32
+ const parsedUrl = parseFluidUrl(resolvedUrl.url);
33
+ const discoveredResolvedUrl: IFluidResolvedUrl = {
34
+ endpoints: {
35
+ deltaStorageUrl: deltaStorageUrl.toString(),
36
+ ordererUrl: session.ordererUrl,
37
+ deltaStreamUrl: session.deltaStreamUrl,
38
+ storageUrl: storageUrl.toString(),
39
+ },
40
+ id: resolvedUrl.id,
41
+ tokens: resolvedUrl.tokens,
42
+ type: resolvedUrl.type,
43
+ url: new URLParse(`fluid://${discoveredOrdererUrl.host}${parsedUrl.pathname}`).toString(),
44
+ };
45
+ return discoveredResolvedUrl;
50
46
  };