nanodb-orm 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +379 -39
- package/dist/cli.d.ts +84 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +227 -149
- package/dist/cli.js.map +1 -1
- package/dist/core/connection.d.ts +6 -0
- package/dist/core/connection.d.ts.map +1 -1
- package/dist/core/connection.js +17 -3
- package/dist/core/connection.js.map +1 -1
- package/dist/index.d.ts +206 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +222 -50
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +114 -62
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +79 -44
- package/dist/init.js.map +1 -1
- package/dist/types/index.d.ts +176 -38
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/migrations.d.ts +2 -2
- package/dist/utils/migrations.d.ts.map +1 -1
- package/dist/utils/migrations.js +53 -48
- package/dist/utils/migrations.js.map +1 -1
- package/dist/utils/schema-introspection.d.ts +4 -4
- package/dist/utils/schema-introspection.d.ts.map +1 -1
- package/dist/utils/schema-introspection.js +0 -3
- package/dist/utils/schema-introspection.js.map +1 -1
- package/dist/utils/seeds.d.ts +2 -2
- package/dist/utils/seeds.d.ts.map +1 -1
- package/dist/utils/seeds.js +10 -1
- package/dist/utils/seeds.js.map +1 -1
- package/dist/utils/sync.d.ts.map +1 -1
- package/dist/utils/sync.js +11 -2
- package/dist/utils/sync.js.map +1 -1
- package/dist/utils/transactions.d.ts +58 -2
- package/dist/utils/transactions.d.ts.map +1 -1
- package/dist/utils/transactions.js +89 -8
- package/dist/utils/transactions.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# nanodb-orm
|
|
2
2
|
|
|
3
|
-
A lightweight
|
|
3
|
+
A lightweight ORM wrapper for Drizzle ORM with automatic migrations, schema introspection, CLI tools, and support for SQLite/Turso databases.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
+
- **TypeScript First** — Full type inference from schema to queries
|
|
7
8
|
- **Auto-Migrations** — Automatically creates and updates database schemas from Drizzle tables
|
|
8
9
|
- **Schema Introspection** — Comprehensive schema analysis and validation
|
|
9
10
|
- **Multi-Database** — Works with local SQLite and remote Turso databases
|
|
10
11
|
- **Transactions** — Full transaction support with automatic rollback
|
|
11
12
|
- **CLI Tools** — Built-in commands including Drizzle Studio integration
|
|
12
|
-
- **Type Safe** — Full TypeScript support with proper type inference
|
|
13
13
|
- **Plugin System** — Extensible with hooks for audit, validation, transformations
|
|
14
14
|
- **Minimal** — ~1K lines of code, zero bloat
|
|
15
15
|
|
|
@@ -54,65 +54,136 @@ npx nanodb studio
|
|
|
54
54
|
|
|
55
55
|

