Skip to content

Commit 7e3af04

Browse files
refactor schema package
1 parent edaf82d commit 7e3af04

6 files changed

Lines changed: 92 additions & 85 deletions

File tree

packages/client-lib/src/services/migration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { LoroDocMigration, SnapshotToLoroDoc } from "@local/schema";
1+
import { SnapshotToLoroDoc } from "@local/schema";
2+
import { LoroDocMigration } from "@local/schema/migrations";
23
import { Data, Effect, Schema, type ParseResult } from "effect";
34
import { TempWorkspace } from "./temp-workspace";
45

packages/schema/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"typecheck": "tsc"
66
},
77
"exports": {
8-
".": "./src/main.ts"
8+
".": "./src/main.ts",
9+
"./migrations": "./src/migrations.ts"
910
},
1011
"devDependencies": {},
1112
"peerDependencies": {

packages/schema/src/main.ts

Lines changed: 4 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
1-
import { ParseResult, Schema } from "effect";
1+
import { Schema } from "effect";
22
import { LoroDoc, LoroList, LoroMap } from "loro-crdt";
3-
import { ActivitySchema, ActivityV1, type ActivityV2 } from "./schema";
3+
import { ActivitySchema, AnyLoroDocSchema } from "./schema";
4+
import type { Version } from "./versioning";
45

5-
/** 👇 Versioning management */
6-
7-
const Version = [1, 2, 3] as const;
8-
export type Version = (typeof Version)[number];
96
export const VERSION = 3 satisfies Version;
107

11-
/** 👆 Versioning management */
12-
13-
const AnyLoroDocSchema = Schema.instanceOf(LoroDoc);
14-
158
export const Metadata = Schema.Struct({ version: Schema.Number });
169
export const Activity = ActivitySchema[VERSION];
1710

1811
export type LoroSchema = {
19-
metadata: LoroMap<{ version: number }>;
12+
metadata: LoroMap<typeof Metadata.Encoded>;
2013
activity: LoroList<LoroMap<typeof Activity.Encoded>>;
2114
};
2215

@@ -34,77 +27,6 @@ export class SnapshotSchema extends Schema.Class<SnapshotSchema>(
3427
};
3528
}
3629

