nanodb-orm 0.0.3 → 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 +506 -333
- package/dist/cli.d.ts +96 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +348 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants/index.d.ts +7 -54
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +9 -61
- package/dist/constants/index.js.map +1 -1
- package/dist/core/config.d.ts +3 -13
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +5 -27
- package/dist/core/config.js.map +1 -1
- package/dist/core/connection.d.ts +16 -31
- package/dist/core/connection.d.ts.map +1 -1
- package/dist/core/connection.js +42 -78
- package/dist/core/connection.js.map +1 -1
- package/dist/core/index.d.ts +2 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -18
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +235 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -35
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +127 -18
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +280 -47
- package/dist/init.js.map +1 -1
- package/dist/jest.setup.d.ts +4 -0
- package/dist/jest.setup.d.ts.map +1 -0
- package/dist/jest.setup.js +40 -0
- package/dist/jest.setup.js.map +1 -0
- package/dist/types/errors.d.ts +30 -12
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +98 -23
- package/dist/types/errors.js.map +1 -1
- package/dist/types/index.d.ts +268 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-handler.d.ts +6 -31
- package/dist/utils/error-handler.d.ts.map +1 -1
- package/dist/utils/error-handler.js +24 -81
- package/dist/utils/error-handler.js.map +1 -1
- package/dist/utils/index.d.ts +1 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -6
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +6 -25
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +20 -38
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/migrations.d.ts +16 -90
- package/dist/utils/migrations.d.ts.map +1 -1
- package/dist/utils/migrations.js +220 -422
- package/dist/utils/migrations.js.map +1 -1
- package/dist/utils/schema-introspection.d.ts +30 -169
- package/dist/utils/schema-introspection.d.ts.map +1 -1
- package/dist/utils/schema-introspection.js +125 -462
- package/dist/utils/schema-introspection.js.map +1 -1
- package/dist/utils/seeds.d.ts +15 -48
- package/dist/utils/seeds.d.ts.map +1 -1
- package/dist/utils/seeds.js +108 -186
- package/dist/utils/seeds.js.map +1 -1
- package/dist/utils/sync.d.ts +16 -41
- package/dist/utils/sync.d.ts.map +1 -1
- package/dist/utils/sync.js +78 -172
- package/dist/utils/sync.js.map +1 -1
- package/dist/utils/transactions.d.ts +61 -44
- package/dist/utils/transactions.d.ts.map +1 -1
- package/dist/utils/transactions.js +103 -137
- package/dist/utils/transactions.js.map +1 -1
- package/package.json +29 -10
- package/dist/example.d.ts +0 -67
- package/dist/example.d.ts.map +0 -1
- package/dist/example.js +0 -86
- package/dist/example.js.map +0 -1
- package/dist/types/database.d.ts +0 -74
- package/dist/types/database.d.ts.map +0 -1
- package/dist/types/database.js +0 -6
- package/dist/types/database.js.map +0 -1
- package/dist/types/types.d.ts +0 -30
- package/dist/types/types.d.ts.map +0 -1
- package/dist/types/types.js +0 -6
- package/dist/types/types.js.map +0 -1
- package/llm.txt +0 -336
package/README.md
CHANGED
|
@@ -1,503 +1,676 @@
|
|
|
1
1
|
# nanodb-orm
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- ⚙️ **Configurable**: Flexible migration and seeding options
|
|
16
|
-
- 🧪 **Test Ready**: Built-in testing utilities and isolation
|
|
17
|
-
- 🔧 **Production Ready**: Enhanced error handling and reliability features
|
|
7
|
+
- **TypeScript First** — Full type inference from schema to queries
|
|
8
|
+
- **Auto-Migrations** — Automatically creates and updates database schemas from Drizzle tables
|
|
9
|
+
- **Schema Introspection** — Comprehensive schema analysis and validation
|
|
10
|
+
- **Multi-Database** — Works with local SQLite and remote Turso databases
|
|
11
|
+
- **Transactions** — Full transaction support with automatic rollback
|
|
12
|
+
- **CLI Tools** — Built-in commands including Drizzle Studio integration
|
|
13
|
+
- **Plugin System** — Extensible with hooks for audit, validation, transformations
|
|
14
|
+
- **Minimal** — ~1K lines of code, zero bloat
|
|
18
15
|
|
|
19
16
|
## Installation
|
|
20
17
|
|
|
21
18
|
```bash
|
|
22
19
|
npm install nanodb-orm
|
|
20
|
+
|
|
21
|
+
# For Drizzle Studio support (optional)
|
|
22
|
+
npm install drizzle-kit --save-dev
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## CLI
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
nanodb-orm includes a CLI for common database operations:
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
```bash
|
|
30
|
+
# Launch Drizzle Studio (visual database browser)
|
|
31
|
+
npx nanodb studio
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
33
|
+
# With custom port
|
|
34
|
+
npx nanodb studio --port 3000
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
name: text('name').notNull(),
|
|
38
|
-
age: integer('age').notNull(),
|
|
39
|
-
email: text('email').unique().notNull(),
|
|
40
|
-
});
|
|
36
|
+
# With specific database file
|
|
37
|
+
npx nanodb studio --db ./data/myapp.db
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
# Other commands
|
|
40
|
+
npx nanodb setup # Initialize schema and seed data
|
|
41
|
+
npx nanodb reset # Drop all tables and recreate
|
|
42
|
+
npx nanodb status # Show database health and stats
|
|
43
|
+
npx nanodb validate # Validate schema against database
|
|
44
|
+
npx nanodb help # Show all commands
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
// models/posts.ts
|
|
48
|
-
import { sql } from 'drizzle-orm';
|
|
49
|
-
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
50
|
-
import { usersTable } from './users';
|
|
47
|
+
### Drizzle Studio
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
54
|
-
title: text('title').notNull(),
|
|
55
|
-
content: text('content').notNull(),
|
|
56
|
-
userId: integer('user_id')
|
|
57
|
-
.notNull()
|
|
58
|
-
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
|
59
|
-
createdAt: text('created_at')
|
|
60
|
-
.default(sql`(datetime('now'))`)
|
|
61
|
-
.notNull(),
|
|
62
|
-
updatedAt: text('updated_at').$onUpdate(() => new Date().toISOString()),
|
|
63
|
-
});
|
|
49
|
+
Launch a visual database browser at `https://local.drizzle.studio`:
|
|
64
50
|
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
```bash
|
|
52
|
+
npx nanodb studio
|
|
67
53
|
```
|
|
68
54
|
|
|
69
|
-
|
|
70
|
-
// models/categories.ts
|
|
71
|
-
import { sql } from 'drizzle-orm';
|
|
72
|
-
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
55
|
+

