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/client/sync.ts
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import { BrowserSession } from "./id.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Duration,
|
|
6
|
-
Effect,
|
|
7
|
-
Fiber,
|
|
8
|
-
HashMap,
|
|
9
|
-
Layer,
|
|
10
|
-
ManagedRuntime,
|
|
11
|
-
Option,
|
|
12
|
-
Schedule,
|
|
13
|
-
Schema,
|
|
14
|
-
} from "effect";
|
|
15
|
-
import * as Socket from "effect/unstable/socket/Socket";
|
|
16
|
-
import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto";
|
|
2
|
+
import { ManagedBrowserClient } from "./managed-client.js";
|
|
3
|
+
import type { ExecutionSelection } from "@ignotum/contracts/platform";
|
|
4
|
+
import { Context, Effect, Layer, ManagedRuntime, Schema } from "effect";
|
|
17
5
|
|
|
18
6
|
import { encodeCanonicalJson } from "@ignotum/contracts/json";
|
|
19
7
|
import { type AppStateRevision } from "@ignotum/contracts/runtime/hosted";
|
|
20
8
|
import { IdGenerator } from "@ignotum/shared/id";
|
|
21
|
-
import {
|
|
9
|
+
import type { ManagedClientMessage, ManagedServerMessage } from "@ignotum/contracts/managed-sync";
|
|
22
10
|
|
|
23
11
|
import type {
|
|
24
12
|
ErrorValue,
|
|
@@ -27,33 +15,20 @@ import type {
|
|
|
27
15
|
} from "@ignotum/contracts/runtime/result";
|
|
28
16
|
import { failureFromWire, pending, Result } from "@ignotum/contracts/runtime/result";
|
|
29
17
|
import {
|
|
30
|
-
ClientMessageJson,
|
|
31
|
-
DeploymentCloseCode,
|
|
32
|
-
InvocationId,
|
|
33
|
-
ServerMessageJson,
|
|
34
18
|
SubscriptionId,
|
|
35
19
|
decodeTransportValue,
|
|
36
|
-
type ClientMessage,
|
|
37
20
|
type FunctionAddress,
|
|
38
|
-
type ServerMessage,
|
|
39
|
-
type SyncHandshake,
|
|
40
21
|
type WireResult,
|
|
41
22
|
} from "@ignotum/contracts/runtime/sync";
|
|
42
23
|
import {
|
|
43
24
|
ClientInfrastructureError,
|
|
44
25
|
ConnectionUnavailable,
|
|
45
|
-
InvalidClientMessage,
|
|
46
26
|
InvalidServerMessage,
|
|
47
|
-
ServerError,
|
|
48
|
-
InvalidMutationArguments,
|
|
49
27
|
clientErrorReporterLayer,
|
|
50
|
-
reportClientError,
|
|
51
28
|
} from "./errors.js";
|
|
52
|
-
import {
|
|
53
|
-
import { applyFileGrants, prepareMutationArguments } from "./files.js";
|
|
29
|
+
import { applyFileGrants } from "./files.js";
|
|
54
30
|
|
|
55
31
|
type Listener = () => void;
|
|
56
|
-
type SocketWriter = (chunk: string) => Effect.Effect<void, Socket.SocketError>;
|
|
57
32
|
|
|
58
33
|
interface QueryEntry {
|
|
59
34
|
readonly args: Schema.Json;
|
|
@@ -67,13 +42,16 @@ interface QueryEntry {
|
|
|
67
42
|
result: QueryResult<unknown, ErrorValue> | ClientInfrastructureError;
|
|
68
43
|
}
|
|
69
44
|
|
|
70
|
-
interface QueryObserver {
|
|
45
|
+
export interface QueryObserver {
|
|
71
46
|
readonly getSnapshot: () => QueryResult<unknown, ErrorValue> | ClientInfrastructureError;
|
|
72
47
|
readonly release: Effect.Effect<void>;
|
|
73
48
|
readonly subscribe: (listener: Listener) => () => void;
|
|
74
49
|
}
|
|
75
50
|
|
|
76
|
-
type QueryLifecycleMessage = Extract<
|
|
51
|
+
type QueryLifecycleMessage = Extract<
|
|
52
|
+
ManagedClientMessage,
|
|
53
|
+
{ readonly type: "Subscribe" | "Unsubscribe" }
|
|
54
|
+
>;
|
|
77
55
|
|
|
78
56
|
interface QueryCache {
|
|
79
57
|
readonly invalidate: (ids?: ReadonlyArray<SubscriptionId>) => void;
|
|
@@ -201,7 +179,9 @@ const makeQueryCache = (
|
|
|
201
179
|
} satisfies QueryCache;
|
|
202
180
|
};
|
|
203
181
|
|
|
204
|
-
type SnapshotFiles = NonNullable<
|
|
182
|
+
type SnapshotFiles = NonNullable<
|
|
183
|
+
Extract<ManagedServerMessage, { readonly type: "QuerySnapshot" }>["files"]
|
|
184
|
+
>;
|
|
205
185
|
|
|
206
186
|
const resultFromWire = Effect.fn("SyncClient.resultFromWire")(
|
|
207
187
|
(wire: WireResult, files: SnapshotFiles = []) =>
|
|
@@ -222,40 +202,13 @@ const resultFromWire = Effect.fn("SyncClient.resultFromWire")(
|
|
|
222
202
|
}),
|
|
223
203
|
);
|
|
224
204
|
|
|
225
|
-
export const shouldReloadForCloseCode = (code: number): boolean => code === DeploymentCloseCode;
|
|
226
|
-
|
|
227
|
-
const handshakesMatch = (first: SyncHandshake, next: SyncHandshake): boolean =>
|
|
228
|
-
first.appId === next.appId &&
|
|
229
|
-
first.deploymentId === next.deploymentId &&
|
|
230
|
-
first.generation === next.generation;
|
|
231
|
-
|
|
232
|
-
type HandshakeDecision = "Accept" | "Ignore" | "Reload";
|
|
233
|
-
|
|
234
|
-
const handshakeDecision = (
|
|
235
|
-
remembered: SyncHandshake | undefined,
|
|
236
|
-
acceptedOnConnection: boolean,
|
|
237
|
-
next: SyncHandshake,
|
|
238
|
-
): HandshakeDecision => {
|
|
239
|
-
if (remembered !== undefined && !handshakesMatch(remembered, next)) return "Reload";
|
|
240
|
-
return acceptedOnConnection ? "Ignore" : "Accept";
|
|
241
|
-
};
|
|
242
|
-
|
|
243
205
|
export const syncClientInternals = {
|
|
244
206
|
makeQueryCache,
|
|
245
|
-
handshakeDecision,
|
|
246
207
|
queryIdentity: queryKey,
|
|
247
208
|
resultFromWire,
|
|
248
|
-
handshakesMatch,
|
|
249
|
-
shouldReloadForCloseCode,
|
|
250
209
|
};
|
|
251
210
|
|
|
252
|
-
|
|
253
|
-
const url = new URL(syncPath, globalThis.location.href);
|
|
254
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
255
|
-
return url.href;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
interface SyncClientService {
|
|
211
|
+
export interface SyncClientService {
|
|
259
212
|
readonly mutate: (
|
|
260
213
|
functionAddress: FunctionAddress,
|
|
261
214
|
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- Generated reference argument types are deliberately erased at the client transport boundary.
|
|
@@ -267,539 +220,60 @@ interface SyncClientService {
|
|
|
267
220
|
) => Effect.Effect<QueryObserver, ClientInfrastructureError>;
|
|
268
221
|
}
|
|
269
222
|
|
|
270
|
-
interface PendingInvocation {
|
|
271
|
-
readonly sessionEpoch: string;
|
|
272
|
-
readonly deferred: Deferred.Deferred<
|
|
273
|
-
IgnotumResult<unknown, ErrorValue>,
|
|
274
|
-
ClientInfrastructureError
|
|
275
|
-
>;
|
|
276
|
-
readonly function: FunctionAddress;
|
|
277
|
-
readonly message: Extract<ClientMessage, { readonly type: "Invoke" | "Prepare" }>;
|
|
278
|
-
readonly prepared: Deferred.Deferred<
|
|
279
|
-
Extract<ServerMessage, { readonly type: "Preparation" }>["uploads"],
|
|
280
|
-
ClientInfrastructureError
|
|
281
|
-
>;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
223
|
export class SyncClient extends Context.Service<SyncClient, SyncClientService>()(
|
|
285
224
|
"ignotum/client/sync/SyncClient",
|
|
286
|
-
) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const unsubscribeSession = session.subscribe(() => {
|
|
340
|
-
if (session.current() === undefined) {
|
|
341
|
-
queryCache.invalidatePrivate();
|
|
342
|
-
writer = undefined;
|
|
343
|
-
if (sessionSuspended !== undefined) Deferred.doneUnsafe(sessionSuspended, Effect.void);
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
yield* Effect.addFinalizer(() => Effect.sync(unsubscribeSession));
|
|
347
|
-
|
|
348
|
-
const rejectInvocation = (id: InvocationId, error: ClientInfrastructureError) => {
|
|
349
|
-
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, id));
|
|
350
|
-
if (pendingInvocation === undefined) {
|
|
351
|
-
return Effect.void;
|
|
352
|
-
}
|
|
353
|
-
invocations = HashMap.remove(invocations, id);
|
|
354
|
-
return reportClientError(error).pipe(
|
|
355
|
-
Effect.andThen(Deferred.fail(pendingInvocation.prepared, error)),
|
|
356
|
-
Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)),
|
|
357
|
-
Effect.asVoid,
|
|
358
|
-
);
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (
|
|
362
|
-
message: Exclude<ServerMessage, SyncHandshake | { readonly type: "Deployment" }>,
|
|
363
|
-
) {
|
|
364
|
-
switch (message.type) {
|
|
365
|
-
case "Session": {
|
|
366
|
-
if (message.event === "Updated") {
|
|
367
|
-
const previous = session.current();
|
|
368
|
-
if (previous !== undefined && previous.sessionEpoch !== message.session.sessionEpoch)
|
|
369
|
-
return;
|
|
370
|
-
if (previous?.viewRevision !== message.session.viewRevision)
|
|
371
|
-
queryCache.invalidatePrivate();
|
|
372
|
-
session.accept(message.session, message.serverTime);
|
|
373
|
-
} else {
|
|
374
|
-
if (rememberedHandshake?.session?.sessionEpoch !== message.sessionEpoch) return;
|
|
375
|
-
session.suspend(
|
|
376
|
-
message.event === "Unavailable"
|
|
377
|
-
? "Your ID session is temporarily unavailable."
|
|
378
|
-
: undefined,
|
|
379
|
-
);
|
|
380
|
-
if (message.event !== "Unavailable") {
|
|
381
|
-
yield* Effect.forEach(HashMap.keys(invocations), (id) =>
|
|
382
|
-
rejectInvocation(
|
|
383
|
-
id,
|
|
384
|
-
ServerError.make({
|
|
385
|
-
code: "SessionChanged",
|
|
386
|
-
message: "The session changed. This operation may already have completed.",
|
|
387
|
-
}),
|
|
388
|
-
),
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
case "Invalidate": {
|
|
395
|
-
if (message.sessionEpoch === session.current()?.sessionEpoch)
|
|
396
|
-
queryCache.invalidate(message.ids);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
case "Snapshot": {
|
|
400
|
-
const current = session.current();
|
|
401
|
-
if (current === undefined || message.identity?.sessionEpoch !== current.sessionEpoch)
|
|
402
|
-
return;
|
|
403
|
-
if (
|
|
404
|
-
message.identity.dependsOnId &&
|
|
405
|
-
message.identity.viewRevision !== current.viewRevision
|
|
406
|
-
)
|
|
407
|
-
return;
|
|
408
|
-
const entry = queryCache.getById(message.id);
|
|
409
|
-
if (entry === undefined) return;
|
|
410
|
-
entry.dependsOnId = message.identity.dependsOnId;
|
|
411
|
-
if (entry.revision !== undefined && message.revision < entry.revision) return;
|
|
412
|
-
yield* resultFromWire(message.result, message.files ?? []).pipe(
|
|
413
|
-
Effect.tap((result) =>
|
|
414
|
-
Effect.sync(() => queryCache.setResult(message.id, result, message.revision)),
|
|
415
|
-
),
|
|
416
|
-
Effect.catchTags({
|
|
417
|
-
InvalidServerMessage: (error) => {
|
|
418
|
-
queryCache.setResult(message.id, error, message.revision);
|
|
419
|
-
return reportClientError(error);
|
|
420
|
-
},
|
|
421
|
-
}),
|
|
422
|
-
);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
case "Preparation": {
|
|
426
|
-
if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
|
|
427
|
-
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
|
|
428
|
-
if (pendingInvocation !== undefined) {
|
|
429
|
-
yield* Deferred.succeed(pendingInvocation.prepared, message.uploads);
|
|
430
|
-
}
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
case "Result": {
|
|
434
|
-
if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
|
|
435
|
-
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
|
|
436
|
-
if (pendingInvocation === undefined) return;
|
|
437
|
-
yield* resultFromWire(message.result).pipe(
|
|
438
|
-
Effect.flatMap((result) => {
|
|
439
|
-
invocations = HashMap.remove(invocations, message.id);
|
|
440
|
-
return Deferred.succeed(pendingInvocation.deferred, result);
|
|
441
|
-
}),
|
|
442
|
-
Effect.catchTags({
|
|
443
|
-
InvalidServerMessage: (error) => rejectInvocation(message.id, error),
|
|
444
|
-
}),
|
|
445
|
-
);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
case "Error": {
|
|
449
|
-
if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
|
|
450
|
-
const error =
|
|
451
|
-
message.operation === undefined
|
|
452
|
-
? ServerError.make({
|
|
453
|
-
code: message.code,
|
|
454
|
-
message: message.message,
|
|
455
|
-
})
|
|
456
|
-
: ServerError.make({
|
|
457
|
-
code: message.code,
|
|
458
|
-
message: message.message,
|
|
459
|
-
operation: message.operation,
|
|
460
|
-
});
|
|
461
|
-
if (message.operation?.type === "Invocation") {
|
|
462
|
-
yield* rejectInvocation(message.operation.id, error);
|
|
463
|
-
} else if (message.operation?.type === "Subscription") {
|
|
464
|
-
queryCache.setResult(message.operation.id, error);
|
|
465
|
-
yield* reportClientError(error);
|
|
466
|
-
} else {
|
|
467
|
-
yield* reportClientError(error);
|
|
468
|
-
}
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
const requestReload = () => {
|
|
475
|
-
if (reloadRequested) return;
|
|
476
|
-
reloadRequested = true;
|
|
477
|
-
writer = undefined;
|
|
478
|
-
globalThis.location.reload();
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
const disconnect = Effect.sync(() => {
|
|
482
|
-
writer = undefined;
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
const connect = Effect.scoped(
|
|
486
|
-
Effect.gen(function* () {
|
|
487
|
-
if (reloadRequested) return yield* Effect.never;
|
|
488
|
-
yield* session.bootstrap.pipe(
|
|
489
|
-
Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message })),
|
|
490
|
-
);
|
|
491
|
-
const socket = yield* Socket.makeWebSocket(
|
|
492
|
-
`${socketUrl()}${session.socketParameters()}`,
|
|
493
|
-
{
|
|
494
|
-
closeCodeIsError: (code) => {
|
|
495
|
-
if (shouldReloadForCloseCode(code)) requestReload();
|
|
496
|
-
return false;
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
);
|
|
500
|
-
const write = yield* socket.writer;
|
|
501
|
-
const handshakeReceived = yield* Deferred.make<void>();
|
|
502
|
-
let handshakeAccepted = false;
|
|
503
|
-
const acceptHandshake = Effect.fn("SyncClient.acceptHandshake")(function* (
|
|
504
|
-
handshake: SyncHandshake,
|
|
505
|
-
) {
|
|
506
|
-
const decision = handshakeDecision(rememberedHandshake, handshakeAccepted, handshake);
|
|
507
|
-
if (decision === "Reload") {
|
|
508
|
-
requestReload();
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
if (decision === "Ignore") return;
|
|
512
|
-
if (handshake.session === undefined || handshake.serverTime === undefined) {
|
|
513
|
-
requestReload();
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
if (
|
|
517
|
-
rememberedHandshake?.session !== undefined &&
|
|
518
|
-
rememberedHandshake.session.sessionEpoch !== handshake.session.sessionEpoch
|
|
519
|
-
) {
|
|
520
|
-
queryCache.invalidatePrivate();
|
|
521
|
-
yield* Effect.forEach(HashMap.keys(invocations), (id) =>
|
|
522
|
-
rejectInvocation(
|
|
523
|
-
id,
|
|
524
|
-
ServerError.make({
|
|
525
|
-
code: "SessionChanged",
|
|
526
|
-
message: "The session changed. This operation may already have completed.",
|
|
527
|
-
}),
|
|
528
|
-
),
|
|
529
|
-
);
|
|
530
|
-
} else if (
|
|
531
|
-
rememberedHandshake?.session?.viewRevision !== handshake.session.viewRevision
|
|
532
|
-
) {
|
|
533
|
-
queryCache.invalidatePrivate();
|
|
534
|
-
}
|
|
535
|
-
yield* Effect.forEach(HashMap.entries(invocations), ([id, pending]) =>
|
|
536
|
-
pending.sessionEpoch === handshake.session!.sessionEpoch
|
|
537
|
-
? Effect.void
|
|
538
|
-
: rejectInvocation(
|
|
539
|
-
id,
|
|
540
|
-
ServerError.make({
|
|
541
|
-
code: "SessionChanged",
|
|
542
|
-
message: "The session changed. This operation may already have completed.",
|
|
543
|
-
}),
|
|
544
|
-
),
|
|
545
|
-
);
|
|
546
|
-
session.accept(handshake.session, handshake.serverTime);
|
|
547
|
-
rememberedHandshake = handshake;
|
|
548
|
-
handshakeAccepted = true;
|
|
549
|
-
writer = write;
|
|
550
|
-
yield* Deferred.succeed(handshakeReceived, undefined);
|
|
551
|
-
yield* Effect.gen(function* () {
|
|
552
|
-
yield* Effect.forEach(queryCache.entries(), (entry) =>
|
|
553
|
-
sendWith(write, subscribeMessage(entry)),
|
|
554
|
-
);
|
|
555
|
-
yield* Effect.forEach(HashMap.values(invocations), (pendingInvocation) =>
|
|
556
|
-
sendWith(write, pendingInvocation.message),
|
|
557
|
-
);
|
|
558
|
-
}).pipe(Effect.tapError(reportClientError), Effect.ignore);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
const handleText = Effect.fn("SyncClient.handleText")(function* (text: string) {
|
|
562
|
-
const message = yield* Schema.decodeEffect(ServerMessageJson)(text).pipe(
|
|
563
|
-
Effect.mapError((cause) =>
|
|
564
|
-
InvalidServerMessage.make({
|
|
565
|
-
cause,
|
|
566
|
-
message: "The server returned an invalid sync message.",
|
|
567
|
-
}),
|
|
568
|
-
),
|
|
569
|
-
);
|
|
570
|
-
if (message.type === "Handshake") return yield* acceptHandshake(message);
|
|
571
|
-
if (message.type === "Deployment") {
|
|
572
|
-
requestReload();
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (!handshakeAccepted) {
|
|
576
|
-
return yield* InvalidServerMessage.make({
|
|
577
|
-
cause: new Error("The server sent a sync message before the handshake."),
|
|
578
|
-
message: "The server returned a sync message before the handshake.",
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
return yield* handleMessage(message);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
const suspended = yield* Deferred.make<void>();
|
|
585
|
-
sessionSuspended = suspended;
|
|
586
|
-
yield* Effect.addFinalizer(() =>
|
|
587
|
-
Effect.sync(() => {
|
|
588
|
-
if (sessionSuspended === suspended) sessionSuspended = undefined;
|
|
589
|
-
}),
|
|
590
|
-
);
|
|
591
|
-
const run = yield* socket.runString(handleText).pipe(Effect.forkChild);
|
|
592
|
-
yield* Effect.raceFirst(
|
|
593
|
-
Deferred.await(handshakeReceived).pipe(Effect.timeout("10 seconds")),
|
|
594
|
-
Fiber.join(run),
|
|
595
|
-
);
|
|
596
|
-
yield* Effect.raceFirst(Fiber.join(run), Deferred.await(suspended));
|
|
597
|
-
}),
|
|
598
|
-
).pipe(
|
|
599
|
-
Effect.mapError((cause) =>
|
|
600
|
-
Schema.is(ClientInfrastructureError)(cause)
|
|
601
|
-
? cause
|
|
602
|
-
: ConnectionUnavailable.make({
|
|
603
|
-
cause,
|
|
604
|
-
message: "The sync connection was lost.",
|
|
605
|
-
}),
|
|
606
|
-
),
|
|
607
|
-
Effect.ensuring(disconnect),
|
|
608
|
-
);
|
|
609
|
-
|
|
610
|
-
const reconnectSchedule = Schedule.exponential("250 millis").pipe(
|
|
611
|
-
Schedule.modifyDelay(({ duration }) =>
|
|
612
|
-
Effect.succeed(Duration.min(duration, Duration.seconds(5))),
|
|
613
|
-
),
|
|
614
|
-
Schedule.jittered,
|
|
615
|
-
);
|
|
616
|
-
yield* connect.pipe(
|
|
617
|
-
Effect.andThen(
|
|
618
|
-
Effect.fail(
|
|
619
|
-
ConnectionUnavailable.make({
|
|
620
|
-
message: "The sync connection closed; reconnecting.",
|
|
621
|
-
}),
|
|
622
|
-
),
|
|
623
|
-
),
|
|
624
|
-
Effect.tapError(reportClientError),
|
|
625
|
-
Effect.retry(reconnectSchedule),
|
|
626
|
-
Effect.forkScoped,
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
const observe: SyncClientService["observe"] = Effect.fn("SyncClient.observe")(
|
|
630
|
-
function* (functionAddress, args) {
|
|
631
|
-
return yield* queryCache.observe(functionAddress, args);
|
|
632
|
-
},
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
const mutate: SyncClientService["mutate"] = Effect.fn("SyncClient.mutate")(
|
|
636
|
-
function* (functionAddress, args) {
|
|
637
|
-
const admittedSession =
|
|
638
|
-
session.current() ??
|
|
639
|
-
rememberedHandshake?.session ??
|
|
640
|
-
(yield* session.bootstrap.pipe(
|
|
641
|
-
Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message })),
|
|
642
|
-
));
|
|
643
|
-
const invocationId = yield* ids.generate(InvocationId);
|
|
644
|
-
const preparedArguments = yield* prepareMutationArguments(args).pipe(
|
|
645
|
-
Effect.provideService(IdGenerator, ids),
|
|
646
|
-
Effect.mapError((cause) =>
|
|
647
|
-
InvalidMutationArguments.make({
|
|
648
|
-
cause,
|
|
649
|
-
function: functionAddress,
|
|
650
|
-
message: `Mutation arguments for ${functionAddress} contain an unsupported file or value.`,
|
|
651
|
-
}),
|
|
652
|
-
),
|
|
653
|
-
Effect.tapError(reportClientError),
|
|
654
|
-
);
|
|
655
|
-
const jsonArgs = yield* Effect.try({
|
|
656
|
-
try: () => encodeTransportObject(preparedArguments.value),
|
|
657
|
-
catch: (cause) =>
|
|
658
|
-
InvalidMutationArguments.make({
|
|
659
|
-
cause,
|
|
660
|
-
function: functionAddress,
|
|
661
|
-
message: `Mutation arguments for ${functionAddress} contain an unsupported value.`,
|
|
662
|
-
}),
|
|
663
|
-
}).pipe(Effect.tapError(reportClientError));
|
|
664
|
-
const deferred = yield* Deferred.make<
|
|
665
|
-
IgnotumResult<unknown, ErrorValue>,
|
|
666
|
-
ClientInfrastructureError
|
|
667
|
-
>();
|
|
668
|
-
const prepared = yield* Deferred.make<
|
|
669
|
-
Extract<ServerMessage, { readonly type: "Preparation" }>["uploads"],
|
|
670
|
-
ClientInfrastructureError
|
|
671
|
-
>();
|
|
672
|
-
const invokeMessage: Extract<ClientMessage, { readonly type: "Invoke" }> = {
|
|
673
|
-
type: "Invoke",
|
|
674
|
-
id: invocationId,
|
|
675
|
-
kind: "Mutation",
|
|
676
|
-
function: functionAddress,
|
|
677
|
-
args: jsonArgs,
|
|
678
|
-
};
|
|
679
|
-
const message: PendingInvocation["message"] =
|
|
680
|
-
preparedArguments.fileIds.length === 0
|
|
681
|
-
? invokeMessage
|
|
682
|
-
: {
|
|
683
|
-
type: "Prepare",
|
|
684
|
-
id: invocationId,
|
|
685
|
-
kind: "Mutation",
|
|
686
|
-
function: functionAddress,
|
|
687
|
-
args: jsonArgs,
|
|
688
|
-
files: preparedArguments.fileIds,
|
|
689
|
-
};
|
|
690
|
-
invocations = HashMap.set(invocations, invocationId, {
|
|
691
|
-
sessionEpoch: admittedSession.sessionEpoch,
|
|
692
|
-
deferred,
|
|
693
|
-
function: functionAddress,
|
|
694
|
-
message,
|
|
695
|
-
prepared,
|
|
696
|
-
});
|
|
697
|
-
return yield* Effect.gen(function* () {
|
|
698
|
-
if (writer !== undefined) {
|
|
699
|
-
yield* sendWith(writer, message).pipe(
|
|
700
|
-
Effect.catchTags({
|
|
701
|
-
ConnectionUnavailable: reportClientError,
|
|
702
|
-
InvalidClientMessage: (error) => rejectInvocation(invocationId, error),
|
|
703
|
-
}),
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
if (message.type === "Prepare") {
|
|
707
|
-
const uploads = yield* Deferred.await(prepared);
|
|
708
|
-
const expected = new Set(message.files);
|
|
709
|
-
const received = new Set<string>();
|
|
710
|
-
const transfers: Array<{ readonly file: File; readonly url: string }> = [];
|
|
711
|
-
for (const upload of uploads) {
|
|
712
|
-
if (!expected.has(upload.id) || received.has(upload.id)) {
|
|
713
|
-
return yield* InvalidServerMessage.make({
|
|
714
|
-
cause: new Error(`Unexpected prepared file '${upload.id}'.`),
|
|
715
|
-
message: "The server returned invalid application file preparation data.",
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
received.add(upload.id);
|
|
719
|
-
const file = preparedArguments.nativeFiles.get(upload.id);
|
|
720
|
-
if (file === undefined) {
|
|
721
|
-
return yield* InvalidServerMessage.make({
|
|
722
|
-
cause: new Error(`Missing native file '${upload.id}'.`),
|
|
723
|
-
message: "The server prepared an unknown application file.",
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
if (upload.url === undefined) continue;
|
|
727
|
-
transfers.push({ file, url: upload.url });
|
|
728
|
-
}
|
|
729
|
-
if (received.size !== expected.size) {
|
|
730
|
-
return yield* InvalidServerMessage.make({
|
|
731
|
-
cause: new Error("The server omitted a prepared file."),
|
|
732
|
-
message: "The server returned incomplete application file preparation data.",
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
yield* Effect.forEach(
|
|
736
|
-
transfers,
|
|
737
|
-
Effect.fn("SyncClient.uploadFile")(function* ({ file, url }) {
|
|
738
|
-
const response = yield* Effect.tryPromise({
|
|
739
|
-
try: (signal) =>
|
|
740
|
-
fetch(url, {
|
|
741
|
-
method: "PUT",
|
|
742
|
-
body: file,
|
|
743
|
-
headers: { "content-type": file.type },
|
|
744
|
-
signal,
|
|
745
|
-
}),
|
|
746
|
-
catch: (cause) =>
|
|
747
|
-
ConnectionUnavailable.make({
|
|
748
|
-
cause,
|
|
749
|
-
message: `File '${file.name}' could not be uploaded.`,
|
|
750
|
-
}),
|
|
751
|
-
});
|
|
752
|
-
if (!response.ok) {
|
|
753
|
-
const detail = yield* Effect.promise(() => response.text());
|
|
754
|
-
return yield* InvalidMutationArguments.make({
|
|
755
|
-
cause: new Error(detail),
|
|
756
|
-
function: functionAddress,
|
|
757
|
-
message: `File '${file.name}' was rejected by application storage.`,
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
}),
|
|
761
|
-
{ concurrency: 3, discard: true },
|
|
762
|
-
);
|
|
763
|
-
const pendingInvocation = Option.getOrUndefined(
|
|
764
|
-
HashMap.get(invocations, invocationId),
|
|
765
|
-
);
|
|
766
|
-
if (pendingInvocation === undefined) return yield* Deferred.await(deferred);
|
|
767
|
-
invocations = HashMap.set(invocations, invocationId, {
|
|
768
|
-
...pendingInvocation,
|
|
769
|
-
message: invokeMessage,
|
|
770
|
-
});
|
|
771
|
-
if (writer !== undefined) {
|
|
772
|
-
yield* sendWith(writer, invokeMessage).pipe(
|
|
773
|
-
Effect.catchTags({
|
|
774
|
-
ConnectionUnavailable: reportClientError,
|
|
775
|
-
InvalidClientMessage: (error) => rejectInvocation(invocationId, error),
|
|
776
|
-
}),
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return yield* Deferred.await(deferred);
|
|
781
|
-
}).pipe(
|
|
782
|
-
Effect.tapError(() =>
|
|
783
|
-
Effect.sync(() => {
|
|
784
|
-
invocations = HashMap.remove(invocations, invocationId);
|
|
785
|
-
}),
|
|
786
|
-
),
|
|
787
|
-
);
|
|
788
|
-
},
|
|
789
|
-
);
|
|
790
|
-
|
|
791
|
-
return SyncClient.of({ mutate, observe });
|
|
792
|
-
}),
|
|
793
|
-
).pipe(Layer.provide(Layer.merge(Socket.layerWebSocketConstructorGlobal, BrowserCrypto.layer)));
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
export const syncRuntime = ManagedRuntime.make(
|
|
797
|
-
SyncClient.layer.pipe(
|
|
798
|
-
Layer.provideMerge(BrowserSession.layer),
|
|
799
|
-
Layer.provideMerge(clientErrorReporterLayer),
|
|
800
|
-
Layer.provideMerge(IdGenerator.layer),
|
|
801
|
-
),
|
|
802
|
-
);
|
|
225
|
+
) {}
|
|
226
|
+
|
|
227
|
+
const makeUnmountedSyncRuntime = () =>
|
|
228
|
+
ManagedRuntime.make(
|
|
229
|
+
Layer.succeed(SyncClient)({
|
|
230
|
+
observe: () =>
|
|
231
|
+
Effect.fail(ConnectionUnavailable.make({ message: "The application has not mounted." })),
|
|
232
|
+
mutate: () =>
|
|
233
|
+
Effect.fail(ConnectionUnavailable.make({ message: "The application has not mounted." })),
|
|
234
|
+
}).pipe(
|
|
235
|
+
Layer.provideMerge(BrowserSession.layer),
|
|
236
|
+
Layer.provideMerge(clientErrorReporterLayer),
|
|
237
|
+
Layer.provideMerge(IdGenerator.layer),
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
export let syncRuntime = makeUnmountedSyncRuntime();
|
|
241
|
+
|
|
242
|
+
export const mountManagedSync = (
|
|
243
|
+
selection: ExecutionSelection,
|
|
244
|
+
browser: Window,
|
|
245
|
+
callbacks: { readonly stopAdmission: () => void; readonly reload: () => void },
|
|
246
|
+
) => {
|
|
247
|
+
const client = new ManagedBrowserClient(selection, browser, syncClientInternals, callbacks);
|
|
248
|
+
const previous = syncRuntime;
|
|
249
|
+
void previous.dispose();
|
|
250
|
+
syncRuntime = ManagedRuntime.make(
|
|
251
|
+
Layer.effect(
|
|
252
|
+
SyncClient,
|
|
253
|
+
Effect.gen(function* () {
|
|
254
|
+
client.attachSession(yield* BrowserSession, yield* IdGenerator);
|
|
255
|
+
return SyncClient.of({ observe: client.observe, mutate: client.mutate });
|
|
256
|
+
}),
|
|
257
|
+
).pipe(
|
|
258
|
+
Layer.provideMerge(BrowserSession.layer),
|
|
259
|
+
Layer.provideMerge(clientErrorReporterLayer),
|
|
260
|
+
Layer.provideMerge(IdGenerator.layer),
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
const runtime = syncRuntime;
|
|
264
|
+
void runtime.runPromise(SyncClient).then(
|
|
265
|
+
() => client.start(),
|
|
266
|
+
() => client.dispose(),
|
|
267
|
+
);
|
|
268
|
+
return {
|
|
269
|
+
client,
|
|
270
|
+
dispose: () => {
|
|
271
|
+
client.dispose();
|
|
272
|
+
void runtime.dispose();
|
|
273
|
+
if (syncRuntime === runtime) syncRuntime = makeUnmountedSyncRuntime();
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
};
|
|
803
277
|
|
|
804
278
|
// SAFETY: Vite adds this optional property to browser development modules.
|
|
805
279
|
const hot = (
|