@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/dist/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/dist/frozenServices.js
CHANGED
|
@@ -4,35 +4,53 @@
|
|
|
4
4
|
* Licensed under the MIT License.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.isFrozenDeltaStreamConnection = exports.FrozenDeltaStream = exports.FrozenDocumentServiceFactory = exports.createFrozenDocumentServiceFactory = void 0;
|
|
7
|
+
exports.isWritableFrozenDeltaStreamConnection = exports.isFrozenDeltaStreamConnection = exports.WritableFrozenDeltaStream = exports.FrozenDeltaStream = exports.FrozenDocumentServiceFactory = exports.createFrozenDocumentServiceFactory = void 0;
|
|
8
8
|
const client_utils_1 = require("@fluid-internal/client-utils");
|
|
9
9
|
const internal_1 = require("@fluidframework/core-utils/internal");
|
|
10
10
|
const internal_2 = require("@fluidframework/driver-definitions/internal");
|
|
11
|
+
const uuid_1 = require("uuid");
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Creates an `IDocumentServiceFactory` that produces a "frozen" document service: one whose
|
|
14
|
+
* delta stream never sends or receives ops, and whose storage service only supports
|
|
15
|
+
* `IDocumentStorageService.readBlob`. Used to load a container from pending local state
|
|
16
|
+
* without re-establishing a live connection.
|
|
14
17
|
*
|
|
15
|
-
* @param
|
|
16
|
-
*
|
|
18
|
+
* @param factory - The underlying factory to wrap. Its storage backs blob reads; all other
|
|
19
|
+
* storage operations throw. May be omitted when blob fetches are not required.
|
|
20
|
+
* @param readOnly - When `true` (the default), the document service advertises the
|
|
21
|
+
* `IDocumentServicePolicies.storageOnly` policy, which causes the loader to surface the
|
|
22
|
+
* container as read-only (see `IContainer.readOnlyInfo`).
|
|
23
|
+
*
|
|
24
|
+
* When `false`, the container is loaded as writable so the runtime will accept DDS submissions.
|
|
25
|
+
* The connection itself stays `Connected`: `ConnectionManager.sendMessages` recognizes the
|
|
26
|
+
* `WritableFrozenDeltaStream` as the live connection and short-circuits — the message is dropped
|
|
27
|
+
* at the network layer rather than triggering a read→write reconnect. Local DDS state continues
|
|
28
|
+
* to update via optimistic apply, and submitted ops accumulate in the runtime's pending-state
|
|
29
|
+
* manager, which is exactly the state needed to capture pending local state. Use `false` when
|
|
30
|
+
* callers want to accrue and capture pending state without publishing it.
|
|
31
|
+
* @returns A factory that produces frozen document services.
|
|
17
32
|
* @legacy @alpha
|
|
18
33
|
*/
|
|
19
|
-
function createFrozenDocumentServiceFactory(factory) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
function createFrozenDocumentServiceFactory(factory, readOnly = true) {
|
|
35
|
+
if (factory instanceof FrozenDocumentServiceFactory) {
|
|
36
|
+
// Already wrapped. Reuse if readOnly matches; otherwise unwrap and rewrap so the caller's
|
|
37
|
+
// most recent readOnly intent wins (silently honoring caller intent rather than dropping
|
|
38
|
+
// the new argument).
|
|
39
|
+
return factory.readOnly === readOnly
|
|
40
|
+
? factory
|
|
41
|
+
: new FrozenDocumentServiceFactory(readOnly, factory.inner);
|
|
42
|
+
}
|
|
43
|
+
return new FrozenDocumentServiceFactory(readOnly, factory);
|
|
24
44
|
}
|
|
25
45
|
exports.createFrozenDocumentServiceFactory = createFrozenDocumentServiceFactory;
|
|
26
46
|
class FrozenDocumentServiceFactory {
|
|
27
|
-
constructor(
|
|
28
|
-
this.
|
|
47
|
+
constructor(readOnly, inner) {
|
|
48
|
+
this.readOnly = readOnly;
|
|
49
|
+
this.inner = inner;
|
|
29
50
|
}
|
|
30
51
|
async createDocumentService(resolvedUrl) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
factory = await this.documentServiceFactory;
|
|
34
|
-
}
|
|
35
|
-
return new FrozenDocumentService(resolvedUrl, await factory?.createDocumentService(resolvedUrl));
|
|
52
|
+
const factory = (0, internal_1.isPromiseLike)(this.inner) ? await this.inner : this.inner;
|
|
53
|
+
return new FrozenDocumentService(resolvedUrl, this.readOnly, await factory?.createDocumentService(resolvedUrl));
|
|
36
54
|
}
|
|
37
55
|
async createContainer() {
|
|
38
56
|
throw new Error("The FrozenDocumentServiceFactory cannot be used to create containers.");
|
|
@@ -40,39 +58,117 @@ class FrozenDocumentServiceFactory {
|
|
|
40
58
|
}
|
|
41
59
|
exports.FrozenDocumentServiceFactory = FrozenDocumentServiceFactory;
|
|
42
60
|
class FrozenDocumentService extends client_utils_1.TypedEventEmitter {
|
|
43
|
-
constructor(resolvedUrl, documentService) {
|
|
61
|
+
constructor(resolvedUrl, readOnly, documentService) {
|
|
44
62
|
super();
|
|
45
63
|
this.resolvedUrl = resolvedUrl;
|
|
64
|
+
this.readOnly = readOnly;
|
|
46
65
|
this.documentService = documentService;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
66
|
+
// Tracks every storage instance handed out by `connectToStorage` so `dispose()` can cascade
|
|
67
|
+
// disposal to each one (rejecting their hanging `createBlob` deferreds). A Set rather than
|
|
68
|
+
// a single field because `IDocumentService.connectToStorage` is a public API that can be
|
|
69
|
+
// called more than once — we cannot assume the Container holds a single instance.
|
|
70
|
+
this.storageServices = new Set();
|
|
71
|
+
// When readOnly, advertise the storageOnly policy. The connectionManager short-circuits
|
|
72
|
+
// on it: it synthesizes a FrozenDeltaStream itself and never calls
|
|
73
|
+
// connectToDeltaStream, and the readOnlyInfo getter forces the container to read-only
|
|
74
|
+
// because the live connection is a FrozenDeltaStream.
|
|
75
|
+
//
|
|
76
|
+
// Audit (2026-05-05): the only consumer of `policies.storageOnly` as a frozen-container
|
|
77
|
+
// signal is `ConnectionManager` (synthesizing a `FrozenDeltaStream` when set). All other
|
|
78
|
+
// matches in the loader/runtime/driver layers are either drivers reading their own
|
|
79
|
+
// policies (e.g. local-driver) or `IReadOnlyInfo.storageOnly`, which is derived from the
|
|
80
|
+
// live connection — not the policy. So the writable-frozen container is intentionally
|
|
81
|
+
// indistinguishable from a normal container at the policies layer; downstream behavior
|
|
82
|
+
// flows through the live `WritableFrozenDeltaStream` instead.
|
|
83
|
+
this.policies = readOnly ? { storageOnly: true } : {};
|
|
50
84
|
}
|
|
51
85
|
async connectToStorage() {
|
|
52
|
-
|
|
86
|
+
const storage = new FrozenDocumentStorageService(this.readOnly, await this.documentService?.connectToStorage());
|
|
87
|
+
this.storageServices.add(storage);
|
|
88
|
+
return storage;
|
|
53
89
|
}
|
|
54
90
|
async connectToDeltaStorage() {
|
|
55
91
|
return frozenDocumentDeltaStorageService;
|
|
56
92
|
}
|
|
57
|
-
async connectToDeltaStream(
|
|
58
|
-
|
|
93
|
+
async connectToDeltaStream(_client) {
|
|
94
|
+
if (this.readOnly) {
|
|
95
|
+
// connectionManager short-circuits via policies.storageOnly before reaching here
|
|
96
|
+
// in the read-only path; reaching this branch indicates a non-connectionManager
|
|
97
|
+
// consumer or a regression of the short-circuit. Throw to surface the misuse
|
|
98
|
+
// rather than silently produce a working stream.
|
|
99
|
+
throw new Error("FrozenDocumentService is read-only; connectToDeltaStream should not be called (connectionManager short-circuits via policies.storageOnly)");
|
|
100
|
+
}
|
|
101
|
+
// Writable path: hand out a fresh WritableFrozenDeltaStream regardless of client.mode
|
|
102
|
+
// or whether this is the initial connect or a reconnect. The stream's own mode is
|
|
103
|
+
// "read" (advertising "write" would imply quorum membership we cannot honor), and
|
|
104
|
+
// `ConnectionManager.sendMessages` short-circuits on WritableFrozenDeltaStream so
|
|
105
|
+
// outbound writes never reach a real network. The per-instance clientId minted in
|
|
106
|
+
// FrozenDeltaStreamBase prevents pendingStateManager 0x173 on replay across reconnects.
|
|
107
|
+
return new WritableFrozenDeltaStream();
|
|
108
|
+
}
|
|
109
|
+
dispose(error) {
|
|
110
|
+
// Cascade disposal to each storage instance so any hanging `createBlob` promises (the
|
|
111
|
+
// writable-frozen pending-blob mechanism) reject and the BlobManager can release its
|
|
112
|
+
// references. Without this, hung promises remain held by BlobManager closures for the
|
|
113
|
+
// lifetime of the process.
|
|
114
|
+
for (const storage of this.storageServices) {
|
|
115
|
+
storage.dispose();
|
|
116
|
+
}
|
|
117
|
+
this.storageServices.clear();
|
|
118
|
+
// Forward disposal to the wrapped service. We own its lifetime (it was created for us
|
|
119
|
+
// by the wrapping factory and is never exposed to callers), so the contract from
|
|
120
|
+
// `IDocumentService.dispose` ("called by storage consumer when done with storage")
|
|
121
|
+
// applies here.
|
|
122
|
+
this.documentService?.dispose(error);
|
|
59
123
|
}
|
|
60
|
-
dispose() { }
|
|
61
124
|
}
|
|
62
125
|
const frozenDocumentStorageServiceHandler = () => {
|
|
63
126
|
throw new Error("Operations are not supported on the FrozenDocumentStorageService.");
|
|
64
127
|
};
|
|
65
128
|
class FrozenDocumentStorageService {
|
|
66
|
-
|
|
129
|
+
get disposed() {
|
|
130
|
+
return this._disposed;
|
|
131
|
+
}
|
|
132
|
+
constructor(readOnly, documentStorageService) {
|
|
67
133
|
this.documentStorageService = documentStorageService;
|
|
134
|
+
this._disposed = false;
|
|
68
135
|
this.getSnapshotTree = frozenDocumentStorageServiceHandler;
|
|
69
136
|
this.getSnapshot = frozenDocumentStorageServiceHandler;
|
|
70
137
|
this.getVersions = frozenDocumentStorageServiceHandler;
|
|
71
|
-
this.createBlob = frozenDocumentStorageServiceHandler;
|
|
72
138
|
this.readBlob = this.documentStorageService?.readBlob.bind(this.documentStorageService) ??
|
|
73
139
|
frozenDocumentStorageServiceHandler;
|
|
74
140
|
this.uploadSummaryWithContext = frozenDocumentStorageServiceHandler;
|
|
75
141
|
this.downloadSummary = frozenDocumentStorageServiceHandler;
|
|
142
|
+
let rejectFn;
|
|
143
|
+
const promise = new Promise((_, reject) => {
|
|
144
|
+
rejectFn = reject;
|
|
145
|
+
});
|
|
146
|
+
// Attach a no-op catch so node doesn't log an unhandled-rejection warning when
|
|
147
|
+
// dispose runs before any caller has awaited the promise. Callers awaiting the
|
|
148
|
+
// original promise still observe the rejection.
|
|
149
|
+
promise.catch(() => { });
|
|
150
|
+
this.disposalDeferred = { promise, reject: rejectFn };
|
|
151
|
+
// In the writable-frozen path, `createBlob` returns a never-resolving promise instead
|
|
152
|
+
// of throwing. This keeps the BlobManager's `localBlobCache` entry in the `uploading`
|
|
153
|
+
// state: `getPendingBlobs` downgrades `uploading` blobs to `localOnly` in pending
|
|
154
|
+
// state, so the blob survives `getPendingLocalState`. A subsequent live load runs
|
|
155
|
+
// `sharePendingBlobs`, which re-enters `uploadAndAttach` against the real storage to
|
|
156
|
+
// complete the upload. Throwing here would instead delete the cache entry (in
|
|
157
|
+
// `uploadAndAttach`'s catch handler) and lose the blob — defeating the whole point of
|
|
158
|
+
// accruing pending state.
|
|
159
|
+
this.createBlob = readOnly
|
|
160
|
+
? frozenDocumentStorageServiceHandler
|
|
161
|
+
: async () => this.disposalDeferred.promise;
|
|
162
|
+
}
|
|
163
|
+
dispose() {
|
|
164
|
+
if (this._disposed) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this._disposed = true;
|
|
168
|
+
// Don't propagate any caller-supplied error here. `IDocumentService.dispose` already
|
|
169
|
+
// logs the Container's error path; the createBlob deferred's consumer (BlobManager)
|
|
170
|
+
// only needs the "this storage is going away" signal — not a chain of error causes.
|
|
171
|
+
this.disposalDeferred.reject(new Error("FrozenDocumentStorageService is disposed"));
|
|
76
172
|
}
|
|
77
173
|
}
|
|
78
174
|
const frozenDocumentDeltaStorageService = {
|
|
@@ -90,54 +186,71 @@ const clientFrozenDeltaStream = {
|
|
|
90
186
|
scopes: [],
|
|
91
187
|
};
|
|
92
188
|
const clientIdFrozenDeltaStream = "storage-only client";
|
|
189
|
+
// Cast rationale: ITokenClaims requires tenantId/documentId/user/iat/exp/ver, but a frozen
|
|
190
|
+
// delta stream has no tenant or session to draw real values from — it's a synthetic
|
|
191
|
+
// in-process connection that never reaches a service. Inventing sentinel values would imply
|
|
192
|
+
// quorum membership we cannot honor; only `scopes` actually drives behavior here (DocRead vs
|
|
193
|
+
// DocWrite gates readOnlyInfo). The cast is the honest representation of "this connection
|
|
194
|
+
// has no claims worth populating."
|
|
195
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
196
|
+
const readOnlyClaims = { scopes: [internal_2.ScopeType.DocRead] };
|
|
197
|
+
const writableClaims = {
|
|
198
|
+
scopes: [internal_2.ScopeType.DocRead, internal_2.ScopeType.DocWrite],
|
|
199
|
+
};
|
|
200
|
+
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
93
201
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
202
|
+
* Inert `IDocumentDeltaConnection` for frozen container loads. Has no server upstream:
|
|
203
|
+
* op and signal streams are empty, and `initialClients` contains only its own synthetic
|
|
204
|
+
* read-only client — which lets the connection state handler observe "self" in the audience
|
|
205
|
+
* and transition the container to Connected without waiting for a real join op or signal.
|
|
206
|
+
*
|
|
207
|
+
* Two concrete variants share this base — see their JSDoc for variant-specific details:
|
|
208
|
+
*
|
|
209
|
+
* - {@link FrozenDeltaStream} — read-only.
|
|
210
|
+
* - {@link WritableFrozenDeltaStream} — writable.
|
|
211
|
+
*
|
|
212
|
+
* Both variants nack any incoming `submit`: this connection has no upstream and
|
|
213
|
+
* `ConnectionManager.sendMessages` recognizes `WritableFrozenDeltaStream` and drops messages
|
|
214
|
+
* before they reach `submit`, so under normal flow it should never fire. A nack reaching the
|
|
215
|
+
* connectionManager surfaces the misuse — and may close the container — which is the right
|
|
216
|
+
* defensive signal that something has bypassed the expected flow.
|
|
217
|
+
*
|
|
218
|
+
* `submitSignal` is a silent no-op for both variants. Signals are ephemeral and best-effort —
|
|
219
|
+
* runtime/presence subsystems may submit them at any point in the writable-frozen lifetime, and
|
|
220
|
+
* dropping them is the correct behavior here (we have no upstream). Closing the container or
|
|
221
|
+
* triggering a reconnect on a stray signal would be strictly worse than dropping it.
|
|
96
222
|
*/
|
|
97
|
-
class
|
|
98
|
-
|
|
99
|
-
* Connection which is not connected to socket.
|
|
100
|
-
* @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.
|
|
101
|
-
* @param readonlyConnectionReason - reason/error if any which lead to using FrozenDeltaStream.
|
|
102
|
-
*/
|
|
103
|
-
constructor(storageOnlyReason, readonlyConnectionReason) {
|
|
223
|
+
class FrozenDeltaStreamBase extends client_utils_1.TypedEventEmitter {
|
|
224
|
+
constructor(clientId, claims) {
|
|
104
225
|
super();
|
|
105
|
-
this.storageOnlyReason = storageOnlyReason;
|
|
106
|
-
this.readonlyConnectionReason = readonlyConnectionReason;
|
|
107
|
-
this.clientId = clientIdFrozenDeltaStream;
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
109
|
-
this.claims = {
|
|
110
|
-
scopes: [internal_2.ScopeType.DocRead],
|
|
111
|
-
};
|
|
112
226
|
this.mode = "read";
|
|
113
227
|
this.existing = true;
|
|
114
228
|
this.maxMessageSize = 0;
|
|
115
229
|
this.version = "";
|
|
116
230
|
this.initialMessages = [];
|
|
117
231
|
this.initialSignals = [];
|
|
118
|
-
this.initialClients = [
|
|
119
|
-
{ client: clientFrozenDeltaStream, clientId: clientIdFrozenDeltaStream },
|
|
120
|
-
];
|
|
121
232
|
this.serviceConfiguration = {
|
|
122
233
|
maxMessageSize: 0,
|
|
123
234
|
blockSize: 0,
|
|
124
235
|
};
|
|
125
236
|
this.checkpointSequenceNumber = undefined;
|
|
126
237
|
this._disposed = false;
|
|
238
|
+
this.clientId = clientId;
|
|
239
|
+
this.claims = claims;
|
|
240
|
+
// initialClients mirrors clientId so the audience handler observes "self" and
|
|
241
|
+
// transitions the container to Connected without waiting for a real join op or signal.
|
|
242
|
+
this.initialClients = [{ client: clientFrozenDeltaStream, clientId }];
|
|
127
243
|
}
|
|
128
244
|
submit(messages) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
}));
|
|
135
|
-
}
|
|
136
|
-
submitSignal(
|
|
137
|
-
|
|
138
|
-
operation: message,
|
|
139
|
-
content: { message: "Cannot submit signal with storage-only connection", code: 403 },
|
|
140
|
-
});
|
|
245
|
+
// Defensive nack: nothing should send on a frozen delta stream. If this fires, an
|
|
246
|
+
// invariant in connectionManager has changed and we want it to surface loudly.
|
|
247
|
+
this.emit("nack", this.clientId, messages.map((operation) => ({
|
|
248
|
+
operation,
|
|
249
|
+
content: { message: "Cannot submit on a frozen delta stream", code: 403 },
|
|
250
|
+
})));
|
|
251
|
+
}
|
|
252
|
+
submitSignal(_message) {
|
|
253
|
+
// Intentional no-op. See class JSDoc for rationale.
|
|
141
254
|
}
|
|
142
255
|
get disposed() {
|
|
143
256
|
return this._disposed;
|
|
@@ -146,9 +259,74 @@ class FrozenDeltaStream extends client_utils_1.TypedEventEmitter {
|
|
|
146
259
|
this._disposed = true;
|
|
147
260
|
}
|
|
148
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Read-only variant of {@link FrozenDeltaStreamBase}. Claims show only `DocRead`. Used by
|
|
264
|
+
* storage-only loads (where `connectionManager` synthesizes one directly via
|
|
265
|
+
* `policies.storageOnly`) and by the forbidden / out-of-storage fallback paths.
|
|
266
|
+
* {@link isFrozenDeltaStreamConnection} matches this variant and drives the read-only forcing
|
|
267
|
+
* in `ConnectionManager.readOnlyInfo`. Uses the historical `"storage-only client"` constant
|
|
268
|
+
* `clientId`, preserving existing behavior for any consumer that keys off it.
|
|
269
|
+
*
|
|
270
|
+
* `storageOnlyReason` and `readonlyConnectionReason` are surfaced through `IContainer.readOnlyInfo`
|
|
271
|
+
* for diagnostics on the fallback paths (`isDeltaStreamConnectionForbiddenError`,
|
|
272
|
+
* `outOfStorageError`).
|
|
273
|
+
*/
|
|
274
|
+
class FrozenDeltaStream extends FrozenDeltaStreamBase {
|
|
275
|
+
constructor(options) {
|
|
276
|
+
// Constant clientId: preserves the pre-PR `"storage-only client"` identity for any
|
|
277
|
+
// consumer that keys off it. The 0x173 replay-assert risk that motivates per-instance
|
|
278
|
+
// clientIds applies only to the writable variant, where the runtime accumulates dirty
|
|
279
|
+
// pending ops across reconnects; the read-only variant does not.
|
|
280
|
+
super(clientIdFrozenDeltaStream, readOnlyClaims);
|
|
281
|
+
this.storageOnlyReason = options?.storageOnlyReason;
|
|
282
|
+
this.readonlyConnectionReason = options?.readonlyConnectionReason;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
149
285
|
exports.FrozenDeltaStream = FrozenDeltaStream;
|
|
286
|
+
/**
|
|
287
|
+
* Variant of {@link FrozenDeltaStreamBase} that appears to support writing but remains
|
|
288
|
+
* "frozen" — no messages are actually sent or received. The stream itself does not enforce
|
|
289
|
+
* the no-send guarantee; that lives in `ConnectionManager.sendMessages`, which recognizes
|
|
290
|
+
* any `WritableFrozenDeltaStream` (via {@link isWritableFrozenDeltaStreamConnection}) and
|
|
291
|
+
* short-circuits before its read-mode upgrade branch. Submitted ops are dropped at the
|
|
292
|
+
* connection-manager layer, so the container stays `Connected` and the runtime accumulates
|
|
293
|
+
* them in `pendingStateManager`.
|
|
294
|
+
*
|
|
295
|
+
* "Appears writable" mechanics: claims include `DocWrite` so the container surfaces as
|
|
296
|
+
* writable; not matched by {@link isFrozenDeltaStreamConnection}, so `readOnlyInfo` reports
|
|
297
|
+
* `readonly: false`. Connection mode stays `"read"` (advertising `"write"` would imply quorum
|
|
298
|
+
* membership we cannot honor).
|
|
299
|
+
*
|
|
300
|
+
* Each instance mints a fresh `frozen-delta-stream/<uuid>` `clientId` to avoid
|
|
301
|
+
* `pendingStateManager` `0x173` (`replayPendingStates called twice for same clientId!`) on
|
|
302
|
+
* reconnect with dirty pending ops. Sibling (not subclass) of `FrozenDeltaStream` so
|
|
303
|
+
* `instanceof` cleanly distinguishes the two for `ConnectionManager`'s short-circuits.
|
|
304
|
+
*/
|
|
305
|
+
class WritableFrozenDeltaStream extends FrozenDeltaStreamBase {
|
|
306
|
+
constructor() {
|
|
307
|
+
super(`frozen-delta-stream/${(0, uuid_1.v4)()}`, writableClaims);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
exports.WritableFrozenDeltaStream = WritableFrozenDeltaStream;
|
|
311
|
+
/**
|
|
312
|
+
* Recognizes the read-only variant of {@link FrozenDeltaStreamBase}. Drives the storage-only
|
|
313
|
+
* forcing in `ConnectionManager.readOnlyInfo`: only the read-only variant should make the
|
|
314
|
+
* container surface as read-only. {@link WritableFrozenDeltaStream} is a sibling class, not
|
|
315
|
+
* a subclass, so `instanceof FrozenDeltaStream` already excludes it.
|
|
316
|
+
*/
|
|
150
317
|
function isFrozenDeltaStreamConnection(connection) {
|
|
151
318
|
return connection instanceof FrozenDeltaStream;
|
|
152
319
|
}
|
|
153
320
|
exports.isFrozenDeltaStreamConnection = isFrozenDeltaStreamConnection;
|
|
321
|
+
/**
|
|
322
|
+
* Recognizes the writable variant of {@link FrozenDeltaStreamBase}. Drives the
|
|
323
|
+
* `ConnectionManager.sendMessages` short-circuit: writable-frozen submits must be dropped at
|
|
324
|
+
* the network layer instead of triggering a read→write reconnect. Sibling (not subclass) of
|
|
325
|
+
* {@link FrozenDeltaStream}, so `instanceof WritableFrozenDeltaStream` excludes the read-only
|
|
326
|
+
* variant.
|
|
327
|
+
*/
|
|
328
|
+
function isWritableFrozenDeltaStreamConnection(connection) {
|
|
329
|
+
return connection instanceof WritableFrozenDeltaStream;
|
|
330
|
+
}
|
|
331
|
+
exports.isWritableFrozenDeltaStreamConnection = isWritableFrozenDeltaStreamConnection;
|
|
154
332
|
//# sourceMappingURL=frozenServices.js.map
|