@fluidframework/container-loader 2.100.0 → 2.101.1
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 +4 -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.map +1 -1
- package/dist/connectionManager.js +25 -7
- 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 +6 -1
- package/dist/container.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +19 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- 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/loaderLayerCompatState.d.ts +1 -1
- package/dist/packageVersion.d.ts +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/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.map +1 -1
- package/lib/connectionManager.js +26 -8
- 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 +6 -1
- package/lib/container.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +19 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- 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/loaderLayerCompatState.d.ts +1 -1
- package/lib/packageVersion.d.ts +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/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 +11 -11
- package/src/captureReferencedContents.ts +446 -0
- package/src/connectionManager.ts +30 -8
- package/src/connectionStateHandler.ts +14 -9
- package/src/container.ts +6 -0
- package/src/containerStorageAdapter.ts +20 -1
- package/src/createAndLoadContainerUtils.ts +229 -2
- package/src/frozenServices.ts +285 -64
- package/src/index.ts +7 -0
- package/src/packageVersion.ts +1 -1
- package/src/pendingLocalStateStore.ts +8 -1
- package/src/serializedStateManager.ts +28 -1
package/lib/frozenServices.d.ts
CHANGED
|
@@ -7,49 +7,132 @@ import type { IDisposable } from "@fluidframework/core-interfaces";
|
|
|
7
7
|
import { type ConnectionMode, type IClientConfiguration, type IDocumentDeltaConnection, type IDocumentDeltaConnectionEvents, type IDocumentMessage, type IDocumentService, type IDocumentServiceFactory, type IResolvedUrl, type ISequencedDocumentMessage, type ISignalClient, type ISignalMessage, type ITokenClaims } from "@fluidframework/driver-definitions/internal";
|
|
8
8
|
import type { IConnectionStateChangeReason } from "./contracts.js";
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Creates an `IDocumentServiceFactory` that produces a "frozen" document service: one whose
|
|
11
|
+
* delta stream never sends or receives ops, and whose storage service only supports
|
|
12
|
+
* `IDocumentStorageService.readBlob`. Used to load a container from pending local state
|
|
13
|
+
* without re-establishing a live connection.
|
|
12
14
|
*
|
|
13
|
-
* @param
|
|
14
|
-
*
|
|
15
|
+
* @param factory - The underlying factory to wrap. Its storage backs blob reads; all other
|
|
16
|
+
* storage operations throw. May be omitted when blob fetches are not required.
|
|
17
|
+
* @param readOnly - When `true` (the default), the document service advertises the
|
|
18
|
+
* `IDocumentServicePolicies.storageOnly` policy, which causes the loader to surface the
|
|
19
|
+
* container as read-only (see `IContainer.readOnlyInfo`).
|
|
20
|
+
*
|
|
21
|
+
* When `false`, the container is loaded as writable so the runtime will accept DDS submissions.
|
|
22
|
+
* The connection itself stays `Connected`: `ConnectionManager.sendMessages` recognizes the
|
|
23
|
+
* `WritableFrozenDeltaStream` as the live connection and short-circuits — the message is dropped
|
|
24
|
+
* at the network layer rather than triggering a read→write reconnect. Local DDS state continues
|
|
25
|
+
* to update via optimistic apply, and submitted ops accumulate in the runtime's pending-state
|
|
26
|
+
* manager, which is exactly the state needed to capture pending local state. Use `false` when
|
|
27
|
+
* callers want to accrue and capture pending state without publishing it.
|
|
28
|
+
* @returns A factory that produces frozen document services.
|
|
15
29
|
* @legacy @alpha
|
|
16
30
|
*/
|
|
17
|
-
export declare function createFrozenDocumentServiceFactory(factory?: IDocumentServiceFactory | Promise<IDocumentServiceFactory
|
|
31
|
+
export declare function createFrozenDocumentServiceFactory(factory?: IDocumentServiceFactory | Promise<IDocumentServiceFactory>, readOnly?: boolean): IDocumentServiceFactory;
|
|
18
32
|
export declare class FrozenDocumentServiceFactory implements IDocumentServiceFactory {
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
readonly readOnly: boolean;
|
|
34
|
+
readonly inner?: IDocumentServiceFactory | Promise<IDocumentServiceFactory> | undefined;
|
|
35
|
+
constructor(readOnly: boolean, inner?: IDocumentServiceFactory | Promise<IDocumentServiceFactory> | undefined);
|
|
21
36
|
createDocumentService(resolvedUrl: IResolvedUrl): Promise<IDocumentService>;
|
|
22
37
|
createContainer(): Promise<IDocumentService>;
|
|
23
38
|
}
|
|
24
39
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
40
|
+
* Inert `IDocumentDeltaConnection` for frozen container loads. Has no server upstream:
|
|
41
|
+
* op and signal streams are empty, and `initialClients` contains only its own synthetic
|
|
42
|
+
* read-only client — which lets the connection state handler observe "self" in the audience
|
|
43
|
+
* and transition the container to Connected without waiting for a real join op or signal.
|
|
44
|
+
*
|
|
45
|
+
* Two concrete variants share this base — see their JSDoc for variant-specific details:
|
|
46
|
+
*
|
|
47
|
+
* - {@link FrozenDeltaStream} — read-only.
|
|
48
|
+
* - {@link WritableFrozenDeltaStream} — writable.
|
|
49
|
+
*
|
|
50
|
+
* Both variants nack any incoming `submit`: this connection has no upstream and
|
|
51
|
+
* `ConnectionManager.sendMessages` recognizes `WritableFrozenDeltaStream` and drops messages
|
|
52
|
+
* before they reach `submit`, so under normal flow it should never fire. A nack reaching the
|
|
53
|
+
* connectionManager surfaces the misuse — and may close the container — which is the right
|
|
54
|
+
* defensive signal that something has bypassed the expected flow.
|
|
55
|
+
*
|
|
56
|
+
* `submitSignal` is a silent no-op for both variants. Signals are ephemeral and best-effort —
|
|
57
|
+
* runtime/presence subsystems may submit them at any point in the writable-frozen lifetime, and
|
|
58
|
+
* dropping them is the correct behavior here (we have no upstream). Closing the container or
|
|
59
|
+
* triggering a reconnect on a stray signal would be strictly worse than dropping it.
|
|
27
60
|
*/
|
|
28
|
-
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
checkpointSequenceNumber?: number | undefined;
|
|
42
|
-
/**
|
|
43
|
-
* Connection which is not connected to socket.
|
|
44
|
-
* @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.
|
|
45
|
-
* @param readonlyConnectionReason - reason/error if any which lead to using FrozenDeltaStream.
|
|
46
|
-
*/
|
|
47
|
-
constructor(storageOnlyReason?: string | undefined, readonlyConnectionReason?: IConnectionStateChangeReason<import("@fluidframework/core-interfaces").IErrorBase> | undefined);
|
|
61
|
+
declare abstract class FrozenDeltaStreamBase extends TypedEventEmitter<IDocumentDeltaConnectionEvents> implements IDocumentDeltaConnection, IDisposable {
|
|
62
|
+
readonly clientId: string;
|
|
63
|
+
readonly claims: ITokenClaims;
|
|
64
|
+
readonly initialClients: ISignalClient[];
|
|
65
|
+
readonly mode: ConnectionMode;
|
|
66
|
+
readonly existing: boolean;
|
|
67
|
+
readonly maxMessageSize: number;
|
|
68
|
+
readonly version: string;
|
|
69
|
+
readonly initialMessages: ISequencedDocumentMessage[];
|
|
70
|
+
readonly initialSignals: ISignalMessage[];
|
|
71
|
+
readonly serviceConfiguration: IClientConfiguration;
|
|
72
|
+
readonly checkpointSequenceNumber?: number | undefined;
|
|
73
|
+
constructor(clientId: string, claims: ITokenClaims);
|
|
48
74
|
submit(messages: IDocumentMessage[]): void;
|
|
49
|
-
submitSignal(
|
|
75
|
+
submitSignal(_message: unknown): void;
|
|
50
76
|
private _disposed;
|
|
51
77
|
get disposed(): boolean;
|
|
52
78
|
dispose(): void;
|
|
53
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Read-only variant of {@link FrozenDeltaStreamBase}. Claims show only `DocRead`. Used by
|
|
82
|
+
* storage-only loads (where `connectionManager` synthesizes one directly via
|
|
83
|
+
* `policies.storageOnly`) and by the forbidden / out-of-storage fallback paths.
|
|
84
|
+
* {@link isFrozenDeltaStreamConnection} matches this variant and drives the read-only forcing
|
|
85
|
+
* in `ConnectionManager.readOnlyInfo`. Uses the historical `"storage-only client"` constant
|
|
86
|
+
* `clientId`, preserving existing behavior for any consumer that keys off it.
|
|
87
|
+
*
|
|
88
|
+
* `storageOnlyReason` and `readonlyConnectionReason` are surfaced through `IContainer.readOnlyInfo`
|
|
89
|
+
* for diagnostics on the fallback paths (`isDeltaStreamConnectionForbiddenError`,
|
|
90
|
+
* `outOfStorageError`).
|
|
91
|
+
*/
|
|
92
|
+
export declare class FrozenDeltaStream extends FrozenDeltaStreamBase {
|
|
93
|
+
readonly storageOnlyReason: string | undefined;
|
|
94
|
+
readonly readonlyConnectionReason: IConnectionStateChangeReason | undefined;
|
|
95
|
+
constructor(options?: {
|
|
96
|
+
storageOnlyReason?: string;
|
|
97
|
+
readonlyConnectionReason?: IConnectionStateChangeReason;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Variant of {@link FrozenDeltaStreamBase} that appears to support writing but remains
|
|
102
|
+
* "frozen" — no messages are actually sent or received. The stream itself does not enforce
|
|
103
|
+
* the no-send guarantee; that lives in `ConnectionManager.sendMessages`, which recognizes
|
|
104
|
+
* any `WritableFrozenDeltaStream` (via {@link isWritableFrozenDeltaStreamConnection}) and
|
|
105
|
+
* short-circuits before its read-mode upgrade branch. Submitted ops are dropped at the
|
|
106
|
+
* connection-manager layer, so the container stays `Connected` and the runtime accumulates
|
|
107
|
+
* them in `pendingStateManager`.
|
|
108
|
+
*
|
|
109
|
+
* "Appears writable" mechanics: claims include `DocWrite` so the container surfaces as
|
|
110
|
+
* writable; not matched by {@link isFrozenDeltaStreamConnection}, so `readOnlyInfo` reports
|
|
111
|
+
* `readonly: false`. Connection mode stays `"read"` (advertising `"write"` would imply quorum
|
|
112
|
+
* membership we cannot honor).
|
|
113
|
+
*
|
|
114
|
+
* Each instance mints a fresh `frozen-delta-stream/<uuid>` `clientId` to avoid
|
|
115
|
+
* `pendingStateManager` `0x173` (`replayPendingStates called twice for same clientId!`) on
|
|
116
|
+
* reconnect with dirty pending ops. Sibling (not subclass) of `FrozenDeltaStream` so
|
|
117
|
+
* `instanceof` cleanly distinguishes the two for `ConnectionManager`'s short-circuits.
|
|
118
|
+
*/
|
|
119
|
+
export declare class WritableFrozenDeltaStream extends FrozenDeltaStreamBase {
|
|
120
|
+
constructor();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Recognizes the read-only variant of {@link FrozenDeltaStreamBase}. Drives the storage-only
|
|
124
|
+
* forcing in `ConnectionManager.readOnlyInfo`: only the read-only variant should make the
|
|
125
|
+
* container surface as read-only. {@link WritableFrozenDeltaStream} is a sibling class, not
|
|
126
|
+
* a subclass, so `instanceof FrozenDeltaStream` already excludes it.
|
|
127
|
+
*/
|
|
54
128
|
export declare function isFrozenDeltaStreamConnection(connection: unknown): connection is FrozenDeltaStream;
|
|
129
|
+
/**
|
|
130
|
+
* Recognizes the writable variant of {@link FrozenDeltaStreamBase}. Drives the
|
|
131
|
+
* `ConnectionManager.sendMessages` short-circuit: writable-frozen submits must be dropped at
|
|
132
|
+
* the network layer instead of triggering a read→write reconnect. Sibling (not subclass) of
|
|
133
|
+
* {@link FrozenDeltaStream}, so `instanceof WritableFrozenDeltaStream` excludes the read-only
|
|
134
|
+
* variant.
|
|
135
|
+
*/
|
|
136
|
+
export declare function isWritableFrozenDeltaStreamConnection(connection: unknown): connection is WritableFrozenDeltaStream;
|
|
137
|
+
export {};
|
|
55
138
|
//# sourceMappingURL=frozenServices.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frozenServices.d.ts","sourceRoot":"","sources":["../src/frozenServices.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAEN,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EAEnC,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAErB,KAAK,uBAAuB,EAG5B,KAAK,YAAY,EACjB,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,MAAM,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"frozenServices.d.ts","sourceRoot":"","sources":["../src/frozenServices.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAEN,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EAEnC,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAErB,KAAK,uBAAuB,EAG5B,KAAK,YAAY,EACjB,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,MAAM,6CAA6C,CAAC;AAGrD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kCAAkC,CACjD,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC,EACpE,QAAQ,GAAE,OAAc,GACtB,uBAAuB,CAUzB;AAED,qBAAa,4BAA6B,YAAW,uBAAuB;aAE1D,QAAQ,EAAE,OAAO;aACjB,KAAK,CAAC;gBADN,QAAQ,EAAE,OAAO,EACjB,KAAK,CAAC,wEAA4D;IAG7E,qBAAqB,CAAC,WAAW,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAQ3E,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC;CAGlD;AAoLD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,uBAAe,qBACd,SAAQ,iBAAiB,CAAC,8BAA8B,CACxD,YAAW,wBAAwB,EAAE,WAAW;IAEhD,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,MAAM,EAAE,YAAY,CAAC;IACrC,SAAgB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChD,SAAgB,IAAI,EAAE,cAAc,CAAU;IAC9C,SAAgB,QAAQ,EAAE,OAAO,CAAQ;IACzC,SAAgB,cAAc,EAAE,MAAM,CAAK;IAC3C,SAAgB,OAAO,EAAE,MAAM,CAAM;IACrC,SAAgB,eAAe,EAAE,yBAAyB,EAAE,CAAM;IAClE,SAAgB,cAAc,EAAE,cAAc,EAAE,CAAM;IACtD,SAAgB,oBAAoB,EAAE,oBAAoB,CAGxD;IACF,SAAgB,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAa;gBAE9D,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IASlD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAa1C,YAAY,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIrC,OAAO,CAAC,SAAS,CAAS;IAC1B,IAAW,QAAQ,IAAI,OAAO,CAE7B;IACM,OAAO,IAAI,IAAI;CAGtB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,qBAAqB;IAC3D,SAAgB,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtD,SAAgB,wBAAwB,EAAE,4BAA4B,GAAG,SAAS,CAAC;gBAEvE,OAAO,CAAC,EAAE;QACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;KACxD;CASD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,yBAA0B,SAAQ,qBAAqB;;CAInE;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC5C,UAAU,EAAE,OAAO,GACjB,UAAU,IAAI,iBAAiB,CAEjC;AAED;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CACpD,UAAU,EAAE,OAAO,GACjB,UAAU,IAAI,yBAAyB,CAEzC"}
|
package/lib/frozenServices.js
CHANGED
|
@@ -5,69 +5,165 @@
|
|
|
5
5
|
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
6
6
|
import { isPromiseLike } from "@fluidframework/core-utils/internal";
|
|
7
7
|
import { ScopeType, } from "@fluidframework/driver-definitions/internal";
|
|
8
|
+
import { v4 as uuid } from "uuid";
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Creates an `IDocumentServiceFactory` that produces a "frozen" document service: one whose
|
|
11
|
+
* delta stream never sends or receives ops, and whose storage service only supports
|
|
12
|
+
* `IDocumentStorageService.readBlob`. Used to load a container from pending local state
|
|
13
|
+
* without re-establishing a live connection.
|
|
11
14
|
*
|
|
12
|
-
* @param
|
|
13
|
-
*
|
|
15
|
+
* @param factory - The underlying factory to wrap. Its storage backs blob reads; all other
|
|
16
|
+
* storage operations throw. May be omitted when blob fetches are not required.
|
|
17
|
+
* @param readOnly - When `true` (the default), the document service advertises the
|
|
18
|
+
* `IDocumentServicePolicies.storageOnly` policy, which causes the loader to surface the
|
|
19
|
+
* container as read-only (see `IContainer.readOnlyInfo`).
|
|
20
|
+
*
|
|
21
|
+
* When `false`, the container is loaded as writable so the runtime will accept DDS submissions.
|
|
22
|
+
* The connection itself stays `Connected`: `ConnectionManager.sendMessages` recognizes the
|
|
23
|
+
* `WritableFrozenDeltaStream` as the live connection and short-circuits — the message is dropped
|
|
24
|
+
* at the network layer rather than triggering a read→write reconnect. Local DDS state continues
|
|
25
|
+
* to update via optimistic apply, and submitted ops accumulate in the runtime's pending-state
|
|
26
|
+
* manager, which is exactly the state needed to capture pending local state. Use `false` when
|
|
27
|
+
* callers want to accrue and capture pending state without publishing it.
|
|
28
|
+
* @returns A factory that produces frozen document services.
|
|
14
29
|
* @legacy @alpha
|
|
15
30
|
*/
|
|
16
|
-
export function createFrozenDocumentServiceFactory(factory) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
export function createFrozenDocumentServiceFactory(factory, readOnly = true) {
|
|
32
|
+
if (factory instanceof FrozenDocumentServiceFactory) {
|
|
33
|
+
// Already wrapped. Reuse if readOnly matches; otherwise unwrap and rewrap so the caller's
|
|
34
|
+
// most recent readOnly intent wins (silently honoring caller intent rather than dropping
|
|
35
|
+
// the new argument).
|
|
36
|
+
return factory.readOnly === readOnly
|
|
37
|
+
? factory
|
|
38
|
+
: new FrozenDocumentServiceFactory(readOnly, factory.inner);
|
|
39
|
+
}
|
|
40
|
+
return new FrozenDocumentServiceFactory(readOnly, factory);
|
|
21
41
|
}
|
|
22
42
|
export class FrozenDocumentServiceFactory {
|
|
23
|
-
constructor(
|
|
24
|
-
this.
|
|
43
|
+
constructor(readOnly, inner) {
|
|
44
|
+
this.readOnly = readOnly;
|
|
45
|
+
this.inner = inner;
|
|
25
46
|
}
|
|
26
47
|
async createDocumentService(resolvedUrl) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
factory = await this.documentServiceFactory;
|
|
30
|
-
}
|
|
31
|
-
return new FrozenDocumentService(resolvedUrl, await factory?.createDocumentService(resolvedUrl));
|
|
48
|
+
const factory = isPromiseLike(this.inner) ? await this.inner : this.inner;
|
|
49
|
+
return new FrozenDocumentService(resolvedUrl, this.readOnly, await factory?.createDocumentService(resolvedUrl));
|
|
32
50
|
}
|
|
33
51
|
async createContainer() {
|
|
34
52
|
throw new Error("The FrozenDocumentServiceFactory cannot be used to create containers.");
|
|
35
53
|
}
|
|
36
54
|
}
|
|
37
55
|
class FrozenDocumentService extends TypedEventEmitter {
|
|
38
|
-
constructor(resolvedUrl, documentService) {
|
|
56
|
+
constructor(resolvedUrl, readOnly, documentService) {
|
|
39
57
|
super();
|
|
40
58
|
this.resolvedUrl = resolvedUrl;
|
|
59
|
+
this.readOnly = readOnly;
|
|
41
60
|
this.documentService = documentService;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
// Tracks every storage instance handed out by `connectToStorage` so `dispose()` can cascade
|
|
62
|
+
// disposal to each one (rejecting their hanging `createBlob` deferreds). A Set rather than
|
|
63
|
+
// a single field because `IDocumentService.connectToStorage` is a public API that can be
|
|
64
|
+
// called more than once — we cannot assume the Container holds a single instance.
|
|
65
|
+
this.storageServices = new Set();
|
|
66
|
+
// When readOnly, advertise the storageOnly policy. The connectionManager short-circuits
|
|
67
|
+
// on it: it synthesizes a FrozenDeltaStream itself and never calls
|
|
68
|
+
// connectToDeltaStream, and the readOnlyInfo getter forces the container to read-only
|
|
69
|
+
// because the live connection is a FrozenDeltaStream.
|
|
70
|
+
//
|
|
71
|
+
// Audit (2026-05-05): the only consumer of `policies.storageOnly` as a frozen-container
|
|
72
|
+
// signal is `ConnectionManager` (synthesizing a `FrozenDeltaStream` when set). All other
|
|
73
|
+
// matches in the loader/runtime/driver layers are either drivers reading their own
|
|
74
|
+
// policies (e.g. local-driver) or `IReadOnlyInfo.storageOnly`, which is derived from the
|
|
75
|
+
// live connection — not the policy. So the writable-frozen container is intentionally
|
|
76
|
+
// indistinguishable from a normal container at the policies layer; downstream behavior
|
|
77
|
+
// flows through the live `WritableFrozenDeltaStream` instead.
|
|
78
|
+
this.policies = readOnly ? { storageOnly: true } : {};
|
|
45
79
|
}
|
|
46
80
|
async connectToStorage() {
|
|
47
|
-
|
|
81
|
+
const storage = new FrozenDocumentStorageService(this.readOnly, await this.documentService?.connectToStorage());
|
|
82
|
+
this.storageServices.add(storage);
|
|
83
|
+
return storage;
|
|
48
84
|
}
|
|
49
85
|
async connectToDeltaStorage() {
|
|
50
86
|
return frozenDocumentDeltaStorageService;
|
|
51
87
|
}
|
|
52
|
-
async connectToDeltaStream(
|
|
53
|
-
|
|
88
|
+
async connectToDeltaStream(_client) {
|
|
89
|
+
if (this.readOnly) {
|
|
90
|
+
// connectionManager short-circuits via policies.storageOnly before reaching here
|
|
91
|
+
// in the read-only path; reaching this branch indicates a non-connectionManager
|
|
92
|
+
// consumer or a regression of the short-circuit. Throw to surface the misuse
|
|
93
|
+
// rather than silently produce a working stream.
|
|
94
|
+
throw new Error("FrozenDocumentService is read-only; connectToDeltaStream should not be called (connectionManager short-circuits via policies.storageOnly)");
|
|
95
|
+
}
|
|
96
|
+
// Writable path: hand out a fresh WritableFrozenDeltaStream regardless of client.mode
|
|
97
|
+
// or whether this is the initial connect or a reconnect. The stream's own mode is
|
|
98
|
+
// "read" (advertising "write" would imply quorum membership we cannot honor), and
|
|
99
|
+
// `ConnectionManager.sendMessages` short-circuits on WritableFrozenDeltaStream so
|
|
100
|
+
// outbound writes never reach a real network. The per-instance clientId minted in
|
|
101
|
+
// FrozenDeltaStreamBase prevents pendingStateManager 0x173 on replay across reconnects.
|
|
102
|
+
return new WritableFrozenDeltaStream();
|
|
103
|
+
}
|
|
104
|
+
dispose(error) {
|
|
105
|
+
// Cascade disposal to each storage instance so any hanging `createBlob` promises (the
|
|
106
|
+
// writable-frozen pending-blob mechanism) reject and the BlobManager can release its
|
|
107
|
+
// references. Without this, hung promises remain held by BlobManager closures for the
|
|
108
|
+
// lifetime of the process.
|
|
109
|
+
for (const storage of this.storageServices) {
|
|
110
|
+
storage.dispose();
|
|
111
|
+
}
|
|
112
|
+
this.storageServices.clear();
|
|
113
|
+
// Forward disposal to the wrapped service. We own its lifetime (it was created for us
|
|
114
|
+
// by the wrapping factory and is never exposed to callers), so the contract from
|
|
115
|
+
// `IDocumentService.dispose` ("called by storage consumer when done with storage")
|
|
116
|
+
// applies here.
|
|
117
|
+
this.documentService?.dispose(error);
|
|
54
118
|
}
|
|
55
|
-
dispose() { }
|
|
56
119
|
}
|
|
57
120
|
const frozenDocumentStorageServiceHandler = () => {
|
|
58
121
|
throw new Error("Operations are not supported on the FrozenDocumentStorageService.");
|
|
59
122
|
};
|
|
60
123
|
class FrozenDocumentStorageService {
|
|
61
|
-
|
|
124
|
+
get disposed() {
|
|
125
|
+
return this._disposed;
|
|
126
|
+
}
|
|
127
|
+
constructor(readOnly, documentStorageService) {
|
|
62
128
|
this.documentStorageService = documentStorageService;
|
|
129
|
+
this._disposed = false;
|
|
63
130
|
this.getSnapshotTree = frozenDocumentStorageServiceHandler;
|
|
64
131
|
this.getSnapshot = frozenDocumentStorageServiceHandler;
|
|
65
132
|
this.getVersions = frozenDocumentStorageServiceHandler;
|
|
66
|
-
this.createBlob = frozenDocumentStorageServiceHandler;
|
|
67
133
|
this.readBlob = this.documentStorageService?.readBlob.bind(this.documentStorageService) ??
|
|
68
134
|
frozenDocumentStorageServiceHandler;
|
|
69
135
|
this.uploadSummaryWithContext = frozenDocumentStorageServiceHandler;
|
|
70
136
|
this.downloadSummary = frozenDocumentStorageServiceHandler;
|
|
137
|
+
let rejectFn;
|
|
138
|
+
const promise = new Promise((_, reject) => {
|
|
139
|
+
rejectFn = reject;
|
|
140
|
+
});
|
|
141
|
+
// Attach a no-op catch so node doesn't log an unhandled-rejection warning when
|
|
142
|
+
// dispose runs before any caller has awaited the promise. Callers awaiting the
|
|
143
|
+
// original promise still observe the rejection.
|
|
144
|
+
promise.catch(() => { });
|
|
145
|
+
this.disposalDeferred = { promise, reject: rejectFn };
|
|
146
|
+
// In the writable-frozen path, `createBlob` returns a never-resolving promise instead
|
|
147
|
+
// of throwing. This keeps the BlobManager's `localBlobCache` entry in the `uploading`
|
|
148
|
+
// state: `getPendingBlobs` downgrades `uploading` blobs to `localOnly` in pending
|
|
149
|
+
// state, so the blob survives `getPendingLocalState`. A subsequent live load runs
|
|
150
|
+
// `sharePendingBlobs`, which re-enters `uploadAndAttach` against the real storage to
|
|
151
|
+
// complete the upload. Throwing here would instead delete the cache entry (in
|
|
152
|
+
// `uploadAndAttach`'s catch handler) and lose the blob — defeating the whole point of
|
|
153
|
+
// accruing pending state.
|
|
154
|
+
this.createBlob = readOnly
|
|
155
|
+
? frozenDocumentStorageServiceHandler
|
|
156
|
+
: async () => this.disposalDeferred.promise;
|
|
157
|
+
}
|
|
158
|
+
dispose() {
|
|
159
|
+
if (this._disposed) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this._disposed = true;
|
|
163
|
+
// Don't propagate any caller-supplied error here. `IDocumentService.dispose` already
|
|
164
|
+
// logs the Container's error path; the createBlob deferred's consumer (BlobManager)
|
|
165
|
+
// only needs the "this storage is going away" signal — not a chain of error causes.
|
|
166
|
+
this.disposalDeferred.reject(new Error("FrozenDocumentStorageService is disposed"));
|
|
71
167
|
}
|
|
72
168
|
}
|
|
73
169
|
const frozenDocumentDeltaStorageService = {
|
|
@@ -85,54 +181,71 @@ const clientFrozenDeltaStream = {
|
|
|
85
181
|
scopes: [],
|
|
86
182
|
};
|
|
87
183
|
const clientIdFrozenDeltaStream = "storage-only client";
|
|
184
|
+
// Cast rationale: ITokenClaims requires tenantId/documentId/user/iat/exp/ver, but a frozen
|
|
185
|
+
// delta stream has no tenant or session to draw real values from — it's a synthetic
|
|
186
|
+
// in-process connection that never reaches a service. Inventing sentinel values would imply
|
|
187
|
+
// quorum membership we cannot honor; only `scopes` actually drives behavior here (DocRead vs
|
|
188
|
+
// DocWrite gates readOnlyInfo). The cast is the honest representation of "this connection
|
|
189
|
+
// has no claims worth populating."
|
|
190
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
191
|
+
const readOnlyClaims = { scopes: [ScopeType.DocRead] };
|
|
192
|
+
const writableClaims = {
|
|
193
|
+
scopes: [ScopeType.DocRead, ScopeType.DocWrite],
|
|
194
|
+
};
|
|
195
|
+
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
88
196
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
197
|
+
* Inert `IDocumentDeltaConnection` for frozen container loads. Has no server upstream:
|
|
198
|
+
* op and signal streams are empty, and `initialClients` contains only its own synthetic
|
|
199
|
+
* read-only client — which lets the connection state handler observe "self" in the audience
|
|
200
|
+
* and transition the container to Connected without waiting for a real join op or signal.
|
|
201
|
+
*
|
|
202
|
+
* Two concrete variants share this base — see their JSDoc for variant-specific details:
|
|
203
|
+
*
|
|
204
|
+
* - {@link FrozenDeltaStream} — read-only.
|
|
205
|
+
* - {@link WritableFrozenDeltaStream} — writable.
|
|
206
|
+
*
|
|
207
|
+
* Both variants nack any incoming `submit`: this connection has no upstream and
|
|
208
|
+
* `ConnectionManager.sendMessages` recognizes `WritableFrozenDeltaStream` and drops messages
|
|
209
|
+
* before they reach `submit`, so under normal flow it should never fire. A nack reaching the
|
|
210
|
+
* connectionManager surfaces the misuse — and may close the container — which is the right
|
|
211
|
+
* defensive signal that something has bypassed the expected flow.
|
|
212
|
+
*
|
|
213
|
+
* `submitSignal` is a silent no-op for both variants. Signals are ephemeral and best-effort —
|
|
214
|
+
* runtime/presence subsystems may submit them at any point in the writable-frozen lifetime, and
|
|
215
|
+
* dropping them is the correct behavior here (we have no upstream). Closing the container or
|
|
216
|
+
* triggering a reconnect on a stray signal would be strictly worse than dropping it.
|
|
91
217
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* Connection which is not connected to socket.
|
|
95
|
-
* @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.
|
|
96
|
-
* @param readonlyConnectionReason - reason/error if any which lead to using FrozenDeltaStream.
|
|
97
|
-
*/
|
|
98
|
-
constructor(storageOnlyReason, readonlyConnectionReason) {
|
|
218
|
+
class FrozenDeltaStreamBase extends TypedEventEmitter {
|
|
219
|
+
constructor(clientId, claims) {
|
|
99
220
|
super();
|
|
100
|
-
this.storageOnlyReason = storageOnlyReason;
|
|
101
|
-
this.readonlyConnectionReason = readonlyConnectionReason;
|
|
102
|
-
this.clientId = clientIdFrozenDeltaStream;
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
104
|
-
this.claims = {
|
|
105
|
-
scopes: [ScopeType.DocRead],
|
|
106
|
-
};
|
|
107
221
|
this.mode = "read";
|
|
108
222
|
this.existing = true;
|
|
109
223
|
this.maxMessageSize = 0;
|
|
110
224
|
this.version = "";
|
|
111
225
|
this.initialMessages = [];
|
|
112
226
|
this.initialSignals = [];
|
|
113
|
-
this.initialClients = [
|
|
114
|
-
{ client: clientFrozenDeltaStream, clientId: clientIdFrozenDeltaStream },
|
|
115
|
-
];
|
|
116
227
|
this.serviceConfiguration = {
|
|
117
228
|
maxMessageSize: 0,
|
|
118
229
|
blockSize: 0,
|
|
119
230
|
};
|
|
120
231
|
this.checkpointSequenceNumber = undefined;
|
|
121
232
|
this._disposed = false;
|
|
233
|
+
this.clientId = clientId;
|
|
234
|
+
this.claims = claims;
|
|
235
|
+
// initialClients mirrors clientId so the audience handler observes "self" and
|
|
236
|
+
// transitions the container to Connected without waiting for a real join op or signal.
|
|
237
|
+
this.initialClients = [{ client: clientFrozenDeltaStream, clientId }];
|
|
122
238
|
}
|
|
123
239
|
submit(messages) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
submitSignal(
|
|
132
|
-
|
|
133
|
-
operation: message,
|
|
134
|
-
content: { message: "Cannot submit signal with storage-only connection", code: 403 },
|
|
135
|
-
});
|
|
240
|
+
// Defensive nack: nothing should send on a frozen delta stream. If this fires, an
|
|
241
|
+
// invariant in connectionManager has changed and we want it to surface loudly.
|
|
242
|
+
this.emit("nack", this.clientId, messages.map((operation) => ({
|
|
243
|
+
operation,
|
|
244
|
+
content: { message: "Cannot submit on a frozen delta stream", code: 403 },
|
|
245
|
+
})));
|
|
246
|
+
}
|
|
247
|
+
submitSignal(_message) {
|
|
248
|
+
// Intentional no-op. See class JSDoc for rationale.
|
|
136
249
|
}
|
|
137
250
|
get disposed() {
|
|
138
251
|
return this._disposed;
|
|
@@ -141,7 +254,70 @@ export class FrozenDeltaStream extends TypedEventEmitter {
|
|
|
141
254
|
this._disposed = true;
|
|
142
255
|
}
|
|
143
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Read-only variant of {@link FrozenDeltaStreamBase}. Claims show only `DocRead`. Used by
|
|
259
|
+
* storage-only loads (where `connectionManager` synthesizes one directly via
|
|
260
|
+
* `policies.storageOnly`) and by the forbidden / out-of-storage fallback paths.
|
|
261
|
+
* {@link isFrozenDeltaStreamConnection} matches this variant and drives the read-only forcing
|
|
262
|
+
* in `ConnectionManager.readOnlyInfo`. Uses the historical `"storage-only client"` constant
|
|
263
|
+
* `clientId`, preserving existing behavior for any consumer that keys off it.
|
|
264
|
+
*
|
|
265
|
+
* `storageOnlyReason` and `readonlyConnectionReason` are surfaced through `IContainer.readOnlyInfo`
|
|
266
|
+
* for diagnostics on the fallback paths (`isDeltaStreamConnectionForbiddenError`,
|
|
267
|
+
* `outOfStorageError`).
|
|
268
|
+
*/
|
|
269
|
+
export class FrozenDeltaStream extends FrozenDeltaStreamBase {
|
|
270
|
+
constructor(options) {
|
|
271
|
+
// Constant clientId: preserves the pre-PR `"storage-only client"` identity for any
|
|
272
|
+
// consumer that keys off it. The 0x173 replay-assert risk that motivates per-instance
|
|
273
|
+
// clientIds applies only to the writable variant, where the runtime accumulates dirty
|
|
274
|
+
// pending ops across reconnects; the read-only variant does not.
|
|
275
|
+
super(clientIdFrozenDeltaStream, readOnlyClaims);
|
|
276
|
+
this.storageOnlyReason = options?.storageOnlyReason;
|
|
277
|
+
this.readonlyConnectionReason = options?.readonlyConnectionReason;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Variant of {@link FrozenDeltaStreamBase} that appears to support writing but remains
|
|
282
|
+
* "frozen" — no messages are actually sent or received. The stream itself does not enforce
|
|
283
|
+
* the no-send guarantee; that lives in `ConnectionManager.sendMessages`, which recognizes
|
|
284
|
+
* any `WritableFrozenDeltaStream` (via {@link isWritableFrozenDeltaStreamConnection}) and
|
|
285
|
+
* short-circuits before its read-mode upgrade branch. Submitted ops are dropped at the
|
|
286
|
+
* connection-manager layer, so the container stays `Connected` and the runtime accumulates
|
|
287
|
+
* them in `pendingStateManager`.
|
|
288
|
+
*
|
|
289
|
+
* "Appears writable" mechanics: claims include `DocWrite` so the container surfaces as
|
|
290
|
+
* writable; not matched by {@link isFrozenDeltaStreamConnection}, so `readOnlyInfo` reports
|
|
291
|
+
* `readonly: false`. Connection mode stays `"read"` (advertising `"write"` would imply quorum
|
|
292
|
+
* membership we cannot honor).
|
|
293
|
+
*
|
|
294
|
+
* Each instance mints a fresh `frozen-delta-stream/<uuid>` `clientId` to avoid
|
|
295
|
+
* `pendingStateManager` `0x173` (`replayPendingStates called twice for same clientId!`) on
|
|
296
|
+
* reconnect with dirty pending ops. Sibling (not subclass) of `FrozenDeltaStream` so
|
|
297
|
+
* `instanceof` cleanly distinguishes the two for `ConnectionManager`'s short-circuits.
|
|
298
|
+
*/
|
|
299
|
+
export class WritableFrozenDeltaStream extends FrozenDeltaStreamBase {
|
|
300
|
+
constructor() {
|
|
301
|
+
super(`frozen-delta-stream/${uuid()}`, writableClaims);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Recognizes the read-only variant of {@link FrozenDeltaStreamBase}. Drives the storage-only
|
|
306
|
+
* forcing in `ConnectionManager.readOnlyInfo`: only the read-only variant should make the
|
|
307
|
+
* container surface as read-only. {@link WritableFrozenDeltaStream} is a sibling class, not
|
|
308
|
+
* a subclass, so `instanceof FrozenDeltaStream` already excludes it.
|
|
309
|
+
*/
|
|
144
310
|
export function isFrozenDeltaStreamConnection(connection) {
|
|
145
311
|
return connection instanceof FrozenDeltaStream;
|
|
146
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Recognizes the writable variant of {@link FrozenDeltaStreamBase}. Drives the
|
|
315
|
+
* `ConnectionManager.sendMessages` short-circuit: writable-frozen submits must be dropped at
|
|
316
|
+
* the network layer instead of triggering a read→write reconnect. Sibling (not subclass) of
|
|
317
|
+
* {@link FrozenDeltaStream}, so `instanceof WritableFrozenDeltaStream` excludes the read-only
|
|
318
|
+
* variant.
|
|
319
|
+
*/
|
|
320
|
+
export function isWritableFrozenDeltaStreamConnection(connection) {
|
|
321
|
+
return connection instanceof WritableFrozenDeltaStream;
|
|
322
|
+
}
|
|
147
323
|
//# sourceMappingURL=frozenServices.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frozenServices.js","sourceRoot":"","sources":["../src/frozenServices.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 { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { isPromiseLike } from \"@fluidframework/core-utils/internal\";\nimport {\n\tScopeType,\n\ttype ConnectionMode,\n\ttype IClient,\n\ttype IClientConfiguration,\n\ttype IDocumentDeltaConnection,\n\ttype IDocumentDeltaConnectionEvents,\n\ttype IDocumentDeltaStorageService,\n\ttype IDocumentMessage,\n\ttype IDocumentService,\n\ttype IDocumentServiceEvents,\n\ttype IDocumentServiceFactory,\n\ttype IDocumentServicePolicies,\n\ttype IDocumentStorageService,\n\ttype IResolvedUrl,\n\ttype ISequencedDocumentMessage,\n\ttype ISignalClient,\n\ttype ISignalMessage,\n\ttype ITokenClaims,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport type { IConnectionStateChangeReason } from \"./contracts.js\";\n\n/**\n * Creation of a FrozenDocumentServiceFactory which wraps an existing\n * DocumentServiceFactory to provide a storage-only document service.\n *\n * @param documentServiceFactory - The underlying DocumentServiceFactory to wrap.\n * @returns A FrozenDocumentServiceFactory\n * @legacy @alpha\n */\nexport function createFrozenDocumentServiceFactory(\n\tfactory?: IDocumentServiceFactory | Promise<IDocumentServiceFactory>,\n): IDocumentServiceFactory {\n\t// Sync path\n\treturn factory instanceof FrozenDocumentServiceFactory\n\t\t? factory\n\t\t: new FrozenDocumentServiceFactory(factory);\n}\n\nexport class FrozenDocumentServiceFactory implements IDocumentServiceFactory {\n\tconstructor(\n\t\tprivate readonly documentServiceFactory?:\n\t\t\t| IDocumentServiceFactory\n\t\t\t| Promise<IDocumentServiceFactory>,\n\t) {}\n\n\tasync createDocumentService(resolvedUrl: IResolvedUrl): Promise<IDocumentService> {\n\t\tlet factory = this.documentServiceFactory;\n\t\tif (isPromiseLike(factory)) {\n\t\t\tfactory = await this.documentServiceFactory;\n\t\t}\n\t\treturn new FrozenDocumentService(\n\t\t\tresolvedUrl,\n\t\t\tawait factory?.createDocumentService(resolvedUrl),\n\t\t);\n\t}\n\tasync createContainer(): Promise<IDocumentService> {\n\t\tthrow new Error(\"The FrozenDocumentServiceFactory cannot be used to create containers.\");\n\t}\n}\n\nclass FrozenDocumentService\n\textends TypedEventEmitter<IDocumentServiceEvents>\n\timplements IDocumentService\n{\n\tconstructor(\n\t\tpublic readonly resolvedUrl: IResolvedUrl,\n\t\tprivate readonly documentService?: IDocumentService,\n\t) {\n\t\tsuper();\n\t}\n\n\tpublic readonly policies: IDocumentServicePolicies = {\n\t\tstorageOnly: true,\n\t};\n\tasync connectToStorage(): Promise<IDocumentStorageService> {\n\t\treturn new FrozenDocumentStorageService(await this.documentService?.connectToStorage());\n\t}\n\tasync connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {\n\t\treturn frozenDocumentDeltaStorageService;\n\t}\n\tasync connectToDeltaStream(client: IClient): Promise<IDocumentDeltaConnection> {\n\t\treturn new FrozenDeltaStream();\n\t}\n\tdispose(): void {}\n}\n\nconst frozenDocumentStorageServiceHandler = (): never => {\n\tthrow new Error(\"Operations are not supported on the FrozenDocumentStorageService.\");\n};\nclass FrozenDocumentStorageService implements IDocumentStorageService {\n\tconstructor(private readonly documentStorageService?: IDocumentStorageService) {}\n\n\tgetSnapshotTree = frozenDocumentStorageServiceHandler;\n\tgetSnapshot = frozenDocumentStorageServiceHandler;\n\tgetVersions = frozenDocumentStorageServiceHandler;\n\tcreateBlob = frozenDocumentStorageServiceHandler;\n\treadBlob =\n\t\tthis.documentStorageService?.readBlob.bind(this.documentStorageService) ??\n\t\tfrozenDocumentStorageServiceHandler;\n\tuploadSummaryWithContext = frozenDocumentStorageServiceHandler;\n\tdownloadSummary = frozenDocumentStorageServiceHandler;\n}\n\nconst frozenDocumentDeltaStorageService: IDocumentDeltaStorageService = {\n\tfetchMessages: () => ({\n\t\tread: async () => ({\n\t\t\tdone: true,\n\t\t}),\n\t}),\n};\n\nconst clientFrozenDeltaStream: IClient = {\n\tmode: \"read\",\n\tdetails: { capabilities: { interactive: true } },\n\tpermission: [],\n\tuser: { id: \"storage-only client\" }, // we need some \"fake\" ID here.\n\tscopes: [],\n};\nconst clientIdFrozenDeltaStream: string = \"storage-only client\";\n\n/**\n * Implementation of IDocumentDeltaConnection that does not support submitting\n * or receiving ops. Used in storage-only mode and in frozen loads.\n */\nexport class FrozenDeltaStream\n\textends TypedEventEmitter<IDocumentDeltaConnectionEvents>\n\timplements IDocumentDeltaConnection, IDisposable\n{\n\tclientId = clientIdFrozenDeltaStream;\n\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\tclaims = {\n\t\tscopes: [ScopeType.DocRead],\n\t} as ITokenClaims;\n\tmode: ConnectionMode = \"read\";\n\texisting: boolean = true;\n\tmaxMessageSize: number = 0;\n\tversion: string = \"\";\n\tinitialMessages: ISequencedDocumentMessage[] = [];\n\tinitialSignals: ISignalMessage[] = [];\n\tinitialClients: ISignalClient[] = [\n\t\t{ client: clientFrozenDeltaStream, clientId: clientIdFrozenDeltaStream },\n\t];\n\tserviceConfiguration: IClientConfiguration = {\n\t\tmaxMessageSize: 0,\n\t\tblockSize: 0,\n\t};\n\tcheckpointSequenceNumber?: number | undefined = undefined;\n\t/**\n\t * Connection which is not connected to socket.\n\t * @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.\n\t * @param readonlyConnectionReason - reason/error if any which lead to using FrozenDeltaStream.\n\t */\n\tconstructor(\n\t\tpublic readonly storageOnlyReason?: string,\n\t\tpublic readonly readonlyConnectionReason?: IConnectionStateChangeReason,\n\t) {\n\t\tsuper();\n\t}\n\tsubmit(messages: IDocumentMessage[]): void {\n\t\tthis.emit(\n\t\t\t\"nack\",\n\t\t\tthis.clientId,\n\t\t\tmessages.map((operation) => {\n\t\t\t\treturn {\n\t\t\t\t\toperation,\n\t\t\t\t\tcontent: { message: \"Cannot submit with storage-only connection\", code: 403 },\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t}\n\tsubmitSignal(message: unknown): void {\n\t\tthis.emit(\"nack\", this.clientId, {\n\t\t\toperation: message,\n\t\t\tcontent: { message: \"Cannot submit signal with storage-only connection\", code: 403 },\n\t\t});\n\t}\n\n\tprivate _disposed = false;\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\tpublic dispose(): void {\n\t\tthis._disposed = true;\n\t}\n}\nexport function isFrozenDeltaStreamConnection(\n\tconnection: unknown,\n): connection is FrozenDeltaStream {\n\treturn connection instanceof FrozenDeltaStream;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"frozenServices.js","sourceRoot":"","sources":["../src/frozenServices.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 { TypedEventEmitter } from \"@fluid-internal/client-utils\";\nimport type { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { isPromiseLike } from \"@fluidframework/core-utils/internal\";\nimport {\n\tScopeType,\n\ttype ConnectionMode,\n\ttype IClient,\n\ttype IClientConfiguration,\n\ttype IDocumentDeltaConnection,\n\ttype IDocumentDeltaConnectionEvents,\n\ttype IDocumentDeltaStorageService,\n\ttype IDocumentMessage,\n\ttype IDocumentService,\n\ttype IDocumentServiceEvents,\n\ttype IDocumentServiceFactory,\n\ttype IDocumentServicePolicies,\n\ttype IDocumentStorageService,\n\ttype IResolvedUrl,\n\ttype ISequencedDocumentMessage,\n\ttype ISignalClient,\n\ttype ISignalMessage,\n\ttype ITokenClaims,\n} from \"@fluidframework/driver-definitions/internal\";\nimport { v4 as uuid } from \"uuid\";\n\nimport type { IConnectionStateChangeReason } from \"./contracts.js\";\n\n/**\n * Creates an `IDocumentServiceFactory` that produces a \"frozen\" document service: one whose\n * delta stream never sends or receives ops, and whose storage service only supports\n * `IDocumentStorageService.readBlob`. Used to load a container from pending local state\n * without re-establishing a live connection.\n *\n * @param factory - The underlying factory to wrap. Its storage backs blob reads; all other\n * storage operations throw. May be omitted when blob fetches are not required.\n * @param readOnly - When `true` (the default), the document service advertises the\n * `IDocumentServicePolicies.storageOnly` policy, which causes the loader to surface the\n * container as read-only (see `IContainer.readOnlyInfo`).\n *\n * When `false`, the container is loaded as writable so the runtime will accept DDS submissions.\n * The connection itself stays `Connected`: `ConnectionManager.sendMessages` recognizes the\n * `WritableFrozenDeltaStream` as the live connection and short-circuits — the message is dropped\n * at the network layer rather than triggering a read→write reconnect. Local DDS state continues\n * to update via optimistic apply, and submitted ops accumulate in the runtime's pending-state\n * manager, which is exactly the state needed to capture pending local state. Use `false` when\n * callers want to accrue and capture pending state without publishing it.\n * @returns A factory that produces frozen document services.\n * @legacy @alpha\n */\nexport function createFrozenDocumentServiceFactory(\n\tfactory?: IDocumentServiceFactory | Promise<IDocumentServiceFactory>,\n\treadOnly: boolean = true,\n): IDocumentServiceFactory {\n\tif (factory instanceof FrozenDocumentServiceFactory) {\n\t\t// Already wrapped. Reuse if readOnly matches; otherwise unwrap and rewrap so the caller's\n\t\t// most recent readOnly intent wins (silently honoring caller intent rather than dropping\n\t\t// the new argument).\n\t\treturn factory.readOnly === readOnly\n\t\t\t? factory\n\t\t\t: new FrozenDocumentServiceFactory(readOnly, factory.inner);\n\t}\n\treturn new FrozenDocumentServiceFactory(readOnly, factory);\n}\n\nexport class FrozenDocumentServiceFactory implements IDocumentServiceFactory {\n\tconstructor(\n\t\tpublic readonly readOnly: boolean,\n\t\tpublic readonly inner?: IDocumentServiceFactory | Promise<IDocumentServiceFactory>,\n\t) {}\n\n\tasync createDocumentService(resolvedUrl: IResolvedUrl): Promise<IDocumentService> {\n\t\tconst factory = isPromiseLike(this.inner) ? await this.inner : this.inner;\n\t\treturn new FrozenDocumentService(\n\t\t\tresolvedUrl,\n\t\t\tthis.readOnly,\n\t\t\tawait factory?.createDocumentService(resolvedUrl),\n\t\t);\n\t}\n\tasync createContainer(): Promise<IDocumentService> {\n\t\tthrow new Error(\"The FrozenDocumentServiceFactory cannot be used to create containers.\");\n\t}\n}\n\nclass FrozenDocumentService\n\textends TypedEventEmitter<IDocumentServiceEvents>\n\timplements IDocumentService\n{\n\t// Tracks every storage instance handed out by `connectToStorage` so `dispose()` can cascade\n\t// disposal to each one (rejecting their hanging `createBlob` deferreds). A Set rather than\n\t// a single field because `IDocumentService.connectToStorage` is a public API that can be\n\t// called more than once — we cannot assume the Container holds a single instance.\n\tprivate readonly storageServices = new Set<FrozenDocumentStorageService>();\n\n\tconstructor(\n\t\tpublic readonly resolvedUrl: IResolvedUrl,\n\t\tprivate readonly readOnly: boolean,\n\t\tprivate readonly documentService?: IDocumentService,\n\t) {\n\t\tsuper();\n\t\t// When readOnly, advertise the storageOnly policy. The connectionManager short-circuits\n\t\t// on it: it synthesizes a FrozenDeltaStream itself and never calls\n\t\t// connectToDeltaStream, and the readOnlyInfo getter forces the container to read-only\n\t\t// because the live connection is a FrozenDeltaStream.\n\t\t//\n\t\t// Audit (2026-05-05): the only consumer of `policies.storageOnly` as a frozen-container\n\t\t// signal is `ConnectionManager` (synthesizing a `FrozenDeltaStream` when set). All other\n\t\t// matches in the loader/runtime/driver layers are either drivers reading their own\n\t\t// policies (e.g. local-driver) or `IReadOnlyInfo.storageOnly`, which is derived from the\n\t\t// live connection — not the policy. So the writable-frozen container is intentionally\n\t\t// indistinguishable from a normal container at the policies layer; downstream behavior\n\t\t// flows through the live `WritableFrozenDeltaStream` instead.\n\t\tthis.policies = readOnly ? { storageOnly: true } : {};\n\t}\n\n\tpublic readonly policies: IDocumentServicePolicies;\n\tasync connectToStorage(): Promise<IDocumentStorageService> {\n\t\tconst storage = new FrozenDocumentStorageService(\n\t\t\tthis.readOnly,\n\t\t\tawait this.documentService?.connectToStorage(),\n\t\t);\n\t\tthis.storageServices.add(storage);\n\t\treturn storage;\n\t}\n\tasync connectToDeltaStorage(): Promise<IDocumentDeltaStorageService> {\n\t\treturn frozenDocumentDeltaStorageService;\n\t}\n\tasync connectToDeltaStream(_client: IClient): Promise<IDocumentDeltaConnection> {\n\t\tif (this.readOnly) {\n\t\t\t// connectionManager short-circuits via policies.storageOnly before reaching here\n\t\t\t// in the read-only path; reaching this branch indicates a non-connectionManager\n\t\t\t// consumer or a regression of the short-circuit. Throw to surface the misuse\n\t\t\t// rather than silently produce a working stream.\n\t\t\tthrow new Error(\n\t\t\t\t\"FrozenDocumentService is read-only; connectToDeltaStream should not be called (connectionManager short-circuits via policies.storageOnly)\",\n\t\t\t);\n\t\t}\n\t\t// Writable path: hand out a fresh WritableFrozenDeltaStream regardless of client.mode\n\t\t// or whether this is the initial connect or a reconnect. The stream's own mode is\n\t\t// \"read\" (advertising \"write\" would imply quorum membership we cannot honor), and\n\t\t// `ConnectionManager.sendMessages` short-circuits on WritableFrozenDeltaStream so\n\t\t// outbound writes never reach a real network. The per-instance clientId minted in\n\t\t// FrozenDeltaStreamBase prevents pendingStateManager 0x173 on replay across reconnects.\n\t\treturn new WritableFrozenDeltaStream();\n\t}\n\tdispose(error?: unknown): void {\n\t\t// Cascade disposal to each storage instance so any hanging `createBlob` promises (the\n\t\t// writable-frozen pending-blob mechanism) reject and the BlobManager can release its\n\t\t// references. Without this, hung promises remain held by BlobManager closures for the\n\t\t// lifetime of the process.\n\t\tfor (const storage of this.storageServices) {\n\t\t\tstorage.dispose();\n\t\t}\n\t\tthis.storageServices.clear();\n\t\t// Forward disposal to the wrapped service. We own its lifetime (it was created for us\n\t\t// by the wrapping factory and is never exposed to callers), so the contract from\n\t\t// `IDocumentService.dispose` (\"called by storage consumer when done with storage\")\n\t\t// applies here.\n\t\tthis.documentService?.dispose(error);\n\t}\n}\n\nconst frozenDocumentStorageServiceHandler = (): never => {\n\tthrow new Error(\"Operations are not supported on the FrozenDocumentStorageService.\");\n};\n\nclass FrozenDocumentStorageService implements IDocumentStorageService, IDisposable {\n\t// Single deferred shared by every in-flight `createBlob` call. The writable-frozen\n\t// `createBlob` returns this promise so the BlobManager keeps the blob in `uploading`\n\t// state (see comment in the constructor). Rejecting the deferred on disposal fans the\n\t// rejection out to every awaiter at once — and to any future `createBlob` calls too,\n\t// since they receive the already-rejected promise.\n\tprivate readonly disposalDeferred: {\n\t\treadonly promise: Promise<never>;\n\t\treadonly reject: (error: Error) => void;\n\t};\n\n\tprivate _disposed = false;\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\n\tconstructor(\n\t\treadOnly: boolean,\n\t\tprivate readonly documentStorageService?: IDocumentStorageService,\n\t) {\n\t\tlet rejectFn!: (error: Error) => void;\n\t\tconst promise = new Promise<never>((_, reject) => {\n\t\t\trejectFn = reject;\n\t\t});\n\t\t// Attach a no-op catch so node doesn't log an unhandled-rejection warning when\n\t\t// dispose runs before any caller has awaited the promise. Callers awaiting the\n\t\t// original promise still observe the rejection.\n\t\tpromise.catch(() => {});\n\t\tthis.disposalDeferred = { promise, reject: rejectFn };\n\n\t\t// In the writable-frozen path, `createBlob` returns a never-resolving promise instead\n\t\t// of throwing. This keeps the BlobManager's `localBlobCache` entry in the `uploading`\n\t\t// state: `getPendingBlobs` downgrades `uploading` blobs to `localOnly` in pending\n\t\t// state, so the blob survives `getPendingLocalState`. A subsequent live load runs\n\t\t// `sharePendingBlobs`, which re-enters `uploadAndAttach` against the real storage to\n\t\t// complete the upload. Throwing here would instead delete the cache entry (in\n\t\t// `uploadAndAttach`'s catch handler) and lose the blob — defeating the whole point of\n\t\t// accruing pending state.\n\t\tthis.createBlob = readOnly\n\t\t\t? frozenDocumentStorageServiceHandler\n\t\t\t: async () => this.disposalDeferred.promise;\n\t}\n\n\tgetSnapshotTree = frozenDocumentStorageServiceHandler;\n\tgetSnapshot = frozenDocumentStorageServiceHandler;\n\tgetVersions = frozenDocumentStorageServiceHandler;\n\tcreateBlob: IDocumentStorageService[\"createBlob\"];\n\treadBlob =\n\t\tthis.documentStorageService?.readBlob.bind(this.documentStorageService) ??\n\t\tfrozenDocumentStorageServiceHandler;\n\tuploadSummaryWithContext = frozenDocumentStorageServiceHandler;\n\tdownloadSummary = frozenDocumentStorageServiceHandler;\n\n\tpublic dispose(): void {\n\t\tif (this._disposed) {\n\t\t\treturn;\n\t\t}\n\t\tthis._disposed = true;\n\t\t// Don't propagate any caller-supplied error here. `IDocumentService.dispose` already\n\t\t// logs the Container's error path; the createBlob deferred's consumer (BlobManager)\n\t\t// only needs the \"this storage is going away\" signal — not a chain of error causes.\n\t\tthis.disposalDeferred.reject(new Error(\"FrozenDocumentStorageService is disposed\"));\n\t}\n}\n\nconst frozenDocumentDeltaStorageService: IDocumentDeltaStorageService = {\n\tfetchMessages: () => ({\n\t\tread: async () => ({\n\t\t\tdone: true,\n\t\t}),\n\t}),\n};\n\nconst clientFrozenDeltaStream: IClient = {\n\tmode: \"read\",\n\tdetails: { capabilities: { interactive: true } },\n\tpermission: [],\n\tuser: { id: \"storage-only client\" }, // we need some \"fake\" ID here.\n\tscopes: [],\n};\n\nconst clientIdFrozenDeltaStream: string = \"storage-only client\";\n\n// Cast rationale: ITokenClaims requires tenantId/documentId/user/iat/exp/ver, but a frozen\n// delta stream has no tenant or session to draw real values from — it's a synthetic\n// in-process connection that never reaches a service. Inventing sentinel values would imply\n// quorum membership we cannot honor; only `scopes` actually drives behavior here (DocRead vs\n// DocWrite gates readOnlyInfo). The cast is the honest representation of \"this connection\n// has no claims worth populating.\"\n/* eslint-disable @typescript-eslint/consistent-type-assertions */\nconst readOnlyClaims: ITokenClaims = { scopes: [ScopeType.DocRead] } as ITokenClaims;\nconst writableClaims: ITokenClaims = {\n\tscopes: [ScopeType.DocRead, ScopeType.DocWrite],\n} as ITokenClaims;\n/* eslint-enable @typescript-eslint/consistent-type-assertions */\n\n/**\n * Inert `IDocumentDeltaConnection` for frozen container loads. Has no server upstream:\n * op and signal streams are empty, and `initialClients` contains only its own synthetic\n * read-only client — which lets the connection state handler observe \"self\" in the audience\n * and transition the container to Connected without waiting for a real join op or signal.\n *\n * Two concrete variants share this base — see their JSDoc for variant-specific details:\n *\n * - {@link FrozenDeltaStream} — read-only.\n * - {@link WritableFrozenDeltaStream} — writable.\n *\n * Both variants nack any incoming `submit`: this connection has no upstream and\n * `ConnectionManager.sendMessages` recognizes `WritableFrozenDeltaStream` and drops messages\n * before they reach `submit`, so under normal flow it should never fire. A nack reaching the\n * connectionManager surfaces the misuse — and may close the container — which is the right\n * defensive signal that something has bypassed the expected flow.\n *\n * `submitSignal` is a silent no-op for both variants. Signals are ephemeral and best-effort —\n * runtime/presence subsystems may submit them at any point in the writable-frozen lifetime, and\n * dropping them is the correct behavior here (we have no upstream). Closing the container or\n * triggering a reconnect on a stray signal would be strictly worse than dropping it.\n */\nabstract class FrozenDeltaStreamBase\n\textends TypedEventEmitter<IDocumentDeltaConnectionEvents>\n\timplements IDocumentDeltaConnection, IDisposable\n{\n\tpublic readonly clientId: string;\n\tpublic readonly claims: ITokenClaims;\n\tpublic readonly initialClients: ISignalClient[];\n\tpublic readonly mode: ConnectionMode = \"read\";\n\tpublic readonly existing: boolean = true;\n\tpublic readonly maxMessageSize: number = 0;\n\tpublic readonly version: string = \"\";\n\tpublic readonly initialMessages: ISequencedDocumentMessage[] = [];\n\tpublic readonly initialSignals: ISignalMessage[] = [];\n\tpublic readonly serviceConfiguration: IClientConfiguration = {\n\t\tmaxMessageSize: 0,\n\t\tblockSize: 0,\n\t};\n\tpublic readonly checkpointSequenceNumber?: number | undefined = undefined;\n\n\tconstructor(clientId: string, claims: ITokenClaims) {\n\t\tsuper();\n\t\tthis.clientId = clientId;\n\t\tthis.claims = claims;\n\t\t// initialClients mirrors clientId so the audience handler observes \"self\" and\n\t\t// transitions the container to Connected without waiting for a real join op or signal.\n\t\tthis.initialClients = [{ client: clientFrozenDeltaStream, clientId }];\n\t}\n\n\tsubmit(messages: IDocumentMessage[]): void {\n\t\t// Defensive nack: nothing should send on a frozen delta stream. If this fires, an\n\t\t// invariant in connectionManager has changed and we want it to surface loudly.\n\t\tthis.emit(\n\t\t\t\"nack\",\n\t\t\tthis.clientId,\n\t\t\tmessages.map((operation) => ({\n\t\t\t\toperation,\n\t\t\t\tcontent: { message: \"Cannot submit on a frozen delta stream\", code: 403 },\n\t\t\t})),\n\t\t);\n\t}\n\n\tsubmitSignal(_message: unknown): void {\n\t\t// Intentional no-op. See class JSDoc for rationale.\n\t}\n\n\tprivate _disposed = false;\n\tpublic get disposed(): boolean {\n\t\treturn this._disposed;\n\t}\n\tpublic dispose(): void {\n\t\tthis._disposed = true;\n\t}\n}\n\n/**\n * Read-only variant of {@link FrozenDeltaStreamBase}. Claims show only `DocRead`. Used by\n * storage-only loads (where `connectionManager` synthesizes one directly via\n * `policies.storageOnly`) and by the forbidden / out-of-storage fallback paths.\n * {@link isFrozenDeltaStreamConnection} matches this variant and drives the read-only forcing\n * in `ConnectionManager.readOnlyInfo`. Uses the historical `\"storage-only client\"` constant\n * `clientId`, preserving existing behavior for any consumer that keys off it.\n *\n * `storageOnlyReason` and `readonlyConnectionReason` are surfaced through `IContainer.readOnlyInfo`\n * for diagnostics on the fallback paths (`isDeltaStreamConnectionForbiddenError`,\n * `outOfStorageError`).\n */\nexport class FrozenDeltaStream extends FrozenDeltaStreamBase {\n\tpublic readonly storageOnlyReason: string | undefined;\n\tpublic readonly readonlyConnectionReason: IConnectionStateChangeReason | undefined;\n\n\tconstructor(options?: {\n\t\tstorageOnlyReason?: string;\n\t\treadonlyConnectionReason?: IConnectionStateChangeReason;\n\t}) {\n\t\t// Constant clientId: preserves the pre-PR `\"storage-only client\"` identity for any\n\t\t// consumer that keys off it. The 0x173 replay-assert risk that motivates per-instance\n\t\t// clientIds applies only to the writable variant, where the runtime accumulates dirty\n\t\t// pending ops across reconnects; the read-only variant does not.\n\t\tsuper(clientIdFrozenDeltaStream, readOnlyClaims);\n\t\tthis.storageOnlyReason = options?.storageOnlyReason;\n\t\tthis.readonlyConnectionReason = options?.readonlyConnectionReason;\n\t}\n}\n\n/**\n * Variant of {@link FrozenDeltaStreamBase} that appears to support writing but remains\n * \"frozen\" — no messages are actually sent or received. The stream itself does not enforce\n * the no-send guarantee; that lives in `ConnectionManager.sendMessages`, which recognizes\n * any `WritableFrozenDeltaStream` (via {@link isWritableFrozenDeltaStreamConnection}) and\n * short-circuits before its read-mode upgrade branch. Submitted ops are dropped at the\n * connection-manager layer, so the container stays `Connected` and the runtime accumulates\n * them in `pendingStateManager`.\n *\n * \"Appears writable\" mechanics: claims include `DocWrite` so the container surfaces as\n * writable; not matched by {@link isFrozenDeltaStreamConnection}, so `readOnlyInfo` reports\n * `readonly: false`. Connection mode stays `\"read\"` (advertising `\"write\"` would imply quorum\n * membership we cannot honor).\n *\n * Each instance mints a fresh `frozen-delta-stream/<uuid>` `clientId` to avoid\n * `pendingStateManager` `0x173` (`replayPendingStates called twice for same clientId!`) on\n * reconnect with dirty pending ops. Sibling (not subclass) of `FrozenDeltaStream` so\n * `instanceof` cleanly distinguishes the two for `ConnectionManager`'s short-circuits.\n */\nexport class WritableFrozenDeltaStream extends FrozenDeltaStreamBase {\n\tconstructor() {\n\t\tsuper(`frozen-delta-stream/${uuid()}`, writableClaims);\n\t}\n}\n\n/**\n * Recognizes the read-only variant of {@link FrozenDeltaStreamBase}. Drives the storage-only\n * forcing in `ConnectionManager.readOnlyInfo`: only the read-only variant should make the\n * container surface as read-only. {@link WritableFrozenDeltaStream} is a sibling class, not\n * a subclass, so `instanceof FrozenDeltaStream` already excludes it.\n */\nexport function isFrozenDeltaStreamConnection(\n\tconnection: unknown,\n): connection is FrozenDeltaStream {\n\treturn connection instanceof FrozenDeltaStream;\n}\n\n/**\n * Recognizes the writable variant of {@link FrozenDeltaStreamBase}. Drives the\n * `ConnectionManager.sendMessages` short-circuit: writable-frozen submits must be dropped at\n * the network layer instead of triggering a read→write reconnect. Sibling (not subclass) of\n * {@link FrozenDeltaStream}, so `instanceof WritableFrozenDeltaStream` excludes the read-only\n * variant.\n */\nexport function isWritableFrozenDeltaStreamConnection(\n\tconnection: unknown,\n): connection is WritableFrozenDeltaStream {\n\treturn connection instanceof WritableFrozenDeltaStream;\n}\n"]}
|