|
|
73
56
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
isActive: integer('is_active').notNull().default(1),
|
|
80
|
-
createdAt: text('created_at')
|
|
81
|
-
.default(sql`(datetime('now'))`)
|
|
82
|
-
.notNull(),
|
|
83
|
-
});
|
|
57
|
+
## Import Styles
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Default import (recommended)
|
|
61
|
+
import nanodb from 'nanodb-orm';
|
|
84
62
|
|
|
85
|
-
|
|
86
|
-
|
|
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));
|
|
87
66
|
```
|
|
88
67
|
|
|
89
68
|
```typescript
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
export * from './categories';
|
|
94
|
-
|
|
95
|
-
// Import tables for aggregation
|
|
96
|
-
import { usersTable } from './users';
|
|
97
|
-
import { postsTable } from './posts';
|
|
98
|
-
import { categoriesTable } from './categories';
|
|
69
|
+
// Named imports
|
|
70
|
+
import { createDatabase, schema, query } from 'nanodb-orm';
|
|
71
|
+
```
|
|
99
72
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
posts: postsTable,
|
|
104
|
-
categories: categoriesTable,
|
|
105
|
-
} as const;
|
|
73
|
+
```typescript
|
|
74
|
+
// Individual imports (tree-shakeable)
|
|
75
|
+
import { createDatabase, table, integer, text, eq } from 'nanodb-orm';
|
|
106
76
|
```
|
|
107
77
|
|
|
108
|
-
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
### 1. Define Your Schema
|
|
109
81
|
|
|
110
82
|
```typescript
|
|
111
|
-
import
|
|
112
|
-
import { tables } from './models';
|
|
83
|
+
import nanodb from 'nanodb-orm';
|
|
113
84
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{ name: 'John Doe', age: 30, email: 'john@example.com' },
|
|
120
|
-
{ name: 'Jane Smith', age: 25, email: 'jane@example.com' }
|
|
121
|
-
],
|
|
122
|
-
posts: [
|
|
123
|
-
{ title: 'Welcome Post', content: 'This is my first post!', userId: 1 },
|
|
124
|
-
{ title: 'Getting Started', content: 'Here are some tips...', userId: 2 }
|
|
125
|
-
],
|
|
126
|
-
categories: [
|
|
127
|
-
{ name: 'Technology', description: 'Tech-related posts', color: '#3B82F6' },
|
|
128
|
-
{ name: 'Lifestyle', description: 'Life and personal posts', color: '#10B981' }
|
|
129
|
-
]
|
|
130
|
-
}
|
|
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'),
|
|
131
90
|
});
|
|
132
91
|
|
|
133
|
-
|
|
134
|
-
|
|
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(),
|
|
96
|
+
});
|
|
135
97
|
```
|
|
136
98
|
|
|
137
|
-
###
|
|
99
|
+
### 2. Create Database
|
|
138
100
|
|
|
139
|
-
|
|
101
|
+
```typescript
|
|
102
|
+
const db = await nanodb.createDatabase({
|
|
103
|
+
tables: { users, posts },
|
|
104
|
+
seedData: {
|
|
105
|
+
users: [{ name: 'Alice', email: 'alice@example.com', age: 28 }],
|
|
106
|
+
},
|
|
107
|
+
});
|
|
140
108
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- `text()` - Text strings
|
|
144
|
-
- `real()` - Floating point numbers
|
|
145
|
-
- `blob()` - Binary data
|
|
109
|
+
export { db };
|
|
110
|
+
```
|
|
146
111
|
|
|
147
|
-
|
|
148
|
-
- `.primaryKey({ autoIncrement: true })` - Primary key with auto-increment
|
|
149
|
-
- `.notNull()` - NOT NULL constraint
|
|
150
|
-
- `.unique()` - UNIQUE constraint
|
|
151
|
-
- `.default(value)` - Default value
|
|
152
|
-
- `.references(table.column)` - Foreign key reference
|
|
112
|
+
### 3. Query Your Data
|
|
153
113
|
|
|
154
|
-
#### Example Drizzle Column Definitions
|
|
155
114
|
```typescript
|
|
156
|
-
//
|
|
157
|
-
|
|
115
|
+
// SELECT
|
|
116
|
+
const allUsers = await db.select().from(users);
|
|
117
|
+
const adults = await db.select().from(users).where(nanodb.query.gte(users.age, 18));
|
|
158
118
|
|
|
159
|
-
//
|
|
160
|
-
name:
|
|
119
|
+
// INSERT
|
|
120
|
+
await db.insert(users).values({ name: 'Bob', email: 'bob@example.com' });
|
|
161
121
|
|
|
162
|
-
//
|
|
163
|
-
|
|
122
|
+
// UPDATE
|
|
123
|
+
await db.update(users).set({ name: 'Robert' }).where(nanodb.query.eq(users.email, 'bob@example.com'));
|
|
164
124
|
|
|
165
|
-
//
|
|
166
|
-
|
|
125
|
+
// DELETE
|
|
126
|
+
await db.delete(users).where(nanodb.query.eq(users.email, 'bob@example.com'));
|
|
127
|
+
```
|
|
167
128
|
|
|
168
|
-
|
|
169
|
-
isActive: integer('is_active').notNull().default(1)
|
|
129
|
+
## Type Inference
|
|
170
130
|
|
|
171
|
-
|
|
172
|
-
createdAt: text('created_at')
|
|
173
|
-
.default(sql`(datetime('now'))`)
|
|
174
|
-
.notNull()
|
|
131
|
+
nanodb-orm provides full type inference from your schema:
|
|
175
132
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
});
|
|
181
149
|
|
|
182
|
-
|
|
150
|
+
// Infer types directly from your table definitions
|
|
151
|
+
type User = SelectModel<typeof users>;
|
|
152
|
+
// { id: number; name: string; email: string; age: number | null }
|
|
183
153
|
|
|
184
|
-
|
|
154
|
+
type NewUser = InsertModel<typeof users>;
|
|
155
|
+
// { id?: number; name: string; email: string; age?: number | null }
|
|
185
156
|
|
|
186
|
-
|
|
157
|
+
// The database is fully typed
|
|
158
|
+
const db = await createDatabase({ tables: { users } });
|
|
187
159
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
tables: Record<string, any>; // Your Drizzle table definitions
|
|
191
|
-
seedData?: Record<string, any[]>; // Optional seed data
|
|
192
|
-
migrationConfig?: MigrationConfig; // Optional migration configuration
|
|
193
|
-
}
|
|
160
|
+
// All operations are type-safe
|
|
161
|
+
const allUsers: User[] = await db.select().from(users);
|
|
194
162
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
});
|
|
200
173
|
```
|
|
201
174
|
|
|
202
|
-
###
|
|
175
|
+
### Available Type Utilities
|
|
203
176
|
|
|
204
|
-
|
|
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 |
|
|
205
186
|
|
|
206
|
-
|
|
207
|
-
// Setup database (create tables, migrate, seed)
|
|
208
|
-
await DatabaseSync.setup();
|
|
187
|
+
## API Reference
|
|
209
188
|
|
|
210
|
-
|
|
211
|
-
await DatabaseSync.reset();
|
|
189
|
+
### `createDatabase(config)`
|
|
212
190
|
|
|
213
|
-
|
|
214
|
-
const isReady = await DatabaseSync.isReady();
|
|
215
|
-
```
|
|
191
|
+
Create and initialize database. Returns `db` with all utilities attached.
|
|
216
192
|
|
|
217
|
-
|
|
193
|
+
```typescript
|
|
194
|
+
const db = await createDatabase({
|
|
195
|
+
tables: { users, posts },
|
|
196
|
+
seedData: { users: [...] }, // Type-checked against schema
|
|
197
|
+
migrationConfig: {
|
|
198
|
+
preserveData: true, // default: true
|
|
199
|
+
autoMigrate: true, // default: true
|
|
200
|
+
dropTables: false, // default: false
|
|
201
|
+
},
|
|
202
|
+
plugins: [auditPlugin, validationPlugin], // optional
|
|
203
|
+
});
|
|
204
|
+
```
|
|
218
205
|
|
|
219
|
-
|
|
206
|
+
### Database Operations (from `db`)
|
|
220
207
|
|
|
221
208
|
```typescript
|
|
222
|
-
//
|
|
223
|
-
|
|
209
|
+
// Health & Status
|
|
210
|
+
await db.healthCheck(); // { healthy, tables, totalRecords, ... }
|
|
211
|
+
await db.isReady(); // true/false
|
|
212
|
+
await db.sync(); // Sync with Turso (if remote)
|
|
213
|
+
|
|
214
|
+
// Reset & Seed
|
|
215
|
+
await db.reset(); // Drop all, recreate, reseed
|
|
216
|
+
await db.seed(); // Re-seed data
|
|
217
|
+
await db.clearData(); // Delete all data (keep tables)
|
|
218
|
+
```
|
|
224
219
|
|
|
225
|
-
|
|
226
|
-
const tableInfo = SchemaIntrospection.getTableInfo('users');
|
|
220
|
+
### Schema Introspection (from `db.schema`)
|
|
227
221
|
|
|
228
|
-
|
|
229
|
-
|
|
222
|
+
```typescript
|
|
223
|
+
db.schema.tables(); // ['users', 'posts'] - typed as (keyof Schema)[]
|
|
224
|
+
db.schema.getTable('users'); // { columns, primaryKey, indexes }
|
|
225
|
+
db.schema.getColumns('users'); // ['id', 'name', 'email']
|
|
226
|
+
await db.schema.validate(); // { isValid, missingTables, ... }
|
|
227
|
+
db.schema.stats(); // Full schema statistics
|
|
228
|
+
db.schema.relationships(); // Foreign key relationships
|
|
229
|
+
```
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
### Migrations (from `db.migrations`)
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
await db.migrations.run(); // Run pending migrations
|
|
235
|
+
await db.migrations.validate(); // Validate schema vs DB
|
|
236
|
+
await db.migrations.checkTables(); // { users: true, posts: true }
|
|
233
237
|
```
|
|
234
238
|
|
|
235
|
-
### `
|
|
239
|
+
### `transaction(fn)` / `batch(statements)`
|
|
236
240
|
|
|
237
|
-
|
|
241
|
+
Execute operations atomically. Uses Drizzle's native transaction when available (better for Turso).
|
|
238
242
|
|
|
239
243
|
```typescript
|
|
240
|
-
|
|
241
|
-
await DatabaseMigrations.initializeSchema();
|
|
244
|
+
import nanodb from 'nanodb-orm';
|
|
242
245
|
|
|
243
|
-
//
|
|
244
|
-
const
|
|
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)`);
|
|
250
|
+
return { created: true };
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (result.success) {
|
|
254
|
+
console.log(result.result); // { created: true }
|
|
255
|
+
} else {
|
|
256
|
+
console.log('Rolled back:', result.error?.message);
|
|
257
|
+
}
|
|
245
258
|
|
|
246
|
-
//
|
|
247
|
-
const
|
|
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
|
+
]);
|
|
248
264
|
```
|
|
249
265
|
|
|
250
|
-
### `
|
|
266
|
+
### `parseDbError(error, context)`
|
|
251
267
|
|
|
252
|
-
|
|
268
|
+
Parse SQLite errors into user-friendly messages.
|
|
253
269
|
|
|
254
270
|
```typescript
|
|
255
|
-
|
|
256
|
-
await DatabaseSeeds.seedDatabase();
|
|
271
|
+
import { parseDbError } from 'nanodb-orm';
|
|
257
272
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
273
|
+
try {
|
|
274
|
+
await db.insert(users).values({ email: 'duplicate@example.com' });
|
|
275
|
+
} catch (error) {
|
|
276
|
+
const parsed = parseDbError(error, { table: 'users', operation: 'insert' });
|
|
277
|
+
console.log(parsed.message); // "Duplicate value for unique column 'email'"
|
|
278
|
+
}
|
|
263
279
|
```
|
|
264
280
|
|
|
265
|
-
##
|
|
281
|
+
## Plugins
|
|
266
282
|
|
|
267
|
-
|
|
283
|
+
Extend nanodb-orm with custom hooks that run automatically on database operations.
|
|
268
284
|
|
|
269
|
-
|
|
270
|
-
# Turso Database (optional)
|
|
271
|
-
TURSO_CONNECTION_URL=libsql://your-database.turso.io
|
|
272
|
-
TURSO_AUTH_TOKEN=your-auth-token
|
|
285
|
+
### Plugin Interface
|
|
273
286
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
287
|
+
```typescript
|
|
288
|
+
import { NanoPlugin } from 'nanodb-orm';
|
|
289
|
+
|
|
290
|
+
const myPlugin: NanoPlugin = {
|
|
291
|
+
name: 'my-plugin',
|
|
292
|
+
|
|
293
|
+
// Lifecycle
|
|
294
|
+
install: (db) => db, // Modify db instance
|
|
295
|
+
onReady: (db) => {}, // Called after createDatabase
|
|
296
|
+
onError: (err, op, table) => {}, // Called on hook errors
|
|
297
|
+
|
|
298
|
+
// Auto hooks (run automatically)
|
|
299
|
+
beforeInsert: (table, data) => data, // Transform data before insert
|
|
300
|
+
afterInsert: (table, data, result) => {},
|
|
301
|
+
beforeUpdate: (table, data) => data, // Transform data before update
|
|
302
|
+
afterUpdate: (table, data, result) => {},
|
|
303
|
+
beforeDelete: (table, condition) => condition,
|
|
304
|
+
afterDelete: (table, condition, result) => {},
|
|
305
|
+
|
|
306
|
+
// Query hooks (also auto-triggered)
|
|
307
|
+
beforeQuery: (table, fields) => fields,
|
|
308
|
+
afterQuery: (table, fields, result) => {},
|
|
309
|
+
};
|
|
277
310
|
```
|
|
278
311
|
|
|
279
|
-
###
|
|
312
|
+
### Example Plugins
|
|
313
|
+
|
|
314
|
+
These are **example plugins you can create** - nanodb-orm provides the plugin system, you build the plugins:
|
|
280
315
|
|
|
281
316
|
```typescript
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
317
|
+
// Example: Audit logging with timing
|
|
318
|
+
const timers = new Map<string, number>();
|
|
319
|
+
const auditPlugin: NanoPlugin = {
|
|
320
|
+
name: 'audit',
|
|
321
|
+
beforeInsert: (table) => { timers.set('op', performance.now()); console.log(`INSERT ${table}`); },
|
|
322
|
+
afterInsert: () => { console.log(` ↳ ${(performance.now() - timers.get('op')!).toFixed(1)}ms`); },
|
|
323
|
+
beforeQuery: (table) => { timers.set('op', performance.now()); console.log(`SELECT ${table}`); },
|
|
324
|
+
afterQuery: (t, _, rows) => { console.log(` ↳ ${rows.length} rows in ${(performance.now() - timers.get('op')!).toFixed(1)}ms`); },
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Auto-generate slugs
|
|
328
|
+
const slugPlugin: NanoPlugin = {
|
|
329
|
+
name: 'slug',
|
|
330
|
+
beforeInsert: (table, data) => {
|
|
331
|
+
if (table === 'posts' && data.title && !data.slug) {
|
|
332
|
+
return { ...data, slug: data.title.toLowerCase().replace(/\s+/g, '-') };
|
|
333
|
+
}
|
|
334
|
+
return data;
|
|
335
|
+
},
|
|
286
336
|
};
|
|
287
337
|
|
|
288
|
-
|
|
338
|
+
// Validation
|
|
339
|
+
const validationPlugin: NanoPlugin = {
|
|
340
|
+
name: 'validation',
|
|
341
|
+
beforeInsert: (table, data) => {
|
|
342
|
+
if (table === 'users' && !data.email?.includes('@')) {
|
|
343
|
+
throw new Error('Invalid email format');
|
|
344
|
+
}
|
|
345
|
+
return data;
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// Use plugins
|
|
350
|
+
const db = await createDatabase({
|
|
289
351
|
tables,
|
|
290
|
-
|
|
352
|
+
plugins: [auditPlugin, slugPlugin, validationPlugin],
|
|
291
353
|
});
|
|
354
|
+
|
|
355
|
+
// Hooks run automatically
|
|
356
|
+
await db.insert(posts).values({ title: 'My Post' }); // slug auto-generated
|
|
357
|
+
await db.insert(users).values({ email: 'invalid' }); // throws error
|
|
358
|
+
|
|
359
|
+
// Check loaded plugins
|
|
360
|
+
db.plugins.list(); // ['audit', 'slug', 'validation']
|
|
292
361
|
```
|
|
293
362
|
|
|
294
|
-
##
|
|
363
|
+
## Best Practices
|
|
295
364
|
|
|
296
|
-
###
|
|
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:
|
|
297
379
|
|
|
298
380
|
```typescript
|
|
299
|
-
|
|
300
|
-
import
|
|
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
|
+
});
|
|
301
390
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
};
|
|
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
|
+
});
|
|
306
395
|
|
|
307
|
-
|
|
308
|
-
|
|
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 };
|
|
309
406
|
```
|
|
310
407
|
|
|
311
|
-
###
|
|
408
|
+
### 2. Single Database Instance
|
|
312
409
|
|
|
313
410
|
```typescript
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
],
|
|
319
|
-
posts: [
|
|
320
|
-
{ title: 'Hello World', content: 'My first post', userId: 1 },
|
|
321
|
-
{ title: 'Second Post', content: 'Another post', userId: 2 }
|
|
322
|
-
]
|
|
323
|
-
};
|
|
411
|
+
// db/index.ts
|
|
412
|
+
import nanodb from 'nanodb-orm';
|
|
413
|
+
import { schema } from './schema';
|
|
414
|
+
import { seedData } from './seeds';
|
|
324
415
|
|
|
325
|
-
|
|
326
|
-
await DatabaseSync.setup();
|
|
416
|
+
export const db = await nanodb.createDatabase({ tables: schema, seedData });
|
|
327
417
|
```
|
|
328
418
|
|
|
329
|
-
|
|
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
|
|
330
428
|
|
|
331
429
|
```typescript
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
dropTables: true // Allow dropping tables
|
|
336
|
-
};
|
|
430
|
+
// db/types.ts
|
|
431
|
+
import { type SelectModel, type InsertModel } from 'nanodb-orm';
|
|
432
|
+
import { users, posts } from './schema';
|
|
337
433
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
}
|
|
342
442
|
```
|
|
343
443
|
|
|
344
|
-
###
|
|
444
|
+
### 4. Prefer Grouped Imports
|
|
345
445
|
|
|
346
446
|
```typescript
|
|
347
|
-
|
|
447
|
+
// ✅ Clean - default import
|
|
448
|
+
import nanodb from 'nanodb-orm';
|
|
348
449
|
|
|
349
|
-
|
|
350
|
-
await
|
|
351
|
-
await db.run('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);
|
|
352
|
-
await db.run('INSERT INTO posts (title, content, userId) VALUES (?, ?, ?)', ['Hello', 'World', 1]);
|
|
353
|
-
// All operations succeed or all fail
|
|
354
|
-
});
|
|
450
|
+
const users = nanodb.schema.table('users', { ... });
|
|
451
|
+
await db.select().from(users).where(nanodb.query.eq(users.id, 1));
|
|
355
452
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
async (db) => await db.run('INSERT INTO users ...'),
|
|
359
|
-
async (db) => await db.run('INSERT INTO posts ...'),
|
|
360
|
-
async (db) => await db.run('UPDATE stats ...')
|
|
361
|
-
]);
|
|
453
|
+
// ✅ Also good - grouped imports
|
|
454
|
+
import { schema, query, errors } from 'nanodb-orm';
|
|
362
455
|
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
await db.run('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
366
|
-
});
|
|
456
|
+
// ❌ Avoid - many individual imports
|
|
457
|
+
import { table, integer, text, eq, gte, and, sql, count, ... } from 'nanodb-orm';
|
|
367
458
|
```
|
|
368
459
|
|
|
369
|
-
###
|
|
460
|
+
### 5. Handle Errors Gracefully
|
|
370
461
|
|
|
371
462
|
```typescript
|
|
372
|
-
import {
|
|
463
|
+
import { parseDbError, DatabaseError } from 'nanodb-orm';
|
|
373
464
|
|
|
374
465
|
try {
|
|
375
|
-
await
|
|
466
|
+
await db.insert(users).values({ email: 'duplicate@test.com' });
|
|
376
467
|
} catch (error) {
|
|
377
468
|
if (error instanceof DatabaseError) {
|
|
378
|
-
|
|
379
|
-
console.log(
|
|
380
|
-
console.log(
|
|
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());
|
|
381
475
|
}
|
|
382
476
|
}
|
|
383
|
-
|
|
384
|
-
// Non-throwing error handling
|
|
385
|
-
const result = ErrorHandler.handleNonThrowingError(
|
|
386
|
-
someError,
|
|
387
|
-
'optional-operation',
|
|
388
|
-
'This operation is optional'
|
|
389
|
-
);
|
|
390
477
|
```
|
|
391
478
|
|
|
392
|
-
###
|
|
479
|
+
### 6. Use Transactions for Atomic Operations
|
|
393
480
|
|
|
394
481
|
```typescript
|
|
395
|
-
import
|
|
482
|
+
import nanodb from 'nanodb-orm';
|
|
396
483
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
});
|
|
402
489
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
console.log('Database is connected');
|
|
490
|
+
if (!result.success) {
|
|
491
|
+
console.log('Rolled back:', result.error?.message);
|
|
406
492
|
}
|
|
407
493
|
```
|
|
408
494
|
|
|
409
|
-
###
|
|
495
|
+
### 7. Validate on Startup (Production)
|
|
410
496
|
|
|
411
497
|
```typescript
|
|
412
|
-
import
|
|
498
|
+
import nanodb from 'nanodb-orm';
|
|
413
499
|
|
|
414
|
-
|
|
415
|
-
const schemaInfo = SchemaIntrospection.getSchemaStats();
|
|
416
|
-
console.log('Total tables:', schemaInfo.totalTables);
|
|
417
|
-
console.log('Table details:', schemaInfo.tableDetails);
|
|
500
|
+
const db = await nanodb.createDatabase({ tables: schema });
|
|
418
501
|
|
|
419
|
-
|
|
420
|
-
const validation = await
|
|
421
|
-
if (!validation.isValid) {
|
|
422
|
-
|
|
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
|
+
}
|
|
423
512
|
}
|
|
424
513
|
```
|
|
425
514
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
The package includes built-in testing utilities:
|
|
515
|
+
### 8. Keep Plugins Simple
|
|
429
516
|
|
|
430
517
|
```typescript
|
|
431
|
-
|
|
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
|
+
};
|
|
432
530
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
// Reset database for each test
|
|
436
|
-
await DatabaseSync.reset();
|
|
437
|
-
});
|
|
531
|
+
// Avoid: complex business logic in hooks
|
|
532
|
+
```
|
|
438
533
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
443
542
|
```
|
|
444
543
|
|
|
445
|
-
|
|
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`
|
|
446
548
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
549
|
+
## Configuration
|
|
550
|
+
|
|
551
|
+
### Environment Variables
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
# Remote Turso database
|
|
555
|
+
TURSO_CONNECTION_URL=libsql://your-db.turso.io
|
|
556
|
+
TURSO_AUTH_TOKEN=your-token
|
|
557
|
+
|
|
558
|
+
# Force local SQLite
|
|
559
|
+
FORCE_LOCAL_DB=true
|
|
560
|
+
|
|
561
|
+
# Custom database path (works with FORCE_LOCAL_DB or as fallback)
|
|
562
|
+
DATABASE_PATH=./data/myapp.db
|
|
563
|
+
```
|
|
451
564
|
|
|
452
|
-
###
|
|
453
|
-
|
|
454
|
-
-
|
|
455
|
-
-
|
|
565
|
+
### Database Selection
|
|
566
|
+
|
|
567
|
+
- **Turso** — Used when `TURSO_CONNECTION_URL` and `TURSO_AUTH_TOKEN` are set
|
|
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
|
|
570
|
+
- **Test Mode** — Isolated `test.db` when `NODE_ENV=test`
|
|
456
571
|
|
|
457
572
|
## Error Handling
|
|
458
573
|
|
|
459
|
-
|
|
574
|
+
Errors are automatically parsed into user-friendly messages:
|
|
460
575
|
|
|
461
576
|
```typescript
|
|
462
|
-
import { DatabaseError } from 'nanodb-orm';
|
|
577
|
+
import { DatabaseError, SchemaError, SeedError, parseDbError } from 'nanodb-orm';
|
|
463
578
|
|
|
464
579
|
try {
|
|
465
580
|
await DatabaseSync.setup();
|
|
466
581
|
} catch (error) {
|
|
467
582
|
if (error instanceof DatabaseError) {
|
|
468
|
-
console.log(
|
|
469
|
-
console.log('
|
|
583
|
+
console.log(error.message); // User-friendly message
|
|
584
|
+
console.log(error.operation); // 'seed', 'migration', etc.
|
|
585
|
+
console.log(error.table); // Table name if applicable
|
|
586
|
+
console.log(error.detail); // Additional context
|
|
470
587
|
}
|
|
471
588
|
}
|
|
472
589
|
```
|
|
473
590
|
|
|
474
|
-
|
|
591
|
+
Error output is clean and actionable:
|
|
475
592
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
593
|
+
```
|
|
594
|
+
┌─ nanodb-orm error ─────────────────────────────
|
|
595
|
+
│ Column "email" does not exist
|
|
596
|
+
│ Table: users
|
|
597
|
+
│ Operation: seed
|
|
598
|
+
│ Detail: Row data: {"name":"Alice","email":"alice@example.com"}
|
|
599
|
+
└────────────────────────────────────────────────
|
|
600
|
+
```
|
|
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.
|
|
481
673
|
|
|
482
674
|
## License
|
|
483
675
|
|
|
484
676
|
MIT © Damilola Alao
|
|
485
|
-
|
|
486
|
-
## Changelog
|
|
487
|
-
|
|
488
|
-
### 0.0.2
|
|
489
|
-
- **Enhanced Documentation**: Added comprehensive Drizzle ORM integration examples
|
|
490
|
-
- **Real Model Examples**: Updated documentation with actual Drizzle table definitions
|
|
491
|
-
- **Schema Validation Fix**: Fixed table existence validation logic for proper health checks
|
|
492
|
-
- **LLM Documentation**: Added detailed `llm.txt` file for AI/LLM integration
|
|
493
|
-
- **Column Types Guide**: Added complete Drizzle column types and methods documentation
|
|
494
|
-
- **Foreign Key Support**: Documented foreign key relationships and cascade deletes
|
|
495
|
-
- **Type Safety**: Enhanced TypeScript integration examples with Drizzle's type inference
|
|
496
|
-
|
|
497
|
-
### 0.0.1
|
|
498
|
-
- Initial release
|
|
499
|
-
- Auto-migration system
|
|
500
|
-
- Schema introspection
|
|
501
|
-
- Multi-database support
|
|
502
|
-
- TypeScript support
|
|
503
|
-
- Testing utilities
|