mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-27 00:31:00 +00:00
sync
This commit is contained in:
parent
a614b78c6d
commit
57edb0ddc5
19 changed files with 340 additions and 251 deletions
26
AGENTS.md
26
AGENTS.md
|
|
@ -19,16 +19,16 @@
|
|||
|
||||
### Naming
|
||||
|
||||
Prefer single word variable names. Only use multiple words if necessary.
|
||||
Prefer single word names for variables and functions. Only use multiple words if necessary.
|
||||
|
||||
```ts
|
||||
// Good
|
||||
const foo = 1
|
||||
const bar = 2
|
||||
function journal(dir: string) {}
|
||||
|
||||
// Bad
|
||||
const fooBar = 1
|
||||
const barBaz = 2
|
||||
function prepareJournal(dir: string) {}
|
||||
```
|
||||
|
||||
Reduce total variable count by inlining when a value is only used once.
|
||||
|
|
@ -87,6 +87,26 @@ function foo() {
|
|||
}
|
||||
```
|
||||
|
||||
### Schema Definitions (Drizzle)
|
||||
|
||||
Use snake_case for field names so column names don't need to be redefined as strings.
|
||||
|
||||
```ts
|
||||
// Good
|
||||
const table = sqliteTable("session", {
|
||||
id: text().primaryKey(),
|
||||
project_id: text().notNull(),
|
||||
created_at: integer().notNull(),
|
||||
})
|
||||
|
||||
// Bad
|
||||
const table = sqliteTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
projectID: text("project_id").notNull(),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
})
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
- Avoid mocks as much as possible
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const ExportCommand = cmd({
|
|||
// Export sessions (organized by projectID)
|
||||
const sessionDir = path.join(outDir, "session")
|
||||
for (const row of Database.use((db) => db.select().from(SessionTable).all())) {
|
||||
const dir = path.join(sessionDir, row.projectID)
|
||||
const dir = path.join(sessionDir, row.project_id)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
await Bun.write(path.join(dir, `${row.id}.json`), JSON.stringify(Session.fromRow(row), null, 2))
|
||||
stats.sessions++
|
||||
|
|
@ -74,7 +74,7 @@ const ExportCommand = cmd({
|
|||
// Export messages (organized by sessionID)
|
||||
const messageDir = path.join(outDir, "message")
|
||||
for (const row of Database.use((db) => db.select().from(MessageTable).all())) {
|
||||
const dir = path.join(messageDir, row.sessionID)
|
||||
const dir = path.join(messageDir, row.session_id)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
await Bun.write(path.join(dir, `${row.id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.messages++
|
||||
|
|
@ -83,7 +83,7 @@ const ExportCommand = cmd({
|
|||
// Export parts (organized by messageID)
|
||||
const partDir = path.join(outDir, "part")
|
||||
for (const row of Database.use((db) => db.select().from(PartTable).all())) {
|
||||
const dir = path.join(partDir, row.messageID)
|
||||
const dir = path.join(partDir, row.message_id)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
await Bun.write(path.join(dir, `${row.id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.parts++
|
||||
|
|
@ -93,7 +93,7 @@ const ExportCommand = cmd({
|
|||
const diffDir = path.join(outDir, "session_diff")
|
||||
await fs.mkdir(diffDir, { recursive: true })
|
||||
for (const row of Database.use((db) => db.select().from(SessionDiffTable).all())) {
|
||||
await Bun.write(path.join(diffDir, `${row.sessionID}.json`), JSON.stringify(row.data, null, 2))
|
||||
await Bun.write(path.join(diffDir, `${row.session_id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.diffs++
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ const ExportCommand = cmd({
|
|||
const todoDir = path.join(outDir, "todo")
|
||||
await fs.mkdir(todoDir, { recursive: true })
|
||||
for (const row of Database.use((db) => db.select().from(TodoTable).all())) {
|
||||
await Bun.write(path.join(todoDir, `${row.sessionID}.json`), JSON.stringify(row.data, null, 2))
|
||||
await Bun.write(path.join(todoDir, `${row.session_id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.todos++
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ const ExportCommand = cmd({
|
|||
const permDir = path.join(outDir, "permission")
|
||||
await fs.mkdir(permDir, { recursive: true })
|
||||
for (const row of Database.use((db) => db.select().from(PermissionTable).all())) {
|
||||
await Bun.write(path.join(permDir, `${row.projectID}.json`), JSON.stringify(row.data, null, 2))
|
||||
await Bun.write(path.join(permDir, `${row.project_id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.permissions++
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ const ExportCommand = cmd({
|
|||
const sessionShareDir = path.join(outDir, "session_share")
|
||||
await fs.mkdir(sessionShareDir, { recursive: true })
|
||||
for (const row of Database.use((db) => db.select().from(SessionShareTable).all())) {
|
||||
await Bun.write(path.join(sessionShareDir, `${row.sessionID}.json`), JSON.stringify(row.data, null, 2))
|
||||
await Bun.write(path.join(sessionShareDir, `${row.session_id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.sessionShares++
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ const ExportCommand = cmd({
|
|||
const shareDir = path.join(outDir, "share")
|
||||
await fs.mkdir(shareDir, { recursive: true })
|
||||
for (const row of Database.use((db) => db.select().from(ShareTable).all())) {
|
||||
await Bun.write(path.join(shareDir, `${row.sessionID}.json`), JSON.stringify(row.data, null, 2))
|
||||
await Bun.write(path.join(shareDir, `${row.session_id}.json`), JSON.stringify(row.data, null, 2))
|
||||
stats.shares++
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ export const ImportCommand = cmd({
|
|||
.insert(MessageTable)
|
||||
.values({
|
||||
id: msg.info.id,
|
||||
sessionID: exportData.info.id,
|
||||
createdAt: msg.info.time?.created ?? Date.now(),
|
||||
session_id: exportData.info.id,
|
||||
created_at: msg.info.time?.created ?? Date.now(),
|
||||
data: msg.info,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
|
|
@ -104,8 +104,8 @@ export const ImportCommand = cmd({
|
|||
.insert(PartTable)
|
||||
.values({
|
||||
id: part.id,
|
||||
messageID: msg.info.id,
|
||||
sessionID: exportData.info.id,
|
||||
message_id: msg.info.id,
|
||||
session_id: exportData.info.id,
|
||||
data: part,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export namespace PermissionNext {
|
|||
const state = Instance.state(() => {
|
||||
const projectID = Instance.project.id
|
||||
const row = Database.use((db) =>
|
||||
db.select().from(PermissionTable).where(eq(PermissionTable.projectID, projectID)).get(),
|
||||
db.select().from(PermissionTable).where(eq(PermissionTable.project_id, projectID)).get(),
|
||||
)
|
||||
const stored = row?.data ?? ([] as Ruleset)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||
|
||||
export const ProjectTable = sqliteTable("project", {
|
||||
id: text("id").primaryKey(),
|
||||
worktree: text("worktree").notNull(),
|
||||
vcs: text("vcs"),
|
||||
name: text("name"),
|
||||
icon_url: text("icon_url"),
|
||||
icon_color: text("icon_color"),
|
||||
time_created: integer("time_created").notNull(),
|
||||
time_updated: integer("time_updated").notNull(),
|
||||
time_initialized: integer("time_initialized"),
|
||||
sandboxes: text("sandboxes", { mode: "json" }).notNull().$type<string[]>(),
|
||||
id: text().primaryKey(),
|
||||
worktree: text().notNull(),
|
||||
vcs: text(),
|
||||
name: text(),
|
||||
icon_url: text(),
|
||||
icon_color: text(),
|
||||
time_created: integer().notNull(),
|
||||
time_updated: integer().notNull(),
|
||||
time_initialized: integer(),
|
||||
sandboxes: text({ mode: "json" }).notNull().$type<string[]>(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ export namespace Project {
|
|||
if (!globalRow) return
|
||||
|
||||
const globalSessions = Database.use((db) =>
|
||||
db.select().from(SessionTable).where(eq(SessionTable.projectID, "global")).all(),
|
||||
db.select().from(SessionTable).where(eq(SessionTable.project_id, "global")).all(),
|
||||
)
|
||||
if (globalSessions.length === 0) return
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ export namespace Project {
|
|||
|
||||
log.info("migrating session", { sessionID: row.id, from: "global", to: newProjectID })
|
||||
Database.use((db) =>
|
||||
db.update(SessionTable).set({ projectID: newProjectID }).where(eq(SessionTable.id, row.id)).run(),
|
||||
db.update(SessionTable).set({ project_id: newProjectID }).where(eq(SessionTable.id, row.id)).run(),
|
||||
)
|
||||
}).catch((error) => {
|
||||
log.error("failed to migrate sessions from global to project", { error, projectId: newProjectID })
|
||||
|
|
|
|||
|
|
@ -276,18 +276,15 @@ export const SessionRoutes = lazy(() =>
|
|||
const sessionID = c.req.valid("param").sessionID
|
||||
const updates = c.req.valid("json")
|
||||
|
||||
const updatedSession = await Session.update(
|
||||
sessionID,
|
||||
(session) => {
|
||||
if (updates.title !== undefined) {
|
||||
session.title = updates.title
|
||||
}
|
||||
if (updates.time?.archived !== undefined) session.time.archived = updates.time.archived
|
||||
},
|
||||
{ touch: false },
|
||||
)
|
||||
let session = await Session.get(sessionID)
|
||||
if (updates.title !== undefined) {
|
||||
session = await Session.setTitle({ sessionID, title: updates.title })
|
||||
}
|
||||
if (updates.time?.archived !== undefined) {
|
||||
session = await Session.setArchived({ sessionID, time: updates.time.archived })
|
||||
}
|
||||
|
||||
return c.json(updatedSession)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ export namespace Session {
|
|||
: undefined
|
||||
const share = row.share_url ? { url: row.share_url } : undefined
|
||||
const revert =
|
||||
row.revert_messageID !== null
|
||||
row.revert_message_id !== null
|
||||
? {
|
||||
messageID: row.revert_messageID,
|
||||
partID: row.revert_partID ?? undefined,
|
||||
messageID: row.revert_message_id,
|
||||
partID: row.revert_part_id ?? undefined,
|
||||
snapshot: row.revert_snapshot ?? undefined,
|
||||
diff: row.revert_diff ?? undefined,
|
||||
}
|
||||
|
|
@ -66,9 +66,9 @@ export namespace Session {
|
|||
return {
|
||||
id: row.id,
|
||||
slug: row.slug,
|
||||
projectID: row.projectID,
|
||||
projectID: row.project_id,
|
||||
directory: row.directory,
|
||||
parentID: row.parentID ?? undefined,
|
||||
parentID: row.parent_id ?? undefined,
|
||||
title: row.title,
|
||||
version: row.version,
|
||||
summary,
|
||||
|
|
@ -87,8 +87,8 @@ export namespace Session {
|
|||
export function toRow(info: Info) {
|
||||
return {
|
||||
id: info.id,
|
||||
projectID: info.projectID,
|
||||
parentID: info.parentID,
|
||||
project_id: info.projectID,
|
||||
parent_id: info.parentID,
|
||||
slug: info.slug,
|
||||
directory: info.directory,
|
||||
title: info.title,
|
||||
|
|
@ -98,8 +98,8 @@ export namespace Session {
|
|||
summary_deletions: info.summary?.deletions,
|
||||
summary_files: info.summary?.files,
|
||||
summary_diffs: info.summary?.diffs,
|
||||
revert_messageID: info.revert?.messageID ?? null,
|
||||
revert_partID: info.revert?.partID ?? null,
|
||||
revert_message_id: info.revert?.messageID ?? null,
|
||||
revert_part_id: info.revert?.partID ?? null,
|
||||
revert_snapshot: info.revert?.snapshot ?? null,
|
||||
revert_diff: info.revert?.diff ?? null,
|
||||
permission: info.permission,
|
||||
|
|
@ -255,9 +255,10 @@ export namespace Session {
|
|||
)
|
||||
|
||||
export const touch = fn(Identifier.schema("session"), async (sessionID) => {
|
||||
await update(sessionID, (draft) => {
|
||||
draft.time.updated = Date.now()
|
||||
})
|
||||
const now = Date.now()
|
||||
Database.use((db) => db.update(SessionTable).set({ time_updated: now }).where(eq(SessionTable.id, sessionID)).run())
|
||||
const info = await get(sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
})
|
||||
|
||||
export async function createNext(input: {
|
||||
|
|
@ -288,15 +289,9 @@ export namespace Session {
|
|||
})
|
||||
const cfg = await Config.get()
|
||||
if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
|
||||
share(result.id)
|
||||
.then((share) => {
|
||||
update(result.id, (draft) => {
|
||||
draft.share = share
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// Silently ignore sharing errors during session creation
|
||||
})
|
||||
share(result.id).catch(() => {
|
||||
// Silently ignore sharing errors during session creation
|
||||
})
|
||||
Bus.publish(Event.Updated, {
|
||||
info: result,
|
||||
})
|
||||
|
|
@ -317,7 +312,7 @@ export namespace Session {
|
|||
})
|
||||
|
||||
export const getShare = fn(Identifier.schema("session"), async (id) => {
|
||||
const row = Database.use((db) => db.select().from(ShareTable).where(eq(ShareTable.sessionID, id)).get())
|
||||
const row = Database.use((db) => db.select().from(ShareTable).where(eq(ShareTable.session_id, id)).get())
|
||||
return row?.data
|
||||
})
|
||||
|
||||
|
|
@ -328,15 +323,9 @@ export namespace Session {
|
|||
}
|
||||
const { ShareNext } = await import("@/share/share-next")
|
||||
const share = await ShareNext.create(id)
|
||||
await update(
|
||||
id,
|
||||
(draft) => {
|
||||
draft.share = {
|
||||
url: share.url,
|
||||
}
|
||||
},
|
||||
{ touch: false },
|
||||
)
|
||||
Database.use((db) => db.update(SessionTable).set({ share_url: share.url }).where(eq(SessionTable.id, id)).run())
|
||||
const info = await get(id)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return share
|
||||
})
|
||||
|
||||
|
|
@ -344,33 +333,135 @@ export namespace Session {
|
|||
// Use ShareNext to remove the share (same as share function uses ShareNext to create)
|
||||
const { ShareNext } = await import("@/share/share-next")
|
||||
await ShareNext.remove(id)
|
||||
await update(
|
||||
id,
|
||||
(draft) => {
|
||||
draft.share = undefined
|
||||
},
|
||||
{ touch: false },
|
||||
)
|
||||
Database.use((db) => db.update(SessionTable).set({ share_url: null }).where(eq(SessionTable.id, id)).run())
|
||||
const info = await get(id)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
})
|
||||
|
||||
export function update(id: string, editor: (session: Info) => void, options?: { touch?: boolean }) {
|
||||
const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
|
||||
if (!row) throw new Error(`Session not found: ${id}`)
|
||||
const data = fromRow(row)
|
||||
editor(data)
|
||||
if (options?.touch !== false) {
|
||||
data.time.updated = Date.now()
|
||||
}
|
||||
Database.use((db) => db.update(SessionTable).set(toRow(data)).where(eq(SessionTable.id, id)).run())
|
||||
Bus.publish(Event.Updated, {
|
||||
info: data,
|
||||
})
|
||||
return data
|
||||
}
|
||||
export const setTitle = fn(
|
||||
z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
title: z.string(),
|
||||
}),
|
||||
async (input) => {
|
||||
Database.use((db) =>
|
||||
db.update(SessionTable).set({ title: input.title }).where(eq(SessionTable.id, input.sessionID)).run(),
|
||||
)
|
||||
const info = await get(input.sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return info
|
||||
},
|
||||
)
|
||||
|
||||
export const setArchived = fn(
|
||||
z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
time: z.number().optional(),
|
||||
}),
|
||||
async (input) => {
|
||||
Database.use((db) =>
|
||||
db.update(SessionTable).set({ time_archived: input.time }).where(eq(SessionTable.id, input.sessionID)).run(),
|
||||
)
|
||||
const info = await get(input.sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return info
|
||||
},
|
||||
)
|
||||
|
||||
export const setPermission = fn(
|
||||
z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
permission: PermissionNext.Ruleset,
|
||||
}),
|
||||
async (input) => {
|
||||
Database.use((db) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({ permission: input.permission, time_updated: Date.now() })
|
||||
.where(eq(SessionTable.id, input.sessionID))
|
||||
.run(),
|
||||
)
|
||||
const info = await get(input.sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return info
|
||||
},
|
||||
)
|
||||
|
||||
export const setRevert = fn(
|
||||
z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
revert: Info.shape.revert,
|
||||
summary: Info.shape.summary,
|
||||
}),
|
||||
async (input) => {
|
||||
Database.use((db) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({
|
||||
revert_message_id: input.revert?.messageID ?? null,
|
||||
revert_part_id: input.revert?.partID ?? null,
|
||||
revert_snapshot: input.revert?.snapshot ?? null,
|
||||
revert_diff: input.revert?.diff ?? null,
|
||||
summary_additions: input.summary?.additions,
|
||||
summary_deletions: input.summary?.deletions,
|
||||
summary_files: input.summary?.files,
|
||||
time_updated: Date.now(),
|
||||
})
|
||||
.where(eq(SessionTable.id, input.sessionID))
|
||||
.run(),
|
||||
)
|
||||
const info = await get(input.sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return info
|
||||
},
|
||||
)
|
||||
|
||||
export const clearRevert = fn(Identifier.schema("session"), async (sessionID) => {
|
||||
Database.use((db) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({
|
||||
revert_message_id: null,
|
||||
revert_part_id: null,
|
||||
revert_snapshot: null,
|
||||
revert_diff: null,
|
||||
time_updated: Date.now(),
|
||||
})
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.run(),
|
||||
)
|
||||
const info = await get(sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return info
|
||||
})
|
||||
|
||||
export const setSummary = fn(
|
||||
z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
summary: Info.shape.summary,
|
||||
}),
|
||||
async (input) => {
|
||||
Database.use((db) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
.set({
|
||||
summary_additions: input.summary?.additions,
|
||||
summary_deletions: input.summary?.deletions,
|
||||
summary_files: input.summary?.files,
|
||||
time_updated: Date.now(),
|
||||
})
|
||||
.where(eq(SessionTable.id, input.sessionID))
|
||||
.run(),
|
||||
)
|
||||
const info = await get(input.sessionID)
|
||||
Bus.publish(Event.Updated, { info })
|
||||
return info
|
||||
},
|
||||
)
|
||||
|
||||
export const diff = fn(Identifier.schema("session"), async (sessionID) => {
|
||||
const row = Database.use((db) =>
|
||||
db.select().from(SessionDiffTable).where(eq(SessionDiffTable.sessionID, sessionID)).get(),
|
||||
db.select().from(SessionDiffTable).where(eq(SessionDiffTable.session_id, sessionID)).get(),
|
||||
)
|
||||
return row?.data ?? []
|
||||
})
|
||||
|
|
@ -394,7 +485,7 @@ export namespace Session {
|
|||
export function* list() {
|
||||
const project = Instance.project
|
||||
const rows = Database.use((db) =>
|
||||
db.select().from(SessionTable).where(eq(SessionTable.projectID, project.id)).all(),
|
||||
db.select().from(SessionTable).where(eq(SessionTable.project_id, project.id)).all(),
|
||||
)
|
||||
for (const row of rows) {
|
||||
yield fromRow(row)
|
||||
|
|
@ -402,7 +493,7 @@ export namespace Session {
|
|||
}
|
||||
|
||||
export const children = fn(Identifier.schema("session"), async (parentID) => {
|
||||
const rows = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.parentID, parentID)).all())
|
||||
const rows = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.parent_id, parentID)).all())
|
||||
return rows.map((row) => fromRow(row))
|
||||
})
|
||||
|
||||
|
|
@ -425,14 +516,14 @@ export namespace Session {
|
|||
})
|
||||
|
||||
export const updateMessage = fn(MessageV2.Info, async (msg) => {
|
||||
const createdAt = msg.role === "user" ? msg.time.created : msg.time.created
|
||||
const created_at = msg.role === "user" ? msg.time.created : msg.time.created
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(MessageTable)
|
||||
.values({
|
||||
id: msg.id,
|
||||
sessionID: msg.sessionID,
|
||||
createdAt,
|
||||
session_id: msg.sessionID,
|
||||
created_at,
|
||||
data: msg,
|
||||
})
|
||||
.onConflictDoUpdate({ target: MessageTable.id, set: { data: msg } })
|
||||
|
|
@ -497,8 +588,8 @@ export namespace Session {
|
|||
.insert(PartTable)
|
||||
.values({
|
||||
id: part.id,
|
||||
messageID: part.messageID,
|
||||
sessionID: part.sessionID,
|
||||
message_id: part.messageID,
|
||||
session_id: part.sessionID,
|
||||
data: part,
|
||||
})
|
||||
.onConflictDoUpdate({ target: PartTable.id, set: { data: part } })
|
||||
|
|
|
|||
|
|
@ -612,8 +612,8 @@ export namespace MessageV2 {
|
|||
db
|
||||
.select()
|
||||
.from(MessageTable)
|
||||
.where(eq(MessageTable.sessionID, sessionID))
|
||||
.orderBy(desc(MessageTable.createdAt))
|
||||
.where(eq(MessageTable.session_id, sessionID))
|
||||
.orderBy(desc(MessageTable.created_at))
|
||||
.all(),
|
||||
)
|
||||
for (const row of rows) {
|
||||
|
|
@ -624,8 +624,8 @@ export namespace MessageV2 {
|
|||
}
|
||||
})
|
||||
|
||||
export const parts = fn(Identifier.schema("message"), async (messageID) => {
|
||||
const rows = Database.use((db) => db.select().from(PartTable).where(eq(PartTable.messageID, messageID)).all())
|
||||
export const parts = fn(Identifier.schema("message"), async (message_id) => {
|
||||
const rows = Database.use((db) => db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).all())
|
||||
const result = rows.map((row) => row.data)
|
||||
result.sort((a, b) => (a.id > b.id ? 1 : -1))
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -167,9 +167,7 @@ export namespace SessionPrompt {
|
|||
}
|
||||
if (permissions.length > 0) {
|
||||
session.permission = permissions
|
||||
await Session.update(session.id, (draft) => {
|
||||
draft.permission = permissions
|
||||
})
|
||||
await Session.setPermission({ sessionID: session.id, permission: permissions })
|
||||
}
|
||||
|
||||
if (input.noReply === true) {
|
||||
|
|
@ -1795,21 +1793,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
|||
],
|
||||
})
|
||||
const text = await result.text.catch((err) => log.error("failed to generate title", { error: err }))
|
||||
if (text)
|
||||
return Session.update(
|
||||
input.session.id,
|
||||
(draft) => {
|
||||
const cleaned = text
|
||||
.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.find((line) => line.length > 0)
|
||||
if (!cleaned) return
|
||||
if (text) {
|
||||
const cleaned = text
|
||||
.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.find((line) => line.length > 0)
|
||||
if (!cleaned) return
|
||||
|
||||
const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
|
||||
draft.title = title
|
||||
},
|
||||
{ touch: false },
|
||||
)
|
||||
const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
|
||||
return Session.setTitle({ sessionID: input.session.id, title })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,21 +64,22 @@ export namespace SessionRevert {
|
|||
Database.use((db) =>
|
||||
db
|
||||
.insert(SessionDiffTable)
|
||||
.values({ sessionID: input.sessionID, data: diffs })
|
||||
.onConflictDoUpdate({ target: SessionDiffTable.sessionID, set: { data: diffs } })
|
||||
.values({ session_id: input.sessionID, data: diffs })
|
||||
.onConflictDoUpdate({ target: SessionDiffTable.session_id, set: { data: diffs } })
|
||||
.run(),
|
||||
)
|
||||
Bus.publish(Session.Event.Diff, {
|
||||
sessionID: input.sessionID,
|
||||
diff: diffs,
|
||||
})
|
||||
return Session.update(input.sessionID, (draft) => {
|
||||
draft.revert = revert
|
||||
draft.summary = {
|
||||
return Session.setRevert({
|
||||
sessionID: input.sessionID,
|
||||
revert,
|
||||
summary: {
|
||||
additions: diffs.reduce((sum, x) => sum + x.additions, 0),
|
||||
deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
|
||||
files: diffs.length,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
return session
|
||||
|
|
@ -90,10 +91,7 @@ export namespace SessionRevert {
|
|||
const session = await Session.get(input.sessionID)
|
||||
if (!session.revert) return session
|
||||
if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot)
|
||||
const next = await Session.update(input.sessionID, (draft) => {
|
||||
draft.revert = undefined
|
||||
})
|
||||
return next
|
||||
return Session.clearRevert(input.sessionID)
|
||||
}
|
||||
|
||||
export async function cleanup(session: Session.Info) {
|
||||
|
|
@ -121,8 +119,6 @@ export namespace SessionRevert {
|
|||
})
|
||||
}
|
||||
}
|
||||
await Session.update(sessionID, (draft) => {
|
||||
draft.revert = undefined
|
||||
})
|
||||
await Session.clearRevert(sessionID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,76 +8,76 @@ import type { PermissionNext } from "@/permission/next"
|
|||
export const SessionTable = sqliteTable(
|
||||
"session",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
projectID: text("project_id")
|
||||
id: text().primaryKey(),
|
||||
project_id: text()
|
||||
.notNull()
|
||||
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
||||
parentID: text("parent_id"),
|
||||
slug: text("slug").notNull(),
|
||||
directory: text("directory").notNull(),
|
||||
title: text("title").notNull(),
|
||||
version: text("version").notNull(),
|
||||
share_url: text("share_url"),
|
||||
summary_additions: integer("summary_additions"),
|
||||
summary_deletions: integer("summary_deletions"),
|
||||
summary_files: integer("summary_files"),
|
||||
summary_diffs: text("summary_diffs", { mode: "json" }).$type<Snapshot.FileDiff[]>(),
|
||||
revert_messageID: text("revert_message_id"),
|
||||
revert_partID: text("revert_part_id"),
|
||||
revert_snapshot: text("revert_snapshot"),
|
||||
revert_diff: text("revert_diff"),
|
||||
permission: text("permission", { mode: "json" }).$type<PermissionNext.Ruleset>(),
|
||||
time_created: integer("time_created").notNull(),
|
||||
time_updated: integer("time_updated").notNull(),
|
||||
time_compacting: integer("time_compacting"),
|
||||
time_archived: integer("time_archived"),
|
||||
parent_id: text(),
|
||||
slug: text().notNull(),
|
||||
directory: text().notNull(),
|
||||
title: text().notNull(),
|
||||
version: text().notNull(),
|
||||
share_url: text(),
|
||||
summary_additions: integer(),
|
||||
summary_deletions: integer(),
|
||||
summary_files: integer(),
|
||||
summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(),
|
||||
revert_message_id: text(),
|
||||
revert_part_id: text(),
|
||||
revert_snapshot: text(),
|
||||
revert_diff: text(),
|
||||
permission: text({ mode: "json" }).$type<PermissionNext.Ruleset>(),
|
||||
time_created: integer().notNull(),
|
||||
time_updated: integer().notNull(),
|
||||
time_compacting: integer(),
|
||||
time_archived: integer(),
|
||||
},
|
||||
(table) => [index("session_project_idx").on(table.projectID), index("session_parent_idx").on(table.parentID)],
|
||||
(table) => [index("session_project_idx").on(table.project_id), index("session_parent_idx").on(table.parent_id)],
|
||||
)
|
||||
|
||||
export const MessageTable = sqliteTable(
|
||||
"message",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
sessionID: text("session_id")
|
||||
id: text().primaryKey(),
|
||||
session_id: text()
|
||||
.notNull()
|
||||
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
data: text("data", { mode: "json" }).notNull().$type<MessageV2.Info>(),
|
||||
created_at: integer().notNull(),
|
||||
data: text({ mode: "json" }).notNull().$type<MessageV2.Info>(),
|
||||
},
|
||||
(table) => [index("message_session_idx").on(table.sessionID)],
|
||||
(table) => [index("message_session_idx").on(table.session_id)],
|
||||
)
|
||||
|
||||
export const PartTable = sqliteTable(
|
||||
"part",
|
||||
{
|
||||
id: text("id").primaryKey(),
|
||||
messageID: text("message_id")
|
||||
id: text().primaryKey(),
|
||||
message_id: text()
|
||||
.notNull()
|
||||
.references(() => MessageTable.id, { onDelete: "cascade" }),
|
||||
sessionID: text("session_id").notNull(),
|
||||
data: text("data", { mode: "json" }).notNull().$type<MessageV2.Part>(),
|
||||
session_id: text().notNull(),
|
||||
data: text({ mode: "json" }).notNull().$type<MessageV2.Part>(),
|
||||
},
|
||||
(table) => [index("part_message_idx").on(table.messageID), index("part_session_idx").on(table.sessionID)],
|
||||
(table) => [index("part_message_idx").on(table.message_id), index("part_session_idx").on(table.session_id)],
|
||||
)
|
||||
|
||||
export const SessionDiffTable = sqliteTable("session_diff", {
|
||||
sessionID: text("session_id")
|
||||
session_id: text()
|
||||
.primaryKey()
|
||||
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
||||
data: text("data", { mode: "json" }).notNull().$type<Snapshot.FileDiff[]>(),
|
||||
data: text({ mode: "json" }).notNull().$type<Snapshot.FileDiff[]>(),
|
||||
})
|
||||
|
||||
export const TodoTable = sqliteTable("todo", {
|
||||
sessionID: text("session_id")
|
||||
session_id: text()
|
||||
.primaryKey()
|
||||
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
||||
data: text("data", { mode: "json" }).notNull().$type<Todo.Info[]>(),
|
||||
data: text({ mode: "json" }).notNull().$type<Todo.Info[]>(),
|
||||
})
|
||||
|
||||
export const PermissionTable = sqliteTable("permission", {
|
||||
projectID: text("project_id")
|
||||
project_id: text()
|
||||
.primaryKey()
|
||||
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
||||
data: text("data", { mode: "json" }).notNull().$type<PermissionNext.Ruleset>(),
|
||||
data: text({ mode: "json" }).notNull().$type<PermissionNext.Ruleset>(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -48,18 +48,19 @@ export namespace SessionSummary {
|
|||
return files.has(x.file)
|
||||
}),
|
||||
)
|
||||
await Session.update(input.sessionID, (draft) => {
|
||||
draft.summary = {
|
||||
await Session.setSummary({
|
||||
sessionID: input.sessionID,
|
||||
summary: {
|
||||
additions: diffs.reduce((sum, x) => sum + x.additions, 0),
|
||||
deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
|
||||
files: diffs.length,
|
||||
}
|
||||
},
|
||||
})
|
||||
Database.use((db) =>
|
||||
db
|
||||
.insert(SessionDiffTable)
|
||||
.values({ sessionID: input.sessionID, data: diffs })
|
||||
.onConflictDoUpdate({ target: SessionDiffTable.sessionID, set: { data: diffs } })
|
||||
.values({ session_id: input.sessionID, data: diffs })
|
||||
.onConflictDoUpdate({ target: SessionDiffTable.session_id, set: { data: diffs } })
|
||||
.run(),
|
||||
)
|
||||
Bus.publish(Session.Event.Diff, {
|
||||
|
|
@ -124,7 +125,7 @@ export namespace SessionSummary {
|
|||
}),
|
||||
async (input) => {
|
||||
const row = Database.use((db) =>
|
||||
db.select().from(SessionDiffTable).where(eq(SessionDiffTable.sessionID, input.sessionID)).get(),
|
||||
db.select().from(SessionDiffTable).where(eq(SessionDiffTable.session_id, input.sessionID)).get(),
|
||||
)
|
||||
return row?.data ?? []
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ export namespace Todo {
|
|||
Database.use((db) =>
|
||||
db
|
||||
.insert(TodoTable)
|
||||
.values({ sessionID: input.sessionID, data: input.todos })
|
||||
.onConflictDoUpdate({ target: TodoTable.sessionID, set: { data: input.todos } })
|
||||
.values({ session_id: input.sessionID, data: input.todos })
|
||||
.onConflictDoUpdate({ target: TodoTable.session_id, set: { data: input.todos } })
|
||||
.run(),
|
||||
)
|
||||
Bus.publish(Event.Updated, input)
|
||||
}
|
||||
|
||||
export function get(sessionID: string) {
|
||||
const row = Database.use((db) => db.select().from(TodoTable).where(eq(TodoTable.sessionID, sessionID)).get())
|
||||
const row = Database.use((db) => db.select().from(TodoTable).where(eq(TodoTable.session_id, sessionID)).get())
|
||||
return row?.data ?? []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ export namespace ShareNext {
|
|||
Database.use((db) =>
|
||||
db
|
||||
.insert(SessionShareTable)
|
||||
.values({ sessionID, data: result })
|
||||
.onConflictDoUpdate({ target: SessionShareTable.sessionID, set: { data: result } })
|
||||
.values({ session_id: sessionID, data: result })
|
||||
.onConflictDoUpdate({ target: SessionShareTable.session_id, set: { data: result } })
|
||||
.run(),
|
||||
)
|
||||
fullSync(sessionID)
|
||||
|
|
@ -91,7 +91,7 @@ export namespace ShareNext {
|
|||
|
||||
function get(sessionID: string) {
|
||||
const row = Database.use((db) =>
|
||||
db.select().from(SessionShareTable).where(eq(SessionShareTable.sessionID, sessionID)).get(),
|
||||
db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).get(),
|
||||
)
|
||||
return row?.data
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ export namespace ShareNext {
|
|||
secret: share.secret,
|
||||
}),
|
||||
})
|
||||
Database.use((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.sessionID, sessionID)).run())
|
||||
Database.use((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run())
|
||||
}
|
||||
|
||||
async function fullSync(sessionID: string) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { SessionTable } from "../session/session.sql"
|
|||
import type { Session } from "../session"
|
||||
|
||||
export const SessionShareTable = sqliteTable("session_share", {
|
||||
sessionID: text("session_id")
|
||||
session_id: text()
|
||||
.primaryKey()
|
||||
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
||||
data: text("data", { mode: "json" }).notNull().$type<{
|
||||
data: text({ mode: "json" }).notNull().$type<{
|
||||
id: string
|
||||
secret: string
|
||||
url: string
|
||||
|
|
@ -14,6 +14,6 @@ export const SessionShareTable = sqliteTable("session_share", {
|
|||
})
|
||||
|
||||
export const ShareTable = sqliteTable("share", {
|
||||
sessionID: text("session_id").primaryKey(),
|
||||
data: text("data", { mode: "json" }).notNull().$type<Session.ShareInfo>(),
|
||||
session_id: text().primaryKey(),
|
||||
data: text({ mode: "json" }).notNull().$type<Session.ShareInfo>(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Database as BunDatabase } from "bun:sqlite"
|
||||
import { drizzle, type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
|
||||
import { migrate as drizzleMigrate } from "drizzle-orm/bun-sqlite/migrator"
|
||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator"
|
||||
import type { SQLiteTransaction } from "drizzle-orm/sqlite-core"
|
||||
export * from "drizzle-orm"
|
||||
import { Context } from "../util/context"
|
||||
|
|
@ -29,6 +29,22 @@ export namespace Database {
|
|||
|
||||
type Client = SQLiteBunDatabase
|
||||
|
||||
type Journal = { sql: string; timestamp: number }[]
|
||||
|
||||
function journal(dir: string): Journal {
|
||||
const file = path.join(dir, "meta/_journal.json")
|
||||
if (!Bun.file(file).size) return []
|
||||
|
||||
const data = JSON.parse(readFileSync(file, "utf-8")) as {
|
||||
entries: { tag: string; when: number }[]
|
||||
}
|
||||
|
||||
return data.entries.map((entry) => ({
|
||||
sql: readFileSync(path.join(dir, `${entry.tag}.sql`), "utf-8"),
|
||||
timestamp: entry.when,
|
||||
}))
|
||||
}
|
||||
|
||||
const client = lazy(() => {
|
||||
log.info("opening database", { path: path.join(Global.Path.data, "opencode.db") })
|
||||
|
||||
|
|
@ -41,11 +57,22 @@ export namespace Database {
|
|||
sqlite.run("PRAGMA foreign_keys = ON")
|
||||
|
||||
const db = drizzle({ client: sqlite })
|
||||
migrate(db)
|
||||
|
||||
// Apply schema migrations
|
||||
const entries =
|
||||
typeof OPENCODE_MIGRATIONS !== "undefined"
|
||||
? OPENCODE_MIGRATIONS
|
||||
: journal(path.join(import.meta.dirname, "../../migration"))
|
||||
if (entries.length > 0) {
|
||||
log.info("applying migrations", {
|
||||
count: entries.length,
|
||||
mode: typeof OPENCODE_MIGRATIONS !== "undefined" ? "bundled" : "dev",
|
||||
})
|
||||
migrate(db, entries)
|
||||
}
|
||||
|
||||
// Run json migration if not already done
|
||||
const marker = sqlite.prepare("SELECT 1 FROM __drizzle_migrations WHERE hash = 'json-migration'").get()
|
||||
if (!marker) {
|
||||
if (!sqlite.prepare("SELECT 1 FROM __drizzle_migrations WHERE hash = 'json-migration'").get()) {
|
||||
Bun.file(path.join(Global.Path.data, "storage/project"))
|
||||
.exists()
|
||||
.then((exists) => {
|
||||
|
|
@ -62,19 +89,18 @@ export namespace Database {
|
|||
|
||||
export type TxOrDb = Transaction | Client
|
||||
|
||||
const TransactionContext = Context.create<{
|
||||
const ctx = Context.create<{
|
||||
tx: TxOrDb
|
||||
effects: (() => void | Promise<void>)[]
|
||||
}>("database")
|
||||
|
||||
export function use<T>(callback: (trx: TxOrDb) => T): T {
|
||||
try {
|
||||
const { tx } = TransactionContext.use()
|
||||
return callback(tx)
|
||||
return callback(ctx.use().tx)
|
||||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const effects: (() => void | Promise<void>)[] = []
|
||||
const result = TransactionContext.provide({ effects, tx: client() }, () => callback(client()))
|
||||
const result = ctx.provide({ effects, tx: client() }, () => callback(client()))
|
||||
for (const effect of effects) effect()
|
||||
return result
|
||||
}
|
||||
|
|
@ -82,24 +108,22 @@ export namespace Database {
|
|||
}
|
||||
}
|
||||
|
||||
export function effect(effect: () => void | Promise<void>) {
|
||||
export function effect(fn: () => void | Promise<void>) {
|
||||
try {
|
||||
const { effects } = TransactionContext.use()
|
||||
effects.push(effect)
|
||||
ctx.use().effects.push(fn)
|
||||
} catch {
|
||||
effect()
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
export function transaction<T>(callback: (tx: TxOrDb) => T): T {
|
||||
try {
|
||||
const { tx } = TransactionContext.use()
|
||||
return callback(tx)
|
||||
return callback(ctx.use().tx)
|
||||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const effects: (() => void | Promise<void>)[] = []
|
||||
const result = client().transaction((tx) => {
|
||||
return TransactionContext.provide({ tx, effects }, () => callback(tx))
|
||||
return ctx.provide({ tx, effects }, () => callback(tx))
|
||||
})
|
||||
for (const effect of effects) effect()
|
||||
return result
|
||||
|
|
@ -108,36 +132,3 @@ export namespace Database {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type MigrationsJournal = { sql: string; timestamp: number }[]
|
||||
|
||||
function prepareJournal(dir: string): MigrationsJournal {
|
||||
const file = path.join(dir, "meta/_journal.json")
|
||||
if (!Bun.file(file).size) return []
|
||||
|
||||
const journal = JSON.parse(readFileSync(file, "utf-8")) as {
|
||||
entries: { tag: string; when: number }[]
|
||||
}
|
||||
|
||||
return journal.entries.map((entry) => ({
|
||||
sql: readFileSync(path.join(dir, `${entry.tag}.sql`), "utf-8"),
|
||||
timestamp: entry.when,
|
||||
}))
|
||||
}
|
||||
|
||||
function migrate(db: SQLiteBunDatabase) {
|
||||
const journal =
|
||||
typeof OPENCODE_MIGRATIONS !== "undefined"
|
||||
? OPENCODE_MIGRATIONS
|
||||
: prepareJournal(path.join(import.meta.dirname, "../../migration"))
|
||||
|
||||
if (journal.length === 0) {
|
||||
log.info("no migrations found")
|
||||
return
|
||||
}
|
||||
log.info("applying migrations", {
|
||||
count: journal.length,
|
||||
mode: typeof OPENCODE_MIGRATIONS !== "undefined" ? "bundled" : "dev",
|
||||
})
|
||||
drizzleMigrate(db, journal)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
db.insert(SessionTable)
|
||||
.values({
|
||||
id: data.id,
|
||||
projectID: data.projectID,
|
||||
parentID: data.parentID ?? null,
|
||||
project_id: data.projectID,
|
||||
parent_id: data.parentID ?? null,
|
||||
slug: data.slug ?? "",
|
||||
directory: data.directory ?? "",
|
||||
title: data.title ?? "",
|
||||
|
|
@ -95,8 +95,8 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
summary_deletions: data.summary?.deletions ?? null,
|
||||
summary_files: data.summary?.files ?? null,
|
||||
summary_diffs: data.summary?.diffs ?? null,
|
||||
revert_messageID: data.revert?.messageID ?? null,
|
||||
revert_partID: data.revert?.partID ?? null,
|
||||
revert_message_id: data.revert?.messageID ?? null,
|
||||
revert_part_id: data.revert?.partID ?? null,
|
||||
revert_snapshot: data.revert?.snapshot ?? null,
|
||||
revert_diff: data.revert?.diff ?? null,
|
||||
permission: data.permission ?? null,
|
||||
|
|
@ -132,8 +132,8 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
db.insert(MessageTable)
|
||||
.values({
|
||||
id: data.id,
|
||||
sessionID: data.sessionID,
|
||||
createdAt: data.time?.created ?? Date.now(),
|
||||
session_id: data.sessionID,
|
||||
created_at: data.time?.created ?? Date.now(),
|
||||
data,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
|
|
@ -163,8 +163,8 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
db.insert(PartTable)
|
||||
.values({
|
||||
id: data.id,
|
||||
messageID: data.messageID,
|
||||
sessionID: data.sessionID,
|
||||
message_id: data.messageID,
|
||||
session_id: data.sessionID,
|
||||
data,
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
|
|
@ -188,7 +188,7 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
log.warn("skipping orphaned session_diff", { sessionID })
|
||||
continue
|
||||
}
|
||||
db.insert(SessionDiffTable).values({ sessionID, data }).onConflictDoNothing().run()
|
||||
db.insert(SessionDiffTable).values({ session_id: sessionID, data }).onConflictDoNothing().run()
|
||||
stats.diffs++
|
||||
} catch (e) {
|
||||
stats.errors.push(`failed to migrate session_diff ${file}: ${e}`)
|
||||
|
|
@ -207,7 +207,7 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
log.warn("skipping orphaned todo", { sessionID })
|
||||
continue
|
||||
}
|
||||
db.insert(TodoTable).values({ sessionID, data }).onConflictDoNothing().run()
|
||||
db.insert(TodoTable).values({ session_id: sessionID, data }).onConflictDoNothing().run()
|
||||
stats.todos++
|
||||
} catch (e) {
|
||||
stats.errors.push(`failed to migrate todo ${file}: ${e}`)
|
||||
|
|
@ -226,7 +226,7 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
log.warn("skipping orphaned permission", { projectID })
|
||||
continue
|
||||
}
|
||||
db.insert(PermissionTable).values({ projectID, data }).onConflictDoNothing().run()
|
||||
db.insert(PermissionTable).values({ project_id: projectID, data }).onConflictDoNothing().run()
|
||||
stats.permissions++
|
||||
} catch (e) {
|
||||
stats.errors.push(`failed to migrate permission ${file}: ${e}`)
|
||||
|
|
@ -245,7 +245,7 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
log.warn("skipping orphaned session_share", { sessionID })
|
||||
continue
|
||||
}
|
||||
db.insert(SessionShareTable).values({ sessionID, data }).onConflictDoNothing().run()
|
||||
db.insert(SessionShareTable).values({ session_id: sessionID, data }).onConflictDoNothing().run()
|
||||
stats.shares++
|
||||
} catch (e) {
|
||||
stats.errors.push(`failed to migrate session_share ${file}: ${e}`)
|
||||
|
|
@ -259,7 +259,7 @@ export async function migrateFromJson(sqlite: Database, customStorageDir?: strin
|
|||
try {
|
||||
const data = await Bun.file(file).json()
|
||||
const sessionID = path.basename(file, ".json")
|
||||
db.insert(ShareTable).values({ sessionID, data }).onConflictDoNothing().run()
|
||||
db.insert(ShareTable).values({ session_id: sessionID, data }).onConflictDoNothing().run()
|
||||
} catch (e) {
|
||||
stats.errors.push(`failed to migrate share ${file}: ${e}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ describe("JSON to SQLite migration", () => {
|
|||
const sessions = db.select().from(SessionTable).all()
|
||||
expect(sessions.length).toBe(1)
|
||||
expect(sessions[0].id).toBe("ses_test456def")
|
||||
expect(sessions[0].projectID).toBe("proj_test123abc")
|
||||
expect(sessions[0].project_id).toBe("proj_test123abc")
|
||||
expect(sessions[0].slug).toBe("test-session")
|
||||
expect(sessions[0].title).toBe("Test Session Title")
|
||||
expect(sessions[0].summary_additions).toBe(10)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue