@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebun/drizzle",
3
- "version": "0.1.10",
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.15",
50
+ "@onebun/core": "^0.1.19",
51
51
  "@onebun/envs": "^0.1.4",
52
- "@onebun/logger": "^0.1.5",
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 { BunSQLDatabase } from 'drizzle-orm/bun-sql';
9
- import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
10
- import type { PgTable } from 'drizzle-orm/pg-core';
11
- import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
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<any>>(
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<any>>(
66
- table: TTable,
67
- ): ReturnType<PgDb['insert']>;
68
+ insert<TTable extends PgTable>(table: TTable): PgInsertBuilder<TTable, BunSQLQueryResultHKT>;
68
69
 
69
- insert(table: SQLiteTable<any> | PgTable<any>) {
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<any>>(
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<any>>(
84
- table: TTable,
85
- ): ReturnType<PgDb['update']>;
82
+ update<TTable extends PgTable>(table: TTable): PgUpdateBuilder<TTable, BunSQLQueryResultHKT>;
86
83
 
87
- update(table: SQLiteTable<any> | PgTable<any>) {
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<any>>(
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<any>>(
102
- table: TTable,
103
- ): ReturnType<PgDb['delete']>;
96
+ delete<TTable extends PgTable>(table: TTable): PgDeleteBase<TTable, BunSQLQueryResultHKT>;
104
97
 
105
- delete(table: SQLiteTable<any> | PgTable<any>) {
98
+ delete(table: SQLiteTable | PgTable) {
106
99
  return (this.tx as any).delete(table);
107
100
  }
108
101
 
@@ -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 { PgTable } from 'drizzle-orm/pg-core';
12
- import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
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 { BaseService, Service } from '@onebun/core';
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
- * Async initialization hook - called by the framework after initializeService()
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
- override async onAsyncInit(): Promise<void> {
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
721
- insert<TTable extends PgTable<any>>(table: TTable): ReturnType<PgDb['insert']>;
722
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
753
- update<TTable extends PgTable<any>>(table: TTable): ReturnType<PgDb['update']>;
754
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
782
- delete<TTable extends PgTable<any>>(table: TTable): ReturnType<PgDb['delete']>;
783
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
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