Skip to content

Commit cd174d8

Browse files
committed
sync
1 parent 246e901 commit cd174d8

6 files changed

Lines changed: 143 additions & 20 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
2-
import { Database } from "@/storage/db"
2+
import { Timestamps } from "@/storage/schema.sql"
33

44
export const ProjectTable = sqliteTable("project", {
55
id: text().primaryKey(),
@@ -8,7 +8,7 @@ export const ProjectTable = sqliteTable("project", {
88
name: text(),
99
icon_url: text(),
1010
icon_color: text(),
11-
...Database.Timestamps,
11+
...Timestamps,
1212
time_initialized: integer(),
1313
sandboxes: text({ mode: "json" }).notNull().$type<string[]>(),
1414
})

packages/opencode/src/session/session.sql.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ProjectTable } from "../project/project.sql"
33
import type { MessageV2 } from "./message-v2"
44
import type { Snapshot } from "@/snapshot"
55
import type { PermissionNext } from "@/permission/next"
6-
import { Database } from "@/storage/db"
6+
import { Timestamps } from "@/storage/schema.sql"
77

88
type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID">
99
type InfoData = Omit<MessageV2.Info, "id" | "sessionID">
@@ -27,7 +27,7 @@ export const SessionTable = sqliteTable(
2727
summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(),
2828
revert: text({ mode: "json" }).$type<{ messageID: string; partID?: string; snapshot?: string; diff?: string }>(),
2929
permission: text({ mode: "json" }).$type<PermissionNext.Ruleset>(),
30-
...Database.Timestamps,
30+
...Timestamps,
3131
time_compacting: integer(),
3232
time_archived: integer(),
3333
},
@@ -41,7 +41,7 @@ export const MessageTable = sqliteTable(
4141
session_id: text()
4242
.notNull()
4343
.references(() => SessionTable.id, { onDelete: "cascade" }),
44-
...Database.Timestamps,
44+
...Timestamps,
4545
data: text({ mode: "json" }).notNull().$type<InfoData>(),
4646
},
4747
(table) => [index("message_session_idx").on(table.session_id)],
@@ -55,7 +55,7 @@ export const PartTable = sqliteTable(
5555
.notNull()
5656
.references(() => MessageTable.id, { onDelete: "cascade" }),
5757
session_id: text().notNull(),
58-
...Database.Timestamps,
58+
...Timestamps,
5959
data: text({ mode: "json" }).notNull().$type<PartData>(),
6060
},
6161
(table) => [index("part_message_idx").on(table.message_id), index("part_session_idx").on(table.session_id)],
@@ -72,7 +72,7 @@ export const TodoTable = sqliteTable(
7272
status: text().notNull(),
7373
priority: text().notNull(),
7474
position: integer().notNull(),
75-
...Database.Timestamps,
75+
...Timestamps,
7676
},
7777
(table) => [primaryKey({ columns: [table.session_id, table.id] }), index("todo_session_idx").on(table.session_id)],
7878
)
@@ -81,6 +81,6 @@ export const PermissionTable = sqliteTable("permission", {
8181
project_id: text()
8282
.primaryKey()
8383
.references(() => ProjectTable.id, { onDelete: "cascade" }),
84-
...Database.Timestamps,
84+
...Timestamps,
8585
data: text({ mode: "json" }).notNull().$type<PermissionNext.Ruleset>(),
8686
})
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
22
import { SessionTable } from "../session/session.sql"
3-
import { Database } from "@/storage/db"
3+
import { Timestamps } from "@/storage/schema.sql"
44

55
export const SessionShareTable = sqliteTable("session_share", {
66
session_id: text()
@@ -9,5 +9,5 @@ export const SessionShareTable = sqliteTable("session_share", {
99
id: text().notNull(),
1010
secret: text().notNull(),
1111
url: text().notNull(),
12-
...Database.Timestamps,
12+
...Timestamps,
1313
})

packages/opencode/src/storage/db.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Database as BunDatabase } from "bun:sqlite"
22
import { drizzle, type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
33
import { migrate } from "drizzle-orm/bun-sqlite/migrator"
4-
import { integer, type SQLiteTransaction } from "drizzle-orm/sqlite-core"
4+
import { type SQLiteTransaction } from "drizzle-orm/sqlite-core"
55
export * from "drizzle-orm"
66
import { Context } from "../util/context"
77
import { lazy } from "../util/lazy"
@@ -137,13 +137,4 @@ export namespace Database {
137137
throw err
138138
}
139139
}
140-
141-
export const Timestamps = {
142-
time_created: integer()
143-
.notNull()
144-
.$default(() => Date.now()),
145-
time_updated: integer()
146-
.notNull()
147-
.$onUpdate(() => Date.now()),
148-
}
149140
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { integer } from "drizzle-orm/sqlite-core"
2+
3+
export const Timestamps = {
4+
time_created: integer()
5+
.notNull()
6+
.$default(() => Date.now()),
7+
time_updated: integer()
8+
.notNull()
9+
.$onUpdate(() => Date.now()),
10+
}

packages/opencode/test/storage/json-migration.test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Global } from "../../src/global"
1111
import { ProjectTable } from "../../src/project/project.sql"
1212
import { Project } from "../../src/project/project"
1313
import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql"
14+
import { SessionShareTable } from "../../src/share/share.sql"
1415

