@onebun/drizzle 0.1.10 → 0.1.12
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 +3 -3
- package/src/builders/transaction-client.ts +22 -29
- package/src/drizzle.service.ts +159 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/drizzle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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.1.
|
|
50
|
+
"@onebun/core": "^0.1.19",
|
|
51
51
|
"@onebun/envs": "^0.1.4",
|
|
52
|
-
"@onebun/logger": "^0.1.
|
|
52
|
+
"@onebun/logger": "^0.1.6",
|
|
53
53
|
"drizzle-orm": "^0.44.7",
|
|
54
54
|
"drizzle-kit": "^0.31.6",
|
|
55
55
|
"drizzle-arktype": "^0.1.3",
|
|
@@ -5,17 +5,22 @@
|
|
|
5
5
|
// Method overloads define return types, so explicit return type on implementation is not needed
|
|
6
6
|
|
|
7
7
|
import type { DatabaseInstance } from '../types';
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import type { BunSQLQueryResultHKT } from 'drizzle-orm/bun-sql/session';
|
|
9
|
+
import type {
|
|
10
|
+
PgDeleteBase,
|
|
11
|
+
PgInsertBuilder,
|
|
12
|
+
PgTable,
|
|
13
|
+
PgUpdateBuilder,
|
|
14
|
+
} from 'drizzle-orm/pg-core';
|
|
15
|
+
import type {
|
|
16
|
+
SQLiteDeleteBase,
|
|
17
|
+
SQLiteInsertBuilder,
|
|
18
|
+
SQLiteTable,
|
|
19
|
+
SQLiteUpdateBuilder,
|
|
20
|
+
} from 'drizzle-orm/sqlite-core';
|
|
12
21
|
|
|
13
22
|
import { UniversalSelectBuilder, UniversalSelectDistinctBuilder } from './select-builder';
|
|
14
23
|
|
|
15
|
-
// Type helpers for insert/update/delete return types
|
|
16
|
-
type SQLiteDb = BunSQLiteDatabase<Record<string, SQLiteTable>>;
|
|
17
|
-
type PgDb = BunSQLDatabase<Record<string, PgTable>>;
|
|
18
|
-
|
|
19
24
|
/**
|
|
20
25
|
* Universal Transaction Client
|
|
21
26
|
*
|
|
@@ -55,54 +60,42 @@ export class UniversalTransactionClient {
|
|
|
55
60
|
/**
|
|
56
61
|
* Create an INSERT query for SQLite table
|
|
57
62
|
*/
|
|
58
|
-
insert<TTable extends SQLiteTable<
|
|
59
|
-
table: TTable,
|
|
60
|
-
): ReturnType<SQLiteDb['insert']>;
|
|
63
|
+
insert<TTable extends SQLiteTable>(table: TTable): SQLiteInsertBuilder<TTable, 'sync', void>;
|
|
61
64
|
|
|
62
65
|
/**
|
|
63
66
|
* Create an INSERT query for PostgreSQL table
|
|
64
67
|
*/
|
|
65
|
-
insert<TTable extends PgTable<
|
|
66
|
-
table: TTable,
|
|
67
|
-
): ReturnType<PgDb['insert']>;
|
|
68
|
+
insert<TTable extends PgTable>(table: TTable): PgInsertBuilder<TTable, BunSQLQueryResultHKT>;
|
|
68
69
|
|
|
69
|
-
insert(table: SQLiteTable
|
|
70
|
+
insert(table: SQLiteTable | PgTable) {
|
|
70
71
|
return (this.tx as any).insert(table);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
75
|
* Create an UPDATE query for SQLite table
|
|
75
76
|
*/
|
|
76
|
-
update<TTable extends SQLiteTable<
|
|
77
|
-
table: TTable,
|
|
78
|
-
): ReturnType<SQLiteDb['update']>;
|
|
77
|
+
update<TTable extends SQLiteTable>(table: TTable): SQLiteUpdateBuilder<TTable, 'sync', void>;
|
|
79
78
|
|
|
80
79
|
/**
|
|
81
80
|
* Create an UPDATE query for PostgreSQL table
|
|
82
81
|
*/
|
|
83
|
-
update<TTable extends PgTable<
|
|
84
|
-
table: TTable,
|
|
85
|
-
): ReturnType<PgDb['update']>;
|
|
82
|
+
update<TTable extends PgTable>(table: TTable): PgUpdateBuilder<TTable, BunSQLQueryResultHKT>;
|
|
86
83
|
|
|
87
|
-
update(table: SQLiteTable
|
|
84
|
+
update(table: SQLiteTable | PgTable) {
|
|
88
85
|
return (this.tx as any).update(table);
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
/**
|
|
92
89
|
* Create a DELETE query for SQLite table
|
|
93
90
|
*/
|
|
94
|
-
delete<TTable extends SQLiteTable<
|
|
95
|
-
table: TTable,
|
|
96
|
-
): ReturnType<SQLiteDb['delete']>;
|
|
91
|
+
delete<TTable extends SQLiteTable>(table: TTable): SQLiteDeleteBase<TTable, 'sync', void>;
|
|
97
92
|
|
|
98
93
|
/**
|
|
99
94
|
* Create a DELETE query for PostgreSQL table
|
|
100
95
|
*/
|
|
101
|
-
delete<TTable extends PgTable<
|
|
102
|
-
table: TTable,
|
|
103
|
-
): ReturnType<PgDb['delete']>;
|
|
96
|
+
delete<TTable extends PgTable>(table: TTable): PgDeleteBase<TTable, BunSQLQueryResultHKT>;
|
|
104
97
|
|
|
105
|
-
delete(table: SQLiteTable
|
|
98
|
+
delete(table: SQLiteTable | PgTable) {
|
|
106
99
|
return (this.tx as any).delete(table);
|
|
107
100
|
}
|
|
108
101
|
|
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';
|
|
@@ -7,11 +11,26 @@ import { migrate } from 'drizzle-orm/bun-sqlite/migrator';
|
|
|
7
11
|
import { Effect } from 'effect';
|
|
8
12
|
|
|
9
13
|
import type { BunSQLDatabase } from 'drizzle-orm/bun-sql';
|
|
14
|
+
import type { BunSQLQueryResultHKT } from 'drizzle-orm/bun-sql/session';
|
|
10
15
|
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
|
11
|
-
import type {
|
|
12
|
-
|
|
16
|
+
import type {
|
|
17
|
+
PgDeleteBase,
|
|
18
|
+
PgInsertBuilder,
|
|
19
|
+
PgTable,
|
|
20
|
+
PgUpdateBuilder,
|
|
21
|
+
} from 'drizzle-orm/pg-core';
|
|
22
|
+
import type {
|
|
23
|
+
SQLiteDeleteBase,
|
|
24
|
+
SQLiteInsertBuilder,
|
|
25
|
+
SQLiteTable,
|
|
26
|
+
SQLiteUpdateBuilder,
|
|
27
|
+
} from 'drizzle-orm/sqlite-core';
|
|
13
28
|
|
|
14
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
BaseService,
|
|
31
|
+
Service,
|
|
32
|
+
type OnModuleInit,
|
|
33
|
+
} from '@onebun/core';
|
|
15
34
|
import {
|
|
16
35
|
Env,
|
|
17
36
|
EnvLoader,
|
|
@@ -33,10 +52,6 @@ import {
|
|
|
33
52
|
type PostgreSQLConnectionOptions,
|
|
34
53
|
} from './types';
|
|
35
54
|
|
|
36
|
-
// Type helpers for insert/update/delete return types
|
|
37
|
-
type SQLiteDb = BunSQLiteDatabase<Record<string, SQLiteTable>>;
|
|
38
|
-
type PgDb = BunSQLDatabase<Record<string, PgTable>>;
|
|
39
|
-
|
|
40
55
|
/**
|
|
41
56
|
* Default environment variable prefix
|
|
42
57
|
*/
|
|
@@ -204,7 +219,7 @@ interface BufferedLogEntry {
|
|
|
204
219
|
* ```
|
|
205
220
|
*/
|
|
206
221
|
@Service()
|
|
207
|
-
export class DrizzleService extends BaseService {
|
|
222
|
+
export class DrizzleService extends BaseService implements OnModuleInit {
|
|
208
223
|
private db: DatabaseInstance | null = null;
|
|
209
224
|
private dbType: DatabaseTypeLiteral | null = null;
|
|
210
225
|
private connectionOptions: DatabaseConnectionOptions | null = null;
|
|
@@ -291,10 +306,10 @@ export class DrizzleService extends BaseService {
|
|
|
291
306
|
}
|
|
292
307
|
|
|
293
308
|
/**
|
|
294
|
-
*
|
|
309
|
+
* Module initialization hook - called by the framework after initializeService()
|
|
295
310
|
* This ensures the database is fully ready before client code runs
|
|
296
311
|
*/
|
|
297
|
-
|
|
312
|
+
async onModuleInit(): Promise<void> {
|
|
298
313
|
// Flush any buffered logs now that logger is available
|
|
299
314
|
this.flushLogBuffer();
|
|
300
315
|
|
|
@@ -546,6 +561,92 @@ export class DrizzleService extends BaseService {
|
|
|
546
561
|
return this.connectionOptions;
|
|
547
562
|
}
|
|
548
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
|
+
|
|
549
650
|
/**
|
|
550
651
|
* Run migrations
|
|
551
652
|
*
|
|
@@ -553,6 +654,8 @@ export class DrizzleService extends BaseService {
|
|
|
553
654
|
* and prevents double application of migrations. This method uses drizzle's built-in
|
|
554
655
|
* migration system which ensures idempotency.
|
|
555
656
|
*
|
|
657
|
+
* Logs the names of each migration file that was applied during this run.
|
|
658
|
+
*
|
|
556
659
|
* @param options - Migration options
|
|
557
660
|
* @param skipWait - Internal flag to skip waitForInit (used by autoInitialize to avoid deadlock)
|
|
558
661
|
* @throws Error if database is not initialized
|
|
@@ -569,6 +672,12 @@ export class DrizzleService extends BaseService {
|
|
|
569
672
|
|
|
570
673
|
const migrationsFolder = options?.migrationsFolder ?? './drizzle';
|
|
571
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
|
+
|
|
572
681
|
if (this.connectionOptions.type === DatabaseType.SQLITE) {
|
|
573
682
|
if (!this.db) {
|
|
574
683
|
throw new Error('Database not initialized');
|
|
@@ -576,7 +685,6 @@ export class DrizzleService extends BaseService {
|
|
|
576
685
|
await migrate(this.db as BunSQLiteDatabase<Record<string, SQLiteTable>>, {
|
|
577
686
|
migrationsFolder,
|
|
578
687
|
});
|
|
579
|
-
this.safeLog('info', 'SQLite migrations applied', { migrationsFolder });
|
|
580
688
|
} else if (this.connectionOptions.type === DatabaseType.POSTGRESQL) {
|
|
581
689
|
if (!this.db) {
|
|
582
690
|
throw new Error('Database not initialized');
|
|
@@ -584,8 +692,35 @@ export class DrizzleService extends BaseService {
|
|
|
584
692
|
await migratePostgres(this.db as BunSQLDatabase<Record<string, PgTable>>, {
|
|
585
693
|
migrationsFolder,
|
|
586
694
|
});
|
|
587
|
-
this.safeLog('info', 'PostgreSQL migrations applied', { migrationsFolder });
|
|
588
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
|
+
});
|
|
589
724
|
}
|
|
590
725
|
|
|
591
726
|
/**
|
|
@@ -712,15 +847,13 @@ export class DrizzleService extends BaseService {
|
|
|
712
847
|
* .returning();
|
|
713
848
|
* ```
|
|
714
849
|
*/
|
|
715
|
-
|
|
716
|
-
insert<TTable extends SQLiteTable<any>>(table: TTable): ReturnType<SQLiteDb['insert']>;
|
|
850
|
+
insert<TTable extends SQLiteTable>(table: TTable): SQLiteInsertBuilder<TTable, 'sync', void>;
|
|
717
851
|
/**
|
|
718
852
|
* Create an INSERT query for PostgreSQL table
|
|
719
853
|
*/
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
insert(table: SQLiteTable<any> | PgTable<any>) {
|
|
854
|
+
insert<TTable extends PgTable>(table: TTable): PgInsertBuilder<TTable, BunSQLQueryResultHKT>;
|
|
855
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
856
|
+
insert(table: SQLiteTable | PgTable) {
|
|
724
857
|
const db = this.getDatabase();
|
|
725
858
|
|
|
726
859
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -744,15 +877,13 @@ export class DrizzleService extends BaseService {
|
|
|
744
877
|
* .returning();
|
|
745
878
|
* ```
|
|
746
879
|
*/
|
|
747
|
-
|
|
748
|
-
update<TTable extends SQLiteTable<any>>(table: TTable): ReturnType<SQLiteDb['update']>;
|
|
880
|
+
update<TTable extends SQLiteTable>(table: TTable): SQLiteUpdateBuilder<TTable, 'sync', void>;
|
|
749
881
|
/**
|
|
750
882
|
* Create an UPDATE query for PostgreSQL table
|
|
751
883
|
*/
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
update(table: SQLiteTable<any> | PgTable<any>) {
|
|
884
|
+
update<TTable extends PgTable>(table: TTable): PgUpdateBuilder<TTable, BunSQLQueryResultHKT>;
|
|
885
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
886
|
+
update(table: SQLiteTable | PgTable) {
|
|
756
887
|
const db = this.getDatabase();
|
|
757
888
|
|
|
758
889
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -773,15 +904,13 @@ export class DrizzleService extends BaseService {
|
|
|
773
904
|
* .returning();
|
|
774
905
|
* ```
|
|
775
906
|
*/
|
|
776
|
-
|
|
777
|
-
delete<TTable extends SQLiteTable<any>>(table: TTable): ReturnType<SQLiteDb['delete']>;
|
|
907
|
+
delete<TTable extends SQLiteTable>(table: TTable): SQLiteDeleteBase<TTable, 'sync', void>;
|
|
778
908
|
/**
|
|
779
909
|
* Create a DELETE query for PostgreSQL table
|
|
780
910
|
*/
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
delete(table: SQLiteTable<any> | PgTable<any>) {
|
|
911
|
+
delete<TTable extends PgTable>(table: TTable): PgDeleteBase<TTable, BunSQLQueryResultHKT>;
|
|
912
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
913
|
+
delete(table: SQLiteTable | PgTable) {
|
|
785
914
|
const db = this.getDatabase();
|
|
786
915
|
|
|
787
916
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|