Skip to content

Commit a4f8c13

Browse files
migration versioning bootstrap
1 parent d538332 commit a4f8c13

File tree

5 files changed

+56
-27
lines changed

5 files changed

+56
-27
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export class Migration extends Effect.Service<Migration>()("Migration", {
1212
const temp = yield* TempWorkspace;
1313
return {
1414
migrate: temp.getAll.pipe(
15+
Effect.tap((workspaces) =>
16+
Effect.log(`Migrating ${workspaces.length} workspaces`)
17+
),
1518
Effect.flatMap((workspaces) =>
1619
Effect.all(
1720
workspaces.map((workspace) =>
@@ -38,6 +41,9 @@ export class Migration extends Effect.Service<Migration>()("Migration", {
3841
})
3942
)
4043
)
44+
),
45+
Effect.tap(() =>
46+
Effect.log("Migration completed successfully for all workspaces")
4147
)
4248
),
4349
};

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function RouteComponent() {
6464
id: crypto.randomUUID(),
6565
firstName,
6666
lastName,
67+
age: 10,
6768
},
6869
});
6970
})
@@ -129,6 +130,7 @@ function RouteComponent() {
129130
<div key={activity.id}>
130131
<p>First name: {activity.firstName}</p>
131132
<p>Last name: {activity.lastName}</p>
133+
{activity.age && <p>Age: {activity.age}</p>}
132134
</div>
133135
))}
134136
</div>

apps/client/src/routes/__root.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@ import { Outlet, createRootRoute } from "@tanstack/react-router";
22
import { Effect } from "effect";
33
import { Dexie } from "../lib/dexie";
44
import { RuntimeClient } from "../lib/runtime-client";
5+
import { Migration } from "../lib/services/migration";
56

67
export const Route = createRootRoute({
78
component: RootComponent,
89
loader: () =>
910
RuntimeClient.runPromise(
10-
Effect.gen(function* () {
11-
const { initClient } = yield* Dexie;
12-
// const { migrate } = yield* Migration;
13-
14-
const clientId = yield* initClient;
15-
// yield* migrate;
16-
return clientId;
17-
})
11+
Migration.pipe(
12+
Effect.flatMap((migration) => migration.migrate),
13+
Effect.catchAll((error) => Effect.logError("Migration error", error)),
14+
Effect.andThen(
15+
Effect.gen(function* () {
16+
const { initClient } = yield* Dexie;
17+
return yield* initClient;
18+
})
19+
)
20+
)
1821
),
1922
});
2023

packages/schema/src/main.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { ParseResult, Schema } from "effect";
22
import { LoroDoc, LoroList, LoroMap } from "loro-crdt";
3-
import { ActivitySchema, ActivityV1 } from "./schema";
3+
import { ActivitySchema, ActivityV1, type ActivityV2 } from "./schema";
44

5-
export const VERSION = 2;
5+
const Version = [1, 2, 3] as const;
6+
export type Version = (typeof Version)[number];
7+
8+
export const VERSION = 3 satisfies Version;
69

710
const AnyLoroDocSchema = Schema.instanceOf(LoroDoc);
811

@@ -32,7 +35,7 @@ export class SnapshotSchema extends Schema.Class<SnapshotSchema>(
3235
};
3336
}
3437

35-
const migrations: Record<number, (doc: LoroDoc) => LoroDoc> = {
38+
const migrations = {
3639
1: (doc) => {
3740
const metadata = doc.getMap("metadata");
3841
metadata.set("version", VERSION);
@@ -58,7 +61,24 @@ const migrations: Record<number, (doc: LoroDoc) => LoroDoc> = {
5861

5962
return doc;
6063
},
61-
};
64+
"3": (doc) => {
65+
const metadata = doc.getMap("metadata");
66+
metadata.set("version", 3);
67+
68+
const activity = doc.getList("activity");
69+
for (let i = 0; i < activity.length; i++) {
70+
const item = activity.get(i) as LoroMap<typeof ActivityV2.Encoded>;
71+
const map = new LoroMap();
72+
map.set("id", item.get("id"));
73+
map.set("firstName", item.get("firstName"));
74+
map.set("lastName", item.get("lastName"));
75+
map.set("age", undefined);
76+
activity.insertContainer(i, map);
77+
}
78+
79+
return doc;
80+
},
81+
} satisfies Record<Version, (doc: LoroDoc) => LoroDoc>;
6282

6383
export const LoroDocMigration = AnyLoroDocSchema.pipe(
6484
Schema.transformOrFail(AnyLoroDocSchema, {
@@ -69,20 +89,9 @@ export const LoroDocMigration = AnyLoroDocSchema.pipe(
6989
const currentVersion = metadata.get("version");
7090

7191
if (typeof currentVersion === "number") {
72-
for (let version = currentVersion + 1; version <= VERSION; version++) {
73-
const migration = migrations[version];
74-
if (migration === undefined) {
75-
return ParseResult.fail(
76-
new ParseResult.Type(
77-
ast,
78-
from,
79-
`Migration from version ${version - 1} to ${version} not found`
80-
)
81-
);
82-
}
83-
84-
doc.import(migration(doc).export({ mode: "snapshot" }));
85-
}
92+
Version.forEach((version) => {
93+
doc.import(migrations[version](doc).export({ mode: "snapshot" }));
94+
});
8695
} else {
8796
return ParseResult.fail(
8897
new ParseResult.Type(ast, from, "Invalid version number in metadata")

packages/schema/src/schema.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Schema } from "effect";
2+
import type { Version } from "./main";
23

34
export class ActivityV1 extends Schema.Class<ActivityV1>("ActivityV1")({
45
id: Schema.UUID,
@@ -11,7 +12,15 @@ export class ActivityV2 extends Schema.Class<ActivityV2>("ActivityV2")({
1112
lastName: Schema.String,
1213
}) {}
1314

15+
export class ActivityV3 extends Schema.Class<ActivityV3>("ActivityV3")({
16+
id: Schema.UUID,
17+
firstName: Schema.String,
18+
lastName: Schema.String,
19+
age: Schema.UndefinedOr(Schema.Number),
20+
}) {}
21+
1422
export const ActivitySchema = {
1523
1: ActivityV1,
1624
2: ActivityV2,
17-
} as const;
25+
3: ActivityV3,
26+
} as const satisfies Record<Version, Schema.Schema.AnyNoContext>;

0 commit comments

Comments
 (0)