1516
// Test fixtures
1617
const fixtures = {
@@ -240,4 +241,125 @@ describe("JSON to SQLite migration", () => {
240241
const projects = db.select().from(ProjectTable).all()
241242
expect(projects.length).toBe(1) // Still only 1 due to onConflictDoNothing
242243
})
244+
245+
test("migrates todos", async () => {
246+
// First create the project and session
247+
await Bun.write(
248+
path.join(storageDir, "project", "proj_test123abc.json"),
249+
JSON.stringify({
250+
id: "proj_test123abc",
251+
worktree: "/",
252+
time: { created: Date.now(), updated: Date.now() },
253+
sandboxes: [],
254+
}),
255+
)
256+
await Bun.write(
257+
path.join(storageDir, "session", "proj_test123abc", "ses_test456def.json"),
258+
JSON.stringify({ ...fixtures.session }),
259+
)
260+
261+
// Create todo file (named by sessionID, contains array of todos)
262+
await Bun.write(
263+
path.join(storageDir, "todo", "ses_test456def.json"),
264+
JSON.stringify([
265+
{
266+
id: "todo_1",
267+
content: "First todo",
268+
status: "pending",
269+
priority: "high",
270+
},
271+
{
272+
id: "todo_2",
273+
content: "Second todo",
274+
status: "completed",
275+
priority: "medium",
276+
},
277+
]),
278+
)
279+
280+
const stats = await JsonMigration.run(sqlite)
281+
282+
expect(stats?.todos).toBe(2)
283+
284+
const db = drizzle({ client: sqlite })
285+
const todos = db.select().from(TodoTable).all()
286+
expect(todos.length).toBe(2)
287+
expect(todos[0].id).toBe("todo_1")
288+
expect(todos[0].content).toBe("First todo")
289+
expect(todos[0].status).toBe("pending")
290+
expect(todos[0].priority).toBe("high")
291+
expect(todos[0].position).toBe(0)
292+
expect(todos[1].id).toBe("todo_2")
293+
expect(todos[1].position).toBe(1)
294+
})
295+
296+
test("migrates permissions", async () => {
297+
// First create the project
298+
await Bun.write(
299+
path.join(storageDir, "project", "proj_test123abc.json"),
300+
JSON.stringify({
301+
id: "proj_test123abc",
302+
worktree: "/",
303+
time: { created: Date.now(), updated: Date.now() },
304+
sandboxes: [],
305+
}),
306+
)
307+
308+
// Create permission file (named by projectID, contains array of rules)
309+
const permissionData = [
310+
{ permission: "file.read", pattern: "/test/file1.ts", action: "allow" as const },
311+
{ permission: "file.write", pattern: "/test/file2.ts", action: "ask" as const },
312+
{ permission: "command.run", pattern: "npm install", action: "deny" as const },
313+
]
314+
await Bun.write(path.join(storageDir, "permission", "proj_test123abc.json"), JSON.stringify(permissionData))
315+
316+
const stats = await JsonMigration.run(sqlite)
317+
318+
expect(stats?.permissions).toBe(1)
319+
320+
const db = drizzle({ client: sqlite })
321+
const permissions = db.select().from(PermissionTable).all()
322+
expect(permissions.length).toBe(1)
323+
expect(permissions[0].project_id).toBe("proj_test123abc")
324+
expect(permissions[0].data).toEqual(permissionData)
325+
})
326+
327+
test("migrates session shares", async () => {
328+
// First create the project and session
329+
await Bun.write(
330+
path.join(storageDir, "project", "proj_test123abc.json"),
331+
JSON.stringify({
332+
id: "proj_test123abc",
333+
worktree: "/",
334+
time: { created: Date.now(), updated: Date.now() },
335+
sandboxes: [],
336+
}),
337+
)
338+
await Bun.write(
339+
path.join(storageDir, "session", "proj_test123abc", "ses_test456def.json"),
340+
JSON.stringify({ ...fixtures.session }),
341+
)
342+
343+
// Create session share file (named by sessionID)
344+
await Bun.write(
345+
path.join(storageDir, "session_share", "ses_test456def.json"),
346+
JSON.stringify({
347+
id: "share_123",
348+
secret: "supersecretkey",
349+
url: "https://share.example.com/ses_test456def",
350+
}),
351+
)
352+
353+
const stats = await JsonMigration.run(sqlite)
354+
355+
expect(stats?.shares).toBe(1)
356+
357+
const db = drizzle({ client: sqlite })
358+
const shares = db.select().from(SessionShareTable).all()
359+
expect(shares.length).toBe(1)
360+
expect(shares[0].session_id).toBe("ses_test456def")
361+
expect(shares[0].id).toBe("share_123")
362+
expect(shares[0].secret).toBe("supersecretkey")
363+
expect(shares[0].url).toBe("https://share.example.com/ses_test456def")
364+
})
243365
})

0 commit comments

Comments
 (0)