|
|
56
56
|
|
|
57
|
+
## Import Styles
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Default import (recommended)
|
|
61
|
+
import nanodb from 'nanodb-orm';
|
|
62
|
+
|
|
63
|
+
const users = nanodb.schema.table('users', { ... });
|
|
64
|
+
const db = await nanodb.createDatabase({ tables: { users } });
|
|
65
|
+
await db.select().from(users).where(nanodb.query.eq(users.id, 1));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Named imports
|
|
70
|
+
import { createDatabase, schema, query } from 'nanodb-orm';
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Individual imports (tree-shakeable)
|
|
75
|
+
import { createDatabase, table, integer, text, eq } from 'nanodb-orm';
|
|
76
|
+
```
|
|
77
|
+
|
|
57
78
|
## Quick Start
|
|
58
79
|
|
|
59
80
|
### 1. Define Your Schema
|
|
60
81
|
|
|
61
82
|
```typescript
|
|
62
|
-
import
|
|
83
|
+
import nanodb from 'nanodb-orm';
|
|
63
84
|
|
|
64
|
-
const users = table('users', {
|
|
65
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
66
|
-
name: text('name').notNull(),
|
|
67
|
-
email: text('email').unique().notNull(),
|
|
68
|
-
age: integer('age'),
|
|
85
|
+
const users = nanodb.schema.table('users', {
|
|
86
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
87
|
+
name: nanodb.schema.text('name').notNull(),
|
|
88
|
+
email: nanodb.schema.text('email').unique().notNull(),
|
|
89
|
+
age: nanodb.schema.integer('age'),
|
|
69
90
|
});
|
|
70
91
|
|
|
71
|
-
const posts = table('posts', {
|
|
72
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
73
|
-
title: text('title').notNull(),
|
|
74
|
-
userId: integer('userId').notNull(),
|
|
92
|
+
const posts = nanodb.schema.table('posts', {
|
|
93
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
94
|
+
title: nanodb.schema.text('title').notNull(),
|
|
95
|
+
userId: nanodb.schema.integer('userId').notNull(),
|
|
75
96
|
});
|
|
76
|
-
|
|
77
|
-
const tables = { users, posts };
|
|
78
97
|
```
|
|
79
98
|
|
|
80
99
|
### 2. Create Database
|
|
81
100
|
|
|
82
101
|
```typescript
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// One line: creates tables, runs migrations, seeds data, returns db
|
|
86
|
-
const db = await createDatabase({
|
|
87
|
-
tables,
|
|
102
|
+
const db = await nanodb.createDatabase({
|
|
103
|
+
tables: { users, posts },
|
|
88
104
|
seedData: {
|
|
89
105
|
users: [{ name: 'Alice', email: 'alice@example.com', age: 28 }],
|
|
90
106
|
},
|
|
91
107
|
});
|
|
92
108
|
|
|
93
|
-
// Store db and use it everywhere - no need for getInstance() or getDb()
|
|
94
109
|
export { db };
|
|
95
110
|
```
|
|
96
111
|
|
|
97
|
-
### 3. Query
|
|
112
|
+
### 3. Query Your Data
|
|
98
113
|
|
|
99
114
|
```typescript
|
|
100
|
-
import { eq, gte } from 'nanodb-orm';
|
|
101
|
-
|
|
102
115
|
// SELECT
|
|
103
116
|
const allUsers = await db.select().from(users);
|
|
104
|
-
const adults = await db.select().from(users).where(gte(users.age, 18));
|
|
117
|
+
const adults = await db.select().from(users).where(nanodb.query.gte(users.age, 18));
|
|
105
118
|
|
|
106
119
|
// INSERT
|
|
107
120
|
await db.insert(users).values({ name: 'Bob', email: 'bob@example.com' });
|
|
108
121
|
|
|
109
122
|
// UPDATE
|
|
110
|
-
await db.update(users).set({ name: 'Robert' }).where(eq(users.email, 'bob@example.com'));
|
|
123
|
+
await db.update(users).set({ name: 'Robert' }).where(nanodb.query.eq(users.email, 'bob@example.com'));
|
|
111
124
|
|
|
112
125
|
// DELETE
|
|
113
|
-
await db.delete(users).where(eq(users.email, 'bob@example.com'));
|
|
126
|
+
await db.delete(users).where(nanodb.query.eq(users.email, 'bob@example.com'));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Type Inference
|
|
130
|
+
|
|
131
|
+
nanodb-orm provides full type inference from your schema:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import {
|
|
135
|
+
createDatabase,
|
|
136
|
+
table,
|
|
137
|
+
integer,
|
|
138
|
+
text,
|
|
139
|
+
type SelectModel,
|
|
140
|
+
type InsertModel,
|
|
141
|
+
} from 'nanodb-orm';
|
|
142
|
+
|
|
143
|
+
const users = table('users', {
|
|
144
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
145
|
+
name: text('name').notNull(),
|
|
146
|
+
email: text('email').notNull(),
|
|
147
|
+
age: integer('age'),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Infer types directly from your table definitions
|
|
151
|
+
type User = SelectModel<typeof users>;
|
|
152
|
+
// { id: number; name: string; email: string; age: number | null }
|
|
153
|
+
|
|
154
|
+
type NewUser = InsertModel<typeof users>;
|
|
155
|
+
// { id?: number; name: string; email: string; age?: number | null }
|
|
156
|
+
|
|
157
|
+
// The database is fully typed
|
|
158
|
+
const db = await createDatabase({ tables: { users } });
|
|
159
|
+
|
|
160
|
+
// All operations are type-safe
|
|
161
|
+
const allUsers: User[] = await db.select().from(users);
|
|
162
|
+
|
|
163
|
+
// Seed data is type-checked at compile time
|
|
164
|
+
const db2 = await createDatabase({
|
|
165
|
+
tables: { users },
|
|
166
|
+
seedData: {
|
|
167
|
+
users: [
|
|
168
|
+
{ name: 'Alice', email: 'alice@example.com' }, // ✓ Valid
|
|
169
|
+
// { name: 123 }, // ✗ TypeScript error!
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
});
|
|
114
173
|
```
|
|
115
174
|
|
|
175
|
+
### Available Type Utilities
|
|
176
|
+
|
|
177
|
+
| Type | Description |
|
|
178
|
+
|------|-------------|
|
|
179
|
+
| `SelectModel<T>` | Infer the row type (SELECT result) from a table |
|
|
180
|
+
| `InsertModel<T>` | Infer the insert type from a table (optional auto-generated columns) |
|
|
181
|
+
| `SchemaModels<S>` | Extract all row types from a schema object |
|
|
182
|
+
| `SchemaInsertModels<S>` | Extract all insert types from a schema |
|
|
183
|
+
| `NanoDatabase<S>` | The typed database instance |
|
|
184
|
+
| `Schema` | Type for schema objects |
|
|
185
|
+
| `AnyTable` | Type constraint for Drizzle tables |
|
|
186
|
+
|
|
116
187
|
## API Reference
|
|
117
188
|
|
|
118
189
|
### `createDatabase(config)`
|
|
@@ -122,7 +193,7 @@ Create and initialize database. Returns `db` with all utilities attached.
|
|
|
122
193
|
```typescript
|
|
123
194
|
const db = await createDatabase({
|
|
124
195
|
tables: { users, posts },
|
|
125
|
-
seedData: { users: [...] },
|
|
196
|
+
seedData: { users: [...] }, // Type-checked against schema
|
|
126
197
|
migrationConfig: {
|
|
127
198
|
preserveData: true, // default: true
|
|
128
199
|
autoMigrate: true, // default: true
|
|
@@ -149,7 +220,7 @@ await db.clearData(); // Delete all data (keep tables)
|
|
|
149
220
|
### Schema Introspection (from `db.schema`)
|
|
150
221
|
|
|
151
222
|
```typescript
|
|
152
|
-
db.schema.tables(); // ['users', 'posts']
|
|
223
|
+
db.schema.tables(); // ['users', 'posts'] - typed as (keyof Schema)[]
|
|
153
224
|
db.schema.getTable('users'); // { columns, primaryKey, indexes }
|
|
154
225
|
db.schema.getColumns('users'); // ['id', 'name', 'email']
|
|
155
226
|
await db.schema.validate(); // { isValid, missingTables, ... }
|
|
@@ -165,24 +236,31 @@ await db.migrations.validate(); // Validate schema vs DB
|
|
|
165
236
|
await db.migrations.checkTables(); // { users: true, posts: true }
|
|
166
237
|
```
|
|
167
238
|
|
|
168
|
-
### `transaction(fn)`
|
|
239
|
+
### `transaction(fn)` / `batch(statements)`
|
|
169
240
|
|
|
170
|
-
Execute operations atomically.
|
|
241
|
+
Execute operations atomically. Uses Drizzle's native transaction when available (better for Turso).
|
|
171
242
|
|
|
172
243
|
```typescript
|
|
173
|
-
import
|
|
244
|
+
import nanodb from 'nanodb-orm';
|
|
174
245
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
await tx.run(sql`INSERT INTO
|
|
246
|
+
// Transaction with custom logic
|
|
247
|
+
const result = await nanodb.transaction(async (tx) => {
|
|
248
|
+
await tx.run(nanodb.query.sql`INSERT INTO users (name) VALUES ('Alice')`);
|
|
249
|
+
await tx.run(nanodb.query.sql`INSERT INTO posts (title, userId) VALUES ('Hello', 1)`);
|
|
178
250
|
return { created: true };
|
|
179
251
|
});
|
|
180
252
|
|
|
181
253
|
if (result.success) {
|
|
182
|
-
console.log(result.result);
|
|
254
|
+
console.log(result.result); // { created: true }
|
|
183
255
|
} else {
|
|
184
256
|
console.log('Rolled back:', result.error?.message);
|
|
185
257
|
}
|
|
258
|
+
|
|
259
|
+
// Batch multiple statements (simpler for bulk operations)
|
|
260
|
+
const batchResult = await nanodb.batch([
|
|
261
|
+
nanodb.query.sql`INSERT INTO users (name) VALUES ('Bob')`,
|
|
262
|
+
nanodb.query.sql`INSERT INTO users (name) VALUES ('Carol')`,
|
|
263
|
+
]);
|
|
186
264
|
```
|
|
187
265
|
|
|
188
266
|
### `parseDbError(error, context)`
|
|
@@ -193,10 +271,10 @@ Parse SQLite errors into user-friendly messages.
|
|
|
193
271
|
import { parseDbError } from 'nanodb-orm';
|
|
194
272
|
|
|
195
273
|
try {
|
|
196
|
-
await db.
|
|
274
|
+
await db.insert(users).values({ email: 'duplicate@example.com' });
|
|
197
275
|
} catch (error) {
|
|
198
276
|
const parsed = parseDbError(error, { table: 'users', operation: 'insert' });
|
|
199
|
-
console.log(parsed.message); // "
|
|
277
|
+
console.log(parsed.message); // "Duplicate value for unique column 'email'"
|
|
200
278
|
}
|
|
201
279
|
```
|
|
202
280
|
|
|
@@ -282,6 +360,192 @@ await db.insert(users).values({ email: 'invalid' }); // throws error
|
|
|
282
360
|
db.plugins.list(); // ['audit', 'slug', 'validation']
|
|
283
361
|
```
|
|
284
362
|
|
|
363
|
+
## Best Practices
|
|
364
|
+
|
|
365
|
+
### Recommended Project Structure
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
db/
|
|
369
|
+
├── schema.ts # Table definitions
|
|
370
|
+
├── index.ts # Database instance export
|
|
371
|
+
├── types.ts # Type aliases (SelectModel, InsertModel)
|
|
372
|
+
├── plugins.ts # Custom plugins
|
|
373
|
+
└── seeds.ts # Seed data
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 1. Schema Order Matters
|
|
377
|
+
|
|
378
|
+
Define parent tables before children for correct seeding order:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// db/schema.ts
|
|
382
|
+
import nanodb from 'nanodb-orm';
|
|
383
|
+
|
|
384
|
+
// Parent tables first (no foreign keys)
|
|
385
|
+
export const users = nanodb.schema.table('users', {
|
|
386
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
387
|
+
name: nanodb.schema.text('name').notNull(),
|
|
388
|
+
email: nanodb.schema.text('email').notNull().unique(),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
export const categories = nanodb.schema.table('categories', {
|
|
392
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
393
|
+
name: nanodb.schema.text('name').notNull(),
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Child tables after (have foreign keys)
|
|
397
|
+
export const posts = nanodb.schema.table('posts', {
|
|
398
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
399
|
+
title: nanodb.schema.text('title').notNull(),
|
|
400
|
+
userId: nanodb.schema.integer('userId').notNull(), // FK to users
|
|
401
|
+
categoryId: nanodb.schema.integer('categoryId'), // FK to categories
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Order: parents → children
|
|
405
|
+
export const schema = { users, categories, posts };
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### 2. Single Database Instance
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// db/index.ts
|
|
412
|
+
import nanodb from 'nanodb-orm';
|
|
413
|
+
import { schema } from './schema';
|
|
414
|
+
import { seedData } from './seeds';
|
|
415
|
+
|
|
416
|
+
export const db = await nanodb.createDatabase({ tables: schema, seedData });
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// anywhere.ts
|
|
421
|
+
import { db } from './db';
|
|
422
|
+
import { users } from './db/schema';
|
|
423
|
+
|
|
424
|
+
const allUsers = await db.select().from(users);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 3. Use Type Inference
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// db/types.ts
|
|
431
|
+
import { type SelectModel, type InsertModel } from 'nanodb-orm';
|
|
432
|
+
import { users, posts } from './schema';
|
|
433
|
+
|
|
434
|
+
export type User = SelectModel<typeof users>;
|
|
435
|
+
export type NewUser = InsertModel<typeof users>;
|
|
436
|
+
export type Post = SelectModel<typeof posts>;
|
|
437
|
+
|
|
438
|
+
// Usage
|
|
439
|
+
async function createUser(data: NewUser): Promise<void> {
|
|
440
|
+
await db.insert(users).values(data); // TypeScript enforces shape
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### 4. Prefer Grouped Imports
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
// ✅ Clean - default import
|
|
448
|
+
import nanodb from 'nanodb-orm';
|
|
449
|
+
|
|
450
|
+
const users = nanodb.schema.table('users', { ... });
|
|
451
|
+
await db.select().from(users).where(nanodb.query.eq(users.id, 1));
|
|
452
|
+
|
|
453
|
+
// ✅ Also good - grouped imports
|
|
454
|
+
import { schema, query, errors } from 'nanodb-orm';
|
|
455
|
+
|
|
456
|
+
// ❌ Avoid - many individual imports
|
|
457
|
+
import { table, integer, text, eq, gte, and, sql, count, ... } from 'nanodb-orm';
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### 5. Handle Errors Gracefully
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { parseDbError, DatabaseError } from 'nanodb-orm';
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
await db.insert(users).values({ email: 'duplicate@test.com' });
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (error instanceof DatabaseError) {
|
|
469
|
+
// Already formatted with context
|
|
470
|
+
console.log(error.message); // "UNIQUE constraint failed: users.email"
|
|
471
|
+
console.log(error.table); // "users"
|
|
472
|
+
} else {
|
|
473
|
+
const parsed = parseDbError(error, { table: 'users' });
|
|
474
|
+
console.log(parsed.format());
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 6. Use Transactions for Atomic Operations
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import nanodb from 'nanodb-orm';
|
|
483
|
+
|
|
484
|
+
const result = await nanodb.transaction(async (tx) => {
|
|
485
|
+
await tx.run(nanodb.query.sql`INSERT INTO users (name) VALUES ('Alice')`);
|
|
486
|
+
await tx.run(nanodb.query.sql`INSERT INTO posts (title, userId) VALUES ('Hello', 1)`);
|
|
487
|
+
return { inserted: 2 };
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
if (!result.success) {
|
|
491
|
+
console.log('Rolled back:', result.error?.message);
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 7. Validate on Startup (Production)
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import nanodb from 'nanodb-orm';
|
|
499
|
+
|
|
500
|
+
const db = await nanodb.createDatabase({ tables: schema });
|
|
501
|
+
|
|
502
|
+
if (process.env.NODE_ENV === 'production') {
|
|
503
|
+
const validation = await db.schema.validate();
|
|
504
|
+
if (!validation.isValid) {
|
|
505
|
+
throw new Error(`Schema mismatch: ${validation.missingTables.join(', ')}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const health = await db.healthCheck();
|
|
509
|
+
if (!health.healthy) {
|
|
510
|
+
console.warn('Database issues:', health.errors);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### 8. Keep Plugins Simple
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// Good: focused, single responsibility
|
|
519
|
+
const timestampPlugin: NanoPlugin = {
|
|
520
|
+
name: 'timestamps',
|
|
521
|
+
beforeInsert: (_table, data) => ({
|
|
522
|
+
...data,
|
|
523
|
+
createdAt: new Date().toISOString(),
|
|
524
|
+
}),
|
|
525
|
+
beforeUpdate: (_table, data) => ({
|
|
526
|
+
...data,
|
|
527
|
+
updatedAt: new Date().toISOString(),
|
|
528
|
+
}),
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// Avoid: complex business logic in hooks
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 9. Environment Configuration
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
# .env
|
|
538
|
+
TURSO_CONNECTION_URL=libsql://your-db.turso.io
|
|
539
|
+
TURSO_AUTH_TOKEN=your-token
|
|
540
|
+
FORCE_LOCAL_DB=true # Use local SQLite
|
|
541
|
+
DATABASE_PATH=./data/app.db # Custom DB path
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
nanodb-orm auto-detects the right database:
|
|
545
|
+
- **Turso**: when `TURSO_*` vars are set
|
|
546
|
+
- **Local**: when `FORCE_LOCAL_DB=true` or no Turso config
|
|
547
|
+
- **Test**: isolated `test.db` when `NODE_ENV=test`
|
|
548
|
+
|
|
285
549
|
## Configuration
|
|
286
550
|
|
|
287
551
|
### Environment Variables
|
|
@@ -291,14 +555,18 @@ db.plugins.list(); // ['audit', 'slug', 'validation']
|
|
|
291
555
|
TURSO_CONNECTION_URL=libsql://your-db.turso.io
|
|
292
556
|
TURSO_AUTH_TOKEN=your-token
|
|
293
557
|
|
|
294
|
-
# Force local SQLite
|
|
558
|
+
# Force local SQLite
|
|
295
559
|
FORCE_LOCAL_DB=true
|
|
560
|
+
|
|
561
|
+
# Custom database path (works with FORCE_LOCAL_DB or as fallback)
|
|
562
|
+
DATABASE_PATH=./data/myapp.db
|
|
296
563
|
```
|
|
297
564
|
|
|
298
565
|
### Database Selection
|
|
299
566
|
|
|
300
567
|
- **Turso** — Used when `TURSO_CONNECTION_URL` and `TURSO_AUTH_TOKEN` are set
|
|
301
|
-
- **Local SQLite** —
|
|
568
|
+
- **Local SQLite** — Used when `FORCE_LOCAL_DB=true` or Turso credentials missing
|
|
569
|
+
- **Custom Path** — Set `DATABASE_PATH=./path/to/db.sqlite` for custom location
|
|
302
570
|
- **Test Mode** — Isolated `test.db` when `NODE_ENV=test`
|
|
303
571
|
|
|
304
572
|
## Error Handling
|
|
@@ -331,6 +599,78 @@ Error output is clean and actionable:
|
|
|
331
599
|
└────────────────────────────────────────────────
|
|
332
600
|
```
|
|
333
601
|
|
|
602
|
+
## Exports
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
// Default export (recommended)
|
|
606
|
+
import nanodb from 'nanodb-orm';
|
|
607
|
+
|
|
608
|
+
nanodb.createDatabase // Main entry point
|
|
609
|
+
nanodb.transaction // Atomic operations
|
|
610
|
+
nanodb.schema // .table, .integer, .text, .real, .blob
|
|
611
|
+
nanodb.query // .eq, .gte, .and, .or, .sql, .count, ...
|
|
612
|
+
nanodb.errors // .DatabaseError, .parse
|
|
613
|
+
nanodb.cli // .studio, .setup, .reset, .status, .validate
|
|
614
|
+
|
|
615
|
+
// Types (named imports)
|
|
616
|
+
import { type SelectModel, type InsertModel, type NanoPlugin } from 'nanodb-orm';
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## nanodb-orm vs Drizzle + Turso (Direct)
|
|
620
|
+
|
|
621
|
+
| Feature | nanodb-orm | Drizzle + Turso |
|
|
622
|
+
|---------|------------|-----------------|
|
|
623
|
+
| **Setup** | One-liner: `createDatabase({ tables })` | Manual: create client, drizzle, manage connection |
|
|
624
|
+
| **Migrations** | Automatic on startup | Manual: `drizzle-kit push/migrate` |
|
|
625
|
+
| **Seeding** | Built-in with `seedData` | Write seed scripts |
|
|
626
|
+
| **Type Safety** | ✅ Full (same as Drizzle) | ✅ Full |
|
|
627
|
+
| **Query API** | ✅ Same as Drizzle | ✅ Native Drizzle |
|
|
628
|
+
| **Plugins/Hooks** | ✅ beforeInsert, afterQuery, etc. | ❌ None |
|
|
629
|
+
| **Schema Introspection** | ✅ `db.schema.tables()` | ❌ Manual |
|
|
630
|
+
| **Health Checks** | ✅ `db.healthCheck()` | ❌ Manual |
|
|
631
|
+
| **CLI** | `npx nanodb studio/status/validate` | `npx drizzle-kit studio` only |
|
|
632
|
+
| **Error Parsing** | User-friendly messages | Raw SQLite errors |
|
|
633
|
+
| **Connection** | Auto-detects Turso vs local | Manual configuration |
|
|
634
|
+
|
|
635
|
+
### When to Use What
|
|
636
|
+
|
|
637
|
+
| Use Case | Recommendation |
|
|
638
|
+
|----------|----------------|
|
|
639
|
+
| Quick prototyping | **nanodb-orm** |
|
|
640
|
+
| Need plugins/hooks | **nanodb-orm** |
|
|
641
|
+
| Want auto-migrations | **nanodb-orm** |
|
|
642
|
+
| New SQLite/Turso project | **nanodb-orm** |
|
|
643
|
+
| Maximum control | Drizzle directly |
|
|
644
|
+
| Complex migration strategies | Drizzle + drizzle-kit |
|
|
645
|
+
| Existing Drizzle project | Keep Drizzle |
|
|
646
|
+
|
|
647
|
+
### Comparison
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
// nanodb-orm: 5 lines
|
|
651
|
+
import nanodb from 'nanodb-orm';
|
|
652
|
+
|
|
653
|
+
const users = nanodb.schema.table('users', { id: nanodb.schema.integer('id').primaryKey() });
|
|
654
|
+
const db = await nanodb.createDatabase({ tables: { users }, seedData: { users: [{ id: 1 }] } });
|
|
655
|
+
// Ready - tables created, seeded
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
// Drizzle + Turso: More setup
|
|
660
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
661
|
+
import { createClient } from '@libsql/client';
|
|
662
|
+
import { sqliteTable, integer } from 'drizzle-orm/sqlite-core';
|
|
663
|
+
import { migrate } from 'drizzle-orm/libsql/migrator';
|
|
664
|
+
|
|
665
|
+
const users = sqliteTable('users', { id: integer('id').primaryKey() });
|
|
666
|
+
const client = createClient({ url: process.env.TURSO_CONNECTION_URL!, authToken: process.env.TURSO_AUTH_TOKEN! });
|
|
667
|
+
const db = drizzle(client);
|
|
668
|
+
await migrate(db, { migrationsFolder: './drizzle' });
|
|
669
|
+
await db.insert(users).values([{ id: 1 }]);
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**nanodb-orm is a convenience layer** — it uses Drizzle under the hood and passes through all queries unchanged. You get Drizzle's full type safety plus automatic setup, plugins, and utilities.
|
|
673
|
+
|
|
334
674
|
## License
|
|
335
675
|
|
|
336
676
|
MIT © Damilola Alao
|
package/dist/cli.d.ts
CHANGED
|
@@ -9,5 +9,88 @@
|
|
|
9
9
|
* status - Show database health and stats
|
|
10
10
|
* validate - Validate schema against database
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
import { type ChildProcess } from 'child_process';
|
|
13
|
+
export interface StudioOptions {
|
|
14
|
+
/** Database file path (default: auto-detected or 'database.db') */
|
|
15
|
+
dbPath?: string | undefined;
|
|
16
|
+
/** Port for Drizzle Studio (default: 4983) */
|
|
17
|
+
port?: number | undefined;
|
|
18
|
+
/** Suppress console output */
|
|
19
|
+
silent?: boolean | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface StatusResult {
|
|
22
|
+
healthy: boolean;
|
|
23
|
+
schemaValid: boolean;
|
|
24
|
+
totalTables: number;
|
|
25
|
+
totalRecords: number;
|
|
26
|
+
tables: Record<string, number>;
|
|
27
|
+
errors: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface ValidationResult {
|
|
30
|
+
isValid: boolean;
|
|
31
|
+
missingTables: string[];
|
|
32
|
+
extraTables: string[];
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Launch Drizzle Studio for visual database browsing.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { launchStudio } from 'nanodb-orm';
|
|
41
|
+
*
|
|
42
|
+
* // Launch with defaults
|
|
43
|
+
* await launchStudio();
|
|
44
|
+
*
|
|
45
|
+
* // Launch with options
|
|
46
|
+
* await launchStudio({ dbPath: './data/app.db', port: 3000 });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function launchStudio(options?: StudioOptions): Promise<ChildProcess>;
|
|
50
|
+
/**
|
|
51
|
+
* Run database setup (initialize schema and seed data).
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { runSetup } from 'nanodb-orm';
|
|
56
|
+
* await runSetup();
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function runSetup(silent?: boolean): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Reset database (drop all tables and recreate with seed data).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { runReset } from 'nanodb-orm';
|
|
66
|
+
* await runReset();
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function runReset(silent?: boolean): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Get database status and health information.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { getStatus } from 'nanodb-orm';
|
|
76
|
+
*
|
|
77
|
+
* const status = await getStatus();
|
|
78
|
+
* console.log(status.healthy, status.totalRecords);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function getStatus(silent?: boolean): Promise<StatusResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Validate schema against database.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* import { runValidate } from 'nanodb-orm';
|
|
88
|
+
*
|
|
89
|
+
* const result = await runValidate();
|
|
90
|
+
* if (!result.isValid) {
|
|
91
|
+
* console.log('Missing tables:', result.missingTables);
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function runValidate(silent?: boolean): Promise<ValidationResult>;
|
|
13
96
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAQzD,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,8BAA8B;IAC9B,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8CrF;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAsCrE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsC3E"}
|