mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-25 03:34:22 +01:00
wip: cloud
This commit is contained in:
@@ -80,14 +80,12 @@ export namespace Billing {
|
||||
cacheWriteTokens: input.cacheWriteTokens,
|
||||
cost,
|
||||
})
|
||||
const [updated] = await tx
|
||||
await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} - ${cost}`,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||
.returning()
|
||||
return updated.balance
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js"
|
||||
import { drizzle } from "drizzle-orm/planetscale-serverless"
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
export * from "drizzle-orm"
|
||||
import postgres from "postgres"
|
||||
import { Client } from "@planetscale/database"
|
||||
|
||||
const init = () => {
|
||||
const client = postgres({
|
||||
idle_timeout: 30000,
|
||||
connect_timeout: 30000,
|
||||
host: Resource.Database.host,
|
||||
database: Resource.Database.database,
|
||||
user: Resource.Database.username,
|
||||
password: Resource.Database.password,
|
||||
port: Resource.Database.port,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
max: 6,
|
||||
})
|
||||
return drizzle(client, {})
|
||||
}
|
||||
|
||||
const createClient = init
|
||||
|
||||
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
|
||||
import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
|
||||
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
||||
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"
|
||||
import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
|
||||
import { Context } from "../context"
|
||||
import { memo } from "../util/memo"
|
||||
|
||||
export namespace Database {
|
||||
export type Transaction = PgTransaction<
|
||||
PostgresJsQueryResultHKT,
|
||||
Record<string, unknown>,
|
||||
ExtractTablesWithRelations<Record<string, unknown>>
|
||||
export type Transaction = MySqlTransaction<
|
||||
PlanetscaleQueryResultHKT,
|
||||
PlanetScalePreparedQueryHKT,
|
||||
Record<string, never>,
|
||||
ExtractTablesWithRelations<Record<string, never>>
|
||||
>
|
||||
|
||||
export type TxOrDb = Transaction | ReturnType<typeof createClient>
|
||||
const client = memo(() => {
|
||||
const result = new Client({
|
||||
host: Resource.Database.host,
|
||||
username: Resource.Database.username,
|
||||
password: Resource.Database.password,
|
||||
})
|
||||
const db = drizzle(result, {})
|
||||
return db
|
||||
})
|
||||
|
||||
export type TxOrDb = Transaction | ReturnType<typeof client>
|
||||
|
||||
const TransactionContext = Context.create<{
|
||||
tx: TxOrDb
|
||||
@@ -48,14 +40,13 @@ export namespace Database {
|
||||
return tx.transaction(callback)
|
||||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const client = createClient()
|
||||
const effects: (() => void | Promise<void>)[] = []
|
||||
const result = await TransactionContext.provide(
|
||||
{
|
||||
effects,
|
||||
tx: client,
|
||||
tx: client(),
|
||||
},
|
||||
() => callback(client),
|
||||
() => callback(client()),
|
||||
)
|
||||
await Promise.all(effects.map((x) => x()))
|
||||
return result
|
||||
@@ -76,15 +67,14 @@ export namespace Database {
|
||||
}
|
||||
}
|
||||
|
||||
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: PgTransactionConfig) {
|
||||
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: MySqlTransactionConfig) {
|
||||
try {
|
||||
const { tx } = TransactionContext.use()
|
||||
return callback(tx)
|
||||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const client = createClient()
|
||||
const effects: (() => void | Promise<void>)[] = []
|
||||
const result = await client.transaction(async (tx) => {
|
||||
const result = await client().transaction(async (tx) => {
|
||||
return TransactionContext.provide({ tx, effects }, () => callback(tx))
|
||||
}, config)
|
||||
await Promise.all(effects.map((x) => x()))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { bigint, timestamp, varchar } from "drizzle-orm/pg-core"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { bigint, timestamp, varchar } from "drizzle-orm/mysql-core"
|
||||
|
||||
export const ulid = (name: string) => varchar(name, { length: 30 })
|
||||
|
||||
@@ -15,7 +16,7 @@ export const id = () => ulid("id").notNull()
|
||||
|
||||
export const utc = (name: string) =>
|
||||
timestamp(name, {
|
||||
withTimezone: true,
|
||||
fsp: 3,
|
||||
})
|
||||
|
||||
export const currency = (name: string) =>
|
||||
@@ -25,5 +26,8 @@ export const currency = (name: string) =>
|
||||
|
||||
export const timestamps = {
|
||||
timeCreated: utc("time_created").notNull().defaultNow(),
|
||||
timeUpdated: utc("time_updated")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
|
||||
timeDeleted: utc("time_deleted"),
|
||||
}
|
||||
|
||||
@@ -36,44 +36,26 @@ export namespace Key {
|
||||
randomPart += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
const secretKey = `sk-${randomPart}`
|
||||
const keyID = Identifier.create("key")
|
||||
|
||||
const keyRecord = await Database.use((tx) =>
|
||||
tx
|
||||
.insert(KeyTable)
|
||||
.values({
|
||||
id: Identifier.create("key"),
|
||||
workspaceID: user.properties.workspaceID,
|
||||
userID: user.properties.userID,
|
||||
name,
|
||||
key: secretKey,
|
||||
timeUsed: null,
|
||||
})
|
||||
.returning(),
|
||||
await Database.use((tx) =>
|
||||
tx.insert(KeyTable).values({
|
||||
id: keyID,
|
||||
workspaceID: user.properties.workspaceID,
|
||||
userID: user.properties.userID,
|
||||
name,
|
||||
key: secretKey,
|
||||
timeUsed: null,
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
key: secretKey,
|
||||
id: keyRecord[0].id,
|
||||
name: keyRecord[0].name,
|
||||
created: keyRecord[0].timeCreated,
|
||||
}
|
||||
return keyID
|
||||
})
|
||||
|
||||
export const remove = fn(z.object({ id: z.string() }), async (input) => {
|
||||
const user = Actor.assert("user")
|
||||
const { id } = input
|
||||
|
||||
const result = await Database.use((tx) =>
|
||||
tx
|
||||
.delete(KeyTable)
|
||||
.where(and(eq(KeyTable.id, id), eq(KeyTable.workspaceID, user.properties.workspaceID)))
|
||||
.returning({ id: KeyTable.id }),
|
||||
await Database.use((tx) =>
|
||||
tx.delete(KeyTable).where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, user.properties.workspaceID))),
|
||||
)
|
||||
|
||||
if (result.length === 0) {
|
||||
throw new Error("Key not found")
|
||||
}
|
||||
|
||||
return { id: result[0].id }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"
|
||||
import { mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||
import { id, timestamps } from "../drizzle/types"
|
||||
|
||||
export const AccountTable = pgTable(
|
||||
export const AccountTable = mysqlTable(
|
||||
"account",
|
||||
{
|
||||
id: id(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bigint, boolean, integer, pgTable, varchar } from "drizzle-orm/pg-core"
|
||||
import { bigint, boolean, int, mysqlTable, varchar } from "drizzle-orm/mysql-core"
|
||||
import { timestamps, workspaceColumns } from "../drizzle/types"
|
||||
import { workspaceIndexes } from "./workspace.sql"
|
||||
|
||||
export const BillingTable = pgTable(
|
||||
export const BillingTable = mysqlTable(
|
||||
"billing",
|
||||
{
|
||||
...workspaceColumns,
|
||||
@@ -16,7 +16,7 @@ export const BillingTable = pgTable(
|
||||
(table) => [...workspaceIndexes(table)],
|
||||
)
|
||||
|
||||
export const PaymentTable = pgTable(
|
||||
export const PaymentTable = mysqlTable(
|
||||
"payment",
|
||||
{
|
||||
...workspaceColumns,
|
||||
@@ -28,17 +28,17 @@ export const PaymentTable = pgTable(
|
||||
(table) => [...workspaceIndexes(table)],
|
||||
)
|
||||
|
||||
export const UsageTable = pgTable(
|
||||
export const UsageTable = mysqlTable(
|
||||
"usage",
|
||||
{
|
||||
...workspaceColumns,
|
||||
...timestamps,
|
||||
model: varchar("model", { length: 255 }).notNull(),
|
||||
inputTokens: integer("input_tokens").notNull(),
|
||||
outputTokens: integer("output_tokens").notNull(),
|
||||
reasoningTokens: integer("reasoning_tokens"),
|
||||
cacheReadTokens: integer("cache_read_tokens"),
|
||||
cacheWriteTokens: integer("cache_write_tokens"),
|
||||
inputTokens: int("input_tokens").notNull(),
|
||||
outputTokens: int("output_tokens").notNull(),
|
||||
reasoningTokens: int("reasoning_tokens"),
|
||||
cacheReadTokens: int("cache_read_tokens"),
|
||||
cacheWriteTokens: int("cache_write_tokens"),
|
||||
cost: bigint("cost", { mode: "number" }).notNull(),
|
||||
},
|
||||
(table) => [...workspaceIndexes(table)],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { text, pgTable, varchar, uniqueIndex } from "drizzle-orm/pg-core"
|
||||
import { text, mysqlTable, varchar, uniqueIndex } from "drizzle-orm/mysql-core"
|
||||
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||
import { workspaceIndexes } from "./workspace.sql"
|
||||
|
||||
export const KeyTable = pgTable(
|
||||
export const KeyTable = mysqlTable(
|
||||
"key",
|
||||
{
|
||||
...workspaceColumns,
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { text, pgTable, uniqueIndex, varchar, integer } from "drizzle-orm/pg-core"
|
||||
import { text, mysqlTable, uniqueIndex, varchar, int } from "drizzle-orm/mysql-core"
|
||||
import { timestamps, utc, workspaceColumns } from "../drizzle/types"
|
||||
import { workspaceIndexes } from "./workspace.sql"
|
||||
|
||||
export const UserTable = pgTable(
|
||||
export const UserTable = mysqlTable(
|
||||
"user",
|
||||
{
|
||||
...workspaceColumns,
|
||||
...timestamps,
|
||||
email: text("email").notNull(),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
timeSeen: utc("time_seen"),
|
||||
color: integer("color"),
|
||||
color: int("color"),
|
||||
},
|
||||
(table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { primaryKey, foreignKey, pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core"
|
||||
import { primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||
import { timestamps, ulid } from "../drizzle/types"
|
||||
|
||||
export const WorkspaceTable = pgTable(
|
||||
export const WorkspaceTable = mysqlTable(
|
||||
"workspace",
|
||||
{
|
||||
id: ulid("id").notNull().primaryKey(),
|
||||
@@ -17,9 +17,5 @@ export function workspaceIndexes(table: any) {
|
||||
primaryKey({
|
||||
columns: [table.workspaceID, table.id],
|
||||
}),
|
||||
foreignKey({
|
||||
foreignColumns: [WorkspaceTable.id],
|
||||
columns: [table.workspaceID],
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
export function memo<T>(fn: () => T) {
|
||||
export function memo<T>(fn: () => T, cleanup?: (input: T) => Promise<void>) {
|
||||
let value: T | undefined
|
||||
let loaded = false
|
||||
|
||||
return (): T => {
|
||||
const result = (): T => {
|
||||
if (loaded) return value as T
|
||||
loaded = true
|
||||
value = fn()
|
||||
return value as T
|
||||
}
|
||||
result.reset = async () => {
|
||||
if (cleanup && value) await cleanup(value)
|
||||
loaded = false
|
||||
value = undefined
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user