Skip to content

Instantly share code, notes, and snippets.

@simonbengtsson
Last active May 30, 2025 05:32
Show Gist options
  • Save simonbengtsson/8308f494805e3c9e5dd7ab65ec6b022b to your computer and use it in GitHub Desktop.
Save simonbengtsson/8308f494805e3c9e5dd7ab65ec6b022b to your computer and use it in GitHub Desktop.
D1 wrapper with kv+ api
export type Document = z.infer<typeof Document>
export const Document = z.object({
id: z.string(),
})
export type User = z.infer<typeof User>
export const User = Document.extend({
name: z.string(),
email: z.email(),
})
export const getUsersStore = function (db: D1Database) {
return getCollection<User>(db, {
schema: User,
tableName: "users",
})
}
export async function getCollection<T extends Document>(
db: D1Database,
options: {
schema: z.ZodSchema<T>
tableName: string
},
) {
const sql = /*sql*/ `CREATE TABLE IF NOT EXISTS ${options.tableName} (key TEXT PRIMARY KEY, value JSON NOT NULL);`
await db.exec(sql)
async function get(key: string): Promise<T | null> {
const result = await db
.prepare(/*sql*/ `SELECT value FROM ${options.tableName} WHERE key = ?`)
.bind(key)
.first<{ value: string }>()
const raw = result?.value
if (!raw) return null
return parseDocument(raw)
}
async function set(document: T) {
await db
.prepare(
/*sql*/ `INSERT INTO ${options.tableName} (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
)
.bind(document.id, JSON.stringify(document))
.run()
}
async function del(key: string) {
await db
.prepare(/*sql*/ `DELETE FROM ${options.tableName} WHERE key = ?`)
.bind(key)
.run()
}
async function list(): Promise<T[]> {
const result = await db
.prepare(/*sql*/ `SELECT value FROM ${options.tableName}`)
.all()
return result.results.map((row) => parseDocument(row.value))
}
async function listWhere(
field: string,
operator: string,
value: any,
): Promise<T[]> {
field = field.replace(/[^a-zA-Z0-9_]/g, "")
const supportedSqliteOperators = ["=", ">", "<", ">=", "<=", "!="]
if (!supportedSqliteOperators.includes(operator)) {
throw new Error(`Unsupported operator: ${operator}`)
}
const sql = /* sql */ `
SELECT
value,
json_extract(value, '$.${field}') AS operation
FROM ${options.tableName}
WHERE operation ${operator} ?
`
const result = await db.prepare(sql).bind(value).all()
return result.results.map((row) => parseDocument(row.value))
}
function parseDocument(raw: any): T {
return options.schema.parse(JSON.parse(raw))
}
return { get, set, del, list, listWhere }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment