Skip to content

Commit ba7159f

Browse files
migrations (1)
1 parent 215dea8 commit ba7159f

File tree

14 files changed

+283
-16
lines changed

14 files changed

+283
-16
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Metadata } from "@local/schema";
2+
import { Effect } from "effect";
3+
import { RuntimeClient } from "../runtime-client";
4+
import { useDexieQuery } from "../use-dexie-query";
5+
import { hookQuery } from "./hook-query";
6+
7+
export const useMetadata = ({ workspaceId }: { workspaceId: string }) => {
8+
return useDexieQuery(
9+
() =>
10+
RuntimeClient.runPromise(
11+
hookQuery({ workspaceId }).pipe(
12+
Effect.map((snapshot) => [snapshot.metadata])
13+
)
14+
),
15+
Metadata
16+
);
17+
};

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Layer, ManagedRuntime } from "effect";
22
import { ApiClient } from "./api-client";
33
import { Dexie } from "./dexie";
44
import { LoroStorage } from "./services/loro-storage";
5+
import { Migration } from "./services/migration";
56
import { Sync } from "./services/sync";
67
import { TempWorkspace } from "./services/temp-workspace";
78
import { WorkspaceManager } from "./services/workspace-manager";
@@ -12,7 +13,8 @@ const MainLayer = Layer.mergeAll(
1213
WorkspaceManager.Default,
1314
TempWorkspace.Default,
1415
LoroStorage.Default,
15-
Sync.Default
16+
Sync.Default,
17+
Migration.Default
1618
);
1719

1820
export const RuntimeClient = ManagedRuntime.make(MainLayer);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { LoroDocMigration, SnapshotToLoroDoc } from "@local/schema";
2+
import { Data, Effect, Schema, type ParseResult } from "effect";
3+
import { TempWorkspace } from "./temp-workspace";
4+
5+
class MigrationError extends Data.TaggedError("MigrationError")<{
6+
parseError: ParseResult.ParseError;
7+
}> {}
8+
9+
export class Migration extends Effect.Service<Migration>()("Migration", {
10+
dependencies: [TempWorkspace.Default],
11+
effect: Effect.gen(function* () {
12+
const temp = yield* TempWorkspace;
13+
return {
14+
migrate: temp.getAll.pipe(
15+
Effect.flatMap((workspaces) =>
16+
Effect.all(
17+
workspaces.map((workspace) =>
18+
Effect.gen(function* () {
19+
const doc = yield* Schema.decode(SnapshotToLoroDoc)(
20+
workspace.snapshot
21+
);
22+
23+
const newDoc = yield* Schema.decode(LoroDocMigration)(doc).pipe(
24+
Effect.catchTag(
25+
"ParseError",
26+
(parseError) => new MigrationError({ parseError })
27+
)
28+
);
29+
30+
const newSnapshot =
31+
yield* Schema.encode(SnapshotToLoroDoc)(newDoc);
32+
33+
yield* temp.put({
34+
workspaceId: workspace.workspaceId,
35+
snapshot: newSnapshot,
36+
snapshotId: workspace.snapshotId,
37+
});
38+
})
39+
)
40+
)
41+
)
42+
),
43+
};
44+
}),
45+
}) {}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { LoroSchema } from "@local/schema";
12
import { Effect, flow, Option } from "effect";
23
import { LoroDoc } from "loro-crdt";
34
import { ApiClient } from "../api-client";
@@ -77,7 +78,7 @@ export class Sync extends Effect.Service<Sync>()("Sync", {
7778
)
7879
);
7980

