nanodb-orm 0.0.4 → 0.0.6
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/LICENSE +1 -1
- package/README.md +438 -40
- 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/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/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 +167 -56
- 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 +85 -2
- package/dist/utils/transactions.d.ts.map +1 -1
- package/dist/utils/transactions.js +160 -8
- package/dist/utils/transactions.js.map +1 -1
- package/package.json +2 -2
package/LICENSE
CHANGED
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,89 @@ await db.migrations.validate(); // Validate schema vs DB
|
|
|
165
236
|
await db.migrations.checkTables(); // { users: true, posts: true }
|
|
166
237
|
```
|
|
167
238
|
|
|
168
|
-
###
|
|
239
|
+
### Data-Preserving Auto Migrations
|
|
240
|
+
|
|
241
|
+
nanodb-orm automatically migrates your schema while **preserving existing data**. When you change your schema (add/remove columns, change types), the migration:
|
|
169
242
|
|
|
170
|
-
|
|
243
|
+
1. Creates a temporary table with the new schema
|
|
244
|
+
2. Copies data from matching columns
|
|
245
|
+
3. Drops the old table
|
|
246
|
+
4. Renames the temp table
|
|
171
247
|
|
|
172
248
|
```typescript
|
|
173
|
-
|
|
249
|
+
const db = await createDatabase({
|
|
250
|
+
tables: { users, posts },
|
|
251
|
+
migrationConfig: {
|
|
252
|
+
autoMigrate: true, // Enable automatic migrations (default: true)
|
|
253
|
+
preserveData: true, // Preserve existing data during migration (default: true)
|
|
254
|
+
dropTables: false, // Allow destructive drop & recreate (default: false)
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Migration Config Options
|
|
260
|
+
|
|
261
|
+
| Option | Default | Description |
|
|
262
|
+
|--------|---------|-------------|
|
|
263
|
+
| `autoMigrate` | `true` | Master switch - enables/disables automatic schema changes |
|
|
264
|
+
| `preserveData` | `true` | Copy existing data to new schema during migration |
|
|
265
|
+
| `dropTables` | `false` | Allow destructive operations (bypasses data preservation) |
|
|
266
|
+
|
|
267
|
+
#### How Column Changes Work
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
Adding a column:
|
|
271
|
+
OLD: (id, name) → NEW: (id, name, email)
|
|
272
|
+
✅ All rows preserved, 'email' starts as NULL/default
|
|
273
|
+
|
|
274
|
+
Removing a column:
|
|
275
|
+
OLD: (id, name, oldField) → NEW: (id, name)
|
|
276
|
+
⚠️ Rows preserved, but 'oldField' data is LOST
|
|
277
|
+
|
|
278
|
+
Renaming a column:
|
|
279
|
+
OLD: (id, username) → NEW: (id, name)
|
|
280
|
+
⚠️ Treated as remove + add, 'username' data is LOST
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### Manual Migration
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { migrateTablePreservingData } from 'nanodb-orm';
|
|
287
|
+
|
|
288
|
+
const result = await migrateTablePreservingData(
|
|
289
|
+
'users',
|
|
290
|
+
'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)',
|
|
291
|
+
['id', 'name'], // old columns
|
|
292
|
+
['id', 'name', 'email'] // new columns
|
|
293
|
+
);
|
|
294
|
+
// { rowsMigrated: 150, columnsPreserved: ['id', 'name'] }
|
|
295
|
+
```
|
|
174
296
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
297
|
+
### `transaction(fn)` / `batch(statements)`
|
|
298
|
+
|
|
299
|
+
Execute operations atomically. Uses Drizzle's native transaction when available (better for Turso).
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import nanodb from 'nanodb-orm';
|
|
303
|
+
|
|
304
|
+
// Transaction with custom logic
|
|
305
|
+
const result = await nanodb.transaction(async (tx) => {
|
|
306
|
+
await tx.run(nanodb.query.sql`INSERT INTO users (name) VALUES ('Alice')`);
|
|
307
|
+
await tx.run(nanodb.query.sql`INSERT INTO posts (title, userId) VALUES ('Hello', 1)`);
|
|
178
308
|
return { created: true };
|
|
179
309
|
});
|
|
180
310
|
|
|
181
311
|
if (result.success) {
|
|
182
|
-
console.log(result.result);
|
|
312
|
+
console.log(result.result); // { created: true }
|
|
183
313
|
} else {
|
|
184
314
|
console.log('Rolled back:', result.error?.message);
|
|
185
315
|
}
|
|
316
|
+
|
|
317
|
+
// Batch multiple statements (simpler for bulk operations)
|
|
318
|
+
const batchResult = await nanodb.batch([
|
|
319
|
+
nanodb.query.sql`INSERT INTO users (name) VALUES ('Bob')`,
|
|
320
|
+
nanodb.query.sql`INSERT INTO users (name) VALUES ('Carol')`,
|
|
321
|
+
]);
|
|
186
322
|
```
|
|
187
323
|
|
|
188
324
|
### `parseDbError(error, context)`
|
|
@@ -193,10 +329,10 @@ Parse SQLite errors into user-friendly messages.
|
|
|
193
329
|
import { parseDbError } from 'nanodb-orm';
|
|
194
330
|
|
|
195
331
|
try {
|
|
196
|
-
await db.
|
|
332
|
+
await db.insert(users).values({ email: 'duplicate@example.com' });
|
|
197
333
|
} catch (error) {
|
|
198
334
|
const parsed = parseDbError(error, { table: 'users', operation: 'insert' });
|
|
199
|
-
console.log(parsed.message); // "
|
|
335
|
+
console.log(parsed.message); // "Duplicate value for unique column 'email'"
|
|
200
336
|
}
|
|
201
337
|
```
|
|
202
338
|
|
|
@@ -282,6 +418,192 @@ await db.insert(users).values({ email: 'invalid' }); // throws error
|
|
|
282
418
|
db.plugins.list(); // ['audit', 'slug', 'validation']
|
|
283
419
|
```
|
|
284
420
|
|
|
421
|
+
## Best Practices
|
|
422
|
+
|
|
423
|
+
### Recommended Project Structure
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
db/
|
|
427
|
+
├── schema.ts # Table definitions
|
|
428
|
+
├── index.ts # Database instance export
|
|
429
|
+
├── types.ts # Type aliases (SelectModel, InsertModel)
|
|
430
|
+
├── plugins.ts # Custom plugins
|
|
431
|
+
└── seeds.ts # Seed data
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 1. Schema Order Matters
|
|
435
|
+
|
|
436
|
+
Define parent tables before children for correct seeding order:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// db/schema.ts
|
|
440
|
+
import nanodb from 'nanodb-orm';
|
|
441
|
+
|
|
442
|
+
// Parent tables first (no foreign keys)
|
|
443
|
+
export const users = nanodb.schema.table('users', {
|
|
444
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
445
|
+
name: nanodb.schema.text('name').notNull(),
|
|
446
|
+
email: nanodb.schema.text('email').notNull().unique(),
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
export const categories = nanodb.schema.table('categories', {
|
|
450
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
451
|
+
name: nanodb.schema.text('name').notNull(),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Child tables after (have foreign keys)
|
|
455
|
+
export const posts = nanodb.schema.table('posts', {
|
|
456
|
+
id: nanodb.schema.integer('id').primaryKey({ autoIncrement: true }),
|
|
457
|
+
title: nanodb.schema.text('title').notNull(),
|
|
458
|
+
userId: nanodb.schema.integer('userId').notNull(), // FK to users
|
|
459
|
+
categoryId: nanodb.schema.integer('categoryId'), // FK to categories
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Order: parents → children
|
|
463
|
+
export const schema = { users, categories, posts };
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### 2. Single Database Instance
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
// db/index.ts
|
|
470
|
+
import nanodb from 'nanodb-orm';
|
|
471
|
+
import { schema } from './schema';
|
|
472
|
+
import { seedData } from './seeds';
|
|
473
|
+
|
|
474
|
+
export const db = await nanodb.createDatabase({ tables: schema, seedData });
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// anywhere.ts
|
|
479
|
+
import { db } from './db';
|
|
480
|
+
import { users } from './db/schema';
|
|
481
|
+
|
|
482
|
+
const allUsers = await db.select().from(users);
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### 3. Use Type Inference
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// db/types.ts
|
|
489
|
+
import { type SelectModel, type InsertModel } from 'nanodb-orm';
|
|
490
|
+
import { users, posts } from './schema';
|
|
491
|
+
|
|
492
|
+
export type User = SelectModel<typeof users>;
|
|
493
|
+
export type NewUser = InsertModel<typeof users>;
|
|
494
|
+
export type Post = SelectModel<typeof posts>;
|
|
495
|
+
|
|
496
|
+
// Usage
|
|
497
|
+
async function createUser(data: NewUser): Promise<void> {
|
|
498
|
+
await db.insert(users).values(data); // TypeScript enforces shape
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 4. Prefer Grouped Imports
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
// ✅ Clean - default import
|
|
506
|
+
import nanodb from 'nanodb-orm';
|
|
507
|
+
|
|
508
|
+
const users = nanodb.schema.table('users', { ... });
|
|
509
|
+
await db.select().from(users).where(nanodb.query.eq(users.id, 1));
|
|
510
|
+
|
|
511
|
+
// ✅ Also good - grouped imports
|
|
512
|
+
import { schema, query, errors } from 'nanodb-orm';
|
|
513
|
+
|
|
514
|
+
// ❌ Avoid - many individual imports
|
|
515
|
+
import { table, integer, text, eq, gte, and, sql, count, ... } from 'nanodb-orm';
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 5. Handle Errors Gracefully
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
import { parseDbError, DatabaseError } from 'nanodb-orm';
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
await db.insert(users).values({ email: 'duplicate@test.com' });
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (error instanceof DatabaseError) {
|
|
527
|
+
// Already formatted with context
|
|
528
|
+
console.log(error.message); // "UNIQUE constraint failed: users.email"
|
|
529
|
+
console.log(error.table); // "users"
|
|
530
|
+
} else {
|
|
531
|
+
const parsed = parseDbError(error, { table: 'users' });
|
|
532
|
+
console.log(parsed.format());
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 6. Use Transactions for Atomic Operations
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import nanodb from 'nanodb-orm';
|
|
541
|
+
|
|
542
|
+
const result = await nanodb.transaction(async (tx) => {
|
|
543
|
+
await tx.run(nanodb.query.sql`INSERT INTO users (name) VALUES ('Alice')`);
|
|
544
|
+
await tx.run(nanodb.query.sql`INSERT INTO posts (title, userId) VALUES ('Hello', 1)`);
|
|
545
|
+
return { inserted: 2 };
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
if (!result.success) {
|
|
549
|
+
console.log('Rolled back:', result.error?.message);
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### 7. Validate on Startup (Production)
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import nanodb from 'nanodb-orm';
|
|
557
|
+
|
|
558
|
+
const db = await nanodb.createDatabase({ tables: schema });
|
|
559
|
+
|
|
560
|
+
if (process.env.NODE_ENV === 'production') {
|
|
561
|
+
const validation = await db.schema.validate();
|
|
562
|
+
if (!validation.isValid) {
|
|
563
|
+
throw new Error(`Schema mismatch: ${validation.missingTables.join(', ')}`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const health = await db.healthCheck();
|
|
567
|
+
if (!health.healthy) {
|
|
568
|
+
console.warn('Database issues:', health.errors);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### 8. Keep Plugins Simple
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// Good: focused, single responsibility
|
|
577
|
+
const timestampPlugin: NanoPlugin = {
|
|
578
|
+
name: 'timestamps',
|
|
579
|
+
beforeInsert: (_table, data) => ({
|
|
580
|
+
...data,
|
|
581
|
+
createdAt: new Date().toISOString(),
|
|
582
|
+
}),
|
|
583
|
+
beforeUpdate: (_table, data) => ({
|
|
584
|
+
...data,
|
|
585
|
+
updatedAt: new Date().toISOString(),
|
|
586
|
+
}),
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// Avoid: complex business logic in hooks
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### 9. Environment Configuration
|
|
593
|
+
|
|
594
|
+
```bash
|
|
595
|
+
# .env
|
|
596
|
+
TURSO_CONNECTION_URL=libsql://your-db.turso.io
|
|
597
|
+
TURSO_AUTH_TOKEN=your-token
|
|
598
|
+
FORCE_LOCAL_DB=true # Use local SQLite
|
|
599
|
+
DATABASE_PATH=./data/app.db # Custom DB path
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
nanodb-orm auto-detects the right database:
|
|
603
|
+
- **Turso**: when `TURSO_*` vars are set
|
|
604
|
+
- **Local**: when `FORCE_LOCAL_DB=true` or no Turso config
|
|
605
|
+
- **Test**: isolated `test.db` when `NODE_ENV=test`
|
|
606
|
+
|
|
285
607
|
## Configuration
|
|
286
608
|
|
|
287
609
|
### Environment Variables
|
|
@@ -291,14 +613,18 @@ db.plugins.list(); // ['audit', 'slug', 'validation']
|
|
|
291
613
|
TURSO_CONNECTION_URL=libsql://your-db.turso.io
|
|
292
614
|
TURSO_AUTH_TOKEN=your-token
|
|
293
615
|
|
|
294
|
-
# Force local SQLite
|
|
616
|
+
# Force local SQLite
|
|
295
617
|
FORCE_LOCAL_DB=true
|
|
618
|
+
|
|
619
|
+
# Custom database path (works with FORCE_LOCAL_DB or as fallback)
|
|
620
|
+
DATABASE_PATH=./data/myapp.db
|
|
296
621
|
```
|
|
297
622
|
|
|
298
623
|
### Database Selection
|
|
299
624
|
|
|
300
625
|
- **Turso** — Used when `TURSO_CONNECTION_URL` and `TURSO_AUTH_TOKEN` are set
|
|
301
|
-
- **Local SQLite** —
|
|
626
|
+
- **Local SQLite** — Used when `FORCE_LOCAL_DB=true` or Turso credentials missing
|
|
627
|
+
- **Custom Path** — Set `DATABASE_PATH=./path/to/db.sqlite` for custom location
|
|
302
628
|
- **Test Mode** — Isolated `test.db` when `NODE_ENV=test`
|
|
303
629
|
|
|
304
630
|
## Error Handling
|
|
@@ -331,6 +657,78 @@ Error output is clean and actionable:
|
|
|
331
657
|
└────────────────────────────────────────────────
|
|
332
658
|
```
|
|
333
659
|
|
|
660
|
+
## Exports
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
// Default export (recommended)
|
|
664
|
+
import nanodb from 'nanodb-orm';
|
|
665
|
+
|
|
666
|
+
nanodb.createDatabase // Main entry point
|
|
667
|
+
nanodb.transaction // Atomic operations
|
|
668
|
+
nanodb.schema // .table, .integer, .text, .real, .blob
|
|
669
|
+
nanodb.query // .eq, .gte, .and, .or, .sql, .count, ...
|
|
670
|
+
nanodb.errors // .DatabaseError, .parse
|
|
671
|
+
nanodb.cli // .studio, .setup, .reset, .status, .validate
|
|
672
|
+
|
|
673
|
+
// Types (named imports)
|
|
674
|
+
import { type SelectModel, type InsertModel, type NanoPlugin } from 'nanodb-orm';
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
## nanodb-orm vs Drizzle + Turso (Direct)
|
|
678
|
+
|
|
679
|
+
| Feature | nanodb-orm | Drizzle + Turso |
|
|
680
|
+
|---------|------------|-----------------|
|
|
681
|
+
| **Setup** | One-liner: `createDatabase({ tables })` | Manual: create client, drizzle, manage connection |
|
|
682
|
+
| **Migrations** | Automatic on startup | Manual: `drizzle-kit push/migrate` |
|
|
683
|
+
| **Seeding** | Built-in with `seedData` | Write seed scripts |
|
|
684
|
+
| **Type Safety** | ✅ Full (same as Drizzle) | ✅ Full |
|
|
685
|
+
| **Query API** | ✅ Same as Drizzle | ✅ Native Drizzle |
|
|
686
|
+
| **Plugins/Hooks** | ✅ beforeInsert, afterQuery, etc. | ❌ None |
|
|
687
|
+
| **Schema Introspection** | ✅ `db.schema.tables()` | ❌ Manual |
|
|
688
|
+
| **Health Checks** | ✅ `db.healthCheck()` | ❌ Manual |
|
|
689
|
+
| **CLI** | `npx nanodb studio/status/validate` | `npx drizzle-kit studio` only |
|
|
690
|
+
| **Error Parsing** | User-friendly messages | Raw SQLite errors |
|
|
691
|
+
| **Connection** | Auto-detects Turso vs local | Manual configuration |
|
|
692
|
+
|
|
693
|
+
### When to Use What
|
|
694
|
+
|
|
695
|
+
| Use Case | Recommendation |
|
|
696
|
+
|----------|----------------|
|
|
697
|
+
| Quick prototyping | **nanodb-orm** |
|
|
698
|
+
| Need plugins/hooks | **nanodb-orm** |
|
|
699
|
+
| Want auto-migrations | **nanodb-orm** |
|
|
700
|
+
| New SQLite/Turso project | **nanodb-orm** |
|
|
701
|
+
| Maximum control | Drizzle directly |
|
|
702
|
+
| Complex migration strategies | Drizzle + drizzle-kit |
|
|
703
|
+
| Existing Drizzle project | Keep Drizzle |
|
|
704
|
+
|
|
705
|
+
### Comparison
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
// nanodb-orm: 5 lines
|
|
709
|
+
import nanodb from 'nanodb-orm';
|
|
710
|
+
|
|
711
|
+
const users = nanodb.schema.table('users', { id: nanodb.schema.integer('id').primaryKey() });
|
|
712
|
+
const db = await nanodb.createDatabase({ tables: { users }, seedData: { users: [{ id: 1 }] } });
|
|
713
|
+
// Ready - tables created, seeded
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
// Drizzle + Turso: More setup
|
|
718
|
+
import { drizzle } from 'drizzle-orm/libsql';
|
|
719
|
+
import { createClient } from '@libsql/client';
|
|
720
|
+
import { sqliteTable, integer } from 'drizzle-orm/sqlite-core';
|
|
721
|
+
import { migrate } from 'drizzle-orm/libsql/migrator';
|
|
722
|
+
|
|
723
|
+
const users = sqliteTable('users', { id: integer('id').primaryKey() });
|
|
724
|
+
const client = createClient({ url: process.env.TURSO_CONNECTION_URL!, authToken: process.env.TURSO_AUTH_TOKEN! });
|
|
725
|
+
const db = drizzle(client);
|
|
726
|
+
await migrate(db, { migrationsFolder: './drizzle' });
|
|
727
|
+
await db.insert(users).values([{ id: 1 }]);
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
**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.
|
|
731
|
+
|
|
334
732
|
## License
|
|
335
733
|
|
|
336
|
-
MIT ©
|
|
734
|
+
MIT © Easy-Deploy-Dev
|
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"}
|