@fluidframework/routerlicious-driver 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.1.0.148229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache.js +2 -2
- package/dist/cache.js.map +1 -1
- package/dist/documentService.d.ts.map +1 -1
- package/dist/documentService.js +9 -3
- package/dist/documentService.js.map +1 -1
- package/dist/documentServiceFactory.d.ts +0 -1
- package/dist/documentServiceFactory.d.ts.map +1 -1
- package/dist/documentServiceFactory.js +3 -4
- package/dist/documentServiceFactory.js.map +1 -1
- package/dist/errorUtils.d.ts +9 -2
- package/dist/errorUtils.d.ts.map +1 -1
- package/dist/errorUtils.js +15 -8
- package/dist/errorUtils.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/mapWithExpiration.d.ts +34 -0
- package/dist/mapWithExpiration.d.ts.map +1 -0
- package/dist/mapWithExpiration.js +105 -0
- package/dist/mapWithExpiration.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/tokens.d.ts +24 -7
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js.map +1 -1
- package/dist/treeUtils.d.ts +51 -0
- package/dist/treeUtils.d.ts.map +1 -0
- package/dist/treeUtils.js +85 -0
- package/dist/treeUtils.js.map +1 -0
- package/dist/wholeSummaryDocumentStorageService.d.ts.map +1 -1
- package/dist/wholeSummaryDocumentStorageService.js +5 -6
- package/dist/wholeSummaryDocumentStorageService.js.map +1 -1
- package/lib/cache.js +1 -1
- package/lib/cache.js.map +1 -1
- package/lib/documentService.d.ts.map +1 -1
- package/lib/documentService.js +11 -5
- package/lib/documentService.js.map +1 -1
- package/lib/documentServiceFactory.d.ts +0 -1
- package/lib/documentServiceFactory.d.ts.map +1 -1
- package/lib/documentServiceFactory.js +4 -5
- package/lib/documentServiceFactory.js.map +1 -1
- package/lib/errorUtils.d.ts +9 -2
- package/lib/errorUtils.d.ts.map +1 -1
- package/lib/errorUtils.js +14 -7
- package/lib/errorUtils.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/mapWithExpiration.d.ts +34 -0
- package/lib/mapWithExpiration.d.ts.map +1 -0
- package/lib/mapWithExpiration.js +101 -0
- package/lib/mapWithExpiration.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/tokens.d.ts +24 -7
- package/lib/tokens.d.ts.map +1 -1
- package/lib/tokens.js.map +1 -1
- package/lib/treeUtils.d.ts +51 -0
- package/lib/treeUtils.d.ts.map +1 -0
- package/lib/treeUtils.js +80 -0
- package/lib/treeUtils.js.map +1 -0
- package/lib/wholeSummaryDocumentStorageService.d.ts.map +1 -1
- package/lib/wholeSummaryDocumentStorageService.js +4 -5
- package/lib/wholeSummaryDocumentStorageService.js.map +1 -1
- package/package.json +52 -53
- package/src/cache.ts +1 -1
- package/src/documentService.ts +26 -11
- package/src/documentServiceFactory.ts +5 -4
- package/src/errorUtils.ts +16 -4
- package/src/index.ts +3 -0
- package/src/mapWithExpiration.ts +124 -0
- package/src/packageVersion.ts +1 -1
- package/src/tokens.ts +24 -7
- package/src/treeUtils.ts +107 -0
- package/src/wholeSummaryDocumentStorageService.ts +7 -5
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert } from "@fluidframework/common-utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An extension of Map that expires (deletes) entries after a period of inactivity.
|
|
10
|
+
* The policy is based on the last time a key was written to.
|
|
11
|
+
*/
|
|
12
|
+
export class MapWithExpiration<TKey = any, TValue = any> extends Map<TKey, TValue> {
|
|
13
|
+
/** Timestamps (as epoch ms numbers) of when each key was last refreshed */
|
|
14
|
+
private readonly lastRefreshedTimes = new Map<TKey, number>();
|
|
15
|
+
|
|
16
|
+
constructor(private readonly expiryMs: number) {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private refresh(key: TKey): void {
|
|
21
|
+
this.lastRefreshedTimes.set(key, new Date().valueOf());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if the key is present and expired, false if it's not expired, and undefined if it's not found
|
|
26
|
+
* If cleanUp is passed as true, then delete any expired entry before returning.
|
|
27
|
+
*/
|
|
28
|
+
private checkExpiry(key: TKey, cleanUp: boolean = false): boolean | undefined {
|
|
29
|
+
const refreshTime = this.lastRefreshedTimes.get(key);
|
|
30
|
+
assert(
|
|
31
|
+
(refreshTime !== undefined) === super.has(key),
|
|
32
|
+
0x50c /* freshness map out of sync */,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (refreshTime === undefined) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const expired = new Date().valueOf() - refreshTime >= this.expiryMs;
|
|
39
|
+
if (expired && cleanUp) {
|
|
40
|
+
this.delete(key);
|
|
41
|
+
}
|
|
42
|
+
return expired;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private clearExpiredEntries() {
|
|
46
|
+
// forEach clears out any expired entries
|
|
47
|
+
this.forEach(() => {});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get size(): number {
|
|
51
|
+
this.clearExpiredEntries();
|
|
52
|
+
return super.size;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
has(key: TKey): boolean {
|
|
56
|
+
this.checkExpiry(key, true /* cleanUp */);
|
|
57
|
+
return super.has(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get(key: TKey): TValue | undefined {
|
|
61
|
+
this.checkExpiry(key, true /* cleanUp */);
|
|
62
|
+
return super.get(key);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set(key: TKey, value: TValue): this {
|
|
66
|
+
// Sliding window expiration policy (on write)
|
|
67
|
+
this.refresh(key);
|
|
68
|
+
return super.set(key, value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
delete(key: TKey): boolean {
|
|
72
|
+
this.lastRefreshedTimes.delete(key);
|
|
73
|
+
return super.delete(key);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clear(): void {
|
|
77
|
+
this.lastRefreshedTimes.clear();
|
|
78
|
+
super.clear();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
forEach(
|
|
82
|
+
callbackfn: (value: TValue, key: TKey, map: Map<TKey, TValue>) => void,
|
|
83
|
+
thisArg?: any,
|
|
84
|
+
): void {
|
|
85
|
+
const expiredKeys: TKey[] = [];
|
|
86
|
+
super.forEach(
|
|
87
|
+
(v, k, m) => {
|
|
88
|
+
if (this.checkExpiry(k) === true) {
|
|
89
|
+
expiredKeys.push(k);
|
|
90
|
+
} else {
|
|
91
|
+
callbackfn.bind(thisArg)(v, k, m);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
// Note we don't pass thisArg here, since we bind it directly to callbackFn and don't need it otherwise
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Clean up keys we know are expired now that we're done iterating
|
|
98
|
+
expiredKeys.forEach((key: TKey) => {
|
|
99
|
+
this.delete(key);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
entries(): IterableIterator<[TKey, TValue]> {
|
|
104
|
+
this.clearExpiredEntries();
|
|
105
|
+
return super.entries();
|
|
106
|
+
}
|
|
107
|
+
keys(): IterableIterator<TKey> {
|
|
108
|
+
this.clearExpiredEntries();
|
|
109
|
+
return super.keys();
|
|
110
|
+
}
|
|
111
|
+
values(): IterableIterator<TValue> {
|
|
112
|
+
this.clearExpiredEntries();
|
|
113
|
+
return super.values();
|
|
114
|
+
}
|
|
115
|
+
[Symbol.iterator](): IterableIterator<[TKey, TValue]> {
|
|
116
|
+
this.clearExpiredEntries();
|
|
117
|
+
return super[Symbol.iterator]();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
valueOf() {
|
|
121
|
+
this.clearExpiredEntries();
|
|
122
|
+
return super.valueOf();
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/packageVersion.ts
CHANGED
package/src/tokens.ts
CHANGED
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
import { ITokenClaims } from "@fluidframework/protocol-definitions";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Abstracts the discovery of claims contained within a token.
|
|
10
10
|
*/
|
|
11
11
|
export interface ITokenService {
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the {@link @fluidframework/protocol-definitions#ITokenClaims | token claims} from the provided
|
|
14
|
+
* {@link https://jwt.io/introduction/ | JSON Web Token (JWT)} string representation.
|
|
15
|
+
*/
|
|
12
16
|
extractClaims(token: string): ITokenClaims;
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -19,8 +23,9 @@ export interface ITokenResponse {
|
|
|
19
23
|
jwt: string;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
26
|
+
* A flag indicating whether token was obtained from local cache.
|
|
27
|
+
*
|
|
28
|
+
* @remarks `undefined` indicates that the source of the token could not be determined.
|
|
24
29
|
*/
|
|
25
30
|
fromCache?: boolean;
|
|
26
31
|
}
|
|
@@ -32,11 +37,16 @@ export interface ITokenResponse {
|
|
|
32
37
|
export interface ITokenProvider {
|
|
33
38
|
/**
|
|
34
39
|
* Fetches the orderer token from host.
|
|
40
|
+
*
|
|
35
41
|
* @param tenantId - Tenant ID.
|
|
36
42
|
* @param documentId - Optional. Document ID is only required for document-scoped requests.
|
|
37
43
|
* @param refresh - Optional flag indicating whether token fetch must bypass local cache.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
44
|
+
* This likely indicates that some previous request failed authorization due to an expired token,
|
|
45
|
+
* and so a fresh token is required.
|
|
46
|
+
*
|
|
47
|
+
* Default: `false`.
|
|
48
|
+
*
|
|
49
|
+
* NOTE: This parameter will be made required in the future.
|
|
40
50
|
*/
|
|
41
51
|
fetchOrdererToken(
|
|
42
52
|
tenantId: string,
|
|
@@ -46,11 +56,16 @@ export interface ITokenProvider {
|
|
|
46
56
|
|
|
47
57
|
/**
|
|
48
58
|
* Fetches the storage token from host.
|
|
59
|
+
*
|
|
49
60
|
* @param tenantId - Tenant ID.
|
|
50
61
|
* @param documentId - Document ID.
|
|
51
62
|
* @param refresh - Optional flag indicating whether token fetch must bypass local cache.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
63
|
+
* This likely indicates that some previous request failed authorization due to an expired token,
|
|
64
|
+
* and so a fresh token is required.
|
|
65
|
+
*
|
|
66
|
+
* Default: `false`.
|
|
67
|
+
*
|
|
68
|
+
* NOTE: This parameter will be made required in the future.
|
|
54
69
|
*/
|
|
55
70
|
fetchStorageToken(
|
|
56
71
|
tenantId: string,
|
|
@@ -64,7 +79,9 @@ export interface ITokenProvider {
|
|
|
64
79
|
* created it.
|
|
65
80
|
*
|
|
66
81
|
* @remarks Notes:
|
|
82
|
+
*
|
|
67
83
|
* * Using the callback may have performance impact on the document creation process.
|
|
84
|
+
*
|
|
68
85
|
* * Any exceptions thrown in the callback would fail the creation workflow
|
|
69
86
|
* (see {@link RouterliciousDocumentServiceFactory.createContainer} for more details).
|
|
70
87
|
*
|
package/src/treeUtils.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert, IsoBuffer } from "@fluidframework/common-utils";
|
|
7
|
+
import {
|
|
8
|
+
SummaryType,
|
|
9
|
+
ISnapshotTree,
|
|
10
|
+
ISummaryTree,
|
|
11
|
+
SummaryObject,
|
|
12
|
+
} from "@fluidframework/protocol-definitions";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Summary tree assembler props
|
|
16
|
+
*/
|
|
17
|
+
export interface ISummaryTreeAssemblerProps {
|
|
18
|
+
/**
|
|
19
|
+
* Indicates that this tree is unreferenced. If this is not present, the tree is considered referenced.
|
|
20
|
+
*/
|
|
21
|
+
unreferenced?: true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Summary tree assembler (without stats collection).
|
|
26
|
+
*/
|
|
27
|
+
export class SummaryTreeAssembler {
|
|
28
|
+
private attachmentCounter: number = 0;
|
|
29
|
+
private readonly summaryTree: { [path: string]: SummaryObject } = {};
|
|
30
|
+
|
|
31
|
+
constructor(private readonly props?: ISummaryTreeAssemblerProps) {}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get final summary
|
|
35
|
+
*/
|
|
36
|
+
public get summary(): ISummaryTree {
|
|
37
|
+
return {
|
|
38
|
+
type: SummaryType.Tree,
|
|
39
|
+
tree: { ...this.summaryTree },
|
|
40
|
+
unreferenced: this.props?.unreferenced,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add blob to summary
|
|
46
|
+
*/
|
|
47
|
+
public addBlob(key: string, content: string | Uint8Array): void {
|
|
48
|
+
this.summaryTree[key] = {
|
|
49
|
+
type: SummaryType.Blob,
|
|
50
|
+
content,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Add handle to summary
|
|
56
|
+
*/
|
|
57
|
+
public addHandle(
|
|
58
|
+
key: string,
|
|
59
|
+
handleType: SummaryType.Tree | SummaryType.Blob | SummaryType.Attachment,
|
|
60
|
+
handle: string,
|
|
61
|
+
): void {
|
|
62
|
+
this.summaryTree[key] = {
|
|
63
|
+
type: SummaryType.Handle,
|
|
64
|
+
handleType,
|
|
65
|
+
handle,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add tree to summary
|
|
71
|
+
*/
|
|
72
|
+
public addTree(key: string, summary: ISummaryTree): void {
|
|
73
|
+
this.summaryTree[key] = summary;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add attachment to summary
|
|
78
|
+
*/
|
|
79
|
+
public addAttachment(id: string) {
|
|
80
|
+
this.summaryTree[this.attachmentCounter++] = { id, type: SummaryType.Attachment };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Helper function that converts ISnapshotTree and blobs to ISummaryTree
|
|
86
|
+
* @param snapshot - Source snapshot tree
|
|
87
|
+
* @param blobs - Blobs cache
|
|
88
|
+
* @returns Converted snapshot in ISummaryTree format
|
|
89
|
+
*/
|
|
90
|
+
export function convertSnapshotAndBlobsToSummaryTree(
|
|
91
|
+
snapshot: ISnapshotTree,
|
|
92
|
+
blobs: Map<string, ArrayBuffer>,
|
|
93
|
+
): ISummaryTree {
|
|
94
|
+
const assembler = new SummaryTreeAssembler({
|
|
95
|
+
unreferenced: snapshot.unreferenced,
|
|
96
|
+
});
|
|
97
|
+
for (const [path, id] of Object.entries(snapshot.blobs)) {
|
|
98
|
+
const blob = blobs.get(id);
|
|
99
|
+
assert(blob !== undefined, 0x2dd /* "Cannot find blob for a given id" */);
|
|
100
|
+
assembler.addBlob(path, IsoBuffer.from(blob).toString("utf-8"));
|
|
101
|
+
}
|
|
102
|
+
for (const [key, tree] of Object.entries(snapshot.trees)) {
|
|
103
|
+
const subtree = convertSnapshotAndBlobsToSummaryTree(tree, blobs);
|
|
104
|
+
assembler.addTree(key, subtree);
|
|
105
|
+
}
|
|
106
|
+
return assembler.summary;
|
|
107
|
+
}
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
ISummaryContext,
|
|
11
11
|
IDocumentStorageServicePolicies,
|
|
12
12
|
} from "@fluidframework/driver-definitions";
|
|
13
|
-
import { convertSnapshotAndBlobsToSummaryTree } from "@fluidframework/driver-utils";
|
|
14
13
|
import {
|
|
15
14
|
ICreateBlobResponse,
|
|
16
15
|
ISnapshotTree,
|
|
@@ -28,6 +27,7 @@ import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
|
28
27
|
import { ICache, InMemoryCache } from "./cache";
|
|
29
28
|
import { ISnapshotTreeVersion } from "./definitions";
|
|
30
29
|
import { IRouterliciousDriverPolicies } from "./policies";
|
|
30
|
+
import { convertSnapshotAndBlobsToSummaryTree } from "./treeUtils";
|
|
31
31
|
|
|
32
32
|
const latestSnapshotId: string = "latest";
|
|
33
33
|
|
|
@@ -248,19 +248,21 @@ export class WholeSummaryDocumentStorageService implements IDocumentStorageServi
|
|
|
248
248
|
);
|
|
249
249
|
const normalizedWholeSummary =
|
|
250
250
|
convertWholeFlatSummaryToSnapshotTreeAndBlobs(wholeFlatSummary);
|
|
251
|
+
assert(
|
|
252
|
+
normalizedWholeSummary.snapshotTree.id !== undefined,
|
|
253
|
+
0x275 /* "Root tree should contain the id" */,
|
|
254
|
+
);
|
|
251
255
|
const wholeFlatSummaryId: string = wholeFlatSummary.id;
|
|
252
|
-
const snapshotTreeId = normalizedWholeSummary.snapshotTree.id;
|
|
253
|
-
assert(snapshotTreeId !== undefined, 0x275 /* "Root tree should contain the id" */);
|
|
254
256
|
const snapshotTreeVersion = {
|
|
255
257
|
id: wholeFlatSummaryId,
|
|
256
258
|
snapshotTree: normalizedWholeSummary.snapshotTree,
|
|
257
259
|
};
|
|
258
260
|
|
|
259
261
|
const cachePs: Promise<any>[] = [
|
|
260
|
-
this.snapshotTreeCache.put(this.getCacheKey(
|
|
262
|
+
this.snapshotTreeCache.put(this.getCacheKey(wholeFlatSummaryId), snapshotTreeVersion),
|
|
261
263
|
this.initBlobCache(normalizedWholeSummary.blobs),
|
|
262
264
|
];
|
|
263
|
-
if (
|
|
265
|
+
if (wholeFlatSummaryId !== versionId) {
|
|
264
266
|
// versionId could be "latest". When summarizer checks cache for "latest", we want it to be available.
|
|
265
267
|
// TODO: For in-memory cache, <latest,snapshotTree> will be a shared pointer with <snapshotId,snapshotTree>,
|
|
266
268
|
// However, for something like Redis, this will cache the same value twice. Alternatively, could we simply
|