@onebun/drizzle 0.1.8 → 0.1.10
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 +2 -2
- package/src/builders/index.ts +9 -0
- package/src/builders/select-builder.ts +109 -0
- package/src/builders/transaction-client.ts +116 -0
- package/src/drizzle.module.ts +1 -1
- package/src/drizzle.service.ts +240 -109
- package/src/entity.decorator.ts +3 -6
- package/src/index.ts +7 -0
- package/src/repository.ts +17 -56
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/drizzle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Drizzle ORM module for OneBun framework - SQLite and PostgreSQL support",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "RemRyahirev",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dev": "bun run --watch src/index.ts"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@onebun/core": "^0.1.
|
|
50
|
+
"@onebun/core": "^0.1.15",
|
|
51
51
|
"@onebun/envs": "^0.1.4",
|
|
52
52
|
"@onebun/logger": "^0.1.5",
|
|
53
53
|
"drizzle-orm": "^0.44.7",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Query Builders
|
|
3
|
+
*
|
|
4
|
+
* These builders provide type-safe database operations that infer
|
|
5
|
+
* the correct database type (SQLite or PostgreSQL) from table schemas.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { UniversalSelectBuilder, UniversalSelectDistinctBuilder } from './select-builder';
|
|
9
|
+
export { UniversalTransactionClient } from './transaction-client';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// Drizzle ORM uses complex conditional types that require `any` for proper type inference
|
|
3
|
+
|
|
4
|
+
import type { DatabaseInstance } from '../types';
|
|
5
|
+
import type { PgTable } from 'drizzle-orm/pg-core';
|
|
6
|
+
import type { SQLiteSelectBase } from 'drizzle-orm/sqlite-core';
|
|
7
|
+
import type { SQLiteTable, SQLiteColumn } from 'drizzle-orm/sqlite-core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* SQLite select query result type with proper typing for table columns
|
|
11
|
+
*/
|
|
12
|
+
type SQLiteSelectQueryResult<TTable extends SQLiteTable<any>> = SQLiteSelectBase<
|
|
13
|
+
TTable['_']['name'],
|
|
14
|
+
'sync',
|
|
15
|
+
void,
|
|
16
|
+
TTable['_']['columns'],
|
|
17
|
+
'single',
|
|
18
|
+
Record<TTable['_']['name'], 'not-null'>,
|
|
19
|
+
false,
|
|
20
|
+
never,
|
|
21
|
+
TTable['$inferSelect'][],
|
|
22
|
+
Record<keyof TTable['_']['columns'], SQLiteColumn>
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Universal Select Builder that works with any table type
|
|
27
|
+
*
|
|
28
|
+
* This builder allows using DrizzleService without generic type parameter.
|
|
29
|
+
* The result type is determined by the actual database operation at runtime,
|
|
30
|
+
* but the select result array element type is inferred from the table schema.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // DrizzleService without generic
|
|
35
|
+
* const db = new DrizzleService();
|
|
36
|
+
*
|
|
37
|
+
* // Type of result elements is inferred from table schema
|
|
38
|
+
* const users = await db.select().from(usersTable);
|
|
39
|
+
* // users has type: UserRow[]
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class UniversalSelectBuilder<TFields extends Record<string, unknown> | undefined = undefined> {
|
|
43
|
+
constructor(
|
|
44
|
+
private db: DatabaseInstance,
|
|
45
|
+
private fields?: TFields,
|
|
46
|
+
) {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Select from a SQLite table
|
|
50
|
+
* Returns a chainable query builder that resolves to table row type
|
|
51
|
+
*/
|
|
52
|
+
from<TTable extends SQLiteTable<any>>(
|
|
53
|
+
table: TTable,
|
|
54
|
+
): SQLiteSelectQueryResult<TTable>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Select from a PostgreSQL table
|
|
58
|
+
* Returns a chainable query builder that resolves to table row type
|
|
59
|
+
*/
|
|
60
|
+
from<TTable extends PgTable<any>>(
|
|
61
|
+
table: TTable,
|
|
62
|
+
): Promise<TTable['$inferSelect'][]> & {
|
|
63
|
+
where: (condition: any) => Promise<TTable['$inferSelect'][]> & { get: () => Promise<TTable['$inferSelect'] | undefined> };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Implementation - runtime call to appropriate database
|
|
68
|
+
*/
|
|
69
|
+
from(table: SQLiteTable<any> | PgTable<any>): any {
|
|
70
|
+
const selectBuilder = this.fields
|
|
71
|
+
? (this.db as any).select(this.fields)
|
|
72
|
+
: (this.db as any).select();
|
|
73
|
+
|
|
74
|
+
return selectBuilder.from(table);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Universal SelectDistinct Builder
|
|
80
|
+
* Same as UniversalSelectBuilder but for DISTINCT queries
|
|
81
|
+
*/
|
|
82
|
+
export class UniversalSelectDistinctBuilder<TFields extends Record<string, unknown> | undefined = undefined> {
|
|
83
|
+
constructor(
|
|
84
|
+
private db: DatabaseInstance,
|
|
85
|
+
private fields?: TFields,
|
|
86
|
+
) {}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Select distinct from a SQLite table
|
|
90
|
+
*/
|
|
91
|
+
from<TTable extends SQLiteTable<any>>(
|
|
92
|
+
table: TTable,
|
|
93
|
+
): SQLiteSelectQueryResult<TTable>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Select distinct from a PostgreSQL table
|
|
97
|
+
*/
|
|
98
|
+
from<TTable extends PgTable<any>>(
|
|
99
|
+
table: TTable,
|
|
100
|
+
): Promise<TTable['$inferSelect'][]>;
|
|
101
|
+
|
|
102
|
+
from(table: SQLiteTable<any> | PgTable<any>): any {
|
|
103
|
+
const selectBuilder = this.fields
|
|
104
|
+
? (this.db as any).selectDistinct(this.fields)
|
|
105
|
+
: (this.db as any).selectDistinct();
|
|
106
|
+
|
|
107
|
+
return selectBuilder.from(table);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* eslint-disable
|
|
2
|
+
@typescript-eslint/no-explicit-any,
|
|
3
|
+
@typescript-eslint/explicit-module-boundary-types */
|
|
4
|
+
// Drizzle ORM uses complex conditional types that require `any` for proper type inference
|
|
5
|
+
// Method overloads define return types, so explicit return type on implementation is not needed
|
|
6
|
+
|
|
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';
|
|
12
|
+
|
|
13
|
+
import { UniversalSelectBuilder, UniversalSelectDistinctBuilder } from './select-builder';
|
|
14
|
+
|
|
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
|
+
/**
|
|
20
|
+
* Universal Transaction Client
|
|
21
|
+
*
|
|
22
|
+
* Wraps the transaction object to provide methods with overloads
|
|
23
|
+
* that infer the correct type from table schemas.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* await db.transaction(async (tx) => {
|
|
28
|
+
* // tx has the same API as DrizzleService
|
|
29
|
+
* const users = await tx.select().from(usersTable);
|
|
30
|
+
* await tx.insert(usersTable).values({ name: 'John' });
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class UniversalTransactionClient {
|
|
35
|
+
constructor(private tx: DatabaseInstance) {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a SELECT query
|
|
39
|
+
*/
|
|
40
|
+
select(): UniversalSelectBuilder;
|
|
41
|
+
select<TFields extends Record<string, unknown>>(fields: TFields): UniversalSelectBuilder<TFields>;
|
|
42
|
+
select<TFields extends Record<string, unknown>>(fields?: TFields) {
|
|
43
|
+
return new UniversalSelectBuilder(this.tx, fields);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a SELECT DISTINCT query
|
|
48
|
+
*/
|
|
49
|
+
selectDistinct(): UniversalSelectDistinctBuilder;
|
|
50
|
+
selectDistinct<TFields extends Record<string, unknown>>(fields: TFields): UniversalSelectDistinctBuilder<TFields>;
|
|
51
|
+
selectDistinct<TFields extends Record<string, unknown>>(fields?: TFields) {
|
|
52
|
+
return new UniversalSelectDistinctBuilder(this.tx, fields);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create an INSERT query for SQLite table
|
|
57
|
+
*/
|
|
58
|
+
insert<TTable extends SQLiteTable<any>>(
|
|
59
|
+
table: TTable,
|
|
60
|
+
): ReturnType<SQLiteDb['insert']>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create an INSERT query for PostgreSQL table
|
|
64
|
+
*/
|
|
65
|
+
insert<TTable extends PgTable<any>>(
|
|
66
|
+
table: TTable,
|
|
67
|
+
): ReturnType<PgDb['insert']>;
|
|
68
|
+
|
|
69
|
+
insert(table: SQLiteTable<any> | PgTable<any>) {
|
|
70
|
+
return (this.tx as any).insert(table);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create an UPDATE query for SQLite table
|
|
75
|
+
*/
|
|
76
|
+
update<TTable extends SQLiteTable<any>>(
|
|
77
|
+
table: TTable,
|
|
78
|
+
): ReturnType<SQLiteDb['update']>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create an UPDATE query for PostgreSQL table
|
|
82
|
+
*/
|
|
83
|
+
update<TTable extends PgTable<any>>(
|
|
84
|
+
table: TTable,
|
|
85
|
+
): ReturnType<PgDb['update']>;
|
|
86
|
+
|
|
87
|
+
update(table: SQLiteTable<any> | PgTable<any>) {
|
|
88
|
+
return (this.tx as any).update(table);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a DELETE query for SQLite table
|
|
93
|
+
*/
|
|
94
|
+
delete<TTable extends SQLiteTable<any>>(
|
|
95
|
+
table: TTable,
|
|
96
|
+
): ReturnType<SQLiteDb['delete']>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create a DELETE query for PostgreSQL table
|
|
100
|
+
*/
|
|
101
|
+
delete<TTable extends PgTable<any>>(
|
|
102
|
+
table: TTable,
|
|
103
|
+
): ReturnType<PgDb['delete']>;
|
|
104
|
+
|
|
105
|
+
delete(table: SQLiteTable<any> | PgTable<any>) {
|
|
106
|
+
return (this.tx as any).delete(table);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the raw transaction object for advanced usage
|
|
111
|
+
* Note: Returns union type - use type guards for specific database type
|
|
112
|
+
*/
|
|
113
|
+
getRawTransaction(): DatabaseInstance {
|
|
114
|
+
return this.tx;
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/drizzle.module.ts
CHANGED
|
@@ -30,7 +30,7 @@ const DRIZZLE_MODULE_OPTIONS = Symbol('DRIZZLE_MODULE_OPTIONS');
|
|
|
30
30
|
* - DB_URL: Database connection URL (default: ':memory:' for SQLite)
|
|
31
31
|
* - DB_SCHEMA_PATH: Path to schema files (optional)
|
|
32
32
|
* - DB_MIGRATIONS_FOLDER: Path to migrations folder (default: './drizzle')
|
|
33
|
-
* - DB_AUTO_MIGRATE: Whether to run migrations on startup (default:
|
|
33
|
+
* - DB_AUTO_MIGRATE: Whether to run migrations on startup (default: true)
|
|
34
34
|
* - DB_LOG_QUERIES: Whether to log SQL queries (default: false)
|
|
35
35
|
*
|
|
36
36
|
* @example Basic usage with environment variables (global by default)
|
package/src/drizzle.service.ts
CHANGED
|
@@ -18,10 +18,14 @@ import {
|
|
|
18
18
|
EnvParser,
|
|
19
19
|
} from '@onebun/envs';
|
|
20
20
|
|
|
21
|
+
import {
|
|
22
|
+
UniversalSelectBuilder,
|
|
23
|
+
UniversalSelectDistinctBuilder,
|
|
24
|
+
UniversalTransactionClient,
|
|
25
|
+
} from './builders';
|
|
21
26
|
import {
|
|
22
27
|
type DatabaseConnectionOptions,
|
|
23
28
|
type DatabaseInstance,
|
|
24
|
-
type DatabaseInstanceForType,
|
|
25
29
|
DatabaseType,
|
|
26
30
|
type DatabaseTypeLiteral,
|
|
27
31
|
type DrizzleModuleOptions,
|
|
@@ -29,6 +33,10 @@ import {
|
|
|
29
33
|
type PostgreSQLConnectionOptions,
|
|
30
34
|
} from './types';
|
|
31
35
|
|
|
36
|
+
// Type helpers for insert/update/delete return types
|
|
37
|
+
type SQLiteDb = BunSQLiteDatabase<Record<string, SQLiteTable>>;
|
|
38
|
+
type PgDb = BunSQLDatabase<Record<string, PgTable>>;
|
|
39
|
+
|
|
32
40
|
/**
|
|
33
41
|
* Default environment variable prefix
|
|
34
42
|
*/
|
|
@@ -158,68 +166,140 @@ async function loadFromEnv(prefix: string = DEFAULT_ENV_PREFIX): Promise<Databas
|
|
|
158
166
|
};
|
|
159
167
|
}
|
|
160
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Buffered log entry for pre-logger initialization logging
|
|
171
|
+
*/
|
|
172
|
+
interface BufferedLogEntry {
|
|
173
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
174
|
+
message: string;
|
|
175
|
+
meta?: object;
|
|
176
|
+
timestamp: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
161
179
|
/**
|
|
162
180
|
* Drizzle service for database operations
|
|
163
|
-
*
|
|
181
|
+
*
|
|
182
|
+
* The service automatically infers database types from table schemas.
|
|
183
|
+
* No generic parameter is required - just use select(), insert(), update(), delete()
|
|
184
|
+
* with your table schemas and TypeScript will infer the correct types.
|
|
164
185
|
*
|
|
165
186
|
* @example
|
|
166
187
|
* ```typescript
|
|
167
|
-
* //
|
|
168
|
-
* const
|
|
169
|
-
* const
|
|
170
|
-
*
|
|
171
|
-
* //
|
|
172
|
-
*
|
|
173
|
-
*
|
|
188
|
+
* // Define tables with proper types
|
|
189
|
+
* const users = sqliteTable('users', { ... }); // SQLite table
|
|
190
|
+
* const orders = pgTable('orders', { ... }); // PostgreSQL table
|
|
191
|
+
*
|
|
192
|
+
* // Use DrizzleService without generic parameter
|
|
193
|
+
* @Service()
|
|
194
|
+
* class UserService extends BaseService {
|
|
195
|
+
* constructor(private db: DrizzleService) {
|
|
196
|
+
* super();
|
|
197
|
+
* }
|
|
198
|
+
*
|
|
199
|
+
* async findAll() {
|
|
200
|
+
* // TypeScript infers SQLite types from `users` table
|
|
201
|
+
* return this.db.select().from(users);
|
|
202
|
+
* }
|
|
203
|
+
* }
|
|
174
204
|
* ```
|
|
175
205
|
*/
|
|
176
206
|
@Service()
|
|
177
|
-
export class DrizzleService
|
|
207
|
+
export class DrizzleService extends BaseService {
|
|
178
208
|
private db: DatabaseInstance | null = null;
|
|
179
|
-
private dbType:
|
|
209
|
+
private dbType: DatabaseTypeLiteral | null = null;
|
|
180
210
|
private connectionOptions: DatabaseConnectionOptions | null = null;
|
|
181
211
|
private initialized = false;
|
|
182
212
|
private initPromise: Promise<void> | null = null;
|
|
183
213
|
private sqliteClient: Database | null = null;
|
|
184
214
|
private postgresClient: SQL | null = null;
|
|
215
|
+
private logBuffer: BufferedLogEntry[] = [];
|
|
216
|
+
private exitHandlerRegistered = false;
|
|
185
217
|
|
|
186
218
|
constructor() {
|
|
187
219
|
super();
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
// Register exit handler to flush buffered logs on crash
|
|
221
|
+
this.registerExitHandler();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Register process exit handler to flush buffered logs to console.error on crash
|
|
226
|
+
*/
|
|
227
|
+
private registerExitHandler(): void {
|
|
228
|
+
if (this.exitHandlerRegistered) {
|
|
229
|
+
return;
|
|
192
230
|
}
|
|
231
|
+
this.exitHandlerRegistered = true;
|
|
232
|
+
|
|
233
|
+
const flushToConsole = () => {
|
|
234
|
+
if (this.logBuffer.length > 0) {
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
236
|
+
console.error('[DrizzleService] Buffered logs (app crashed before logger init):');
|
|
237
|
+
for (const entry of this.logBuffer) {
|
|
238
|
+
const timestamp = new Date(entry.timestamp).toISOString();
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
240
|
+
console.error(` [${timestamp}] [${entry.level.toUpperCase()}] ${entry.message}`, entry.meta ?? '');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Register handlers for various exit scenarios
|
|
246
|
+
process.on('exit', flushToConsole);
|
|
247
|
+
process.on('uncaughtException', (err) => {
|
|
248
|
+
flushToConsole();
|
|
249
|
+
// eslint-disable-next-line no-console
|
|
250
|
+
console.error('[DrizzleService] Uncaught exception:', err);
|
|
251
|
+
});
|
|
252
|
+
process.on('unhandledRejection', (reason) => {
|
|
253
|
+
flushToConsole();
|
|
254
|
+
// eslint-disable-next-line no-console
|
|
255
|
+
console.error('[DrizzleService] Unhandled rejection:', reason);
|
|
256
|
+
});
|
|
193
257
|
}
|
|
194
258
|
|
|
195
259
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* Module options are checked in autoInitialize() which is async.
|
|
260
|
+
* Safe logging that buffers logs before logger is available
|
|
261
|
+
* When logger becomes available, buffered logs are flushed
|
|
262
|
+
* If app crashes before logger init, logs are output via console.error
|
|
200
263
|
*/
|
|
201
|
-
private
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
264
|
+
private safeLog(level: 'debug' | 'info' | 'warn' | 'error', message: string, meta?: object): void {
|
|
265
|
+
if (this.logger) {
|
|
266
|
+
this.logger[level](message, meta);
|
|
267
|
+
} else {
|
|
268
|
+
// Buffer the log for later
|
|
269
|
+
this.logBuffer.push({
|
|
270
|
+
level,
|
|
271
|
+
message,
|
|
272
|
+
meta,
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
});
|
|
207
275
|
}
|
|
276
|
+
}
|
|
208
277
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Flush buffered logs to the logger (called when logger becomes available)
|
|
280
|
+
*/
|
|
281
|
+
private flushLogBuffer(): void {
|
|
282
|
+
if (!this.logger || this.logBuffer.length === 0) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.logger.debug(`Flushing ${this.logBuffer.length} buffered log entries`);
|
|
287
|
+
for (const entry of this.logBuffer) {
|
|
288
|
+
this.logger[entry.level](entry.message, entry.meta);
|
|
220
289
|
}
|
|
290
|
+
this.logBuffer = [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Async initialization hook - called by the framework after initializeService()
|
|
295
|
+
* This ensures the database is fully ready before client code runs
|
|
296
|
+
*/
|
|
297
|
+
override async onAsyncInit(): Promise<void> {
|
|
298
|
+
// Flush any buffered logs now that logger is available
|
|
299
|
+
this.flushLogBuffer();
|
|
221
300
|
|
|
222
|
-
|
|
301
|
+
// Run auto-initialization
|
|
302
|
+
await this.autoInitialize();
|
|
223
303
|
}
|
|
224
304
|
|
|
225
305
|
/**
|
|
@@ -232,16 +312,30 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
232
312
|
|
|
233
313
|
// If module options are provided, use them
|
|
234
314
|
if (moduleOptions?.connection) {
|
|
235
|
-
this.
|
|
315
|
+
this.safeLog('debug', 'Auto-initializing database service from module options', {
|
|
236
316
|
type: moduleOptions.connection.type,
|
|
237
317
|
});
|
|
238
318
|
|
|
239
|
-
|
|
319
|
+
// Pass skipWait=true to avoid deadlock (we're already inside initPromise)
|
|
320
|
+
await this.initialize(moduleOptions.connection, true);
|
|
240
321
|
|
|
241
|
-
// Auto-migrate
|
|
242
|
-
|
|
322
|
+
// Auto-migrate is enabled by default (unless explicitly set to false)
|
|
323
|
+
const shouldAutoMigrate = moduleOptions.autoMigrate !== false;
|
|
324
|
+
if (shouldAutoMigrate) {
|
|
243
325
|
const migrationsFolder = moduleOptions.migrationsFolder ?? './drizzle';
|
|
244
|
-
|
|
326
|
+
this.safeLog('debug', 'Running auto-migrations', { migrationsFolder });
|
|
327
|
+
try {
|
|
328
|
+
// Pass skipWait=true to avoid deadlock (we're already inside initPromise)
|
|
329
|
+
await this.runMigrations({ migrationsFolder }, true);
|
|
330
|
+
this.safeLog('debug', 'Auto-migrations completed successfully');
|
|
331
|
+
} catch (migrationError) {
|
|
332
|
+
this.safeLog('warn', 'Auto-migration failed, database initialized without migrations', {
|
|
333
|
+
error: migrationError instanceof Error ? migrationError.message : String(migrationError),
|
|
334
|
+
});
|
|
335
|
+
// Don't rethrow - allow DB to be used even if migrations fail
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
this.safeLog('debug', 'Auto-migrations disabled via module options');
|
|
245
339
|
}
|
|
246
340
|
|
|
247
341
|
this.initialized = true;
|
|
@@ -255,7 +349,7 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
255
349
|
// Check process.env directly to ensure we only auto-initialize when explicitly configured
|
|
256
350
|
const dbUrlFromProcess = process.env[`${envPrefix}_URL`];
|
|
257
351
|
if (!dbUrlFromProcess || dbUrlFromProcess.trim() === '') {
|
|
258
|
-
this.
|
|
352
|
+
this.safeLog('debug', 'Skipping auto-initialization: no database configuration found in process.env');
|
|
259
353
|
this.initialized = false;
|
|
260
354
|
|
|
261
355
|
return;
|
|
@@ -275,22 +369,24 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
275
369
|
options: parsePostgreSQLUrl(envConfig.url),
|
|
276
370
|
};
|
|
277
371
|
|
|
278
|
-
this.
|
|
372
|
+
this.safeLog('debug', `Auto-initializing database service with type: ${connectionOptions.type}`, {
|
|
279
373
|
envPrefix,
|
|
280
374
|
});
|
|
281
375
|
|
|
282
|
-
|
|
376
|
+
// Pass skipWait=true to avoid deadlock (we're already inside initPromise)
|
|
377
|
+
await this.initialize(connectionOptions, true);
|
|
283
378
|
|
|
284
|
-
// Auto-migrate
|
|
379
|
+
// Auto-migrate is enabled by default (env schema default is true)
|
|
285
380
|
if (envConfig.autoMigrate) {
|
|
286
381
|
const migrationsFolder = envConfig.migrationsFolder ?? './drizzle';
|
|
287
|
-
|
|
382
|
+
// Pass skipWait=true to avoid deadlock (we're already inside initPromise)
|
|
383
|
+
await this.runMigrations({ migrationsFolder }, true);
|
|
288
384
|
}
|
|
289
385
|
|
|
290
386
|
this.initialized = true;
|
|
291
387
|
} catch (error) {
|
|
292
388
|
// Don't throw error - just log it and allow manual initialization
|
|
293
|
-
this.
|
|
389
|
+
this.safeLog('debug', 'Failed to auto-initialize database from environment', { error });
|
|
294
390
|
this.initialized = false;
|
|
295
391
|
}
|
|
296
392
|
}
|
|
@@ -320,23 +416,29 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
320
416
|
|
|
321
417
|
/**
|
|
322
418
|
* Initialize database connection
|
|
419
|
+
* @param options - Database connection options
|
|
420
|
+
* @param skipWait - Internal flag to skip waitForInit (used by autoInitialize to avoid deadlock)
|
|
323
421
|
*/
|
|
324
|
-
async initialize(options: DatabaseConnectionOptions): Promise<void> {
|
|
325
|
-
|
|
422
|
+
async initialize(options: DatabaseConnectionOptions, skipWait = false): Promise<void> {
|
|
423
|
+
// Skip waitForInit when called from autoInitialize to avoid deadlock
|
|
424
|
+
// (autoInitialize is the function that creates initPromise)
|
|
425
|
+
if (!skipWait) {
|
|
426
|
+
await this.waitForInit();
|
|
427
|
+
}
|
|
326
428
|
|
|
327
429
|
if (this.initialized && this.connectionOptions) {
|
|
328
|
-
this.
|
|
430
|
+
this.safeLog('warn', 'Database already initialized, closing existing connection');
|
|
329
431
|
await this.close();
|
|
330
432
|
}
|
|
331
433
|
|
|
332
434
|
this.connectionOptions = options;
|
|
333
|
-
this.dbType = options.type
|
|
435
|
+
this.dbType = options.type;
|
|
334
436
|
|
|
335
437
|
if (options.type === DatabaseType.SQLITE) {
|
|
336
438
|
const sqliteOptions = options.options;
|
|
337
439
|
this.sqliteClient = new Database(sqliteOptions.url, sqliteOptions.options);
|
|
338
440
|
this.db = drizzleSQLite(this.sqliteClient);
|
|
339
|
-
this.
|
|
441
|
+
this.safeLog('info', 'SQLite database initialized', { url: sqliteOptions.url });
|
|
340
442
|
} else if (options.type === DatabaseType.POSTGRESQL) {
|
|
341
443
|
const pgOptions = options.options;
|
|
342
444
|
|
|
@@ -352,7 +454,7 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
352
454
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
455
|
this.postgresClient = (this.db as any).$client as SQL | null;
|
|
354
456
|
|
|
355
|
-
this.
|
|
457
|
+
this.safeLog('info', 'PostgreSQL database initialized with Bun.SQL', {
|
|
356
458
|
host: pgOptions.host,
|
|
357
459
|
port: pgOptions.port,
|
|
358
460
|
database: pgOptions.database,
|
|
@@ -367,16 +469,20 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
367
469
|
}
|
|
368
470
|
|
|
369
471
|
/**
|
|
370
|
-
* Get database instance
|
|
371
|
-
*
|
|
472
|
+
* Get raw database instance
|
|
473
|
+
*
|
|
474
|
+
* Returns a union type - use isSQLite()/isPostgreSQL() type guards
|
|
475
|
+
* or getSQLiteDatabase()/getPostgreSQLDatabase() for specific types.
|
|
476
|
+
*
|
|
477
|
+
* For most use cases, prefer using select(), insert(), update(), delete()
|
|
478
|
+
* methods which automatically infer types from table schemas.
|
|
372
479
|
*/
|
|
373
|
-
getDatabase():
|
|
480
|
+
getDatabase(): DatabaseInstance {
|
|
374
481
|
if (!this.db || !this.dbType) {
|
|
375
482
|
throw new Error('Database not initialized. Call initialize() first.');
|
|
376
483
|
}
|
|
377
484
|
|
|
378
|
-
|
|
379
|
-
return this.db as DatabaseInstanceForType<TDbType>;
|
|
485
|
+
return this.db;
|
|
380
486
|
}
|
|
381
487
|
|
|
382
488
|
/**
|
|
@@ -448,10 +554,14 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
448
554
|
* migration system which ensures idempotency.
|
|
449
555
|
*
|
|
450
556
|
* @param options - Migration options
|
|
557
|
+
* @param skipWait - Internal flag to skip waitForInit (used by autoInitialize to avoid deadlock)
|
|
451
558
|
* @throws Error if database is not initialized
|
|
452
559
|
*/
|
|
453
|
-
async runMigrations(options?: MigrationOptions): Promise<void> {
|
|
454
|
-
|
|
560
|
+
async runMigrations(options?: MigrationOptions, skipWait = false): Promise<void> {
|
|
561
|
+
// Skip waitForInit when called from autoInitialize to avoid deadlock
|
|
562
|
+
if (!skipWait) {
|
|
563
|
+
await this.waitForInit();
|
|
564
|
+
}
|
|
455
565
|
|
|
456
566
|
if (!this.db || !this.connectionOptions) {
|
|
457
567
|
throw new Error('Database not initialized. Call initialize() first.');
|
|
@@ -466,7 +576,7 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
466
576
|
await migrate(this.db as BunSQLiteDatabase<Record<string, SQLiteTable>>, {
|
|
467
577
|
migrationsFolder,
|
|
468
578
|
});
|
|
469
|
-
this.
|
|
579
|
+
this.safeLog('info', 'SQLite migrations applied', { migrationsFolder });
|
|
470
580
|
} else if (this.connectionOptions.type === DatabaseType.POSTGRESQL) {
|
|
471
581
|
if (!this.db) {
|
|
472
582
|
throw new Error('Database not initialized');
|
|
@@ -474,7 +584,7 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
474
584
|
await migratePostgres(this.db as BunSQLDatabase<Record<string, PgTable>>, {
|
|
475
585
|
migrationsFolder,
|
|
476
586
|
});
|
|
477
|
-
this.
|
|
587
|
+
this.safeLog('info', 'PostgreSQL migrations applied', { migrationsFolder });
|
|
478
588
|
}
|
|
479
589
|
}
|
|
480
590
|
|
|
@@ -505,14 +615,26 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
505
615
|
this.dbType = null;
|
|
506
616
|
this.connectionOptions = null;
|
|
507
617
|
this.initialized = false;
|
|
508
|
-
this.
|
|
618
|
+
this.safeLog('info', 'Database connection closed');
|
|
509
619
|
}
|
|
510
620
|
|
|
511
621
|
/**
|
|
512
|
-
* Execute a transaction with
|
|
622
|
+
* Execute a transaction with universal transaction client
|
|
623
|
+
*
|
|
624
|
+
* The transaction client provides the same API as DrizzleService
|
|
625
|
+
* with automatic type inference from table schemas.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```typescript
|
|
629
|
+
* await db.transaction(async (tx) => {
|
|
630
|
+
* const users = await tx.select().from(usersTable);
|
|
631
|
+
* await tx.insert(usersTable).values({ name: 'John' });
|
|
632
|
+
* await tx.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.id, 1));
|
|
633
|
+
* });
|
|
634
|
+
* ```
|
|
513
635
|
*/
|
|
514
636
|
async transaction<R>(
|
|
515
|
-
callback: (tx:
|
|
637
|
+
callback: (tx: UniversalTransactionClient) => Promise<R>,
|
|
516
638
|
): Promise<R> {
|
|
517
639
|
await this.waitForInit();
|
|
518
640
|
|
|
@@ -520,43 +642,42 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
520
642
|
throw new Error('Database not initialized. Call initialize() first.');
|
|
521
643
|
}
|
|
522
644
|
|
|
523
|
-
// Type assertion needed because TypeScript cannot infer methods from conditional types
|
|
524
|
-
// Runtime type is correct - this is a TypeScript limitation
|
|
525
645
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
526
|
-
return await (this.db as any).transaction(
|
|
646
|
+
return await (this.db as any).transaction(async (rawTx: DatabaseInstance) => {
|
|
647
|
+
const wrappedTx = new UniversalTransactionClient(rawTx);
|
|
648
|
+
|
|
649
|
+
return await callback(wrappedTx);
|
|
650
|
+
});
|
|
527
651
|
}
|
|
528
652
|
|
|
529
653
|
// ============================================
|
|
530
|
-
// Direct database operation methods
|
|
654
|
+
// Direct database operation methods
|
|
655
|
+
// These methods use universal builders that infer
|
|
656
|
+
// database type from table schemas
|
|
531
657
|
// ============================================
|
|
532
658
|
|
|
533
659
|
/**
|
|
534
660
|
* Create a SELECT query
|
|
535
661
|
*
|
|
662
|
+
* Returns a builder with from() method that infers the correct
|
|
663
|
+
* database type from the table schema.
|
|
664
|
+
*
|
|
536
665
|
* @example
|
|
537
666
|
* ```typescript
|
|
538
|
-
* // Select all columns
|
|
667
|
+
* // Select all columns - type inferred from table
|
|
539
668
|
* const users = await this.db.select().from(usersTable);
|
|
540
669
|
*
|
|
541
670
|
* // Select specific columns
|
|
542
671
|
* const names = await this.db.select({ name: usersTable.name }).from(usersTable);
|
|
543
672
|
* ```
|
|
544
673
|
*/
|
|
545
|
-
select():
|
|
546
|
-
select<
|
|
547
|
-
|
|
548
|
-
)
|
|
549
|
-
select<TSelection extends Record<string, unknown>>(
|
|
550
|
-
fields?: TSelection,
|
|
551
|
-
): ReturnType<DatabaseInstanceForType<TDbType>['select']> {
|
|
674
|
+
select(): UniversalSelectBuilder;
|
|
675
|
+
select<TFields extends Record<string, unknown>>(fields: TFields): UniversalSelectBuilder<TFields>;
|
|
676
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
677
|
+
select<TFields extends Record<string, unknown>>(fields?: TFields) {
|
|
552
678
|
const db = this.getDatabase();
|
|
553
|
-
if (fields) {
|
|
554
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
555
|
-
return (db as any).select(fields);
|
|
556
|
-
}
|
|
557
679
|
|
|
558
|
-
|
|
559
|
-
return (db as any).select();
|
|
680
|
+
return new UniversalSelectBuilder(db, fields);
|
|
560
681
|
}
|
|
561
682
|
|
|
562
683
|
/**
|
|
@@ -568,25 +689,17 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
568
689
|
* const uniqueNames = await this.db.selectDistinct({ name: usersTable.name }).from(usersTable);
|
|
569
690
|
* ```
|
|
570
691
|
*/
|
|
571
|
-
selectDistinct():
|
|
572
|
-
selectDistinct<
|
|
573
|
-
|
|
574
|
-
)
|
|
575
|
-
selectDistinct<TSelection extends Record<string, unknown>>(
|
|
576
|
-
fields?: TSelection,
|
|
577
|
-
): ReturnType<DatabaseInstanceForType<TDbType>['selectDistinct']> {
|
|
692
|
+
selectDistinct(): UniversalSelectDistinctBuilder;
|
|
693
|
+
selectDistinct<TFields extends Record<string, unknown>>(fields: TFields): UniversalSelectDistinctBuilder<TFields>;
|
|
694
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
695
|
+
selectDistinct<TFields extends Record<string, unknown>>(fields?: TFields) {
|
|
578
696
|
const db = this.getDatabase();
|
|
579
|
-
if (fields) {
|
|
580
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
581
|
-
return (db as any).selectDistinct(fields);
|
|
582
|
-
}
|
|
583
697
|
|
|
584
|
-
|
|
585
|
-
return (db as any).selectDistinct();
|
|
698
|
+
return new UniversalSelectDistinctBuilder(db, fields);
|
|
586
699
|
}
|
|
587
700
|
|
|
588
701
|
/**
|
|
589
|
-
* Create an INSERT query
|
|
702
|
+
* Create an INSERT query for SQLite table
|
|
590
703
|
*
|
|
591
704
|
* @example
|
|
592
705
|
* ```typescript
|
|
@@ -599,9 +712,15 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
599
712
|
* .returning();
|
|
600
713
|
* ```
|
|
601
714
|
*/
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
715
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
716
|
+
insert<TTable extends SQLiteTable<any>>(table: TTable): ReturnType<SQLiteDb['insert']>;
|
|
717
|
+
/**
|
|
718
|
+
* Create an INSERT query for PostgreSQL table
|
|
719
|
+
*/
|
|
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>) {
|
|
605
724
|
const db = this.getDatabase();
|
|
606
725
|
|
|
607
726
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -609,7 +728,7 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
609
728
|
}
|
|
610
729
|
|
|
611
730
|
/**
|
|
612
|
-
* Create an UPDATE query
|
|
731
|
+
* Create an UPDATE query for SQLite table
|
|
613
732
|
*
|
|
614
733
|
* @example
|
|
615
734
|
* ```typescript
|
|
@@ -625,9 +744,15 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
625
744
|
* .returning();
|
|
626
745
|
* ```
|
|
627
746
|
*/
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
747
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
748
|
+
update<TTable extends SQLiteTable<any>>(table: TTable): ReturnType<SQLiteDb['update']>;
|
|
749
|
+
/**
|
|
750
|
+
* Create an UPDATE query for PostgreSQL table
|
|
751
|
+
*/
|
|
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>) {
|
|
631
756
|
const db = this.getDatabase();
|
|
632
757
|
|
|
633
758
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -635,7 +760,7 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
635
760
|
}
|
|
636
761
|
|
|
637
762
|
/**
|
|
638
|
-
* Create a DELETE query
|
|
763
|
+
* Create a DELETE query for SQLite table
|
|
639
764
|
*
|
|
640
765
|
* @example
|
|
641
766
|
* ```typescript
|
|
@@ -648,9 +773,15 @@ export class DrizzleService<TDbType extends DatabaseTypeLiteral = DatabaseTypeLi
|
|
|
648
773
|
* .returning();
|
|
649
774
|
* ```
|
|
650
775
|
*/
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
776
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
777
|
+
delete<TTable extends SQLiteTable<any>>(table: TTable): ReturnType<SQLiteDb['delete']>;
|
|
778
|
+
/**
|
|
779
|
+
* Create a DELETE query for PostgreSQL table
|
|
780
|
+
*/
|
|
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>) {
|
|
654
785
|
const db = this.getDatabase();
|
|
655
786
|
|
|
656
787
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/src/entity.decorator.ts
CHANGED
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { DrizzleService } from './drizzle.service';
|
|
26
26
|
import type { BaseRepository } from './repository';
|
|
27
|
-
import type { InferDbTypeFromTable } from './types';
|
|
28
27
|
import type { PgTable } from 'drizzle-orm/pg-core';
|
|
29
28
|
import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
30
29
|
|
|
@@ -32,8 +31,6 @@ import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
|
32
31
|
const ENTITY_METADATA = new Map<Function, PgTable<any> | SQLiteTable<any>>();
|
|
33
32
|
|
|
34
33
|
export function Entity<TTable extends PgTable<any> | SQLiteTable<any>>(table: TTable) {
|
|
35
|
-
type TDbType = InferDbTypeFromTable<TTable>;
|
|
36
|
-
|
|
37
34
|
return <T extends new (...args: any[]) => BaseRepository<TTable>>(target: T) => {
|
|
38
35
|
// Store table schema in metadata
|
|
39
36
|
ENTITY_METADATA.set(target, table);
|
|
@@ -42,8 +39,8 @@ export function Entity<TTable extends PgTable<any> | SQLiteTable<any>>(table: TT
|
|
|
42
39
|
// The wrapped class only requires DrizzleService as constructor argument
|
|
43
40
|
class WrappedRepository extends target {
|
|
44
41
|
constructor(...args: any[]) {
|
|
45
|
-
// First argument should be DrizzleService
|
|
46
|
-
const drizzleService = args[0] as DrizzleService
|
|
42
|
+
// First argument should be DrizzleService (no generic parameter needed)
|
|
43
|
+
const drizzleService = args[0] as DrizzleService;
|
|
47
44
|
// Pass drizzleService and table to parent constructor
|
|
48
45
|
super(drizzleService, table);
|
|
49
46
|
}
|
|
@@ -57,7 +54,7 @@ export function Entity<TTable extends PgTable<any> | SQLiteTable<any>>(table: TT
|
|
|
57
54
|
});
|
|
58
55
|
|
|
59
56
|
// Return wrapped class - TypeScript will infer the correct constructor signature
|
|
60
|
-
return WrappedRepository as any as new (drizzleService: DrizzleService
|
|
57
|
+
return WrappedRepository as any as new (drizzleService: DrizzleService) => InstanceType<T>;
|
|
61
58
|
};
|
|
62
59
|
}
|
|
63
60
|
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,13 @@ export { DatabaseType } from './types';
|
|
|
25
25
|
export { DrizzleModule } from './drizzle.module';
|
|
26
26
|
export { DrizzleService } from './drizzle.service';
|
|
27
27
|
|
|
28
|
+
// Universal builders for type inference
|
|
29
|
+
export {
|
|
30
|
+
UniversalSelectBuilder,
|
|
31
|
+
UniversalSelectDistinctBuilder,
|
|
32
|
+
UniversalTransactionClient,
|
|
33
|
+
} from './builders';
|
|
34
|
+
|
|
28
35
|
// Repository
|
|
29
36
|
export { BaseRepository, type QueryBuilder } from './repository';
|
|
30
37
|
|
package/src/repository.ts
CHANGED
|
@@ -4,19 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import { eq, type SQL } from 'drizzle-orm';
|
|
6
6
|
|
|
7
|
-
import type { IRepository } from './types';
|
|
8
|
-
import type { DatabaseTypeLiteral, DatabaseInstanceForType } from './types';
|
|
7
|
+
import type { IRepository, DatabaseInstance } from './types';
|
|
9
8
|
import type { PgTable } from 'drizzle-orm/pg-core';
|
|
10
9
|
import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
11
10
|
|
|
12
11
|
|
|
12
|
+
import { UniversalTransactionClient } from './builders';
|
|
13
13
|
import { DrizzleService } from './drizzle.service';
|
|
14
14
|
import {
|
|
15
15
|
getPrimaryKeyColumn,
|
|
16
16
|
type SelectType,
|
|
17
17
|
type InsertType,
|
|
18
18
|
} from './schema-utils';
|
|
19
|
-
import { DatabaseType } from './types';
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* Query builder interface for type-safe database operations
|
|
@@ -90,55 +89,13 @@ export interface QueryBuilder {
|
|
|
90
89
|
* }
|
|
91
90
|
* ```
|
|
92
91
|
*/
|
|
93
|
-
/**
|
|
94
|
-
* Infer database type from table schema
|
|
95
|
-
*/
|
|
96
|
-
type InferDbTypeFromTable<TTable> =
|
|
97
|
-
TTable extends SQLiteTable<any>
|
|
98
|
-
? DatabaseType.SQLITE
|
|
99
|
-
: TTable extends PgTable<any>
|
|
100
|
-
? DatabaseType.POSTGRESQL
|
|
101
|
-
: never;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Base repository class for Drizzle ORM
|
|
105
|
-
*
|
|
106
|
-
* Simplified version with single generic parameter - database type is automatically inferred from table schema
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* ```typescript
|
|
110
|
-
* import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
|
|
111
|
-
* import { BaseRepository } from '@onebun/drizzle';
|
|
112
|
-
*
|
|
113
|
-
* const users = sqliteTable('users', {
|
|
114
|
-
* id: integer('id').primaryKey({ autoIncrement: true }),
|
|
115
|
-
* name: text('name').notNull(),
|
|
116
|
-
* });
|
|
117
|
-
*
|
|
118
|
-
* export class UserRepository extends BaseRepository<typeof users> {
|
|
119
|
-
* constructor(drizzleService: DrizzleService) {
|
|
120
|
-
* super(drizzleService, users);
|
|
121
|
-
* }
|
|
122
|
-
* }
|
|
123
|
-
* ```
|
|
124
|
-
*
|
|
125
|
-
* Advanced version with explicit database type (for edge cases):
|
|
126
|
-
* ```typescript
|
|
127
|
-
* export class UserRepository extends BaseRepository<DatabaseType.SQLITE, typeof users> {
|
|
128
|
-
* constructor(drizzleService: DrizzleService<DatabaseType.SQLITE>) {
|
|
129
|
-
* super(drizzleService, users);
|
|
130
|
-
* }
|
|
131
|
-
* }
|
|
132
|
-
* ```
|
|
133
|
-
*/
|
|
134
92
|
export class BaseRepository<
|
|
135
93
|
TTable extends PgTable<any> | SQLiteTable<any>,
|
|
136
|
-
TDbType extends DatabaseTypeLiteral = InferDbTypeFromTable<TTable>
|
|
137
94
|
> implements IRepository<SelectType<TTable>> {
|
|
138
95
|
/**
|
|
139
|
-
* Database instance
|
|
96
|
+
* Database instance
|
|
140
97
|
*/
|
|
141
|
-
protected readonly db:
|
|
98
|
+
protected readonly db: DatabaseInstance;
|
|
142
99
|
|
|
143
100
|
/**
|
|
144
101
|
* Table schema instance
|
|
@@ -146,20 +103,16 @@ export class BaseRepository<
|
|
|
146
103
|
protected readonly table: TTable;
|
|
147
104
|
|
|
148
105
|
/**
|
|
149
|
-
* DrizzleService instance
|
|
106
|
+
* DrizzleService instance
|
|
150
107
|
*/
|
|
151
|
-
protected readonly drizzleService: DrizzleService
|
|
108
|
+
protected readonly drizzleService: DrizzleService;
|
|
152
109
|
|
|
153
110
|
constructor(
|
|
154
|
-
drizzleService: DrizzleService
|
|
111
|
+
drizzleService: DrizzleService,
|
|
155
112
|
table: TTable,
|
|
156
113
|
) {
|
|
157
114
|
this.table = table;
|
|
158
|
-
|
|
159
|
-
// Type assertion is safe because TDbType is inferred from TTable
|
|
160
|
-
this.drizzleService = drizzleService as DrizzleService<TDbType>;
|
|
161
|
-
// getDatabase() returns correctly typed instance based on TDbType
|
|
162
|
-
// TDbType is automatically inferred from TTable
|
|
115
|
+
this.drizzleService = drizzleService;
|
|
163
116
|
this.db = this.drizzleService.getDatabase();
|
|
164
117
|
}
|
|
165
118
|
|
|
@@ -308,9 +261,17 @@ export class BaseRepository<
|
|
|
308
261
|
|
|
309
262
|
/**
|
|
310
263
|
* Execute a transaction
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* await userRepository.transaction(async (tx) => {
|
|
268
|
+
* const users = await tx.select().from(usersTable);
|
|
269
|
+
* await tx.insert(usersTable).values({ name: 'John' });
|
|
270
|
+
* });
|
|
271
|
+
* ```
|
|
311
272
|
*/
|
|
312
273
|
async transaction<R>(
|
|
313
|
-
callback: (tx:
|
|
274
|
+
callback: (tx: UniversalTransactionClient) => Promise<R>,
|
|
314
275
|
): Promise<R> {
|
|
315
276
|
return await this.drizzleService.transaction(callback);
|
|
316
277
|
}
|