noormme 1.0.2 → 1.0.3
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 +291 -11
- package/dist/cjs/dialect/postgresql/postgresql-adapter.d.ts +81 -0
- package/dist/cjs/dialect/postgresql/postgresql-adapter.js +40 -0
- package/dist/cjs/dialect/postgresql/postgresql-dialect-config.d.ts +65 -0
- package/dist/cjs/dialect/postgresql/postgresql-dialect-config.js +2 -0
- package/dist/cjs/dialect/postgresql/postgresql-dialect.d.ts +80 -0
- package/dist/cjs/dialect/postgresql/postgresql-dialect.js +76 -0
- package/dist/cjs/dialect/postgresql/postgresql-driver.d.ts +51 -0
- package/dist/cjs/dialect/postgresql/postgresql-driver.js +148 -0
- package/dist/cjs/dialect/postgresql/postgresql-features.d.ts +377 -0
- package/dist/cjs/dialect/postgresql/postgresql-features.js +459 -0
- package/dist/cjs/dialect/postgresql/postgresql-introspector.d.ts +12 -0
- package/dist/cjs/dialect/postgresql/postgresql-introspector.js +232 -0
- package/dist/cjs/dialect/postgresql/postgresql-query-compiler.d.ts +11 -0
- package/dist/cjs/dialect/postgresql/postgresql-query-compiler.js +39 -0
- package/dist/cjs/helpers/postgresql.d.ts +96 -0
- package/dist/cjs/helpers/postgresql.js +147 -0
- package/dist/cjs/index.d.ts +8 -1
- package/dist/cjs/index.js +21 -2
- package/dist/cjs/migration/data_migrator.d.ts +40 -0
- package/dist/cjs/migration/data_migrator.js +222 -0
- package/dist/cjs/migration/database_migration_manager.d.ts +77 -0
- package/dist/cjs/migration/database_migration_manager.js +383 -0
- package/dist/cjs/migration/index.d.ts +19 -0
- package/dist/cjs/migration/index.js +53 -0
- package/dist/cjs/migration/migration-types.d.ts +157 -0
- package/dist/cjs/migration/migration-types.js +5 -0
- package/dist/cjs/migration/schema_differ.d.ts +24 -0
- package/dist/cjs/migration/schema_differ.js +323 -0
- package/dist/cjs/migration/schema_introspector.d.ts +13 -0
- package/dist/cjs/migration/schema_introspector.js +364 -0
- package/dist/cjs/migration/type_mapper.d.ts +23 -0
- package/dist/cjs/migration/type_mapper.js +263 -0
- package/dist/cjs/noormme.js +58 -1
- package/dist/cjs/operation-node/data-type-node.d.ts +1 -1
- package/dist/cjs/operation-node/data-type-node.js +26 -0
- package/dist/cjs/schema/core/utils/type-mapper.js +21 -0
- package/dist/cjs/types/type-generator.js +9 -0
- package/dist/esm/dialect/postgresql/postgresql-adapter.d.ts +81 -0
- package/dist/esm/dialect/postgresql/postgresql-adapter.js +37 -0
- package/dist/esm/dialect/postgresql/postgresql-dialect-config.d.ts +65 -0
- package/dist/esm/dialect/postgresql/postgresql-dialect-config.js +2 -0
- package/dist/esm/dialect/postgresql/postgresql-dialect.d.ts +80 -0
- package/dist/esm/dialect/postgresql/postgresql-dialect.js +73 -0
- package/dist/esm/dialect/postgresql/postgresql-driver.d.ts +51 -0
- package/dist/esm/dialect/postgresql/postgresql-driver.js +112 -0
- package/dist/esm/dialect/postgresql/postgresql-features.d.ts +377 -0
- package/dist/esm/dialect/postgresql/postgresql-features.js +457 -0
- package/dist/esm/dialect/postgresql/postgresql-introspector.d.ts +12 -0
- package/dist/esm/dialect/postgresql/postgresql-introspector.js +229 -0
- package/dist/esm/dialect/postgresql/postgresql-query-compiler.d.ts +11 -0
- package/dist/esm/dialect/postgresql/postgresql-query-compiler.js +36 -0
- package/dist/esm/helpers/postgresql.d.ts +96 -0
- package/dist/esm/helpers/postgresql.js +126 -0
- package/dist/esm/index.d.ts +8 -1
- package/dist/esm/index.js +9 -1
- package/dist/esm/migration/data_migrator.d.ts +40 -0
- package/dist/esm/migration/data_migrator.js +217 -0
- package/dist/esm/migration/database_migration_manager.d.ts +77 -0
- package/dist/esm/migration/database_migration_manager.js +379 -0
- package/dist/esm/migration/index.d.ts +19 -0
- package/dist/esm/migration/index.js +21 -0
- package/dist/esm/migration/migration-types.d.ts +157 -0
- package/dist/esm/migration/migration-types.js +5 -0
- package/dist/esm/migration/schema_differ.d.ts +24 -0
- package/dist/esm/migration/schema_differ.js +319 -0
- package/dist/esm/migration/schema_introspector.d.ts +13 -0
- package/dist/esm/migration/schema_introspector.js +361 -0
- package/dist/esm/migration/type_mapper.d.ts +23 -0
- package/dist/esm/migration/type_mapper.js +258 -0
- package/dist/esm/noormme.js +58 -1
- package/dist/esm/operation-node/data-type-node.d.ts +1 -1
- package/dist/esm/operation-node/data-type-node.js +26 -0
- package/dist/esm/schema/core/utils/type-mapper.js +21 -0
- package/dist/esm/types/type-generator.js +9 -0
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -32,27 +32,46 @@ We fixed all that nonsense:
|
|
|
32
32
|
|
|
33
33
|
### **Real Benefits (The Stuff That Actually Matters):**
|
|
34
34
|
- 🚀 **Feel like a coding wizard** - AI that understands your project
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
35
|
+
- 🗄️ **Multi-database support** - SQLite for simplicity, PostgreSQL for power
|
|
36
|
+
- 💰 **Save money** - SQLite for dev/small apps, PostgreSQL when you need it
|
|
37
|
+
- 🔧 **Deploy easily** - SQLite = one file, PostgreSQL = full production ready
|
|
38
|
+
- ⚡ **Go fast** - Optimized performance for both SQLite and PostgreSQL
|
|
39
|
+
- 🛡️ **Stay secure** - Built-in connection pooling and security best practices
|
|
39
40
|
- 😊 **Have fun** - Finally, coding that doesn't make you cry
|
|
40
41
|
|
|
41
|
-
**Bottom line:** You get enterprise-grade superpowers with
|
|
42
|
+
**Bottom line:** You get enterprise-grade superpowers with your choice of database.
|
|
42
43
|
|
|
43
44
|
## 🚀 What's the Big Deal? (Spoiler: It's Actually Pretty Big)
|
|
44
45
|
|
|
45
46
|
**Before NOORMME:** You spend 8 hours setting up a database, 4 hours organizing your project, and 2 hours fighting with AI tools that suggest code from 2019.
|
|
46
47
|
|
|
47
48
|
**With NOORMME:** Your development environment becomes AI-ready in 5 minutes:
|
|
48
|
-
- ✅ **SQLite
|
|
49
|
+
- ✅ **SQLite & PostgreSQL support** - Start simple, scale when needed
|
|
49
50
|
- ✅ **Organized architecture** - Patterns that actually make sense
|
|
50
51
|
- ✅ **AI context rules** - Cursor IDE integration that works
|
|
51
52
|
- ✅ **Type safety** - TypeScript that doesn't make you want to throw things
|
|
52
|
-
- ✅ **Production features** - WAL mode, caching, and performance optimization
|
|
53
|
+
- ✅ **Production features** - WAL mode, connection pooling, caching, and performance optimization
|
|
53
54
|
|
|
54
55
|
**It's literally a development environment that makes AI assistance useful.** 🤯
|
|
55
56
|
|
|
57
|
+
## 🗄️ Database Support
|
|
58
|
+
|
|
59
|
+
NOORMME supports multiple database systems with the same API:
|
|
60
|
+
|
|
61
|
+
### SQLite - Perfect for:
|
|
62
|
+
- 🚀 **Prototyping & MVPs** - Get started in seconds
|
|
63
|
+
- 📱 **Edge deployments** - Cloudflare Workers, Vercel Edge, etc.
|
|
64
|
+
- 💻 **Local-first apps** - Embedded databases in desktop/mobile apps
|
|
65
|
+
- 🧪 **Testing** - Fast, isolated test environments
|
|
66
|
+
- 💰 **Small to medium apps** - No database server needed
|
|
67
|
+
|
|
68
|
+
### PostgreSQL - Perfect for:
|
|
69
|
+
- 🏢 **Production applications** - Battle-tested reliability
|
|
70
|
+
- 📊 **Complex queries** - Advanced SQL features
|
|
71
|
+
- 🔒 **Multi-user systems** - Robust concurrency handling
|
|
72
|
+
- 📈 **Scaling** - Handle millions of records
|
|
73
|
+
- 🌐 **Traditional deployments** - Client-server architecture
|
|
74
|
+
|
|
56
75
|
## ⚡ Get Started in 60 Seconds (No, Really)
|
|
57
76
|
|
|
58
77
|
### 1. Install It (The Easy Part)
|
|
@@ -66,19 +85,55 @@ npx noormme init
|
|
|
66
85
|
```
|
|
67
86
|
|
|
68
87
|
### 3. Point at Your Database (The "Wait, That's It?" Part)
|
|
88
|
+
|
|
89
|
+
#### Option A: SQLite (Simplest)
|
|
69
90
|
```typescript
|
|
70
91
|
import { NOORMME } from 'noormme'
|
|
71
92
|
|
|
72
93
|
const db = new NOORMME({
|
|
73
94
|
dialect: 'sqlite',
|
|
74
95
|
connection: {
|
|
75
|
-
database: './your-database.sqlite'
|
|
96
|
+
database: './your-database.sqlite'
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
await db.initialize()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Option B: PostgreSQL (Production-Ready)
|
|
104
|
+
```typescript
|
|
105
|
+
import { NOORMME } from 'noormme'
|
|
106
|
+
|
|
107
|
+
const db = new NOORMME({
|
|
108
|
+
dialect: 'postgresql',
|
|
109
|
+
connection: {
|
|
110
|
+
host: 'localhost',
|
|
111
|
+
port: 5432,
|
|
112
|
+
database: 'your_database',
|
|
113
|
+
username: 'postgres',
|
|
114
|
+
password: 'your_password'
|
|
76
115
|
}
|
|
77
116
|
})
|
|
78
117
|
|
|
79
118
|
await db.initialize()
|
|
80
119
|
```
|
|
81
120
|
|
|
121
|
+
#### Option C: Connection String (Easiest)
|
|
122
|
+
```typescript
|
|
123
|
+
import { NOORMME } from 'noormme'
|
|
124
|
+
|
|
125
|
+
// SQLite
|
|
126
|
+
const db = new NOORMME('sqlite:./your-database.sqlite')
|
|
127
|
+
|
|
128
|
+
// PostgreSQL
|
|
129
|
+
// const db = new NOORMME('postgresql://user:password@localhost:5432/database')
|
|
130
|
+
|
|
131
|
+
// Or use DATABASE_URL environment variable
|
|
132
|
+
// const db = new NOORMME() // Reads from process.env.DATABASE_URL
|
|
133
|
+
|
|
134
|
+
await db.initialize()
|
|
135
|
+
```
|
|
136
|
+
|
|
82
137
|
### 4. Start Building with AI Assistance (The "I'm a Genius Now" Part)
|
|
83
138
|
```typescript
|
|
84
139
|
// NOORMME automatically finds your 'users' table and creates methods
|
|
@@ -247,10 +302,50 @@ const featuredProducts = await productRepo.findManyByFeatured(true)
|
|
|
247
302
|
const pendingOrders = await orderRepo.findManyByStatus('pending')
|
|
248
303
|
```
|
|
249
304
|
|
|
305
|
+
### Production App with PostgreSQL (The "I'm Going Big" Example)
|
|
306
|
+
```typescript
|
|
307
|
+
// Connect to PostgreSQL for production-grade performance
|
|
308
|
+
const db = new NOORMME({
|
|
309
|
+
dialect: 'postgresql',
|
|
310
|
+
connection: {
|
|
311
|
+
host: process.env.DB_HOST || 'localhost',
|
|
312
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
313
|
+
database: process.env.DB_NAME || 'myapp',
|
|
314
|
+
username: process.env.DB_USER || 'postgres',
|
|
315
|
+
password: process.env.DB_PASSWORD,
|
|
316
|
+
ssl: process.env.NODE_ENV === 'production',
|
|
317
|
+
pool: {
|
|
318
|
+
max: 20, // Maximum connections
|
|
319
|
+
min: 5, // Minimum connections
|
|
320
|
+
idleTimeoutMillis: 30000,
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
await db.initialize()
|
|
326
|
+
|
|
327
|
+
// Same API as SQLite - just more powerful!
|
|
328
|
+
const userRepo = db.getRepository('users')
|
|
329
|
+
const users = await userRepo.findAll()
|
|
330
|
+
|
|
331
|
+
// Advanced PostgreSQL features work seamlessly
|
|
332
|
+
const analytics = await db.getKysely()
|
|
333
|
+
.selectFrom('orders')
|
|
334
|
+
.select(({ fn }) => [
|
|
335
|
+
fn.count('id').as('total_orders'),
|
|
336
|
+
fn.sum('amount').as('total_revenue'),
|
|
337
|
+
fn.avg('amount').as('avg_order_value'),
|
|
338
|
+
])
|
|
339
|
+
.where('created_at', '>=', new Date('2024-01-01'))
|
|
340
|
+
.groupBy('user_id')
|
|
341
|
+
.execute()
|
|
342
|
+
```
|
|
343
|
+
|
|
250
344
|
## 🔧 Configuration (Optional, Because We're Not Dictators)
|
|
251
345
|
|
|
252
346
|
For most people, the default settings work perfectly. But if you want to customize (because you're a special snowflake):
|
|
253
347
|
|
|
348
|
+
### SQLite Configuration
|
|
254
349
|
```typescript
|
|
255
350
|
const db = new NOORMME({
|
|
256
351
|
dialect: 'sqlite',
|
|
@@ -269,6 +364,183 @@ const db = new NOORMME({
|
|
|
269
364
|
})
|
|
270
365
|
```
|
|
271
366
|
|
|
367
|
+
### PostgreSQL Configuration
|
|
368
|
+
```typescript
|
|
369
|
+
const db = new NOORMME({
|
|
370
|
+
dialect: 'postgresql',
|
|
371
|
+
connection: {
|
|
372
|
+
host: 'localhost',
|
|
373
|
+
port: 5432,
|
|
374
|
+
database: 'myapp',
|
|
375
|
+
username: 'postgres',
|
|
376
|
+
password: 'secret',
|
|
377
|
+
ssl: true, // Enable SSL
|
|
378
|
+
pool: {
|
|
379
|
+
max: 20, // Maximum pool size
|
|
380
|
+
min: 5, // Minimum pool size
|
|
381
|
+
idleTimeoutMillis: 30000, // Close idle connections after 30s
|
|
382
|
+
connectionTimeoutMillis: 2000, // Timeout for acquiring connection
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
automation: {
|
|
386
|
+
enableAutoOptimization: true,
|
|
387
|
+
enableIndexRecommendations: true,
|
|
388
|
+
enableQueryAnalysis: true,
|
|
389
|
+
},
|
|
390
|
+
performance: {
|
|
391
|
+
enableCaching: true,
|
|
392
|
+
maxCacheSize: 1000
|
|
393
|
+
}
|
|
394
|
+
})
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### PostgreSQL-Specific Features (The Power Tools)
|
|
398
|
+
|
|
399
|
+
NOORMME provides comprehensive support for PostgreSQL-specific features:
|
|
400
|
+
|
|
401
|
+
#### Array Types
|
|
402
|
+
```typescript
|
|
403
|
+
import { PostgresArrayHelpers } from 'noormme/helpers/postgresql'
|
|
404
|
+
|
|
405
|
+
// Create table with array columns
|
|
406
|
+
await db.kysely.schema
|
|
407
|
+
.createTable('posts')
|
|
408
|
+
.addColumn('tags', 'text[]')
|
|
409
|
+
.addColumn('scores', 'integer[]')
|
|
410
|
+
.execute()
|
|
411
|
+
|
|
412
|
+
// Query with array operations
|
|
413
|
+
const posts = await db.kysely
|
|
414
|
+
.selectFrom('posts')
|
|
415
|
+
.where(PostgresArrayHelpers.contains('tags', ['typescript']))
|
|
416
|
+
.execute()
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### JSON/JSONB Operations
|
|
420
|
+
```typescript
|
|
421
|
+
import { PostgresJSONHelpers } from 'noormme/helpers/postgresql'
|
|
422
|
+
|
|
423
|
+
// Extract and query JSON fields
|
|
424
|
+
const users = await db.kysely
|
|
425
|
+
.selectFrom('users')
|
|
426
|
+
.select(PostgresJSONHelpers.extract('metadata', 'email').as('email'))
|
|
427
|
+
.where(PostgresJSONHelpers.hasKey('metadata', 'phone'))
|
|
428
|
+
.execute()
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### Full-Text Search
|
|
432
|
+
```typescript
|
|
433
|
+
import { PostgresFullTextHelpers } from 'noormme/helpers/postgresql'
|
|
434
|
+
|
|
435
|
+
// Add full-text search
|
|
436
|
+
await PostgresFullTextHelpers.addGeneratedTSVectorColumn(
|
|
437
|
+
db.kysely, 'articles', 'search_vector', ['title', 'content']
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
// Search with ranking
|
|
441
|
+
const results = await db.kysely
|
|
442
|
+
.selectFrom('articles')
|
|
443
|
+
.select(PostgresFullTextHelpers.rank('search_vector', 'typescript').as('rank'))
|
|
444
|
+
.where(PostgresFullTextHelpers.match('search_vector', 'typescript'))
|
|
445
|
+
.orderBy('rank', 'desc')
|
|
446
|
+
.execute()
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### Materialized Views
|
|
450
|
+
```typescript
|
|
451
|
+
import { PostgresMaterializedViewHelpers } from 'noormme/helpers/postgresql'
|
|
452
|
+
|
|
453
|
+
// Create and manage materialized views
|
|
454
|
+
await PostgresMaterializedViewHelpers.create(
|
|
455
|
+
db.kysely, 'user_stats',
|
|
456
|
+
sql`SELECT user_id, COUNT(*) as post_count FROM posts GROUP BY user_id`
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
await PostgresMaterializedViewHelpers.refresh(db.kysely, 'user_stats', {
|
|
460
|
+
concurrently: true
|
|
461
|
+
})
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**📚 Learn More:**
|
|
465
|
+
- [PostgreSQL Features Documentation](docs/postgresql-features.md) for detailed feature guides
|
|
466
|
+
- [PostgreSQL Support Documentation](docs/postgresql/POSTGRESQL_SUPPORT.md) for comprehensive overview
|
|
467
|
+
|
|
468
|
+
### Database Migration Tools (The "Time to Move" Feature)
|
|
469
|
+
|
|
470
|
+
NOORMME provides powerful tools for migrating databases between SQLite and PostgreSQL:
|
|
471
|
+
|
|
472
|
+
#### Automated Migration
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { createMigrationManager } from 'noormme/helpers/postgresql'
|
|
476
|
+
|
|
477
|
+
// Source database (SQLite)
|
|
478
|
+
const sourceDb = new NOORMME('sqlite:./source.sqlite')
|
|
479
|
+
|
|
480
|
+
// Target database (PostgreSQL)
|
|
481
|
+
const targetDb = new NOORMME('postgresql://user:pass@localhost/target')
|
|
482
|
+
|
|
483
|
+
await sourceDb.initialize()
|
|
484
|
+
await targetDb.initialize()
|
|
485
|
+
|
|
486
|
+
// Create migration manager
|
|
487
|
+
const migrationManager = createMigrationManager(
|
|
488
|
+
sourceDb.getKysely(),
|
|
489
|
+
targetDb.getKysely(),
|
|
490
|
+
{
|
|
491
|
+
source: {
|
|
492
|
+
dialect: 'sqlite',
|
|
493
|
+
database: './source.sqlite',
|
|
494
|
+
},
|
|
495
|
+
target: {
|
|
496
|
+
dialect: 'postgresql',
|
|
497
|
+
database: 'target',
|
|
498
|
+
host: 'localhost',
|
|
499
|
+
port: 5432,
|
|
500
|
+
username: 'user',
|
|
501
|
+
password: 'pass',
|
|
502
|
+
},
|
|
503
|
+
options: {
|
|
504
|
+
batchSize: 1000,
|
|
505
|
+
verbose: true,
|
|
506
|
+
},
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
// Perform complete migration (schema + data)
|
|
511
|
+
const result = await migrationManager.migrate()
|
|
512
|
+
console.log(`Migrated ${result.rowsMigrated} rows in ${(result.duration / 1000).toFixed(2)}s`)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### Schema Comparison & Sync
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// Compare schemas
|
|
519
|
+
const comparison = await migrationManager.compareSchemas()
|
|
520
|
+
console.log(`Differences: ${comparison.differences.length}`)
|
|
521
|
+
|
|
522
|
+
// Generate sync SQL
|
|
523
|
+
const syncResult = await migrationManager.syncSchema({
|
|
524
|
+
generateSQL: true,
|
|
525
|
+
apply: false, // Preview changes without applying
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
syncResult.sqlStatements.forEach(sql => console.log(sql))
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### Key Migration Features
|
|
532
|
+
|
|
533
|
+
- ✅ **Bidirectional Migration** - SQLite ↔ PostgreSQL
|
|
534
|
+
- ✅ **Automatic Type Conversion** - Smart mapping between database types
|
|
535
|
+
- ✅ **Value Transformation** - Handle booleans, arrays, JSON, and dates
|
|
536
|
+
- ✅ **Batch Processing** - Efficient large dataset migration
|
|
537
|
+
- ✅ **Parallel Migration** - Multi-table concurrent processing
|
|
538
|
+
- ✅ **Progress Tracking** - Real-time migration progress
|
|
539
|
+
- ✅ **Schema Diff** - Detect schema differences
|
|
540
|
+
- ✅ **Verification** - Automated post-migration checks
|
|
541
|
+
|
|
542
|
+
**📚 Learn More:** See [Migration Tools Documentation](docs/migration-tools.md) for detailed guides and examples.
|
|
543
|
+
|
|
272
544
|
## 🚀 Production Ready (Because We're Not Playing Around)
|
|
273
545
|
|
|
274
546
|
NOORMME is already running in production applications with real-world success stories. It includes:
|
|
@@ -294,10 +566,15 @@ The DreamBeesArt application successfully migrated from Drizzle ORM to NOORMME w
|
|
|
294
566
|
A: Nope! NOORMME handles most things automatically. You only need SQL for complex queries (and even then, we'll help you).
|
|
295
567
|
|
|
296
568
|
**Q: Can I use my existing database?**
|
|
297
|
-
A: Yes! Just point NOORMME at your existing SQLite
|
|
569
|
+
A: Yes! Just point NOORMME at your existing SQLite or PostgreSQL database and it figures everything out (because we're not picky).
|
|
570
|
+
|
|
571
|
+
**Q: Should I use SQLite or PostgreSQL?**
|
|
572
|
+
A: Start with SQLite for prototyping and small apps (it's faster to set up). Switch to PostgreSQL when you need multi-user support, complex queries, or scaling. The API is identical, so switching is easy!
|
|
298
573
|
|
|
299
574
|
**Q: Is this really production-ready?**
|
|
300
|
-
A: Absolutely. Real applications are using it right now
|
|
575
|
+
A: Absolutely. Real applications are using it right now:
|
|
576
|
+
- SQLite with WAL mode for edge deployments and local-first apps
|
|
577
|
+
- PostgreSQL for traditional production deployments with millions of records
|
|
301
578
|
|
|
302
579
|
**Q: What if I'm already using another ORM?**
|
|
303
580
|
A: NOORMME works alongside other tools. You can migrate gradually or use both (because we're not territorial).
|
|
@@ -309,7 +586,10 @@ A: NOORMME works with JavaScript too, but TypeScript gives you the best experien
|
|
|
309
586
|
A: NOORMME includes Cursor IDE context rules that make AI assistance actually useful. The AI understands your project structure and generates code that follows your conventions (because we're not savages).
|
|
310
587
|
|
|
311
588
|
**Q: What makes this different from other toolkits?**
|
|
312
|
-
A: NOORMME combines database automation, organized architecture, and AI-ready patterns in one integrated toolkit. It's not just an ORM - it's a complete development environment (because we're ambitious).
|
|
589
|
+
A: NOORMME combines database automation, organized architecture, and AI-ready patterns in one integrated toolkit. It's not just an ORM - it's a complete development environment that supports both SQLite and PostgreSQL with the same API (because we're ambitious).
|
|
590
|
+
|
|
591
|
+
**Q: Can I migrate from SQLite to PostgreSQL later?**
|
|
592
|
+
A: Yes! Since NOORMME uses the same API for both databases, migrating is as simple as changing the connection config. Your application code stays the same!
|
|
313
593
|
|
|
314
594
|
**Q: Will this make me a better developer?**
|
|
315
595
|
A: Probably not, but it will make you feel like one (which is half the battle).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Kysely } from '../../kysely.js';
|
|
2
|
+
import { DialectAdapterBase } from '../dialect-adapter-base.js';
|
|
3
|
+
import { MigrationLockOptions } from '../dialect-adapter.js';
|
|
4
|
+
export declare class PostgresAdapter extends DialectAdapterBase {
|
|
5
|
+
#private;
|
|
6
|
+
/**
|
|
7
|
+
* Whether or not this dialect supports transactional DDL.
|
|
8
|
+
*
|
|
9
|
+
* If this is true, migrations are executed inside a transaction.
|
|
10
|
+
*/
|
|
11
|
+
get supportsTransactionalDdl(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether or not this dialect supports the `returning` in inserts
|
|
14
|
+
* updates and deletes.
|
|
15
|
+
*/
|
|
16
|
+
get supportsReturning(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* This method is used to acquire a lock for the migrations so that
|
|
19
|
+
* it's not possible for two migration operations to run in parallel.
|
|
20
|
+
*
|
|
21
|
+
* Most dialects have explicit locks that can be used, like advisory locks
|
|
22
|
+
* in PostgreSQL and the get_lock function in MySQL.
|
|
23
|
+
*
|
|
24
|
+
* If the dialect doesn't have explicit locks the {@link MigrationLockOptions.lockTable}
|
|
25
|
+
* created by Kysely can be used instead. You can access it through the `options` object.
|
|
26
|
+
* The lock table has two columns `id` and `is_locked` and there's only one row in the table
|
|
27
|
+
* whose id is {@link MigrationLockOptions.lockRowId}. `is_locked` is an integer. Kysely
|
|
28
|
+
* takes care of creating the lock table and inserting the one single row to it before this
|
|
29
|
+
* method is executed. If the dialect supports schemas and the user has specified a custom
|
|
30
|
+
* schema in their migration settings, the options object also contains the schema name in
|
|
31
|
+
* {@link MigrationLockOptions.lockTableSchema}.
|
|
32
|
+
*
|
|
33
|
+
* Here's an example of how you might implement this method for a dialect that doesn't
|
|
34
|
+
* have explicit locks but supports `FOR UPDATE` row locks and transactional DDL:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { DialectAdapterBase, type MigrationLockOptions, Kysely } from 'kysely'
|
|
38
|
+
*
|
|
39
|
+
* export class MyAdapter extends DialectAdapterBase {
|
|
40
|
+
* override async acquireMigrationLock(
|
|
41
|
+
* db: Kysely<any>,
|
|
42
|
+
* options: MigrationLockOptions
|
|
43
|
+
* ): Promise<void> {
|
|
44
|
+
* const queryDb = options.lockTableSchema
|
|
45
|
+
* ? db.withSchema(options.lockTableSchema)
|
|
46
|
+
* : db
|
|
47
|
+
*
|
|
48
|
+
* // Since our imaginary dialect supports transactional DDL and has
|
|
49
|
+
* // row locks, we can simply take a row lock here and it will guarantee
|
|
50
|
+
* // all subsequent calls to this method from other transactions will
|
|
51
|
+
* // wait until this transaction finishes.
|
|
52
|
+
* await queryDb
|
|
53
|
+
* .selectFrom(options.lockTable)
|
|
54
|
+
* .selectAll()
|
|
55
|
+
* .where('id', '=', options.lockRowId)
|
|
56
|
+
* .forUpdate()
|
|
57
|
+
* .execute()
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* override async releaseMigrationLock() {
|
|
61
|
+
* // noop
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* If `supportsTransactionalDdl` is `true` then the `db` passed to this method
|
|
67
|
+
* is a transaction inside which the migrations will be executed. Otherwise
|
|
68
|
+
* `db` is a single connection (session) that will be used to execute the
|
|
69
|
+
* migrations.
|
|
70
|
+
*/
|
|
71
|
+
acquireMigrationLock(db: Kysely<any>, opt: MigrationLockOptions): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Releases the migration lock. See {@link acquireMigrationLock}.
|
|
74
|
+
*
|
|
75
|
+
* If `supportsTransactionalDdl` is `true` then the `db` passed to this method
|
|
76
|
+
* is a transaction inside which the migrations were executed. Otherwise `db`
|
|
77
|
+
* is a single connection (session) that was used to execute the migrations
|
|
78
|
+
* and the `acquireMigrationLock` call.
|
|
79
|
+
*/
|
|
80
|
+
releaseMigrationLock(_db: Kysely<any>, _opt: MigrationLockOptions): Promise<void>;
|
|
81
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresAdapter = void 0;
|
|
4
|
+
const dialect_adapter_base_js_1 = require("../dialect-adapter-base.js");
|
|
5
|
+
// Random id for our migration lock
|
|
6
|
+
const MIGRATION_LOCK_ID = '0.0.1';
|
|
7
|
+
class PostgresAdapter extends dialect_adapter_base_js_1.DialectAdapterBase {
|
|
8
|
+
get supportsTransactionalDdl() {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
get supportsReturning() {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
async acquireMigrationLock(db, opt) {
|
|
15
|
+
const lockTable = opt.lockTable ?? 'kysely_migration_lock';
|
|
16
|
+
// Postgres uses advisory locks. We need to create a unique lock id
|
|
17
|
+
// from the lock table name. We use a simple hash function for that.
|
|
18
|
+
const lockId = this.#hashString(lockTable);
|
|
19
|
+
await db
|
|
20
|
+
.selectFrom(lockTable)
|
|
21
|
+
.selectAll()
|
|
22
|
+
.where('id', '=', MIGRATION_LOCK_ID)
|
|
23
|
+
.forUpdate()
|
|
24
|
+
.execute();
|
|
25
|
+
}
|
|
26
|
+
async releaseMigrationLock(_db, _opt) {
|
|
27
|
+
// Nothing to do here. The migration lock is automatically released
|
|
28
|
+
// when the transaction is committed/rolled back.
|
|
29
|
+
}
|
|
30
|
+
#hashString(str) {
|
|
31
|
+
let hash = 0;
|
|
32
|
+
for (let i = 0; i < str.length; i++) {
|
|
33
|
+
const chr = str.charCodeAt(i);
|
|
34
|
+
hash = (hash << 5) - hash + chr;
|
|
35
|
+
hash |= 0; // Convert to 32bit integer
|
|
36
|
+
}
|
|
37
|
+
return hash;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.PostgresAdapter = PostgresAdapter;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { DatabaseConnection } from '../../driver/database-connection.js';
|
|
2
|
+
import type { PoolConfig } from 'pg';
|
|
3
|
+
/**
|
|
4
|
+
* Config for the PostgreSQL dialect.
|
|
5
|
+
*/
|
|
6
|
+
export interface PostgresDialectConfig {
|
|
7
|
+
/**
|
|
8
|
+
* A postgres Pool instance or a function that returns one.
|
|
9
|
+
*
|
|
10
|
+
* If a function is provided, it's called once when the first query is executed.
|
|
11
|
+
*
|
|
12
|
+
* https://node-postgres.com/apis/pool
|
|
13
|
+
*/
|
|
14
|
+
pool?: PostgresPool | (() => Promise<PostgresPool>);
|
|
15
|
+
/**
|
|
16
|
+
* Pool configuration for PostgreSQL
|
|
17
|
+
*
|
|
18
|
+
* https://node-postgres.com/apis/pool
|
|
19
|
+
*/
|
|
20
|
+
poolConfig?: PoolConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Called once for each created connection.
|
|
23
|
+
*
|
|
24
|
+
* This is a NOORMME specific feature and does not come from the `pg` module.
|
|
25
|
+
*/
|
|
26
|
+
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Cursor constructor for streaming queries.
|
|
29
|
+
*/
|
|
30
|
+
cursor?: PostgresCursorConstructor;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* This interface is the subset of pg Pool that NOORMME needs.
|
|
34
|
+
*
|
|
35
|
+
* We don't use the type from `pg` here to not have a dependency to it.
|
|
36
|
+
*
|
|
37
|
+
* https://node-postgres.com/apis/pool
|
|
38
|
+
*/
|
|
39
|
+
export interface PostgresPool {
|
|
40
|
+
connect(): Promise<PostgresPoolClient>;
|
|
41
|
+
end(): Promise<void>;
|
|
42
|
+
query<R = any>(sql: string, parameters?: ReadonlyArray<unknown>): Promise<PostgresQueryResult<R>>;
|
|
43
|
+
}
|
|
44
|
+
export interface PostgresPoolClient {
|
|
45
|
+
query<R = any>(sql: string, parameters?: ReadonlyArray<unknown>): Promise<PostgresQueryResult<R>>;
|
|
46
|
+
release(): void;
|
|
47
|
+
}
|
|
48
|
+
export interface PostgresQueryResult<R = any> {
|
|
49
|
+
command: string;
|
|
50
|
+
rowCount: number | null;
|
|
51
|
+
rows: R[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* https://github.com/brianc/node-pg-cursor
|
|
55
|
+
*/
|
|
56
|
+
export interface PostgresCursorConstructor {
|
|
57
|
+
new (text: string, values: unknown[], config?: unknown): PostgresCursor;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* https://github.com/brianc/node-pg-cursor
|
|
61
|
+
*/
|
|
62
|
+
export interface PostgresCursor extends AsyncIterableIterator<any> {
|
|
63
|
+
read(rowCount: number): Promise<any[]>;
|
|
64
|
+
close(): Promise<void>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Driver } from '../../driver/driver.js';
|
|
2
|
+
import { Kysely } from '../../kysely.js';
|
|
3
|
+
import { QueryCompiler } from '../../query-compiler/query-compiler.js';
|
|
4
|
+
import { Dialect } from '../dialect.js';
|
|
5
|
+
import { DatabaseIntrospector } from '../database-introspector.js';
|
|
6
|
+
import { DialectAdapter } from '../dialect-adapter.js';
|
|
7
|
+
import { PostgresDialectConfig } from './postgresql-dialect-config.js';
|
|
8
|
+
/**
|
|
9
|
+
* PostgreSQL dialect that uses the [pg](https://github.com/brianc/node-postgres) library.
|
|
10
|
+
*
|
|
11
|
+
* The constructor takes an instance of {@link PostgresDialectConfig}.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { Pool } from 'pg'
|
|
15
|
+
*
|
|
16
|
+
* new PostgresDialect({
|
|
17
|
+
* pool: new Pool({
|
|
18
|
+
* host: 'localhost',
|
|
19
|
+
* database: 'my_database',
|
|
20
|
+
* user: 'postgres',
|
|
21
|
+
* password: 'postgres',
|
|
22
|
+
* port: 5432,
|
|
23
|
+
* max: 10,
|
|
24
|
+
* })
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Alternatively, you can use poolConfig:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* new PostgresDialect({
|
|
32
|
+
* poolConfig: {
|
|
33
|
+
* host: 'localhost',
|
|
34
|
+
* database: 'my_database',
|
|
35
|
+
* user: 'postgres',
|
|
36
|
+
* password: 'postgres',
|
|
37
|
+
* port: 5432,
|
|
38
|
+
* max: 10,
|
|
39
|
+
* }
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* If you want the pool to only be created once it's first used, `pool`
|
|
44
|
+
* can be a function:
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { Pool } from 'pg'
|
|
48
|
+
*
|
|
49
|
+
* new PostgresDialect({
|
|
50
|
+
* pool: async () => new Pool({
|
|
51
|
+
* host: 'localhost',
|
|
52
|
+
* database: 'my_database',
|
|
53
|
+
* })
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare class PostgresDialect implements Dialect {
|
|
58
|
+
#private;
|
|
59
|
+
constructor(config: PostgresDialectConfig);
|
|
60
|
+
/**
|
|
61
|
+
* Creates a driver for the dialect.
|
|
62
|
+
*/
|
|
63
|
+
createDriver(): Driver;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a query compiler for the dialect.
|
|
66
|
+
*/
|
|
67
|
+
createQueryCompiler(): QueryCompiler;
|
|
68
|
+
/**
|
|
69
|
+
* Creates an adapter for the dialect.
|
|
70
|
+
*/
|
|
71
|
+
createAdapter(): DialectAdapter;
|
|
72
|
+
/**
|
|
73
|
+
* Creates a database introspector that can be used to get database metadata
|
|
74
|
+
* such as the table names and column names of those tables.
|
|
75
|
+
*
|
|
76
|
+
* `db` never has any plugins installed. It's created using
|
|
77
|
+
* {@link Kysely.withoutPlugins}.
|
|
78
|
+
*/
|
|
79
|
+
createIntrospector(db: Kysely<any>): DatabaseIntrospector;
|
|
80
|
+
}
|