37-
const migrations = {
38-
1: (doc) => {
39-
doc.getMap("metadata").set("version", VERSION);
40-
doc.getList("activity");
41-
return doc;
42-
},
43-
2: (doc) => {
44-
doc.getMap("metadata").set("version", 2);
45-
46-
const activity = doc.getList("activity");
47-
for (let i = 0; i < activity.length; i++) {
48-
const item = activity.get(i) as LoroMap<typeof ActivityV1.Encoded>;
49-
const map = new LoroMap();
50-
const [firstName, lastName] = item.get("name").split(" ");
51-
map.set("id", item.get("id"));
52-
map.set("firstName", firstName);
53-
map.set("lastName", lastName);
54-
activity.insertContainer(i, map);
55-
}
56-
57-
return doc;
58-
},
59-
3: (doc) => {
60-
doc.getMap("metadata").set("version", 3);
61-
62-
const activity = doc.getList("activity");
63-
for (let i = 0; i < activity.length; i++) {
64-
const item = activity.get(i) as LoroMap<typeof ActivityV2.Encoded>;
65-
const map = new LoroMap();
66-
map.set("id", item.get("id"));
67-
map.set("firstName", item.get("firstName"));
68-
map.set("lastName", item.get("lastName"));
69-
map.set("age", undefined);
70-
activity.insertContainer(i, map);
71-
}
72-
73-
return doc;
74-
},
75-
} satisfies Record<Version, (doc: LoroDoc) => LoroDoc>;
76-
77-
export const LoroDocMigration = AnyLoroDocSchema.pipe(
78-
Schema.transformOrFail(AnyLoroDocSchema, {
79-
decode: (from, _, ast) => {
80-
const doc = new LoroDoc();
81-
doc.import(from.export({ mode: "snapshot" }));
82-
const currentVersion = doc.getMap("metadata").get("version");
83-
84-
if (typeof currentVersion === "number") {
85-
Version.forEach((version) => {
86-
doc.import(migrations[version](doc).export({ mode: "snapshot" }));
87-
});
88-
} else {
89-
return ParseResult.fail(
90-
new ParseResult.Type(ast, from, "Invalid version number in metadata")
91-
);
92-
}
93-
94-
return ParseResult.succeed(doc);
95-
},
96-
97-
encode: (to, _, ast) =>
98-
ParseResult.fail(
99-
new ParseResult.Forbidden(
100-
ast,
101-
to,
102-
"Encoding LoroDoc migration is not allowed (should not happen...)"
103-
)
104-
),
105-
})
106-
);
107-
10830
export const SnapshotToLoroDoc = Schema.Uint8ArrayFromSelf.pipe(
10931
Schema.transform(AnyLoroDocSchema, {
11032
decode: (from) => {

packages/schema/src/migrations.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { ParseResult, Schema } from "effect";
2+
import { LoroDoc, LoroMap } from "loro-crdt";
3+
import { VERSION } from "./main";
4+
import { AnyLoroDocSchema, type ActivityV1, type ActivityV2 } from "./schema";
5+
import { Version } from "./versioning";
6+
7+
const migrations = {
8+
1: (doc) => {
9+
doc.getMap("metadata").set("version", VERSION);
10+
doc.getList("activity");
11+
return doc;
12+
},
13+
14+
2: (doc) => {
15+
doc.getMap("metadata").set("version", 2);
16+
17+
const activity = doc.getList("activity");
18+
for (let i = 0; i < activity.length; i++) {
19+
const item = activity.get(i) as LoroMap<typeof ActivityV1.Encoded>;
20+
const map = new LoroMap();
21+
const [firstName, lastName] = item.get("name").split(" ");
22+
map.set("id", item.get("id"));
23+
map.set("firstName", firstName);
24+
map.set("lastName", lastName);
25+
activity.insertContainer(i, map);
26+
}
27+
28+
return doc;
29+
},
30+
31+
3: (doc) => {
32+
doc.getMap("metadata").set("version", 3);
33+
34+
const activity = doc.getList("activity");
35+
for (let i = 0; i < activity.length; i++) {
36+
const item = activity.get(i) as LoroMap<typeof ActivityV2.Encoded>;
37+
const map = new LoroMap();
38+
map.set("id", item.get("id"));
39+
map.set("firstName", item.get("firstName"));
40+
map.set("lastName", item.get("lastName"));
41+
map.set("age", undefined);
42+
activity.insertContainer(i, map);
43+
}
44+
45+
return doc;
46+
},
47+
} satisfies Record<Version, (doc: LoroDoc) => LoroDoc>;
48+
49+
export const LoroDocMigration = AnyLoroDocSchema.pipe(
50+
Schema.transformOrFail(AnyLoroDocSchema, {
51+
decode: (from, _, ast) => {
52+
const doc = new LoroDoc();
53+
doc.import(from.export({ mode: "snapshot" }));
54+
const currentVersion = doc.getMap("metadata").get("version");
55+
56+
if (typeof currentVersion === "number") {
57+
Version.forEach((version) => {
58+
doc.import(migrations[version](doc).export({ mode: "snapshot" }));
59+
});
60+
} else {
61+
return ParseResult.fail(
62+
new ParseResult.Type(ast, from, "Invalid version number in metadata")
63+
);
64+
}
65+
66+
return ParseResult.succeed(doc);
67+
},
68+
69+
encode: (to, _, ast) =>
70+
ParseResult.fail(
71+
new ParseResult.Forbidden(
72+
ast,
73+
to,
74+
"Encoding LoroDoc migration is not allowed (should not happen...)"
75+
)
76+
),
77+
})
78+
);

packages/schema/src/schema.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Schema } from "effect";
2-
import type { Version } from "./main";
2+
import { LoroDoc } from "loro-crdt";
3+
import { type Version } from "./versioning";
4+
5+
export const AnyLoroDocSchema = Schema.instanceOf(LoroDoc);
36

47
export class ActivityV1 extends Schema.Class<ActivityV1>("ActivityV1")({
58
id: Schema.UUID,

packages/schema/src/versioning.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const Version = [1, 2, 3] as const;
2+
export type Version = (typeof Version)[number];

0 commit comments

Comments
 (0)