From 1ad7f7019c0d5a4f56f95c98ea91c3553d3e532f Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Thu, 14 Aug 2025 20:46:38 +0200 Subject: [PATCH] feat(examples): add drizzle example --- examples/drizzle/README.md | 16 +++--- .../drizzle/0000_flippant_bloodstrike.sql | 6 +++ .../drizzle/0000_wonderful_iron_patriot.sql | 8 --- .../drizzle/drizzle/meta/0000_snapshot.json | 30 +++++------ examples/drizzle/drizzle/meta/_journal.json | 11 +--- examples/drizzle/drizzle/migrations.js | 2 +- examples/drizzle/package.json | 7 +-- examples/drizzle/src/db/schema.ts | 15 +++--- examples/drizzle/src/registry.ts | 53 +++++++++++-------- examples/drizzle/src/server.ts | 8 +-- packages/core/src/inspector/actor.ts | 21 ++++---- packages/db/src/config.ts | 14 ++--- packages/db/src/drizzle/mod.ts | 30 +++++++++-- packages/db/src/mod.ts | 28 ++++++++-- packages/db/src/utils.ts | 2 +- pnpm-lock.yaml | 9 ++-- 16 files changed, 148 insertions(+), 112 deletions(-) create mode 100644 examples/drizzle/drizzle/0000_flippant_bloodstrike.sql delete mode 100644 examples/drizzle/drizzle/0000_wonderful_iron_patriot.sql diff --git a/examples/drizzle/README.md b/examples/drizzle/README.md index bb41842a6..7561b114b 100644 --- a/examples/drizzle/README.md +++ b/examples/drizzle/README.md @@ -1,6 +1,6 @@ -# Hono Integration for RivetKit +# Drizzle Integration for RivetKit -Example project demonstrating Hono web framework integration with [RivetKit](https://rivetkit.org). +Example project demonstrating Drizzle ORM integration with [RivetKit](https://rivetkit.org). [Learn More →](https://github.com/rivet-gg/rivetkit) @@ -16,18 +16,16 @@ Example project demonstrating Hono web framework integration with [RivetKit](htt ```sh git clone https://github.com/rivet-gg/rivetkit -cd rivetkit/examples/hono -npm install +cd rivetkit/examples/drizzle +pnpm install ``` ### Development - ```sh -npm run dev +pnpm run dev ``` - -Open your browser to http://localhost:3000 to see the Hono server with RivetKit integration. +Open your browser to https://studio.rivet.gg/ to see your RivetKit server. ## License -Apache 2.0 \ No newline at end of file +Apache 2.0 diff --git a/examples/drizzle/drizzle/0000_flippant_bloodstrike.sql b/examples/drizzle/drizzle/0000_flippant_bloodstrike.sql new file mode 100644 index 000000000..1b77f13e4 --- /dev/null +++ b/examples/drizzle/drizzle/0000_flippant_bloodstrike.sql @@ -0,0 +1,6 @@ +CREATE TABLE `messages_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `sender` text NOT NULL, + `text` text NOT NULL, + `timestamp` integer NOT NULL +); diff --git a/examples/drizzle/drizzle/0000_wonderful_iron_patriot.sql b/examples/drizzle/drizzle/0000_wonderful_iron_patriot.sql deleted file mode 100644 index 2382ea5a2..000000000 --- a/examples/drizzle/drizzle/0000_wonderful_iron_patriot.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE `users_table` ( - `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `name` text NOT NULL, - `age` integer NOT NULL, - `email` text NOT NULL -); ---> statement-breakpoint -CREATE UNIQUE INDEX `users_table_email_unique` ON `users_table` (`email`); \ No newline at end of file diff --git a/examples/drizzle/drizzle/meta/0000_snapshot.json b/examples/drizzle/drizzle/meta/0000_snapshot.json index e55326710..68661a6f5 100644 --- a/examples/drizzle/drizzle/meta/0000_snapshot.json +++ b/examples/drizzle/drizzle/meta/0000_snapshot.json @@ -1,11 +1,11 @@ { "version": "6", "dialect": "sqlite", - "id": "22f3d49c-97d5-46ca-b0f1-99950c3efec7", + "id": "cb13fd0c-ec74-4b9d-b939-8ad091b0dca1", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { - "users_table": { - "name": "users_table", + "messages_table": { + "name": "messages_table", "columns": { "id": { "name": "id", @@ -14,35 +14,29 @@ "notNull": true, "autoincrement": true }, - "name": { - "name": "name", + "sender": { + "name": "sender", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "age": { - "name": "age", - "type": "integer", + "text": { + "name": "text", + "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "email": { - "name": "email", - "type": "text", + "timestamp": { + "name": "timestamp", + "type": "integer", "primaryKey": false, "notNull": true, "autoincrement": false } }, - "indexes": { - "users_table_email_unique": { - "name": "users_table_email_unique", - "columns": ["email"], - "isUnique": true - } - }, + "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {}, diff --git a/examples/drizzle/drizzle/meta/_journal.json b/examples/drizzle/drizzle/meta/_journal.json index e6956f6d8..7f559e26e 100644 --- a/examples/drizzle/drizzle/meta/_journal.json +++ b/examples/drizzle/drizzle/meta/_journal.json @@ -5,15 +5,8 @@ { "idx": 0, "version": "6", - "when": 1750711614205, - "tag": "0000_wonderful_iron_patriot", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1750716663518, - "tag": "0001_rich_susan_delgado", + "when": 1755196233006, + "tag": "0000_flippant_bloodstrike", "breakpoints": true } ] diff --git a/examples/drizzle/drizzle/migrations.js b/examples/drizzle/drizzle/migrations.js index 3da3769a9..bfe41a3ab 100644 --- a/examples/drizzle/drizzle/migrations.js +++ b/examples/drizzle/drizzle/migrations.js @@ -1,4 +1,4 @@ -import m0000 from "./0000_wonderful_iron_patriot.sql"; +import m0000 from "./0000_flippant_bloodstrike.sql"; import journal from "./meta/_journal.json"; export default { diff --git a/examples/drizzle/package.json b/examples/drizzle/package.json index 8c6d5ec4e..0f1755f38 100644 --- a/examples/drizzle/package.json +++ b/examples/drizzle/package.json @@ -1,20 +1,21 @@ { - "name": "example-sqlite", + "name": "example-drizzle", "version": "0.9.9", "private": true, "type": "module", "scripts": { - "dev": "tsx --watch src/server.ts", + "dev": "tsx --loader @rivetkit/sql-loader --watch src/server.ts", "check-types": "tsc --noEmit" }, "devDependencies": { "@types/node": "^22.13.9", - "rivetkit": "workspace:*", + "@rivetkit/sql-loader": "workspace:*", "tsx": "^3.12.7", "typescript": "^5.5.2" }, "dependencies": { "@rivetkit/db": "workspace:*", + "@rivetkit/actor": "workspace:*", "drizzle-kit": "^0.31.2", "drizzle-orm": "^0.44.2" }, diff --git a/examples/drizzle/src/db/schema.ts b/examples/drizzle/src/db/schema.ts index f24058ac8..73c2c91c0 100644 --- a/examples/drizzle/src/db/schema.ts +++ b/examples/drizzle/src/db/schema.ts @@ -1,9 +1,8 @@ -// import { int, sqliteTable, text } from "@rivetkit/db/drizzle"; +import { int, sqliteTable, text } from "@rivetkit/db/drizzle"; -// export const usersTable = sqliteTable("users_table", { -// id: int().primaryKey({ autoIncrement: true }), -// name: text().notNull(), -// age: int().notNull(), -// email: text().notNull().unique(), -// email2: text().notNull().unique(), -// }); +export const messagesTable = sqliteTable("messages_table", { + id: int().primaryKey({ autoIncrement: true }), + sender: text().notNull(), + text: text().notNull(), + timestamp: int().notNull(), +}); diff --git a/examples/drizzle/src/registry.ts b/examples/drizzle/src/registry.ts index f4dce5845..d5ae7b5df 100644 --- a/examples/drizzle/src/registry.ts +++ b/examples/drizzle/src/registry.ts @@ -1,25 +1,32 @@ -// import { actor, setup } from "rivetkit"; -// import { db } from "@rivetkit/db/drizzle"; -// import * as schema from "./db/schema"; -// import migrations from "../drizzle/migrations"; +import { actor, setup } from "@rivetkit/actor"; +import { db } from "@rivetkit/db/drizzle"; +import { desc } from "drizzle-orm"; +import migrations from "../drizzle/migrations"; +import * as schema from "./db/schema"; -// export const counter = actor({ -// db: db({ schema, migrations }), -// state: { -// count: 0, -// }, -// onAuth: () => { -// // Configure auth here -// }, -// actions: { -// increment: (c, x: number) => { -// // createState or state fix fix fix -// c.db.c.state.count += x; -// return c.state.count; -// }, -// }, -// }); +export const chat = actor({ + db: db({ schema, migrations }), + onAuth: () => {}, + actions: { + // Callable functions from clients: https://rivet.gg/docs/actors/actions + sendMessage: async (c, sender: string, text: string) => { + const message = { sender, text, timestamp: Date.now() }; + // State changes are automatically persisted + await c.db.insert(schema.messagesTable).values(message); + // Send events to all connected clients: https://rivet.gg/docs/actors/events + c.broadcast("newMessage", message); + return message; + }, -// export const registry = setup({ -// use: { counter }, -// }); + getHistory: (c) => + c.db + .select() + .from(schema.messagesTable) + .orderBy(desc(schema.messagesTable.timestamp)) + .limit(100), + }, +}); + +export const registry = setup({ + use: { chat }, +}); diff --git a/examples/drizzle/src/server.ts b/examples/drizzle/src/server.ts index 5be165d64..11163905a 100644 --- a/examples/drizzle/src/server.ts +++ b/examples/drizzle/src/server.ts @@ -1,7 +1,3 @@ -// import { registry } from "./registry"; -// import { createMemoryDriver } from "@rivetkit/memory"; -// import { serve } from "@rivetkit/nodejs"; +import { registry } from "./registry"; -// serve(registry, { -// driver: createMemoryDriver(), -// }); +registry.runServer(); diff --git a/packages/core/src/inspector/actor.ts b/packages/core/src/inspector/actor.ts index e202f96fa..d6f20ad2a 100644 --- a/packages/core/src/inspector/actor.ts +++ b/packages/core/src/inspector/actor.ts @@ -181,20 +181,22 @@ export function createActorInspectorRouter() { const db = await c.var.inspector.accessors.getDb(); // Get list of tables - const rows = await db.execute(`PRAGMA table_list`); + const rows = await db.prepare(`PRAGMA table_list`).all(); const tables = TablesSchema.parse(rows).filter( (table) => table.schema !== "temp" && !table.name.startsWith("sqlite_"), ); // Get columns for each table const tablesInfo = await Promise.all( - tables.map((table) => db.execute(`PRAGMA table_info(${table.name})`)), + tables.map((table) => + db.prepare(`PRAGMA table_info(${table.name})`).all(), + ), ); const columns = tablesInfo.map((def) => ColumnsSchema.parse(def)); // Get foreign keys for each table const foreignKeysList = await Promise.all( tables.map((table) => - db.execute(`PRAGMA foreign_key_list(${table.name})`), + db.prepare(`PRAGMA foreign_key_list(${table.name})`).all(), ), ); const foreignKeys = foreignKeysList.map((def) => @@ -204,7 +206,7 @@ export function createActorInspectorRouter() { // Get record counts for each table const countInfo = await Promise.all( tables.map((table) => - db.execute(`SELECT COUNT(*) as count FROM ${table.name}`), + db.prepare(`SELECT COUNT(*) as count FROM ${table.name}`).all(), ), ); const counts = countInfo.map((def) => { @@ -239,13 +241,14 @@ export function createActorInspectorRouter() { const db = await c.var.inspector.accessors.getDb(); try { - const result = (await db.execute( - c.req.valid("json").query, - ...(c.req.valid("json").params || []), - )) as unknown; + const result = (await db + .prepare( + c.req.valid("json").query, + ...(c.req.valid("json").params || []), + ) + .all()) as unknown; return c.json({ result }, 200); } catch (error) { - c; return c.json({ error: (error as Error).message }, 500); } }, diff --git a/packages/db/src/config.ts b/packages/db/src/config.ts index 03dac5f13..e0097d8f8 100644 --- a/packages/db/src/config.ts +++ b/packages/db/src/config.ts @@ -17,14 +17,16 @@ export type DatabaseProvider = { onMigrate: (client: DB) => void | Promise; }; -type ExecuteFunction = ( - query: string, - ...args: unknown[] -) => Promise; +type PrepareFunction = (query: string, ...args: unknown[]) => Statement; + +type Statement = { + all(...args: unknown[]): Promise; + run(...args: unknown[]): Promise; +}; export type RawAccess = { /** - * Executes a raw SQL query. + * Prepares a raw SQL query. */ - execute: ExecuteFunction; + prepare: PrepareFunction; }; diff --git a/packages/db/src/drizzle/mod.ts b/packages/db/src/drizzle/mod.ts index 14fc4f722..bbbf1c53b 100644 --- a/packages/db/src/drizzle/mod.ts +++ b/packages/db/src/drizzle/mod.ts @@ -60,8 +60,20 @@ export function db< const client = durableDrizzle(conn, config); return Object.assign(client, { // client.$client.exec is the underlying SQLite client - execute: async (query, ...args) => - client.$client.exec(query, ...args), + prepare: (query, ...args) => { + return { + all: async (...innerArgs) => + client.$client.exec( + query, + ...(innerArgs.length > 0 ? innerArgs : args), + ) as Promise, + run: async (...innerArgs) => + client.$client.exec( + query, + ...(innerArgs.length > 0 ? innerArgs : args), + ) as Promise, + }; + }, } satisfies RawAccess); } @@ -72,8 +84,18 @@ export function db< }); return Object.assign(client, { - execute: async (query, ...args) => - client.$client.prepare(query).all(...args), + prepare: (query, ...args) => { + return { + all: async (...innerArgs) => + client.$client + .prepare(query) + .all(...(innerArgs.length > 0 ? innerArgs : args)), + run: async (...innerArgs) => + client.$client + .prepare(query) + .run(...(innerArgs.length > 0 ? innerArgs : args)), + }; + }, } satisfies RawAccess); }, onMigrate: async (client) => { diff --git a/packages/db/src/mod.ts b/packages/db/src/mod.ts index 095e45c77..57f2bffbd 100644 --- a/packages/db/src/mod.ts +++ b/packages/db/src/mod.ts @@ -28,16 +28,36 @@ export function db({ // If the connection is already an object with exec method, return it // i.e. in serverless environments (Cloudflare Workers) return Object.assign({}, conn, { - execute: async (query, ...args) => { - return conn.exec(query, ...args); + prepare: (query, ...args) => { + return { + all: (...innerArgs) => + conn.exec( + query, + ...(innerArgs.length > 0 ? innerArgs : args), + ) as Promise, + run: (...innerArgs) => + conn.exec( + query, + ...(innerArgs.length > 0 ? innerArgs : args), + ) as Promise, + }; }, } satisfies RawAccess) as SQLiteShim & RawAccess; } const client = new SQLite(conn as string); return Object.assign({}, client, { - execute: async (query, ...args) => { - return client.prepare(query).all(...args); + prepare: (query, ...args) => { + return { + all: async (...innerArgs) => + client + .prepare(query) + .all(...(innerArgs.length > 0 ? innerArgs : args)), + run: async (...innerArgs) => + client + .prepare(query) + .run(...(innerArgs.length > 0 ? innerArgs : args)), + }; }, } satisfies RawAccess) as RawAccess; }, diff --git a/packages/db/src/utils.ts b/packages/db/src/utils.ts index 82daaf759..fd6e99bc2 100644 --- a/packages/db/src/utils.ts +++ b/packages/db/src/utils.ts @@ -4,7 +4,7 @@ * running raw SQL commands. */ export type SQLiteShim = { - exec: (query: string, ...args: unknown[]) => unknown[]; + exec: (query: string, ...args: unknown[]) => Promise; }; export function isSQLiteShim(conn: unknown): conn is SQLiteShim & T { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 347e50710..d497338fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -374,6 +374,9 @@ importers: examples/drizzle: dependencies: + '@rivetkit/actor': + specifier: workspace:* + version: link:../../packages/actor '@rivetkit/db': specifier: workspace:* version: link:../../packages/db @@ -384,12 +387,12 @@ importers: specifier: ^0.44.2 version: 0.44.2(@cloudflare/workers-types@4.20250619.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@11.10.0)(kysely@0.28.2) devDependencies: + '@rivetkit/sql-loader': + specifier: workspace:* + version: link:../../packages/misc/sql-loader '@types/node': specifier: ^22.13.9 version: 22.15.32 - rivetkit: - specifier: workspace:* - version: link:../../packages/rivetkit tsx: specifier: ^3.12.7 version: 3.14.0