Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions docs/3.integrations/prisma.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ icon: simple-icons:prisma

:read-more{to="https://www.prisma.io"}

## Example

::read-more{to="https://github.com/unjs/db0/issues/50"}
An example for this integration is planned. Follow up via [unjs/db0#50](https://github.com/unjs/db0/issues/50).
::
Initialize your database with Prisma integration:

```ts [database.ts]
import { createDatabase } from "db0";
import sqlite from "db0/connectors/better-sqlite3";
import { prisma } from "db0/integrations/prisma";
import * as schema from "./schema";

// Initialize DB instance with SQLite connector
const db0 = createDatabase(sqlite({ name: 'database.sqlite' }));

// Create Prisma Client with DB0 adapter
const adapter = prisma(db0);
export const prisma = new PrismaClient({ adapter });
```
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@
"@electric-sql/pglite": "^0.3.15",
"@libsql/client": "^0.17.0",
"@planetscale/database": "^1.19.0",
"@prisma/client": "^7.2.0",
"@prisma/driver-adapter-utils": "^7.2.0",
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "^1.3.6",
"@types/pg": "^8.16.0",
"@typescript/native-preview": "7.0.0-dev.20260122.3",
"@vitest/coverage-v8": "^4.0.17",
"async-mutex": "^0.5.0",
"automd": "^0.4.2",
"better-sqlite3": "^12.6.2",
"changelogen": "^0.6.2",
Expand All @@ -65,6 +68,7 @@
"obuild": "^0.4.19",
"pg": "^8.17.2",
"prettier": "^3.8.1",
"prisma": "^7.2.0",
"scule": "^1.3.0",
"typescript": "^5.9.3",
"vitest": "^4.0.17",
Expand All @@ -73,6 +77,7 @@
"peerDependencies": {
"@electric-sql/pglite": "*",
"@libsql/client": "*",
"@prisma/driver-adapter-utils": "*",
"better-sqlite3": "*",
"drizzle-orm": "*",
"mysql2": "*",
Expand All @@ -82,6 +87,9 @@
"@libsql/client": {
"optional": true
},
"@prisma/driver-adapter-utils": {
"optional": true
},
"better-sqlite3": {
"optional": true
},
Expand Down
1,824 changes: 1,125 additions & 699 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

264 changes: 264 additions & 0 deletions src/integrations/prisma/_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import type { Primitive, SQLDialect } from "../../types.ts";
import type { SqlQuery, ColumnType } from "@prisma/driver-adapter-utils";
import { ColumnTypeEnum } from "@prisma/driver-adapter-utils";

export const convertDialectColumnToEnum = (dialect: SQLDialect, column: string): ColumnType | null => {
const columnType = column.toUpperCase();

switch (dialect) {
case 'postgresql': {
switch (columnType) {
case '': {
return null
}
case 'DECIMAL': {
return ColumnTypeEnum.Numeric
}
case 'FLOAT': {
return ColumnTypeEnum.Float
}
case 'DOUBLE':
case 'DOUBLE PRECISION':
case 'NUMERIC':
case 'REAL': {
return ColumnTypeEnum.Double
}
case 'TINYINT':
case 'SMALLINT':
case 'MEDIUMINT':
case 'INT':
case 'INTEGER':
case 'SERIAL':
case 'INT2': {
return ColumnTypeEnum.Int32
}
case 'BIGINT':
case 'UNSIGNED BIG INT':
case 'INT8': {
return ColumnTypeEnum.Int64
}
case 'DATETIME':
case 'TIMESTAMP': {
return ColumnTypeEnum.DateTime
}
case 'TIME': {
return ColumnTypeEnum.Time
}
case 'DATE': {
return ColumnTypeEnum.Date
}
case 'TEXT':
case 'CLOB':
case 'CHARACTER':
case 'VARCHAR':
case 'VARYING CHARACTER':
case 'NCHAR':
case 'NATIVE CHARACTER':
case 'NVARCHAR': {
return ColumnTypeEnum.Text
}
case 'BLOB': {
return ColumnTypeEnum.Bytes
}
case 'BOOLEAN': {
return ColumnTypeEnum.Boolean
}
case 'JSONB': {
return ColumnTypeEnum.Json
}
default: {
return null
}
}
}
case 'libsql': {
switch (columnType) {
case '': {
return null
}
case 'DECIMAL': {
return ColumnTypeEnum.Numeric
}
case 'FLOAT': {
return ColumnTypeEnum.Float
}
case 'DOUBLE':
case 'DOUBLE PRECISION':
case 'NUMERIC':
case 'REAL': {
return ColumnTypeEnum.Double
}
case 'TINYINT':
case 'SMALLINT':
case 'MEDIUMINT':
case 'INT':
case 'INTEGER':
case 'SERIAL':
case 'INT2': {
return ColumnTypeEnum.Int32
}
case 'BIGINT':
case 'UNSIGNED BIG INT':
case 'INT8': {
return ColumnTypeEnum.Int64
}
case 'DATETIME':
case 'TIMESTAMP': {
return ColumnTypeEnum.DateTime
}
case 'TIME': {
return ColumnTypeEnum.Time
}
case 'DATE': {
return ColumnTypeEnum.Date
}
case 'TEXT':
case 'CLOB':
case 'CHARACTER':
case 'VARCHAR':
case 'VARYING CHARACTER':
case 'NCHAR':
case 'NATIVE CHARACTER':
case 'NVARCHAR': {
return ColumnTypeEnum.Text
}
case 'BLOB': {
return ColumnTypeEnum.Bytes
}
case 'BOOLEAN': {
return ColumnTypeEnum.Boolean
}
case 'JSONB': {
return ColumnTypeEnum.Json
}
default: {
return null
}
}
}
case 'sqlite': {
switch (columnType) {
case '': {
return null
}
case 'DECIMAL': {
return ColumnTypeEnum.Numeric
}
case 'FLOAT': {
return ColumnTypeEnum.Float
}
case 'DOUBLE':
case 'DOUBLE PRECISION':
case 'NUMERIC':
case 'REAL': {
return ColumnTypeEnum.Double
}
case 'TINYINT':
case 'SMALLINT':
case 'MEDIUMINT':
case 'INT':
case 'INTEGER':
case 'SERIAL':
case 'INT2': {
return ColumnTypeEnum.Int32
}
case 'BIGINT':
case 'UNSIGNED BIG INT':
case 'INT8': {
return ColumnTypeEnum.Int64
}
case 'DATETIME':
case 'TIMESTAMP': {
return ColumnTypeEnum.DateTime
}
case 'TIME': {
return ColumnTypeEnum.Time
}
case 'DATE': {
return ColumnTypeEnum.Date
}
case 'TEXT':
case 'CLOB':
case 'CHARACTER':
case 'VARCHAR':
case 'VARYING CHARACTER':
case 'NCHAR':
case 'NATIVE CHARACTER':
case 'NVARCHAR': {
return ColumnTypeEnum.Text
}
case 'BLOB': {
return ColumnTypeEnum.Bytes
}
case 'BOOLEAN': {
return ColumnTypeEnum.Boolean
}
case 'JSONB': {
return ColumnTypeEnum.Json
}
default: {
return null
}
}
}
default: {
return null
}
}
}

export const getQueryArgs = (query: SqlQuery, options?: Record<string, unknown>): Array<bigint | Primitive | Buffer<ArrayBuffer>> => {
return ((query.args || []) as Primitive[]).map((arg: Primitive | Date, i) => {
const argType = query.argTypes[i]
if (arg === null) {
return null
}

if (typeof arg === 'string' && argType.scalarType === 'int') {
return Number.parseInt(arg)
}

if (typeof arg === 'string' && argType.scalarType === 'float') {
return Number.parseFloat(arg)
}

if (typeof arg === 'string' && argType.scalarType === 'decimal') {
// This can lose precision, but SQLite does not have a native decimal type.
// This is how we have historically handled it.
return Number.parseFloat(arg)
}

if (typeof arg === 'string' && argType.scalarType === 'bigint') {
return BigInt(arg)
}

if (typeof arg === 'boolean') {
return arg ? 1 : 0 // SQLite does not natively support booleans
}

if (typeof arg === 'string' && argType.scalarType === 'datetime') {
arg = new Date(arg)
}

if (arg instanceof Date) {
const format = options?.timestampFormat ?? 'iso8601'
switch (format) {
case 'unixepoch-ms': {
return arg.getTime()
}
case 'iso8601': {
return arg.toISOString().replace('Z', '+00:00')
}
default: {
throw new Error(`Unknown timestamp format: ${format}`)
}
}
}

if (typeof arg === 'string' && argType.scalarType === 'bytes') {
return Buffer.from(arg, 'base64')
}

return arg
})
}
Loading