Skip to content

Commit ca66910

Browse files
sync client dexie setup
1 parent 00bdce0 commit ca66910

14 files changed

Lines changed: 57 additions & 279 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
# Async Sync Engine
22
[Source](https://x.com/i/grok?conversation=1893597036278100311)
33

4+
1. Init: Store clientId locally.
5+
2. Create: Owner creates workshop, gets masterToken, stores it.
6+
3. Share: Owner issues accessToken for collaborator, shares manually.
7+
4. Access: Collaborator inputs workshopId and accessToken, downloads data.
8+
5. Sync: Clients edit locally, sync with server using their token.
9+
6. Revoke: Owner revokes access, collaborator’s token fails.
10+
7. Expiry: Client handles token expiration gracefully.

apps/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
},
2222
"dependencies": {
2323
"@effect/platform": "^0.77.2",
24-
"@local/api": "workspace:*",
24+
"@local/sync": "workspace:*",
2525
"@tanstack/react-router": "^1.105.0",
2626
"dexie": "^4.0.11",
2727
"dexie-react-hooks": "^1.1.7",

apps/client/src/lib/api-client.ts

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,14 @@
11
import { FetchHttpClient, HttpApiClient } from "@effect/platform";
2-
import { ServerApi } from "@local/api";
2+
import { SyncApi } from "@local/sync";
33
import { Effect } from "effect";
4-
import { LoroDoc, LoroMap, type LoroList } from "loro-crdt";
5-
import type { UserTable } from "./schema";
64

75
export class ApiClient extends Effect.Service<ApiClient>()("ApiClient", {
86
dependencies: [FetchHttpClient.layer],
97
effect: Effect.gen(function* () {
10-
const client = yield* HttpApiClient.make(ServerApi, {
8+
const client = yield* HttpApiClient.make(SyncApi, {
119
baseUrl: "http://localhost:3000",
1210
});
1311

14-
const push = (users: readonly UserTable[]) =>
15-
Effect.gen(function* () {
16-
const doc = new LoroDoc<{
17-
user: LoroList<LoroMap<typeof UserTable.Encoded>>;
18-
}>();
19-
const list = doc.getList("user");
20-
21-
users.forEach((user, index) => {
22-
const map = new LoroMap<typeof UserTable.Encoded>();
23-
map.set("userId", user.userId);
24-
map.set("name", user.name);
25-
list.insertContainer(index, map);
26-
});
27-
28-
return yield* client.user.createUser({
29-
payload: {
30-
name: crypto.randomUUID(),
31-
snapshot: doc.export({ mode: "snapshot" }),
32-
},
33-
});
34-
});
35-
36-
const pull = Effect.gen(function* () {
37-
const user = yield* client.user.getUser({ path: { id: 3 } });
38-
const doc = new LoroDoc<{
39-
user: LoroList<LoroMap<typeof UserTable.Encoded>>;
40-
}>();
41-
doc.import(user.snapshot!);
42-
console.log(doc, doc.toJSON());
43-
44-
return doc.getList("user").toJSON() as UserTable[];
45-
});
46-
47-
return { push, pull };
12+
return { client };
4813
}),
4914
}) {}

apps/client/src/lib/dexie.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import * as _Dexie from "dexie";
22
import { Data, Effect, Schema } from "effect";
3-
import { type UserTable } from "./schema";
3+
import {
4+
type ClientTable,
5+
type TempWorkspaceTable,
6+
type WorkspaceTable,
7+
} from "./schema";
48

59
class WriteApiError extends Data.TaggedError("WriteApiError")<{
610
cause: unknown;
@@ -20,11 +24,21 @@ export class Dexie extends Effect.Service<Dexie>()("Dexie", {
2024
accessors: true,
2125
effect: Effect.gen(function* () {
2226
const db = new _Dexie.Dexie("_db") as _Dexie.Dexie & {
23-
user: _Dexie.EntityTable<typeof UserTable.Encoded, "userId">;
27+
client: _Dexie.EntityTable<typeof ClientTable.Encoded, "clientId">;
28+
workspace: _Dexie.EntityTable<
29+
typeof WorkspaceTable.Encoded,
30+
"workspaceId"
31+
>;
32+
temp_workspace: _Dexie.EntityTable<
33+
typeof TempWorkspaceTable.Encoded,
34+
"workspaceId"
35+
>;
2436
};
2537

2638
db.version(1).stores({
27-
user: "++userId, &name",
39+
workspace: "workspaceId",
40+
temp_workspace: "workspaceId",
41+
client: "clientId",
2842
});
2943

3044
const formAction =
@@ -60,22 +74,6 @@ export class Dexie extends Effect.Service<Dexie>()("Dexie", {
6074
)
6175
);
6276

63-
return {
64-
db,
65-
66-
insertUser: formAction(
67-
Schema.Struct({ name: Schema.NonEmptyString }),
68-
({ name }) => db.user.add({ userId: crypto.randomUUID(), name })
69-
),
70-
71-
updateUser: changeAction(
72-
Schema.Struct({
73-
userId: Schema.UUID,
74-
name: Schema.NonEmptyString,
75-
}),
76-
77-
({ userId, name }) => db.user.update(userId, { name })
78-
),
79-
};
77+
return { db };
8078
}),
8179
}) {}

apps/client/src/lib/schema.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
1+
import { ClientId, Snapshot, WorkspaceId } from "@local/sync";
12
import { Schema } from "effect";
23

3-
export class UserTable extends Schema.Class<UserTable>("UserTable")({
4-
userId: Schema.UUID,
5-
name: Schema.String,
4+
export class ClientTable extends Schema.Class<ClientTable>("ClientTable")({
5+
clientId: ClientId,
6+
}) {}
7+
8+
export class WorkspaceTable extends Schema.Class<WorkspaceTable>(
9+
"WorkspaceTable"
10+
)({
11+
workspaceId: WorkspaceId,
12+
snapshot: Snapshot,
13+
token: Schema.String,
14+
15+
version: Schema.Uint8Array,
16+
}) {}
17+
18+
export class TempWorkspaceTable extends Schema.Class<TempWorkspaceTable>(
19+
"TempWorkspaceTable"
20+
)({
21+
workspaceId: WorkspaceId,
22+
snapshot: Snapshot,
623
}) {}

apps/client/src/lib/use-user.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

apps/client/src/routes/index.tsx

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,7 @@
11
import { createFileRoute } from "@tanstack/react-router";
2-
import { Effect } from "effect";
3-
import { useActionState } from "react";
4-
import { ApiClient } from "../lib/api-client";
5-
import { Dexie } from "../lib/dexie";
6-
import { RuntimeClient } from "../lib/runtime-client";
7-
import { useActionEffect } from "../lib/use-action-effect";
8-
import { useUser } from "../lib/use-user";
92

103
export const Route = createFileRoute("/")({ component: HomeComponent });
114

125
function HomeComponent() {
13-
const { data } = useUser();
14-
const [, add] = useActionEffect(Dexie.insertUser);
15-
const [, push] = useActionState(
16-
() =>
17-
RuntimeClient.runPromise(
18-
Effect.gen(function* () {
19-
const api = yield* ApiClient;
20-
return yield* api.push(data ?? []);
21-
})
22-
),
23-
null
24-
);
25-
const [user, pull] = useActionState(
26-
() =>
27-
RuntimeClient.runPromise(
28-
Effect.gen(function* () {
29-
const api = yield* ApiClient;
30-
return yield* api.pull;
31-
})
32-
),
33-
null
34-
);
35-
return (
36-
<div>
37-
<button type="button" onClick={push}>
38-
Push
39-
</button>
40-
41-
<button type="button" onClick={pull}>
42-
Pull
43-
</button>
44-
45-
{user && (
46-
<div>
47-
{user.map((u) => (
48-
<p key={u.userId}>{u.name}</p>
49-
))}
50-
</div>
51-
)}
52-
53-
<form action={add}>
54-
<input type="text" name="name" />
55-
<button>Add</button>
56-
</form>
57-
58-
<div>{data?.map((user) => <p key={user.userId}>{user.name}</p>)}</div>
59-
</div>
60-
);
6+
return <></>;
617
}

apps/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@effect/platform-node": "^0.73.1",
2222
"@effect/sql": "^0.30.2",
2323
"@effect/sql-pg": "^0.31.2",
24-
"@local/api": "workspace:*",
24+
"@local/sync": "workspace:*",
2525
"drizzle-orm": "^0.39.3",
2626
"effect": "^3.13.2",
2727
"pg": "^8.13.3"

apps/server/src/user.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

packages/api/package.json

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)