Skip to content

Commit c832c1d

Browse files
client-lib
1 parent d5e27a5 commit c832c1d

31 files changed

+330
-191
lines changed

apps/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@effect/platform-browser": "^0.56.2",
2525
"@local/sync": "workspace:*",
2626
"@local/schema": "workspace:*",
27+
"@local/client-lib": "workspace:*",
2728
"@tanstack/react-router": "^1.105.0",
2829
"dexie": "^4.0.11",
2930
"dexie-react-hooks": "^1.1.7",

apps/client/src/lib/hooks/use-activity.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1+
import { hookQuery, RuntimeLib, useDexieQuery } from "@local/client-lib";
12
import { Activity } from "@local/schema";
23
import { Effect } from "effect";
3-
import { RuntimeClient } from "../runtime-client";
4-
import { useDexieQuery } from "../use-dexie-query";
5-
import { hookQuery } from "./hook-query";
64

75
export const useActivity = ({ workspaceId }: { workspaceId: string }) => {
86
return useDexieQuery(
97
() =>
10-
RuntimeClient.runPromise(
8+
RuntimeLib.runPromise(
119
hookQuery({ workspaceId }).pipe(
1210
Effect.map((snapshot) => [...snapshot.activity])
1311
)

apps/client/src/lib/hooks/use-metadata.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1+
import { hookQuery, RuntimeLib, useDexieQuery } from "@local/client-lib";
12
import { Metadata } from "@local/schema";
23
import { Effect } from "effect";
3-
import { RuntimeClient } from "../runtime-client";
4-
import { useDexieQuery } from "../use-dexie-query";
5-
import { hookQuery } from "./hook-query";
64

75
export const useMetadata = ({ workspaceId }: { workspaceId: string }) => {
86
return useDexieQuery(
97
() =>
10-
RuntimeClient.runPromise(
8+
RuntimeLib.runPromise(
119
hookQuery({ workspaceId }).pipe(
1210
Effect.map((snapshot) => [snapshot.metadata])
1311
)

apps/client/src/routes/$workspaceId/index.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import { Worker } from "@effect/platform";
22
import { BrowserWorker } from "@effect/platform-browser";
3+
import {
4+
RuntimeLib,
5+
Service,
6+
SyncWorker,
7+
useActionEffect,
8+
} from "@local/client-lib";
39
import { createFileRoute, Link } from "@tanstack/react-router";
410
import { Effect } from "effect";
511
import { startTransition, useEffect } from "react";
612
import { useActivity } from "../../lib/hooks/use-activity";
713
import { useMetadata } from "../../lib/hooks/use-metadata";
8-
import { RuntimeClient } from "../../lib/runtime-client";
9-
import { LoroStorage } from "../../lib/services/loro-storage";
10-
import { WorkspaceManager } from "../../lib/services/workspace-manager";
11-
import { useActionEffect } from "../../lib/use-action-effect";
12-
import { Bootstrap, LiveQuery } from "../../workers/schema";
1314

1415
const bootstrap = ({ workspaceId }: { workspaceId: string }) =>
1516
Effect.gen(function* () {
1617
const pool = yield* Worker.makePoolSerialized({ size: 1 });
17-
return yield* pool.broadcast(new Bootstrap({ workspaceId }));
18+
return yield* pool.broadcast(new SyncWorker.Bootstrap({ workspaceId }));
1819
}).pipe(
1920
Effect.scoped,
2021
Effect.provide(
2122
BrowserWorker.layer(
2223
() =>
2324
new globalThis.Worker(
24-
new URL("./src/workers/sync.ts", globalThis.origin),
25+
new URL("./src/workers/bootstrap.ts", globalThis.origin),
2526
{ type: "module" }
2627
)
2728
)
@@ -32,8 +33,8 @@ const bootstrap = ({ workspaceId }: { workspaceId: string }) =>
3233
export const Route = createFileRoute("/$workspaceId/")({
3334
component: RouteComponent,
3435
loader: ({ params: { workspaceId } }) =>
35-
RuntimeClient.runPromise(
36-
WorkspaceManager.getById({ workspaceId }).pipe(
36+
RuntimeLib.runPromise(
37+
Service.WorkspaceManager.getById({ workspaceId }).pipe(
3738
Effect.flatMap(Effect.fromNullable),
3839
Effect.tap(({ workspaceId }) => bootstrap({ workspaceId }))
3940
)
@@ -53,7 +54,7 @@ function RouteComponent() {
5354
const [, onBootstrap, bootstrapping] = useActionEffect(bootstrap);
5455
const [, onAdd] = useActionEffect((formData: FormData) =>
5556
Effect.gen(function* () {
56-
const loroStorage = yield* LoroStorage;
57+
const loroStorage = yield* Service.LoroStorage;
5758

5859
const firstName = formData.get("firstName") as string;
5960
const lastName = formData.get("lastName") as string;
@@ -74,11 +75,11 @@ function RouteComponent() {
7475
const url = new URL("./src/workers/live.ts", globalThis.origin);
7576
const newWorker = new globalThis.Worker(url, { type: "module" });
7677

77-
void RuntimeClient.runPromise(
78+
void RuntimeLib.runPromise(
7879
Effect.gen(function* () {
7980
const pool = yield* Worker.makePoolSerialized({ size: 1 });
8081
return yield* pool.broadcast(
81-
new LiveQuery({ workspaceId: workspace.workspaceId })
82+
new SyncWorker.LiveQuery({ workspaceId: workspace.workspaceId })
8283
);
8384
}).pipe(
8485
Effect.scoped,

apps/client/src/routes/$workspaceId/join.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
import { RuntimeLib, Service } from "@local/client-lib";
12
import { createFileRoute, redirect } from "@tanstack/react-router";
23
import { Effect } from "effect";
3-
import { RuntimeClient } from "../../lib/runtime-client";
4-
import { Sync } from "../../lib/services/sync";
54

65
export const Route = createFileRoute("/$workspaceId/join")({
76
component: RouteComponent,
87
loader: ({ params }) =>
9-
RuntimeClient.runPromise(
8+
RuntimeLib.runPromise(
109
Effect.gen(function* () {
11-
const { join } = yield* Sync;
10+
const { join } = yield* Service.Sync;
1211
yield* join({ workspaceId: params.workspaceId });
1312
return redirect({
1413
to: `/$workspaceId`,

apps/client/src/routes/$workspaceId/token.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1+
import { RuntimeLib, Service, useActionEffect } from "@local/client-lib";
12
import { createFileRoute, Link, useRouter } from "@tanstack/react-router";
23
import { Duration, Effect } from "effect";
3-
import { ApiClient } from "../../lib/api-client";
44
import { WEBSITE_URL } from "../../lib/constants";
5-
import { RuntimeClient } from "../../lib/runtime-client";
6-
import { WorkspaceManager } from "../../lib/services/workspace-manager";
7-
import { useActionEffect } from "../../lib/use-action-effect";
85

96
export const Route = createFileRoute("/$workspaceId/token")({
107
component: RouteComponent,
118
loader: ({ params: { workspaceId } }) =>
12-
RuntimeClient.runPromise(
9+
RuntimeLib.runPromise(
1310
Effect.gen(function* () {
14-
const api = yield* ApiClient;
15-
const token = yield* WorkspaceManager.getById({ workspaceId }).pipe(
11+
const api = yield* Service.ApiClient;
12+
const token = yield* Service.WorkspaceManager.getById({
13+
workspaceId,
14+
}).pipe(
1615
Effect.flatMap((workspace) => Effect.fromNullable(workspace?.token))
1716
);
1817

@@ -33,7 +32,7 @@ function RouteComponent() {
3332

3433
const [, onIssueToken, issuing] = useActionEffect((formData: FormData) =>
3534
Effect.gen(function* () {
36-
const api = yield* ApiClient;
35+
const api = yield* Service.ApiClient;
3736

3837
const clientId = formData.get("clientId") as string;
3938

@@ -53,7 +52,7 @@ function RouteComponent() {
5352

5453
const [, onRevoke, revoking] = useActionEffect((formData: FormData) =>
5554
Effect.gen(function* () {
56-
const api = yield* ApiClient;
55+
const api = yield* Service.ApiClient;
5756

5857
const clientId = formData.get("clientId") as string;
5958

apps/client/src/routes/__root.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1+
import { RuntimeLib, Service } from "@local/client-lib";
12
import { Outlet, createRootRoute } from "@tanstack/react-router";
23
import { Effect } from "effect";
3-
import { Dexie } from "../lib/dexie";
4-
import { RuntimeClient } from "../lib/runtime-client";
5-
import { Migration } from "../lib/services/migration";
64

75
export const Route = createRootRoute({
86
component: RootComponent,
97
loader: () =>
10-
RuntimeClient.runPromise(
11-
Migration.pipe(
8+
RuntimeLib.runPromise(
9+
Service.Migration.pipe(
1210
Effect.flatMap((migration) => migration.migrate),
1311
Effect.catchAll((error) => Effect.logError("Migration error", error)),
1412
Effect.andThen(
1513
Effect.gen(function* () {
16-
const { initClient } = yield* Dexie;
14+
const { initClient } = yield* Service.Dexie;
1715
return yield* initClient;
1816
})
1917
)

apps/client/src/routes/index.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1+
import { RuntimeLib, Service, useActionEffect } from "@local/client-lib";
12
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
23
import { Effect } from "effect";
3-
import { RuntimeClient } from "../lib/runtime-client";
4-
import { WorkspaceManager } from "../lib/services/workspace-manager";
5-
import { useActionEffect } from "../lib/use-action-effect";
64

75
export const Route = createFileRoute("/")({
86
component: HomeComponent,
9-
loader: () => RuntimeClient.runPromise(WorkspaceManager.getAll),
7+
loader: () => RuntimeLib.runPromise(Service.WorkspaceManager.getAll),
108
});
119

1210
function HomeComponent() {
@@ -15,7 +13,7 @@ function HomeComponent() {
1513

1614
const [, joinWorkspace] = useActionEffect(() =>
1715
Effect.gen(function* () {
18-
const workspace = yield* WorkspaceManager.create;
16+
const workspace = yield* Service.WorkspaceManager.create;
1917
yield* Effect.sync(() =>
2018
navigate({
2119
to: `/$workspaceId`,

apps/client/src/workers/sync.ts renamed to apps/client/src/workers/bootstrap.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import { WorkerRunner } from "@effect/platform";
22
import { BrowserWorkerRunner } from "@effect/platform-browser";
3+
import { RuntimeLib, Service, SyncWorker } from "@local/client-lib";
34
import { Effect, Layer } from "effect";
4-
import { RuntimeClient } from "../lib/runtime-client";
5-
import { Sync } from "../lib/services/sync";
6-
import { TempWorkspace } from "../lib/services/temp-workspace";
7-
import { WorkspaceManager } from "../lib/services/workspace-manager";
8-
import { WorkerMessage } from "./schema";
95

10-
const WorkerLive = WorkerRunner.layerSerialized(WorkerMessage, {
6+
const WorkerLive = WorkerRunner.layerSerialized(SyncWorker.WorkerMessage, {
117
Bootstrap: (params) =>
128
Effect.gen(function* () {
13-
const { push, pull } = yield* Sync;
9+
const { push, pull } = yield* Service.Sync;
1410

15-
const manager = yield* WorkspaceManager;
16-
const temp = yield* TempWorkspace;
11+
const manager = yield* Service.WorkspaceManager;
12+
const temp = yield* Service.TempWorkspace;
1713

1814
yield* Effect.log(`Running workspace '${params.workspaceId}'`);
1915

@@ -43,4 +39,4 @@ const WorkerLive = WorkerRunner.layerSerialized(WorkerMessage, {
4339
),
4440
}).pipe(Layer.provide(BrowserWorkerRunner.layer));
4541

46-
RuntimeClient.runFork(WorkerRunner.launch(WorkerLive));
42+
RuntimeLib.runFork(WorkerRunner.launch(WorkerLive));

apps/client/src/workers/live.ts

Lines changed: 6 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,22 @@
11
import { WorkerRunner } from "@effect/platform";
22
import { BrowserWorkerRunner } from "@effect/platform-browser";
3-
import { Snapshot } from "@local/sync";
4-
import { liveQuery } from "dexie";
5-
import {
6-
Array,
7-
Effect,
8-
Layer,
9-
Number,
10-
Schema,
11-
Stream,
12-
SynchronizedRef,
13-
} from "effect";
14-
import { Dexie } from "../lib/dexie";
15-
import { RuntimeClient } from "../lib/runtime-client";
16-
import { Sync } from "../lib/services/sync";
17-
import { WorkspaceManager } from "../lib/services/workspace-manager";
18-
import { type LiveQuery } from "./schema";
3+
import { RuntimeLib, SyncWorker } from "@local/client-lib";
4+
import { Effect, Layer } from "effect";
195

20-
const main = (params: { workspaceId: string }) =>
21-
Effect.gen(function* () {
22-
const manager = yield* WorkspaceManager;
23-
const { db } = yield* Dexie;
24-
const { push } = yield* Sync;
25-
26-
const snapshotEq = Array.getEquivalence(Number.Equivalence);
27-
28-
yield* Effect.log(`Live query workspace '${params.workspaceId}'`);
29-
30-
const workspace = yield* manager
31-
.getById({ workspaceId: params.workspaceId })
32-
.pipe(Effect.flatMap(Effect.fromNullable));
33-
34-
const live = liveQuery(() =>
35-
db.temp_workspace
36-
.where("workspaceId")
37-
.equals(params.workspaceId)
38-
.toArray()
39-
);
40-
41-
yield* Effect.forkScoped(
42-
Effect.acquireRelease(
43-
Effect.gen(function* () {
44-
yield* Effect.log("Subscribing");
45-
46-
const ref = yield* SynchronizedRef.make(0);
47-
return live.subscribe((payload) =>
48-
Effect.runPromise(
49-
Effect.gen(function* () {
50-
yield* Effect.log(`Change detected`);
51-
52-
const id = yield* ref.pipe(
53-
SynchronizedRef.updateAndGet((n) => n + 1)
54-
);
55-
56-
yield* Stream.runDrain(
57-
Stream.make(...payload).pipe(
58-
Stream.changesWith((a, b) =>
59-
snapshotEq(a.snapshot, b.snapshot)
60-
),
61-
Stream.debounce("3 seconds"),
62-
Stream.tap((message) =>
63-
Effect.gen(function* () {
64-
const streamId = yield* ref.get;
65-
if (streamId === id) {
66-
yield* Effect.log(
67-
`Syncing ${payload.length} changes`
68-
);
69-
70-
const snapshot = yield* Schema.decode(Snapshot)(
71-
message.snapshot
72-
);
73-
74-
yield* push({
75-
snapshot,
76-
snapshotId: message.snapshotId,
77-
workspaceId: workspace.workspaceId,
78-
});
79-
}
80-
})
81-
)
82-
)
83-
);
84-
})
85-
)
86-
);
87-
}),
88-
(subscription) =>
89-
Effect.gen(function* () {
90-
yield* Effect.log("Live query unsubscribing");
91-
return subscription.unsubscribe();
92-
})
93-
)
94-
);
95-
96-
return true;
97-
});
98-
99-
const WorkerLive = WorkerRunner.layer((params: LiveQuery) =>
6+
const WorkerLive = WorkerRunner.layer((params: SyncWorker.LiveQuery) =>
1007
Effect.scoped(
1018
Effect.gen(function* () {
9+
const worker = yield* SyncWorker.SyncWorker;
10210
yield* Effect.log("Startup live query connection");
10311

10412
yield* Effect.addFinalizer(() =>
10513
Effect.log("Closed live query connection")
10614
);
10715

108-
yield* Effect.fork(main({ workspaceId: params.workspaceId }));
16+
yield* Effect.fork(worker.liveSync({ workspaceId: params.workspaceId }));
10917
yield* Effect.never;
11018
}).pipe(Effect.mapError(() => "Live query error"))
11119
)
11220
).pipe(Layer.provide(BrowserWorkerRunner.layer));
11321

114-
RuntimeClient.runFork(WorkerRunner.launch(WorkerLive));
22+
RuntimeLib.runFork(WorkerRunner.launch(WorkerLive));

0 commit comments

Comments
 (0)