ignotum 0.0.13 → 0.0.15
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/README.md +6 -6
- package/dist/cli/bin.mjs +6111 -4448
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/cli/emscripten-module-D5GzfUNv.mjs +533 -0
- package/dist/cli/emscripten-module-D5GzfUNv.mjs.map +1 -0
- package/dist/cli/ffi-7IElU4Co.mjs +148 -0
- package/dist/cli/ffi-7IElU4Co.mjs.map +1 -0
- package/dist/cli/managed-release-WHYLZwaS.mjs +10902 -0
- package/dist/cli/managed-release-WHYLZwaS.mjs.map +1 -0
- package/dist/cli/managed-release.d.mts +1 -0
- package/dist/cli/managed-release.mjs +14 -0
- package/dist/cli/managed-release.mjs.map +1 -0
- package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs +1289 -0
- package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs.map +1 -0
- package/dist/runtime/{api-Cv3hMbzo.d.ts → api-B4fwp5a2.d.ts} +4 -4
- package/dist/runtime/bootstrap-BISQSn_O.js +916 -0
- package/dist/runtime/bootstrap-BISQSn_O.js.map +1 -0
- package/dist/runtime/bootstrap-BTFScKAc.d.ts +545 -0
- package/dist/runtime/client.d.ts +50 -15
- package/dist/runtime/client.js +7 -1138
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +269 -0
- package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +1 -0
- package/dist/runtime/{id-Bt9XWRGL.js → id--1m0NuUL.js} +4 -4
- package/dist/runtime/id--1m0NuUL.js.map +1 -0
- package/dist/runtime/{id-BzFHf3Wo-DGPCjgrf.d.ts → id-7an3nxTu-DZJSM5xh.d.ts} +2 -2
- package/dist/runtime/id-CBOt2kDo.d.ts +1 -0
- package/dist/runtime/identity-QjtJRxBD.d.ts +2 -0
- package/dist/runtime/image-Djtjy4Z8.js +19 -0
- package/dist/runtime/image-Djtjy4Z8.js.map +1 -0
- package/dist/runtime/index-D4seNHlX.d.ts +184 -0
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +30 -1
- package/dist/runtime/internal/api.js.map +1 -0
- package/dist/runtime/internal/bootstrap.d.ts +2 -0
- package/dist/runtime/internal/bootstrap.js +2 -0
- package/dist/runtime/internal/client.d.ts +2 -0
- package/dist/runtime/internal/client.js +75 -0
- package/dist/runtime/internal/client.js.map +1 -0
- package/dist/runtime/internal/host.d.ts +25 -8
- package/dist/runtime/internal/host.js +27 -6
- package/dist/runtime/internal/host.js.map +1 -1
- package/dist/runtime/internal/routes.d.ts +2 -0
- package/dist/runtime/internal/routes.js +2 -0
- package/dist/runtime/internal/server.d.ts +1 -1
- package/dist/runtime/internal/server.js +1 -1
- package/dist/runtime/internal/types.d.ts +2 -1
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/pagination-CBOt2kDo.d.ts +1 -0
- package/dist/runtime/pagination-D3qd6s9N.js +33 -0
- package/dist/runtime/pagination-D3qd6s9N.js.map +1 -0
- package/dist/runtime/result-BkziOG1L.d.ts +1 -0
- package/dist/runtime/router-BSBI1oN1.js +2377 -0
- package/dist/runtime/router-BSBI1oN1.js.map +1 -0
- package/dist/runtime/routes-Ce8TVP-T.js +690 -0
- package/dist/runtime/routes-Ce8TVP-T.js.map +1 -0
- package/dist/runtime/{schema-1Zs03-iS.js → schema-D1wOqiNH.js} +40 -12
- package/dist/runtime/schema-D1wOqiNH.js.map +1 -0
- package/dist/runtime/server.d.ts +5 -5
- package/dist/runtime/server.js +2 -2
- package/dist/runtime/{sync-Bs8J3fIr.d.ts → sync-D-GK2sv9.d.ts} +3 -2
- package/dist/runtime/{api-CAgKDij7.js → sync-DzUwA8W9.js} +22 -41
- package/dist/runtime/sync-DzUwA8W9.js.map +1 -0
- package/dist/runtime/types-DeCCyqLU-BIm7rU-0.d.ts +341 -0
- package/package.json +29 -4
- package/src/cli/agent-files.ts +14 -8
- package/src/cli/build/managed-client.ts +411 -0
- package/src/cli/build/managed-server.ts +294 -0
- package/src/cli/build/managed.ts +67 -0
- package/src/cli/build/output.ts +47 -0
- package/src/cli/build/server.ts +51 -220
- package/src/cli/client-config.ts +1 -140
- package/src/cli/client-styles.ts +1 -0
- package/src/cli/codegen.ts +9 -4
- package/src/cli/command.ts +28 -5
- package/src/cli/control-client.ts +84 -101
- package/src/cli/deploy.ts +133 -177
- package/src/cli/dev.ts +506 -107
- package/src/cli/image-assets.ts +499 -0
- package/src/cli/managed-client-boundaries.ts +103 -0
- package/src/cli/managed-dev-platform.ts +118 -0
- package/src/cli/managed-exports.ts +219 -0
- package/src/cli/managed-release-bin.ts +13 -0
- package/src/cli/managed-release.ts +260 -0
- package/src/cli/module-boundaries.ts +11 -1
- package/src/cli/new-app.ts +111 -87
- package/src/cli/route-codegen.ts +311 -0
- package/src/cli/route-static.ts +710 -0
- package/src/client/bootstrap.ts +110 -0
- package/src/client/errors.ts +13 -7
- package/src/client/files.ts +64 -0
- package/src/client/id.ts +9 -5
- package/src/client/image.ts +28 -0
- package/src/client/index.ts +25 -2
- package/src/client/managed-client.ts +729 -0
- package/src/client/managed-upgrade.ts +283 -0
- package/src/client/recovery-journal.ts +195 -0
- package/src/client/route-boundaries.ts +184 -0
- package/src/client/router-history.ts +152 -0
- package/src/client/router-store.ts +644 -0
- package/src/client/router.ts +324 -0
- package/src/client/routes.ts +28 -0
- package/src/client/sync.ts +66 -592
- package/src/dev-runtime/database.ts +77 -2
- package/src/dev-runtime/functions.ts +3 -3
- package/src/dev-runtime/id.ts +19 -4
- package/src/dev-runtime/managed-functions.ts +255 -0
- package/src/dev-runtime/managed-sync.ts +263 -0
- package/src/dev-runtime/managed-websocket.ts +71 -0
- package/src/dev-runtime/migrations.ts +20 -0
- package/src/dev-runtime/sync.ts +236 -236
- package/src/image-assets.d.ts +36 -0
- package/src/internal/client.ts +186 -0
- package/src/internal/host.ts +1 -1
- package/src/internal/http-paths.ts +0 -3
- package/src/internal/routes.ts +45 -0
- package/dist/runtime/api-CAgKDij7.js.map +0 -1
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +0 -154
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +0 -1
- package/dist/runtime/id-Bt9XWRGL.js.map +0 -1
- package/dist/runtime/id-Dz0apuB3.d.ts +0 -1
- package/dist/runtime/index-D54flWtH.d.ts +0 -402
- package/dist/runtime/pagination-DnKg3dkI-r5ZUxBBx.d.ts +0 -112
- package/dist/runtime/pagination-Dz0apuB3.d.ts +0 -1
- package/dist/runtime/result-DKAA4gpS.d.ts +0 -1
- package/dist/runtime/schema-1Zs03-iS.js.map +0 -1
- package/src/cli/build/client.ts +0 -119
- package/src/cli/build/public.ts +0 -186
- package/src/cli/client-entry.ts +0 -152
- package/src/cli/client-plugin.ts +0 -136
- package/src/client/app.ts +0 -15
package/src/dev-runtime/sync.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
managedSyncPath,
|
|
3
|
+
type ManagedClientMessage,
|
|
4
|
+
type ManagedServerMessage,
|
|
5
|
+
} from "@ignotum/contracts/managed-sync";
|
|
6
|
+
import { runManagedSession, type ManagedSyncOptions } from "./managed-sync.js";
|
|
7
|
+
import { ManagedWebSocket } from "./managed-websocket.js";
|
|
1
8
|
import { LocalQueryCache } from "./query-cache.js";
|
|
2
9
|
import {
|
|
3
10
|
SessionRequest,
|
|
4
|
-
idViewKey,
|
|
5
11
|
sessionPath,
|
|
6
12
|
sessionRequestsPath,
|
|
7
13
|
type SessionState,
|
|
@@ -13,31 +19,19 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
13
19
|
import { NodeHttpServer, NodeServices, NodeSocket } from "@effect/platform-node";
|
|
14
20
|
import { SqliteClient } from "@effect/sql-sqlite-node";
|
|
15
21
|
import {
|
|
16
|
-
AppId,
|
|
17
22
|
AppStateRevision,
|
|
18
|
-
DeploymentGeneration,
|
|
19
|
-
DeploymentId,
|
|
20
23
|
InvocationId,
|
|
21
24
|
RuntimeMutationResult,
|
|
22
25
|
canonicalInvocationInput,
|
|
23
26
|
canonicalQueryKey,
|
|
24
27
|
} from "@ignotum/contracts/runtime/hosted";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
ServerMessageJson,
|
|
28
|
-
type ClientMessage,
|
|
29
|
-
type FunctionAddress,
|
|
30
|
-
type Operation,
|
|
31
|
-
type ErrorCode,
|
|
32
|
-
type ServerMessage,
|
|
33
|
-
type SubscriptionId,
|
|
34
|
-
} from "@ignotum/contracts/runtime/sync";
|
|
28
|
+
import { SubscriptionId, type FunctionAddress } from "@ignotum/contracts/runtime/sync";
|
|
29
|
+
import type { ExecutionSelection } from "@ignotum/contracts/platform";
|
|
35
30
|
import {
|
|
36
31
|
Effect,
|
|
37
32
|
Clock,
|
|
38
33
|
Context,
|
|
39
34
|
FileSystem,
|
|
40
|
-
Function,
|
|
41
35
|
FiberSet,
|
|
42
36
|
HashMap,
|
|
43
37
|
Layer,
|
|
@@ -70,7 +64,7 @@ import {
|
|
|
70
64
|
} from "@ignotum/runtime/sync";
|
|
71
65
|
|
|
72
66
|
import { generate } from "../cli/codegen.js";
|
|
73
|
-
import { isIgnotumPath
|
|
67
|
+
import { isIgnotumPath } from "../internal/http-paths.js";
|
|
74
68
|
import { LocalDatabase } from "./database.js";
|
|
75
69
|
import { IdGenerator } from "@ignotum/shared/id";
|
|
76
70
|
import { DevelopmentDatabase } from "./migrations.js";
|
|
@@ -85,16 +79,11 @@ import { LocalApplicationFiles, localApplicationFilesLayer } from "./files.js";
|
|
|
85
79
|
interface QuerySubscription {
|
|
86
80
|
readonly args: Schema.Json;
|
|
87
81
|
readonly function: FunctionAddress;
|
|
82
|
+
readonly grantId: SubscriptionId;
|
|
88
83
|
readonly queryKey: ReturnType<typeof canonicalQueryKey>;
|
|
89
84
|
refreshGeneration: number;
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
export const localSyncIdentity = {
|
|
93
|
-
appId: AppId.make("app_000000000000000000000001"),
|
|
94
|
-
deploymentId: DeploymentId.make("dep_000000000000000000000001"),
|
|
95
|
-
generation: DeploymentGeneration.make(0),
|
|
96
|
-
} as const;
|
|
97
|
-
|
|
98
87
|
export { QueryInvalidation };
|
|
99
88
|
|
|
100
89
|
const reloadEnvironmentAndInvalidate = <Error, Requirements>(
|
|
@@ -134,9 +123,12 @@ class InvocationIdConflict extends Schema.TaggedError<InvocationIdConflict>()(
|
|
|
134
123
|
|
|
135
124
|
const MutationReplayRow = Schema.Struct({ input: Schema.String, result: Schema.String });
|
|
136
125
|
|
|
137
|
-
class MutationReplay extends Context.Service<
|
|
126
|
+
export class MutationReplay extends Context.Service<
|
|
138
127
|
MutationReplay,
|
|
139
128
|
{
|
|
129
|
+
readonly lookup: (
|
|
130
|
+
id: InvocationId,
|
|
131
|
+
) => Effect.Effect<RuntimeMutationResult | undefined, SqlError.SqlError>;
|
|
140
132
|
readonly execute: (
|
|
141
133
|
id: InvocationId,
|
|
142
134
|
input: string,
|
|
@@ -161,6 +153,16 @@ class MutationReplay extends Context.Service<
|
|
|
161
153
|
});
|
|
162
154
|
|
|
163
155
|
return MutationReplay.of({
|
|
156
|
+
lookup: Effect.fn("MutationReplay.lookup")(function* (id) {
|
|
157
|
+
const replay = yield* findReplay({ id }).pipe(
|
|
158
|
+
Effect.catchTags({ SchemaError: Effect.die }),
|
|
159
|
+
);
|
|
160
|
+
return Option.isNone(replay)
|
|
161
|
+
? undefined
|
|
162
|
+
: yield* Schema.decodeEffect(Schema.fromJsonString(RuntimeMutationResult))(
|
|
163
|
+
replay.value.result,
|
|
164
|
+
).pipe(Effect.orDie);
|
|
165
|
+
}),
|
|
164
166
|
execute: Effect.fn("MutationReplay.execute")(
|
|
165
167
|
function* (
|
|
166
168
|
id,
|
|
@@ -198,63 +200,57 @@ class MutationReplay extends Context.Service<
|
|
|
198
200
|
|
|
199
201
|
export const mutationReplayLayer = MutationReplay.layer;
|
|
200
202
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const syncError = (code: ErrorCode, message: string, operation?: Operation): ServerMessage => {
|
|
207
|
-
if (operation === undefined) {
|
|
208
|
-
return { type: "Error", code, message };
|
|
203
|
+
export type LocalSessionMessage = Extract<
|
|
204
|
+
ManagedClientMessage,
|
|
205
|
+
{
|
|
206
|
+
readonly type: "Subscribe" | "Unsubscribe" | "Prepare" | "Invoke";
|
|
209
207
|
}
|
|
210
|
-
|
|
211
|
-
};
|
|
208
|
+
>;
|
|
209
|
+
type LocalErrorCode = Extract<ManagedServerMessage, { readonly type: "Error" }>["code"];
|
|
210
|
+
const syncError = (code: LocalErrorCode, message: string, id: string): ManagedServerMessage => ({
|
|
211
|
+
type: "Error",
|
|
212
|
+
code,
|
|
213
|
+
message,
|
|
214
|
+
id,
|
|
215
|
+
});
|
|
212
216
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
/** Executes admitted session commands directly; the transport owns framing and selection checks. */
|
|
218
|
+
export const createLocalSession = Effect.fn("LocalSession.create")(function* (
|
|
219
|
+
selection: ExecutionSelection,
|
|
220
|
+
identity: SessionState,
|
|
221
|
+
send: (message: ManagedServerMessage) => Effect.Effect<void, Socket.SocketError>,
|
|
217
222
|
) {
|
|
218
|
-
let identity = initialIdentity ?? {
|
|
219
|
-
sessionEpoch: "dev:anonymous",
|
|
220
|
-
user: null,
|
|
221
|
-
viewRevision: 0,
|
|
222
|
-
validUntil: Number.MAX_SAFE_INTEGER,
|
|
223
|
-
};
|
|
224
223
|
const runtime = yield* FunctionRuntime;
|
|
224
|
+
const ids = yield* IdGenerator;
|
|
225
225
|
const invalidation = yield* QueryInvalidation;
|
|
226
226
|
const mutationReplay = yield* MutationReplay;
|
|
227
227
|
const queryCache = yield* LocalQueryCache;
|
|
228
228
|
const files = yield* LocalApplicationFiles;
|
|
229
229
|
const subscriptions = yield* Ref.make(HashMap.empty<SubscriptionId, QuerySubscription>());
|
|
230
230
|
const dependencyIndex = makeDependencyIndex<SubscriptionId>();
|
|
231
|
+
yield* Effect.addFinalizer(() =>
|
|
232
|
+
Ref.get(subscriptions).pipe(
|
|
233
|
+
Effect.flatMap((active) =>
|
|
234
|
+
Effect.forEach(HashMap.values(active), (subscription) =>
|
|
235
|
+
files.releaseGrants(subscription.grantId),
|
|
236
|
+
),
|
|
237
|
+
),
|
|
238
|
+
Effect.asVoid,
|
|
239
|
+
),
|
|
240
|
+
);
|
|
231
241
|
const invocationFibers = yield* FiberSet.make();
|
|
232
|
-
const messageSemaphore = yield* Semaphore.make(1);
|
|
233
|
-
const writeSemaphore = yield* Semaphore.make(1);
|
|
234
|
-
const write = yield* socket.writer;
|
|
235
|
-
|
|
236
|
-
const send = Effect.fn("SyncServer.send")(function* (message: ServerMessage) {
|
|
237
|
-
const bound =
|
|
238
|
-
message.type === "Result" || message.type === "Preparation" || message.type === "Error"
|
|
239
|
-
? { ...message, sessionEpoch: identity.sessionEpoch }
|
|
240
|
-
: message;
|
|
241
|
-
yield* writeSemaphore.withPermits(1)(
|
|
242
|
-
Schema.encodeEffect(ServerMessageJson)(bound).pipe(Effect.flatMap(write)),
|
|
243
|
-
);
|
|
244
|
-
});
|
|
245
242
|
|
|
246
243
|
const sendResolutionError = (
|
|
247
|
-
operation:
|
|
244
|
+
operation: string,
|
|
248
245
|
error:
|
|
249
246
|
| { readonly _tag: "UnknownFunction"; readonly message: string }
|
|
250
247
|
| { readonly _tag: "WrongFunctionKind"; readonly message: string }
|
|
251
248
|
| { readonly _tag: "InvalidArguments"; readonly message: string }
|
|
252
249
|
| { readonly _tag: "FunctionUnavailable"; readonly message: string },
|
|
253
|
-
deliver: (
|
|
254
|
-
message: ServerMessage,
|
|
255
|
-
) => Effect.Effect<void, Schema.SchemaError | Socket.SocketError> = send,
|
|
250
|
+
deliver: (message: ManagedServerMessage) => Effect.Effect<void, Socket.SocketError> = send,
|
|
256
251
|
) => {
|
|
257
|
-
const code:
|
|
252
|
+
const code: LocalErrorCode =
|
|
253
|
+
error._tag === "InvalidArguments" ? "InvalidArguments" : "Unavailable";
|
|
258
254
|
return deliver(syncError(code, error.message, operation));
|
|
259
255
|
};
|
|
260
256
|
|
|
@@ -268,7 +264,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
268
264
|
const sendIfActive = Effect.fn("SyncServer.sendIfActive")(function* (
|
|
269
265
|
subscriptionId: SubscriptionId,
|
|
270
266
|
subscription: QuerySubscription,
|
|
271
|
-
message:
|
|
267
|
+
message: ManagedServerMessage,
|
|
272
268
|
) {
|
|
273
269
|
if (yield* isActive(subscriptionId, subscription)) {
|
|
274
270
|
yield* send(message);
|
|
@@ -281,8 +277,9 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
281
277
|
refreshGeneration: number,
|
|
282
278
|
previouslyPrepared?: PreparedFunction,
|
|
283
279
|
) {
|
|
284
|
-
const operation =
|
|
285
|
-
const deliver = (message:
|
|
280
|
+
const operation = subscriptionId;
|
|
281
|
+
const deliver = (message: ManagedServerMessage) =>
|
|
282
|
+
sendIfActive(subscriptionId, subscription, message);
|
|
286
283
|
let prepared = previouslyPrepared;
|
|
287
284
|
|
|
288
285
|
while (yield* isActive(subscriptionId, subscription)) {
|
|
@@ -317,17 +314,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
317
314
|
}
|
|
318
315
|
dependencyIndex.record(subscriptionId, result.dependencies);
|
|
319
316
|
if ((yield* invalidation.latestRevision) > result.observedRevision) continue;
|
|
320
|
-
const granted = yield* files.grants(
|
|
317
|
+
const granted = yield* files.grants(subscription.grantId, result.files ?? []);
|
|
321
318
|
const snapshot = {
|
|
322
|
-
type: "
|
|
319
|
+
type: "QuerySnapshot",
|
|
323
320
|
id: subscriptionId,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
identity: {
|
|
327
|
-
sessionEpoch: identity.sessionEpoch,
|
|
328
|
-
viewRevision: identity.viewRevision,
|
|
329
|
-
dependsOnId: result.dependencies.some((dependency) => dependency.type === "Id"),
|
|
330
|
-
},
|
|
321
|
+
selection,
|
|
322
|
+
result,
|
|
331
323
|
} as const;
|
|
332
324
|
yield* deliver(granted.length === 0 ? snapshot : { ...snapshot, files: granted });
|
|
333
325
|
return;
|
|
@@ -352,19 +344,6 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
352
344
|
};
|
|
353
345
|
|
|
354
346
|
const refreshAll = Effect.fn("SyncServer.refreshAll")(function* () {
|
|
355
|
-
if (refreshIdentity !== undefined) {
|
|
356
|
-
const next = yield* refreshIdentity;
|
|
357
|
-
const changed =
|
|
358
|
-
idViewKey(identity) !== idViewKey(next) || identity.validUntil !== next.validUntil;
|
|
359
|
-
identity = next;
|
|
360
|
-
if (changed)
|
|
361
|
-
yield* send({
|
|
362
|
-
type: "Session",
|
|
363
|
-
event: "Updated",
|
|
364
|
-
session: identity,
|
|
365
|
-
serverTime: yield* Clock.currentTimeMillis,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
347
|
const current = yield* Ref.get(subscriptions);
|
|
369
348
|
yield* Effect.forEach(HashMap.toEntries(current), ([subscriptionId, subscription]) =>
|
|
370
349
|
scheduleQuery(subscriptionId, subscription),
|
|
@@ -391,9 +370,9 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
391
370
|
);
|
|
392
371
|
|
|
393
372
|
const handleSubscribe = Effect.fn("SyncServer.handleSubscribe")(function* (
|
|
394
|
-
message: Extract<
|
|
373
|
+
message: Extract<LocalSessionMessage, { readonly type: "Subscribe" }>,
|
|
395
374
|
) {
|
|
396
|
-
const operation =
|
|
375
|
+
const operation = message.id;
|
|
397
376
|
if (encodedFileOccurrencesOf(message.args).length > 0) {
|
|
398
377
|
yield* send(
|
|
399
378
|
syncError("InvalidArguments", "Queries cannot take files as arguments.", operation),
|
|
@@ -404,7 +383,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
404
383
|
|
|
405
384
|
if (HashMap.has(current, message.id)) {
|
|
406
385
|
yield* send(
|
|
407
|
-
syncError("
|
|
386
|
+
syncError("InvalidMessage", `Subscription ${message.id} already exists.`, operation),
|
|
408
387
|
);
|
|
409
388
|
return;
|
|
410
389
|
}
|
|
@@ -425,7 +404,8 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
425
404
|
const subscription = {
|
|
426
405
|
args: message.args,
|
|
427
406
|
function: message.function,
|
|
428
|
-
|
|
407
|
+
grantId: yield* ids.generate(SubscriptionId),
|
|
408
|
+
queryKey: canonicalQueryKey(selection.versionId, message.function, message.args),
|
|
429
409
|
refreshGeneration: 0,
|
|
430
410
|
};
|
|
431
411
|
yield* Ref.update(subscriptions, HashMap.set(message.id, subscription));
|
|
@@ -433,10 +413,10 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
433
413
|
});
|
|
434
414
|
|
|
435
415
|
const handleInvoke = Effect.fn("SyncServer.handleInvoke")(function* (
|
|
436
|
-
message: Extract<
|
|
416
|
+
message: Extract<LocalSessionMessage, { readonly type: "Invoke" }>,
|
|
437
417
|
) {
|
|
438
|
-
const operation
|
|
439
|
-
const admitted = yield* files.admit(message.
|
|
418
|
+
const operation = message.invocationId;
|
|
419
|
+
const admitted = yield* files.admit(message.invocationId, message.function, message.args).pipe(
|
|
440
420
|
Effect.as(true),
|
|
441
421
|
Effect.catchCause((cause) =>
|
|
442
422
|
send(
|
|
@@ -449,7 +429,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
449
429
|
),
|
|
450
430
|
);
|
|
451
431
|
if (!admitted) {
|
|
452
|
-
yield* files.releasePreparation(message.
|
|
432
|
+
yield* files.releasePreparation(message.invocationId);
|
|
453
433
|
return;
|
|
454
434
|
}
|
|
455
435
|
const prepared = yield* runtime
|
|
@@ -464,12 +444,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
464
444
|
);
|
|
465
445
|
|
|
466
446
|
if (prepared === undefined) {
|
|
467
|
-
yield* files.releasePreparation(message.
|
|
447
|
+
yield* files.releasePreparation(message.invocationId);
|
|
468
448
|
return;
|
|
469
449
|
}
|
|
470
450
|
|
|
471
451
|
const input = canonicalInvocationInput(
|
|
472
|
-
|
|
452
|
+
selection.versionId,
|
|
473
453
|
"Mutation",
|
|
474
454
|
message.function,
|
|
475
455
|
message.args,
|
|
@@ -477,7 +457,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
477
457
|
);
|
|
478
458
|
yield* mutationReplay
|
|
479
459
|
.execute(
|
|
480
|
-
message.
|
|
460
|
+
message.invocationId,
|
|
481
461
|
input,
|
|
482
462
|
prepared.execute.pipe(
|
|
483
463
|
Effect.flatMap((result) =>
|
|
@@ -487,39 +467,42 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
487
467
|
)
|
|
488
468
|
.pipe(
|
|
489
469
|
Effect.flatMap((result) => {
|
|
490
|
-
if (!("committedRevision" in result)) {
|
|
491
|
-
return send({ type: "Result", id: message.id, result: result.result });
|
|
492
|
-
}
|
|
493
470
|
const response = send({
|
|
494
|
-
type: "
|
|
495
|
-
id: message.
|
|
496
|
-
|
|
497
|
-
|
|
471
|
+
type: "MutationOutcome",
|
|
472
|
+
id: message.invocationId,
|
|
473
|
+
identity: { invocationId: message.invocationId, originalSelection: selection },
|
|
474
|
+
outcome: { type: "Settled", result },
|
|
498
475
|
});
|
|
499
|
-
return
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
476
|
+
return "committedRevision" in result
|
|
477
|
+
? response.pipe(
|
|
478
|
+
Effect.ensuring(
|
|
479
|
+
invalidation.publish({
|
|
480
|
+
type: "Dependencies",
|
|
481
|
+
invalidations: result.invalidations,
|
|
482
|
+
committedRevision: result.committedRevision,
|
|
483
|
+
}),
|
|
484
|
+
),
|
|
485
|
+
)
|
|
486
|
+
: response;
|
|
508
487
|
}),
|
|
509
488
|
Effect.catchTags({
|
|
510
489
|
InvocationIdConflict: () =>
|
|
511
490
|
send(
|
|
512
491
|
syncError(
|
|
513
492
|
"InvocationIdConflict",
|
|
514
|
-
`Invocation ${message.
|
|
493
|
+
`Invocation ${message.invocationId} was already used with different inputs.`,
|
|
515
494
|
operation,
|
|
516
495
|
),
|
|
517
496
|
),
|
|
518
497
|
}),
|
|
519
|
-
Effect.ensuring(files.releasePreparation(message.
|
|
498
|
+
Effect.ensuring(files.releasePreparation(message.invocationId)),
|
|
520
499
|
Effect.catchCause((cause) =>
|
|
521
500
|
Effect.logError("A mutation invocation fiber failed.").pipe(
|
|
522
|
-
Effect.annotateLogs({
|
|
501
|
+
Effect.annotateLogs({
|
|
502
|
+
cause,
|
|
503
|
+
function: message.function,
|
|
504
|
+
requestId: message.invocationId,
|
|
505
|
+
}),
|
|
523
506
|
),
|
|
524
507
|
),
|
|
525
508
|
FiberSet.run(invocationFibers),
|
|
@@ -528,12 +511,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
528
511
|
});
|
|
529
512
|
|
|
530
513
|
const handlePrepare = Effect.fn("SyncServer.handlePrepare")(function* (
|
|
531
|
-
message: Extract<
|
|
514
|
+
message: Extract<LocalSessionMessage, { readonly type: "Prepare" }>,
|
|
532
515
|
) {
|
|
533
|
-
const operation
|
|
534
|
-
yield* files.prepare(message.
|
|
516
|
+
const operation = message.invocationId;
|
|
517
|
+
yield* files.prepare(message.invocationId, message.function, message.args, message.files).pipe(
|
|
535
518
|
Effect.flatMap((uploads) =>
|
|
536
|
-
send({ type: "Preparation",
|
|
519
|
+
send({ type: "Preparation", invocationId: message.invocationId, uploads }),
|
|
537
520
|
),
|
|
538
521
|
Effect.catchCause((cause) =>
|
|
539
522
|
send(
|
|
@@ -547,26 +530,19 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
547
530
|
);
|
|
548
531
|
});
|
|
549
532
|
|
|
550
|
-
const
|
|
551
|
-
const message = yield* Schema.decodeEffect(ClientMessageJson)(text).pipe(
|
|
552
|
-
Effect.catch(() =>
|
|
553
|
-
send(syncError("InvalidMessage", "The WebSocket frame is not valid Ignotum JSON.")),
|
|
554
|
-
),
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
if (message === undefined) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
533
|
+
const handle = Effect.fn("LocalSession.handle")(function* (message: LocalSessionMessage) {
|
|
561
534
|
switch (message.type) {
|
|
562
535
|
case "Subscribe":
|
|
563
536
|
yield* handleSubscribe(message);
|
|
564
537
|
return;
|
|
565
|
-
case "Unsubscribe":
|
|
538
|
+
case "Unsubscribe": {
|
|
539
|
+
const current = yield* Ref.get(subscriptions);
|
|
540
|
+
const subscription = Option.getOrUndefined(HashMap.get(current, message.id));
|
|
566
541
|
dependencyIndex.remove(message.id);
|
|
567
542
|
yield* Ref.update(subscriptions, HashMap.remove(message.id));
|
|
568
|
-
yield* files.releaseGrants(
|
|
543
|
+
if (subscription !== undefined) yield* files.releaseGrants(subscription.grantId);
|
|
569
544
|
return;
|
|
545
|
+
}
|
|
570
546
|
case "Prepare":
|
|
571
547
|
yield* handlePrepare(message);
|
|
572
548
|
return;
|
|
@@ -575,31 +551,11 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (
|
|
|
575
551
|
return;
|
|
576
552
|
}
|
|
577
553
|
});
|
|
578
|
-
|
|
579
|
-
yield* socket
|
|
580
|
-
.runString((text) => messageSemaphore.withPermits(1)(handleMessage(text)), {
|
|
581
|
-
onOpen: send({
|
|
582
|
-
type: "Handshake",
|
|
583
|
-
...localSyncIdentity,
|
|
584
|
-
session: identity,
|
|
585
|
-
serverTime: yield* Clock.currentTimeMillis,
|
|
586
|
-
}).pipe(Effect.orDie),
|
|
587
|
-
})
|
|
588
|
-
.pipe(
|
|
589
|
-
Effect.ensuring(
|
|
590
|
-
Ref.get(subscriptions).pipe(
|
|
591
|
-
Effect.flatMap((active) =>
|
|
592
|
-
Effect.forEach(HashMap.keys(active), (subscriptionId) =>
|
|
593
|
-
files.releaseGrants(subscriptionId),
|
|
594
|
-
),
|
|
595
|
-
),
|
|
596
|
-
Effect.asVoid,
|
|
597
|
-
),
|
|
598
|
-
),
|
|
599
|
-
);
|
|
554
|
+
return { handle };
|
|
600
555
|
});
|
|
601
556
|
|
|
602
557
|
interface SyncHandlersService {
|
|
558
|
+
readonly invalidate: Effect.Effect<void>;
|
|
603
559
|
readonly http: (request: IncomingMessage, response: ServerResponse) => void;
|
|
604
560
|
readonly upgrade: NodeJS.EventEmitter extends infer _EventEmitter
|
|
605
561
|
? (request: IncomingMessage, socket: import("node:stream").Duplex, head: Buffer) => void
|
|
@@ -611,7 +567,17 @@ class SyncHandlers extends Context.Service<SyncHandlers, SyncHandlersService>()(
|
|
|
611
567
|
"ignotum/dev-runtime/sync/SyncHandlers",
|
|
612
568
|
) {}
|
|
613
569
|
|
|
614
|
-
|
|
570
|
+
type LocalDevSyncOptions = ManagedSyncOptions & {
|
|
571
|
+
readonly loadModule: ViteDevServer["ssrLoadModule"];
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const makeHandlersLayer = (
|
|
575
|
+
appDirectory: string,
|
|
576
|
+
databasePath: string,
|
|
577
|
+
managed: LocalDevSyncOptions,
|
|
578
|
+
) => {
|
|
579
|
+
const transportPath = managedSyncPath;
|
|
580
|
+
const loader = { ssrLoadModule: managed.loadModule };
|
|
615
581
|
const sqliteLayer = SqliteClient.layer({ filename: databasePath });
|
|
616
582
|
const persistenceLayer = Layer.merge(LocalDatabase.layer, mutationReplayLayer).pipe(
|
|
617
583
|
Layer.provideMerge(DevelopmentDatabase.layer),
|
|
@@ -621,14 +587,14 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
621
587
|
Layer.provide(persistenceLayer),
|
|
622
588
|
Layer.provide(IdGenerator.layer),
|
|
623
589
|
);
|
|
624
|
-
const environmentLayer = DevelopmentEnvironment.layer(
|
|
590
|
+
const environmentLayer = DevelopmentEnvironment.layer(loader, appDirectory).pipe(
|
|
625
591
|
Layer.provide(NodeServices.layer),
|
|
626
592
|
Layer.orDie,
|
|
627
593
|
);
|
|
628
594
|
const devFunctionRuntimeLayer = functionRuntimeLayer.pipe(
|
|
629
595
|
Layer.provide(
|
|
630
596
|
Layer.merge(
|
|
631
|
-
FunctionRegistry.layer(
|
|
597
|
+
FunctionRegistry.layer(loader, appDirectory).pipe(
|
|
632
598
|
Layer.provide(Layer.merge(NodeServices.layer, environmentLayer)),
|
|
633
599
|
),
|
|
634
600
|
executorLayer,
|
|
@@ -640,6 +606,8 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
640
606
|
environmentLayer,
|
|
641
607
|
queryInvalidationLayer,
|
|
642
608
|
persistenceLayer,
|
|
609
|
+
sqliteLayer,
|
|
610
|
+
IdGenerator.layer,
|
|
643
611
|
NodeServices.layer,
|
|
644
612
|
LocalId.layer(appDirectory).pipe(Layer.provide(NodeServices.layer)),
|
|
645
613
|
localApplicationFilesLayer(appDirectory).pipe(
|
|
@@ -778,31 +746,32 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
778
746
|
: HttpServerResponse.uint8Array(granted.value.bytes, { headers });
|
|
779
747
|
}
|
|
780
748
|
return yield* HttpServerResponse.json(
|
|
781
|
-
pathname ===
|
|
782
|
-
? { code: "UpgradeRequired", message: `Connect to ${
|
|
749
|
+
pathname === transportPath
|
|
750
|
+
? { code: "UpgradeRequired", message: `Connect to ${transportPath} with WebSocket.` }
|
|
783
751
|
: { code: "NotFound", message: "Unknown Ignotum API route." },
|
|
784
|
-
{ status: pathname ===
|
|
785
|
-
);
|
|
786
|
-
});
|
|
787
|
-
const socketApp = Effect.gen(function* () {
|
|
788
|
-
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
789
|
-
const url = new URL(request.url, `http://${request.headers.host ?? "ignotum.local"}`);
|
|
790
|
-
if (request.headers.origin !== url.origin) return HttpServerResponse.empty({ status: 403 });
|
|
791
|
-
const refresh = localId.session(
|
|
792
|
-
url.searchParams.get("username"),
|
|
793
|
-
url.searchParams.get("epoch") ?? "dev:anonymous",
|
|
752
|
+
{ status: pathname === transportPath ? 426 : 404 },
|
|
794
753
|
);
|
|
795
|
-
const identity = yield* refresh;
|
|
796
|
-
const socket = yield* request.upgrade;
|
|
797
|
-
yield* runSession(socket, identity, refresh);
|
|
798
|
-
return HttpServerResponse.empty();
|
|
799
754
|
});
|
|
800
755
|
const http = yield* NodeHttpServer.makeHandler(httpApp, { scope });
|
|
801
|
-
const upgrade = yield*
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
756
|
+
const upgrade = yield* Layer.build(
|
|
757
|
+
ManagedWebSocket.layer(
|
|
758
|
+
webSocketServer,
|
|
759
|
+
Effect.fn("ManagedSyncServer.session")(function* (
|
|
760
|
+
socket: Socket.Socket,
|
|
761
|
+
request: IncomingMessage,
|
|
762
|
+
) {
|
|
763
|
+
const url = new URL(
|
|
764
|
+
request.url ?? "/",
|
|
765
|
+
`http://${request.headers.host ?? "ignotum.local"}`,
|
|
766
|
+
);
|
|
767
|
+
const refresh = localId.session(
|
|
768
|
+
url.searchParams.get("username"),
|
|
769
|
+
url.searchParams.get("epoch") ?? "dev:anonymous",
|
|
770
|
+
);
|
|
771
|
+
yield* runManagedSession(socket, managed, yield* refresh, refresh);
|
|
772
|
+
}),
|
|
773
|
+
),
|
|
774
|
+
).pipe(Effect.map((context) => Context.get(context, ManagedWebSocket).upgrade));
|
|
806
775
|
const serverDirectory = path.join(appDirectory, "server");
|
|
807
776
|
const environmentPath = path.join(appDirectory, ".env.ignotum");
|
|
808
777
|
const reloadUnsafe = Effect.fn("SyncServer.reload")(function* (
|
|
@@ -813,10 +782,19 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
813
782
|
const relative = path.relative(serverDirectory, file);
|
|
814
783
|
const isServerTypeScript =
|
|
815
784
|
!relative.startsWith("..") && !path.isAbsolute(relative) && file.endsWith(".ts");
|
|
816
|
-
if (
|
|
785
|
+
if (
|
|
786
|
+
!isEnvironmentValues &&
|
|
787
|
+
!isServerTypeScript &&
|
|
788
|
+
managed.isServerDependency?.(file) !== true
|
|
789
|
+
) {
|
|
817
790
|
return;
|
|
818
791
|
}
|
|
819
792
|
|
|
793
|
+
if (managed.beforeReload !== undefined)
|
|
794
|
+
yield* Effect.tryPromise(() => managed.beforeReload!(file, event)).pipe(
|
|
795
|
+
Effect.onError(() => invalidation.publish({ type: "All" })),
|
|
796
|
+
);
|
|
797
|
+
|
|
820
798
|
if (isServerTypeScript && event !== "change") {
|
|
821
799
|
yield* generate(appDirectory).pipe(
|
|
822
800
|
Effect.provideService(FileSystem.FileSystem, fileSystem),
|
|
@@ -843,7 +821,12 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
|
|
|
843
821
|
),
|
|
844
822
|
);
|
|
845
823
|
|
|
846
|
-
return SyncHandlers.of({
|
|
824
|
+
return SyncHandlers.of({
|
|
825
|
+
http,
|
|
826
|
+
reload,
|
|
827
|
+
upgrade,
|
|
828
|
+
invalidate: invalidation.publish({ type: "All" }),
|
|
829
|
+
});
|
|
847
830
|
}),
|
|
848
831
|
).pipe(Layer.provide(dependencies));
|
|
849
832
|
};
|
|
@@ -870,60 +853,77 @@ const rejectUpgrade = (socket: import("node:stream").Duplex): void => {
|
|
|
870
853
|
|
|
871
854
|
export const syncPluginInternals = { isAppOrigin, reloadEnvironmentAndInvalidate };
|
|
872
855
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
856
|
+
const createSyncPlugin = (
|
|
857
|
+
appDirectory: string,
|
|
858
|
+
databasePath: string,
|
|
859
|
+
managed: LocalDevSyncOptions,
|
|
860
|
+
): Plugin & { api: { invalidate: () => Promise<void>; dispose: () => Promise<void> } } => {
|
|
861
|
+
const api = {
|
|
862
|
+
invalidate: (): Promise<void> => Promise.resolve(),
|
|
863
|
+
dispose: (): Promise<void> => Promise.resolve(),
|
|
864
|
+
};
|
|
865
|
+
return {
|
|
866
|
+
api,
|
|
867
|
+
name: "ignotum:sync",
|
|
868
|
+
configureServer: (server) => {
|
|
869
|
+
if (server.httpServer === null) {
|
|
870
|
+
throw new Error("Ignotum sync requires Vite's Node HTTP server.");
|
|
871
|
+
}
|
|
882
872
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
(
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
873
|
+
const runtime = ManagedRuntime.make(makeHandlersLayer(appDirectory, databasePath, managed));
|
|
874
|
+
api.dispose = () => runtime.dispose();
|
|
875
|
+
const httpServer = server.httpServer;
|
|
876
|
+
return runtime.runPromise(SyncHandlers).then(
|
|
877
|
+
(handlers) => {
|
|
878
|
+
api.invalidate = () => runtime.runPromise(handlers.invalidate);
|
|
879
|
+
const onUpgrade = (
|
|
880
|
+
request: IncomingMessage,
|
|
881
|
+
socket: import("node:stream").Duplex,
|
|
882
|
+
head: Buffer,
|
|
883
|
+
) => {
|
|
884
|
+
if (new URL(request.url ?? "/", "http://ignotum.local").pathname !== managedSyncPath)
|
|
885
|
+
return;
|
|
886
|
+
if (!isAppOrigin(request.headers.host, request.headers.origin)) {
|
|
887
|
+
rejectUpgrade(socket);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
handlers.upgrade(request, socket, head);
|
|
891
|
+
};
|
|
892
|
+
const onAdd = (file: string) => runtime.runFork(handlers.reload(file, "add"));
|
|
893
|
+
const onChange = (file: string) => runtime.runFork(handlers.reload(file, "change"));
|
|
894
|
+
const onUnlink = (file: string) => runtime.runFork(handlers.reload(file, "unlink"));
|
|
895
|
+
const cleanup = () => {
|
|
896
|
+
httpServer.off("upgrade", onUpgrade);
|
|
897
|
+
server.watcher.off("add", onAdd);
|
|
898
|
+
server.watcher.off("change", onChange);
|
|
899
|
+
server.watcher.off("unlink", onUnlink);
|
|
900
|
+
void runtime.dispose();
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
httpServer.on("upgrade", onUpgrade);
|
|
904
|
+
httpServer.once("close", cleanup);
|
|
905
|
+
server.watcher.on("add", onAdd);
|
|
906
|
+
server.watcher.on("change", onChange);
|
|
907
|
+
server.watcher.on("unlink", onUnlink);
|
|
908
|
+
server.middlewares.use((request, response, next) => {
|
|
909
|
+
if (!isApiPath(request)) {
|
|
910
|
+
next();
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
handlers.http(request, response);
|
|
914
|
+
});
|
|
915
|
+
},
|
|
916
|
+
(error) => {
|
|
907
917
|
void runtime.dispose();
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
handlers.http(request, response);
|
|
921
|
-
});
|
|
922
|
-
},
|
|
923
|
-
(error) => {
|
|
924
|
-
void runtime.dispose();
|
|
925
|
-
throw error;
|
|
926
|
-
},
|
|
927
|
-
);
|
|
928
|
-
},
|
|
929
|
-
}));
|
|
918
|
+
throw error;
|
|
919
|
+
},
|
|
920
|
+
);
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
export const ignotumManagedSyncPlugin = (
|
|
926
|
+
appDirectory: string,
|
|
927
|
+
databasePath: string,
|
|
928
|
+
options: LocalDevSyncOptions,
|
|
929
|
+
) => createSyncPlugin(appDirectory, databasePath, options);
|