Drop-in replacement for @prisma/client that runs natively — no Node.js, no Prisma engine, no network overhead. Built for Perry apps targeting macOS, iOS, Linux, Android, and Windows.
Uses sqlx with SQLite under the hood. Same API as perry-prisma (MySQL) and perry-postgres, ~95% identical Rust code.
TypeScript → JSON string → FFI → Rust (sqlx + SQLite) → JSON result → TypeScript
import { PrismaClient } from 'perry-sqlite';
const prisma = new PrismaClient();
await prisma.$connect();
const user = await prisma.user.create({
data: { id: 'u1', email: 'alice@example.com', passwordHash: 'hash' },
});
const users = await prisma.user.findMany({
where: { email: { contains: '@example.com' } },
orderBy: { email: 'asc' },
take: 10,
});
await prisma.$disconnect();Set DATABASE_URL to a SQLite URL before connecting:
export DATABASE_URL=sqlite:/path/to/app.db| Operation | Notes |
|---|---|
findMany |
where, orderBy, take, skip, select, distinct |
findFirst |
Returns null if not found |
findUnique |
Returns null if not found |
findUniqueOrThrow |
Throws if not found |
create |
Returns created record |
createMany |
skipDuplicates via INSERT OR IGNORE |
update |
Returns updated record |
updateMany |
Returns { count } |
upsert |
Create-or-update |
delete |
Returns deleted record |
deleteMany |
Returns { count } |
count |
Supports where filters |
$transaction |
Serializable via dedicated single-connection pool |
$executeRaw |
Returns affected row count |
$queryRaw |
Returns array of row objects |
// Equality
{ where: { email: 'alice@example.com' } }
// String operators
{ where: { email: { contains: 'example', startsWith: 'alice', endsWith: '.com' } } }
// Numeric operators
{ where: { chainId: { gt: 1, lte: 100 } } }
// Null checks
{ where: { lastActiveAt: { isNull: true } } }
{ where: { lastActiveAt: { isNotNull: true } } }
// Set membership
{ where: { id: { in: ['u1', 'u2'] } } }
{ where: { id: { notIn: ['u3'] } } }
// Logical combinators
{ where: { AND: [{ email: 'a@b.com' }, { passwordHash: 'x' }] } }
{ where: { OR: [{ email: 'a@b.com' }, { email: 'b@b.com' }] } }
{ where: { NOT: { email: 'alice@example.com' } } }build.rs reads your Prisma schema at compile time to generate model metadata. Point it at your schema:
# Option 1: env var
export PRISMA_SCHEMA_PATH=/path/to/prisma/schema.prisma
# Option 2: default location (relative to native/)
# ../../chainblick/api/prisma/schema.prismaOnly scalar fields are mapped to columns. Relation fields are tracked as metadata but not queried.
# Type-check the Rust crate
cd native && cargo check
# Cross-compile for iOS
cd native && cargo check --target aarch64-apple-ios
# Perry type-check (from package root)
perry checkcd tests
./run.sh
# or: DATABASE_URL=sqlite:/tmp/test.db ./run.shTests cover all 26 operation categories: CRUD, filters, orderBy, pagination, upsert, transactions, raw SQL, and more (45 assertions total).
perry-sqlite/
├── src/index.ts # PrismaClient, ModelClient, FFI declarations
├── native/
│ ├── src/lib.rs # Query builder, row→JSON conversion, FFI exports
│ ├── build.rs # Prisma schema parser → generated_models.rs
│ └── Cargo.toml
├── tests/
│ ├── test.ts # Integration test suite
│ └── run.sh
└── perry.config.ts # Perry compiler config (targets, FFI)
Query flow:
- TypeScript serializes query args to JSON
- JSON string crosses the FFI boundary to Rust
- Rust parses the query, builds SQL with bound parameters, executes via sqlx
- Result rows are serialized back to JSON
- TypeScript receives the JSON string and parses it
Transactions use a dedicated max_connections(1) SqlitePool per transaction, ensuring BEGIN, all queries, and COMMIT/ROLLBACK run on the same physical connection.
| Behavior | SQLite | MySQL (perry-prisma) |
|---|---|---|
| Identifier quoting | "col" |
`col` |
| Skip duplicates | INSERT OR IGNORE |
INSERT IGNORE |
| Unlimited offset | LIMIT -1 OFFSET n |
LIMIT 18446744073709551615 OFFSET n |
| Last insert ID | last_insert_rowid() |
last_insert_id() |
| Boolean binding | native bool | i8 |
| TLS deps | none | Security.framework / OpenSSL |
MIT