80-
const doc = new LoroDoc();
81+
const doc = new LoroDoc<LoroSchema>();
8182
doc.import(response.snapshot);
8283
yield* Effect.all([
8384
manager.put({

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export class TempWorkspace extends Effect.Service<TempWorkspace>()(
99
effect: Effect.gen(function* () {
1010
const { query } = yield* Dexie;
1111
return {
12+
getAll: query((_) => _.temp_workspace.toArray()).pipe(
13+
Effect.flatMap(Schema.decode(Schema.Array(TempWorkspaceTable)))
14+
),
15+
1216
put: (params: typeof TempWorkspaceTable.Type) =>
1317
Schema.encode(TempWorkspaceTable)(params).pipe(
1418
Effect.flatMap((data) => query((_) => _.temp_workspace.put(data)))

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
44
import { Effect } from "effect";
55
import { startTransition, useEffect } from "react";
66
import { useActivity } from "../../lib/hooks/use-activity";
7+
import { useMetadata } from "../../lib/hooks/use-metadata";
78
import { RuntimeClient } from "../../lib/runtime-client";
89
import { LoroStorage } from "../../lib/services/loro-storage";
910
import { WorkspaceManager } from "../../lib/services/workspace-manager";
@@ -41,6 +42,9 @@ export const Route = createFileRoute("/$workspaceId/")({
4142
function RouteComponent() {
4243
const workspace = Route.useLoaderData();
4344

45+
const { data: metadata } = useMetadata({
46+
workspaceId: workspace.workspaceId,
47+
});
4448
const { data, error, loading } = useActivity({
4549
workspaceId: workspace.workspaceId,
4650
});
@@ -50,13 +54,15 @@ function RouteComponent() {
5054
Effect.gen(function* () {
5155
const loroStorage = yield* LoroStorage;
5256

53-
const name = formData.get("name") as string;
57+
const firstName = formData.get("firstName") as string;
58+
const lastName = formData.get("lastName") as string;
5459

5560
yield* loroStorage.insertActivity({
5661
workspaceId: workspace.workspaceId,
5762
value: {
5863
id: crypto.randomUUID(),
59-
name,
64+
firstName,
65+
lastName,
6066
},
6167
});
6268
})
@@ -89,6 +95,7 @@ function RouteComponent() {
8995

9096
return (
9197
<div>
98+
<pre>{JSON.stringify(metadata)}</pre>
9299
<Link
93100
to="/$workspaceId/token"
94101
params={{ workspaceId: workspace.workspaceId }}
@@ -109,7 +116,8 @@ function RouteComponent() {
109116
</button>
110117

111118
<form action={onAdd}>
112-
<input type="text" name="name" />
119+
<input type="text" name="firstName" />
120+
<input type="text" name="lastName" />
113121
<button type="submit">Add activity</button>
114122
</form>
115123

@@ -118,7 +126,8 @@ function RouteComponent() {
118126
{error && <pre>{JSON.stringify(error, null, 2)}</pre>}
119127
{(data ?? []).map((activity) => (
120128
<div key={activity.id}>
121-
<label>Name {activity.name}</label>
129+
<p>First name: {activity.firstName}</p>
130+
<p>Last name: {activity.lastName}</p>
122131
</div>
123132
))}
124133
</div>

apps/client/src/routes/__root.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ export const Route = createRootRoute({
99
RuntimeClient.runPromise(
1010
Effect.gen(function* () {
1111
const { initClient } = yield* Dexie;
12-
return yield* initClient;
12+
// const { migrate } = yield* Migration;
13+
14+
const clientId = yield* initClient;
15+
// yield* migrate;
16+
return clientId;
1317
})
1418
),
1519
});

apps/client/src/workers/sync.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { WorkerRunner } from "@effect/platform";
22
import { BrowserWorkerRunner } from "@effect/platform-browser";
3+
import type { LoroSchema } from "@local/schema";
34
import { Effect, Layer } from "effect";
5+
import { LoroDoc } from "loro-crdt";
46
import { RuntimeClient } from "../lib/runtime-client";
57
import { Sync } from "../lib/services/sync";
68
import { TempWorkspace } from "../lib/services/temp-workspace";
@@ -26,6 +28,10 @@ const WorkerLive = WorkerRunner.layerSerialized(WorkerMessage, {
2628
});
2729

2830
if (tempUpdates !== undefined) {
31+
const docI = new LoroDoc<LoroSchema>();
32+
docI.import(tempUpdates.snapshot);
33+
yield* Effect.log("Doc", tempUpdates, docI.toJSON());
34+
2935
yield* push({
3036
workspaceId: workspace.workspaceId,
3137
snapshot: tempUpdates.snapshot,

apps/server/src/group/sync-auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DateTime, Effect, Layer, Schema } from "effect";
55
import { tokenTable, workspaceTable } from "../db/schema";
66
import { AuthorizationLive } from "../middleware/authorization";
77
import { MasterAuthorizationLive } from "../middleware/master-authorization";
8+
import { VersionCheckLive } from "../middleware/version-check";
89
import { Drizzle } from "../services/drizzle";
910
import { Jwt } from "../services/jwt";
1011

@@ -234,6 +235,7 @@ export const SyncAuthGroupLive = HttpApiBuilder.group(
234235
Drizzle.Default,
235236
AuthorizationLive,
236237
MasterAuthorizationLive,
238+
VersionCheckLive,
237239
Jwt.Default,
238240
])
239241
);

apps/server/src/group/sync-data.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { HttpApiBuilder } from "@effect/platform";
22
import { SnapshotToLoroDoc } from "@local/schema";
33
import { AuthWorkspace, SyncApi } from "@local/sync";
44
import { and, desc, eq, gt, isNull, or } from "drizzle-orm";
5-
import { Array, Effect, Layer, Schema } from "effect";
5+
import { Array, Data, Effect, Layer, Schema } from "effect";
66
import { tokenTable, workspaceTable } from "../db/schema";
77
import { AuthorizationLive } from "../middleware/authorization";
8+
import { VersionCheckLive } from "../middleware/version-check";
89
import { Drizzle } from "../services/drizzle";
910

11+
class InvalidVersionError extends Data.TaggedError("InvalidVersionError")<{
12+
reason: "missing" | "outdated";
13+
}> {}
14+
1015
export const SyncDataGroupLive = HttpApiBuilder.group(
1116
SyncApi,
1217
"syncData",
@@ -132,4 +137,4 @@ export const SyncDataGroupLive = HttpApiBuilder.group(
132137
)
133138
);
134139
})
135-
).pipe(Layer.provide([Drizzle.Default, AuthorizationLive]));
140+
).pipe(Layer.provide([Drizzle.Default, AuthorizationLive, VersionCheckLive]));

0 commit comments

Comments
 (0)