@fluidframework/container-loader 2.93.0 → 2.101.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/api-report/container-loader.legacy.alpha.api.md +13 -1
- package/dist/captureReferencedContents.d.ts +154 -0
- package/dist/captureReferencedContents.d.ts.map +1 -0
- package/dist/captureReferencedContents.js +349 -0
- package/dist/captureReferencedContents.js.map +1 -0
- package/dist/connectionManager.d.ts +2 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +39 -8
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +3 -1
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +13 -4
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +20 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +2 -2
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/createAndLoadContainerUtils.d.ts +95 -0
- package/dist/createAndLoadContainerUtils.d.ts.map +1 -1
- package/dist/createAndLoadContainerUtils.js +137 -11
- package/dist/createAndLoadContainerUtils.js.map +1 -1
- package/dist/frozenServices.d.ts +113 -30
- package/dist/frozenServices.d.ts.map +1 -1
- package/dist/frozenServices.js +236 -58
- package/dist/frozenServices.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/legacyAlpha.d.ts +2 -0
- package/dist/loader.d.ts +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +1 -1
- package/dist/loader.js.map +1 -1
- package/dist/loaderLayerCompatState.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingLocalStateStore.d.ts.map +1 -1
- package/dist/pendingLocalStateStore.js +9 -3
- package/dist/pendingLocalStateStore.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +3 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/serializedStateManager.d.ts +16 -1
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +11 -1
- package/dist/serializedStateManager.js.map +1 -1
- package/lib/captureReferencedContents.d.ts +154 -0
- package/lib/captureReferencedContents.d.ts.map +1 -0
- package/lib/captureReferencedContents.js +338 -0
- package/lib/captureReferencedContents.js.map +1 -0
- package/lib/connectionManager.d.ts +2 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +40 -9
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +3 -1
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +14 -5
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +20 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +2 -2
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/createAndLoadContainerUtils.d.ts +95 -0
- package/lib/createAndLoadContainerUtils.d.ts.map +1 -1
- package/lib/createAndLoadContainerUtils.js +128 -3
- package/lib/createAndLoadContainerUtils.js.map +1 -1
- package/lib/frozenServices.d.ts +113 -30
- package/lib/frozenServices.d.ts.map +1 -1
- package/lib/frozenServices.js +233 -57
- package/lib/frozenServices.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/legacyAlpha.d.ts +2 -0
- package/lib/loader.d.ts +1 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +2 -2
- package/lib/loader.js.map +1 -1
- package/lib/loaderLayerCompatState.d.ts +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingLocalStateStore.d.ts.map +1 -1
- package/lib/pendingLocalStateStore.js +9 -3
- package/lib/pendingLocalStateStore.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +2 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +3 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/serializedStateManager.d.ts +16 -1
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +11 -1
- package/lib/serializedStateManager.js.map +1 -1
- package/package.json +13 -13
- package/src/captureReferencedContents.ts +446 -0
- package/src/connectionManager.ts +47 -8
- package/src/connectionStateHandler.ts +14 -9
- package/src/container.ts +18 -4
- package/src/containerStorageAdapter.ts +22 -2
- package/src/createAndLoadContainerUtils.ts +229 -2
- package/src/frozenServices.ts +285 -64
- package/src/index.ts +7 -0
- package/src/loader.ts +4 -2
- package/src/packageVersion.ts +1 -1
- package/src/pendingLocalStateStore.ts +8 -1
- package/src/retriableDocumentStorageService.ts +11 -4
- package/src/serializedStateManager.ts +28 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @fluidframework/container-loader
|
|
2
2
|
|
|
3
|
+
## 2.101.0
|
|
4
|
+
|
|
5
|
+
Dependency updates only.
|
|
6
|
+
|
|
7
|
+
## 2.100.0
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- Node 22 is now the minimum supported Node.js version ([#27116](https://github.com/microsoft/FluidFramework/pull/27116)) [e8214d29663](https://github.com/microsoft/FluidFramework/commit/e8214d29663f5ee98d737daed82506a25d8de8d0)
|
|
12
|
+
|
|
13
|
+
All Fluid Framework client packages now require Node.js 22 or later. This aligns with the standing Node upgrade policy as Node 20 reaches end-of-life on April 30, 2026.
|
|
14
|
+
|
|
3
15
|
## 2.93.0
|
|
4
16
|
|
|
5
17
|
Dependency updates only.
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
// @alpha @legacy
|
|
8
8
|
export function asLegacyAlpha(base: IContainer): ContainerAlpha;
|
|
9
9
|
|
|
10
|
+
// @alpha @legacy
|
|
11
|
+
export function captureFullContainerState(input: ICaptureFullContainerStateProps): Promise<string>;
|
|
12
|
+
|
|
10
13
|
// @public
|
|
11
14
|
export enum ConnectionState {
|
|
12
15
|
CatchingUp = 1,
|
|
@@ -24,7 +27,7 @@ export interface ContainerAlpha extends IContainer {
|
|
|
24
27
|
export function createDetachedContainer(createDetachedContainerProps: ICreateDetachedContainerProps): Promise<IContainer>;
|
|
25
28
|
|
|
26
29
|
// @alpha @legacy
|
|
27
|
-
export function createFrozenDocumentServiceFactory(factory?: IDocumentServiceFactory | Promise<IDocumentServiceFactory
|
|
30
|
+
export function createFrozenDocumentServiceFactory(factory?: IDocumentServiceFactory | Promise<IDocumentServiceFactory>, readOnly?: boolean): IDocumentServiceFactory;
|
|
28
31
|
|
|
29
32
|
// @beta @legacy (undocumented)
|
|
30
33
|
export interface IBaseProtocolHandler {
|
|
@@ -44,6 +47,14 @@ export interface IBaseProtocolHandler {
|
|
|
44
47
|
snapshot(): IQuorumSnapshot;
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
// @alpha @legacy
|
|
51
|
+
export interface ICaptureFullContainerStateProps {
|
|
52
|
+
readonly documentServiceFactory: IDocumentServiceFactory;
|
|
53
|
+
readonly logger?: ITelemetryBaseLogger | undefined;
|
|
54
|
+
readonly request: IRequest;
|
|
55
|
+
readonly urlResolver: IUrlResolver;
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
// @beta @deprecated @legacy (undocumented)
|
|
48
59
|
export interface ICodeDetailsLoader extends Partial<IProvideFluidCodeDetailsComparer> {
|
|
49
60
|
load(source: IFluidCodeDetails): Promise<IFluidModuleWithDetails>;
|
|
@@ -106,6 +117,7 @@ export interface ILoadExistingContainerProps extends ICreateAndLoadContainerProp
|
|
|
106
117
|
// @alpha @legacy
|
|
107
118
|
export interface ILoadFrozenContainerFromPendingStateProps extends ILoadExistingContainerProps {
|
|
108
119
|
readonly pendingLocalState: string;
|
|
120
|
+
readonly readOnly?: boolean;
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
// @alpha @legacy
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { IDocumentStorageService, ISequencedDocumentMessage, ISnapshot, ISnapshotTree } from "@fluidframework/driver-definitions/internal";
|
|
6
|
+
import type { IBase64BlobContents, ISerializableBlobContents } from "./containerStorageAdapter.js";
|
|
7
|
+
/**
|
|
8
|
+
* Wire-format constants this module needs to walk and filter snapshots.
|
|
9
|
+
* Authoritative definitions live in `container-runtime` and
|
|
10
|
+
* `runtime-definitions`; the values are duplicated here to avoid a
|
|
11
|
+
* loader → runtime layering dependency. A contract test in
|
|
12
|
+
* `packages/test/local-server-tests` asserts these match the authoritative
|
|
13
|
+
* values; do not change them in isolation.
|
|
14
|
+
*
|
|
15
|
+
* Authoritative sources:
|
|
16
|
+
* - `blobsTreeName`, `redirectTableBlobName`: `packages/runtime/container-runtime/src/blobManager/blobManagerSnapSum.ts`
|
|
17
|
+
* - `blobManagerBasePath`: `packages/runtime/container-runtime/src/blobManager/blobManager.ts`
|
|
18
|
+
* - `gcTreeKey`, `gcBlobPrefix`, `gcTombstoneBlobKey`, `gcDeletedBlobKey`: `packages/runtime/runtime-definitions/src/garbageCollectionDefinitions.ts`
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare const wireFormatConstants: {
|
|
23
|
+
readonly blobsTreeName: ".blobs";
|
|
24
|
+
readonly redirectTableBlobName: ".redirectTable";
|
|
25
|
+
readonly blobManagerBasePath: "_blobs";
|
|
26
|
+
readonly gcTreeKey: "gc";
|
|
27
|
+
readonly gcBlobPrefix: "__gc";
|
|
28
|
+
readonly gcTombstoneBlobKey: "__tombstones";
|
|
29
|
+
readonly gcDeletedBlobKey: "__deletedNodes";
|
|
30
|
+
};
|
|
31
|
+
interface IGcNodeData {
|
|
32
|
+
outboundRoutes: string[];
|
|
33
|
+
unreferencedTimestampMs?: number;
|
|
34
|
+
}
|
|
35
|
+
interface IGcState {
|
|
36
|
+
gcNodes: {
|
|
37
|
+
[id: string]: IGcNodeData;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The parsed subset of the `gc` subtree that drives reachability decisions.
|
|
42
|
+
*/
|
|
43
|
+
export interface IGcSnapshotData {
|
|
44
|
+
gcState: IGcState | undefined;
|
|
45
|
+
tombstones: string[] | undefined;
|
|
46
|
+
deletedNodes: string[] | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runs `fn` over `items` with at most `limit` promises in flight. Preserves
|
|
50
|
+
* input order on output (not that any caller depends on it today).
|
|
51
|
+
*
|
|
52
|
+
* Exported for unit tests; not part of the package public API.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Parses the `gc` subtree of a base snapshot. Returns `undefined` if the
|
|
59
|
+
* snapshot has no GC tree (GC disabled or pre-GC document).
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseGcSnapshotData(baseSnapshot: ISnapshotTree, storage: Pick<IDocumentStorageService, "readBlob">): Promise<IGcSnapshotData | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* Walks a snapshot and inlines the contents of every blob reachable without
|
|
64
|
+
* crossing an `unreferenced` subtree boundary. Subtrees flagged
|
|
65
|
+
* `unreferenced: true` are skipped entirely — the summarizer sets that flag
|
|
66
|
+
* from GC state, so honouring it filters out dead subtrees without a
|
|
67
|
+
* separate GC-path traversal.
|
|
68
|
+
*
|
|
69
|
+
* The root-level `.blobs` subtree is special-cased: only its `.redirectTable`
|
|
70
|
+
* blob is read, because attachment blob contents are captured separately via
|
|
71
|
+
* {@link captureReferencedAttachmentBlobs}.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readReferencedSnapshotBlobs(snapshot: ISnapshot | ISnapshotTree, storage: Pick<IDocumentStorageService, "readBlob">): Promise<ISerializableBlobContents>;
|
|
74
|
+
/**
|
|
75
|
+
* Fetches attachment blob contents from a snapshot, filtered by GC
|
|
76
|
+
* reachability. Blobs GC has explicitly marked unreferenced, tombstoned, or
|
|
77
|
+
* deleted are skipped. Blobs absent from the GC graph are kept — GC state
|
|
78
|
+
* lags behind recent attachments and dropping them would lose live data.
|
|
79
|
+
* If `gcData` is `undefined`, every attachment blob is returned.
|
|
80
|
+
*
|
|
81
|
+
* The returned map is keyed by attachment blob storage id. Values are the
|
|
82
|
+
* raw bytes encoded as **base64** strings — attachment blobs may carry
|
|
83
|
+
* arbitrary binary payloads (images, encrypted data, etc.) and a
|
|
84
|
+
* UTF-8 round-trip would silently corrupt non-UTF-8 byte sequences with
|
|
85
|
+
* replacement characters. The runtime's own pending-blob serializer uses
|
|
86
|
+
* base64 for the same reason. This diverges from the structural-blob path
|
|
87
|
+
* in {@link readReferencedSnapshotBlobs}, which encodes UTF-8 because those
|
|
88
|
+
* blobs are JSON or other text the runtime authored. Callers must keep the
|
|
89
|
+
* two encodings on separate fields of the pending state so the load side
|
|
90
|
+
* can decode each correctly.
|
|
91
|
+
*/
|
|
92
|
+
export declare function captureReferencedAttachmentBlobs(baseSnapshot: ISnapshotTree, storage: Pick<IDocumentStorageService, "readBlob">, gcData: IGcSnapshotData | undefined): Promise<IBase64BlobContents>;
|
|
93
|
+
/**
|
|
94
|
+
* A blob reference extracted from a `BlobAttach` op. `localId` is the
|
|
95
|
+
* `BlobManager` GC identity for the blob; `storageId` is the id used for
|
|
96
|
+
* `IDocumentStorageService.readBlob`.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
export interface IBlobAttachReference {
|
|
101
|
+
readonly localId: string;
|
|
102
|
+
readonly storageId: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Extracts every `BlobAttach` reference an op carries. Returns an empty array
|
|
106
|
+
* for non-blobAttach ops.
|
|
107
|
+
*
|
|
108
|
+
* This is the single place in the loader that interprets the BlobAttach
|
|
109
|
+
* wire format. Capture and load-side reasoning about ops should call into
|
|
110
|
+
* this function rather than reading `op.metadata` directly, so a future
|
|
111
|
+
* protocol change touches one site.
|
|
112
|
+
*
|
|
113
|
+
* BlobAttach ops carry `(localId, storageId)` directly on
|
|
114
|
+
* `ISequencedDocumentMessage.metadata` and are not grouped — the container
|
|
115
|
+
* runtime routes them through a separate `outbox.submitBlobAttach` lane,
|
|
116
|
+
* and `OpGroupingManager.groupBatch` asserts (0x5dd) that no op carrying
|
|
117
|
+
* non-batch metadata enters a grouped batch. If either guarantee changes,
|
|
118
|
+
* extend this function rather than each call site.
|
|
119
|
+
*
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
export declare function extractBlobAttachReferences(op: Pick<ISequencedDocumentMessage, "metadata">): IBlobAttachReference[];
|
|
123
|
+
/**
|
|
124
|
+
* Set of attachment-blob localIds that GC has marked unreferenced,
|
|
125
|
+
* tombstoned, or deleted in the base snapshot. `undefined` if `gcData`
|
|
126
|
+
* is `undefined` (GC disabled / pre-GC document).
|
|
127
|
+
*
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
export declare function unreferencedAttachmentBlobLocalIds(gcData: IGcSnapshotData | undefined): Set<string> | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Inline attachment blob contents for the given `(localId, storageId)`
|
|
133
|
+
* references. Skips entries already present in `existing` (de-dupe with
|
|
134
|
+
* the snapshot path) and entries whose `localId` is in
|
|
135
|
+
* `unreferencedLocalIds`. Returns only the freshly-read entries; the
|
|
136
|
+
* caller merges them into the existing map.
|
|
137
|
+
*
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
export declare function inlineAttachmentBlobsByReference(references: readonly IBlobAttachReference[], storage: Pick<IDocumentStorageService, "readBlob">, unreferencedLocalIds: ReadonlySet<string> | undefined, existing: Readonly<IBase64BlobContents>): Promise<IBase64BlobContents>;
|
|
141
|
+
/**
|
|
142
|
+
* Returns true if any referenced subtree of `baseSnapshot` declares a
|
|
143
|
+
* `groupId` — the snapshot-tree wire field that carries the runtime's
|
|
144
|
+
* loading-group identifier. Subtrees flagged `unreferenced` are skipped —
|
|
145
|
+
* a dead subtree's `groupId` would not be loaded by the runtime either.
|
|
146
|
+
*
|
|
147
|
+
* `captureFullContainerState` does not yet support loading groups: prefetching
|
|
148
|
+
* per-group snapshots adds a code path that has no end-to-end coverage and no
|
|
149
|
+
* known production consumer. Callers use this to fail fast with a `UsageError`
|
|
150
|
+
* rather than silently producing a pending state that omits group data.
|
|
151
|
+
*/
|
|
152
|
+
export declare function snapshotHasLoadingGroups(baseSnapshot: ISnapshotTree): boolean;
|
|
153
|
+
export {};
|
|
154
|
+
//# sourceMappingURL=captureReferencedContents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureReferencedContents.d.ts","sourceRoot":"","sources":["../src/captureReferencedContents.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACX,uBAAuB,EACvB,yBAAyB,EACzB,SAAS,EACT,aAAa,EACb,MAAM,6CAA6C,CAAC;AAGrD,OAAO,KAAK,EACX,mBAAmB,EACnB,yBAAyB,EACzB,MAAM,8BAA8B,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;CAQtB,CAAC;AAYX,UAAU,WAAW;IACpB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uBAAuB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,UAAU,QAAQ;IACjB,OAAO,EAAE;QAAE,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACjC,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACnC;AAaD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAC5C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,EAAE,CAAC,CAed;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,YAAY,EAAE,aAAa,EAC3B,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAChD,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAyBtC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAChD,QAAQ,EAAE,SAAS,GAAG,aAAa,EACnC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAChD,OAAO,CAAC,yBAAyB,CAAC,CAUpC;AA8CD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gCAAgC,CACrD,YAAY,EAAE,aAAa,EAC3B,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,EAClD,MAAM,EAAE,eAAe,GAAG,SAAS,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA0B9B;AAwDD;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CAC1C,EAAE,EAAE,IAAI,CAAC,yBAAyB,EAAE,UAAU,CAAC,GAC7C,oBAAoB,EAAE,CAKxB;AAED;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CACjD,MAAM,EAAE,eAAe,GAAG,SAAS,GACjC,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAEzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gCAAgC,CACrD,UAAU,EAAE,SAAS,oBAAoB,EAAE,EAC3C,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,EAClD,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,EACrD,QAAQ,EAAE,QAAQ,CAAC,mBAAmB,CAAC,GACrC,OAAO,CAAC,mBAAmB,CAAC,CAoB9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,aAAa,GAAG,OAAO,CAa7E"}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.snapshotHasLoadingGroups = exports.inlineAttachmentBlobsByReference = exports.unreferencedAttachmentBlobLocalIds = exports.extractBlobAttachReferences = exports.captureReferencedAttachmentBlobs = exports.readReferencedSnapshotBlobs = exports.parseGcSnapshotData = exports.mapWithConcurrency = exports.wireFormatConstants = void 0;
|
|
8
|
+
const client_utils_1 = require("@fluid-internal/client-utils");
|
|
9
|
+
const internal_1 = require("@fluidframework/driver-utils/internal");
|
|
10
|
+
/**
|
|
11
|
+
* Wire-format constants this module needs to walk and filter snapshots.
|
|
12
|
+
* Authoritative definitions live in `container-runtime` and
|
|
13
|
+
* `runtime-definitions`; the values are duplicated here to avoid a
|
|
14
|
+
* loader → runtime layering dependency. A contract test in
|
|
15
|
+
* `packages/test/local-server-tests` asserts these match the authoritative
|
|
16
|
+
* values; do not change them in isolation.
|
|
17
|
+
*
|
|
18
|
+
* Authoritative sources:
|
|
19
|
+
* - `blobsTreeName`, `redirectTableBlobName`: `packages/runtime/container-runtime/src/blobManager/blobManagerSnapSum.ts`
|
|
20
|
+
* - `blobManagerBasePath`: `packages/runtime/container-runtime/src/blobManager/blobManager.ts`
|
|
21
|
+
* - `gcTreeKey`, `gcBlobPrefix`, `gcTombstoneBlobKey`, `gcDeletedBlobKey`: `packages/runtime/runtime-definitions/src/garbageCollectionDefinitions.ts`
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
exports.wireFormatConstants = {
|
|
26
|
+
blobsTreeName: ".blobs",
|
|
27
|
+
redirectTableBlobName: ".redirectTable",
|
|
28
|
+
blobManagerBasePath: "_blobs",
|
|
29
|
+
gcTreeKey: "gc",
|
|
30
|
+
gcBlobPrefix: "__gc",
|
|
31
|
+
gcTombstoneBlobKey: "__tombstones",
|
|
32
|
+
gcDeletedBlobKey: "__deletedNodes",
|
|
33
|
+
};
|
|
34
|
+
const { blobsTreeName, redirectTableBlobName, blobManagerBasePath, gcTreeKey, gcBlobPrefix, gcTombstoneBlobKey, gcDeletedBlobKey, } = exports.wireFormatConstants;
|
|
35
|
+
/**
|
|
36
|
+
* Upper bound on concurrent `readBlob` calls. Driver/service back-pressure is
|
|
37
|
+
* real for large documents, and unbounded `Promise.all` can trigger throttling
|
|
38
|
+
* or spike memory. The value is a pragmatic middle ground — high enough to
|
|
39
|
+
* keep a typical driver's request pipeline full, low enough to avoid storms.
|
|
40
|
+
*/
|
|
41
|
+
const maxReadConcurrency = 32;
|
|
42
|
+
/**
|
|
43
|
+
* Runs `fn` over `items` with at most `limit` promises in flight. Preserves
|
|
44
|
+
* input order on output (not that any caller depends on it today).
|
|
45
|
+
*
|
|
46
|
+
* Exported for unit tests; not part of the package public API.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
51
|
+
const results = Array.from({ length: items.length });
|
|
52
|
+
let cursor = 0;
|
|
53
|
+
const workerCount = Math.min(limit, items.length);
|
|
54
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
55
|
+
while (cursor < items.length) {
|
|
56
|
+
const index = cursor++;
|
|
57
|
+
const item = items[index];
|
|
58
|
+
if (item !== undefined) {
|
|
59
|
+
results[index] = await fn(item);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
await Promise.all(workers);
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
exports.mapWithConcurrency = mapWithConcurrency;
|
|
67
|
+
/**
|
|
68
|
+
* Parses the `gc` subtree of a base snapshot. Returns `undefined` if the
|
|
69
|
+
* snapshot has no GC tree (GC disabled or pre-GC document).
|
|
70
|
+
*/
|
|
71
|
+
async function parseGcSnapshotData(baseSnapshot, storage) {
|
|
72
|
+
const gcSnapshotTree = baseSnapshot.trees[gcTreeKey];
|
|
73
|
+
if (gcSnapshotTree === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
let gcState;
|
|
77
|
+
let tombstones;
|
|
78
|
+
let deletedNodes;
|
|
79
|
+
for (const [key, blobId] of Object.entries(gcSnapshotTree.blobs)) {
|
|
80
|
+
if (key === gcDeletedBlobKey) {
|
|
81
|
+
deletedNodes = await (0, internal_1.readAndParse)(storage, blobId);
|
|
82
|
+
}
|
|
83
|
+
else if (key === gcTombstoneBlobKey) {
|
|
84
|
+
tombstones = await (0, internal_1.readAndParse)(storage, blobId);
|
|
85
|
+
}
|
|
86
|
+
else if (key.startsWith(gcBlobPrefix)) {
|
|
87
|
+
const partial = await (0, internal_1.readAndParse)(storage, blobId);
|
|
88
|
+
if (gcState === undefined) {
|
|
89
|
+
gcState = { gcNodes: { ...partial.gcNodes } };
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
for (const [nodeId, nodeData] of Object.entries(partial.gcNodes)) {
|
|
93
|
+
gcState.gcNodes[nodeId] ??= nodeData;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { gcState, tombstones, deletedNodes };
|
|
99
|
+
}
|
|
100
|
+
exports.parseGcSnapshotData = parseGcSnapshotData;
|
|
101
|
+
/**
|
|
102
|
+
* Walks a snapshot and inlines the contents of every blob reachable without
|
|
103
|
+
* crossing an `unreferenced` subtree boundary. Subtrees flagged
|
|
104
|
+
* `unreferenced: true` are skipped entirely — the summarizer sets that flag
|
|
105
|
+
* from GC state, so honouring it filters out dead subtrees without a
|
|
106
|
+
* separate GC-path traversal.
|
|
107
|
+
*
|
|
108
|
+
* The root-level `.blobs` subtree is special-cased: only its `.redirectTable`
|
|
109
|
+
* blob is read, because attachment blob contents are captured separately via
|
|
110
|
+
* {@link captureReferencedAttachmentBlobs}.
|
|
111
|
+
*/
|
|
112
|
+
async function readReferencedSnapshotBlobs(snapshot, storage) {
|
|
113
|
+
const { tree, read } = toTreeAndReader(snapshot, storage);
|
|
114
|
+
const ids = new Set();
|
|
115
|
+
collectReferencedBlobIds(tree, true, ids);
|
|
116
|
+
const blobs = {};
|
|
117
|
+
await mapWithConcurrency([...ids], maxReadConcurrency, async (id) => {
|
|
118
|
+
const data = await read(id);
|
|
119
|
+
blobs[id] = (0, client_utils_1.bufferToString)(data, "utf8");
|
|
120
|
+
});
|
|
121
|
+
return blobs;
|
|
122
|
+
}
|
|
123
|
+
exports.readReferencedSnapshotBlobs = readReferencedSnapshotBlobs;
|
|
124
|
+
/**
|
|
125
|
+
* Synchronously walks the snapshot tree and gathers the set of blob ids that
|
|
126
|
+
* should be inlined. Subtrees flagged `unreferenced: true` are skipped
|
|
127
|
+
* entirely. The root-level `.blobs` subtree is special-cased: only its
|
|
128
|
+
* `.redirectTable` id is collected, because attachment blob contents are
|
|
129
|
+
* captured separately via {@link captureReferencedAttachmentBlobs}.
|
|
130
|
+
*/
|
|
131
|
+
function collectReferencedBlobIds(tree, isRoot, ids) {
|
|
132
|
+
if (tree.unreferenced === true) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const blobId of Object.values(tree.blobs)) {
|
|
136
|
+
ids.add(blobId);
|
|
137
|
+
}
|
|
138
|
+
for (const [key, subTree] of Object.entries(tree.trees)) {
|
|
139
|
+
if (isRoot && key === blobsTreeName) {
|
|
140
|
+
const tableBlobId = subTree.blobs[redirectTableBlobName];
|
|
141
|
+
if (tableBlobId !== undefined) {
|
|
142
|
+
ids.add(tableBlobId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
collectReferencedBlobIds(subTree, false, ids);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function toTreeAndReader(snapshot, storage) {
|
|
151
|
+
if ("snapshotTree" in snapshot) {
|
|
152
|
+
const blobContents = snapshot.blobContents;
|
|
153
|
+
return {
|
|
154
|
+
tree: snapshot.snapshotTree,
|
|
155
|
+
read: async (id) => blobContents.get(id) ?? storage.readBlob(id),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { tree: snapshot, read: async (id) => storage.readBlob(id) };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Fetches attachment blob contents from a snapshot, filtered by GC
|
|
162
|
+
* reachability. Blobs GC has explicitly marked unreferenced, tombstoned, or
|
|
163
|
+
* deleted are skipped. Blobs absent from the GC graph are kept — GC state
|
|
164
|
+
* lags behind recent attachments and dropping them would lose live data.
|
|
165
|
+
* If `gcData` is `undefined`, every attachment blob is returned.
|
|
166
|
+
*
|
|
167
|
+
* The returned map is keyed by attachment blob storage id. Values are the
|
|
168
|
+
* raw bytes encoded as **base64** strings — attachment blobs may carry
|
|
169
|
+
* arbitrary binary payloads (images, encrypted data, etc.) and a
|
|
170
|
+
* UTF-8 round-trip would silently corrupt non-UTF-8 byte sequences with
|
|
171
|
+
* replacement characters. The runtime's own pending-blob serializer uses
|
|
172
|
+
* base64 for the same reason. This diverges from the structural-blob path
|
|
173
|
+
* in {@link readReferencedSnapshotBlobs}, which encodes UTF-8 because those
|
|
174
|
+
* blobs are JSON or other text the runtime authored. Callers must keep the
|
|
175
|
+
* two encodings on separate fields of the pending state so the load side
|
|
176
|
+
* can decode each correctly.
|
|
177
|
+
*/
|
|
178
|
+
async function captureReferencedAttachmentBlobs(baseSnapshot, storage, gcData) {
|
|
179
|
+
const blobsTree = baseSnapshot.trees[blobsTreeName];
|
|
180
|
+
if (blobsTree === undefined) {
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
const localIdToStorageId = await readRedirectTable(blobsTree, storage);
|
|
184
|
+
if (localIdToStorageId.size === 0) {
|
|
185
|
+
return {};
|
|
186
|
+
}
|
|
187
|
+
const unreferencedLocalIds = gcData === undefined ? undefined : collectUnreferencedBlobLocalIds(gcData);
|
|
188
|
+
const storageIdsToFetch = new Set();
|
|
189
|
+
for (const [localId, storageId] of localIdToStorageId) {
|
|
190
|
+
if (unreferencedLocalIds?.has(localId) !== true) {
|
|
191
|
+
storageIdsToFetch.add(storageId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const contents = {};
|
|
195
|
+
await mapWithConcurrency([...storageIdsToFetch], maxReadConcurrency, async (storageId) => {
|
|
196
|
+
const buffer = await storage.readBlob(storageId);
|
|
197
|
+
contents[storageId] = (0, client_utils_1.bufferToString)(buffer, "base64");
|
|
198
|
+
});
|
|
199
|
+
return contents;
|
|
200
|
+
}
|
|
201
|
+
exports.captureReferencedAttachmentBlobs = captureReferencedAttachmentBlobs;
|
|
202
|
+
/**
|
|
203
|
+
* Reconstructs the BlobManager's redirect table from a `.blobs` subtree.
|
|
204
|
+
* Mirrors `toRedirectTable` in blobManagerSnapSum.ts.
|
|
205
|
+
*/
|
|
206
|
+
async function readRedirectTable(blobsTree, storage) {
|
|
207
|
+
const redirectTable = new Map();
|
|
208
|
+
const tableBlobId = blobsTree.blobs[redirectTableBlobName];
|
|
209
|
+
if (tableBlobId !== undefined) {
|
|
210
|
+
const entries = await (0, internal_1.readAndParse)(storage, tableBlobId);
|
|
211
|
+
for (const [localId, storageId] of entries) {
|
|
212
|
+
redirectTable.set(localId, storageId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const [key, storageId] of Object.entries(blobsTree.blobs)) {
|
|
216
|
+
if (key !== redirectTableBlobName) {
|
|
217
|
+
// Identity mapping: storage ids referenced directly in handles (legacy).
|
|
218
|
+
redirectTable.set(storageId, storageId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return redirectTable;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Collects the set of blob localIds that GC has explicitly marked as
|
|
225
|
+
* unreferenced (via `unreferencedTimestampMs` on a gc node), tombstoned, or
|
|
226
|
+
* deleted. Tombstones and deletedNodes are applied regardless of whether
|
|
227
|
+
* `gcState` is present — they are authoritative on their own and must not
|
|
228
|
+
* be silently dropped when gc state is absent but tombstone/deleted lists
|
|
229
|
+
* exist.
|
|
230
|
+
*/
|
|
231
|
+
function collectUnreferencedBlobLocalIds(gcData) {
|
|
232
|
+
const blobPathPrefix = `/${blobManagerBasePath}/`;
|
|
233
|
+
const unreferenced = new Set();
|
|
234
|
+
if (gcData.gcState !== undefined) {
|
|
235
|
+
for (const [nodePath, nodeData] of Object.entries(gcData.gcState.gcNodes)) {
|
|
236
|
+
if (nodePath.startsWith(blobPathPrefix) &&
|
|
237
|
+
nodeData.unreferencedTimestampMs !== undefined) {
|
|
238
|
+
unreferenced.add(nodePath.slice(blobPathPrefix.length));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const nodePath of [...(gcData.tombstones ?? []), ...(gcData.deletedNodes ?? [])]) {
|
|
243
|
+
if (nodePath.startsWith(blobPathPrefix)) {
|
|
244
|
+
unreferenced.add(nodePath.slice(blobPathPrefix.length));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return unreferenced;
|
|
248
|
+
}
|
|
249
|
+
function isBlobAttachLikeMetadata(metadata) {
|
|
250
|
+
if (typeof metadata !== "object" || metadata === null) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const candidate = metadata;
|
|
254
|
+
return typeof candidate.localId === "string" && typeof candidate.blobId === "string";
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Extracts every `BlobAttach` reference an op carries. Returns an empty array
|
|
258
|
+
* for non-blobAttach ops.
|
|
259
|
+
*
|
|
260
|
+
* This is the single place in the loader that interprets the BlobAttach
|
|
261
|
+
* wire format. Capture and load-side reasoning about ops should call into
|
|
262
|
+
* this function rather than reading `op.metadata` directly, so a future
|
|
263
|
+
* protocol change touches one site.
|
|
264
|
+
*
|
|
265
|
+
* BlobAttach ops carry `(localId, storageId)` directly on
|
|
266
|
+
* `ISequencedDocumentMessage.metadata` and are not grouped — the container
|
|
267
|
+
* runtime routes them through a separate `outbox.submitBlobAttach` lane,
|
|
268
|
+
* and `OpGroupingManager.groupBatch` asserts (0x5dd) that no op carrying
|
|
269
|
+
* non-batch metadata enters a grouped batch. If either guarantee changes,
|
|
270
|
+
* extend this function rather than each call site.
|
|
271
|
+
*
|
|
272
|
+
* @internal
|
|
273
|
+
*/
|
|
274
|
+
function extractBlobAttachReferences(op) {
|
|
275
|
+
if (!isBlobAttachLikeMetadata(op.metadata)) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
return [{ localId: op.metadata.localId, storageId: op.metadata.blobId }];
|
|
279
|
+
}
|
|
280
|
+
exports.extractBlobAttachReferences = extractBlobAttachReferences;
|
|
281
|
+
/**
|
|
282
|
+
* Set of attachment-blob localIds that GC has marked unreferenced,
|
|
283
|
+
* tombstoned, or deleted in the base snapshot. `undefined` if `gcData`
|
|
284
|
+
* is `undefined` (GC disabled / pre-GC document).
|
|
285
|
+
*
|
|
286
|
+
* @internal
|
|
287
|
+
*/
|
|
288
|
+
function unreferencedAttachmentBlobLocalIds(gcData) {
|
|
289
|
+
return gcData === undefined ? undefined : collectUnreferencedBlobLocalIds(gcData);
|
|
290
|
+
}
|
|
291
|
+
exports.unreferencedAttachmentBlobLocalIds = unreferencedAttachmentBlobLocalIds;
|
|
292
|
+
/**
|
|
293
|
+
* Inline attachment blob contents for the given `(localId, storageId)`
|
|
294
|
+
* references. Skips entries already present in `existing` (de-dupe with
|
|
295
|
+
* the snapshot path) and entries whose `localId` is in
|
|
296
|
+
* `unreferencedLocalIds`. Returns only the freshly-read entries; the
|
|
297
|
+
* caller merges them into the existing map.
|
|
298
|
+
*
|
|
299
|
+
* @internal
|
|
300
|
+
*/
|
|
301
|
+
async function inlineAttachmentBlobsByReference(references, storage, unreferencedLocalIds, existing) {
|
|
302
|
+
const storageIdsToFetch = new Set();
|
|
303
|
+
for (const { localId, storageId } of references) {
|
|
304
|
+
if (unreferencedLocalIds?.has(localId) === true) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (existing[storageId] !== undefined) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
storageIdsToFetch.add(storageId);
|
|
311
|
+
}
|
|
312
|
+
const added = {};
|
|
313
|
+
if (storageIdsToFetch.size === 0) {
|
|
314
|
+
return added;
|
|
315
|
+
}
|
|
316
|
+
await mapWithConcurrency([...storageIdsToFetch], maxReadConcurrency, async (storageId) => {
|
|
317
|
+
const buffer = await storage.readBlob(storageId);
|
|
318
|
+
added[storageId] = (0, client_utils_1.bufferToString)(buffer, "base64");
|
|
319
|
+
});
|
|
320
|
+
return added;
|
|
321
|
+
}
|
|
322
|
+
exports.inlineAttachmentBlobsByReference = inlineAttachmentBlobsByReference;
|
|
323
|
+
/**
|
|
324
|
+
* Returns true if any referenced subtree of `baseSnapshot` declares a
|
|
325
|
+
* `groupId` — the snapshot-tree wire field that carries the runtime's
|
|
326
|
+
* loading-group identifier. Subtrees flagged `unreferenced` are skipped —
|
|
327
|
+
* a dead subtree's `groupId` would not be loaded by the runtime either.
|
|
328
|
+
*
|
|
329
|
+
* `captureFullContainerState` does not yet support loading groups: prefetching
|
|
330
|
+
* per-group snapshots adds a code path that has no end-to-end coverage and no
|
|
331
|
+
* known production consumer. Callers use this to fail fast with a `UsageError`
|
|
332
|
+
* rather than silently producing a pending state that omits group data.
|
|
333
|
+
*/
|
|
334
|
+
function snapshotHasLoadingGroups(baseSnapshot) {
|
|
335
|
+
if (baseSnapshot.unreferenced === true) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
if (baseSnapshot.groupId !== undefined) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
for (const child of Object.values(baseSnapshot.trees)) {
|
|
342
|
+
if (snapshotHasLoadingGroups(child)) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
exports.snapshotHasLoadingGroups = snapshotHasLoadingGroups;
|
|
349
|
+
//# sourceMappingURL=captureReferencedContents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureReferencedContents.js","sourceRoot":"","sources":["../src/captureReferencedContents.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { bufferToString } from \"@fluid-internal/client-utils\";\nimport type {\n\tIDocumentStorageService,\n\tISequencedDocumentMessage,\n\tISnapshot,\n\tISnapshotTree,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { readAndParse } from \"@fluidframework/driver-utils/internal\";\n\nimport type {\n\tIBase64BlobContents,\n\tISerializableBlobContents,\n} from \"./containerStorageAdapter.js\";\n\n/**\n * Wire-format constants this module needs to walk and filter snapshots.\n * Authoritative definitions live in `container-runtime` and\n * `runtime-definitions`; the values are duplicated here to avoid a\n * loader → runtime layering dependency. A contract test in\n * `packages/test/local-server-tests` asserts these match the authoritative\n * values; do not change them in isolation.\n *\n * Authoritative sources:\n * - `blobsTreeName`, `redirectTableBlobName`: `packages/runtime/container-runtime/src/blobManager/blobManagerSnapSum.ts`\n * - `blobManagerBasePath`: `packages/runtime/container-runtime/src/blobManager/blobManager.ts`\n * - `gcTreeKey`, `gcBlobPrefix`, `gcTombstoneBlobKey`, `gcDeletedBlobKey`: `packages/runtime/runtime-definitions/src/garbageCollectionDefinitions.ts`\n *\n * @internal\n */\nexport const wireFormatConstants = {\n\tblobsTreeName: \".blobs\",\n\tredirectTableBlobName: \".redirectTable\",\n\tblobManagerBasePath: \"_blobs\",\n\tgcTreeKey: \"gc\",\n\tgcBlobPrefix: \"__gc\",\n\tgcTombstoneBlobKey: \"__tombstones\",\n\tgcDeletedBlobKey: \"__deletedNodes\",\n} as const;\n\nconst {\n\tblobsTreeName,\n\tredirectTableBlobName,\n\tblobManagerBasePath,\n\tgcTreeKey,\n\tgcBlobPrefix,\n\tgcTombstoneBlobKey,\n\tgcDeletedBlobKey,\n} = wireFormatConstants;\n\ninterface IGcNodeData {\n\toutboundRoutes: string[];\n\tunreferencedTimestampMs?: number;\n}\n\ninterface IGcState {\n\tgcNodes: { [id: string]: IGcNodeData };\n}\n\n/**\n * The parsed subset of the `gc` subtree that drives reachability decisions.\n */\nexport interface IGcSnapshotData {\n\tgcState: IGcState | undefined;\n\ttombstones: string[] | undefined;\n\tdeletedNodes: string[] | undefined;\n}\n\n/** Reader that returns a blob's contents for a given storage id. */\ntype BlobReader = (id: string) => Promise<ArrayBufferLike>;\n\n/**\n * Upper bound on concurrent `readBlob` calls. Driver/service back-pressure is\n * real for large documents, and unbounded `Promise.all` can trigger throttling\n * or spike memory. The value is a pragmatic middle ground — high enough to\n * keep a typical driver's request pipeline full, low enough to avoid storms.\n */\nconst maxReadConcurrency = 32;\n\n/**\n * Runs `fn` over `items` with at most `limit` promises in flight. Preserves\n * input order on output (not that any caller depends on it today).\n *\n * Exported for unit tests; not part of the package public API.\n *\n * @internal\n */\nexport async function mapWithConcurrency<T, R>(\n\titems: readonly T[],\n\tlimit: number,\n\tfn: (item: T) => Promise<R>,\n): Promise<R[]> {\n\tconst results: R[] = Array.from({ length: items.length });\n\tlet cursor = 0;\n\tconst workerCount = Math.min(limit, items.length);\n\tconst workers = Array.from({ length: workerCount }, async () => {\n\t\twhile (cursor < items.length) {\n\t\t\tconst index = cursor++;\n\t\t\tconst item = items[index];\n\t\t\tif (item !== undefined) {\n\t\t\t\tresults[index] = await fn(item);\n\t\t\t}\n\t\t}\n\t});\n\tawait Promise.all(workers);\n\treturn results;\n}\n\n/**\n * Parses the `gc` subtree of a base snapshot. Returns `undefined` if the\n * snapshot has no GC tree (GC disabled or pre-GC document).\n */\nexport async function parseGcSnapshotData(\n\tbaseSnapshot: ISnapshotTree,\n\tstorage: Pick<IDocumentStorageService, \"readBlob\">,\n): Promise<IGcSnapshotData | undefined> {\n\tconst gcSnapshotTree: ISnapshotTree | undefined = baseSnapshot.trees[gcTreeKey];\n\tif (gcSnapshotTree === undefined) {\n\t\treturn undefined;\n\t}\n\tlet gcState: IGcState | undefined;\n\tlet tombstones: string[] | undefined;\n\tlet deletedNodes: string[] | undefined;\n\tfor (const [key, blobId] of Object.entries(gcSnapshotTree.blobs)) {\n\t\tif (key === gcDeletedBlobKey) {\n\t\t\tdeletedNodes = await readAndParse<string[]>(storage, blobId);\n\t\t} else if (key === gcTombstoneBlobKey) {\n\t\t\ttombstones = await readAndParse<string[]>(storage, blobId);\n\t\t} else if (key.startsWith(gcBlobPrefix)) {\n\t\t\tconst partial = await readAndParse<IGcState>(storage, blobId);\n\t\t\tif (gcState === undefined) {\n\t\t\t\tgcState = { gcNodes: { ...partial.gcNodes } };\n\t\t\t} else {\n\t\t\t\tfor (const [nodeId, nodeData] of Object.entries(partial.gcNodes)) {\n\t\t\t\t\tgcState.gcNodes[nodeId] ??= nodeData;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn { gcState, tombstones, deletedNodes };\n}\n\n/**\n * Walks a snapshot and inlines the contents of every blob reachable without\n * crossing an `unreferenced` subtree boundary. Subtrees flagged\n * `unreferenced: true` are skipped entirely — the summarizer sets that flag\n * from GC state, so honouring it filters out dead subtrees without a\n * separate GC-path traversal.\n *\n * The root-level `.blobs` subtree is special-cased: only its `.redirectTable`\n * blob is read, because attachment blob contents are captured separately via\n * {@link captureReferencedAttachmentBlobs}.\n */\nexport async function readReferencedSnapshotBlobs(\n\tsnapshot: ISnapshot | ISnapshotTree,\n\tstorage: Pick<IDocumentStorageService, \"readBlob\">,\n): Promise<ISerializableBlobContents> {\n\tconst { tree, read } = toTreeAndReader(snapshot, storage);\n\tconst ids = new Set<string>();\n\tcollectReferencedBlobIds(tree, true, ids);\n\tconst blobs: ISerializableBlobContents = {};\n\tawait mapWithConcurrency([...ids], maxReadConcurrency, async (id) => {\n\t\tconst data = await read(id);\n\t\tblobs[id] = bufferToString(data, \"utf8\");\n\t});\n\treturn blobs;\n}\n\n/**\n * Synchronously walks the snapshot tree and gathers the set of blob ids that\n * should be inlined. Subtrees flagged `unreferenced: true` are skipped\n * entirely. The root-level `.blobs` subtree is special-cased: only its\n * `.redirectTable` id is collected, because attachment blob contents are\n * captured separately via {@link captureReferencedAttachmentBlobs}.\n */\nfunction collectReferencedBlobIds(\n\ttree: ISnapshotTree,\n\tisRoot: boolean,\n\tids: Set<string>,\n): void {\n\tif (tree.unreferenced === true) {\n\t\treturn;\n\t}\n\tfor (const blobId of Object.values(tree.blobs)) {\n\t\tids.add(blobId);\n\t}\n\tfor (const [key, subTree] of Object.entries(tree.trees)) {\n\t\tif (isRoot && key === blobsTreeName) {\n\t\t\tconst tableBlobId = subTree.blobs[redirectTableBlobName];\n\t\t\tif (tableBlobId !== undefined) {\n\t\t\t\tids.add(tableBlobId);\n\t\t\t}\n\t\t} else {\n\t\t\tcollectReferencedBlobIds(subTree, false, ids);\n\t\t}\n\t}\n}\n\nfunction toTreeAndReader(\n\tsnapshot: ISnapshot | ISnapshotTree,\n\tstorage: Pick<IDocumentStorageService, \"readBlob\">,\n): { tree: ISnapshotTree; read: BlobReader } {\n\tif (\"snapshotTree\" in snapshot) {\n\t\tconst blobContents = snapshot.blobContents;\n\t\treturn {\n\t\t\ttree: snapshot.snapshotTree,\n\t\t\tread: async (id) => blobContents.get(id) ?? storage.readBlob(id),\n\t\t};\n\t}\n\treturn { tree: snapshot, read: async (id) => storage.readBlob(id) };\n}\n\n/**\n * Fetches attachment blob contents from a snapshot, filtered by GC\n * reachability. Blobs GC has explicitly marked unreferenced, tombstoned, or\n * deleted are skipped. Blobs absent from the GC graph are kept — GC state\n * lags behind recent attachments and dropping them would lose live data.\n * If `gcData` is `undefined`, every attachment blob is returned.\n *\n * The returned map is keyed by attachment blob storage id. Values are the\n * raw bytes encoded as **base64** strings — attachment blobs may carry\n * arbitrary binary payloads (images, encrypted data, etc.) and a\n * UTF-8 round-trip would silently corrupt non-UTF-8 byte sequences with\n * replacement characters. The runtime's own pending-blob serializer uses\n * base64 for the same reason. This diverges from the structural-blob path\n * in {@link readReferencedSnapshotBlobs}, which encodes UTF-8 because those\n * blobs are JSON or other text the runtime authored. Callers must keep the\n * two encodings on separate fields of the pending state so the load side\n * can decode each correctly.\n */\nexport async function captureReferencedAttachmentBlobs(\n\tbaseSnapshot: ISnapshotTree,\n\tstorage: Pick<IDocumentStorageService, \"readBlob\">,\n\tgcData: IGcSnapshotData | undefined,\n): Promise<IBase64BlobContents> {\n\tconst blobsTree: ISnapshotTree | undefined = baseSnapshot.trees[blobsTreeName];\n\tif (blobsTree === undefined) {\n\t\treturn {};\n\t}\n\tconst localIdToStorageId = await readRedirectTable(blobsTree, storage);\n\tif (localIdToStorageId.size === 0) {\n\t\treturn {};\n\t}\n\n\tconst unreferencedLocalIds =\n\t\tgcData === undefined ? undefined : collectUnreferencedBlobLocalIds(gcData);\n\n\tconst storageIdsToFetch = new Set<string>();\n\tfor (const [localId, storageId] of localIdToStorageId) {\n\t\tif (unreferencedLocalIds?.has(localId) !== true) {\n\t\t\tstorageIdsToFetch.add(storageId);\n\t\t}\n\t}\n\n\tconst contents: IBase64BlobContents = {};\n\tawait mapWithConcurrency([...storageIdsToFetch], maxReadConcurrency, async (storageId) => {\n\t\tconst buffer = await storage.readBlob(storageId);\n\t\tcontents[storageId] = bufferToString(buffer, \"base64\");\n\t});\n\treturn contents;\n}\n\n/**\n * Reconstructs the BlobManager's redirect table from a `.blobs` subtree.\n * Mirrors `toRedirectTable` in blobManagerSnapSum.ts.\n */\nasync function readRedirectTable(\n\tblobsTree: ISnapshotTree,\n\tstorage: Pick<IDocumentStorageService, \"readBlob\">,\n): Promise<Map<string, string>> {\n\tconst redirectTable = new Map<string, string>();\n\tconst tableBlobId: string | undefined = blobsTree.blobs[redirectTableBlobName];\n\tif (tableBlobId !== undefined) {\n\t\tconst entries = await readAndParse<[string, string][]>(storage, tableBlobId);\n\t\tfor (const [localId, storageId] of entries) {\n\t\t\tredirectTable.set(localId, storageId);\n\t\t}\n\t}\n\tfor (const [key, storageId] of Object.entries(blobsTree.blobs)) {\n\t\tif (key !== redirectTableBlobName) {\n\t\t\t// Identity mapping: storage ids referenced directly in handles (legacy).\n\t\t\tredirectTable.set(storageId, storageId);\n\t\t}\n\t}\n\treturn redirectTable;\n}\n\n/**\n * Collects the set of blob localIds that GC has explicitly marked as\n * unreferenced (via `unreferencedTimestampMs` on a gc node), tombstoned, or\n * deleted. Tombstones and deletedNodes are applied regardless of whether\n * `gcState` is present — they are authoritative on their own and must not\n * be silently dropped when gc state is absent but tombstone/deleted lists\n * exist.\n */\nfunction collectUnreferencedBlobLocalIds(gcData: IGcSnapshotData): Set<string> {\n\tconst blobPathPrefix = `/${blobManagerBasePath}/`;\n\tconst unreferenced = new Set<string>();\n\tif (gcData.gcState !== undefined) {\n\t\tfor (const [nodePath, nodeData] of Object.entries(gcData.gcState.gcNodes)) {\n\t\t\tif (\n\t\t\t\tnodePath.startsWith(blobPathPrefix) &&\n\t\t\t\tnodeData.unreferencedTimestampMs !== undefined\n\t\t\t) {\n\t\t\t\tunreferenced.add(nodePath.slice(blobPathPrefix.length));\n\t\t\t}\n\t\t}\n\t}\n\tfor (const nodePath of [...(gcData.tombstones ?? []), ...(gcData.deletedNodes ?? [])]) {\n\t\tif (nodePath.startsWith(blobPathPrefix)) {\n\t\t\tunreferenced.add(nodePath.slice(blobPathPrefix.length));\n\t\t}\n\t}\n\treturn unreferenced;\n}\n\n/**\n * A blob reference extracted from a `BlobAttach` op. `localId` is the\n * `BlobManager` GC identity for the blob; `storageId` is the id used for\n * `IDocumentStorageService.readBlob`.\n *\n * @internal\n */\nexport interface IBlobAttachReference {\n\treadonly localId: string;\n\treadonly storageId: string;\n}\n\ninterface IBlobAttachLikeMetadata {\n\treadonly localId: string;\n\treadonly blobId: string;\n}\n\nfunction isBlobAttachLikeMetadata(metadata: unknown): metadata is IBlobAttachLikeMetadata {\n\tif (typeof metadata !== \"object\" || metadata === null) {\n\t\treturn false;\n\t}\n\tconst candidate = metadata as { localId?: unknown; blobId?: unknown };\n\treturn typeof candidate.localId === \"string\" && typeof candidate.blobId === \"string\";\n}\n\n/**\n * Extracts every `BlobAttach` reference an op carries. Returns an empty array\n * for non-blobAttach ops.\n *\n * This is the single place in the loader that interprets the BlobAttach\n * wire format. Capture and load-side reasoning about ops should call into\n * this function rather than reading `op.metadata` directly, so a future\n * protocol change touches one site.\n *\n * BlobAttach ops carry `(localId, storageId)` directly on\n * `ISequencedDocumentMessage.metadata` and are not grouped — the container\n * runtime routes them through a separate `outbox.submitBlobAttach` lane,\n * and `OpGroupingManager.groupBatch` asserts (0x5dd) that no op carrying\n * non-batch metadata enters a grouped batch. If either guarantee changes,\n * extend this function rather than each call site.\n *\n * @internal\n */\nexport function extractBlobAttachReferences(\n\top: Pick<ISequencedDocumentMessage, \"metadata\">,\n): IBlobAttachReference[] {\n\tif (!isBlobAttachLikeMetadata(op.metadata)) {\n\t\treturn [];\n\t}\n\treturn [{ localId: op.metadata.localId, storageId: op.metadata.blobId }];\n}\n\n/**\n * Set of attachment-blob localIds that GC has marked unreferenced,\n * tombstoned, or deleted in the base snapshot. `undefined` if `gcData`\n * is `undefined` (GC disabled / pre-GC document).\n *\n * @internal\n */\nexport function unreferencedAttachmentBlobLocalIds(\n\tgcData: IGcSnapshotData | undefined,\n): Set<string> | undefined {\n\treturn gcData === undefined ? undefined : collectUnreferencedBlobLocalIds(gcData);\n}\n\n/**\n * Inline attachment blob contents for the given `(localId, storageId)`\n * references. Skips entries already present in `existing` (de-dupe with\n * the snapshot path) and entries whose `localId` is in\n * `unreferencedLocalIds`. Returns only the freshly-read entries; the\n * caller merges them into the existing map.\n *\n * @internal\n */\nexport async function inlineAttachmentBlobsByReference(\n\treferences: readonly IBlobAttachReference[],\n\tstorage: Pick<IDocumentStorageService, \"readBlob\">,\n\tunreferencedLocalIds: ReadonlySet<string> | undefined,\n\texisting: Readonly<IBase64BlobContents>,\n): Promise<IBase64BlobContents> {\n\tconst storageIdsToFetch = new Set<string>();\n\tfor (const { localId, storageId } of references) {\n\t\tif (unreferencedLocalIds?.has(localId) === true) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (existing[storageId] !== undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tstorageIdsToFetch.add(storageId);\n\t}\n\tconst added: IBase64BlobContents = {};\n\tif (storageIdsToFetch.size === 0) {\n\t\treturn added;\n\t}\n\tawait mapWithConcurrency([...storageIdsToFetch], maxReadConcurrency, async (storageId) => {\n\t\tconst buffer = await storage.readBlob(storageId);\n\t\tadded[storageId] = bufferToString(buffer, \"base64\");\n\t});\n\treturn added;\n}\n\n/**\n * Returns true if any referenced subtree of `baseSnapshot` declares a\n * `groupId` — the snapshot-tree wire field that carries the runtime's\n * loading-group identifier. Subtrees flagged `unreferenced` are skipped —\n * a dead subtree's `groupId` would not be loaded by the runtime either.\n *\n * `captureFullContainerState` does not yet support loading groups: prefetching\n * per-group snapshots adds a code path that has no end-to-end coverage and no\n * known production consumer. Callers use this to fail fast with a `UsageError`\n * rather than silently producing a pending state that omits group data.\n */\nexport function snapshotHasLoadingGroups(baseSnapshot: ISnapshotTree): boolean {\n\tif (baseSnapshot.unreferenced === true) {\n\t\treturn false;\n\t}\n\tif (baseSnapshot.groupId !== undefined) {\n\t\treturn true;\n\t}\n\tfor (const child of Object.values(baseSnapshot.trees)) {\n\t\tif (snapshotHasLoadingGroups(child)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
|
|
@@ -20,6 +20,7 @@ export declare class ConnectionManager implements IConnectionManager {
|
|
|
20
20
|
private readonly client;
|
|
21
21
|
private readonly logger;
|
|
22
22
|
private readonly props;
|
|
23
|
+
private maxInitialConnectionAttempts?;
|
|
23
24
|
/**
|
|
24
25
|
* Connection mode used when reconnecting on error or disconnect.
|
|
25
26
|
*/
|
|
@@ -106,7 +107,7 @@ export declare class ConnectionManager implements IConnectionManager {
|
|
|
106
107
|
private get readonly();
|
|
107
108
|
get readOnlyInfo(): ReadOnlyInfo;
|
|
108
109
|
private static detailsFromConnection;
|
|
109
|
-
constructor(serviceProvider: () => IDocumentService | undefined, containerDirty: () => boolean, client: IClient, reconnectAllowed: boolean, logger: ITelemetryLoggerExt, props: IConnectionManagerFactoryArgs);
|
|
110
|
+
constructor(serviceProvider: () => IDocumentService | undefined, containerDirty: () => boolean, client: IClient, reconnectAllowed: boolean, logger: ITelemetryLoggerExt, props: IConnectionManagerFactoryArgs, maxInitialConnectionAttempts?: number | undefined);
|
|
110
111
|
dispose(error?: ICriticalContainerError, switchToReadonly?: boolean): void;
|
|
111
112
|
/**
|
|
112
113
|
* Enables or disables automatic reconnecting.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectionManager.d.ts","sourceRoot":"","sources":["../src/connectionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,KAAK,EACX,WAAW,EACX,YAAY,EACZ,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,KAAK,wBAAwB,EAAY,MAAM,iCAAiC,CAAC;AAI1F,OAAO,KAAK,EACX,cAAc,EACd,OAAO,EACP,cAAc,EACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAEN,KAAK,gBAAgB,EAGrB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAOrB,KAAK,yBAAyB,EAE9B,MAAM,6CAA6C,CAAC;AAYrD,OAAO,EACN,KAAK,mBAAmB,EAOxB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EACN,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EACjC,aAAa,EACb,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"connectionManager.d.ts","sourceRoot":"","sources":["../src/connectionManager.ts"],"names":[],"mappings":"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"}
|