Skip to content

Commit 215dea8

Browse files
app schema in separate package
1 parent 5b0c393 commit 215dea8

File tree

21 files changed

+290
-229
lines changed

21 files changed

+290
-229
lines changed

apps/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@effect/platform": "^0.77.2",
2424
"@effect/platform-browser": "^0.56.2",
2525
"@local/sync": "workspace:*",
26+
"@local/schema": "workspace:*",
2627
"@tanstack/react-router": "^1.105.0",
2728
"dexie": "^4.0.11",
2829
"dexie-react-hooks": "^1.1.7",

apps/client/src/lib/hooks/hook-query.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { type LoroSchema } from "@local/sync/loro";
2-
import { Effect } from "effect";
1+
import { SnapshotSchema, type LoroSchema } from "@local/schema";
2+
import { Effect, Schema } from "effect";
33
import { LoroDoc } from "loro-crdt";
44
import { TempWorkspace } from "../services/temp-workspace";
55
import { WorkspaceManager } from "../services/workspace-manager";
@@ -21,5 +21,6 @@ export const hookQuery = ({ workspaceId }: { workspaceId: string }) =>
2121
doc.import(tempWorkspace.snapshot);
2222
}
2323

24-
return doc.getList("activity").toJSON();
24+
const json = doc.toJSON() as unknown;
25+
return yield* Schema.decodeUnknown(SnapshotSchema)(json);
2526
});
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import { Activity } from "@local/sync/loro";
1+
import { Activity } from "@local/schema";
2+
import { Effect } from "effect";
23
import { RuntimeClient } from "../runtime-client";
34
import { useDexieQuery } from "../use-dexie-query";
45
import { hookQuery } from "./hook-query";
56

67
export const useActivity = ({ workspaceId }: { workspaceId: string }) => {
78
return useDexieQuery(
8-
() => RuntimeClient.runPromise(hookQuery({ workspaceId })),
9+
() =>
10+
RuntimeClient.runPromise(
11+
hookQuery({ workspaceId }).pipe(
12+
Effect.map((snapshot) => [...snapshot.activity])
13+
)
14+
),
915
Activity
1016
);
1117
};

apps/client/src/lib/schema.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ClientId, SnapshotId, WorkspaceId } from "@local/sync";
2-
import { Snapshot } from "@local/sync/loro";
1+
import { ClientId, Snapshot, SnapshotId, WorkspaceId } from "@local/sync";
32
import { Schema } from "effect";
43

54
export class ClientTable extends Schema.Class<ClientTable>("ClientTable")({

apps/client/src/lib/services/loro-storage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Activity, type LoroSchema } from "@local/sync/loro";
1+
import { Activity, type LoroSchema } from "@local/schema";
22
import { Effect, Schema } from "effect";
33
import { LoroDoc, LoroMap, VersionVector } from "loro-crdt";
44
import { TempWorkspace } from "./temp-workspace";

apps/client/src/lib/services/sync.ts

Lines changed: 110 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Effect } from "effect";
1+
import { Effect, flow, Option } from "effect";
22
import { LoroDoc } from "loro-crdt";
33
import { ApiClient } from "../api-client";
44
import { Dexie } from "../dexie";
5-
import type { WorkspaceTable } from "../schema";
65
import { TempWorkspace } from "./temp-workspace";
76
import { WorkspaceManager } from "./workspace-manager";
87

