From 9d99a620a181c30d59774ca8b479f5a688ecf7c6 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sat, 16 Mar 2024 15:08:58 +0100 Subject: [PATCH 01/21] crsqlite drizzle adapter --- client/src/components/drizzle/Test.tsx | 24 ++++ .../src/components/drizzle/crsqlite/driver.ts | 44 +++++++ .../src/components/drizzle/crsqlite/index.ts | 2 + .../components/drizzle/crsqlite/session.ts | 124 ++++++++++++++++++ client/src/components/drizzle/schema.ts | 30 +++++ package.json | 1 + pnpm-lock.yaml | 90 ++++++++++++- 7 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 client/src/components/drizzle/Test.tsx create mode 100644 client/src/components/drizzle/crsqlite/driver.ts create mode 100644 client/src/components/drizzle/crsqlite/index.ts create mode 100644 client/src/components/drizzle/crsqlite/session.ts create mode 100644 client/src/components/drizzle/schema.ts diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx new file mode 100644 index 0000000..5e9da19 --- /dev/null +++ b/client/src/components/drizzle/Test.tsx @@ -0,0 +1,24 @@ +import type { DB } from "@vlcn.io/crsqlite-wasm" +import { drizzle } from "./crsqlite" +import type * as schema from "./schema" + +export function makeDrizzleDb(db: DB) { + return drizzle(db) +} + +const db = makeDrizzleDb({} as DB) + +const res = db.query.countries + .findMany({ + with: { + cities: { + where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), + }, + }, + }) + .prepare() + +const data = await res.all({ cityName: "New York" }) +// ^? + +console.log(data) diff --git a/client/src/components/drizzle/crsqlite/driver.ts b/client/src/components/drizzle/crsqlite/driver.ts new file mode 100644 index 0000000..48274f9 --- /dev/null +++ b/client/src/components/drizzle/crsqlite/driver.ts @@ -0,0 +1,44 @@ +import { DefaultLogger } from "drizzle-orm/logger" +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from "drizzle-orm/relations" +import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core" +import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core" +import type { DrizzleConfig } from "drizzle-orm/utils" +import { CRSQLiteSession } from "./session" +import type { DB } from "@vlcn.io/crsqlite-wasm" + +export type CRSQLite3Database = Record> = + BaseSQLiteDatabase<"async", void, TSchema> + +export function drizzle = Record>( + client: DB, + config: DrizzleConfig = {} +): CRSQLite3Database { + const dialect = new SQLiteAsyncDialect() + let logger + if (config.logger === true) { + logger = new DefaultLogger() + } else if (config.logger !== false) { + logger = config.logger + } + + let schema: RelationalSchemaConfig | undefined + if (config.schema) { + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers + ) + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + } + } + + const session = new CRSQLiteSession(client, dialect, schema, { logger }) + return new BaseSQLiteDatabase("async", dialect, session, schema) as CRSQLite3Database +} diff --git a/client/src/components/drizzle/crsqlite/index.ts b/client/src/components/drizzle/crsqlite/index.ts new file mode 100644 index 0000000..4c057a6 --- /dev/null +++ b/client/src/components/drizzle/crsqlite/index.ts @@ -0,0 +1,2 @@ +export * from "./driver" +export * from "./session" diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts new file mode 100644 index 0000000..a6330fe --- /dev/null +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -0,0 +1,124 @@ +// import type { Client, InArgs, InStatement, ResultSet, Transaction } from '@libsql/client'; +import type { SQLite3, DB } from "@vlcn.io/crsqlite-wasm" +import type { BatchItem as BatchItem } from "drizzle-orm/batch" +import { entityKind } from "drizzle-orm/entity" +import type { Logger } from "drizzle-orm/logger" +import { NoopLogger } from "drizzle-orm/logger" +import type { RelationalSchemaConfig, TablesRelationalConfig } from "drizzle-orm/relations" +import type { PreparedQuery } from "drizzle-orm/session" +import { fillPlaceholders, type Query, sql } from "drizzle-orm/sql/sql" +import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" +import { type SQLiteTransaction } from "drizzle-orm/sqlite-core" +import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" +import type { + PreparedQueryConfig, + SQLiteExecuteMethod, + SQLiteTransactionConfig, +} from "drizzle-orm/sqlite-core/session" +import { SQLitePreparedQuery, SQLiteSession } from "drizzle-orm/sqlite-core/session" +// import { mapResultRow } from 'drizzle-orm/utils'; + +interface CRSQLiteSessionOptions { + logger?: Logger +} + +export class CRSQLiteSession< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SQLiteSession<"async", void, TFullSchema, TSchema> { + static readonly [entityKind]: string = "CRSQLiteSession" + + private logger: Logger + + constructor( + private client: DB, + dialect: SQLiteAsyncDialect, + private schema: RelationalSchemaConfig | undefined, + private options: CRSQLiteSessionOptions + // private tx: Transaction | undefined, + ) { + super(dialect) + this.logger = options.logger ?? new NoopLogger() + } + + prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod, + customResultMapper?: (rows: unknown[][]) => unknown + ): CRSQLPreparedQuery { + return new CRSQLPreparedQuery( + this.client, + query, + this.logger, + fields, + // this.tx, + executeMethod, + customResultMapper + ) + } +} + +type StmtAsync = Awaited> + +export class CRSQLPreparedQuery< + T extends PreparedQueryConfig = PreparedQueryConfig, +> extends SQLitePreparedQuery<{ + type: "async" + run: void + all: T["all"] + get: T["get"] + values: T["values"] + execute: T["execute"] +}> { + static readonly [entityKind]: string = "CRSQLPreparedQuery" + + private stmt: Promise + + constructor( + private client: DB, + query: Query, + private logger: Logger, + fields: SelectedFieldsOrdered | undefined, + // private tx: + // | SQLiteTransaction<"async", void, Record, TablesRelationalConfig> + // | undefined, + executeMethod: SQLiteExecuteMethod, + private customResultMapper?: (rows: unknown[][]) => unknown + ) { + super("async", executeMethod, query) + this.stmt = this.client.prepare(query.sql) + } + + async run(placeholderValues?: Record): Promise { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) + this.logger.logQuery(this.query.sql, params) + const stmt = await this.stmt + await stmt.run(null, ...params) + } + + async all(placeholderValues?: Record): Promise { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) + this.logger.logQuery(this.query.sql, params) + const stmt = await this.stmt + const rows = await stmt.all(null, ...params) + return this.customResultMapper ? this.customResultMapper(rows) : rows + } + + async get(placeholderValues?: Record): Promise { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) + this.logger.logQuery(this.query.sql, params) + const stmt = await this.stmt + const row = await stmt.get(null, ...params) + return this.customResultMapper ? this.customResultMapper([row]) : row + } + + async values(placeholderValues?: Record): Promise { + const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) + this.logger.logQuery(this.query.sql, params) + const stmt = await this.stmt + const rows = await stmt.all(null, ...params) + // wtf is `.values` supposed to do? + return rows.map((row) => Object.values(row)[0]) + } +} diff --git a/client/src/components/drizzle/schema.ts b/client/src/components/drizzle/schema.ts new file mode 100644 index 0000000..b3ffa63 --- /dev/null +++ b/client/src/components/drizzle/schema.ts @@ -0,0 +1,30 @@ +import { sqliteTable, text, integer, uniqueIndex } from "drizzle-orm/sqlite-core" +import { relations } from "drizzle-orm" + +export const countries = sqliteTable( + "countries", + { + id: integer("id").primaryKey(), + name: text("name"), + }, + (countries) => ({ + nameIdx: uniqueIndex("nameIdx").on(countries.name), + }) +) + +export const countriesRelations = relations(countries, ({ many }) => ({ + cities: many(cities), +})) + +export const cities = sqliteTable("cities", { + id: integer("id").primaryKey(), + name: text("name"), + countryId: integer("country_id").references(() => countries.id), +}) + +export const citiesRelations = relations(cities, ({ one }) => ({ + country: one(countries, { + fields: [cities.countryId], + references: [countries.id], + }), +})) diff --git a/package.json b/package.json index da02fb0..22380ce 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "better-sqlite3": "^9.4.3", "clsx": "^2.1.0", "dotenv": "^16.4.5", + "drizzle-orm": "^0.30.2", "fastify": "^4.26.2", "grant": "^5.4.22", "pino-pretty": "10.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d93191..8ef2596 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.5 + drizzle-orm: + specifier: ^0.30.2 + version: 0.30.2(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) fastify: specifier: ^4.26.2 version: 4.26.2 @@ -1672,7 +1675,6 @@ packages: resolution: {integrity: sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==} dependencies: '@types/node': 20.11.25 - dev: true /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1686,7 +1688,6 @@ packages: resolution: {integrity: sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==} dependencies: undici-types: 5.26.5 - dev: true /@types/picomatch@2.3.3: resolution: {integrity: sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==} @@ -1694,7 +1695,6 @@ packages: /@types/prop-types@15.7.11: resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} - dev: true /@types/react-dom@18.2.21: resolution: {integrity: sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==} @@ -1708,11 +1708,9 @@ packages: '@types/prop-types': 15.7.11 '@types/scheduler': 0.16.8 csstype: 3.1.3 - dev: true /@types/scheduler@0.16.8: resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} - dev: true /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} @@ -2602,7 +2600,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: true /data-uri-to-buffer@3.0.1: resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} @@ -2714,6 +2711,86 @@ packages: engines: {node: '>=12'} dev: false + /drizzle-orm@0.30.2(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0): + resolution: {integrity: sha512-DNd3djg03o+WxZX3pGD8YD+qrWT8gbrbhaZ2W0PVb6yH4rtM/VTB92cTGvumcRh7SSd2KfV0NWYDB70BHIXQTg==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/react': '>=18' + '@types/sql.js': '*' + '@vercel/postgres': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=13.2.0' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + react: '>=18' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/react': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dependencies: + '@types/better-sqlite3': 7.6.9 + '@types/react': 18.2.64 + better-sqlite3: 9.4.3 + react: 18.2.0 + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5290,7 +5367,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true /undici@5.28.2: resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==} From 9e2c794b7d7a0ae26cd2f9336467f2ad97dc85d2 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 13:23:58 +0100 Subject: [PATCH 02/21] need to pass schema as runtime arg, not just TS arg --- client/src/components/drizzle/Test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 5e9da19..3d9f279 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -1,9 +1,9 @@ import type { DB } from "@vlcn.io/crsqlite-wasm" import { drizzle } from "./crsqlite" -import type * as schema from "./schema" +import * as schema from "./schema" export function makeDrizzleDb(db: DB) { - return drizzle(db) + return drizzle(db, { schema }) } const db = makeDrizzleDb({} as DB) From 1d6af2f1411b0792a6ebb6c70759850cbd329fee Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 20:16:44 +0100 Subject: [PATCH 03/21] what a mess --- client/src/App.tsx | 4 + client/src/components/drizzle/Test.tsx | 83 ++- .../components/drizzle/crsqlite/migrator.ts | 39 ++ .../components/drizzle/crsqlite/session.ts | 107 +++- drizzle.config.ts | 9 + package.json | 4 +- pnpm-lock.yaml | 520 ++++++++++++++++++ .../0000_nervous_black_queen.sql | 13 + shared/src/drizzle-migrations/index.ts | 5 + .../meta/0000_snapshot.json | 89 +++ .../src/drizzle-migrations/meta/_journal.json | 13 + .../src/drizzle-test}/schema.ts | 0 tsconfig.tools.json | 2 +- 13 files changed, 859 insertions(+), 29 deletions(-) create mode 100644 client/src/components/drizzle/crsqlite/migrator.ts create mode 100644 drizzle.config.ts create mode 100644 shared/src/drizzle-migrations/0000_nervous_black_queen.sql create mode 100644 shared/src/drizzle-migrations/index.ts create mode 100644 shared/src/drizzle-migrations/meta/0000_snapshot.json create mode 100644 shared/src/drizzle-migrations/meta/_journal.json rename {client/src/components/drizzle => shared/src/drizzle-test}/schema.ts (100%) diff --git a/client/src/App.tsx b/client/src/App.tsx index 26ad6e6..86f3203 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -7,6 +7,7 @@ import { WorkerDemo } from "client/components/WorkerDemo" import { DbDemo } from "client/components/DbDemo" import { Hero } from "client/components/Hero/Hero" import styles from "client/App.module.css" +import { DrizzleTest } from "client/components/drizzle/Test" fooBar() @@ -37,6 +38,9 @@ export default function App() { + + + ) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 3d9f279..711fa97 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -1,24 +1,73 @@ import type { DB } from "@vlcn.io/crsqlite-wasm" import { drizzle } from "./crsqlite" -import * as schema from "./schema" +import * as schema from "../../../../shared/src/drizzle-test/schema" +import type { Dialect, SQL } from "drizzle-orm" +import { useEffect } from "react" +import initWasm from "@vlcn.io/crsqlite-wasm" +import tblrx from "@vlcn.io/rx-tbl" -export function makeDrizzleDb(db: DB) { - return drizzle(db, { schema }) -} +// export function makeDrizzleDb(db: DB) { +// return drizzle(db, { schema }) +// } + +// const db = makeDrizzleDb({} as DB) + +// const res = db.query.countries +// .findMany({ +// with: { +// cities: { +// where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), +// }, +// }, +// }) +// .prepare() + +// type foo = keyof typeof res & {} & string +// // ^? + +// res.finalize() -const db = makeDrizzleDb({} as DB) +// const data = await res.all({ cityName: "New York" }) +// // ^? -const res = db.query.countries - .findMany({ - with: { - cities: { - where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), - }, - }, - }) - .prepare() +// console.log(data) -const data = await res.all({ cityName: "New York" }) -// ^? +import { migrate } from "./crsqlite/migrator" +import { useQueryClient } from "@tanstack/react-query" -console.log(data) +async function make() { + const sqlite = await initWasm() + const sql = await sqlite.open("test") + const db = drizzle(sql, { schema }) + await migrate(db, { migrationsFolder: "drizzle" }) + const rx = tblrx(sql) + return { db, rx } +} + +export function DrizzleTest() { + const client = useQueryClient() + useEffect(() => { + const key = "test" + make() + .then((db) => { + client.setQueryData(key, { + // schema: cleanSchema, + // schemaName, + name: "test", + db, + }) + }) + .catch(console.error) + }, []) + return ( +
+ Drizzle +
+ +
+ ) +} + +function TestChild() { + return
Test
+} diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts new file mode 100644 index 0000000..995c0be --- /dev/null +++ b/client/src/components/drizzle/crsqlite/migrator.ts @@ -0,0 +1,39 @@ +import type { MigrationConfig, MigrationMeta } from "drizzle-orm/migrator" +import type { CRSQLite3Database } from "./driver" +import migrationJournal from "shared/drizzle-migrations/meta/_journal.json" +import { migrations } from "shared/drizzle-migrations/index" + +export async function migrate>( + db: CRSQLite3Database, + config: string | MigrationConfig +) { + const migrations = await getMigrations() + db.dialect.migrate(migrations, db.session, config) +} + +export async function getMigrations() { + const journal = migrationJournal as { + entries: Array<{ idx: number; when: number; tag: string; breakpoints: boolean }> + } + const migrationQueries: MigrationMeta[] = [] + for (const journalEntry of journal.entries) { + const query = migrations[journalEntry.tag as keyof typeof migrations] + const result = query.split("--> statement-breakpoint") + migrationQueries.push({ + sql: result, + bps: journalEntry.breakpoints, + folderMillis: journalEntry.when, + hash: await createSha256Hash(query), + }) + } + return migrationQueries +} + +async function createSha256Hash(query: string) { + const encoder = new TextEncoder() + const data = encoder.encode(query) + const hash = await window.crypto.subtle.digest("SHA-256", data) + const hashArray = Array.from(new Uint8Array(hash)) + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("") + return hashHex +} diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index a6330fe..599ba9d 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -6,9 +6,9 @@ import type { Logger } from "drizzle-orm/logger" import { NoopLogger } from "drizzle-orm/logger" import type { RelationalSchemaConfig, TablesRelationalConfig } from "drizzle-orm/relations" import type { PreparedQuery } from "drizzle-orm/session" -import { fillPlaceholders, type Query, sql } from "drizzle-orm/sql/sql" +import { fillPlaceholders, type Query, sql, type SQL } from "drizzle-orm/sql/sql" import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" -import { type SQLiteTransaction } from "drizzle-orm/sqlite-core" +import { SQLiteTransaction } from "drizzle-orm/sqlite-core" import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" import type { PreparedQueryConfig, @@ -16,8 +16,11 @@ import type { SQLiteTransactionConfig, } from "drizzle-orm/sqlite-core/session" import { SQLitePreparedQuery, SQLiteSession } from "drizzle-orm/sqlite-core/session" +import type { Dialect } from "drizzle-orm" // import { mapResultRow } from 'drizzle-orm/utils'; +type Transaction = Awaited> + interface CRSQLiteSessionOptions { logger?: Logger } @@ -32,13 +35,14 @@ export class CRSQLiteSession< constructor( private client: DB, - dialect: SQLiteAsyncDialect, + private dialect: SQLiteAsyncDialect, private schema: RelationalSchemaConfig | undefined, - private options: CRSQLiteSessionOptions - // private tx: Transaction | undefined, + private options: CRSQLiteSessionOptions, + private tx?: Transaction | undefined ) { super(dialect) this.logger = options.logger ?? new NoopLogger() + console.log("CRSQLiteSession.constructor") } prepareQuery( @@ -47,20 +51,57 @@ export class CRSQLiteSession< executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown ): CRSQLPreparedQuery { + console.log("CRSQLiteSession.prepareQuery") return new CRSQLPreparedQuery( this.client, query, this.logger, fields, - // this.tx, + this.tx, executeMethod, customResultMapper ) } + + override async transaction( + transaction: (db: CRSQLTransaction) => T | Promise, + _config?: SQLiteTransactionConfig + ): Promise { + console.log("CRSQLiteSession.transaction") + const crsqliteTx = await this.client.imperativeTx() + const session = new CRSQLiteSession( + this.client, + this.dialect, + this.schema, + this.options, + crsqliteTx + ) + const tx = new CRSQLTransaction("async", this.dialect, session, this.schema) + try { + const result = await transaction(tx) + crsqliteTx[0]() + return result + } catch (err) { + crsqliteTx[0]() + throw err + } + } + + // run(query: SQL): Promise { + // console.log("CRSQLiteSession.run") + // return this.client.exec(query.) + + // } } type StmtAsync = Awaited> +// declare module "drizzle-orm/sqlite-core/session" { +// export interface PreparedQuery { +// finalize(): Promise +// } +// } + export class CRSQLPreparedQuery< T extends PreparedQueryConfig = PreparedQueryConfig, > extends SQLitePreparedQuery<{ @@ -80,9 +121,7 @@ export class CRSQLPreparedQuery< query: Query, private logger: Logger, fields: SelectedFieldsOrdered | undefined, - // private tx: - // | SQLiteTransaction<"async", void, Record, TablesRelationalConfig> - // | undefined, + private tx: Transaction | undefined, executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][]) => unknown ) { @@ -90,6 +129,9 @@ export class CRSQLPreparedQuery< this.stmt = this.client.prepare(query.sql) } + /** + * execute query, no result expected + */ async run(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) @@ -97,6 +139,9 @@ export class CRSQLPreparedQuery< await stmt.run(null, ...params) } + /** + * execute query and return all rows + */ async all(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) @@ -105,6 +150,9 @@ export class CRSQLPreparedQuery< return this.customResultMapper ? this.customResultMapper(rows) : rows } + /** + * only query first row + */ async get(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) @@ -113,12 +161,51 @@ export class CRSQLPreparedQuery< return this.customResultMapper ? this.customResultMapper([row]) : row } + /** + * directly extract first column value from each row + */ async values(placeholderValues?: Record): Promise { const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt const rows = await stmt.all(null, ...params) - // wtf is `.values` supposed to do? return rows.map((row) => Object.values(row)[0]) } + + async finalize(): Promise { + const stmt = await this.stmt + await stmt.finalize(null) + } +} + +export class CRSQLTransaction< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SQLiteTransaction<"async", void, TFullSchema, TSchema> { + static readonly [entityKind]: string = "CRSQLTransaction" + + private dialect: SQLiteAsyncDialect + private session: CRSQLiteSession + + override async transaction( + transaction: (tx: CRSQLTransaction) => Promise + ): Promise { + const savepointName = `sp${this.nestedIndex}` + const tx = new CRSQLTransaction( + "async", + this.dialect, + this.session, + this.schema, + this.nestedIndex + 1 + ) + await this.session.run(sql.raw(`SAVEPOINT ${savepointName}`)) + try { + const result = await transaction(tx) + await this.session.run(sql.raw(`RELEASE savepoint ${savepointName}`)) + return result + } catch (err) { + await this.session.run(sql.raw(`ROLLBACK TO savepoint ${savepointName}`)) + throw err + } + } } diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..1a9f0d4 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "drizzle-kit" + +export default defineConfig({ + schema: "./shared/src/drizzle-test/schema.ts", + driver: "better-sqlite", + out: "./shared/src/drizzle-migrations", + verbose: true, + strict: true, +}) diff --git a/package.json b/package.json index 22380ce..147767f 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "root:format": "prettier --check '*.ts' '*.json' 'types/*' '.github/**/*' --config .prettierrc.json --log-level warn", "root:format:fix": "prettier --write '*.ts' '*.json' 'types/*' '.github/**/*' --config .prettierrc.json --log-level warn", "root:spell": "cspell --config .cspell.json --quiet --gitignore '*' 'types/*' '.github/**/*'", - "root:clear": "rm -rf dist; rm -rf node_modules; rm -rf .turbo; find . -name '*.tsbuildinfo' -type f -delete;" + "root:clear": "rm -rf dist; rm -rf node_modules; rm -rf .turbo; find . -name '*.tsbuildinfo' -type f -delete;", + "generate": "drizzle-kit generate:sqlite" }, "dependencies": { "@fastify/cookie": "^9.3.1", @@ -70,6 +71,7 @@ "chalk": "^5.3.0", "chokidar": "3.6.0", "cspell": "^8.6.0", + "drizzle-kit": "^0.20.14", "esbuild": "0.20.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ef2596..8fd48ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: cspell: specifier: ^8.6.0 version: 8.6.0 + drizzle-kit: + specifier: ^0.20.14 + version: 0.20.14 esbuild: specifier: 0.20.1 version: 0.20.1 @@ -559,6 +562,12 @@ packages: engines: {node: '>=18'} dev: true + /@drizzle-team/studio@0.0.39: + resolution: {integrity: sha512-c5Hkm7MmQC2n5qAsKShjQrHoqlfGslB8+qWzsGGZ+2dHMRTNG60UuzalF0h0rvBax5uzPXuGkYLGaQ+TUX3yMw==} + dependencies: + superjson: 2.2.1 + dev: true + /@ericcornelissen/bash-parser@0.5.2: resolution: {integrity: sha512-4pIMTa1nEFfMXitv7oaNEWOdM+zpOZavesa5GaiWTgda6Zk32CFGxjUp/iIaN0PwgUW1yTq/fztSjbpE8SLGZQ==} engines: {node: '>=4'} @@ -584,6 +593,20 @@ packages: unescape-js: 1.1.4 dev: true + /@esbuild-kit/core-utils@3.3.2: + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + dev: true + + /@esbuild-kit/esm-loader@2.6.5: + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.7.2 + dev: true + /@esbuild/aix-ppc64@0.19.12: resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} @@ -602,6 +625,15 @@ packages: dev: true optional: true + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm64@0.19.12: resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -620,6 +652,15 @@ packages: dev: true optional: true + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-arm@0.19.12: resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -638,6 +679,15 @@ packages: dev: true optional: true + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/android-x64@0.19.12: resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -656,6 +706,15 @@ packages: dev: true optional: true + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-arm64@0.19.12: resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -674,6 +733,15 @@ packages: dev: true optional: true + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /@esbuild/darwin-x64@0.19.12: resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -692,6 +760,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-arm64@0.19.12: resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -710,6 +787,15 @@ packages: dev: true optional: true + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/freebsd-x64@0.19.12: resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -728,6 +814,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm64@0.19.12: resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -746,6 +841,15 @@ packages: dev: true optional: true + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-arm@0.19.12: resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -764,6 +868,15 @@ packages: dev: true optional: true + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ia32@0.19.12: resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -782,6 +895,15 @@ packages: dev: true optional: true + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64@0.19.12: resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -800,6 +922,15 @@ packages: dev: true optional: true + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-mips64el@0.19.12: resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -818,6 +949,15 @@ packages: dev: true optional: true + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-ppc64@0.19.12: resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -836,6 +976,15 @@ packages: dev: true optional: true + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-riscv64@0.19.12: resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -854,6 +1003,15 @@ packages: dev: true optional: true + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-s390x@0.19.12: resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -872,6 +1030,15 @@ packages: dev: true optional: true + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-x64@0.19.12: resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -890,6 +1057,15 @@ packages: dev: true optional: true + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/netbsd-x64@0.19.12: resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -908,6 +1084,15 @@ packages: dev: true optional: true + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/openbsd-x64@0.19.12: resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -926,6 +1111,15 @@ packages: dev: true optional: true + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + /@esbuild/sunos-x64@0.19.12: resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -944,6 +1138,15 @@ packages: dev: true optional: true + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-arm64@0.19.12: resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -962,6 +1165,15 @@ packages: dev: true optional: true + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-ia32@0.19.12: resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -980,6 +1192,15 @@ packages: dev: true optional: true + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@esbuild/win32-x64@0.19.12: resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -2225,6 +2446,10 @@ packages: requiresBuild: true dev: false + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: @@ -2268,6 +2493,11 @@ packages: engines: {node: '>=10'} dev: true + /camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: true + /chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} engines: {node: '>=4'} @@ -2348,6 +2578,17 @@ packages: resolve-from: 5.0.0 dev: true + /cli-color@2.0.4: + resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} + engines: {node: '>=0.10'} + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + memoizee: 0.4.15 + timers-ext: 0.1.7 + dev: true + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2406,6 +2647,11 @@ packages: engines: {node: '>= 6'} dev: true + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /comment-json@4.2.3: resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} engines: {node: '>= 6'} @@ -2455,6 +2701,13 @@ packages: engines: {node: '>= 0.6'} dev: false + /copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.16 + dev: true + /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true @@ -2601,6 +2854,14 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + /d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + dependencies: + es5-ext: 0.10.64 + type: 2.7.2 + dev: true + /data-uri-to-buffer@3.0.1: resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} engines: {node: '>= 6'} @@ -2685,6 +2946,12 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /difflib@0.2.4: + resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} + dependencies: + heap: 0.2.7 + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2711,6 +2978,35 @@ packages: engines: {node: '>=12'} dev: false + /dreamopt@0.8.0: + resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} + engines: {node: '>=0.4.0'} + dependencies: + wordwrap: 1.0.0 + dev: true + + /drizzle-kit@0.20.14: + resolution: {integrity: sha512-0fHv3YIEaUcSVPSGyaaBfOi9bmpajjhbJNdPsRMIUvYdLVxBu9eGjH8mRc3Qk7HVmEidFc/lhG1YyJhoXrn5yA==} + hasBin: true + dependencies: + '@drizzle-team/studio': 0.0.39 + '@esbuild-kit/esm-loader': 2.6.5 + camelcase: 7.0.1 + chalk: 5.3.0 + commander: 9.5.0 + env-paths: 3.0.0 + esbuild: 0.19.12 + esbuild-register: 3.5.0(esbuild@0.19.12) + glob: 8.1.0 + hanji: 0.0.5 + json-diff: 0.9.0 + minimatch: 7.4.6 + semver: 7.6.0 + zod: 3.22.4 + transitivePeerDependencies: + - supports-color + dev: true + /drizzle-orm@0.30.2(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0): resolution: {integrity: sha512-DNd3djg03o+WxZX3pGD8YD+qrWT8gbrbhaZ2W0PVb6yH4rtM/VTB92cTGvumcRh7SSd2KfV0NWYDB70BHIXQTg==} peerDependencies: @@ -2841,6 +3137,11 @@ packages: dependencies: once: 1.4.0 + /env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} dev: true @@ -2851,6 +3152,83 @@ packages: is-arrayish: 0.2.1 dev: true + /es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + dev: true + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + dev: true + + /es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + dependencies: + d: 1.0.2 + ext: 1.7.0 + dev: true + + /es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + dev: true + + /esbuild-register@3.5.0(esbuild@0.19.12): + resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} + peerDependencies: + esbuild: '>=0.12 <1' + dependencies: + debug: 4.3.4 + esbuild: 0.19.12 + transitivePeerDependencies: + - supports-color + dev: true + + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + dev: true + /esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} @@ -3009,6 +3387,16 @@ packages: - supports-color dev: true + /esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.2 + dev: true + /espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3054,6 +3442,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + dev: true + /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -3084,6 +3479,12 @@ packages: engines: {node: '>=6'} dev: false + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: true + /fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} @@ -3394,6 +3795,17 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + /global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -3448,6 +3860,13 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /hanji@0.0.5: + resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} + dependencies: + lodash.throttle: 4.1.1 + sisteransi: 1.0.5 + dev: true + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -3498,6 +3917,10 @@ packages: dependencies: function-bind: 1.1.2 + /heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + dev: true + /help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} dev: false @@ -3697,6 +4120,10 @@ packages: engines: {node: '>=8'} dev: true + /is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + dev: true + /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3710,6 +4137,11 @@ packages: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} dev: true + /is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + dev: true + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -3770,6 +4202,15 @@ packages: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true + /json-diff@0.9.0: + resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} + hasBin: true + dependencies: + cli-color: 2.0.4 + difflib: 0.2.4 + dreamopt: 0.8.0 + dev: true + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -3954,6 +4395,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + dev: true + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -3977,6 +4422,12 @@ packages: dependencies: yallist: 4.0.0 + /lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + dependencies: + es5-ext: 0.10.64 + dev: true + /magic-string@0.16.0: resolution: {integrity: sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==} dependencies: @@ -4018,6 +4469,19 @@ packages: mimic-fn: 3.1.0 dev: true + /memoizee@0.4.15: + resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.7 + dev: true + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -4073,6 +4537,20 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -4134,6 +4612,10 @@ packages: through2: 4.0.2 dev: true + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: true + /node-abi@3.54.0: resolution: {integrity: sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==} engines: {node: '>=10'} @@ -4951,6 +5433,10 @@ packages: simple-concat: 1.0.1 dev: false + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -4972,6 +5458,18 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + /source-map@0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} @@ -5101,6 +5599,13 @@ packages: resolution: {integrity: sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==} dev: true + /superjson@2.2.1: + resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} + engines: {node: '>=16'} + dependencies: + copy-anything: 3.0.5 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -5151,6 +5656,13 @@ packages: readable-stream: 3.6.2 dev: true + /timers-ext@0.1.7: + resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} + dependencies: + es5-ext: 0.10.64 + next-tick: 1.1.0 + dev: true + /tiny-lru@11.2.5: resolution: {integrity: sha512-JpqM0K33lG6iQGKiigcwuURAKZlq6rHXfrgeL4/I8/REoyJTGU+tEMszvT/oTRVHG2OiylhGDjqPp1jWMlr3bw==} engines: {node: '>=12'} @@ -5329,6 +5841,10 @@ packages: engines: {node: '>=10'} dev: true + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: true + /typed-css-modules@0.9.1: resolution: {integrity: sha512-W2HWKncdKd+bLWsnuWB2EyuQBzZ7KJ9Byr/67KLiiyGegcN52rOveun9JR8yAvuL5IXunRMxt0eORMtAUj5bmA==} engines: {node: '>=18.0.0'} @@ -5625,6 +6141,10 @@ packages: stackback: 0.0.2 dev: true + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} diff --git a/shared/src/drizzle-migrations/0000_nervous_black_queen.sql b/shared/src/drizzle-migrations/0000_nervous_black_queen.sql new file mode 100644 index 0000000..8627479 --- /dev/null +++ b/shared/src/drizzle-migrations/0000_nervous_black_queen.sql @@ -0,0 +1,13 @@ +CREATE TABLE `cities` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text, + `country_id` integer, + FOREIGN KEY (`country_id`) REFERENCES `countries`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `countries` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text +); +--> statement-breakpoint +CREATE UNIQUE INDEX `nameIdx` ON `countries` (`name`); \ No newline at end of file diff --git a/shared/src/drizzle-migrations/index.ts b/shared/src/drizzle-migrations/index.ts new file mode 100644 index 0000000..6b6acb0 --- /dev/null +++ b/shared/src/drizzle-migrations/index.ts @@ -0,0 +1,5 @@ +import sql_0000_nervous_black_queen from "./0000_nervous_black_queen.sql?raw" + +export const migrations = { + "0000_nervous_black_queen": sql_0000_nervous_black_queen, +} diff --git a/shared/src/drizzle-migrations/meta/0000_snapshot.json b/shared/src/drizzle-migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..90abd79 --- /dev/null +++ b/shared/src/drizzle-migrations/meta/0000_snapshot.json @@ -0,0 +1,89 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "80e880bd-65df-470a-89d5-642e20655222", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "cities": { + "name": "cities", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country_id": { + "name": "country_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "cities_country_id_countries_id_fk": { + "name": "cities_country_id_countries_id_fk", + "tableFrom": "cities", + "tableTo": "countries", + "columnsFrom": [ + "country_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "countries": { + "name": "countries", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "nameIdx": { + "name": "nameIdx", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/shared/src/drizzle-migrations/meta/_journal.json b/shared/src/drizzle-migrations/meta/_journal.json new file mode 100644 index 0000000..3374e30 --- /dev/null +++ b/shared/src/drizzle-migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "5", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1710699044633, + "tag": "0000_nervous_black_queen", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/client/src/components/drizzle/schema.ts b/shared/src/drizzle-test/schema.ts similarity index 100% rename from client/src/components/drizzle/schema.ts rename to shared/src/drizzle-test/schema.ts diff --git a/tsconfig.tools.json b/tsconfig.tools.json index 743109b..f9a1ccd 100644 --- a/tsconfig.tools.json +++ b/tsconfig.tools.json @@ -7,5 +7,5 @@ "module": "ESNext" }, "include": [], - "files": ["vitest.workspace.ts"] + "files": ["vitest.workspace.ts", "drizzle.config.ts"] } From 9f3d16a15f57896ed64fa70229813cbc66a50a20 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 20:53:55 +0100 Subject: [PATCH 04/21] some progress (also, doesnt work so well) --- client/src/components/drizzle/Test.tsx | 28 ++++++++--- .../components/drizzle/crsqlite/migrator.ts | 48 ++++++++++++++++++- .../components/drizzle/crsqlite/session.ts | 5 ++ 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 711fa97..94fb57e 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -1,5 +1,5 @@ import type { DB } from "@vlcn.io/crsqlite-wasm" -import { drizzle } from "./crsqlite" +import { drizzle, type CRSQLite3Database } from "./crsqlite" import * as schema from "../../../../shared/src/drizzle-test/schema" import type { Dialect, SQL } from "drizzle-orm" import { useEffect } from "react" @@ -33,13 +33,13 @@ import tblrx from "@vlcn.io/rx-tbl" // console.log(data) import { migrate } from "./crsqlite/migrator" -import { useQueryClient } from "@tanstack/react-query" +import { useQuery, useQueryClient } from "@tanstack/react-query" async function make() { const sqlite = await initWasm() const sql = await sqlite.open("test") const db = drizzle(sql, { schema }) - await migrate(db, { migrationsFolder: "drizzle" }) + await migrate(db, { migrationsFolder: "drizzle" }).catch(console.error) const rx = tblrx(sql) return { db, rx } } @@ -49,12 +49,13 @@ export function DrizzleTest() { useEffect(() => { const key = "test" make() - .then((db) => { - client.setQueryData(key, { + .then((ctx) => { + console.log("ctx", ctx) + client.setQueryData([key], { // schema: cleanSchema, // schemaName, name: "test", - db, + ctx, }) }) .catch(console.error) @@ -69,5 +70,20 @@ export function DrizzleTest() { } function TestChild() { + const { data } = useQuery({ queryKey: ["test"] }) + useEffect(() => { + if (!data) return + const db = data.ctx.db as CRSQLite3Database + const res = db.query.countries + .findMany({ + with: { + cities: { + where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), + }, + }, + }) + .prepare() + res.all({ cityName: "New York" }).then(console.log).catch(console.error) + }, [data]) return
Test
} diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts index 995c0be..5b3a18e 100644 --- a/client/src/components/drizzle/crsqlite/migrator.ts +++ b/client/src/components/drizzle/crsqlite/migrator.ts @@ -2,13 +2,53 @@ import type { MigrationConfig, MigrationMeta } from "drizzle-orm/migrator" import type { CRSQLite3Database } from "./driver" import migrationJournal from "shared/drizzle-migrations/meta/_journal.json" import { migrations } from "shared/drizzle-migrations/index" +import { sql } from "drizzle-orm" export async function migrate>( db: CRSQLite3Database, config: string | MigrationConfig ) { const migrations = await getMigrations() - db.dialect.migrate(migrations, db.session, config) + const migrationsTable = "__drizzle_migrations" + const migrationTableIdent = sql.identifier(migrationsTable) + const migrationTableCreate = sql` + CREATE TABLE IF NOT EXISTS ${migrationTableIdent} ( + id SERIAL PRIMARY KEY, + hash text NOT NULL, + created_at numeric + ) + ` + + await db.session.run(migrationTableCreate) + + const dbMigrations = await db.values<[number, string, string]>( + sql`SELECT id, hash, created_at FROM ${migrationTableIdent} ORDER BY created_at DESC LIMIT 1` + ) + + const lastDbMigration = dbMigrations[0] ?? undefined + + console.log("migrations", migrations) + console.log("lastDbMigration", lastDbMigration) + console.log("dbMigrations", dbMigrations) + + for (const migration of migrations) { + if (!lastDbMigration || Number(lastDbMigration[2]) < migration.folderMillis) { + console.log("migrating", migration) + for (const stmt of migration.sql) { + await db.run(sql.raw(stmt)) + } + + await db.run( + sql`INSERT INTO ${migrationTableIdent} ("hash", "created_at") VALUES(${migration.hash}, ${migration.folderMillis})` + ) + } + } + console.log("done migrating") + + const dbMigrationsAfter = await db.values<[number, string, string]>( + sql`SELECT id, hash, created_at FROM ${sql.identifier(migrationsTable)} ORDER BY created_at DESC LIMIT 1` + ) + console.log("dbMigrationsAfter", dbMigrationsAfter) } export async function getMigrations() { @@ -29,6 +69,12 @@ export async function getMigrations() { return migrationQueries } +/** + * Browser implementation of node's + * ```ts + * crypto.createHash("sha256").update(query).digest("hex") + * ``` + */ async function createSha256Hash(query: string) { const encoder = new TextEncoder() const data = encoder.encode(query) diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index 599ba9d..e4e66d9 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -87,6 +87,11 @@ export class CRSQLiteSession< } } + exec(query: string) { + console.log("CRSQLiteSession.exec") + return this.client.exec(query) + } + // run(query: SQL): Promise { // console.log("CRSQLiteSession.run") // return this.client.exec(query.) From d25de691f34da8e289fb63decf260506b1d1de12 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 21:06:22 +0100 Subject: [PATCH 05/21] migration more or less working --- client/src/components/drizzle/Test.tsx | 2 +- .../src/components/drizzle/crsqlite/migrator.ts | 17 +++++++++-------- .../src/components/drizzle/crsqlite/session.ts | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 94fb57e..26868f5 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -38,7 +38,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query" async function make() { const sqlite = await initWasm() const sql = await sqlite.open("test") - const db = drizzle(sql, { schema }) + const db = drizzle(sql, { schema, logger: true }) await migrate(db, { migrationsFolder: "drizzle" }).catch(console.error) const rx = tblrx(sql) return { db, rx } diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts index 5b3a18e..42c47fc 100644 --- a/client/src/components/drizzle/crsqlite/migrator.ts +++ b/client/src/components/drizzle/crsqlite/migrator.ts @@ -13,40 +13,41 @@ export async function migrate>( const migrationTableIdent = sql.identifier(migrationsTable) const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS ${migrationTableIdent} ( - id SERIAL PRIMARY KEY, + id TEXT NOT NULL PRIMARY KEY, hash text NOT NULL, - created_at numeric + created_at INTEGER ) ` await db.session.run(migrationTableCreate) + type MigrationEntry = { id: string; hash: string; created_at: number } - const dbMigrations = await db.values<[number, string, string]>( + const dbMigrations = await db.get( sql`SELECT id, hash, created_at FROM ${migrationTableIdent} ORDER BY created_at DESC LIMIT 1` ) - const lastDbMigration = dbMigrations[0] ?? undefined + const lastDbMigration = dbMigrations ?? undefined console.log("migrations", migrations) console.log("lastDbMigration", lastDbMigration) console.log("dbMigrations", dbMigrations) for (const migration of migrations) { - if (!lastDbMigration || Number(lastDbMigration[2]) < migration.folderMillis) { + if (!lastDbMigration || lastDbMigration.created_at < migration.folderMillis) { console.log("migrating", migration) for (const stmt of migration.sql) { await db.run(sql.raw(stmt)) } await db.run( - sql`INSERT INTO ${migrationTableIdent} ("hash", "created_at") VALUES(${migration.hash}, ${migration.folderMillis})` + sql`INSERT INTO ${migrationTableIdent} ("id", "hash", "created_at") VALUES(${crypto.randomUUID()}, ${migration.hash}, ${migration.folderMillis})` ) } } console.log("done migrating") - const dbMigrationsAfter = await db.values<[number, string, string]>( - sql`SELECT id, hash, created_at FROM ${sql.identifier(migrationsTable)} ORDER BY created_at DESC LIMIT 1` + const dbMigrationsAfter = await db.get( + sql`SELECT id, hash, created_at FROM ${migrationTableIdent} ORDER BY created_at DESC LIMIT 1` ) console.log("dbMigrationsAfter", dbMigrationsAfter) } diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index e4e66d9..9ba9ea4 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -51,7 +51,7 @@ export class CRSQLiteSession< executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown ): CRSQLPreparedQuery { - console.log("CRSQLiteSession.prepareQuery") + console.log("CRSQLiteSession.prepareQuery", query) return new CRSQLPreparedQuery( this.client, query, From c75aeb1ec3e8fe9254073e0c54026c37088d13b3 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 21:13:25 +0100 Subject: [PATCH 06/21] noop --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ed9526c..7f921b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,7 +52,7 @@ }, "prettier.enable": true, "explorer.fileNesting.patterns": { - "*package.json": "${capture}.eslint*, ${capture}.prettier*, ${capture}pnpm*, ${capture}turbo*, ${capture}tsconfig.json, ${capture}vite.config.ts, ${capture}vitest.*, ${capture}.gitignore, ${capture}esbuild.*, ${capture}knip.*, ${capture}.nvmrc, ${capture}.cspell.json, ${capture}*.dict.txt", + "*package.json": "${capture}.eslint*, ${capture}.prettier*, ${capture}pnpm*, ${capture}turbo*, ${capture}tsconfig.json, ${capture}vite.config.ts, ${capture}vitest.*, ${capture}.gitignore, ${capture}esbuild.*, ${capture}knip.*, ${capture}.nvmrc, ${capture}.cspell.json, ${capture}*.dict.txt, ${capture}drizzle.config.*", "*.js": "${capture}.js.*, ${capture}.d.ts*", "*.ts": "${capture}.ts.*, ${capture}.d.ts*, ${capture}.js*, ${capture}.mjs*", "*.css": "${capture}.css.*", From 42eb2f88d0a1f8f9eee82c8883b3810203cd3223 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 21:13:45 +0100 Subject: [PATCH 07/21] update migration for crsql --- .../drizzle-migrations/0000_nervous_black_queen.sql | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/shared/src/drizzle-migrations/0000_nervous_black_queen.sql b/shared/src/drizzle-migrations/0000_nervous_black_queen.sql index 8627479..6d6070c 100644 --- a/shared/src/drizzle-migrations/0000_nervous_black_queen.sql +++ b/shared/src/drizzle-migrations/0000_nervous_black_queen.sql @@ -1,13 +1,16 @@ CREATE TABLE `cities` ( - `id` integer PRIMARY KEY NOT NULL, + `id` TEXT NOT NULL PRIMARY KEY, `name` text, - `country_id` integer, - FOREIGN KEY (`country_id`) REFERENCES `countries`(`id`) ON UPDATE no action ON DELETE no action + `country_id` text ); --> statement-breakpoint +SELECT crsql_as_crr ('cities'); +--> statement-breakpoint CREATE TABLE `countries` ( - `id` integer PRIMARY KEY NOT NULL, + `id` TEXT NOT NULL PRIMARY KEY, `name` text ); --> statement-breakpoint -CREATE UNIQUE INDEX `nameIdx` ON `countries` (`name`); \ No newline at end of file +SELECT crsql_as_crr ('countries'); +--> statement-breakpoint +CREATE INDEX `nameIdx` ON `countries` (`name`); \ No newline at end of file From 25af17a74d6fa45c3dc59a17ffe26056f545e054 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 22:03:23 +0100 Subject: [PATCH 08/21] create new migration for testing --- .../0001_loving_madame_hydra.sql | 11 ++ shared/src/drizzle-migrations/index.ts | 2 + .../meta/0001_snapshot.json | 103 ++++++++++++++++++ .../src/drizzle-migrations/meta/_journal.json | 7 ++ shared/src/drizzle-test/schema.ts | 6 +- 5 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 shared/src/drizzle-migrations/0001_loving_madame_hydra.sql create mode 100644 shared/src/drizzle-migrations/meta/0001_snapshot.json diff --git a/shared/src/drizzle-migrations/0001_loving_madame_hydra.sql b/shared/src/drizzle-migrations/0001_loving_madame_hydra.sql new file mode 100644 index 0000000..62b5e84 --- /dev/null +++ b/shared/src/drizzle-migrations/0001_loving_madame_hydra.sql @@ -0,0 +1,11 @@ +SELECT crsql_begin_alter('cities'); +--> statement-breakpoint +ALTER TABLE cities ADD `population` integer; +--> statement-breakpoint +SELECT crsql_commit_alter('cities'); +--> statement-breakpoint +SELECT crsql_begin_alter('countries'); +--> statement-breakpoint +ALTER TABLE countries ADD `population` integer; +--> statement-breakpoint +SELECT crsql_commit_alter('countries'); \ No newline at end of file diff --git a/shared/src/drizzle-migrations/index.ts b/shared/src/drizzle-migrations/index.ts index 6b6acb0..6182e9a 100644 --- a/shared/src/drizzle-migrations/index.ts +++ b/shared/src/drizzle-migrations/index.ts @@ -1,5 +1,7 @@ import sql_0000_nervous_black_queen from "./0000_nervous_black_queen.sql?raw" +import sql_0001_loving_madame_hydra from "./0001_loving_madame_hydra.sql?raw" export const migrations = { "0000_nervous_black_queen": sql_0000_nervous_black_queen, + "0001_loving_madame_hydra": sql_0001_loving_madame_hydra, } diff --git a/shared/src/drizzle-migrations/meta/0001_snapshot.json b/shared/src/drizzle-migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..34e9c3b --- /dev/null +++ b/shared/src/drizzle-migrations/meta/0001_snapshot.json @@ -0,0 +1,103 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "e3a94504-f960-4e13-8de0-cc85a48e2378", + "prevId": "80e880bd-65df-470a-89d5-642e20655222", + "tables": { + "cities": { + "name": "cities", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country_id": { + "name": "country_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "population": { + "name": "population", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "cities_country_id_countries_id_fk": { + "name": "cities_country_id_countries_id_fk", + "tableFrom": "cities", + "tableTo": "countries", + "columnsFrom": [ + "country_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "countries": { + "name": "countries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "population": { + "name": "population", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "nameIdx": { + "name": "nameIdx", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/shared/src/drizzle-migrations/meta/_journal.json b/shared/src/drizzle-migrations/meta/_journal.json index 3374e30..a3b6cd4 100644 --- a/shared/src/drizzle-migrations/meta/_journal.json +++ b/shared/src/drizzle-migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1710699044633, "tag": "0000_nervous_black_queen", "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1710708236564, + "tag": "0001_loving_madame_hydra", + "breakpoints": true } ] } \ No newline at end of file diff --git a/shared/src/drizzle-test/schema.ts b/shared/src/drizzle-test/schema.ts index b3ffa63..1649397 100644 --- a/shared/src/drizzle-test/schema.ts +++ b/shared/src/drizzle-test/schema.ts @@ -4,8 +4,9 @@ import { relations } from "drizzle-orm" export const countries = sqliteTable( "countries", { - id: integer("id").primaryKey(), + id: text("id").primaryKey(), name: text("name"), + population: integer("population"), }, (countries) => ({ nameIdx: uniqueIndex("nameIdx").on(countries.name), @@ -17,9 +18,10 @@ export const countriesRelations = relations(countries, ({ many }) => ({ })) export const cities = sqliteTable("cities", { - id: integer("id").primaryKey(), + id: text("id").primaryKey(), name: text("name"), countryId: integer("country_id").references(() => countries.id), + population: integer("population"), }) export const citiesRelations = relations(cities, ({ one }) => ({ From 98e94100e16b40d1a5783ea5fb122f520ac2ba69 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 22:03:34 +0100 Subject: [PATCH 09/21] noop --- client/src/components/drizzle/Test.tsx | 1 + client/src/components/drizzle/crsqlite/migrator.ts | 12 ++++++++---- repo.dict.txt | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 26868f5..ae4f97d 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -83,6 +83,7 @@ function TestChild() { }, }) .prepare() + console.log("prepared user-query", res) res.all({ cityName: "New York" }).then(console.log).catch(console.error) }, [data]) return
Test
diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts index 42c47fc..1d54255 100644 --- a/client/src/components/drizzle/crsqlite/migrator.ts +++ b/client/src/components/drizzle/crsqlite/migrator.ts @@ -2,7 +2,8 @@ import type { MigrationConfig, MigrationMeta } from "drizzle-orm/migrator" import type { CRSQLite3Database } from "./driver" import migrationJournal from "shared/drizzle-migrations/meta/_journal.json" import { migrations } from "shared/drizzle-migrations/index" -import { sql } from "drizzle-orm" +import { sql, type TablesRelationalConfig } from "drizzle-orm" +import type { SQLiteSession } from "drizzle-orm/sqlite-core" export async function migrate>( db: CRSQLite3Database, @@ -19,7 +20,10 @@ export async function migrate>( ) ` - await db.session.run(migrationTableCreate) + // @ts-expect-error -- `session` exists but is marked as `@internal` on the type level + await (db.session as SQLiteSession<"async", void, TSchema, TablesRelationalConfig>).run( + migrationTableCreate + ) type MigrationEntry = { id: string; hash: string; created_at: number } const dbMigrations = await db.get( @@ -71,7 +75,7 @@ export async function getMigrations() { } /** - * Browser implementation of node's + * Cross-platform implementation of node's * ```ts * crypto.createHash("sha256").update(query).digest("hex") * ``` @@ -79,7 +83,7 @@ export async function getMigrations() { async function createSha256Hash(query: string) { const encoder = new TextEncoder() const data = encoder.encode(query) - const hash = await window.crypto.subtle.digest("SHA-256", data) + const hash = await globalThis.crypto.subtle.digest("SHA-256", data) const hashArray = Array.from(new Uint8Array(hash)) const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("") return hashHex diff --git a/repo.dict.txt b/repo.dict.txt index 6772e44..75c4b5f 100644 --- a/repo.dict.txt +++ b/repo.dict.txt @@ -14,6 +14,7 @@ fract fumadocs hsts knip +Millis outdir outfile pino From 05feffc3683a121eac7b5cdad2f92a489b155f10 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 17 Mar 2024 23:09:57 +0100 Subject: [PATCH 10/21] auto-import all migrations (temp) --- client/src/components/drizzle/crsqlite/migrator.ts | 3 +++ shared/src/drizzle-migrations/index.ts | 12 +++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts index 1d54255..77ad1fd 100644 --- a/client/src/components/drizzle/crsqlite/migrator.ts +++ b/client/src/components/drizzle/crsqlite/migrator.ts @@ -56,6 +56,9 @@ export async function migrate>( console.log("dbMigrationsAfter", dbMigrationsAfter) } +/** + * TODO: this should be done at build-time through Vite `define` config + */ export async function getMigrations() { const journal = migrationJournal as { entries: Array<{ idx: number; when: number; tag: string; breakpoints: boolean }> diff --git a/shared/src/drizzle-migrations/index.ts b/shared/src/drizzle-migrations/index.ts index 6182e9a..f8109ba 100644 --- a/shared/src/drizzle-migrations/index.ts +++ b/shared/src/drizzle-migrations/index.ts @@ -1,7 +1,5 @@ -import sql_0000_nervous_black_queen from "./0000_nervous_black_queen.sql?raw" -import sql_0001_loving_madame_hydra from "./0001_loving_madame_hydra.sql?raw" - -export const migrations = { - "0000_nervous_black_queen": sql_0000_nervous_black_queen, - "0001_loving_madame_hydra": sql_0001_loving_madame_hydra, -} +export const migrations = Object.fromEntries( + Object.entries( + import.meta.glob("./*.sql", { eager: true, query: "?raw", import: "default" }) + ).map(([key, value]) => [key.slice(2, -4), value]) +) From ecba533be713f84f1ea0ddb62725b3802cf00edf Mon Sep 17 00:00:00 2001 From: Sheraff Date: Fri, 22 Mar 2024 19:25:34 +0100 Subject: [PATCH 11/21] advance schema, better test case --- client/src/components/drizzle/Test.tsx | 56 ++++++++-- .../meta/0002_snapshot.json | 103 ++++++++++++++++++ .../src/drizzle-migrations/meta/_journal.json | 38 +++---- shared/src/drizzle-test/schema.ts | 6 +- 4 files changed, 169 insertions(+), 34 deletions(-) create mode 100644 shared/src/drizzle-migrations/meta/0002_snapshot.json diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index ae4f97d..9a2093e 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -1,7 +1,7 @@ import type { DB } from "@vlcn.io/crsqlite-wasm" import { drizzle, type CRSQLite3Database } from "./crsqlite" -import * as schema from "../../../../shared/src/drizzle-test/schema" -import type { Dialect, SQL } from "drizzle-orm" +import * as schema from "shared/drizzle-test/schema" +import { eq, type Dialect, type SQL } from "drizzle-orm" import { useEffect } from "react" import initWasm from "@vlcn.io/crsqlite-wasm" import tblrx from "@vlcn.io/rx-tbl" @@ -74,17 +74,49 @@ function TestChild() { useEffect(() => { if (!data) return const db = data.ctx.db as CRSQLite3Database - const res = db.query.countries - .findMany({ - with: { - cities: { - where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), + void (async function () { + let [usa] = await db + .select() + .from(schema.countries) + .where(eq(schema.countries.name, "USA")) + if (!usa) { + ;[usa] = await db + .insert(schema.countries) + .values({ + id: crypto.randomUUID(), + name: "USA", + population: 331_900_000, + }) + .returning() + } + let [nyc] = await db + .select() + .from(schema.cities) + .where(eq(schema.cities.name, "New York")) + if (!nyc) { + ;[nyc] = await db + .insert(schema.cities) + .values({ + id: crypto.randomUUID(), + name: "New York", + population: 8_336_817, + countryId: usa!.id, + }) + .returning() + } + const res = db.query.countries + .findMany({ + with: { + cities: { + where: (city, { eq, sql }) => + eq(city.name, sql.placeholder("cityName")), + }, }, - }, - }) - .prepare() - console.log("prepared user-query", res) - res.all({ cityName: "New York" }).then(console.log).catch(console.error) + }) + .prepare() + console.log("prepared user-query", res) + res.all({ cityName: "New York" }).then(console.log).catch(console.error) + })() }, [data]) return
Test
} diff --git a/shared/src/drizzle-migrations/meta/0002_snapshot.json b/shared/src/drizzle-migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..5032a00 --- /dev/null +++ b/shared/src/drizzle-migrations/meta/0002_snapshot.json @@ -0,0 +1,103 @@ +{ + "version": "5", + "dialect": "sqlite", + "id": "7998d8cd-050a-460d-a1af-472126b0453f", + "prevId": "e3a94504-f960-4e13-8de0-cc85a48e2378", + "tables": { + "cities": { + "name": "cities", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country_id": { + "name": "country_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "population": { + "name": "population", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "cities_country_id_countries_id_fk": { + "name": "cities_country_id_countries_id_fk", + "tableFrom": "cities", + "tableTo": "countries", + "columnsFrom": [ + "country_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "countries": { + "name": "countries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "population": { + "name": "population", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "nameIdx": { + "name": "nameIdx", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/shared/src/drizzle-migrations/meta/_journal.json b/shared/src/drizzle-migrations/meta/_journal.json index a3b6cd4..91a80e3 100644 --- a/shared/src/drizzle-migrations/meta/_journal.json +++ b/shared/src/drizzle-migrations/meta/_journal.json @@ -1,20 +1,20 @@ { - "version": "5", - "dialect": "sqlite", - "entries": [ - { - "idx": 0, - "version": "5", - "when": 1710699044633, - "tag": "0000_nervous_black_queen", - "breakpoints": true - }, - { - "idx": 1, - "version": "5", - "when": 1710708236564, - "tag": "0001_loving_madame_hydra", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "5", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1710699044633, + "tag": "0000_nervous_black_queen", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1710708236564, + "tag": "0001_loving_madame_hydra", + "breakpoints": true + } + ] +} diff --git a/shared/src/drizzle-test/schema.ts b/shared/src/drizzle-test/schema.ts index 1649397..3f7baed 100644 --- a/shared/src/drizzle-test/schema.ts +++ b/shared/src/drizzle-test/schema.ts @@ -1,4 +1,4 @@ -import { sqliteTable, text, integer, uniqueIndex } from "drizzle-orm/sqlite-core" +import { sqliteTable, text, integer, uniqueIndex, index } from "drizzle-orm/sqlite-core" import { relations } from "drizzle-orm" export const countries = sqliteTable( @@ -9,7 +9,7 @@ export const countries = sqliteTable( population: integer("population"), }, (countries) => ({ - nameIdx: uniqueIndex("nameIdx").on(countries.name), + nameIdx: index("nameIdx").on(countries.name), }) ) @@ -20,7 +20,7 @@ export const countriesRelations = relations(countries, ({ many }) => ({ export const cities = sqliteTable("cities", { id: text("id").primaryKey(), name: text("name"), - countryId: integer("country_id").references(() => countries.id), + countryId: text("country_id").references(() => countries.id), population: integer("population"), }) From c701c30ea4a59b9eabe80a63da19518d3b916160 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Fri, 22 Mar 2024 19:26:16 +0100 Subject: [PATCH 12/21] probably no-op --- .../components/drizzle/crsqlite/session.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index 9ba9ea4..b353fd0 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -151,8 +151,26 @@ export class CRSQLPreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt - const rows = await stmt.all(null, ...params) + stmt.bind(params) + stmt.raw(false) + const rows = await stmt.all(null) + console.log("CRSQLPreparedQuery.all", rows) + // debugger return this.customResultMapper ? this.customResultMapper(rows) : rows + + // const { fields, logger, query, tx, client, customResultMapper } = this + // if (!fields && !customResultMapper) { + // const params = fillPlaceholders(query.params, placeholderValues ?? {}) + // logger.logQuery(query.sql, params) + // const stmt: InStatement = { sql: query.sql, args: params as InArgs } + // return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => + // this.mapAllResult(rows) + // ) + // } + + // const rows = (await this.values(placeholderValues)) as unknown[][] + + // return this.mapAllResult(rows) } /** From 27e951e5a1caf319010ed700e5b0be7a60697563 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 24 Mar 2024 16:45:17 +0100 Subject: [PATCH 13/21] use raw sqlite queries, drizzle will format --- client/src/components/drizzle/Test.tsx | 32 +++++++++++-------- .../components/drizzle/crsqlite/session.ts | 18 ++++++++++- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 9a2093e..754a259 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -1,7 +1,7 @@ import type { DB } from "@vlcn.io/crsqlite-wasm" import { drizzle, type CRSQLite3Database } from "./crsqlite" import * as schema from "shared/drizzle-test/schema" -import { eq, type Dialect, type SQL } from "drizzle-orm" +import { eq, fillPlaceholders, type Dialect, type SQL } from "drizzle-orm" import { useEffect } from "react" import initWasm from "@vlcn.io/crsqlite-wasm" import tblrx from "@vlcn.io/rx-tbl" @@ -41,7 +41,7 @@ async function make() { const db = drizzle(sql, { schema, logger: true }) await migrate(db, { migrationsFolder: "drizzle" }).catch(console.error) const rx = tblrx(sql) - return { db, rx } + return { db, rx, sql } } export function DrizzleTest() { @@ -93,6 +93,7 @@ function TestChild() { .select() .from(schema.cities) .where(eq(schema.cities.name, "New York")) + console.log("::::::::::: nyc", nyc) if (!nyc) { ;[nyc] = await db .insert(schema.cities) @@ -104,18 +105,23 @@ function TestChild() { }) .returning() } - const res = db.query.countries - .findMany({ - with: { - cities: { - where: (city, { eq, sql }) => - eq(city.name, sql.placeholder("cityName")), - }, + const res = db.query.countries.findMany({ + with: { + cities: { + where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), }, - }) - .prepare() - console.log("prepared user-query", res) - res.all({ cityName: "New York" }).then(console.log).catch(console.error) + }, + }) + + console.log(":::::::::::::: prepared user-query", res.toSQL()) + ;(data.ctx.sql as DB) + .exec( + res.toSQL().sql, + fillPlaceholders(res.toSQL().params, { cityName: "New York" }) + ) + .then(console.log) + .catch(console.error) + res.prepare().all({ cityName: "New York" }).then(console.log).catch(console.error) })() }, [data]) return
Test
diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index b353fd0..9e2d0cb 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -152,7 +152,9 @@ export class CRSQLPreparedQuery< this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt stmt.bind(params) - stmt.raw(false) + if (this.customResultMapper) { + stmt.raw(true) + } const rows = await stmt.all(null) console.log("CRSQLPreparedQuery.all", rows) // debugger @@ -180,8 +182,21 @@ export class CRSQLPreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt + if (this.customResultMapper) { + stmt.raw(true) + } const row = await stmt.get(null, ...params) return this.customResultMapper ? this.customResultMapper([row]) : row + + // (rawRows, mapColumnValue) => { + // const rows = rawRows.map( + // (row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection, mapColumnValue) + // ); + // if (this.mode === "first") { + // return rows[0]; + // } + // return rows; + // } } /** @@ -191,6 +206,7 @@ export class CRSQLPreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt + stmt.raw(true) const rows = await stmt.all(null, ...params) return rows.map((row) => Object.values(row)[0]) } From 6d506f29702cc35b70439077afa15a96b7dac640 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 24 Mar 2024 17:53:48 +0100 Subject: [PATCH 14/21] handle finalizing statements --- client/src/components/drizzle/Test.tsx | 31 +++--- .../components/drizzle/crsqlite/session.ts | 98 ++++++++++--------- 2 files changed, 69 insertions(+), 60 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 754a259..8a380ff 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -41,7 +41,7 @@ async function make() { const db = drizzle(sql, { schema, logger: true }) await migrate(db, { migrationsFolder: "drizzle" }).catch(console.error) const rx = tblrx(sql) - return { db, rx, sql } + return { db, rx } } export function DrizzleTest() { @@ -105,23 +105,24 @@ function TestChild() { }) .returning() } - const res = db.query.countries.findMany({ - with: { - cities: { - where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), + const res = db.query.countries + .findMany({ + with: { + cities: { + where: (city, { eq, sql }) => + eq(city.name, sql.placeholder("cityName")), + }, }, - }, - }) + }) + .prepare() - console.log(":::::::::::::: prepared user-query", res.toSQL()) - ;(data.ctx.sql as DB) - .exec( - res.toSQL().sql, - fillPlaceholders(res.toSQL().params, { cityName: "New York" }) - ) - .then(console.log) + console.log(":::::::::::::: user-query", res) + await res + .all({ cityName: "New York" }) + .then((a) => console.log("DRIZZLE", a)) .catch(console.error) - res.prepare().all({ cityName: "New York" }).then(console.log).catch(console.error) + await res.finalize() + console.log("--------------------------") })() }, [data]) return
Test
diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index 9e2d0cb..59f1c39 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -38,7 +38,7 @@ export class CRSQLiteSession< private dialect: SQLiteAsyncDialect, private schema: RelationalSchemaConfig | undefined, private options: CRSQLiteSessionOptions, - private tx?: Transaction | undefined + private tx?: Transaction[1] | undefined ) { super(dialect) this.logger = options.logger ?? new NoopLogger() @@ -55,17 +55,35 @@ export class CRSQLiteSession< return new CRSQLPreparedQuery( this.client, query, + false, this.logger, fields, - this.tx, + this.tx ?? null, executeMethod, customResultMapper ) } + prepareOneTimeQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + executeMethod: SQLiteExecuteMethod + ): SQLitePreparedQuery { + console.log("CRSQLiteSession.prepareOneTimeQuery", executeMethod, query) + return new CRSQLPreparedQuery( + this.client, + query, + true, + this.logger, + fields, + this.tx ?? null, + executeMethod + ) + } + override async transaction( - transaction: (db: CRSQLTransaction) => T | Promise, - _config?: SQLiteTransactionConfig + transaction: (db: CRSQLTransaction) => T | Promise + // _config?: SQLiteTransactionConfig ): Promise { console.log("CRSQLiteSession.transaction") const crsqliteTx = await this.client.imperativeTx() @@ -74,7 +92,7 @@ export class CRSQLiteSession< this.dialect, this.schema, this.options, - crsqliteTx + crsqliteTx[1] ) const tx = new CRSQLTransaction("async", this.dialect, session, this.schema) try { @@ -91,21 +109,15 @@ export class CRSQLiteSession< console.log("CRSQLiteSession.exec") return this.client.exec(query) } - - // run(query: SQL): Promise { - // console.log("CRSQLiteSession.run") - // return this.client.exec(query.) - - // } } type StmtAsync = Awaited> -// declare module "drizzle-orm/sqlite-core/session" { -// export interface PreparedQuery { -// finalize(): Promise -// } -// } +declare module "drizzle-orm/session" { + interface PreparedQuery { + finalize(): Promise + } +} export class CRSQLPreparedQuery< T extends PreparedQueryConfig = PreparedQueryConfig, @@ -124,9 +136,10 @@ export class CRSQLPreparedQuery< constructor( private client: DB, query: Query, + private oneTime: boolean, private logger: Logger, fields: SelectedFieldsOrdered | undefined, - private tx: Transaction | undefined, + private tx: Transaction[1] | null, executeMethod: SQLiteExecuteMethod, private customResultMapper?: (rows: unknown[][]) => unknown ) { @@ -141,7 +154,10 @@ export class CRSQLPreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt - await stmt.run(null, ...params) + await stmt.run(this.tx, ...params) + if (this.oneTime) { + void stmt.finalize(this.tx) + } } /** @@ -151,28 +167,13 @@ export class CRSQLPreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt - stmt.bind(params) - if (this.customResultMapper) { - stmt.raw(true) - } - const rows = await stmt.all(null) + stmt.raw(Boolean(this.customResultMapper)) + const rows = await stmt.all(this.tx, ...params) console.log("CRSQLPreparedQuery.all", rows) - // debugger + if (this.oneTime) { + void stmt.finalize(this.tx) + } return this.customResultMapper ? this.customResultMapper(rows) : rows - - // const { fields, logger, query, tx, client, customResultMapper } = this - // if (!fields && !customResultMapper) { - // const params = fillPlaceholders(query.params, placeholderValues ?? {}) - // logger.logQuery(query.sql, params) - // const stmt: InStatement = { sql: query.sql, args: params as InArgs } - // return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => - // this.mapAllResult(rows) - // ) - // } - - // const rows = (await this.values(placeholderValues)) as unknown[][] - - // return this.mapAllResult(rows) } /** @@ -182,10 +183,11 @@ export class CRSQLPreparedQuery< const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt - if (this.customResultMapper) { - stmt.raw(true) + stmt.raw(Boolean(this.customResultMapper)) + const row = await stmt.get(this.tx, ...params) + if (this.oneTime) { + void stmt.finalize(this.tx) } - const row = await stmt.get(null, ...params) return this.customResultMapper ? this.customResultMapper([row]) : row // (rawRows, mapColumnValue) => { @@ -207,11 +209,17 @@ export class CRSQLPreparedQuery< this.logger.logQuery(this.query.sql, params) const stmt = await this.stmt stmt.raw(true) - const rows = await stmt.all(null, ...params) - return rows.map((row) => Object.values(row)[0]) + const rows = (await stmt.all(null, ...params)) as unknown[][] + if (this.oneTime) { + void stmt.finalize(this.tx) + } + return rows.map((row) => row[0]) } async finalize(): Promise { + if (this.oneTime) { + throw new Error("Cannot finalize one-time query") + } const stmt = await this.stmt await stmt.finalize(null) } @@ -223,8 +231,8 @@ export class CRSQLTransaction< > extends SQLiteTransaction<"async", void, TFullSchema, TSchema> { static readonly [entityKind]: string = "CRSQLTransaction" - private dialect: SQLiteAsyncDialect - private session: CRSQLiteSession + private dialect!: SQLiteAsyncDialect + private session!: CRSQLiteSession override async transaction( transaction: (tx: CRSQLTransaction) => Promise From 39844ada053878095c5dfa05db10ffc665024554 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 24 Mar 2024 18:13:56 +0100 Subject: [PATCH 15/21] upgrade drizzle from 0.30.2 to 0.30.4 --- .../components/drizzle/crsqlite/session.ts | 27 +++++++++++++++++-- package.json | 2 +- pnpm-lock.yaml | 11 +++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index 59f1c39..ba0b611 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -49,9 +49,10 @@ export class CRSQLiteSession< query: Query, fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, + _isResponseInArrayMode: boolean, customResultMapper?: (rows: unknown[][]) => unknown ): CRSQLPreparedQuery { - console.log("CRSQLiteSession.prepareQuery", query) + console.log("CRSQLiteSession.prepareQuery", executeMethod, query) return new CRSQLPreparedQuery( this.client, query, @@ -67,7 +68,8 @@ export class CRSQLiteSession< prepareOneTimeQuery( query: Query, fields: SelectedFieldsOrdered | undefined, - executeMethod: SQLiteExecuteMethod + executeMethod: SQLiteExecuteMethod, + _isResponseInArrayMode: boolean ): SQLitePreparedQuery { console.log("CRSQLiteSession.prepareOneTimeQuery", executeMethod, query) return new CRSQLPreparedQuery( @@ -96,6 +98,7 @@ export class CRSQLiteSession< ) const tx = new CRSQLTransaction("async", this.dialect, session, this.schema) try { + console.log("transaction built, calling transaction function") const result = await transaction(tx) crsqliteTx[0]() return result @@ -109,10 +112,29 @@ export class CRSQLiteSession< console.log("CRSQLiteSession.exec") return this.client.exec(query) } + + // TODO: can we implement these methods without going through a prepared query? (they are called when doing "one time queries") + // run(query: SQL) { + // console.log("CRSQLiteSession.run") + // return this.client.run(query) + // } + // all(query: SQL): Promise { + // console.log("CRSQLiteSession.all") + // return this.client.all(query) + // } + // get(query: SQL): Promise { + // console.log("CRSQLiteSession.get") + // return this.client.get(query) + // } + // values(query: SQL): Promise { + // console.log("CRSQLiteSession.values") + // return this.client.values(query) + // } } type StmtAsync = Awaited> +// TODO: this interface augmentation doesn't work, why? we do get a `SQLitePreparedQuery` when calling `.prepare()` but it doesn't have the `finalize` method at the type level declare module "drizzle-orm/session" { interface PreparedQuery { finalize(): Promise @@ -237,6 +259,7 @@ export class CRSQLTransaction< override async transaction( transaction: (tx: CRSQLTransaction) => Promise ): Promise { + console.log("CRSQLTransaction.transaction inside transaction function of transaction class") const savepointName = `sp${this.nestedIndex}` const tx = new CRSQLTransaction( "async", diff --git a/package.json b/package.json index 147767f..695da6f 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "better-sqlite3": "^9.4.3", "clsx": "^2.1.0", "dotenv": "^16.4.5", - "drizzle-orm": "^0.30.2", + "drizzle-orm": "^0.30.4", "fastify": "^4.26.2", "grant": "^5.4.22", "pino-pretty": "10.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fd48ec..98aea33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^16.4.5 version: 16.4.5 drizzle-orm: - specifier: ^0.30.2 - version: 0.30.2(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) + specifier: ^0.30.4 + version: 0.30.4(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) fastify: specifier: ^4.26.2 version: 4.26.2 @@ -3007,8 +3007,8 @@ packages: - supports-color dev: true - /drizzle-orm@0.30.2(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0): - resolution: {integrity: sha512-DNd3djg03o+WxZX3pGD8YD+qrWT8gbrbhaZ2W0PVb6yH4rtM/VTB92cTGvumcRh7SSd2KfV0NWYDB70BHIXQTg==} + /drizzle-orm@0.30.4(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0): + resolution: {integrity: sha512-kWoSMGbrOFkmkAweLTFtHJMpN+nwhx89q0mLELqT2aEU+1szNV8jrnBmJwZ0WGNp7J7yQn/ezEtxBI/qzTSElQ==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' @@ -3022,6 +3022,7 @@ packages: '@types/react': '>=18' '@types/sql.js': '*' '@vercel/postgres': '*' + '@xata.io/client': '*' better-sqlite3: '>=7' bun-types: '*' expo-sqlite: '>=13.2.0' @@ -3058,6 +3059,8 @@ packages: optional: true '@vercel/postgres': optional: true + '@xata.io/client': + optional: true better-sqlite3: optional: true bun-types: From 62fe39e43668b18c5916dbc8f5f67a4d6fd3e7f1 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 24 Mar 2024 18:59:43 +0100 Subject: [PATCH 16/21] transactions seem to be working --- client/src/components/drizzle/Test.tsx | 24 +++++++++++++++ .../components/drizzle/crsqlite/session.ts | 29 +++++-------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 8a380ff..d4ce1fa 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -123,6 +123,30 @@ function TestChild() { .catch(console.error) await res.finalize() console.log("--------------------------") + const foo = await db.transaction(async (tx) => { + // throw "rollback" + console.log("inside tx function") + const [usa] = await tx + .select({ + name: schema.countries.name, + pop: schema.countries.population, + }) + .from(schema.countries) + .where(eq(schema.countries.name, "USA")) + console.log("after tx select", usa) + const [nyc] = await tx.transaction(async (tx) => { + console.log("inside nested tx function") + return tx + .select({ + name: schema.cities.name, + pop: schema.cities.population, + }) + .from(schema.cities) + .where(eq(schema.cities.name, "New York")) + }) + return { usa, nyc } + }) + console.log("FOO", foo) })() }, [data]) return
Test
diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index ba0b611..9e80482 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -84,7 +84,7 @@ export class CRSQLiteSession< } override async transaction( - transaction: (db: CRSQLTransaction) => T | Promise + transaction: (db: CRSQLTransaction) => Promise // _config?: SQLiteTransactionConfig ): Promise { console.log("CRSQLiteSession.transaction") @@ -98,7 +98,6 @@ export class CRSQLiteSession< ) const tx = new CRSQLTransaction("async", this.dialect, session, this.schema) try { - console.log("transaction built, calling transaction function") const result = await transaction(tx) crsqliteTx[0]() return result @@ -110,7 +109,8 @@ export class CRSQLiteSession< exec(query: string) { console.log("CRSQLiteSession.exec") - return this.client.exec(query) + this.logger.logQuery(query, []) + return (this.tx ?? this.client).exec(query) } // TODO: can we implement these methods without going through a prepared query? (they are called when doing "one time queries") @@ -166,7 +166,7 @@ export class CRSQLPreparedQuery< private customResultMapper?: (rows: unknown[][]) => unknown ) { super("async", executeMethod, query) - this.stmt = this.client.prepare(query.sql) + this.stmt = (this.tx ?? this.client).prepare(query.sql) } /** @@ -211,16 +211,6 @@ export class CRSQLPreparedQuery< void stmt.finalize(this.tx) } return this.customResultMapper ? this.customResultMapper([row]) : row - - // (rawRows, mapColumnValue) => { - // const rows = rawRows.map( - // (row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection, mapColumnValue) - // ); - // if (this.mode === "first") { - // return rows[0]; - // } - // return rows; - // } } /** @@ -243,7 +233,7 @@ export class CRSQLPreparedQuery< throw new Error("Cannot finalize one-time query") } const stmt = await this.stmt - await stmt.finalize(null) + await stmt.finalize(this.tx) } } @@ -253,9 +243,6 @@ export class CRSQLTransaction< > extends SQLiteTransaction<"async", void, TFullSchema, TSchema> { static readonly [entityKind]: string = "CRSQLTransaction" - private dialect!: SQLiteAsyncDialect - private session!: CRSQLiteSession - override async transaction( transaction: (tx: CRSQLTransaction) => Promise ): Promise { @@ -268,13 +255,13 @@ export class CRSQLTransaction< this.schema, this.nestedIndex + 1 ) - await this.session.run(sql.raw(`SAVEPOINT ${savepointName}`)) + await this.session.exec(`SAVEPOINT ${savepointName};`) try { const result = await transaction(tx) - await this.session.run(sql.raw(`RELEASE savepoint ${savepointName}`)) + await this.session.exec(`RELEASE savepoint ${savepointName};`) return result } catch (err) { - await this.session.run(sql.raw(`ROLLBACK TO savepoint ${savepointName}`)) + await this.session.exec(`ROLLBACK TO savepoint ${savepointName};`) throw err } } From ce613c98449bf7a32f005d4cb18e83f4b6a280f1 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sun, 24 Mar 2024 19:02:37 +0100 Subject: [PATCH 17/21] ensure 1st level Tx is also wrapped in SAVEPOINT --- client/src/components/drizzle/Test.tsx | 6 ++++-- client/src/components/drizzle/crsqlite/session.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index d4ce1fa..51991dd 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -134,15 +134,17 @@ function TestChild() { .from(schema.countries) .where(eq(schema.countries.name, "USA")) console.log("after tx select", usa) - const [nyc] = await tx.transaction(async (tx) => { + const nyc = await tx.transaction(async (tx) => { console.log("inside nested tx function") - return tx + const [nyc] = await tx .select({ name: schema.cities.name, pop: schema.cities.population, }) .from(schema.cities) .where(eq(schema.cities.name, "New York")) + tx.rollback() + return nyc }) return { usa, nyc } }) diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index 9e80482..f0360be 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -98,7 +98,7 @@ export class CRSQLiteSession< ) const tx = new CRSQLTransaction("async", this.dialect, session, this.schema) try { - const result = await transaction(tx) + const result = await tx.transaction(transaction) crsqliteTx[0]() return result } catch (err) { From 17853d30ae6e7dbf8c7efb01d82fa4ce4aa3fcd7 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Mon, 25 Mar 2024 08:51:18 +0100 Subject: [PATCH 18/21] migrator doesn't contain project-specific code --- client/src/components/drizzle/Test.tsx | 4 ++-- client/src/components/drizzle/crsqlite/migrator.ts | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index 51991dd..e70c912 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -32,14 +32,14 @@ import tblrx from "@vlcn.io/rx-tbl" // console.log(data) -import { migrate } from "./crsqlite/migrator" +import { getMigrations, migrate } from "./crsqlite/migrator" import { useQuery, useQueryClient } from "@tanstack/react-query" async function make() { const sqlite = await initWasm() const sql = await sqlite.open("test") const db = drizzle(sql, { schema, logger: true }) - await migrate(db, { migrationsFolder: "drizzle" }).catch(console.error) + await migrate(db, { migrations: await getMigrations() }).catch(console.error) const rx = tblrx(sql) return { db, rx } } diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts index 77ad1fd..d0f675d 100644 --- a/client/src/components/drizzle/crsqlite/migrator.ts +++ b/client/src/components/drizzle/crsqlite/migrator.ts @@ -1,16 +1,22 @@ -import type { MigrationConfig, MigrationMeta } from "drizzle-orm/migrator" +import type { MigrationMeta } from "drizzle-orm/migrator" import type { CRSQLite3Database } from "./driver" import migrationJournal from "shared/drizzle-migrations/meta/_journal.json" import { migrations } from "shared/drizzle-migrations/index" import { sql, type TablesRelationalConfig } from "drizzle-orm" import type { SQLiteSession } from "drizzle-orm/sqlite-core" +type MigrationConfig = { + /** @default "__drizzle_migrations" */ + migrationsTable?: string + migrations: MigrationMeta[] +} + export async function migrate>( db: CRSQLite3Database, - config: string | MigrationConfig + config: MigrationConfig ) { - const migrations = await getMigrations() - const migrationsTable = "__drizzle_migrations" + const migrations = config.migrations + const migrationsTable = config.migrationsTable ?? "__drizzle_migrations" const migrationTableIdent = sql.identifier(migrationsTable) const migrationTableCreate = sql` CREATE TABLE IF NOT EXISTS ${migrationTableIdent} ( From 2068655ad5dde38c07e12cbd6cfabb0981cd631b Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sat, 30 Mar 2024 16:18:50 +0100 Subject: [PATCH 19/21] cleanup --- .../src/components/drizzle/crsqlite/session.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts index f0360be..5a2e9e8 100644 --- a/client/src/components/drizzle/crsqlite/session.ts +++ b/client/src/components/drizzle/crsqlite/session.ts @@ -1,23 +1,14 @@ -// import type { Client, InArgs, InStatement, ResultSet, Transaction } from '@libsql/client'; -import type { SQLite3, DB } from "@vlcn.io/crsqlite-wasm" -import type { BatchItem as BatchItem } from "drizzle-orm/batch" +import type { DB } from "@vlcn.io/crsqlite-wasm" import { entityKind } from "drizzle-orm/entity" import type { Logger } from "drizzle-orm/logger" import { NoopLogger } from "drizzle-orm/logger" import type { RelationalSchemaConfig, TablesRelationalConfig } from "drizzle-orm/relations" -import type { PreparedQuery } from "drizzle-orm/session" -import { fillPlaceholders, type Query, sql, type SQL } from "drizzle-orm/sql/sql" +import { fillPlaceholders, type Query } from "drizzle-orm/sql/sql" import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" import { SQLiteTransaction } from "drizzle-orm/sqlite-core" import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" -import type { - PreparedQueryConfig, - SQLiteExecuteMethod, - SQLiteTransactionConfig, -} from "drizzle-orm/sqlite-core/session" +import type { PreparedQueryConfig, SQLiteExecuteMethod } from "drizzle-orm/sqlite-core/session" import { SQLitePreparedQuery, SQLiteSession } from "drizzle-orm/sqlite-core/session" -import type { Dialect } from "drizzle-orm" -// import { mapResultRow } from 'drizzle-orm/utils'; type Transaction = Awaited> @@ -195,7 +186,7 @@ export class CRSQLPreparedQuery< if (this.oneTime) { void stmt.finalize(this.tx) } - return this.customResultMapper ? this.customResultMapper(rows) : rows + return this.customResultMapper ? (this.customResultMapper(rows) as unknown[]) : rows } /** From a2a71eeb28e4dd282882a2cd06320e7c1fddfd8d Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sat, 30 Mar 2024 19:04:37 +0100 Subject: [PATCH 20/21] Use own drizzle-orm-crsqlite-wasm library for crsqlite-wasm / drizzle --- client/src/components/drizzle/Test.tsx | 45 +-- .../src/components/drizzle/crsqlite/driver.ts | 44 --- .../src/components/drizzle/crsqlite/index.ts | 2 - .../components/drizzle/crsqlite/migrator.ts | 99 ------- .../components/drizzle/crsqlite/session.ts | 259 ------------------ .../src/components/drizzle/getMigrations.ts | 39 +++ package.json | 3 +- pnpm-lock.yaml | 24 +- shared/src/drizzle-test/schema.ts | 2 +- 9 files changed, 75 insertions(+), 442 deletions(-) delete mode 100644 client/src/components/drizzle/crsqlite/driver.ts delete mode 100644 client/src/components/drizzle/crsqlite/index.ts delete mode 100644 client/src/components/drizzle/crsqlite/migrator.ts delete mode 100644 client/src/components/drizzle/crsqlite/session.ts create mode 100644 client/src/components/drizzle/getMigrations.ts diff --git a/client/src/components/drizzle/Test.tsx b/client/src/components/drizzle/Test.tsx index e70c912..ffb4b39 100644 --- a/client/src/components/drizzle/Test.tsx +++ b/client/src/components/drizzle/Test.tsx @@ -1,39 +1,13 @@ -import type { DB } from "@vlcn.io/crsqlite-wasm" -import { drizzle, type CRSQLite3Database } from "./crsqlite" import * as schema from "shared/drizzle-test/schema" -import { eq, fillPlaceholders, type Dialect, type SQL } from "drizzle-orm" +import { eq } from "drizzle-orm" import { useEffect } from "react" import initWasm from "@vlcn.io/crsqlite-wasm" import tblrx from "@vlcn.io/rx-tbl" -// export function makeDrizzleDb(db: DB) { -// return drizzle(db, { schema }) -// } - -// const db = makeDrizzleDb({} as DB) - -// const res = db.query.countries -// .findMany({ -// with: { -// cities: { -// where: (city, { eq, sql }) => eq(city.name, sql.placeholder("cityName")), -// }, -// }, -// }) -// .prepare() - -// type foo = keyof typeof res & {} & string -// // ^? - -// res.finalize() - -// const data = await res.all({ cityName: "New York" }) -// // ^? - -// console.log(data) - -import { getMigrations, migrate } from "./crsqlite/migrator" +import { getMigrations } from "./getMigrations" import { useQuery, useQueryClient } from "@tanstack/react-query" +import { migrate } from "drizzle-orm-crsqlite-wasm/migrator" +import { drizzle, type CRSQLiteDatabase } from "drizzle-orm-crsqlite-wasm" async function make() { const sqlite = await initWasm() @@ -51,7 +25,7 @@ export function DrizzleTest() { make() .then((ctx) => { console.log("ctx", ctx) - client.setQueryData([key], { + client.setQueryData([key], { // schema: cleanSchema, // schemaName, name: "test", @@ -69,11 +43,17 @@ export function DrizzleTest() { ) } +declare module "drizzle-orm/session" { + export interface PreparedQuery { + finalize(): Promise + } +} + function TestChild() { const { data } = useQuery({ queryKey: ["test"] }) useEffect(() => { if (!data) return - const db = data.ctx.db as CRSQLite3Database + const db = data.ctx.db as CRSQLiteDatabase void (async function () { let [usa] = await db .select() @@ -121,6 +101,7 @@ function TestChild() { .all({ cityName: "New York" }) .then((a) => console.log("DRIZZLE", a)) .catch(console.error) + // @ts-expect-error -- the lib does not expose this method on the type level, but it does exist await res.finalize() console.log("--------------------------") const foo = await db.transaction(async (tx) => { diff --git a/client/src/components/drizzle/crsqlite/driver.ts b/client/src/components/drizzle/crsqlite/driver.ts deleted file mode 100644 index 48274f9..0000000 --- a/client/src/components/drizzle/crsqlite/driver.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { DefaultLogger } from "drizzle-orm/logger" -import { - createTableRelationsHelpers, - extractTablesRelationalConfig, - type RelationalSchemaConfig, - type TablesRelationalConfig, -} from "drizzle-orm/relations" -import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core" -import { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core" -import type { DrizzleConfig } from "drizzle-orm/utils" -import { CRSQLiteSession } from "./session" -import type { DB } from "@vlcn.io/crsqlite-wasm" - -export type CRSQLite3Database = Record> = - BaseSQLiteDatabase<"async", void, TSchema> - -export function drizzle = Record>( - client: DB, - config: DrizzleConfig = {} -): CRSQLite3Database { - const dialect = new SQLiteAsyncDialect() - let logger - if (config.logger === true) { - logger = new DefaultLogger() - } else if (config.logger !== false) { - logger = config.logger - } - - let schema: RelationalSchemaConfig | undefined - if (config.schema) { - const tablesConfig = extractTablesRelationalConfig( - config.schema, - createTableRelationsHelpers - ) - schema = { - fullSchema: config.schema, - schema: tablesConfig.tables, - tableNamesMap: tablesConfig.tableNamesMap, - } - } - - const session = new CRSQLiteSession(client, dialect, schema, { logger }) - return new BaseSQLiteDatabase("async", dialect, session, schema) as CRSQLite3Database -} diff --git a/client/src/components/drizzle/crsqlite/index.ts b/client/src/components/drizzle/crsqlite/index.ts deleted file mode 100644 index 4c057a6..0000000 --- a/client/src/components/drizzle/crsqlite/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./driver" -export * from "./session" diff --git a/client/src/components/drizzle/crsqlite/migrator.ts b/client/src/components/drizzle/crsqlite/migrator.ts deleted file mode 100644 index d0f675d..0000000 --- a/client/src/components/drizzle/crsqlite/migrator.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { MigrationMeta } from "drizzle-orm/migrator" -import type { CRSQLite3Database } from "./driver" -import migrationJournal from "shared/drizzle-migrations/meta/_journal.json" -import { migrations } from "shared/drizzle-migrations/index" -import { sql, type TablesRelationalConfig } from "drizzle-orm" -import type { SQLiteSession } from "drizzle-orm/sqlite-core" - -type MigrationConfig = { - /** @default "__drizzle_migrations" */ - migrationsTable?: string - migrations: MigrationMeta[] -} - -export async function migrate>( - db: CRSQLite3Database, - config: MigrationConfig -) { - const migrations = config.migrations - const migrationsTable = config.migrationsTable ?? "__drizzle_migrations" - const migrationTableIdent = sql.identifier(migrationsTable) - const migrationTableCreate = sql` - CREATE TABLE IF NOT EXISTS ${migrationTableIdent} ( - id TEXT NOT NULL PRIMARY KEY, - hash text NOT NULL, - created_at INTEGER - ) - ` - - // @ts-expect-error -- `session` exists but is marked as `@internal` on the type level - await (db.session as SQLiteSession<"async", void, TSchema, TablesRelationalConfig>).run( - migrationTableCreate - ) - type MigrationEntry = { id: string; hash: string; created_at: number } - - const dbMigrations = await db.get( - sql`SELECT id, hash, created_at FROM ${migrationTableIdent} ORDER BY created_at DESC LIMIT 1` - ) - - const lastDbMigration = dbMigrations ?? undefined - - console.log("migrations", migrations) - console.log("lastDbMigration", lastDbMigration) - console.log("dbMigrations", dbMigrations) - - for (const migration of migrations) { - if (!lastDbMigration || lastDbMigration.created_at < migration.folderMillis) { - console.log("migrating", migration) - for (const stmt of migration.sql) { - await db.run(sql.raw(stmt)) - } - - await db.run( - sql`INSERT INTO ${migrationTableIdent} ("id", "hash", "created_at") VALUES(${crypto.randomUUID()}, ${migration.hash}, ${migration.folderMillis})` - ) - } - } - console.log("done migrating") - - const dbMigrationsAfter = await db.get( - sql`SELECT id, hash, created_at FROM ${migrationTableIdent} ORDER BY created_at DESC LIMIT 1` - ) - console.log("dbMigrationsAfter", dbMigrationsAfter) -} - -/** - * TODO: this should be done at build-time through Vite `define` config - */ -export async function getMigrations() { - const journal = migrationJournal as { - entries: Array<{ idx: number; when: number; tag: string; breakpoints: boolean }> - } - const migrationQueries: MigrationMeta[] = [] - for (const journalEntry of journal.entries) { - const query = migrations[journalEntry.tag as keyof typeof migrations] - const result = query.split("--> statement-breakpoint") - migrationQueries.push({ - sql: result, - bps: journalEntry.breakpoints, - folderMillis: journalEntry.when, - hash: await createSha256Hash(query), - }) - } - return migrationQueries -} - -/** - * Cross-platform implementation of node's - * ```ts - * crypto.createHash("sha256").update(query).digest("hex") - * ``` - */ -async function createSha256Hash(query: string) { - const encoder = new TextEncoder() - const data = encoder.encode(query) - const hash = await globalThis.crypto.subtle.digest("SHA-256", data) - const hashArray = Array.from(new Uint8Array(hash)) - const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("") - return hashHex -} diff --git a/client/src/components/drizzle/crsqlite/session.ts b/client/src/components/drizzle/crsqlite/session.ts deleted file mode 100644 index 5a2e9e8..0000000 --- a/client/src/components/drizzle/crsqlite/session.ts +++ /dev/null @@ -1,259 +0,0 @@ -import type { DB } from "@vlcn.io/crsqlite-wasm" -import { entityKind } from "drizzle-orm/entity" -import type { Logger } from "drizzle-orm/logger" -import { NoopLogger } from "drizzle-orm/logger" -import type { RelationalSchemaConfig, TablesRelationalConfig } from "drizzle-orm/relations" -import { fillPlaceholders, type Query } from "drizzle-orm/sql/sql" -import type { SQLiteAsyncDialect } from "drizzle-orm/sqlite-core/dialect" -import { SQLiteTransaction } from "drizzle-orm/sqlite-core" -import type { SelectedFieldsOrdered } from "drizzle-orm/sqlite-core/query-builders/select.types" -import type { PreparedQueryConfig, SQLiteExecuteMethod } from "drizzle-orm/sqlite-core/session" -import { SQLitePreparedQuery, SQLiteSession } from "drizzle-orm/sqlite-core/session" - -type Transaction = Awaited> - -interface CRSQLiteSessionOptions { - logger?: Logger -} - -export class CRSQLiteSession< - TFullSchema extends Record, - TSchema extends TablesRelationalConfig, -> extends SQLiteSession<"async", void, TFullSchema, TSchema> { - static readonly [entityKind]: string = "CRSQLiteSession" - - private logger: Logger - - constructor( - private client: DB, - private dialect: SQLiteAsyncDialect, - private schema: RelationalSchemaConfig | undefined, - private options: CRSQLiteSessionOptions, - private tx?: Transaction[1] | undefined - ) { - super(dialect) - this.logger = options.logger ?? new NoopLogger() - console.log("CRSQLiteSession.constructor") - } - - prepareQuery( - query: Query, - fields: SelectedFieldsOrdered | undefined, - executeMethod: SQLiteExecuteMethod, - _isResponseInArrayMode: boolean, - customResultMapper?: (rows: unknown[][]) => unknown - ): CRSQLPreparedQuery { - console.log("CRSQLiteSession.prepareQuery", executeMethod, query) - return new CRSQLPreparedQuery( - this.client, - query, - false, - this.logger, - fields, - this.tx ?? null, - executeMethod, - customResultMapper - ) - } - - prepareOneTimeQuery( - query: Query, - fields: SelectedFieldsOrdered | undefined, - executeMethod: SQLiteExecuteMethod, - _isResponseInArrayMode: boolean - ): SQLitePreparedQuery { - console.log("CRSQLiteSession.prepareOneTimeQuery", executeMethod, query) - return new CRSQLPreparedQuery( - this.client, - query, - true, - this.logger, - fields, - this.tx ?? null, - executeMethod - ) - } - - override async transaction( - transaction: (db: CRSQLTransaction) => Promise - // _config?: SQLiteTransactionConfig - ): Promise { - console.log("CRSQLiteSession.transaction") - const crsqliteTx = await this.client.imperativeTx() - const session = new CRSQLiteSession( - this.client, - this.dialect, - this.schema, - this.options, - crsqliteTx[1] - ) - const tx = new CRSQLTransaction("async", this.dialect, session, this.schema) - try { - const result = await tx.transaction(transaction) - crsqliteTx[0]() - return result - } catch (err) { - crsqliteTx[0]() - throw err - } - } - - exec(query: string) { - console.log("CRSQLiteSession.exec") - this.logger.logQuery(query, []) - return (this.tx ?? this.client).exec(query) - } - - // TODO: can we implement these methods without going through a prepared query? (they are called when doing "one time queries") - // run(query: SQL) { - // console.log("CRSQLiteSession.run") - // return this.client.run(query) - // } - // all(query: SQL): Promise { - // console.log("CRSQLiteSession.all") - // return this.client.all(query) - // } - // get(query: SQL): Promise { - // console.log("CRSQLiteSession.get") - // return this.client.get(query) - // } - // values(query: SQL): Promise { - // console.log("CRSQLiteSession.values") - // return this.client.values(query) - // } -} - -type StmtAsync = Awaited> - -// TODO: this interface augmentation doesn't work, why? we do get a `SQLitePreparedQuery` when calling `.prepare()` but it doesn't have the `finalize` method at the type level -declare module "drizzle-orm/session" { - interface PreparedQuery { - finalize(): Promise - } -} - -export class CRSQLPreparedQuery< - T extends PreparedQueryConfig = PreparedQueryConfig, -> extends SQLitePreparedQuery<{ - type: "async" - run: void - all: T["all"] - get: T["get"] - values: T["values"] - execute: T["execute"] -}> { - static readonly [entityKind]: string = "CRSQLPreparedQuery" - - private stmt: Promise - - constructor( - private client: DB, - query: Query, - private oneTime: boolean, - private logger: Logger, - fields: SelectedFieldsOrdered | undefined, - private tx: Transaction[1] | null, - executeMethod: SQLiteExecuteMethod, - private customResultMapper?: (rows: unknown[][]) => unknown - ) { - super("async", executeMethod, query) - this.stmt = (this.tx ?? this.client).prepare(query.sql) - } - - /** - * execute query, no result expected - */ - async run(placeholderValues?: Record): Promise { - const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) - this.logger.logQuery(this.query.sql, params) - const stmt = await this.stmt - await stmt.run(this.tx, ...params) - if (this.oneTime) { - void stmt.finalize(this.tx) - } - } - - /** - * execute query and return all rows - */ - async all(placeholderValues?: Record): Promise { - const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) - this.logger.logQuery(this.query.sql, params) - const stmt = await this.stmt - stmt.raw(Boolean(this.customResultMapper)) - const rows = await stmt.all(this.tx, ...params) - console.log("CRSQLPreparedQuery.all", rows) - if (this.oneTime) { - void stmt.finalize(this.tx) - } - return this.customResultMapper ? (this.customResultMapper(rows) as unknown[]) : rows - } - - /** - * only query first row - */ - async get(placeholderValues?: Record): Promise { - const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) - this.logger.logQuery(this.query.sql, params) - const stmt = await this.stmt - stmt.raw(Boolean(this.customResultMapper)) - const row = await stmt.get(this.tx, ...params) - if (this.oneTime) { - void stmt.finalize(this.tx) - } - return this.customResultMapper ? this.customResultMapper([row]) : row - } - - /** - * directly extract first column value from each row - */ - async values(placeholderValues?: Record): Promise { - const params = fillPlaceholders(this.query.params, placeholderValues ?? {}) - this.logger.logQuery(this.query.sql, params) - const stmt = await this.stmt - stmt.raw(true) - const rows = (await stmt.all(null, ...params)) as unknown[][] - if (this.oneTime) { - void stmt.finalize(this.tx) - } - return rows.map((row) => row[0]) - } - - async finalize(): Promise { - if (this.oneTime) { - throw new Error("Cannot finalize one-time query") - } - const stmt = await this.stmt - await stmt.finalize(this.tx) - } -} - -export class CRSQLTransaction< - TFullSchema extends Record, - TSchema extends TablesRelationalConfig, -> extends SQLiteTransaction<"async", void, TFullSchema, TSchema> { - static readonly [entityKind]: string = "CRSQLTransaction" - - override async transaction( - transaction: (tx: CRSQLTransaction) => Promise - ): Promise { - console.log("CRSQLTransaction.transaction inside transaction function of transaction class") - const savepointName = `sp${this.nestedIndex}` - const tx = new CRSQLTransaction( - "async", - this.dialect, - this.session, - this.schema, - this.nestedIndex + 1 - ) - await this.session.exec(`SAVEPOINT ${savepointName};`) - try { - const result = await transaction(tx) - await this.session.exec(`RELEASE savepoint ${savepointName};`) - return result - } catch (err) { - await this.session.exec(`ROLLBACK TO savepoint ${savepointName};`) - throw err - } - } -} diff --git a/client/src/components/drizzle/getMigrations.ts b/client/src/components/drizzle/getMigrations.ts new file mode 100644 index 0000000..8e64c58 --- /dev/null +++ b/client/src/components/drizzle/getMigrations.ts @@ -0,0 +1,39 @@ +import type { MigrationMeta } from "drizzle-orm/migrator" +import migrationJournal from "shared/drizzle-migrations/meta/_journal.json" +import { migrations } from "shared/drizzle-migrations/index" + +/** + * TODO: this should be done at build-time through Vite `define` config + */ +export async function getMigrations() { + const journal = migrationJournal as { + entries: Array<{ idx: number; when: number; tag: string; breakpoints: boolean }> + } + const migrationQueries: MigrationMeta[] = [] + for (const journalEntry of journal.entries) { + const query = migrations[journalEntry.tag as keyof typeof migrations] as string + const result = query.split("--> statement-breakpoint") + migrationQueries.push({ + sql: result, + bps: journalEntry.breakpoints, + folderMillis: journalEntry.when, + hash: await createSha256Hash(query), + }) + } + return migrationQueries +} + +/** + * Cross-platform implementation of node's + * ```ts + * crypto.createHash("sha256").update(query).digest("hex") + * ``` + */ +async function createSha256Hash(query: string) { + const encoder = new TextEncoder() + const data = encoder.encode(query) + const hash = await globalThis.crypto.subtle.digest("SHA-256", data) + const hashArray = Array.from(new Uint8Array(hash)) + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("") + return hashHex +} diff --git a/package.json b/package.json index 695da6f..1da6175 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "better-sqlite3": "^9.4.3", "clsx": "^2.1.0", "dotenv": "^16.4.5", - "drizzle-orm": "^0.30.4", + "drizzle-orm": "^0.30.6", + "drizzle-orm-crsqlite-wasm": "0.0.2", "fastify": "^4.26.2", "grant": "^5.4.22", "pino-pretty": "10.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98aea33..5209eb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,11 @@ importers: specifier: ^16.4.5 version: 16.4.5 drizzle-orm: - specifier: ^0.30.4 - version: 0.30.4(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) + specifier: ^0.30.6 + version: 0.30.6(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) + drizzle-orm-crsqlite-wasm: + specifier: 0.0.2 + version: 0.0.2(@vlcn.io/xplat-api@0.15.0)(drizzle-orm@0.30.6) fastify: specifier: ^4.26.2 version: 4.26.2 @@ -3007,11 +3010,22 @@ packages: - supports-color dev: true - /drizzle-orm@0.30.4(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0): - resolution: {integrity: sha512-kWoSMGbrOFkmkAweLTFtHJMpN+nwhx89q0mLELqT2aEU+1szNV8jrnBmJwZ0WGNp7J7yQn/ezEtxBI/qzTSElQ==} + /drizzle-orm-crsqlite-wasm@0.0.2(@vlcn.io/xplat-api@0.15.0)(drizzle-orm@0.30.6): + resolution: {integrity: sha512-QEvgdS8GTr7j9Lt+iuiZ+6iRlp5zAbZZAwjd1gTSlnorKjwPykciekP9ujrolitHh7sYMuZNyrv3OkPjYxQIng==} + peerDependencies: + '@vlcn.io/xplat-api': ^0.15.0 + drizzle-orm: ^0.30.6 + dependencies: + '@vlcn.io/xplat-api': 0.15.0 + drizzle-orm: 0.30.6(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) + dev: false + + /drizzle-orm@0.30.6(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0): + resolution: {integrity: sha512-8RgNUmY7J03GRuRgBV5SaJNbYgLVPjdSWNS/bRkIMIHt2TFCA439lJsNpqYX8asyKMqkw8ceBiamUnCIXZIt9w==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' + '@electric-sql/pglite': '>=0.1.1' '@libsql/client': '*' '@neondatabase/serverless': '>=0.1' '@op-engineering/op-sqlite': '>=2' @@ -3039,6 +3053,8 @@ packages: optional: true '@cloudflare/workers-types': optional: true + '@electric-sql/pglite': + optional: true '@libsql/client': optional: true '@neondatabase/serverless': diff --git a/shared/src/drizzle-test/schema.ts b/shared/src/drizzle-test/schema.ts index 3f7baed..689877a 100644 --- a/shared/src/drizzle-test/schema.ts +++ b/shared/src/drizzle-test/schema.ts @@ -1,4 +1,4 @@ -import { sqliteTable, text, integer, uniqueIndex, index } from "drizzle-orm/sqlite-core" +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core" import { relations } from "drizzle-orm" export const countries = sqliteTable( From 4d0df15cff8b20634806f64cf7a13b5623ac83b2 Mon Sep 17 00:00:00 2001 From: Sheraff Date: Sat, 30 Mar 2024 19:26:16 +0100 Subject: [PATCH 21/21] Update drizzle-orm-crsqlite-wasm version to 0.0.3 --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1da6175..faa92f6 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "clsx": "^2.1.0", "dotenv": "^16.4.5", "drizzle-orm": "^0.30.6", - "drizzle-orm-crsqlite-wasm": "0.0.2", + "drizzle-orm-crsqlite-wasm": "0.0.3", "fastify": "^4.26.2", "grant": "^5.4.22", "pino-pretty": "10.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5209eb3..654adcf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: ^0.30.6 version: 0.30.6(@types/better-sqlite3@7.6.9)(@types/react@18.2.64)(better-sqlite3@9.4.3)(react@18.2.0) drizzle-orm-crsqlite-wasm: - specifier: 0.0.2 - version: 0.0.2(@vlcn.io/xplat-api@0.15.0)(drizzle-orm@0.30.6) + specifier: 0.0.3 + version: 0.0.3(@vlcn.io/xplat-api@0.15.0)(drizzle-orm@0.30.6) fastify: specifier: ^4.26.2 version: 4.26.2 @@ -3010,8 +3010,8 @@ packages: - supports-color dev: true - /drizzle-orm-crsqlite-wasm@0.0.2(@vlcn.io/xplat-api@0.15.0)(drizzle-orm@0.30.6): - resolution: {integrity: sha512-QEvgdS8GTr7j9Lt+iuiZ+6iRlp5zAbZZAwjd1gTSlnorKjwPykciekP9ujrolitHh7sYMuZNyrv3OkPjYxQIng==} + /drizzle-orm-crsqlite-wasm@0.0.3(@vlcn.io/xplat-api@0.15.0)(drizzle-orm@0.30.6): + resolution: {integrity: sha512-hkTppNrDfSABGWvnFklY2+8vNcwlFcKQkPksbCTzdiz/xQKftSfcKyhi2uKiCVRVss5yEreCDUDoP9PVVE5+dw==} peerDependencies: '@vlcn.io/xplat-api': ^0.15.0 drizzle-orm: ^0.30.6