@fluidframework/local-driver 2.113.1 → 2.114.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/README.md +2 -0
- package/alpha.d.ts +11 -0
- package/api-extractor/api-extractor-lint-alpha.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-alpha.esm.json +5 -0
- package/api-extractor/api-extractor.current.json +4 -1
- package/api-report/local-driver.alpha.api.md +31 -0
- package/dist/alpha.d.ts +19 -0
- package/dist/ephemeralService.d.ts +177 -0
- package/dist/ephemeralService.d.ts.map +1 -0
- package/dist/ephemeralService.js +295 -0
- package/dist/ephemeralService.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/eslint.config.mts +0 -11
- package/lib/alpha.d.ts +19 -0
- package/lib/ephemeralService.d.ts +177 -0
- package/lib/ephemeralService.d.ts.map +1 -0
- package/lib/ephemeralService.js +288 -0
- package/lib/ephemeralService.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +33 -18
- package/src/ephemeralService.ts +551 -0
- package/src/index.ts +10 -0
- package/src/packageVersion.ts +1 -1
- package/tsconfig.json +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# @fluidframework/local-driver
|
|
2
2
|
|
|
3
|
+
## 2.114.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add new @alpha ServiceClient API for creating and loading Fluid containers ([#27693](https://github.com/microsoft/FluidFramework/pull/27693)) [ee47192d4a](https://github.com/microsoft/FluidFramework/commit/ee47192d4ae91bc28f9154c4d1ead2acad762f3c)
|
|
8
|
+
|
|
9
|
+
This introduces an experimental (`@alpha`), service-agnostic API for working with Fluid containers whose root is an arbitrary data store, along with an in-memory implementation for testing.
|
|
10
|
+
|
|
11
|
+
The new surface is made up of:
|
|
12
|
+
- `ServiceClient` (`@fluidframework/driver-definitions`): the entry point for creating and loading containers. Along with it come the supporting container types (`FluidContainer`, `FluidContainerWithService`, `FluidContainerAttached`), the data store model (`DataStoreKind`, `DataStoreKey`, `DataStoreRegistry`, `DataStoreCreator`), and the generic registry primitives (`Registry`, `RegistryKey`, `lookupInRegistry`, `createBasicRegistryKey`).
|
|
13
|
+
- `defineDataStore` and `sharedObjectRegistryFromIterable` (`@fluidframework/shared-object-base`): build a `DataStoreKind` from a root shared object and a registry of shared object kinds.
|
|
14
|
+
- `defineTreeDataStore` and `instantiateTreeFirstTime` (`@fluidframework/tree`): a SharedTree-specific convenience wrapper that produces a `DataStoreKind` backed by a `TreeView`.
|
|
15
|
+
- `startEphemeralService` (`@fluidframework/local-driver`): starts an in-memory `EphemeralService` for tests. The service owns the lifetime of the in-memory documents and resources, and produces `ServiceClient`s connected to it (via `EphemeralService.newClient` or `EphemeralService.defaultClient`). The helpers `cleanupEphemeralService` and `getDefaultEphemeralService` manage an optional default service instance.
|
|
16
|
+
|
|
17
|
+
Apart from the `@fluidframework/local-driver` helpers (which come from `@fluidframework/local-driver/alpha`), these APIs are also re-exported from `fluid-framework`. None reference any `@legacy` types.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { startEphemeralService } from "@fluidframework/local-driver/alpha";
|
|
23
|
+
import {
|
|
24
|
+
ServiceClient,
|
|
25
|
+
defineTreeDataStore,
|
|
26
|
+
TreeViewConfiguration,
|
|
27
|
+
SchemaFactory,
|
|
28
|
+
} from "fluid-framework/alpha";
|
|
29
|
+
import { strict as assert } from "node:assert";
|
|
30
|
+
|
|
31
|
+
// Start an ephemeral in-memory service and get a ServiceClient connected to it.
|
|
32
|
+
const service = startEphemeralService();
|
|
33
|
+
const client: ServiceClient = service.defaultClient;
|
|
34
|
+
// Define a DataStoreKind which uses a SharedTree.
|
|
35
|
+
// In this case the schema is for a single number with an initializer that starts the it at 1.
|
|
36
|
+
// This schema is captures in the type allowing for strongly typed access to the data in the tree,
|
|
37
|
+
// where the type matches the schema based runtime enforcement of the schema.
|
|
38
|
+
const numberStore = defineTreeDataStore({
|
|
39
|
+
type: "my-app-root",
|
|
40
|
+
config: new TreeViewConfiguration({ schema: SchemaFactory.number }),
|
|
41
|
+
initializer: () => 1,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Create a container in the service with the above DataStoreKind.
|
|
45
|
+
// Ideally this creation would use a service independent API, and only the attach call would be service dependent,
|
|
46
|
+
// but that is not supported yet.
|
|
47
|
+
const detachedContainer1 = await client.createContainer(numberStore);
|
|
48
|
+
const container1 = await detachedContainer1.attach();
|
|
49
|
+
|
|
50
|
+
// We now have easy and type safe access to the data in the tree, which will be synced over the service.
|
|
51
|
+
assert.equal(container1.data.root, 1);
|
|
52
|
+
|
|
53
|
+
// A second client can load the same container from the service, and will see the same data.
|
|
54
|
+
const container2 = await client.loadContainer(container1.id, numberStore);
|
|
55
|
+
assert.equal(container2.data.root, 1);
|
|
56
|
+
|
|
57
|
+
// Both clients can modify the data, and the changes will be synced over the service.
|
|
58
|
+
container2.data.root = 2;
|
|
59
|
+
// Since we are using an ephemeral service, we can await the synchronization using service.synchronize.
|
|
60
|
+
await service.synchronize();
|
|
61
|
+
|
|
62
|
+
// And now the changes are visible for all clients.
|
|
63
|
+
assert.equal(container1.data.root, 2);
|
|
64
|
+
assert.equal(container2.data.root, 2);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Note that this example does a couple of things which are difficult to do with the other API surfaces:
|
|
68
|
+
1. It creates a container, then loads a second copy of it, allowing for collaboration. There is currently no non-legacy API surface which allows this without spawning a server process. This is also cleaner than the exacting legacy API options, and can replace the test specific APIs for this as well.
|
|
69
|
+
2. It creates a container which has a SharedTree at the root, and nothing else. This avoids depending on legacy DDS implementations, which is great for long-term document support and bundle size. This is currently impossible using `fluid-static`, which forces a special root data store. It is also impossible if using `aqueduct`, which forces a root directory in every data store. It can be done using the low level legacy APIs directly, but this new API for it is much simpler.
|
|
70
|
+
3. There is a common interface all services implement (`ServiceClient`), making the container creation part of the code work for any service implementation.
|
|
71
|
+
|
|
3
72
|
## 2.113.0
|
|
4
73
|
|
|
5
74
|
Dependency updates only.
|
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ For more information on the related support guarantees, see [API Support Levels]
|
|
|
30
30
|
|
|
31
31
|
To access the `public` ([SemVer](https://semver.org/)) APIs, import via `@fluidframework/local-driver` like normal.
|
|
32
32
|
|
|
33
|
+
To access the `alpha` APIs, import via `@fluidframework/local-driver/alpha`.
|
|
34
|
+
|
|
33
35
|
To access the `legacy` APIs, import via `@fluidframework/local-driver/legacy`.
|
|
34
36
|
|
|
35
37
|
## API Documentation
|
package/alpha.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
8
|
+
* Generated by "flub generate entrypoints --outFileLegacyBeta legacy --outDir ./lib --node10TypeCompat" in @fluid-tools/build-cli.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export * from "./lib/alpha.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
3
|
+
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.entrypoint.json",
|
|
4
|
+
"mainEntryPointFilePath": "<projectFolder>/dist/alpha.d.ts"
|
|
5
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
3
|
+
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-lint.entrypoint.json",
|
|
4
|
+
"mainEntryPointFilePath": "<projectFolder>/lib/alpha.d.ts"
|
|
5
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
|
3
3
|
"extends": "<projectFolder>/../../../common/build/build-common/api-extractor-report.esm.current.json",
|
|
4
|
-
"mainEntryPointFilePath": "<projectFolder>/lib/
|
|
4
|
+
"mainEntryPointFilePath": "<projectFolder>/lib/alpha.d.ts",
|
|
5
|
+
"apiReport": {
|
|
6
|
+
"reportVariants": ["public", "beta", "alpha"]
|
|
7
|
+
},
|
|
5
8
|
// Note: excluding server packages until we can update their tags (and release new server versions)
|
|
6
9
|
// TODO: remove this override once server dependencies have been updated.
|
|
7
10
|
"bundledPackages": [
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Alpha API Report File for "@fluidframework/local-driver"
|
|
2
|
+
|
|
3
|
+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
|
|
7
|
+
// @alpha
|
|
8
|
+
export function cleanupEphemeralService(service?: EphemeralService): Promise<void>;
|
|
9
|
+
|
|
10
|
+
// @alpha @sealed
|
|
11
|
+
export interface EphemeralService extends ErasedBaseType<readonly ["EphemeralService"]> {
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
readonly defaultClient: EphemeralServiceClient;
|
|
14
|
+
newClient(options: ServiceOptions): EphemeralServiceClient;
|
|
15
|
+
synchronize(timeoutMilliseconds?: number): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// @alpha @sealed
|
|
19
|
+
export interface EphemeralServiceClient extends ServiceClient {
|
|
20
|
+
readonly service: EphemeralService;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// @alpha
|
|
24
|
+
export function getDefaultEphemeralService(): EphemeralService;
|
|
25
|
+
|
|
26
|
+
// @alpha
|
|
27
|
+
export function startEphemeralService(isDefault?: boolean): EphemeralService;
|
|
28
|
+
|
|
29
|
+
// (No @packageDocumentation comment for this package)
|
|
30
|
+
|
|
31
|
+
```
|
package/dist/alpha.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
8
|
+
* Generated by "flub generate entrypoints --resolutionConditions require --outFileLegacyBeta legacy --outDir ./dist" in @fluid-tools/build-cli.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
// #region @alpha APIs
|
|
13
|
+
EphemeralService,
|
|
14
|
+
EphemeralServiceClient,
|
|
15
|
+
cleanupEphemeralService,
|
|
16
|
+
getDefaultEphemeralService,
|
|
17
|
+
startEphemeralService
|
|
18
|
+
// #endregion
|
|
19
|
+
} from "./index.js";
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { IRequest } from "@fluidframework/core-interfaces";
|
|
6
|
+
import { type ErasedBaseType } from "@fluidframework/core-interfaces/internal";
|
|
7
|
+
import type { DataStoreKind, DataStoreRegistry, FluidContainerAttached, FluidContainerWithService, ServiceClient, ServiceOptions } from "@fluidframework/driver-definitions/internal";
|
|
8
|
+
import { ServiceContainerBase } from "@fluidframework/runtime-utils/internal";
|
|
9
|
+
/**
|
|
10
|
+
* Starts and returns a new {@link EphemeralService}.
|
|
11
|
+
* @param isDefault - Whether this service should saved as the default service for {@link cleanupEphemeralService} to cleanup.
|
|
12
|
+
* Defaults to true.
|
|
13
|
+
* @remarks
|
|
14
|
+
* The returned service owns an in-memory server and holds the documents created through clients connected to it.
|
|
15
|
+
* {@link cleanupEphemeralService} can be used to ensure the service is properly cleaned up (a no-op if stopped/closed already).
|
|
16
|
+
*
|
|
17
|
+
* As a service, it may start timers which may require an explicit `close` to fully free.
|
|
18
|
+
* @alpha
|
|
19
|
+
*/
|
|
20
|
+
export declare function startEphemeralService(isDefault?: boolean): EphemeralService;
|
|
21
|
+
/**
|
|
22
|
+
* Cleans up the service passed in {@link startEphemeralService}, or the {@link getDefaultEphemeralService|default} if none is passed.
|
|
23
|
+
* @remarks
|
|
24
|
+
* This closes the service, and all its containers.
|
|
25
|
+
* This is a good way to ensure the service and its containers leave no lingering timers
|
|
26
|
+
* which could leak memory, trigger asynchronous work or prevent a clean process exit.
|
|
27
|
+
* @alpha
|
|
28
|
+
*/
|
|
29
|
+
export declare function cleanupEphemeralService(service?: EphemeralService): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the default {@link EphemeralService} if one has been {@link startEphemeralService|started}.
|
|
32
|
+
* @throws If no default service is running.
|
|
33
|
+
* @alpha
|
|
34
|
+
*/
|
|
35
|
+
export declare function getDefaultEphemeralService(): EphemeralService;
|
|
36
|
+
/**
|
|
37
|
+
* Internal Options for creating an {@link EphemeralServiceClient}, extending {@link @fluidframework/driver-definitions#ServiceOptions}
|
|
38
|
+
* with the {@link EphemeralService} the client should connect to.
|
|
39
|
+
* @input
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export interface EphemeralServiceOptions extends ServiceOptions {
|
|
43
|
+
/**
|
|
44
|
+
* The service instance to connect to.
|
|
45
|
+
*/
|
|
46
|
+
readonly service: EphemeralService;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* An in-memory Fluid service that can produce connected {@link EphemeralServiceClient}s.
|
|
50
|
+
* @remarks
|
|
51
|
+
* All documents created through clients connected to a given `EphemeralService` are held in-memory by that service.
|
|
52
|
+
* Closing the service (via {@link EphemeralService.close} or {@link cleanupEphemeralService}) closes the connections
|
|
53
|
+
* to any remaining open containers, and cleans up the service's timers.
|
|
54
|
+
*
|
|
55
|
+
* Create one with {@link startEphemeralService}.
|
|
56
|
+
*
|
|
57
|
+
* Most {@link @fluidframework/driver-definitions#ServiceClient} implementations would take in a URL and credentials to connect to a service,
|
|
58
|
+
* but that is not needed for the ephemeral in-memory service.
|
|
59
|
+
* Instead this object representing the actual service instance is provided.
|
|
60
|
+
* @privateRemarks
|
|
61
|
+
* This is separated out from the actual {@link @fluidframework/driver-definitions#ServiceClient} object so that it's possible to create multiple service clients
|
|
62
|
+
* connected to the same service.
|
|
63
|
+
* Doing so is rarely necessary, but would be needed to test multiple clients collaborating on the same
|
|
64
|
+
* document with different minVersionForCollaboration values.
|
|
65
|
+
* This also exposes a place to put APIs for preloading and exporting document contents in the future.
|
|
66
|
+
*
|
|
67
|
+
* This is an erased type: its only implementation is the module-private {@link EphemeralServiceImplementation}, which holds
|
|
68
|
+
* the mutable server and container state so it does not appear on this public type.
|
|
69
|
+
*
|
|
70
|
+
* TODO: formalize this lifecycle with an interface which documents these stages.
|
|
71
|
+
* Lifecycle:
|
|
72
|
+
* The intended lifecycle of an {@link EphemeralService} follows roughly the same pattern as containers:
|
|
73
|
+
*
|
|
74
|
+
* 1. Open: accepts connections from {@link EphemeralServiceClient}s, which can create and load containers.
|
|
75
|
+
* Might have timers and event registrations which can trigger asynchronous work, and retain the object in memory.
|
|
76
|
+
*
|
|
77
|
+
* 2. Closing: asynchronous transition from open to closed. New use should behave as it closed, but may be cleaning up or saving resources asynchronously.
|
|
78
|
+
* Timers and event registrations may still be active, but should be cleaned up by the time the transition to closed completes.
|
|
79
|
+
*
|
|
80
|
+
* 3. Closed: no longer accepts connections from {@link EphemeralServiceClient}s, and all containers connected to it are closed.
|
|
81
|
+
* Should have no subscriptions to events or timers which could retain it in memory or trigger asynchronous work.
|
|
82
|
+
* The object can still be used in a limited capacity (typically just to inspect its status (e.g. `isClosed`), and to view (but not edit) the final state of any containers which were connected to it before it closed.)
|
|
83
|
+
*
|
|
84
|
+
* Events or errors can cause an open to closing transition. Any nonfunctional state, including error states, should be considered as closed (or closing which will transition to closed),
|
|
85
|
+
* and meet the requirements of closed with regards to timers and events.
|
|
86
|
+
*
|
|
87
|
+
* @alpha @sealed
|
|
88
|
+
*/
|
|
89
|
+
export interface EphemeralService extends ErasedBaseType<readonly ["EphemeralService"]> {
|
|
90
|
+
/**
|
|
91
|
+
* Close this service, which closes all containers connected to it and releases its resources.
|
|
92
|
+
* @remarks
|
|
93
|
+
* All documents held by this service are discarded, and any timers it (or its containers) were keeping alive
|
|
94
|
+
* are cleaned up.
|
|
95
|
+
* The returned promise resolves once all asynchronous cleanup (including shutting down the in-memory server)
|
|
96
|
+
* has completed.
|
|
97
|
+
* Closing is idempotent: calling it again after the service is closed resolves without doing anything.
|
|
98
|
+
*/
|
|
99
|
+
close(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Drives all containers connected to this service toward convergence, processing pending operations and
|
|
102
|
+
* waiting for all dirty containers to save.
|
|
103
|
+
*
|
|
104
|
+
* @param timeoutMilliseconds - The maximum time to wait for containers to quiesce, in milliseconds. Defaults to 30_000.
|
|
105
|
+
*
|
|
106
|
+
* @privateRemarks
|
|
107
|
+
* This is a best-effort implementation simplified from `LoaderContainerTracker.ensureSynchronized`.
|
|
108
|
+
* Currently it does not perform receiver-side sequence-number quiescence or wait for join/leave (audience) ops.
|
|
109
|
+
* See `LoaderContainerTracker.ensureSynchronized` for the fuller version this is based on.
|
|
110
|
+
* For the currently exposed API surface, this should be sufficient,
|
|
111
|
+
* but users down casting to internal types might run into some limitations.
|
|
112
|
+
*/
|
|
113
|
+
synchronize(timeoutMilliseconds?: number): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Creates and returns a {@link EphemeralServiceClient} for an in-memory, ephemeral Fluid service.
|
|
116
|
+
*
|
|
117
|
+
* @param options - Options for the client. `minVersionForCollaboration` may be omitted (since all collaborators
|
|
118
|
+
* are in the same process, it defaults to the current version). `service` may be omitted to allocate a new
|
|
119
|
+
* {@link EphemeralService} dedicated to this client, or provided to connect the client to an existing service instance.
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* The service is ephemeral and in-memory: all documents are held by the {@link EphemeralService} the client is
|
|
123
|
+
* connected to, and live for as long as that service is open — independent of whether any container for them is open.
|
|
124
|
+
* A document created and attached (obtaining an `id`) can be loaded by `id` for as long as its service remains open,
|
|
125
|
+
* even after every container for it has been closed.
|
|
126
|
+
* Closing the service (via {@link EphemeralService.close} or {@link cleanupEphemeralService}) discards all of its
|
|
127
|
+
* documents and releases its resources; afterwards those `id`s can no longer be loaded.
|
|
128
|
+
*
|
|
129
|
+
* When no `service` is provided, a new one is allocated for this client (accessible via {@link EphemeralServiceClient.service}).
|
|
130
|
+
* Provide the same {@link EphemeralService} to multiple clients (via `options.service`) to have them collaborate on the
|
|
131
|
+
* same documents, and control that service's lifetime explicitly.
|
|
132
|
+
*
|
|
133
|
+
* Since a service holds timers while open, tests should close the services they use (e.g. via
|
|
134
|
+
* {@link cleanupEphemeralService} in an `afterEach`) to avoid lingering timers that can hang test runners.
|
|
135
|
+
*
|
|
136
|
+
* @privateRemarks
|
|
137
|
+
* TODO: We should provide a way to extract (for potential serialization as test data) and load documents into a service.
|
|
138
|
+
* This is needed to use this API surface for testing reference documents.
|
|
139
|
+
* Ideally we would provide a service agnostic way to do the export, but likely only support loading them into the local service.
|
|
140
|
+
* This can be done via an API on FluidContainer (or a free function taking one) to do the export, then adding a
|
|
141
|
+
* service specific API (on {@link EphemeralService}) to load from the export format and return the ID of the loaded document.
|
|
142
|
+
*/
|
|
143
|
+
newClient(options: ServiceOptions): EphemeralServiceClient;
|
|
144
|
+
/**
|
|
145
|
+
* A client connected to this service using the default options.
|
|
146
|
+
*/
|
|
147
|
+
readonly defaultClient: EphemeralServiceClient;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A {@link @fluidframework/driver-definitions#ServiceClient} connected to a specific {@link EphemeralService}.
|
|
151
|
+
* @alpha @sealed
|
|
152
|
+
*/
|
|
153
|
+
export interface EphemeralServiceClient extends ServiceClient {
|
|
154
|
+
/**
|
|
155
|
+
* The service instance this client is connected to.
|
|
156
|
+
*/
|
|
157
|
+
readonly service: EphemeralService;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* A Fluid container backed by an ephemeral (in-memory) local service, implementing
|
|
161
|
+
* {@link @fluidframework/driver-definitions#FluidContainerWithService}.
|
|
162
|
+
*
|
|
163
|
+
* @remarks
|
|
164
|
+
* Data is stored in-memory by the {@link EphemeralService} the container's client is connected to (see
|
|
165
|
+
* {@link EphemeralServiceContainer.service}), enabling side-by-side collaboration testing without a real server.
|
|
166
|
+
*
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
export declare class EphemeralServiceContainer<TData> extends ServiceContainerBase<TData, EphemeralServiceOptions> implements FluidContainerWithService<TData> {
|
|
170
|
+
readonly service: EphemeralService;
|
|
171
|
+
static createDetached<T>(registry: DataStoreRegistry<T>, options: EphemeralServiceOptions, root: DataStoreKind<T>): Promise<EphemeralServiceContainer<T>>;
|
|
172
|
+
static load<T>(registry: DataStoreRegistry<T>, options: EphemeralServiceOptions, id: string): Promise<EphemeralServiceContainer<T> & FluidContainerAttached<T>>;
|
|
173
|
+
private constructor();
|
|
174
|
+
close(): void;
|
|
175
|
+
protected createAttachRequest(): IRequest;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=ephemeralService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ephemeralService.d.ts","sourceRoot":"","sources":["../src/ephemeralService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAEN,KAAK,cAAc,EACnB,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EACX,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,yBAAyB,EAEzB,aAAa,EACb,cAAc,EACd,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAMN,oBAAoB,EACpB,MAAM,wCAAwC,CAAC;AAWhD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,UAAO,GAAG,gBAAgB,CAUxE;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASvF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,gBAAgB,CAK7D;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC9D;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,WAAW,gBAAiB,SAAQ,cAAc,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IACtF;;;;;;;;OAQG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,sBAAsB,CAAC;IAE3D;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;CAC/C;AA0KD;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC5D;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;CACnC;AAuDD;;;;;;;;;GASG;AACH,qBAAa,yBAAyB,CAAC,KAAK,CAC3C,SAAQ,oBAAoB,CAAC,KAAK,EAAE,uBAAuB,CAC3D,YAAW,yBAAyB,CAAC,KAAK,CAAC;IAE3C,SAAgB,OAAO,EAAE,gBAAgB,CAAC;WAEtB,cAAc,CAAC,CAAC,EACnC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC9B,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC;WAuBpB,IAAI,CAAC,CAAC,EACzB,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC9B,OAAO,EAAE,uBAAuB,EAChC,EAAE,EAAE,MAAM,GACR,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;IA2BpE,OAAO;IAaS,KAAK,IAAI,IAAI;IAO7B,SAAS,CAAC,mBAAmB,IAAI,QAAQ;CAIzC"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.EphemeralServiceContainer = exports.getDefaultEphemeralService = exports.cleanupEphemeralService = exports.startEphemeralService = void 0;
|
|
8
|
+
const internal_1 = require("@fluidframework/container-definitions/internal");
|
|
9
|
+
const internal_2 = require("@fluidframework/container-loader/internal");
|
|
10
|
+
const internal_3 = require("@fluidframework/container-runtime/internal");
|
|
11
|
+
const internal_4 = require("@fluidframework/core-interfaces/internal");
|
|
12
|
+
const internal_5 = require("@fluidframework/core-utils/internal");
|
|
13
|
+
const internal_6 = require("@fluidframework/driver-definitions/internal");
|
|
14
|
+
const internal_7 = require("@fluidframework/runtime-utils/internal");
|
|
15
|
+
const server_local_server_1 = require("@fluidframework/server-local-server");
|
|
16
|
+
const internal_8 = require("@fluidframework/driver-utils/internal");
|
|
17
|
+
const localDocumentServiceFactory_js_1 = require("./localDocumentServiceFactory.js");
|
|
18
|
+
const localResolver_js_1 = require("./localResolver.js");
|
|
19
|
+
const packageVersion_js_1 = require("./packageVersion.js");
|
|
20
|
+
/**
|
|
21
|
+
* Starts and returns a new {@link EphemeralService}.
|
|
22
|
+
* @param isDefault - Whether this service should saved as the default service for {@link cleanupEphemeralService} to cleanup.
|
|
23
|
+
* Defaults to true.
|
|
24
|
+
* @remarks
|
|
25
|
+
* The returned service owns an in-memory server and holds the documents created through clients connected to it.
|
|
26
|
+
* {@link cleanupEphemeralService} can be used to ensure the service is properly cleaned up (a no-op if stopped/closed already).
|
|
27
|
+
*
|
|
28
|
+
* As a service, it may start timers which may require an explicit `close` to fully free.
|
|
29
|
+
* @alpha
|
|
30
|
+
*/
|
|
31
|
+
function startEphemeralService(isDefault = true) {
|
|
32
|
+
if (isDefault && defaultEphemeralService) {
|
|
33
|
+
throw new internal_8.UsageError("A default EphemeralService is already running");
|
|
34
|
+
}
|
|
35
|
+
const service = new EphemeralServiceImplementation();
|
|
36
|
+
if (isDefault) {
|
|
37
|
+
defaultEphemeralService = service;
|
|
38
|
+
}
|
|
39
|
+
return service;
|
|
40
|
+
}
|
|
41
|
+
exports.startEphemeralService = startEphemeralService;
|
|
42
|
+
/**
|
|
43
|
+
* Cleans up the service passed in {@link startEphemeralService}, or the {@link getDefaultEphemeralService|default} if none is passed.
|
|
44
|
+
* @remarks
|
|
45
|
+
* This closes the service, and all its containers.
|
|
46
|
+
* This is a good way to ensure the service and its containers leave no lingering timers
|
|
47
|
+
* which could leak memory, trigger asynchronous work or prevent a clean process exit.
|
|
48
|
+
* @alpha
|
|
49
|
+
*/
|
|
50
|
+
async function cleanupEphemeralService(service) {
|
|
51
|
+
const toCleanup = service ?? defaultEphemeralService;
|
|
52
|
+
if (toCleanup) {
|
|
53
|
+
// TODO: we may want to make closing of containers a separate operation which is done here.
|
|
54
|
+
await toCleanup.close();
|
|
55
|
+
}
|
|
56
|
+
if (toCleanup === defaultEphemeralService) {
|
|
57
|
+
defaultEphemeralService = undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.cleanupEphemeralService = cleanupEphemeralService;
|
|
61
|
+
/**
|
|
62
|
+
* Get the default {@link EphemeralService} if one has been {@link startEphemeralService|started}.
|
|
63
|
+
* @throws If no default service is running.
|
|
64
|
+
* @alpha
|
|
65
|
+
*/
|
|
66
|
+
function getDefaultEphemeralService() {
|
|
67
|
+
if (defaultEphemeralService) {
|
|
68
|
+
return defaultEphemeralService;
|
|
69
|
+
}
|
|
70
|
+
throw new internal_8.UsageError("No default EphemeralService is running");
|
|
71
|
+
}
|
|
72
|
+
exports.getDefaultEphemeralService = getDefaultEphemeralService;
|
|
73
|
+
/**
|
|
74
|
+
* The {@link defaultEphemeralService} if one has been {@link startEphemeralService|started}.
|
|
75
|
+
*/
|
|
76
|
+
let defaultEphemeralService;
|
|
77
|
+
/**
|
|
78
|
+
* The concrete implementation of {@link EphemeralService}.
|
|
79
|
+
* @remarks
|
|
80
|
+
* Kept module-private so its mutable state and internal helpers are not part of the public API.
|
|
81
|
+
* Narrow an {@link EphemeralService} to it with `EphemeralServiceImplementation.narrow`.
|
|
82
|
+
*/
|
|
83
|
+
class EphemeralServiceImplementation extends internal_4.ErasedTypeImplementation {
|
|
84
|
+
constructor() {
|
|
85
|
+
super();
|
|
86
|
+
// A single server is shared by all containers connected to this service so they can communicate with each other.
|
|
87
|
+
this.server = server_local_server_1.LocalDeltaConnectionServer.create(
|
|
88
|
+
// new LocalSessionStorageDbFactory(),
|
|
89
|
+
);
|
|
90
|
+
this.documentServiceFactory = new localDocumentServiceFactory_js_1.LocalDocumentServiceFactory(this.server);
|
|
91
|
+
this.containers = new Set();
|
|
92
|
+
this.closed = false;
|
|
93
|
+
this.defaultClient = this.newClient();
|
|
94
|
+
}
|
|
95
|
+
newClient(options) {
|
|
96
|
+
const finalOptions = {
|
|
97
|
+
minVersionForCollaboration: options?.minVersionForCollaboration ?? (0, internal_6.featureVersion)(packageVersion_js_1.pkgVersion),
|
|
98
|
+
service: this,
|
|
99
|
+
};
|
|
100
|
+
return new EphemeralServiceClientImplementation(finalOptions);
|
|
101
|
+
}
|
|
102
|
+
async close() {
|
|
103
|
+
if (this.closed) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.closed = true;
|
|
107
|
+
// Close every open container via the same public close() path a user would use.
|
|
108
|
+
// We might want to remove this.
|
|
109
|
+
const toClose = [...this.containers];
|
|
110
|
+
this.containers.clear();
|
|
111
|
+
for (const c of toClose) {
|
|
112
|
+
c.close();
|
|
113
|
+
}
|
|
114
|
+
// Shut down the in-memory server. Its timers (e.g. the Deli read-client idle `setInterval`) belong to the
|
|
115
|
+
// server rather than any container, so closing containers alone would leave them running.
|
|
116
|
+
await this.server.close();
|
|
117
|
+
}
|
|
118
|
+
async synchronize(timeoutMilliseconds = 30_000) {
|
|
119
|
+
// Timeout to allow for better errors in the case of hangs.
|
|
120
|
+
let timedOut = false;
|
|
121
|
+
let deadlineTimer;
|
|
122
|
+
const deadline = new Promise((resolve) => {
|
|
123
|
+
deadlineTimer = setTimeout(() => {
|
|
124
|
+
timedOut = true;
|
|
125
|
+
resolve(true);
|
|
126
|
+
}, timeoutMilliseconds);
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
// Require two consecutive quiescent passes (no dirty containers and no pending server work),
|
|
130
|
+
// each separated by a macrotask turn, to give late side effects a chance to surface.
|
|
131
|
+
let clean = 0;
|
|
132
|
+
while (clean < 2) {
|
|
133
|
+
if (timedOut) {
|
|
134
|
+
throw new internal_8.UsageError(`EphemeralService.synchronize timed out after ${timeoutMilliseconds}ms waiting for local containers to quiesce.`);
|
|
135
|
+
}
|
|
136
|
+
// Yield a macrotask turn *first*, so the local server's scheduled broadcast send and each
|
|
137
|
+
// container's inbound op processing can run before we sample their state below. Sampling
|
|
138
|
+
// hasPendingWork() in a tight `while (await ...)` loop instead would starve that scheduled
|
|
139
|
+
// send (it is a macrotask, while the await resolves on the microtask queue) and could hang.
|
|
140
|
+
await new Promise((resolve) => {
|
|
141
|
+
setTimeout(resolve, 0);
|
|
142
|
+
});
|
|
143
|
+
// Prune any containers that have closed since the last pass.
|
|
144
|
+
for (const container of [...this.containers]) {
|
|
145
|
+
if (container.container.closed) {
|
|
146
|
+
this.containers.delete(container);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const containersToApply = [...this.containers].map((container) => container.container);
|
|
150
|
+
// Ignore readonly/disconnected dirty containers: they can't send ops, so nothing can be done about them being dirty here.
|
|
151
|
+
// Neither state is reachable through the ephemeral service API today, but the checks are cheap and keep this robust to future changes.
|
|
152
|
+
const dirtyContainers = containersToApply.filter((c) => {
|
|
153
|
+
const { deltaManager, isDirty, connectionState } = c;
|
|
154
|
+
return (connectionState !== internal_1.ConnectionState.Disconnected &&
|
|
155
|
+
deltaManager.readOnlyInfo.readonly !== true &&
|
|
156
|
+
isDirty);
|
|
157
|
+
});
|
|
158
|
+
if (dirtyContainers.length > 0) {
|
|
159
|
+
// Bound this wait by the shared deadline: a container that never saves (and never
|
|
160
|
+
// closes) must not block past the overall timeout, since the top-of-loop check can't
|
|
161
|
+
// run while we are awaiting here.
|
|
162
|
+
await Promise.race([
|
|
163
|
+
Promise.all(dirtyContainers.map(async (c) => Promise.race([
|
|
164
|
+
new Promise((resolve) => c.once("saved", resolve)),
|
|
165
|
+
new Promise((resolve) => c.once("closed", resolve)),
|
|
166
|
+
]))),
|
|
167
|
+
deadline,
|
|
168
|
+
]);
|
|
169
|
+
clean = 0;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Sample pending server work once per pass (the macrotask yield above gave the broadcaster's
|
|
173
|
+
// scheduled send a chance to run first).
|
|
174
|
+
if (await Promise.race([this.server.hasPendingWork(), deadline])) {
|
|
175
|
+
clean = 0;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
clean++;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
clearTimeout(deadlineTimer);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The document service factory for this service.
|
|
187
|
+
* @remarks Internal helper for {@link EphemeralServiceContainer}; not part of the public {@link EphemeralService} API.
|
|
188
|
+
*/
|
|
189
|
+
getDocumentServiceFactory() {
|
|
190
|
+
(0, internal_5.assert)(!this.closed, 0xd11 /* Cannot create or load containers on a closed EphemeralService */);
|
|
191
|
+
return this.documentServiceFactory;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Registers a newly created container as connected to this service.
|
|
195
|
+
* @remarks Internal helper for {@link EphemeralServiceContainer}; not part of the public {@link EphemeralService} API.
|
|
196
|
+
*/
|
|
197
|
+
addContainer(container) {
|
|
198
|
+
this.containers.add(container);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Removes a now-closed container from this service.
|
|
202
|
+
* @remarks Internal helper for {@link EphemeralServiceContainer}; not part of the public {@link EphemeralService} API.
|
|
203
|
+
*/
|
|
204
|
+
removeContainer(container) {
|
|
205
|
+
this.containers.delete(container);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
class EphemeralServiceClientImplementation extends internal_7.ServiceClientImplementation {
|
|
209
|
+
constructor(options) {
|
|
210
|
+
super(options, EphemeralServiceContainer);
|
|
211
|
+
this.service = options.service;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const containerRuntimeLoader = async (parameters) => {
|
|
215
|
+
const { runtime } = await internal_3.ContainerRuntime.loadRuntime2({
|
|
216
|
+
context: parameters.context,
|
|
217
|
+
registry: parameters.registry,
|
|
218
|
+
provideEntryPoint: parameters.provideEntryPoint,
|
|
219
|
+
existing: parameters.existing,
|
|
220
|
+
minVersionForCollab: parameters.minVersionForCollab,
|
|
221
|
+
runtimeOptions: { enableRuntimeIdCompressor: "on" },
|
|
222
|
+
});
|
|
223
|
+
if (!parameters.existing) {
|
|
224
|
+
(0, internal_5.assert)(parameters.newContainerRootType !== undefined, 0xd12 /* Root data store kind must be provided for new containers */);
|
|
225
|
+
const dataStore = await runtime.createDataStore(parameters.newContainerRootType);
|
|
226
|
+
const aliasResult = await dataStore.trySetAlias(internal_7.rootDataStoreId);
|
|
227
|
+
(0, internal_5.assert)(aliasResult === "Success", 0xd13 /* Should be able to set alias on new data store */);
|
|
228
|
+
}
|
|
229
|
+
return runtime;
|
|
230
|
+
};
|
|
231
|
+
const urlResolver = new localResolver_js_1.LocalResolver();
|
|
232
|
+
/**
|
|
233
|
+
* Create a request to open an existing document.
|
|
234
|
+
*
|
|
235
|
+
* @param documentId - the existing document to open.
|
|
236
|
+
* @privateRemarks
|
|
237
|
+
* Like createLocalResolverCreateNewRequest, but without the option to create a new document.
|
|
238
|
+
* TODO: At some point we should avoid specifying the URL in so many places, but the current APIs don't accommodate it yet.
|
|
239
|
+
*/
|
|
240
|
+
const createLoadExistingRequest = (documentId) => {
|
|
241
|
+
return { url: `http://localhost:3000/${documentId}` };
|
|
242
|
+
};
|
|
243
|
+
let documentIdCounter = 0;
|
|
244
|
+
/**
|
|
245
|
+
* A Fluid container backed by an ephemeral (in-memory) local service, implementing
|
|
246
|
+
* {@link @fluidframework/driver-definitions#FluidContainerWithService}.
|
|
247
|
+
*
|
|
248
|
+
* @remarks
|
|
249
|
+
* Data is stored in-memory by the {@link EphemeralService} the container's client is connected to (see
|
|
250
|
+
* {@link EphemeralServiceContainer.service}), enabling side-by-side collaboration testing without a real server.
|
|
251
|
+
*
|
|
252
|
+
* @internal
|
|
253
|
+
*/
|
|
254
|
+
class EphemeralServiceContainer extends internal_7.ServiceContainerBase {
|
|
255
|
+
static async createDetached(registry, options, root) {
|
|
256
|
+
EphemeralServiceImplementation.narrow(options.service);
|
|
257
|
+
const container = await (0, internal_2.createDetachedContainer)({
|
|
258
|
+
codeDetails: { package: "1.0" },
|
|
259
|
+
urlResolver,
|
|
260
|
+
documentServiceFactory: options.service.getDocumentServiceFactory(),
|
|
261
|
+
codeLoader: (0, internal_7.makeCodeLoader)(registry, options.minVersionForCollaboration, containerRuntimeLoader, root),
|
|
262
|
+
});
|
|
263
|
+
return new EphemeralServiceContainer(registry, options, container, (await container.getEntryPoint()), undefined);
|
|
264
|
+
}
|
|
265
|
+
static async load(registry, options, id) {
|
|
266
|
+
EphemeralServiceImplementation.narrow(options.service);
|
|
267
|
+
const containerInner = await (0, internal_2.loadExistingContainer)({
|
|
268
|
+
request: createLoadExistingRequest(id),
|
|
269
|
+
urlResolver,
|
|
270
|
+
documentServiceFactory: options.service.getDocumentServiceFactory(),
|
|
271
|
+
codeLoader: (0, internal_7.makeCodeLoader)(registry, options.minVersionForCollaboration, containerRuntimeLoader),
|
|
272
|
+
});
|
|
273
|
+
const container = new EphemeralServiceContainer(registry, options, containerInner, (await containerInner.getEntryPoint()), id);
|
|
274
|
+
(0, internal_5.assert)(container.id !== undefined, 0xd14 /* id should be defined when loading a container */);
|
|
275
|
+
return container;
|
|
276
|
+
}
|
|
277
|
+
constructor(registry, options, container, data, id) {
|
|
278
|
+
super(registry, options, container, data, id);
|
|
279
|
+
this.service = options.service;
|
|
280
|
+
EphemeralServiceImplementation.narrow(this.service);
|
|
281
|
+
this.service.addContainer(this);
|
|
282
|
+
}
|
|
283
|
+
close() {
|
|
284
|
+
super.close();
|
|
285
|
+
// Remove this now-closed container from its service's set of open containers.
|
|
286
|
+
EphemeralServiceImplementation.narrow(this.service);
|
|
287
|
+
this.service.removeContainer(this);
|
|
288
|
+
}
|
|
289
|
+
createAttachRequest() {
|
|
290
|
+
const documentId = (documentIdCounter++).toString();
|
|
291
|
+
return (0, localResolver_js_1.createLocalResolverCreateNewRequest)(documentId);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
exports.EphemeralServiceContainer = EphemeralServiceContainer;
|
|
295
|
+
//# sourceMappingURL=ephemeralService.js.map
|