@@ -22,85 +21,134 @@ export class Sync extends Effect.Service<Sync>()("Sync", {
2221
return {
2322
push: ({
2423
snapshot,
25-
workspace,
24+
workspaceId,
2625
snapshotId,
2726
}: {
28-
workspace: WorkspaceTable;
27+
workspaceId: string;
2928
snapshotId: string;
3029
snapshot: globalThis.Uint8Array;
3130
}) =>
32-
Effect.gen(function* () {
33-
const clientId = yield* initClient;
34-
yield* Effect.log(`Pushing snapshot ${snapshotId}`);
31+
manager.getById({ workspaceId }).pipe(
32+
Effect.flatMap(
33+
flow(
34+
Option.fromNullable,
35+
Option.match({
36+
onNone: () => Effect.log("No workspace found"),
37+
onSome: (workspace) =>
38+
Effect.gen(function* () {
39+
const clientId = yield* initClient;
40+
yield* Effect.log(`Pushing snapshot ${snapshotId}`);
3541

36-
const response = yield* Effect.fromNullable(workspace.token).pipe(
37-
Effect.flatMap((token) =>
38-
client.syncData
39-
.push({
40-
headers: { "x-api-key": token },
41-
path: { workspaceId: workspace.workspaceId },
42-
payload: { snapshot, snapshotId },
43-
})
44-
.pipe(
45-
Effect.map((response) => ({
46-
...response,
47-
token,
48-
}))
49-
)
50-
),
51-
Effect.catchTag("NoSuchElementException", () =>
52-
client.syncAuth
53-
.generateToken({
54-
payload: {
55-
clientId,
56-
snapshot,
57-
snapshotId,
58-
workspaceId: workspace.workspaceId,
59-
},
60-
})
61-
.pipe(
62-
Effect.tap(({ token }) =>
63-
manager.setToken({
64-
workspaceId: workspace.workspaceId,
65-
token,
66-
})
67-
)
68-
)
69-
)
70-
);
42+
const response = yield* Effect.fromNullable(
43+
workspace.token
44+
).pipe(
45+
Effect.flatMap((token) =>
46+
client.syncData
47+
.push({
48+
headers: { "x-api-key": token },
49+
path: { workspaceId: workspace.workspaceId },
50+
payload: { snapshot, snapshotId },
51+
})
52+
.pipe(
53+
Effect.map((response) => ({
54+
...response,
55+
token,
56+
}))
57+
)
58+
),
59+
Effect.catchTag("NoSuchElementException", () =>
60+
client.syncAuth
61+
.generateToken({
62+
payload: {
63+
clientId,
64+
snapshot,
65+
snapshotId,
66+
workspaceId: workspace.workspaceId,
67+
},
68+
})
69+
.pipe(
70+
Effect.tap(({ token }) =>
71+
manager.setToken({
72+
workspaceId: workspace.workspaceId,
73+
token,
74+
})
75+
)
76+
)
77+
)
78+
);
7179

72-
const doc = new LoroDoc();
73-
doc.import(response.snapshot);
74-
yield* Effect.all([
75-
manager.put({
76-
workspaceId: response.workspaceId,
77-
snapshot: response.snapshot,
78-
token: response.token,
79-
version: doc.version().encode(),
80-
}),
81-
temp.clean({
82-
workspaceId: workspace.workspaceId,
83-
}),
84-
]);
85-
}),
80+
const doc = new LoroDoc();
81+
doc.import(response.snapshot);
82+
yield* Effect.all([
83+
manager.put({
84+
workspaceId: response.workspaceId,
85+
snapshot: response.snapshot,
86+
token: response.token,
87+
version: doc.version().encode(),
88+
}),
89+
temp.clean({
90+
workspaceId: workspace.workspaceId,
91+
}),
92+
]);
93+
}),
94+
})
95+
)
96+
)
97+
),
8698

8799
pull: ({ workspaceId }: { workspaceId: string }) =>
100+
manager.getById({ workspaceId }).pipe(
101+
Effect.flatMap(
102+
flow(
103+
Option.fromNullable,
104+
Option.flatMap((workspace) =>
105+
Option.fromNullable(workspace.token)
106+
),
107+
Option.match({
108+
onNone: () =>
109+
Effect.log("No token found").pipe(Effect.map(() => null)),
110+
onSome: (token) =>
111+
Effect.gen(function* () {
112+
yield* Effect.log(`Pulling from ${workspaceId}`);
113+
114+
const response = yield* client.syncData.pull({
115+
headers: { "x-api-key": token },
116+
path: { workspaceId },
117+
});
118+
119+
const doc = new LoroDoc();
120+
doc.import(response.snapshot);
121+
yield* manager.put({
122+
token,
123+
workspaceId,
124+
snapshot: response.snapshot,
125+
version: doc.version().encode(),
126+
});
127+
128+
return response;
129+
}),
130+
})
131+
)
132+
)
133+
),
134+
135+
join: ({ workspaceId }: { workspaceId: string }) =>
88136
Effect.gen(function* () {
89137
const clientId = yield* initClient;
90-
yield* Effect.log(`Pulling from ${workspaceId}`);
91-
92-
const response = yield* client.syncData.pull({
93-
path: { workspaceId, clientId },
138+
const response = yield* client.syncData.join({
139+
path: { clientId, workspaceId },
94140
});
95141

96142
const doc = new LoroDoc();
97143
doc.import(response.snapshot);
98144
yield* manager.put({
99-
workspaceId: response.workspaceId,
100-
snapshot: response.snapshot,
145+
workspaceId,
101146
token: response.token,
147+
snapshot: response.snapshot,
102148
version: doc.version().encode(),
103149
});
150+
151+
return response;
104152
}),
105153
};
106154
}),

apps/client/src/lib/services/workspace-manager.ts

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { type LoroSchema, Snapshot } from "@local/sync/loro";
1+
import { SnapshotSchema } from "@local/schema";
2+
import { Snapshot } from "@local/sync";
23
import { Effect, Schema } from "effect";
3-
import { LoroDoc } from "loro-crdt";
44
import { Dexie } from "../dexie";
55
import { WorkspaceTable } from "../schema";
66

@@ -43,29 +43,28 @@ export class WorkspaceManager extends Effect.Service<WorkspaceManager>()(
4343
)
4444
),
4545

46-
create: (workspaceId: string) =>
47-
query((_) =>
48-
_.workspace.toCollection().modify({ current: false })
49-
).pipe(
50-
Effect.andThen(
51-
Schema.encode(Snapshot)(
52-
new LoroDoc<LoroSchema>().export({
53-
mode: "snapshot",
54-
})
55-
)
56-
),
57-
Effect.flatMap((snapshot) =>
58-
query((_) =>
59-
_.workspace.put({
60-
snapshot,
61-
token: null,
62-
version: null,
63-
workspaceId,
64-
})
65-
)
66-
),
67-
Effect.map((workspaceId) => ({ workspaceId }))
46+
create: query((_) =>
47+
_.workspace.toCollection().modify({ current: false })
48+
).pipe(
49+
Effect.andThen(
50+
Schema.encode(Snapshot)(
51+
SnapshotSchema.EmptyDoc().export({
52+
mode: "snapshot",
53+
})
54+
)
55+
),
56+
Effect.flatMap((snapshot) =>
57+
query((_) =>
58+
_.workspace.put({
59+
snapshot,
60+
token: null,
61+
version: null,
62+
workspaceId: crypto.randomUUID(),
63+
})
64+
)
6865
),
66+
Effect.map((workspaceId) => ({ workspaceId }))
67+
),
6968
};
7069
}),
7170
}

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

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,18 @@
11
import { createFileRoute, redirect } from "@tanstack/react-router";
22
import { Effect } from "effect";
3-
import { LoroDoc } from "loro-crdt";
4-
import { ApiClient } from "../../lib/api-client";
5-
import { Dexie } from "../../lib/dexie";
63
import { RuntimeClient } from "../../lib/runtime-client";
7-
import { WorkspaceManager } from "../../lib/services/workspace-manager";
4+
import { Sync } from "../../lib/services/sync";
85

