@onebun/drizzle 0.1.11 → 0.2.0
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/package.json +5 -5
- package/src/drizzle.service.ts +134 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Drizzle ORM module for OneBun framework - SQLite and PostgreSQL support",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "RemRyahirev",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"dev": "bun run --watch src/index.ts"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@onebun/core": "^0.
|
|
51
|
-
"@onebun/envs": "^0.
|
|
52
|
-
"@onebun/logger": "^0.
|
|
50
|
+
"@onebun/core": "^0.2.0",
|
|
51
|
+
"@onebun/envs": "^0.2.0",
|
|
52
|
+
"@onebun/logger": "^0.2.0",
|
|
53
53
|
"drizzle-orm": "^0.44.7",
|
|
54
54
|
"drizzle-kit": "^0.31.6",
|
|
55
55
|
"drizzle-arktype": "^0.1.3",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"effect": "^3.13.10"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"bun-types": "^1.
|
|
60
|
+
"bun-types": "^1.3.8",
|
|
61
61
|
"testcontainers": "^11.7.1"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
package/src/drizzle.service.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
1
5
|
import { SQL } from 'bun';
|
|
2
6
|
import { Database } from 'bun:sqlite';
|
|
3
7
|
import { drizzle as drizzlePostgres } from 'drizzle-orm/bun-sql';
|
|
@@ -22,7 +26,11 @@ import type {
|
|
|
22
26
|
SQLiteUpdateBuilder,
|
|
23
27
|
} from 'drizzle-orm/sqlite-core';
|
|
24
28
|
|
|
25
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
BaseService,
|
|
31
|
+
Service,
|
|
32
|
+
type OnModuleInit,
|
|
33
|
+
} from '@onebun/core';
|
|
26
34
|
import {
|
|
27
35
|
Env,
|
|
28
36
|
EnvLoader,
|
|
@@ -211,7 +219,7 @@ interface BufferedLogEntry {
|
|
|
211
219
|
* ```
|
|
212
220
|
*/
|
|
213
221
|
@Service()
|
|
214
|
-
export class DrizzleService extends BaseService {
|
|
222
|
+
export class DrizzleService extends BaseService implements OnModuleInit {
|
|
215
223
|
private db: DatabaseInstance | null = null;
|
|
216
224
|
private dbType: DatabaseTypeLiteral | null = null;
|
|
217
225
|
private connectionOptions: DatabaseConnectionOptions | null = null;
|
|
@@ -298,10 +306,10 @@ export class DrizzleService extends BaseService {
|
|
|
298
306
|
}
|
|
299
307
|
|
|
300
308
|
/**
|
|
301
|
-
*
|
|
309
|
+
* Module initialization hook - called by the framework after initializeService()
|
|
302
310
|
* This ensures the database is fully ready before client code runs
|
|
303
311
|
*/
|
|
304
|
-
|
|
312
|
+
async onModuleInit(): Promise<void> {
|
|
305
313
|
// Flush any buffered logs now that logger is available
|
|
306
314
|
this.flushLogBuffer();
|
|
307
315
|
|
|
@@ -553,6 +561,92 @@ export class DrizzleService extends BaseService {
|
|
|
553
561
|
return this.connectionOptions;
|
|
554
562
|
}
|
|
555
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Read migration journal and compute hashes for each migration file
|
|
566
|
+
* Returns a map of hash -> migration filename
|
|
567
|
+
*/
|
|
568
|
+
private readMigrationJournal(migrationsFolder: string): Map<string, string> {
|
|
569
|
+
const hashToFilename = new Map<string, string>();
|
|
570
|
+
const journalPath = path.join(migrationsFolder, 'meta', '_journal.json');
|
|
571
|
+
|
|
572
|
+
if (!fs.existsSync(journalPath)) {
|
|
573
|
+
// No journal file - return empty map
|
|
574
|
+
return hashToFilename;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
const journalContent = fs.readFileSync(journalPath, 'utf-8');
|
|
579
|
+
const journal = JSON.parse(journalContent) as {
|
|
580
|
+
entries: Array<{ idx: number; when: number; tag: string; breakpoints: boolean }>;
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
for (const entry of journal.entries) {
|
|
584
|
+
const migrationPath = path.join(migrationsFolder, `${entry.tag}.sql`);
|
|
585
|
+
|
|
586
|
+
if (fs.existsSync(migrationPath)) {
|
|
587
|
+
const sqlContent = fs.readFileSync(migrationPath, 'utf-8');
|
|
588
|
+
const hash = crypto.createHash('sha256').update(sqlContent).digest('hex');
|
|
589
|
+
hashToFilename.set(hash, entry.tag);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
// If journal parsing fails, return empty map
|
|
594
|
+
this.safeLog('warn', 'Failed to read migration journal', { journalPath });
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return hashToFilename;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Get set of applied migration hashes from __drizzle_migrations table
|
|
602
|
+
*/
|
|
603
|
+
private getAppliedMigrationHashes(): Set<string> {
|
|
604
|
+
const hashes = new Set<string>();
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
if (this.connectionOptions?.type === DatabaseType.SQLITE && this.sqliteClient) {
|
|
608
|
+
// Check if table exists
|
|
609
|
+
const tableExists = this.sqliteClient.query(`
|
|
610
|
+
SELECT name FROM sqlite_master
|
|
611
|
+
WHERE type='table' AND name='__drizzle_migrations'
|
|
612
|
+
`).all();
|
|
613
|
+
|
|
614
|
+
if (tableExists.length > 0) {
|
|
615
|
+
const migrations = this.sqliteClient.query(`
|
|
616
|
+
SELECT hash FROM __drizzle_migrations
|
|
617
|
+
`).all() as Array<{ hash: string }>;
|
|
618
|
+
|
|
619
|
+
for (const m of migrations) {
|
|
620
|
+
hashes.add(m.hash);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} else if (this.connectionOptions?.type === DatabaseType.POSTGRESQL && this.postgresClient) {
|
|
624
|
+
// Check if table exists using Bun.SQL template literal syntax
|
|
625
|
+
// Cast through unknown as Bun.SQL types don't fully reflect runtime behavior
|
|
626
|
+
const tableExistsResult = this.postgresClient`
|
|
627
|
+
SELECT EXISTS (
|
|
628
|
+
SELECT FROM information_schema.tables
|
|
629
|
+
WHERE table_name = '__drizzle_migrations'
|
|
630
|
+
) as exists
|
|
631
|
+
` as unknown as Array<{ exists: boolean }>;
|
|
632
|
+
|
|
633
|
+
if (tableExistsResult.length > 0 && tableExistsResult[0]?.exists) {
|
|
634
|
+
const migrationsResult = this.postgresClient`
|
|
635
|
+
SELECT hash FROM __drizzle_migrations
|
|
636
|
+
` as unknown as Array<{ hash: string }>;
|
|
637
|
+
|
|
638
|
+
for (const m of migrationsResult) {
|
|
639
|
+
hashes.add(m.hash);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} catch {
|
|
644
|
+
// If query fails, return empty set (table might not exist yet)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return hashes;
|
|
648
|
+
}
|
|
649
|
+
|
|
556
650
|
/**
|
|
557
651
|
* Run migrations
|
|
558
652
|
*
|
|
@@ -560,6 +654,8 @@ export class DrizzleService extends BaseService {
|
|
|
560
654
|
* and prevents double application of migrations. This method uses drizzle's built-in
|
|
561
655
|
* migration system which ensures idempotency.
|
|
562
656
|
*
|
|
657
|
+
* Logs the names of each migration file that was applied during this run.
|
|
658
|
+
*
|
|
563
659
|
* @param options - Migration options
|
|
564
660
|
* @param skipWait - Internal flag to skip waitForInit (used by autoInitialize to avoid deadlock)
|
|
565
661
|
* @throws Error if database is not initialized
|
|
@@ -576,6 +672,12 @@ export class DrizzleService extends BaseService {
|
|
|
576
672
|
|
|
577
673
|
const migrationsFolder = options?.migrationsFolder ?? './drizzle';
|
|
578
674
|
|
|
675
|
+
// Read migration journal to get hash -> filename mapping
|
|
676
|
+
const hashToFilename = this.readMigrationJournal(migrationsFolder);
|
|
677
|
+
|
|
678
|
+
// Get already applied migrations before running
|
|
679
|
+
const appliedBefore = this.getAppliedMigrationHashes();
|
|
680
|
+
|
|
579
681
|
if (this.connectionOptions.type === DatabaseType.SQLITE) {
|
|
580
682
|
if (!this.db) {
|
|
581
683
|
throw new Error('Database not initialized');
|
|
@@ -583,7 +685,6 @@ export class DrizzleService extends BaseService {
|
|
|
583
685
|
await migrate(this.db as BunSQLiteDatabase<Record<string, SQLiteTable>>, {
|
|
584
686
|
migrationsFolder,
|
|
585
687
|
});
|
|
586
|
-
this.safeLog('info', 'SQLite migrations applied', { migrationsFolder });
|
|
587
688
|
} else if (this.connectionOptions.type === DatabaseType.POSTGRESQL) {
|
|
588
689
|
if (!this.db) {
|
|
589
690
|
throw new Error('Database not initialized');
|
|
@@ -591,8 +692,35 @@ export class DrizzleService extends BaseService {
|
|
|
591
692
|
await migratePostgres(this.db as BunSQLDatabase<Record<string, PgTable>>, {
|
|
592
693
|
migrationsFolder,
|
|
593
694
|
});
|
|
594
|
-
this.safeLog('info', 'PostgreSQL migrations applied', { migrationsFolder });
|
|
595
695
|
}
|
|
696
|
+
|
|
697
|
+
// Get applied migrations after running
|
|
698
|
+
const appliedAfter = this.getAppliedMigrationHashes();
|
|
699
|
+
|
|
700
|
+
// Find newly applied migrations
|
|
701
|
+
const newlyApplied: string[] = [];
|
|
702
|
+
for (const hash of appliedAfter) {
|
|
703
|
+
if (!appliedBefore.has(hash)) {
|
|
704
|
+
const filename = hashToFilename.get(hash);
|
|
705
|
+
if (filename) {
|
|
706
|
+
newlyApplied.push(filename);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Log each applied migration
|
|
712
|
+
for (const filename of newlyApplied) {
|
|
713
|
+
this.safeLog('info', `Applied migration: ${filename}`);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Log summary
|
|
717
|
+
const dbTypeName = this.connectionOptions.type === DatabaseType.SQLITE ? 'SQLite' : 'PostgreSQL';
|
|
718
|
+
|
|
719
|
+
this.safeLog('info', `${dbTypeName} migrations applied`, {
|
|
720
|
+
migrationsFolder,
|
|
721
|
+
newMigrations: newlyApplied.length,
|
|
722
|
+
appliedFiles: newlyApplied,
|
|
723
|
+
});
|
|
596
724
|
}
|
|
597
725
|
|
|
598
726
|
/**
|