nanodb-orm 0.0.2 → 0.0.4
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 +211 -309
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +270 -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 +11 -20
- package/dist/core/connection.d.ts.map +1 -1
- package/dist/core/connection.js +35 -53
- 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 +40 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -31
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +79 -22
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +246 -48
- 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 +130 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -3
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-handler.d.ts +11 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +36 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -2
- 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 +222 -412
- package/dist/utils/migrations.js.map +1 -1
- package/dist/utils/schema-introspection.d.ts +30 -173
- package/dist/utils/schema-introspection.d.ts.map +1 -1
- package/dist/utils/schema-introspection.js +129 -460
- 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 +101 -185
- package/dist/utils/seeds.js.map +1 -1
- package/dist/utils/sync.d.ts +16 -54
- package/dist/utils/sync.d.ts.map +1 -1
- package/dist/utils/sync.js +71 -173
- package/dist/utils/sync.js.map +1 -1
- package/dist/utils/transactions.d.ts +16 -0
- package/dist/utils/transactions.d.ts.map +1 -0
- package/dist/utils/transactions.js +44 -0
- package/dist/utils/transactions.js.map +1 -0
- 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/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 -268
package/README.md
CHANGED
|
@@ -1,434 +1,336 @@
|
|
|
1
1
|
# nanodb-orm
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight, schema-agnostic 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
|
-
-
|
|
7
|
+
- **Auto-Migrations** — Automatically creates and updates database schemas from Drizzle tables
|
|
8
|
+
- **Schema Introspection** — Comprehensive schema analysis and validation
|
|
9
|
+
- **Multi-Database** — Works with local SQLite and remote Turso databases
|
|
10
|
+
- **Transactions** — Full transaction support with automatic rollback
|
|
11
|
+
- **CLI Tools** — Built-in commands including Drizzle Studio integration
|
|
12
|
+
- **Type Safe** — Full TypeScript support with proper type inference
|
|
13
|
+
- **Plugin System** — Extensible with hooks for audit, validation, transformations
|
|
14
|
+
- **Minimal** — ~1K lines of code, zero bloat
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
19
|
npm install nanodb-orm
|
|
20
|
+
|
|
21
|
+
# For Drizzle Studio support (optional)
|
|
22
|
+
npm install drizzle-kit --save-dev
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
##
|
|
25
|
+
## CLI
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
nanodb-orm includes a CLI for common database operations:
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
```bash
|
|
30
|
+
# Launch Drizzle Studio (visual database browser)
|
|
31
|
+
npx nanodb studio
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
33
|
+
# With custom port
|
|
34
|
+
npx nanodb studio --port 3000
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
name: text('name').notNull(),
|
|
34
|
-
age: integer('age').notNull(),
|
|
35
|
-
email: text('email').unique().notNull(),
|
|
36
|
-
});
|
|
36
|
+
# With specific database file
|
|
37
|
+
npx nanodb studio --db ./data/myapp.db
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
40
45
|
```
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
// models/posts.ts
|
|
44
|
-
import { sql } from 'drizzle-orm';
|
|
45
|
-
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
46
|
-
import { usersTable } from './users';
|
|
47
|
+
### Drizzle Studio
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
50
|
-
title: text('title').notNull(),
|
|
51
|
-
content: text('content').notNull(),
|
|
52
|
-
userId: integer('user_id')
|
|
53
|
-
.notNull()
|
|
54
|
-
.references(() => usersTable.id, { onDelete: 'cascade' }),
|
|
55
|
-
createdAt: text('created_at')
|
|
56
|
-
.default(sql`(datetime('now'))`)
|
|
57
|
-
.notNull(),
|
|
58
|
-
updatedAt: text('updated_at').$onUpdate(() => new Date().toISOString()),
|
|
59
|
-
});
|
|
49
|
+
Launch a visual database browser at `https://local.drizzle.studio`:
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
```bash
|
|
52
|
+
npx nanodb studio
|
|
63
53
|
```
|
|
64
54
|
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
### 1. Define Your Schema
|
|
60
|
+
|
|
65
61
|
```typescript
|
|
66
|
-
|
|
67
|
-
import { sql } from 'drizzle-orm';
|
|
68
|
-
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
62
|
+
import { table, integer, text } from 'nanodb-orm';
|
|
69
63
|
|
|
70
|
-
|
|
64
|
+
const users = table('users', {
|
|
71
65
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
72
66
|
name: text('name').notNull(),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
isActive: integer('is_active').notNull().default(1),
|
|
76
|
-
createdAt: text('created_at')
|
|
77
|
-
.default(sql`(datetime('now'))`)
|
|
78
|
-
.notNull(),
|
|
67
|
+
email: text('email').unique().notNull(),
|
|
68
|
+
age: integer('age'),
|
|
79
69
|
});
|
|
80
70
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
const posts = table('posts', {
|
|
72
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
73
|
+
title: text('title').notNull(),
|
|
74
|
+
userId: integer('userId').notNull(),
|
|
75
|
+
});
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
// models/index.ts
|
|
87
|
-
export * from './users';
|
|
88
|
-
export * from './posts';
|
|
89
|
-
export * from './categories';
|
|
90
|
-
|
|
91
|
-
// Import tables for aggregation
|
|
92
|
-
import { usersTable } from './users';
|
|
93
|
-
import { postsTable } from './posts';
|
|
94
|
-
import { categoriesTable } from './categories';
|
|
95
|
-
|
|
96
|
-
// Export aggregated tables for nanodb-orm
|
|
97
|
-
export const tables = {
|
|
98
|
-
users: usersTable,
|
|
99
|
-
posts: postsTable,
|
|
100
|
-
categories: categoriesTable,
|
|
101
|
-
} as const;
|
|
77
|
+
const tables = { users, posts };
|
|
102
78
|
```
|
|
103
79
|
|
|
104
|
-
### 2.
|
|
80
|
+
### 2. Create Database
|
|
105
81
|
|
|
106
82
|
```typescript
|
|
107
|
-
import {
|
|
108
|
-
import { tables } from './models';
|
|
83
|
+
import { createDatabase } from 'nanodb-orm';
|
|
109
84
|
|
|
110
|
-
//
|
|
111
|
-
|
|
85
|
+
// One line: creates tables, runs migrations, seeds data, returns db
|
|
86
|
+
const db = await createDatabase({
|
|
112
87
|
tables,
|
|
113
88
|
seedData: {
|
|
114
|
-
users: [
|
|
115
|
-
|
|
116
|
-
{ name: 'Jane Smith', age: 25, email: 'jane@example.com' }
|
|
117
|
-
],
|
|
118
|
-
posts: [
|
|
119
|
-
{ title: 'Welcome Post', content: 'This is my first post!', userId: 1 },
|
|
120
|
-
{ title: 'Getting Started', content: 'Here are some tips...', userId: 2 }
|
|
121
|
-
],
|
|
122
|
-
categories: [
|
|
123
|
-
{ name: 'Technology', description: 'Tech-related posts', color: '#3B82F6' },
|
|
124
|
-
{ name: 'Lifestyle', description: 'Life and personal posts', color: '#10B981' }
|
|
125
|
-
]
|
|
126
|
-
}
|
|
89
|
+
users: [{ name: 'Alice', email: 'alice@example.com', age: 28 }],
|
|
90
|
+
},
|
|
127
91
|
});
|
|
128
92
|
|
|
129
|
-
//
|
|
130
|
-
|
|
93
|
+
// Store db and use it everywhere - no need for getInstance() or getDb()
|
|
94
|
+
export { db };
|
|
131
95
|
```
|
|
132
96
|
|
|
133
|
-
### 3.
|
|
134
|
-
|
|
135
|
-
nanodb-orm works seamlessly with Drizzle ORM table definitions. Here are the key Drizzle column types and methods:
|
|
136
|
-
|
|
137
|
-
#### Drizzle Column Types
|
|
138
|
-
- `integer()` - Integer numbers
|
|
139
|
-
- `text()` - Text strings
|
|
140
|
-
- `real()` - Floating point numbers
|
|
141
|
-
- `blob()` - Binary data
|
|
97
|
+
### 3. Query with Type Safety
|
|
142
98
|
|
|
143
|
-
#### Drizzle Column Methods
|
|
144
|
-
- `.primaryKey({ autoIncrement: true })` - Primary key with auto-increment
|
|
145
|
-
- `.notNull()` - NOT NULL constraint
|
|
146
|
-
- `.unique()` - UNIQUE constraint
|
|
147
|
-
- `.default(value)` - Default value
|
|
148
|
-
- `.references(table.column)` - Foreign key reference
|
|
149
|
-
|
|
150
|
-
#### Example Drizzle Column Definitions
|
|
151
99
|
```typescript
|
|
152
|
-
|
|
153
|
-
id: integer('id').primaryKey({ autoIncrement: true })
|
|
154
|
-
|
|
155
|
-
// Required text field
|
|
156
|
-
name: text('name').notNull()
|
|
157
|
-
|
|
158
|
-
// Optional text field with default
|
|
159
|
-
color: text('color').notNull().default('#000000')
|
|
100
|
+
import { eq, gte } from 'nanodb-orm';
|
|
160
101
|
|
|
161
|
-
//
|
|
162
|
-
|
|
102
|
+
// SELECT
|
|
103
|
+
const allUsers = await db.select().from(users);
|
|
104
|
+
const adults = await db.select().from(users).where(gte(users.age, 18));
|
|
163
105
|
|
|
164
|
-
//
|
|
165
|
-
|
|
106
|
+
// INSERT
|
|
107
|
+
await db.insert(users).values({ name: 'Bob', email: 'bob@example.com' });
|
|
166
108
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
.default(sql`(datetime('now'))`)
|
|
170
|
-
.notNull()
|
|
109
|
+
// UPDATE
|
|
110
|
+
await db.update(users).set({ name: 'Robert' }).where(eq(users.email, 'bob@example.com'));
|
|
171
111
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
.notNull()
|
|
175
|
-
.references(() => usersTable.id, { onDelete: 'cascade' })
|
|
112
|
+
// DELETE
|
|
113
|
+
await db.delete(users).where(eq(users.email, 'bob@example.com'));
|
|
176
114
|
```
|
|
177
115
|
|
|
178
116
|
## API Reference
|
|
179
117
|
|
|
180
|
-
### `
|
|
118
|
+
### `createDatabase(config)`
|
|
181
119
|
|
|
182
|
-
|
|
120
|
+
Create and initialize database. Returns `db` with all utilities attached.
|
|
183
121
|
|
|
184
122
|
```typescript
|
|
185
|
-
|
|
186
|
-
tables:
|
|
187
|
-
seedData
|
|
188
|
-
migrationConfig
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
123
|
+
const db = await createDatabase({
|
|
124
|
+
tables: { users, posts },
|
|
125
|
+
seedData: { users: [...] },
|
|
126
|
+
migrationConfig: {
|
|
127
|
+
preserveData: true, // default: true
|
|
128
|
+
autoMigrate: true, // default: true
|
|
129
|
+
dropTables: false, // default: false
|
|
130
|
+
},
|
|
131
|
+
plugins: [auditPlugin, validationPlugin], // optional
|
|
132
|
+
});
|
|
196
133
|
```
|
|
197
134
|
|
|
198
|
-
### `
|
|
199
|
-
|
|
200
|
-
Main database synchronization class.
|
|
135
|
+
### Database Operations (from `db`)
|
|
201
136
|
|
|
202
137
|
```typescript
|
|
203
|
-
//
|
|
204
|
-
await
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
138
|
+
// Health & Status
|
|
139
|
+
await db.healthCheck(); // { healthy, tables, totalRecords, ... }
|
|
140
|
+
await db.isReady(); // true/false
|
|
141
|
+
await db.sync(); // Sync with Turso (if remote)
|
|
142
|
+
|
|
143
|
+
// Reset & Seed
|
|
144
|
+
await db.reset(); // Drop all, recreate, reseed
|
|
145
|
+
await db.seed(); // Re-seed data
|
|
146
|
+
await db.clearData(); // Delete all data (keep tables)
|
|
211
147
|
```
|
|
212
148
|
|
|
213
|
-
### `
|
|
214
|
-
|
|
215
|
-
Comprehensive schema analysis utilities.
|
|
149
|
+
### Schema Introspection (from `db.schema`)
|
|
216
150
|
|
|
217
151
|
```typescript
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
//
|
|
222
|
-
|
|
152
|
+
db.schema.tables(); // ['users', 'posts']
|
|
153
|
+
db.schema.getTable('users'); // { columns, primaryKey, indexes }
|
|
154
|
+
db.schema.getColumns('users'); // ['id', 'name', 'email']
|
|
155
|
+
await db.schema.validate(); // { isValid, missingTables, ... }
|
|
156
|
+
db.schema.stats(); // Full schema statistics
|
|
157
|
+
db.schema.relationships(); // Foreign key relationships
|
|
158
|
+
```
|
|
223
159
|
|
|
224
|
-
|
|
225
|
-
const stats = SchemaIntrospection.getSchemaStats();
|
|
160
|
+
### Migrations (from `db.migrations`)
|
|
226
161
|
|
|
227
|
-
|
|
228
|
-
|
|
162
|
+
```typescript
|
|
163
|
+
await db.migrations.run(); // Run pending migrations
|
|
164
|
+
await db.migrations.validate(); // Validate schema vs DB
|
|
165
|
+
await db.migrations.checkTables(); // { users: true, posts: true }
|
|
229
166
|
```
|
|
230
167
|
|
|
231
|
-
### `
|
|
168
|
+
### `transaction(fn)`
|
|
232
169
|
|
|
233
|
-
|
|
170
|
+
Execute operations atomically.
|
|
234
171
|
|
|
235
172
|
```typescript
|
|
236
|
-
|
|
237
|
-
await DatabaseMigrations.initializeSchema();
|
|
173
|
+
import { transaction, sql } from 'nanodb-orm';
|
|
238
174
|
|
|
239
|
-
|
|
240
|
-
|
|
175
|
+
const result = await transaction(async (tx) => {
|
|
176
|
+
await tx.run(sql`INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')`);
|
|
177
|
+
await tx.run(sql`INSERT INTO posts (title, userId) VALUES ('Hello', 1)`);
|
|
178
|
+
return { created: true };
|
|
179
|
+
});
|
|
241
180
|
|
|
242
|
-
|
|
243
|
-
|
|
181
|
+
if (result.success) {
|
|
182
|
+
console.log(result.result);
|
|
183
|
+
} else {
|
|
184
|
+
console.log('Rolled back:', result.error?.message);
|
|
185
|
+
}
|
|
244
186
|
```
|
|
245
187
|
|
|
246
|
-
### `
|
|
188
|
+
### `parseDbError(error, context)`
|
|
247
189
|
|
|
248
|
-
|
|
190
|
+
Parse SQLite errors into user-friendly messages.
|
|
249
191
|
|
|
250
192
|
```typescript
|
|
251
|
-
|
|
252
|
-
await DatabaseSeeds.seedDatabase();
|
|
253
|
-
|
|
254
|
-
// Check if database has data
|
|
255
|
-
const hasData = await DatabaseSeeds.hasData();
|
|
193
|
+
import { parseDbError } from 'nanodb-orm';
|
|
256
194
|
|
|
257
|
-
|
|
258
|
-
await
|
|
195
|
+
try {
|
|
196
|
+
await db.run('INSERT INTO users ...');
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const parsed = parseDbError(error, { table: 'users', operation: 'insert' });
|
|
199
|
+
console.log(parsed.message); // "Column 'email' does not exist"
|
|
200
|
+
}
|
|
259
201
|
```
|
|
260
202
|
|
|
261
|
-
##
|
|
203
|
+
## Plugins
|
|
262
204
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
# Turso Database (optional)
|
|
267
|
-
TURSO_CONNECTION_URL=libsql://your-database.turso.io
|
|
268
|
-
TURSO_AUTH_TOKEN=your-auth-token
|
|
205
|
+
Extend nanodb-orm with custom hooks that run automatically on database operations.
|
|
269
206
|
|
|
270
|
-
|
|
271
|
-
FORCE_LOCAL_DB=true
|
|
272
|
-
NODE_ENV=test
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Migration Configuration
|
|
207
|
+
### Plugin Interface
|
|
276
208
|
|
|
277
209
|
```typescript
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
210
|
+
import { NanoPlugin } from 'nanodb-orm';
|
|
211
|
+
|
|
212
|
+
const myPlugin: NanoPlugin = {
|
|
213
|
+
name: 'my-plugin',
|
|
214
|
+
|
|
215
|
+
// Lifecycle
|
|
216
|
+
install: (db) => db, // Modify db instance
|
|
217
|
+
onReady: (db) => {}, // Called after createDatabase
|
|
218
|
+
onError: (err, op, table) => {}, // Called on hook errors
|
|
219
|
+
|
|
220
|
+
// Auto hooks (run automatically)
|
|
221
|
+
beforeInsert: (table, data) => data, // Transform data before insert
|
|
222
|
+
afterInsert: (table, data, result) => {},
|
|
223
|
+
beforeUpdate: (table, data) => data, // Transform data before update
|
|
224
|
+
afterUpdate: (table, data, result) => {},
|
|
225
|
+
beforeDelete: (table, condition) => condition,
|
|
226
|
+
afterDelete: (table, condition, result) => {},
|
|
227
|
+
|
|
228
|
+
// Query hooks (also auto-triggered)
|
|
229
|
+
beforeQuery: (table, fields) => fields,
|
|
230
|
+
afterQuery: (table, fields, result) => {},
|
|
282
231
|
};
|
|
283
|
-
|
|
284
|
-
initializeDatabase({
|
|
285
|
-
tables,
|
|
286
|
-
migrationConfig
|
|
287
|
-
});
|
|
288
232
|
```
|
|
289
233
|
|
|
290
|
-
|
|
234
|
+
### Example Plugins
|
|
291
235
|
|
|
292
|
-
|
|
236
|
+
These are **example plugins you can create** - nanodb-orm provides the plugin system, you build the plugins:
|
|
293
237
|
|
|
294
238
|
```typescript
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
239
|
+
// Example: Audit logging with timing
|
|
240
|
+
const timers = new Map<string, number>();
|
|
241
|
+
const auditPlugin: NanoPlugin = {
|
|
242
|
+
name: 'audit',
|
|
243
|
+
beforeInsert: (table) => { timers.set('op', performance.now()); console.log(`INSERT ${table}`); },
|
|
244
|
+
afterInsert: () => { console.log(` ↳ ${(performance.now() - timers.get('op')!).toFixed(1)}ms`); },
|
|
245
|
+
beforeQuery: (table) => { timers.set('op', performance.now()); console.log(`SELECT ${table}`); },
|
|
246
|
+
afterQuery: (t, _, rows) => { console.log(` ↳ ${rows.length} rows in ${(performance.now() - timers.get('op')!).toFixed(1)}ms`); },
|
|
301
247
|
};
|
|
302
248
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
{ name: 'Alice', age: 25, email: 'alice@example.com' },
|
|
313
|
-
{ name: 'Bob', age: 30, email: 'bob@example.com' }
|
|
314
|
-
],
|
|
315
|
-
posts: [
|
|
316
|
-
{ title: 'Hello World', content: 'My first post', userId: 1 },
|
|
317
|
-
{ title: 'Second Post', content: 'Another post', userId: 2 }
|
|
318
|
-
]
|
|
249
|
+
// Auto-generate slugs
|
|
250
|
+
const slugPlugin: NanoPlugin = {
|
|
251
|
+
name: 'slug',
|
|
252
|
+
beforeInsert: (table, data) => {
|
|
253
|
+
if (table === 'posts' && data.title && !data.slug) {
|
|
254
|
+
return { ...data, slug: data.title.toLowerCase().replace(/\s+/g, '-') };
|
|
255
|
+
}
|
|
256
|
+
return data;
|
|
257
|
+
},
|
|
319
258
|
};
|
|
320
259
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
autoMigrate: true, // Enable auto-migrations
|
|
331
|
-
dropTables: true // Allow dropping tables
|
|
260
|
+
// Validation
|
|
261
|
+
const validationPlugin: NanoPlugin = {
|
|
262
|
+
name: 'validation',
|
|
263
|
+
beforeInsert: (table, data) => {
|
|
264
|
+
if (table === 'users' && !data.email?.includes('@')) {
|
|
265
|
+
throw new Error('Invalid email format');
|
|
266
|
+
}
|
|
267
|
+
return data;
|
|
268
|
+
},
|
|
332
269
|
};
|
|
333
270
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
271
|
+
// Use plugins
|
|
272
|
+
const db = await createDatabase({
|
|
273
|
+
tables,
|
|
274
|
+
plugins: [auditPlugin, slugPlugin, validationPlugin],
|
|
337
275
|
});
|
|
338
|
-
```
|
|
339
276
|
|
|
340
|
-
|
|
277
|
+
// Hooks run automatically
|
|
278
|
+
await db.insert(posts).values({ title: 'My Post' }); // slug auto-generated
|
|
279
|
+
await db.insert(users).values({ email: 'invalid' }); // throws error
|
|
341
280
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// Get comprehensive schema information
|
|
346
|
-
const schemaInfo = SchemaIntrospection.getSchemaStats();
|
|
347
|
-
console.log('Total tables:', schemaInfo.totalTables);
|
|
348
|
-
console.log('Table details:', schemaInfo.tableDetails);
|
|
349
|
-
|
|
350
|
-
// Validate schema integrity
|
|
351
|
-
const validation = await SchemaIntrospection.validateSchema();
|
|
352
|
-
if (!validation.isValid) {
|
|
353
|
-
console.log('Schema issues:', validation.errors);
|
|
354
|
-
}
|
|
281
|
+
// Check loaded plugins
|
|
282
|
+
db.plugins.list(); // ['audit', 'slug', 'validation']
|
|
355
283
|
```
|
|
356
284
|
|
|
357
|
-
##
|
|
358
|
-
|
|
359
|
-
The package includes built-in testing utilities:
|
|
285
|
+
## Configuration
|
|
360
286
|
|
|
361
|
-
|
|
362
|
-
import { DatabaseSync } from 'nanodb-orm';
|
|
287
|
+
### Environment Variables
|
|
363
288
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
289
|
+
```bash
|
|
290
|
+
# Remote Turso database
|
|
291
|
+
TURSO_CONNECTION_URL=libsql://your-db.turso.io
|
|
292
|
+
TURSO_AUTH_TOKEN=your-token
|
|
369
293
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
});
|
|
373
|
-
});
|
|
294
|
+
# Force local SQLite (for testing)
|
|
295
|
+
FORCE_LOCAL_DB=true
|
|
374
296
|
```
|
|
375
297
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
### Local SQLite
|
|
379
|
-
- Automatically used when Turso credentials are not available
|
|
380
|
-
- Perfect for development and testing
|
|
381
|
-
- File-based storage
|
|
298
|
+
### Database Selection
|
|
382
299
|
|
|
383
|
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
- Production-ready with global replication
|
|
300
|
+
- **Turso** — Used when `TURSO_CONNECTION_URL` and `TURSO_AUTH_TOKEN` are set
|
|
301
|
+
- **Local SQLite** — Fallback when Turso credentials are missing
|
|
302
|
+
- **Test Mode** — Isolated `test.db` when `NODE_ENV=test`
|
|
387
303
|
|
|
388
304
|
## Error Handling
|
|
389
305
|
|
|
390
|
-
|
|
306
|
+
Errors are automatically parsed into user-friendly messages:
|
|
391
307
|
|
|
392
308
|
```typescript
|
|
393
|
-
import { DatabaseError } from 'nanodb-orm';
|
|
309
|
+
import { DatabaseError, SchemaError, SeedError, parseDbError } from 'nanodb-orm';
|
|
394
310
|
|
|
395
311
|
try {
|
|
396
312
|
await DatabaseSync.setup();
|
|
397
313
|
} catch (error) {
|
|
398
314
|
if (error instanceof DatabaseError) {
|
|
399
|
-
console.log(
|
|
400
|
-
console.log('
|
|
315
|
+
console.log(error.message); // User-friendly message
|
|
316
|
+
console.log(error.operation); // 'seed', 'migration', etc.
|
|
317
|
+
console.log(error.table); // Table name if applicable
|
|
318
|
+
console.log(error.detail); // Additional context
|
|
401
319
|
}
|
|
402
320
|
}
|
|
403
321
|
```
|
|
404
322
|
|
|
405
|
-
|
|
323
|
+
Error output is clean and actionable:
|
|
406
324
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
325
|
+
```
|
|
326
|
+
┌─ nanodb-orm error ─────────────────────────────
|
|
327
|
+
│ Column "email" does not exist
|
|
328
|
+
│ Table: users
|
|
329
|
+
│ Operation: seed
|
|
330
|
+
│ Detail: Row data: {"name":"Alice","email":"alice@example.com"}
|
|
331
|
+
└────────────────────────────────────────────────
|
|
332
|
+
```
|
|
412
333
|
|
|
413
334
|
## License
|
|
414
335
|
|
|
415
336
|
MIT © Damilola Alao
|
|
416
|
-
|
|
417
|
-
## Changelog
|
|
418
|
-
|
|
419
|
-
### 0.0.2
|
|
420
|
-
- **Enhanced Documentation**: Added comprehensive Drizzle ORM integration examples
|
|
421
|
-
- **Real Model Examples**: Updated documentation with actual Drizzle table definitions
|
|
422
|
-
- **Schema Validation Fix**: Fixed table existence validation logic for proper health checks
|
|
423
|
-
- **LLM Documentation**: Added detailed `llm.txt` file for AI/LLM integration
|
|
424
|
-
- **Column Types Guide**: Added complete Drizzle column types and methods documentation
|
|
425
|
-
- **Foreign Key Support**: Documented foreign key relationships and cascade deletes
|
|
426
|
-
- **Type Safety**: Enhanced TypeScript integration examples with Drizzle's type inference
|
|
427
|
-
|
|
428
|
-
### 0.0.1
|
|
429
|
-
- Initial release
|
|
430
|
-
- Auto-migration system
|
|
431
|
-
- Schema introspection
|
|
432
|
-
- Multi-database support
|
|
433
|
-
- TypeScript support
|
|
434
|
-
- Testing utilities
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* nanodb-orm CLI
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* studio - Launch Drizzle Studio (visual database browser)
|
|
7
|
+
* setup - Initialize database schema and seed data
|
|
8
|
+
* reset - Drop all tables and recreate
|
|
9
|
+
* status - Show database health and stats
|
|
10
|
+
* validate - Validate schema against database
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
|