96
export const Route = createFileRoute("/$workspaceId/join")({
107
component: RouteComponent,
118
loader: ({ params }) =>
129
RuntimeClient.runPromise(
1310
Effect.gen(function* () {
14-
const manager = yield* WorkspaceManager;
15-
const api = yield* ApiClient;
16-
const { initClient } = yield* Dexie;
17-
18-
const clientId = yield* initClient;
19-
const workspace = yield* api.client.syncData.pull({
20-
path: { workspaceId: params.workspaceId, clientId },
21-
});
22-
23-
const doc = new LoroDoc();
24-
doc.import(workspace.snapshot);
25-
const workspaceId = yield* manager.put({
26-
token: workspace.token,
27-
snapshot: workspace.snapshot,
28-
workspaceId: workspace.workspaceId,
29-
version: doc.version().encode(),
30-
});
31-
11+
const { join } = yield* Sync;
12+
yield* join({ workspaceId: params.workspaceId });
3213
return redirect({
3314
to: `/$workspaceId`,
34-
params: { workspaceId },
15+
params: { workspaceId: params.workspaceId },
3516
});
3617
})
3718
),

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createFileRoute, useRouter } from "@tanstack/react-router";
1+
import { createFileRoute, Link, useRouter } from "@tanstack/react-router";
22
import { Duration, Effect } from "effect";
33
import { ApiClient } from "../../lib/api-client";
44
import { WEBSITE_URL } from "../../lib/constants";
@@ -68,6 +68,9 @@ function RouteComponent() {
6868

6969
return (
7070
<div>
71+
<Link to={`/$workspaceId`} params={{ workspaceId }}>
72+
Back
73+
</Link>
7174
<h1>Tokens</h1>
7275
<table>
7376
<thead>

0 commit comments

Comments
 (0)