@spfn/core 0.1.0-alpha.7 → 0.1.0-alpha.74
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 +168 -195
- package/dist/auto-loader-JFaZ9gON.d.ts +80 -0
- package/dist/cache/index.d.ts +211 -0
- package/dist/cache/index.js +992 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/client/index.d.ts +92 -92
- package/dist/client/index.js +80 -85
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/generators/index.d.ts +19 -0
- package/dist/codegen/generators/index.js +1500 -0
- package/dist/codegen/generators/index.js.map +1 -0
- package/dist/codegen/index.d.ts +76 -60
- package/dist/codegen/index.js +1486 -736
- package/dist/codegen/index.js.map +1 -1
- package/dist/database-errors-BNNmLTJE.d.ts +86 -0
- package/dist/db/index.d.ts +844 -44
- package/dist/db/index.js +1262 -1309
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +508 -0
- package/dist/env/index.js +1106 -0
- package/dist/env/index.js.map +1 -0
- package/dist/error-handler-wjLL3v-a.d.ts +44 -0
- package/dist/errors/index.d.ts +136 -0
- package/dist/errors/index.js +172 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index-DHiAqhKv.d.ts +101 -0
- package/dist/index.d.ts +3 -374
- package/dist/index.js +2394 -2176
- package/dist/index.js.map +1 -1
- package/dist/logger/index.d.ts +94 -0
- package/dist/logger/index.js +774 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/middleware/index.d.ts +33 -0
- package/dist/middleware/index.js +890 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/route/index.d.ts +21 -53
- package/dist/route/index.js +1234 -219
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.js +2390 -2058
- package/dist/server/index.js.map +1 -1
- package/dist/types-Dzggq1Yb.d.ts +170 -0
- package/package.json +59 -15
- package/dist/auto-loader-C44TcLmM.d.ts +0 -125
- package/dist/bind-pssq1NRT.d.ts +0 -34
- package/dist/postgres-errors-CY_Es8EJ.d.ts +0 -1703
- package/dist/scripts/index.d.ts +0 -24
- package/dist/scripts/index.js +0 -1201
- package/dist/scripts/index.js.map +0 -1
- package/dist/scripts/templates/api-index.template.txt +0 -10
- package/dist/scripts/templates/api-tag.template.txt +0 -11
- package/dist/scripts/templates/contract.template.txt +0 -87
- package/dist/scripts/templates/entity-type.template.txt +0 -31
- package/dist/scripts/templates/entity.template.txt +0 -19
- package/dist/scripts/templates/index.template.txt +0 -10
- package/dist/scripts/templates/repository.template.txt +0 -37
- package/dist/scripts/templates/routes-id.template.txt +0 -59
- package/dist/scripts/templates/routes-index.template.txt +0 -44
- package/dist/types-SlzTr8ZO.d.ts +0 -143
|
@@ -1,1703 +0,0 @@
|
|
|
1
|
-
import * as drizzle_orm_postgres_js from 'drizzle-orm/postgres-js';
|
|
2
|
-
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
3
|
-
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
4
|
-
import { PgTable, PgColumn } from 'drizzle-orm/pg-core';
|
|
5
|
-
import * as hono from 'hono';
|
|
6
|
-
import { MiddlewareHandler } from 'hono';
|
|
7
|
-
import * as postgres from 'postgres';
|
|
8
|
-
import { Sql } from 'postgres';
|
|
9
|
-
import * as drizzle_orm_pg_core_query_builders_raw from 'drizzle-orm/pg-core/query-builders/raw';
|
|
10
|
-
import * as drizzle_orm from 'drizzle-orm';
|
|
11
|
-
import { SQL } from 'drizzle-orm';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Database Configuration
|
|
15
|
-
*
|
|
16
|
-
* DB 연결 및 Connection Pool 설정
|
|
17
|
-
*
|
|
18
|
-
* ✅ 구현 완료:
|
|
19
|
-
* - 환경별 Connection Pool 설정
|
|
20
|
-
* - 재시도 설정 (Exponential Backoff)
|
|
21
|
-
* - 환경변수 기반 설정
|
|
22
|
-
*
|
|
23
|
-
* 🔗 관련 파일:
|
|
24
|
-
* - src/server/core/db/connection.ts (연결 로직)
|
|
25
|
-
* - src/server/core/db/index.ts (메인 export)
|
|
26
|
-
*/
|
|
27
|
-
/**
|
|
28
|
-
* Connection Pool 설정
|
|
29
|
-
*/
|
|
30
|
-
interface PoolConfig {
|
|
31
|
-
max: number;
|
|
32
|
-
idleTimeout: number;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* 재시도 설정
|
|
36
|
-
*/
|
|
37
|
-
interface RetryConfig {
|
|
38
|
-
maxRetries: number;
|
|
39
|
-
initialDelay: number;
|
|
40
|
-
maxDelay: number;
|
|
41
|
-
factor: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Database factory with automatic environment variable detection
|
|
46
|
-
* Supports: Single primary, Primary + Replica
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
interface DatabaseClients {
|
|
50
|
-
/** Primary database for writes (or both read/write if no replica) */
|
|
51
|
-
write?: PostgresJsDatabase;
|
|
52
|
-
/** Replica database for reads (optional, falls back to write) */
|
|
53
|
-
read?: PostgresJsDatabase;
|
|
54
|
-
/** Raw postgres client for write operations (for cleanup) */
|
|
55
|
-
writeClient?: Sql;
|
|
56
|
-
/** Raw postgres client for read operations (for cleanup) */
|
|
57
|
-
readClient?: Sql;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Health check configuration
|
|
61
|
-
*/
|
|
62
|
-
interface HealthCheckConfig {
|
|
63
|
-
enabled: boolean;
|
|
64
|
-
interval: number;
|
|
65
|
-
reconnect: boolean;
|
|
66
|
-
maxRetries: number;
|
|
67
|
-
retryInterval: number;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Query performance monitoring configuration
|
|
71
|
-
*/
|
|
72
|
-
interface MonitoringConfig {
|
|
73
|
-
enabled: boolean;
|
|
74
|
-
slowThreshold: number;
|
|
75
|
-
logQueries: boolean;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Database initialization options
|
|
79
|
-
*/
|
|
80
|
-
interface DatabaseOptions {
|
|
81
|
-
/**
|
|
82
|
-
* Connection pool configuration
|
|
83
|
-
* Overrides environment variables and defaults
|
|
84
|
-
*/
|
|
85
|
-
pool?: Partial<PoolConfig>;
|
|
86
|
-
/**
|
|
87
|
-
* Health check configuration
|
|
88
|
-
* Periodic checks to ensure database connection is alive
|
|
89
|
-
*/
|
|
90
|
-
healthCheck?: Partial<HealthCheckConfig>;
|
|
91
|
-
/**
|
|
92
|
-
* Query performance monitoring configuration
|
|
93
|
-
* Tracks slow queries and logs performance metrics
|
|
94
|
-
*/
|
|
95
|
-
monitoring?: Partial<MonitoringConfig>;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Create database client(s) from environment variables
|
|
99
|
-
*
|
|
100
|
-
* Supported patterns (priority order):
|
|
101
|
-
* 1. Single primary: DATABASE_URL
|
|
102
|
-
* 2. Primary + Replica: DATABASE_WRITE_URL + DATABASE_READ_URL
|
|
103
|
-
* 3. Legacy replica: DATABASE_URL + DATABASE_REPLICA_URL
|
|
104
|
-
*
|
|
105
|
-
* @param options - Optional database configuration (pool settings, etc.)
|
|
106
|
-
* @returns Database client(s) or undefined if no configuration found
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* ```bash
|
|
110
|
-
* # Single primary (most common)
|
|
111
|
-
* DATABASE_URL=postgresql://localhost:5432/mydb
|
|
112
|
-
*
|
|
113
|
-
* # Primary + Replica
|
|
114
|
-
* DATABASE_WRITE_URL=postgresql://primary:5432/mydb
|
|
115
|
-
* DATABASE_READ_URL=postgresql://replica:5432/mydb
|
|
116
|
-
*
|
|
117
|
-
* # Legacy (backward compatibility)
|
|
118
|
-
* DATABASE_URL=postgresql://primary:5432/mydb
|
|
119
|
-
* DATABASE_REPLICA_URL=postgresql://replica:5432/mydb
|
|
120
|
-
* ```
|
|
121
|
-
*
|
|
122
|
-
* @example
|
|
123
|
-
* ```typescript
|
|
124
|
-
* // Custom pool configuration
|
|
125
|
-
* const db = await createDatabaseFromEnv({
|
|
126
|
-
* pool: { max: 50, idleTimeout: 60 }
|
|
127
|
-
* });
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
130
|
-
declare function createDatabaseFromEnv(options?: DatabaseOptions): Promise<DatabaseClients>;
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Global Database instance manager
|
|
134
|
-
* Provides singleton access to database across all modules
|
|
135
|
-
* Supports Primary + Replica pattern with separate read/write instances
|
|
136
|
-
*/
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* DB connection type
|
|
140
|
-
*/
|
|
141
|
-
type DbConnectionType = 'read' | 'write';
|
|
142
|
-
/**
|
|
143
|
-
* Get global database write instance
|
|
144
|
-
*
|
|
145
|
-
* @returns Database write instance or undefined if not initialized
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```typescript
|
|
149
|
-
* import { getDatabase } from '@spfn/core/db';
|
|
150
|
-
*
|
|
151
|
-
* const db = getDatabase();
|
|
152
|
-
* if (db) {
|
|
153
|
-
* const users = await db.select().from(usersTable);
|
|
154
|
-
* }
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
declare function getDatabase(type?: DbConnectionType): PostgresJsDatabase | undefined;
|
|
158
|
-
/**
|
|
159
|
-
* Set global database instances (for testing or manual configuration)
|
|
160
|
-
*
|
|
161
|
-
* @param write - Database write instance
|
|
162
|
-
* @param read - Database read instance (optional, defaults to write)
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* ```typescript
|
|
166
|
-
* import { setDatabase } from '@spfn/core/db';
|
|
167
|
-
* import { drizzle } from 'drizzle-orm/postgres-js';
|
|
168
|
-
* import postgres from 'postgres';
|
|
169
|
-
*
|
|
170
|
-
* const writeClient = postgres('postgresql://primary:5432/mydb');
|
|
171
|
-
* const readClient = postgres('postgresql://replica:5432/mydb');
|
|
172
|
-
* setDatabase(drizzle(writeClient), drizzle(readClient));
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
declare function setDatabase(write: PostgresJsDatabase | undefined, read?: PostgresJsDatabase | undefined): void;
|
|
176
|
-
/**
|
|
177
|
-
* Initialize database from environment variables
|
|
178
|
-
* Automatically called by server startup
|
|
179
|
-
*
|
|
180
|
-
* Supported environment variables:
|
|
181
|
-
* - DATABASE_URL (single primary)
|
|
182
|
-
* - DATABASE_WRITE_URL + DATABASE_READ_URL (primary + replica)
|
|
183
|
-
* - DATABASE_URL + DATABASE_REPLICA_URL (legacy replica)
|
|
184
|
-
* - DB_POOL_MAX (connection pool max size)
|
|
185
|
-
* - DB_POOL_IDLE_TIMEOUT (connection idle timeout in seconds)
|
|
186
|
-
* - DB_HEALTH_CHECK_ENABLED (enable health checks, default: true)
|
|
187
|
-
* - DB_HEALTH_CHECK_INTERVAL (health check interval in ms, default: 60000)
|
|
188
|
-
* - DB_HEALTH_CHECK_RECONNECT (enable auto-reconnect, default: true)
|
|
189
|
-
* - DB_HEALTH_CHECK_MAX_RETRIES (max reconnection attempts, default: 3)
|
|
190
|
-
* - DB_HEALTH_CHECK_RETRY_INTERVAL (retry interval in ms, default: 5000)
|
|
191
|
-
* - DB_MONITORING_ENABLED (enable query monitoring, default: true in dev, false in prod)
|
|
192
|
-
* - DB_MONITORING_SLOW_THRESHOLD (slow query threshold in ms, default: 1000)
|
|
193
|
-
* - DB_MONITORING_LOG_QUERIES (log actual SQL queries, default: false)
|
|
194
|
-
*
|
|
195
|
-
* Configuration priority:
|
|
196
|
-
* 1. options parameter (ServerConfig)
|
|
197
|
-
* 2. Environment variables
|
|
198
|
-
* 3. Defaults (based on NODE_ENV)
|
|
199
|
-
*
|
|
200
|
-
* @param options - Optional database configuration (pool settings, etc.)
|
|
201
|
-
* @returns Object with write and read instances
|
|
202
|
-
*
|
|
203
|
-
* @example
|
|
204
|
-
* ```typescript
|
|
205
|
-
* import { initDatabase } from '@spfn/core/db';
|
|
206
|
-
*
|
|
207
|
-
* // Manual initialization (not needed if using server startup)
|
|
208
|
-
* const { write, read } = await initDatabase();
|
|
209
|
-
* if (write) {
|
|
210
|
-
* console.log('Database connected');
|
|
211
|
-
* }
|
|
212
|
-
* ```
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* ```typescript
|
|
216
|
-
* // Custom pool configuration
|
|
217
|
-
* const { write, read } = await initDatabase({
|
|
218
|
-
* pool: { max: 50, idleTimeout: 60 }
|
|
219
|
-
* });
|
|
220
|
-
* ```
|
|
221
|
-
*/
|
|
222
|
-
declare function initDatabase(options?: DatabaseOptions): Promise<{
|
|
223
|
-
write?: PostgresJsDatabase;
|
|
224
|
-
read?: PostgresJsDatabase;
|
|
225
|
-
}>;
|
|
226
|
-
/**
|
|
227
|
-
* Close all database connections and cleanup
|
|
228
|
-
*
|
|
229
|
-
* Properly closes postgres connection pools with timeout.
|
|
230
|
-
* Should be called during graceful shutdown or after tests.
|
|
231
|
-
*
|
|
232
|
-
* @example
|
|
233
|
-
* ```typescript
|
|
234
|
-
* import { closeDatabase } from '@spfn/core/db';
|
|
235
|
-
*
|
|
236
|
-
* // During graceful shutdown
|
|
237
|
-
* process.on('SIGTERM', async () => {
|
|
238
|
-
* await closeDatabase();
|
|
239
|
-
* process.exit(0);
|
|
240
|
-
* });
|
|
241
|
-
*
|
|
242
|
-
* // In tests
|
|
243
|
-
* afterAll(async () => {
|
|
244
|
-
* await closeDatabase();
|
|
245
|
-
* });
|
|
246
|
-
* ```
|
|
247
|
-
*/
|
|
248
|
-
declare function closeDatabase(): Promise<void>;
|
|
249
|
-
/**
|
|
250
|
-
* Get database connection info (for debugging)
|
|
251
|
-
*/
|
|
252
|
-
declare function getDatabaseInfo(): {
|
|
253
|
-
hasWrite: boolean;
|
|
254
|
-
hasRead: boolean;
|
|
255
|
-
isReplica: boolean;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Database Instance (Backward Compatibility Layer)
|
|
260
|
-
*
|
|
261
|
-
* PostgreSQL + Drizzle ORM connection - now using lazy initialization
|
|
262
|
-
*
|
|
263
|
-
* ✅ Implemented:
|
|
264
|
-
* - Lazy initialization (no top-level await)
|
|
265
|
-
* - Automatic environment variable loading
|
|
266
|
-
* - Read Replica support (read/write separation)
|
|
267
|
-
* - Singleton pattern via db-manager
|
|
268
|
-
* - Backward compatibility with existing code
|
|
269
|
-
*
|
|
270
|
-
* ⚠️ Migration Note:
|
|
271
|
-
* This file now wraps db-manager for backward compatibility.
|
|
272
|
-
* New code should use:
|
|
273
|
-
* - initDatabase() from db-manager
|
|
274
|
-
* - getDatabase() from db-manager
|
|
275
|
-
*
|
|
276
|
-
* 🔗 Related files:
|
|
277
|
-
* - src/db/db-factory.ts (Environment detection)
|
|
278
|
-
* - src/db/db-manager.ts (Singleton management)
|
|
279
|
-
* - src/db/db-context.ts (Transaction-aware access)
|
|
280
|
-
*/
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Default DB instance (Primary - for writes)
|
|
284
|
-
*
|
|
285
|
-
* ⚠️ IMPORTANT: This is a lazy getter. On first access, it will:
|
|
286
|
-
* 1. Auto-initialize database from environment variables
|
|
287
|
-
* 2. Throw error if DATABASE_URL is not set
|
|
288
|
-
*
|
|
289
|
-
* For better error handling, use initDatabase() explicitly:
|
|
290
|
-
* ```typescript
|
|
291
|
-
* import { initDatabase } from '@spfn/core/db';
|
|
292
|
-
* const { write } = await initDatabase();
|
|
293
|
-
* if (!write) throw new Error('Database not configured');
|
|
294
|
-
* ```
|
|
295
|
-
*
|
|
296
|
-
* @example
|
|
297
|
-
* ```typescript
|
|
298
|
-
* import { db } from '@spfn/core/db';
|
|
299
|
-
* const users = await db.select().from(usersTable);
|
|
300
|
-
* ```
|
|
301
|
-
*/
|
|
302
|
-
declare const db: PostgresJsDatabase<Record<string, never>>;
|
|
303
|
-
/**
|
|
304
|
-
* Get raw Drizzle DB instance (direct use without transaction)
|
|
305
|
-
*
|
|
306
|
-
* ⚠️ Warning: This function bypasses AsyncLocalStorage transaction context.
|
|
307
|
-
* For normal cases, use `getDb()` from './db-context.js'.
|
|
308
|
-
*
|
|
309
|
-
* @param type - 'read' (Replica) or 'write' (Primary)
|
|
310
|
-
* @returns Raw Drizzle DB instance
|
|
311
|
-
*
|
|
312
|
-
* @example
|
|
313
|
-
* ```typescript
|
|
314
|
-
* // Read-only query (uses Replica)
|
|
315
|
-
* const users = await getRawDb('read').select().from(usersTable);
|
|
316
|
-
*
|
|
317
|
-
* // Write query (uses Primary)
|
|
318
|
-
* await getRawDb('write').insert(usersTable).values({ email: 'test@example.com' });
|
|
319
|
-
* ```
|
|
320
|
-
*/
|
|
321
|
-
declare function getRawDb(type?: DbConnectionType): PostgresJsDatabase;
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Repository Filter Utilities
|
|
325
|
-
*
|
|
326
|
-
* Utilities for building Drizzle ORM WHERE, ORDER BY, and pagination conditions.
|
|
327
|
-
* Moved from deprecated query module for Repository pattern usage.
|
|
328
|
-
*
|
|
329
|
-
* @module db/repository/filters
|
|
330
|
-
*/
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Filter operators
|
|
334
|
-
*/
|
|
335
|
-
type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'in' | 'nin' | 'is';
|
|
336
|
-
/**
|
|
337
|
-
* Filter value type
|
|
338
|
-
*/
|
|
339
|
-
type FilterValue = string | number | boolean | null | (string | number)[];
|
|
340
|
-
/**
|
|
341
|
-
* Filter condition
|
|
342
|
-
*
|
|
343
|
-
* @example { email: { eq: 'john@example.com' } }
|
|
344
|
-
* @example { age: { gte: 18, lte: 65 } }
|
|
345
|
-
*/
|
|
346
|
-
type FilterCondition = {
|
|
347
|
-
[operator in FilterOperator]?: FilterValue;
|
|
348
|
-
};
|
|
349
|
-
/**
|
|
350
|
-
* Complete filters
|
|
351
|
-
*
|
|
352
|
-
* @example { email: { eq: 'john@example.com' }, role: { in: ['admin', 'user'] } }
|
|
353
|
-
*/
|
|
354
|
-
type Filters = {
|
|
355
|
-
[field: string]: FilterCondition;
|
|
356
|
-
};
|
|
357
|
-
/**
|
|
358
|
-
* Filter builder result type
|
|
359
|
-
*/
|
|
360
|
-
type FilterResult = SQL<unknown> | undefined;
|
|
361
|
-
/**
|
|
362
|
-
* Sort direction
|
|
363
|
-
*/
|
|
364
|
-
type SortDirection = 'asc' | 'desc';
|
|
365
|
-
/**
|
|
366
|
-
* Sort condition
|
|
367
|
-
*
|
|
368
|
-
* @example [{ field: 'createdAt', direction: 'desc' }, { field: 'name', direction: 'asc' }]
|
|
369
|
-
*/
|
|
370
|
-
type SortCondition = {
|
|
371
|
-
field: string;
|
|
372
|
-
direction: SortDirection;
|
|
373
|
-
};
|
|
374
|
-
/**
|
|
375
|
-
* Sort builder result type
|
|
376
|
-
*/
|
|
377
|
-
type SortResult = SQL<unknown>[];
|
|
378
|
-
/**
|
|
379
|
-
* Pagination parameters
|
|
380
|
-
*/
|
|
381
|
-
type PaginationParams = {
|
|
382
|
-
page: number;
|
|
383
|
-
limit: number;
|
|
384
|
-
};
|
|
385
|
-
/**
|
|
386
|
-
* Pagination metadata
|
|
387
|
-
*/
|
|
388
|
-
type PaginationMeta = {
|
|
389
|
-
page: number;
|
|
390
|
-
limit: number;
|
|
391
|
-
total: number;
|
|
392
|
-
totalPages: number;
|
|
393
|
-
hasNext: boolean;
|
|
394
|
-
hasPrev: boolean;
|
|
395
|
-
};
|
|
396
|
-
/**
|
|
397
|
-
* Drizzle table type (generic)
|
|
398
|
-
*/
|
|
399
|
-
type DrizzleTable = PgTable<any> & Record<string, PgColumn>;
|
|
400
|
-
/**
|
|
401
|
-
* Convert filter conditions to Drizzle SQL WHERE conditions
|
|
402
|
-
*
|
|
403
|
-
* @param filters - Parsed filter object
|
|
404
|
-
* @param table - Drizzle table schema
|
|
405
|
-
* @returns SQL WHERE condition (undefined if no filters)
|
|
406
|
-
*
|
|
407
|
-
* @example
|
|
408
|
-
* const filters = { email: { eq: 'john@example.com' }, age: { gte: 18 } };
|
|
409
|
-
* const condition = buildFilters(filters, users);
|
|
410
|
-
* const data = await db.select().from(users).where(condition);
|
|
411
|
-
*/
|
|
412
|
-
declare function buildFilters(filters: Filters, table: DrizzleTable): FilterResult;
|
|
413
|
-
/**
|
|
414
|
-
* Combine conditions with OR
|
|
415
|
-
*
|
|
416
|
-
* @example
|
|
417
|
-
* const conditions = [
|
|
418
|
-
* buildFilters({ status: { eq: 'active' } }, users),
|
|
419
|
-
* buildFilters({ role: { eq: 'admin' } }, users)
|
|
420
|
-
* ];
|
|
421
|
-
* const orCondition = orFilters(...conditions);
|
|
422
|
-
*/
|
|
423
|
-
declare function orFilters(...conditions: (FilterResult)[]): FilterResult;
|
|
424
|
-
/**
|
|
425
|
-
* Convert sort conditions to Drizzle SQL ORDER BY conditions
|
|
426
|
-
*
|
|
427
|
-
* @param sortConditions - Sort condition array
|
|
428
|
-
* @param table - Drizzle table schema
|
|
429
|
-
* @returns SQL ORDER BY condition array
|
|
430
|
-
*
|
|
431
|
-
* @example
|
|
432
|
-
* const sort = [
|
|
433
|
-
* { field: 'createdAt', direction: 'desc' },
|
|
434
|
-
* { field: 'name', direction: 'asc' }
|
|
435
|
-
* ];
|
|
436
|
-
* const orderBy = buildSort(sort, users);
|
|
437
|
-
* const data = await db.select().from(users).orderBy(...orderBy);
|
|
438
|
-
*/
|
|
439
|
-
declare function buildSort(sortConditions: SortCondition[], table: DrizzleTable): SortResult;
|
|
440
|
-
/**
|
|
441
|
-
* Apply pagination to Drizzle query
|
|
442
|
-
*
|
|
443
|
-
* @param pagination - Pagination parameters
|
|
444
|
-
* @returns { offset, limit } object
|
|
445
|
-
*
|
|
446
|
-
* @example
|
|
447
|
-
* const { offset, limit } = applyPagination({ page: 2, limit: 20 });
|
|
448
|
-
* const data = await db.select().from(users).limit(limit).offset(offset);
|
|
449
|
-
*/
|
|
450
|
-
declare function applyPagination(pagination: PaginationParams): {
|
|
451
|
-
offset: number;
|
|
452
|
-
limit: number;
|
|
453
|
-
};
|
|
454
|
-
/**
|
|
455
|
-
* Create pagination metadata
|
|
456
|
-
*
|
|
457
|
-
* @param pagination - Pagination parameters
|
|
458
|
-
* @param total - Total count
|
|
459
|
-
* @returns Pagination metadata
|
|
460
|
-
*
|
|
461
|
-
* @example
|
|
462
|
-
* const meta = createPaginationMeta({ page: 2, limit: 20 }, 156);
|
|
463
|
-
* // { page: 2, limit: 20, total: 156, totalPages: 8, hasNext: true, hasPrev: true }
|
|
464
|
-
*/
|
|
465
|
-
declare function createPaginationMeta(pagination: PaginationParams, total: number): PaginationMeta;
|
|
466
|
-
/**
|
|
467
|
-
* Count total records (count query)
|
|
468
|
-
*
|
|
469
|
-
* @param db - Drizzle DB instance
|
|
470
|
-
* @param table - Table schema
|
|
471
|
-
* @param whereCondition - WHERE condition (optional)
|
|
472
|
-
* @returns Total count
|
|
473
|
-
*
|
|
474
|
-
* @example
|
|
475
|
-
* const total = await countTotal(db, users);
|
|
476
|
-
* const total = await countTotal(db, users, eq(users.status, 'active'));
|
|
477
|
-
*/
|
|
478
|
-
declare function countTotal(db: PostgresJsDatabase<Record<string, never>>, table: DrizzleTable, whereCondition?: any): Promise<number>;
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Query Builder (Fluent Interface)
|
|
482
|
-
*
|
|
483
|
-
* Chainable query builder for Repository pattern.
|
|
484
|
-
* Provides a fluent API for building complex queries.
|
|
485
|
-
*
|
|
486
|
-
* @example
|
|
487
|
-
* ```typescript
|
|
488
|
-
* const users = await userRepo
|
|
489
|
-
* .query()
|
|
490
|
-
* .where({ status: 'active' })
|
|
491
|
-
* .where({ role: 'admin' })
|
|
492
|
-
* .orderBy('createdAt', 'desc')
|
|
493
|
-
* .limit(10)
|
|
494
|
-
* .findMany();
|
|
495
|
-
* ```
|
|
496
|
-
*/
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Query Builder class for chainable queries
|
|
500
|
-
*
|
|
501
|
-
* Supports method chaining for building complex queries in a fluent style.
|
|
502
|
-
*/
|
|
503
|
-
declare class QueryBuilder<TTable extends PgTable, TSelect = TTable['$inferSelect']> {
|
|
504
|
-
private db;
|
|
505
|
-
private table;
|
|
506
|
-
private filterConditions;
|
|
507
|
-
private sortConditions;
|
|
508
|
-
private limitValue?;
|
|
509
|
-
private offsetValue?;
|
|
510
|
-
constructor(db: PostgresJsDatabase<any>, table: TTable);
|
|
511
|
-
/**
|
|
512
|
-
* Add WHERE conditions
|
|
513
|
-
*
|
|
514
|
-
* Multiple where() calls are combined with AND logic.
|
|
515
|
-
*
|
|
516
|
-
* @param filters - Filter conditions
|
|
517
|
-
* @returns QueryBuilder for chaining
|
|
518
|
-
*
|
|
519
|
-
* @example
|
|
520
|
-
* ```typescript
|
|
521
|
-
* query
|
|
522
|
-
* .where({ status: 'active' })
|
|
523
|
-
* .where({ role: 'admin' }) // AND condition
|
|
524
|
-
* ```
|
|
525
|
-
*/
|
|
526
|
-
where(filters: Filters): this;
|
|
527
|
-
/**
|
|
528
|
-
* Add ORDER BY clause
|
|
529
|
-
*
|
|
530
|
-
* Multiple orderBy() calls create multi-column sorting.
|
|
531
|
-
*
|
|
532
|
-
* @param field - Field name to sort by
|
|
533
|
-
* @param direction - Sort direction ('asc' or 'desc')
|
|
534
|
-
* @returns QueryBuilder for chaining
|
|
535
|
-
*
|
|
536
|
-
* @example
|
|
537
|
-
* ```typescript
|
|
538
|
-
* query
|
|
539
|
-
* .orderBy('isPremium', 'desc')
|
|
540
|
-
* .orderBy('createdAt', 'desc')
|
|
541
|
-
* ```
|
|
542
|
-
*/
|
|
543
|
-
orderBy(field: string, direction?: 'asc' | 'desc'): this;
|
|
544
|
-
/**
|
|
545
|
-
* Set LIMIT clause
|
|
546
|
-
*
|
|
547
|
-
* @param limit - Maximum number of records to return
|
|
548
|
-
* @returns QueryBuilder for chaining
|
|
549
|
-
*
|
|
550
|
-
* @example
|
|
551
|
-
* ```typescript
|
|
552
|
-
* query.limit(10)
|
|
553
|
-
* ```
|
|
554
|
-
*/
|
|
555
|
-
limit(limit: number): this;
|
|
556
|
-
/**
|
|
557
|
-
* Set OFFSET clause
|
|
558
|
-
*
|
|
559
|
-
* @param offset - Number of records to skip
|
|
560
|
-
* @returns QueryBuilder for chaining
|
|
561
|
-
*
|
|
562
|
-
* @example
|
|
563
|
-
* ```typescript
|
|
564
|
-
* query.offset(20)
|
|
565
|
-
* ```
|
|
566
|
-
*/
|
|
567
|
-
offset(offset: number): this;
|
|
568
|
-
/**
|
|
569
|
-
* Execute query and return multiple records
|
|
570
|
-
*
|
|
571
|
-
* @returns Array of records
|
|
572
|
-
*
|
|
573
|
-
* @example
|
|
574
|
-
* ```typescript
|
|
575
|
-
* const users = await query
|
|
576
|
-
* .where({ status: 'active' })
|
|
577
|
-
* .orderBy('createdAt', 'desc')
|
|
578
|
-
* .limit(10)
|
|
579
|
-
* .findMany();
|
|
580
|
-
* ```
|
|
581
|
-
*/
|
|
582
|
-
findMany(): Promise<TSelect[]>;
|
|
583
|
-
/**
|
|
584
|
-
* Execute query and return first record
|
|
585
|
-
*
|
|
586
|
-
* @returns First matching record or null
|
|
587
|
-
*
|
|
588
|
-
* @example
|
|
589
|
-
* ```typescript
|
|
590
|
-
* const user = await query
|
|
591
|
-
* .where({ email: 'john@example.com' })
|
|
592
|
-
* .findOne();
|
|
593
|
-
* ```
|
|
594
|
-
*/
|
|
595
|
-
findOne(): Promise<TSelect | null>;
|
|
596
|
-
/**
|
|
597
|
-
* Execute query and return count
|
|
598
|
-
*
|
|
599
|
-
* @returns Number of matching records
|
|
600
|
-
*
|
|
601
|
-
* @example
|
|
602
|
-
* ```typescript
|
|
603
|
-
* const count = await query
|
|
604
|
-
* .where({ status: 'active' })
|
|
605
|
-
* .count();
|
|
606
|
-
* ```
|
|
607
|
-
*/
|
|
608
|
-
count(): Promise<number>;
|
|
609
|
-
/**
|
|
610
|
-
* Merge multiple filter conditions into single object
|
|
611
|
-
*
|
|
612
|
-
* Combines all where() calls into one filter object.
|
|
613
|
-
*/
|
|
614
|
-
private mergeFilters;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Repository Pattern (JPA Style)
|
|
619
|
-
*
|
|
620
|
-
* Spring JPA-inspired Repository pattern for TypeScript/Drizzle ORM
|
|
621
|
-
*
|
|
622
|
-
* ✅ Features:
|
|
623
|
-
* - Auto Read/Write Replica routing (read methods use Replica, write methods use Primary)
|
|
624
|
-
* - Type-safe CRUD operations (findById, findWhere, save, update, delete, etc.)
|
|
625
|
-
* - Advanced filtering with operators (eq, gt, like, in, etc.)
|
|
626
|
-
* - Pagination with metadata (findPage)
|
|
627
|
-
* - Batch operations (saveMany, updateWhere, deleteWhere)
|
|
628
|
-
* - Transaction-aware (automatic participation in Transactional middleware)
|
|
629
|
-
*/
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Pageable interface (Spring Pageable style)
|
|
633
|
-
*/
|
|
634
|
-
type Pageable = {
|
|
635
|
-
filters?: Filters;
|
|
636
|
-
sort?: SortCondition[];
|
|
637
|
-
pagination?: PaginationParams;
|
|
638
|
-
};
|
|
639
|
-
/**
|
|
640
|
-
* Page result (Spring Page style)
|
|
641
|
-
*/
|
|
642
|
-
type Page<T> = {
|
|
643
|
-
data: T[];
|
|
644
|
-
meta: PaginationMeta;
|
|
645
|
-
};
|
|
646
|
-
/**
|
|
647
|
-
* Repository class
|
|
648
|
-
*
|
|
649
|
-
* Provides JPA Repository-style CRUD methods with automatic transaction support
|
|
650
|
-
*
|
|
651
|
-
* ✅ Automatic Transaction Detection:
|
|
652
|
-
* - Automatically participates in active Transactional() middleware context
|
|
653
|
-
* - No need to pass transaction explicitly - uses AsyncLocalStorage
|
|
654
|
-
* - All operations within a transaction use the same transaction DB
|
|
655
|
-
*
|
|
656
|
-
* ✅ Auto Read/Write Replica routing (when NOT in transaction):
|
|
657
|
-
* - Read methods (findAll, findById, findOne, findPage, count) → Uses Read Replica
|
|
658
|
-
* - Write methods (save, update, delete) → Uses Primary DB
|
|
659
|
-
*
|
|
660
|
-
* ✅ DB Priority:
|
|
661
|
-
* 1. Explicit DB (if provided in constructor)
|
|
662
|
-
* 2. Transaction context (if inside Transactional middleware)
|
|
663
|
-
* 3. Read Replica or Primary DB (based on operation type)
|
|
664
|
-
*
|
|
665
|
-
* @example
|
|
666
|
-
* ```typescript
|
|
667
|
-
* // Simple usage - automatically detects transaction
|
|
668
|
-
* class UserService {
|
|
669
|
-
* private get repo() {
|
|
670
|
-
* return new Repository(users); // Auto-detects transaction
|
|
671
|
-
* }
|
|
672
|
-
* }
|
|
673
|
-
*
|
|
674
|
-
* // Route with transaction
|
|
675
|
-
* app.bind(contract, Transactional(), async (c) => {
|
|
676
|
-
* const service = new UserService();
|
|
677
|
-
* await service.createUser(data);
|
|
678
|
-
* // Automatic rollback on error!
|
|
679
|
-
* });
|
|
680
|
-
* ```
|
|
681
|
-
*/
|
|
682
|
-
declare class Repository<TTable extends PgTable, TSelect = TTable['$inferSelect']> {
|
|
683
|
-
protected db: PostgresJsDatabase<any>;
|
|
684
|
-
protected table: TTable;
|
|
685
|
-
protected useReplica: boolean;
|
|
686
|
-
protected explicitDb?: PostgresJsDatabase<any>;
|
|
687
|
-
protected autoUpdateField?: string;
|
|
688
|
-
constructor(dbOrTable: PostgresJsDatabase<any> | TTable, tableOrUseReplica?: TTable | boolean, useReplica?: boolean);
|
|
689
|
-
/**
|
|
690
|
-
* Detect which field (if any) should be auto-updated
|
|
691
|
-
*
|
|
692
|
-
* Checks all table columns for __autoUpdate metadata flag.
|
|
693
|
-
* Set by autoUpdateTimestamp() or timestamps({ autoUpdate: true }) helpers.
|
|
694
|
-
*
|
|
695
|
-
* @returns Field name to auto-update, or undefined if none found
|
|
696
|
-
*/
|
|
697
|
-
private detectAutoUpdateField;
|
|
698
|
-
/**
|
|
699
|
-
* Inject auto-update timestamp if configured
|
|
700
|
-
*
|
|
701
|
-
* Only injects if:
|
|
702
|
-
* 1. Table has an auto-update field configured (via autoUpdateTimestamp() or timestamps({ autoUpdate: true }))
|
|
703
|
-
* 2. The field is not already explicitly provided in the data
|
|
704
|
-
*
|
|
705
|
-
* @param data - Update data object
|
|
706
|
-
* @returns Data with auto-update timestamp injected (if applicable)
|
|
707
|
-
*/
|
|
708
|
-
private injectAutoUpdateTimestamp;
|
|
709
|
-
/**
|
|
710
|
-
* Get id column from table
|
|
711
|
-
*
|
|
712
|
-
* Helper method to reduce code duplication across methods that need id column.
|
|
713
|
-
*
|
|
714
|
-
* @returns The id column object
|
|
715
|
-
* @throws {QueryError} If table does not have an id column
|
|
716
|
-
*/
|
|
717
|
-
private getIdColumn;
|
|
718
|
-
/**
|
|
719
|
-
* Get read-only DB
|
|
720
|
-
*
|
|
721
|
-
* Automatically detects and uses transaction context if available.
|
|
722
|
-
* When in transaction, uses transaction DB to ensure read consistency.
|
|
723
|
-
* Priority: explicitDb > transaction > replica/primary DB
|
|
724
|
-
*/
|
|
725
|
-
private getReadDb;
|
|
726
|
-
/**
|
|
727
|
-
* Get write-only DB
|
|
728
|
-
*
|
|
729
|
-
* Automatically detects and uses transaction context if available.
|
|
730
|
-
* Priority: explicitDb > transaction > primary DB
|
|
731
|
-
*/
|
|
732
|
-
private getWriteDb;
|
|
733
|
-
/**
|
|
734
|
-
* Execute operation with performance monitoring
|
|
735
|
-
*
|
|
736
|
-
* Wraps database operations with timing and logging for slow queries.
|
|
737
|
-
* Only logs if monitoring is enabled and query exceeds threshold.
|
|
738
|
-
*
|
|
739
|
-
* @param operation - Name of the operation (for logging)
|
|
740
|
-
* @param fn - Async function to execute
|
|
741
|
-
* @returns Result of the operation
|
|
742
|
-
*/
|
|
743
|
-
private executeWithMonitoring;
|
|
744
|
-
/**
|
|
745
|
-
* Find all records (uses Replica)
|
|
746
|
-
*
|
|
747
|
-
* @example
|
|
748
|
-
* const users = await userRepo.findAll();
|
|
749
|
-
*/
|
|
750
|
-
findAll(): Promise<TSelect[]>;
|
|
751
|
-
/**
|
|
752
|
-
* Find with pagination (uses Replica)
|
|
753
|
-
*
|
|
754
|
-
* @example
|
|
755
|
-
* const result = await userRepo.findPage({
|
|
756
|
-
* filters: { email: { like: 'john' } },
|
|
757
|
-
* sort: [{ field: 'createdAt', direction: 'desc' }],
|
|
758
|
-
* pagination: { page: 1, limit: 20 }
|
|
759
|
-
* });
|
|
760
|
-
*/
|
|
761
|
-
findPage(pageable: Pageable): Promise<Page<TSelect>>;
|
|
762
|
-
/**
|
|
763
|
-
* Find one record by ID (uses Replica)
|
|
764
|
-
*
|
|
765
|
-
* @example
|
|
766
|
-
* const user = await userRepo.findById(1);
|
|
767
|
-
*/
|
|
768
|
-
findById(id: number | string): Promise<TSelect | null>;
|
|
769
|
-
/**
|
|
770
|
-
* Find one record by condition (uses Replica)
|
|
771
|
-
*
|
|
772
|
-
* @example
|
|
773
|
-
* const user = await userRepo.findOne(eq(users.email, 'john@example.com'));
|
|
774
|
-
*/
|
|
775
|
-
findOne(where: SQL<unknown>): Promise<TSelect | null>;
|
|
776
|
-
/**
|
|
777
|
-
* Create a new record (uses Primary)
|
|
778
|
-
*
|
|
779
|
-
* @example
|
|
780
|
-
* const user = await userRepo.save({ email: 'john@example.com', name: 'John' });
|
|
781
|
-
*/
|
|
782
|
-
save(data: any): Promise<TSelect>;
|
|
783
|
-
/**
|
|
784
|
-
* Update a record (uses Primary)
|
|
785
|
-
*
|
|
786
|
-
* Automatically injects current timestamp if table has auto-update field configured.
|
|
787
|
-
*
|
|
788
|
-
* @example
|
|
789
|
-
* const user = await userRepo.update(1, { name: 'Jane' });
|
|
790
|
-
*/
|
|
791
|
-
update(id: number | string, data: any): Promise<TSelect | null>;
|
|
792
|
-
/**
|
|
793
|
-
* Delete a record (uses Primary)
|
|
794
|
-
*
|
|
795
|
-
* @example
|
|
796
|
-
* const deleted = await userRepo.delete(1);
|
|
797
|
-
*/
|
|
798
|
-
delete(id: number | string): Promise<TSelect | null>;
|
|
799
|
-
/**
|
|
800
|
-
* Count records (uses Replica)
|
|
801
|
-
*
|
|
802
|
-
* @example
|
|
803
|
-
* const count = await userRepo.count();
|
|
804
|
-
*/
|
|
805
|
-
count(where?: SQL<unknown>): Promise<number>;
|
|
806
|
-
/**
|
|
807
|
-
* Find records by filters (uses Replica)
|
|
808
|
-
*
|
|
809
|
-
* @example
|
|
810
|
-
* const users = await userRepo.findWhere({ email: { like: '@gmail.com' }, status: 'active' });
|
|
811
|
-
*/
|
|
812
|
-
findWhere(filters: Filters): Promise<TSelect[]>;
|
|
813
|
-
/**
|
|
814
|
-
* Find one record by filters (uses Replica)
|
|
815
|
-
*
|
|
816
|
-
* @example
|
|
817
|
-
* const user = await userRepo.findOneWhere({ email: 'john@example.com' });
|
|
818
|
-
*/
|
|
819
|
-
findOneWhere(filters: Filters): Promise<TSelect | null>;
|
|
820
|
-
/**
|
|
821
|
-
* Check if record exists by ID (uses Replica)
|
|
822
|
-
*
|
|
823
|
-
* @example
|
|
824
|
-
* const exists = await userRepo.exists(1);
|
|
825
|
-
*/
|
|
826
|
-
exists(id: number | string): Promise<boolean>;
|
|
827
|
-
/**
|
|
828
|
-
* Check if record exists by filters (uses Replica)
|
|
829
|
-
*
|
|
830
|
-
* @example
|
|
831
|
-
* const exists = await userRepo.existsBy({ email: 'john@example.com' });
|
|
832
|
-
*/
|
|
833
|
-
existsBy(filters: Filters): Promise<boolean>;
|
|
834
|
-
/**
|
|
835
|
-
* Count records by filters (uses Replica)
|
|
836
|
-
*
|
|
837
|
-
* @example
|
|
838
|
-
* const count = await userRepo.countBy({ status: 'active' });
|
|
839
|
-
*/
|
|
840
|
-
countBy(filters: Filters): Promise<number>;
|
|
841
|
-
/**
|
|
842
|
-
* Create multiple records (uses Primary)
|
|
843
|
-
*
|
|
844
|
-
* @example
|
|
845
|
-
* const users = await userRepo.saveMany([
|
|
846
|
-
* { email: 'user1@example.com', name: 'User 1' },
|
|
847
|
-
* { email: 'user2@example.com', name: 'User 2' }
|
|
848
|
-
* ]);
|
|
849
|
-
*/
|
|
850
|
-
saveMany(data: any[]): Promise<TSelect[]>;
|
|
851
|
-
/**
|
|
852
|
-
* Update multiple records by filters (uses Primary)
|
|
853
|
-
*
|
|
854
|
-
* Automatically injects current timestamp if table has auto-update field configured.
|
|
855
|
-
*
|
|
856
|
-
* @example
|
|
857
|
-
* const count = await userRepo.updateWhere({ status: 'inactive' }, { status: 'archived' });
|
|
858
|
-
*/
|
|
859
|
-
updateWhere(filters: Filters, data: any): Promise<number>;
|
|
860
|
-
/**
|
|
861
|
-
* Delete multiple records by filters (uses Primary)
|
|
862
|
-
*
|
|
863
|
-
* @example
|
|
864
|
-
* const count = await userRepo.deleteWhere({ status: 'banned' });
|
|
865
|
-
*/
|
|
866
|
-
deleteWhere(filters: Filters): Promise<number>;
|
|
867
|
-
/**
|
|
868
|
-
* Start a chainable query builder (uses Replica)
|
|
869
|
-
*
|
|
870
|
-
* Returns a QueryBuilder instance for building complex queries with method chaining.
|
|
871
|
-
*
|
|
872
|
-
* @returns QueryBuilder instance for chaining
|
|
873
|
-
*
|
|
874
|
-
* @example
|
|
875
|
-
* ```typescript
|
|
876
|
-
* // Simple chaining
|
|
877
|
-
* const users = await userRepo
|
|
878
|
-
* .query()
|
|
879
|
-
* .where({ status: 'active' })
|
|
880
|
-
* .orderBy('createdAt', 'desc')
|
|
881
|
-
* .limit(10)
|
|
882
|
-
* .findMany();
|
|
883
|
-
*
|
|
884
|
-
* // Multiple conditions
|
|
885
|
-
* const admins = await userRepo
|
|
886
|
-
* .query()
|
|
887
|
-
* .where({ role: 'admin' })
|
|
888
|
-
* .where({ status: 'active' }) // AND condition
|
|
889
|
-
* .findMany();
|
|
890
|
-
*
|
|
891
|
-
* // Reusable query
|
|
892
|
-
* const activeQuery = userRepo.query().where({ status: 'active' });
|
|
893
|
-
* const users = await activeQuery.findMany();
|
|
894
|
-
* const count = await activeQuery.count();
|
|
895
|
-
* ```
|
|
896
|
-
*/
|
|
897
|
-
query(): QueryBuilder<TTable, TSelect>;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* Repository Factory
|
|
902
|
-
*
|
|
903
|
-
* Provides singleton instances of Repository classes to prevent unnecessary instantiation
|
|
904
|
-
* and ensure consistent instances across the application.
|
|
905
|
-
*
|
|
906
|
-
* ✅ Features:
|
|
907
|
-
* - Singleton pattern for both base and custom Repository classes
|
|
908
|
-
* - Automatic caching based on table + constructor
|
|
909
|
-
* - Type-safe with full IDE autocomplete
|
|
910
|
-
* - Supports multiple services accessing the same repository
|
|
911
|
-
* - Memory efficient (single instance per table/class combination)
|
|
912
|
-
*
|
|
913
|
-
* @example
|
|
914
|
-
* ```typescript
|
|
915
|
-
* // Base Repository
|
|
916
|
-
* const userRepo = getRepository(users);
|
|
917
|
-
* await userRepo.findById(1);
|
|
918
|
-
*
|
|
919
|
-
* // Custom Repository
|
|
920
|
-
* class UserRepository extends Repository<typeof users> {
|
|
921
|
-
* async findByEmail(email: string) {
|
|
922
|
-
* return this.findOneWhere({ email });
|
|
923
|
-
* }
|
|
924
|
-
* }
|
|
925
|
-
*
|
|
926
|
-
* const userRepo = getRepository(users, UserRepository);
|
|
927
|
-
* await userRepo.findByEmail('john@example.com');
|
|
928
|
-
* ```
|
|
929
|
-
*/
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* Get or create a Repository singleton instance (Global Singleton Pattern)
|
|
933
|
-
*
|
|
934
|
-
* This function ensures that only one instance of each Repository is created,
|
|
935
|
-
* preventing memory waste and ensuring consistency across the application.
|
|
936
|
-
*
|
|
937
|
-
* ✅ Supports both base Repository and custom Repository classes
|
|
938
|
-
* ✅ Returns the same instance on subsequent calls
|
|
939
|
-
* ✅ Type-safe with full IDE autocomplete
|
|
940
|
-
* ✅ Automatically detects transaction context (via Repository internals)
|
|
941
|
-
*
|
|
942
|
-
* ## ⚠️ Note: Global Singleton Pattern
|
|
943
|
-
*
|
|
944
|
-
* This uses a **global singleton cache** that persists throughout application lifecycle.
|
|
945
|
-
*
|
|
946
|
-
* **Tradeoffs:**
|
|
947
|
-
* - ✅ Simple API, no middleware required
|
|
948
|
-
* - ✅ Maximum memory efficiency
|
|
949
|
-
* - ⚠️ Requires manual `clearRepositoryCache()` in tests
|
|
950
|
-
* - ⚠️ Global state (harder to isolate in testing)
|
|
951
|
-
*
|
|
952
|
-
* **For better test isolation**, consider using **request-scoped repositories**:
|
|
953
|
-
* ```typescript
|
|
954
|
-
* import { getScopedRepository, RepositoryScope } from '@spfn/core/db';
|
|
955
|
-
*
|
|
956
|
-
* // Add middleware (once)
|
|
957
|
-
* app.use(RepositoryScope());
|
|
958
|
-
*
|
|
959
|
-
* // Use getScopedRepository() instead - automatic per-request caching
|
|
960
|
-
* const repo = getScopedRepository(users);
|
|
961
|
-
* ```
|
|
962
|
-
*
|
|
963
|
-
* See: `request-scope.ts` for request-scoped alternative
|
|
964
|
-
*
|
|
965
|
-
* ## 🔄 Transaction Handling
|
|
966
|
-
*
|
|
967
|
-
* Repository instances are cached globally, but they automatically detect
|
|
968
|
-
* and use transaction context via AsyncLocalStorage in each method call.
|
|
969
|
-
* This means:
|
|
970
|
-
* - **Same repository instance** can be used both inside and outside transactions
|
|
971
|
-
* - **No need to create separate repository instances** per transaction
|
|
972
|
-
* - **Transaction safety is guaranteed** by AsyncLocalStorage context
|
|
973
|
-
*
|
|
974
|
-
* The Repository internally calls `getTransaction()` on every database operation,
|
|
975
|
-
* ensuring the correct DB instance (transaction or default) is always used.
|
|
976
|
-
*
|
|
977
|
-
* @param table - Drizzle table definition
|
|
978
|
-
* @param RepositoryClass - Optional custom Repository class extending Repository
|
|
979
|
-
* @returns Repository instance (cached singleton)
|
|
980
|
-
*
|
|
981
|
-
* @example
|
|
982
|
-
* ```typescript
|
|
983
|
-
* // Base Repository - simple CRUD
|
|
984
|
-
* import { getRepository } from '@spfn/core/db';
|
|
985
|
-
* import { users } from './entities';
|
|
986
|
-
*
|
|
987
|
-
* export async function getUser(id: number) {
|
|
988
|
-
* const repo = getRepository(users);
|
|
989
|
-
* return repo.findById(id);
|
|
990
|
-
* }
|
|
991
|
-
* ```
|
|
992
|
-
*
|
|
993
|
-
* @example
|
|
994
|
-
* ```typescript
|
|
995
|
-
* // Custom Repository - with custom methods
|
|
996
|
-
* import { Repository, getRepository } from '@spfn/core/db';
|
|
997
|
-
* import { users } from './entities';
|
|
998
|
-
*
|
|
999
|
-
* class UserRepository extends Repository<typeof users> {
|
|
1000
|
-
* async findByEmail(email: string) {
|
|
1001
|
-
* return this.findOneWhere({ email });
|
|
1002
|
-
* }
|
|
1003
|
-
*
|
|
1004
|
-
* async findActiveUsers() {
|
|
1005
|
-
* return this.findWhere({ status: 'active' });
|
|
1006
|
-
* }
|
|
1007
|
-
* }
|
|
1008
|
-
*
|
|
1009
|
-
* export async function getUserByEmail(email: string) {
|
|
1010
|
-
* const repo = getRepository(users, UserRepository);
|
|
1011
|
-
* return repo.findByEmail(email);
|
|
1012
|
-
* }
|
|
1013
|
-
* ```
|
|
1014
|
-
*
|
|
1015
|
-
* @example
|
|
1016
|
-
* ```typescript
|
|
1017
|
-
* // Multiple services - same instance
|
|
1018
|
-
* // services/users.ts
|
|
1019
|
-
* const repo = getRepository(users, UserRepository); // Instance A
|
|
1020
|
-
*
|
|
1021
|
-
* // services/auth.ts
|
|
1022
|
-
* const repo = getRepository(users, UserRepository); // Same Instance A
|
|
1023
|
-
* ```
|
|
1024
|
-
*
|
|
1025
|
-
* @example
|
|
1026
|
-
* ```typescript
|
|
1027
|
-
* // Transaction handling - same instance works everywhere
|
|
1028
|
-
* import { getRepository, Transactional } from '@spfn/core/db';
|
|
1029
|
-
* import { users } from './entities';
|
|
1030
|
-
*
|
|
1031
|
-
* const userRepo = getRepository(users);
|
|
1032
|
-
*
|
|
1033
|
-
* // Outside transaction - uses default DB
|
|
1034
|
-
* await userRepo.findById(1);
|
|
1035
|
-
*
|
|
1036
|
-
* // Inside Transactional() middleware - uses transaction automatically
|
|
1037
|
-
* app.use(Transactional());
|
|
1038
|
-
* app.post('/', async (c) => {
|
|
1039
|
-
* // Same instance, but now uses transaction DB
|
|
1040
|
-
* await userRepo.save({ email: 'test@example.com' });
|
|
1041
|
-
* return c.json({ success: true });
|
|
1042
|
-
* });
|
|
1043
|
-
* ```
|
|
1044
|
-
*/
|
|
1045
|
-
declare function getRepository<TTable extends PgTable, TRepo extends Repository<TTable> = Repository<TTable>>(table: TTable, RepositoryClass?: new (table: TTable) => TRepo): TRepo;
|
|
1046
|
-
/**
|
|
1047
|
-
* Clear repository cache
|
|
1048
|
-
*
|
|
1049
|
-
* Removes all cached repository instances. Useful for testing scenarios
|
|
1050
|
-
* where you need fresh instances.
|
|
1051
|
-
*
|
|
1052
|
-
* ⚠️ Warning: Only use this in tests. In production, cached instances
|
|
1053
|
-
* should persist throughout the application lifecycle.
|
|
1054
|
-
*
|
|
1055
|
-
* @example
|
|
1056
|
-
* ```typescript
|
|
1057
|
-
* import { clearRepositoryCache } from '@spfn/core/db';
|
|
1058
|
-
*
|
|
1059
|
-
* beforeEach(() => {
|
|
1060
|
-
* clearRepositoryCache(); // Fresh instances for each test
|
|
1061
|
-
* });
|
|
1062
|
-
* ```
|
|
1063
|
-
*/
|
|
1064
|
-
declare function clearRepositoryCache(): void;
|
|
1065
|
-
/**
|
|
1066
|
-
* Get cache size (for debugging/monitoring)
|
|
1067
|
-
*
|
|
1068
|
-
* @returns Number of cached repository instances
|
|
1069
|
-
*
|
|
1070
|
-
* @example
|
|
1071
|
-
* ```typescript
|
|
1072
|
-
* const size = getRepositoryCacheSize();
|
|
1073
|
-
* console.log(`Cached repositories: ${size}`);
|
|
1074
|
-
* ```
|
|
1075
|
-
*/
|
|
1076
|
-
declare function getRepositoryCacheSize(): number;
|
|
1077
|
-
|
|
1078
|
-
/**
|
|
1079
|
-
* Request-Scoped Repository Pattern
|
|
1080
|
-
*
|
|
1081
|
-
* Provides request-level repository caching using AsyncLocalStorage.
|
|
1082
|
-
*
|
|
1083
|
-
* ## Benefits:
|
|
1084
|
-
* - ✅ **Automatic isolation**: Each request gets its own repository cache
|
|
1085
|
-
* - ✅ **Memory efficient**: Cache cleared automatically after request ends
|
|
1086
|
-
* - ✅ **Test-friendly**: No global state, tests are fully isolated
|
|
1087
|
-
* - ✅ **DI-compatible**: Can inject custom repositories easily
|
|
1088
|
-
* - ✅ **Zero overhead**: Uses existing AsyncLocalStorage infrastructure
|
|
1089
|
-
*
|
|
1090
|
-
* ## vs Global Singleton:
|
|
1091
|
-
*
|
|
1092
|
-
* | Feature | Global Singleton | Request-Scoped |
|
|
1093
|
-
* |---------|-----------------|----------------|
|
|
1094
|
-
* | Memory | Permanent cache | Request-only cache |
|
|
1095
|
-
* | Test isolation | Manual clearRepositoryCache() | Automatic |
|
|
1096
|
-
* | Thread-safety | Shared state | Isolated per request |
|
|
1097
|
-
* | DI support | Difficult | Easy |
|
|
1098
|
-
*
|
|
1099
|
-
* @example
|
|
1100
|
-
* ```typescript
|
|
1101
|
-
* // 1. Add middleware (routes automatically)
|
|
1102
|
-
* import { RepositoryScope } from '@spfn/core/db';
|
|
1103
|
-
*
|
|
1104
|
-
* app.use(RepositoryScope());
|
|
1105
|
-
*
|
|
1106
|
-
* // 2. Use in service (same request = same instance)
|
|
1107
|
-
* import { getScopedRepository } from '@spfn/core/db';
|
|
1108
|
-
*
|
|
1109
|
-
* export async function createPost(data) {
|
|
1110
|
-
* const repo = getScopedRepository(posts, PostRepository); // First call - creates
|
|
1111
|
-
* const existing = await repo.findBySlug(slug);
|
|
1112
|
-
* return repo.save(data); // Same instance
|
|
1113
|
-
* }
|
|
1114
|
-
*
|
|
1115
|
-
* // 3. Different request = different cache (automatic)
|
|
1116
|
-
* ```
|
|
1117
|
-
*/
|
|
1118
|
-
|
|
1119
|
-
/**
|
|
1120
|
-
* Execute function within a repository scope
|
|
1121
|
-
*
|
|
1122
|
-
* Creates a new request-scoped cache for the duration of the function.
|
|
1123
|
-
* Automatically cleaned up after function completes.
|
|
1124
|
-
*
|
|
1125
|
-
* @param fn - Async function to execute within scope
|
|
1126
|
-
* @returns Result of the function
|
|
1127
|
-
*
|
|
1128
|
-
* @example
|
|
1129
|
-
* ```typescript
|
|
1130
|
-
* // Middleware
|
|
1131
|
-
* app.use(async (c, next) => {
|
|
1132
|
-
* return withRepositoryScope(() => next());
|
|
1133
|
-
* });
|
|
1134
|
-
*
|
|
1135
|
-
* // Manual usage
|
|
1136
|
-
* const result = await withRepositoryScope(async () => {
|
|
1137
|
-
* const repo = getScopedRepository(users);
|
|
1138
|
-
* return repo.findAll();
|
|
1139
|
-
* });
|
|
1140
|
-
* ```
|
|
1141
|
-
*/
|
|
1142
|
-
declare function withRepositoryScope<T>(fn: () => Promise<T>): Promise<T>;
|
|
1143
|
-
/**
|
|
1144
|
-
* Get request-scoped repository instance
|
|
1145
|
-
*
|
|
1146
|
-
* Returns cached instance within the same request, creates new instance for new requests.
|
|
1147
|
-
* Falls back to creating a new instance if called outside of a repository scope.
|
|
1148
|
-
*
|
|
1149
|
-
* ## Behavior:
|
|
1150
|
-
* - **Inside scope**: Returns cached instance (same request = same instance)
|
|
1151
|
-
* - **Outside scope**: Creates new instance every time (graceful degradation)
|
|
1152
|
-
*
|
|
1153
|
-
* @param table - Drizzle table definition
|
|
1154
|
-
* @param RepositoryClass - Optional custom Repository class
|
|
1155
|
-
* @returns Repository instance (cached or new)
|
|
1156
|
-
*
|
|
1157
|
-
* @example
|
|
1158
|
-
* ```typescript
|
|
1159
|
-
* // Base Repository
|
|
1160
|
-
* const repo = getScopedRepository(users);
|
|
1161
|
-
* await repo.findById(1);
|
|
1162
|
-
*
|
|
1163
|
-
* // Custom Repository
|
|
1164
|
-
* class UserRepository extends Repository<typeof users> {
|
|
1165
|
-
* async findByEmail(email: string) {
|
|
1166
|
-
* return this.findOneWhere({ email });
|
|
1167
|
-
* }
|
|
1168
|
-
* }
|
|
1169
|
-
*
|
|
1170
|
-
* const repo = getScopedRepository(users, UserRepository);
|
|
1171
|
-
* await repo.findByEmail('john@example.com');
|
|
1172
|
-
*
|
|
1173
|
-
* // Within same request - returns cached instance
|
|
1174
|
-
* const repo2 = getScopedRepository(users, UserRepository); // Same instance!
|
|
1175
|
-
* ```
|
|
1176
|
-
*/
|
|
1177
|
-
declare function getScopedRepository<TTable extends PgTable, TRepo extends Repository<TTable> = Repository<TTable>>(table: TTable, RepositoryClass?: new (table: TTable) => TRepo): TRepo;
|
|
1178
|
-
/**
|
|
1179
|
-
* Hono middleware for automatic repository scope management
|
|
1180
|
-
*
|
|
1181
|
-
* Wraps each request in a repository scope, ensuring automatic cache isolation.
|
|
1182
|
-
* All repositories accessed via getScopedRepository() within this request will be cached.
|
|
1183
|
-
*
|
|
1184
|
-
* @returns Hono middleware handler
|
|
1185
|
-
*
|
|
1186
|
-
* @example
|
|
1187
|
-
* ```typescript
|
|
1188
|
-
* // Global middleware (recommended)
|
|
1189
|
-
* import { createServer } from '@spfn/core';
|
|
1190
|
-
* import { RepositoryScope } from '@spfn/core/db';
|
|
1191
|
-
*
|
|
1192
|
-
* const app = createServer();
|
|
1193
|
-
* app.use(RepositoryScope());
|
|
1194
|
-
*
|
|
1195
|
-
* // Or in server.config.ts
|
|
1196
|
-
* export default {
|
|
1197
|
-
* middlewares: [
|
|
1198
|
-
* { name: 'repositoryScope', handler: RepositoryScope() }
|
|
1199
|
-
* ]
|
|
1200
|
-
* };
|
|
1201
|
-
* ```
|
|
1202
|
-
*/
|
|
1203
|
-
declare function RepositoryScope(): MiddlewareHandler;
|
|
1204
|
-
/**
|
|
1205
|
-
* Get current repository cache size (for debugging/monitoring)
|
|
1206
|
-
*
|
|
1207
|
-
* Returns the number of cached repository instances in the current request scope.
|
|
1208
|
-
* Returns 0 if called outside a scope.
|
|
1209
|
-
*
|
|
1210
|
-
* @returns Number of cached repositories in current scope
|
|
1211
|
-
*
|
|
1212
|
-
* @example
|
|
1213
|
-
* ```typescript
|
|
1214
|
-
* const size = getScopedCacheSize();
|
|
1215
|
-
* console.log(`Cached repositories in this request: ${size}`);
|
|
1216
|
-
* ```
|
|
1217
|
-
*/
|
|
1218
|
-
declare function getScopedCacheSize(): number;
|
|
1219
|
-
/**
|
|
1220
|
-
* Check if currently inside a repository scope
|
|
1221
|
-
*
|
|
1222
|
-
* @returns true if inside scope, false otherwise
|
|
1223
|
-
*
|
|
1224
|
-
* @example
|
|
1225
|
-
* ```typescript
|
|
1226
|
-
* if (isInRepositoryScope()) {
|
|
1227
|
-
* console.log('Using scoped cache');
|
|
1228
|
-
* } else {
|
|
1229
|
-
* console.log('Creating new instances');
|
|
1230
|
-
* }
|
|
1231
|
-
* ```
|
|
1232
|
-
*/
|
|
1233
|
-
declare function isInRepositoryScope(): boolean;
|
|
1234
|
-
|
|
1235
|
-
/**
|
|
1236
|
-
* WrappedDb 클래스
|
|
1237
|
-
*
|
|
1238
|
-
* Drizzle DB를 래핑하여 추가 기능 제공
|
|
1239
|
-
*/
|
|
1240
|
-
declare class WrappedDb {
|
|
1241
|
-
private db;
|
|
1242
|
-
constructor(db: PostgresJsDatabase<Record<string, never>>);
|
|
1243
|
-
/**
|
|
1244
|
-
* Repository 패턴으로 테이블 접근
|
|
1245
|
-
*
|
|
1246
|
-
* @example
|
|
1247
|
-
* const db = getDb();
|
|
1248
|
-
* const userRepo = db.for(users);
|
|
1249
|
-
* const result = await userRepo.findPage(pageable);
|
|
1250
|
-
*/
|
|
1251
|
-
for<TTable extends PgTable<any>, TSelect = any>(table: TTable): Repository<TTable, TSelect>;
|
|
1252
|
-
/**
|
|
1253
|
-
* Drizzle의 모든 메서드를 프록시
|
|
1254
|
-
*
|
|
1255
|
-
* select, insert, update, delete, transaction 등 모든 Drizzle 메서드 사용 가능
|
|
1256
|
-
*/
|
|
1257
|
-
get select(): {
|
|
1258
|
-
(): drizzle_orm_pg_core.PgSelectBuilder<undefined>;
|
|
1259
|
-
<TSelection extends drizzle_orm_pg_core.SelectedFields>(fields: TSelection): drizzle_orm_pg_core.PgSelectBuilder<TSelection>;
|
|
1260
|
-
};
|
|
1261
|
-
get insert(): <TTable extends PgTable>(table: TTable) => drizzle_orm_pg_core.PgInsertBuilder<TTable, drizzle_orm_postgres_js.PostgresJsQueryResultHKT, false>;
|
|
1262
|
-
get update(): <TTable extends PgTable>(table: TTable) => drizzle_orm_pg_core.PgUpdateBuilder<TTable, drizzle_orm_postgres_js.PostgresJsQueryResultHKT>;
|
|
1263
|
-
get delete(): <TTable extends PgTable>(table: TTable) => drizzle_orm_pg_core.PgDeleteBase<TTable, drizzle_orm_postgres_js.PostgresJsQueryResultHKT, undefined, undefined, false, never>;
|
|
1264
|
-
get execute(): <TRow extends Record<string, unknown> = Record<string, unknown>>(query: drizzle_orm.SQLWrapper | string) => drizzle_orm_pg_core_query_builders_raw.PgRaw<postgres.RowList<drizzle_orm.Assume<TRow, postgres.Row>[]>>;
|
|
1265
|
-
get transaction(): <T>(transaction: (tx: drizzle_orm_pg_core.PgTransaction<drizzle_orm_postgres_js.PostgresJsQueryResultHKT, Record<string, never>, drizzle_orm.ExtractTablesWithRelations<Record<string, never>>>) => Promise<T>, config?: drizzle_orm_pg_core.PgTransactionConfig) => Promise<T>;
|
|
1266
|
-
get query(): drizzle_orm.DrizzleTypeError<"Seems like the schema generic is missing - did you forget to add it to your DB type?">;
|
|
1267
|
-
get $with(): drizzle_orm_pg_core.WithBuilder;
|
|
1268
|
-
/**
|
|
1269
|
-
* Raw Drizzle DB 접근 (필요시)
|
|
1270
|
-
*/
|
|
1271
|
-
get raw(): PostgresJsDatabase;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
/**
|
|
1275
|
-
* Get DB instance (WrappedDb)
|
|
1276
|
-
*
|
|
1277
|
-
* - If transaction context exists: Returns transaction DB
|
|
1278
|
-
* - Otherwise: Returns default DB or specified connection type
|
|
1279
|
-
* - Wraps with WrappedDb to provide both Repository pattern + Drizzle features
|
|
1280
|
-
*
|
|
1281
|
-
* Usage 1: Direct Drizzle use
|
|
1282
|
-
* ```typescript
|
|
1283
|
-
* export async function GET(c: RouteContext) {
|
|
1284
|
-
* const db = getDb();
|
|
1285
|
-
* const users = await db.select().from(users);
|
|
1286
|
-
* return c.json(users);
|
|
1287
|
-
* }
|
|
1288
|
-
* ```
|
|
1289
|
-
*
|
|
1290
|
-
* Usage 2: Repository pattern
|
|
1291
|
-
* ```typescript
|
|
1292
|
-
* export async function GET(c: RouteContext) {
|
|
1293
|
-
* const db = getDb();
|
|
1294
|
-
* const userRepo = db.for(users);
|
|
1295
|
-
* const result = await userRepo.findPage(pageable);
|
|
1296
|
-
* return c.json(result);
|
|
1297
|
-
* }
|
|
1298
|
-
* ```
|
|
1299
|
-
*
|
|
1300
|
-
* Usage 3: Specify connection type
|
|
1301
|
-
* ```typescript
|
|
1302
|
-
* const readDb = getDb('read'); // Use read replica
|
|
1303
|
-
* const writeDb = getDb('write'); // Use primary
|
|
1304
|
-
* ```
|
|
1305
|
-
*
|
|
1306
|
-
* @param type - Optional connection type ('read' or 'write')
|
|
1307
|
-
* @returns WrappedDb instance (transaction or specified DB)
|
|
1308
|
-
*/
|
|
1309
|
-
declare function getDb(type?: DbConnectionType): WrappedDb;
|
|
1310
|
-
|
|
1311
|
-
/**
|
|
1312
|
-
* Drizzle Kit configuration generator
|
|
1313
|
-
* Automatically generates drizzle.config.ts from environment variables
|
|
1314
|
-
*/
|
|
1315
|
-
interface DrizzleConfigOptions {
|
|
1316
|
-
/** Database connection URL (defaults to process.env.DATABASE_URL) */
|
|
1317
|
-
databaseUrl?: string;
|
|
1318
|
-
/** Schema files glob pattern (defaults to './src/server/entities/*.ts') */
|
|
1319
|
-
schema?: string;
|
|
1320
|
-
/** Migration output directory (defaults to './drizzle/migrations') */
|
|
1321
|
-
out?: string;
|
|
1322
|
-
/** Database dialect (auto-detected from URL if not provided) */
|
|
1323
|
-
dialect?: 'postgresql' | 'mysql' | 'sqlite';
|
|
1324
|
-
}
|
|
1325
|
-
/**
|
|
1326
|
-
* Detect database dialect from connection URL
|
|
1327
|
-
*/
|
|
1328
|
-
declare function detectDialect(url: string): 'postgresql' | 'mysql' | 'sqlite';
|
|
1329
|
-
/**
|
|
1330
|
-
* Generate Drizzle Kit configuration
|
|
1331
|
-
*
|
|
1332
|
-
* @param options - Configuration options
|
|
1333
|
-
* @returns Drizzle Kit configuration object
|
|
1334
|
-
*
|
|
1335
|
-
* @example
|
|
1336
|
-
* ```ts
|
|
1337
|
-
* // Zero-config (reads from process.env.DATABASE_URL)
|
|
1338
|
-
* const config = getDrizzleConfig();
|
|
1339
|
-
*
|
|
1340
|
-
* // Custom config
|
|
1341
|
-
* const config = getDrizzleConfig({
|
|
1342
|
-
* databaseUrl: 'postgresql://localhost/mydb',
|
|
1343
|
-
* schema: './src/db/schema/*.ts',
|
|
1344
|
-
* out: './migrations',
|
|
1345
|
-
* });
|
|
1346
|
-
* ```
|
|
1347
|
-
*/
|
|
1348
|
-
declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
|
|
1349
|
-
schema: string;
|
|
1350
|
-
out: string;
|
|
1351
|
-
dialect: "postgresql" | "mysql" | "sqlite";
|
|
1352
|
-
dbCredentials: {
|
|
1353
|
-
url: string;
|
|
1354
|
-
};
|
|
1355
|
-
};
|
|
1356
|
-
/**
|
|
1357
|
-
* Generate drizzle.config.ts file content
|
|
1358
|
-
*
|
|
1359
|
-
* @param options - Configuration options
|
|
1360
|
-
* @returns File content as string
|
|
1361
|
-
*/
|
|
1362
|
-
declare function generateDrizzleConfigFile(options?: DrizzleConfigOptions): string;
|
|
1363
|
-
|
|
1364
|
-
/**
|
|
1365
|
-
* Standard auto-incrementing primary key
|
|
1366
|
-
*
|
|
1367
|
-
* @returns bigserial primary key column
|
|
1368
|
-
*
|
|
1369
|
-
* @example
|
|
1370
|
-
* ```typescript
|
|
1371
|
-
* export const users = pgTable('users', {
|
|
1372
|
-
* id: id(),
|
|
1373
|
-
* // ...
|
|
1374
|
-
* });
|
|
1375
|
-
* ```
|
|
1376
|
-
*/
|
|
1377
|
-
declare function id(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigSerial53BuilderInitial<"id">>>;
|
|
1378
|
-
/**
|
|
1379
|
-
* Standard timestamp fields (createdAt, updatedAt)
|
|
1380
|
-
*
|
|
1381
|
-
* Both fields are timezone-aware, auto-set to current time on creation.
|
|
1382
|
-
* When autoUpdate is enabled, updatedAt will be automatically updated on record updates.
|
|
1383
|
-
*
|
|
1384
|
-
* @param options - Optional configuration
|
|
1385
|
-
* @param options.autoUpdate - Automatically update updatedAt on record updates (default: false)
|
|
1386
|
-
* @returns Object with createdAt and updatedAt columns
|
|
1387
|
-
*
|
|
1388
|
-
* @example
|
|
1389
|
-
* ```typescript
|
|
1390
|
-
* // Without auto-update
|
|
1391
|
-
* export const users = pgTable('users', {
|
|
1392
|
-
* id: id(),
|
|
1393
|
-
* email: text('email'),
|
|
1394
|
-
* ...timestamps(),
|
|
1395
|
-
* });
|
|
1396
|
-
*
|
|
1397
|
-
* // With auto-update
|
|
1398
|
-
* export const posts = pgTable('posts', {
|
|
1399
|
-
* id: id(),
|
|
1400
|
-
* title: text('title'),
|
|
1401
|
-
* ...timestamps({ autoUpdate: true }),
|
|
1402
|
-
* });
|
|
1403
|
-
* ```
|
|
1404
|
-
*/
|
|
1405
|
-
declare function timestamps(options?: {
|
|
1406
|
-
autoUpdate?: boolean;
|
|
1407
|
-
}): {
|
|
1408
|
-
createdAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"created_at">>>;
|
|
1409
|
-
updatedAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"updated_at">>>;
|
|
1410
|
-
};
|
|
1411
|
-
/**
|
|
1412
|
-
* Foreign key reference to another table
|
|
1413
|
-
*
|
|
1414
|
-
* Creates a bigserial column with cascade delete.
|
|
1415
|
-
* Type-safe: ensures the reference points to a valid PostgreSQL column.
|
|
1416
|
-
*
|
|
1417
|
-
* @param name - Column name (e.g., 'author' creates 'author_id')
|
|
1418
|
-
* @param reference - Reference to parent table column
|
|
1419
|
-
* @param options - Optional foreign key options
|
|
1420
|
-
*
|
|
1421
|
-
* @example
|
|
1422
|
-
* ```typescript
|
|
1423
|
-
* import { users } from './users';
|
|
1424
|
-
*
|
|
1425
|
-
* export const posts = pgTable('posts', {
|
|
1426
|
-
* id: id(),
|
|
1427
|
-
* authorId: foreignKey('author', () => users.id),
|
|
1428
|
-
* ...timestamps(),
|
|
1429
|
-
* });
|
|
1430
|
-
* ```
|
|
1431
|
-
*/
|
|
1432
|
-
declare function foreignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
|
|
1433
|
-
onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
1434
|
-
}): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>>;
|
|
1435
|
-
/**
|
|
1436
|
-
* Optional foreign key reference (nullable)
|
|
1437
|
-
*
|
|
1438
|
-
* Type-safe: ensures the reference points to a valid PostgreSQL column.
|
|
1439
|
-
*
|
|
1440
|
-
* @param name - Column name (e.g., 'author' creates 'author_id')
|
|
1441
|
-
* @param reference - Reference to parent table column
|
|
1442
|
-
* @param options - Optional foreign key options
|
|
1443
|
-
*
|
|
1444
|
-
* @example
|
|
1445
|
-
* ```typescript
|
|
1446
|
-
* export const posts = pgTable('posts', {
|
|
1447
|
-
* id: id(),
|
|
1448
|
-
* authorId: optionalForeignKey('author', () => users.id),
|
|
1449
|
-
* });
|
|
1450
|
-
* ```
|
|
1451
|
-
*/
|
|
1452
|
-
declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
|
|
1453
|
-
onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
1454
|
-
}): drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>;
|
|
1455
|
-
|
|
1456
|
-
/**
|
|
1457
|
-
* AsyncLocalStorage-based Transaction Context
|
|
1458
|
-
*
|
|
1459
|
-
* Uses Node.js AsyncLocalStorage to propagate transactions throughout the async call chain.
|
|
1460
|
-
*
|
|
1461
|
-
* ✅ Implemented:
|
|
1462
|
-
* - AsyncLocalStorage-based context management
|
|
1463
|
-
* - Transaction storage/retrieval functions
|
|
1464
|
-
* - Type-safe transaction propagation
|
|
1465
|
-
* - Transaction propagation across async chains
|
|
1466
|
-
*
|
|
1467
|
-
* ⚠️ Needs improvement:
|
|
1468
|
-
* - Nested transaction handling (currently ignores outer transaction)
|
|
1469
|
-
* - Transaction timeout detection
|
|
1470
|
-
*
|
|
1471
|
-
* 💡 Future considerations:
|
|
1472
|
-
* - Add transaction ID (for debugging/tracing)
|
|
1473
|
-
* - Track transaction start time (for performance monitoring)
|
|
1474
|
-
* - Store transaction metadata (route info, user info, etc.)
|
|
1475
|
-
* - Savepoint support (nested transactions)
|
|
1476
|
-
* - Transaction isolation level configuration
|
|
1477
|
-
*
|
|
1478
|
-
* 🔗 Related files:
|
|
1479
|
-
* - src/utils/transaction.ts (Transactional middleware)
|
|
1480
|
-
* - src/db/db-context.ts (getDb helper)
|
|
1481
|
-
*/
|
|
1482
|
-
|
|
1483
|
-
/**
|
|
1484
|
-
* Transaction database type
|
|
1485
|
-
* Record<string, never> represents an empty schema; actual schema is determined at runtime
|
|
1486
|
-
*/
|
|
1487
|
-
type TransactionDB = PostgresJsDatabase;
|
|
1488
|
-
/**
|
|
1489
|
-
* Transaction context stored in AsyncLocalStorage
|
|
1490
|
-
*/
|
|
1491
|
-
type TransactionContext = {
|
|
1492
|
-
tx: TransactionDB;
|
|
1493
|
-
};
|
|
1494
|
-
/**
|
|
1495
|
-
* Get current transaction from AsyncLocalStorage
|
|
1496
|
-
*
|
|
1497
|
-
* @returns Transaction if available, null otherwise
|
|
1498
|
-
*/
|
|
1499
|
-
declare function getTransaction(): TransactionDB | null;
|
|
1500
|
-
/**
|
|
1501
|
-
* Run a function within a transaction context
|
|
1502
|
-
*
|
|
1503
|
-
* The transaction will be available to all async operations within the callback
|
|
1504
|
-
* via getTransaction()
|
|
1505
|
-
*
|
|
1506
|
-
* @param tx - Drizzle transaction object
|
|
1507
|
-
* @param callback - Function to run within transaction context
|
|
1508
|
-
* @returns Result of the callback
|
|
1509
|
-
*/
|
|
1510
|
-
declare function runWithTransaction<T>(tx: TransactionDB, callback: () => Promise<T>): Promise<T>;
|
|
1511
|
-
|
|
1512
|
-
/**
|
|
1513
|
-
* Transaction middleware options
|
|
1514
|
-
*/
|
|
1515
|
-
interface TransactionalOptions {
|
|
1516
|
-
/**
|
|
1517
|
-
* Slow transaction warning threshold in milliseconds
|
|
1518
|
-
* @default 1000 (1 second)
|
|
1519
|
-
*/
|
|
1520
|
-
slowThreshold?: number;
|
|
1521
|
-
/**
|
|
1522
|
-
* Enable transaction logging
|
|
1523
|
-
* @default true
|
|
1524
|
-
*/
|
|
1525
|
-
enableLogging?: boolean;
|
|
1526
|
-
/**
|
|
1527
|
-
* Transaction timeout in milliseconds
|
|
1528
|
-
*
|
|
1529
|
-
* If transaction exceeds this duration, it will be aborted with TransactionError.
|
|
1530
|
-
*
|
|
1531
|
-
* @default 30000 (30 seconds) or TRANSACTION_TIMEOUT environment variable
|
|
1532
|
-
*
|
|
1533
|
-
* @example
|
|
1534
|
-
* ```typescript
|
|
1535
|
-
* // Default timeout (30s or TRANSACTION_TIMEOUT env var)
|
|
1536
|
-
* Transactional()
|
|
1537
|
-
*
|
|
1538
|
-
* // Custom timeout for specific route (60s)
|
|
1539
|
-
* Transactional({ timeout: 60000 })
|
|
1540
|
-
*
|
|
1541
|
-
* // Disable timeout
|
|
1542
|
-
* Transactional({ timeout: 0 })
|
|
1543
|
-
* ```
|
|
1544
|
-
*/
|
|
1545
|
-
timeout?: number;
|
|
1546
|
-
}
|
|
1547
|
-
/**
|
|
1548
|
-
* Transaction middleware for Hono routes
|
|
1549
|
-
*
|
|
1550
|
-
* Automatically wraps route handlers in a database transaction.
|
|
1551
|
-
* Commits on success, rolls back on error.
|
|
1552
|
-
*
|
|
1553
|
-
* @example
|
|
1554
|
-
* ```typescript
|
|
1555
|
-
* // In your route file
|
|
1556
|
-
* export const middlewares = [Transactional()];
|
|
1557
|
-
*
|
|
1558
|
-
* export async function POST(c: RouteContext) {
|
|
1559
|
-
* // All DB operations run in a transaction
|
|
1560
|
-
* const [user] = await db.insert(users).values(body).returning();
|
|
1561
|
-
* await db.insert(profiles).values({ userId: user.id });
|
|
1562
|
-
* // Auto-commits on success
|
|
1563
|
-
* return c.json(user, 201);
|
|
1564
|
-
* }
|
|
1565
|
-
* ```
|
|
1566
|
-
*
|
|
1567
|
-
* @example
|
|
1568
|
-
* ```typescript
|
|
1569
|
-
* // With custom options
|
|
1570
|
-
* export const middlewares = [
|
|
1571
|
-
* Transactional({
|
|
1572
|
-
* slowThreshold: 2000, // Warn if transaction takes > 2s
|
|
1573
|
-
* enableLogging: false, // Disable logging
|
|
1574
|
-
* timeout: 60000, // 60 second timeout for long operations
|
|
1575
|
-
* })
|
|
1576
|
-
* ];
|
|
1577
|
-
* ```
|
|
1578
|
-
*
|
|
1579
|
-
* 🔄 Transaction behavior:
|
|
1580
|
-
* - Success: Auto-commit
|
|
1581
|
-
* - Error: Auto-rollback
|
|
1582
|
-
* - Detects context.error to trigger rollback
|
|
1583
|
-
*
|
|
1584
|
-
* 📊 Transaction logging:
|
|
1585
|
-
* - Auto-logs transaction start/commit/rollback
|
|
1586
|
-
* - Measures and records execution time
|
|
1587
|
-
* - Warns about slow transactions (default: > 1s)
|
|
1588
|
-
*/
|
|
1589
|
-
declare function Transactional(options?: TransactionalOptions): hono.MiddlewareHandler<any, string, {}>;
|
|
1590
|
-
|
|
1591
|
-
/**
|
|
1592
|
-
* Database Error Classes
|
|
1593
|
-
*
|
|
1594
|
-
* Type-safe error handling with custom error class hierarchy
|
|
1595
|
-
* Mapped to HTTP status codes for API responses
|
|
1596
|
-
*/
|
|
1597
|
-
/**
|
|
1598
|
-
* Base Database Error
|
|
1599
|
-
*
|
|
1600
|
-
* Base class for all database-related errors
|
|
1601
|
-
*/
|
|
1602
|
-
declare class DatabaseError<TDetails extends Record<string, unknown> = Record<string, unknown>> extends Error {
|
|
1603
|
-
readonly statusCode: number;
|
|
1604
|
-
readonly details?: TDetails;
|
|
1605
|
-
readonly timestamp: Date;
|
|
1606
|
-
constructor(message: string, statusCode?: number, details?: TDetails);
|
|
1607
|
-
/**
|
|
1608
|
-
* Serialize error for API response
|
|
1609
|
-
*/
|
|
1610
|
-
toJSON(): {
|
|
1611
|
-
name: string;
|
|
1612
|
-
message: string;
|
|
1613
|
-
statusCode: number;
|
|
1614
|
-
details: TDetails | undefined;
|
|
1615
|
-
timestamp: string;
|
|
1616
|
-
};
|
|
1617
|
-
}
|
|
1618
|
-
/**
|
|
1619
|
-
* Connection Error (503 Service Unavailable)
|
|
1620
|
-
*
|
|
1621
|
-
* Database connection failure, connection pool exhaustion, etc.
|
|
1622
|
-
*/
|
|
1623
|
-
declare class ConnectionError extends DatabaseError {
|
|
1624
|
-
constructor(message: string, details?: Record<string, any>);
|
|
1625
|
-
}
|
|
1626
|
-
/**
|
|
1627
|
-
* Query Error (500 Internal Server Error)
|
|
1628
|
-
*
|
|
1629
|
-
* SQL query execution failure, syntax errors, etc.
|
|
1630
|
-
*/
|
|
1631
|
-
declare class QueryError extends DatabaseError {
|
|
1632
|
-
constructor(message: string, statusCode?: number, details?: Record<string, any>);
|
|
1633
|
-
}
|
|
1634
|
-
/**
|
|
1635
|
-
* Not Found Error (404 Not Found)
|
|
1636
|
-
*
|
|
1637
|
-
* Requested resource does not exist
|
|
1638
|
-
*/
|
|
1639
|
-
declare class NotFoundError extends QueryError {
|
|
1640
|
-
constructor(resource: string, id: string | number);
|
|
1641
|
-
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Validation Error (400 Bad Request)
|
|
1644
|
-
*
|
|
1645
|
-
* Input data validation failure
|
|
1646
|
-
*/
|
|
1647
|
-
declare class ValidationError extends QueryError {
|
|
1648
|
-
constructor(message: string, details?: Record<string, any>);
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Transaction Error (500 Internal Server Error)
|
|
1652
|
-
*
|
|
1653
|
-
* Transaction start/commit/rollback failure
|
|
1654
|
-
*/
|
|
1655
|
-
declare class TransactionError extends DatabaseError {
|
|
1656
|
-
constructor(message: string, statusCode?: number, details?: Record<string, any>);
|
|
1657
|
-
}
|
|
1658
|
-
/**
|
|
1659
|
-
* Deadlock Error (409 Conflict)
|
|
1660
|
-
*
|
|
1661
|
-
* Database deadlock detected
|
|
1662
|
-
*/
|
|
1663
|
-
declare class DeadlockError extends TransactionError {
|
|
1664
|
-
constructor(message: string, details?: Record<string, any>);
|
|
1665
|
-
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Duplicate Entry Error (409 Conflict)
|
|
1668
|
-
*
|
|
1669
|
-
* Unique constraint violation (e.g., duplicate email)
|
|
1670
|
-
*/
|
|
1671
|
-
declare class DuplicateEntryError extends QueryError {
|
|
1672
|
-
constructor(field: string, value: string | number);
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
/**
|
|
1676
|
-
* PostgreSQL Error Conversion Utilities
|
|
1677
|
-
*
|
|
1678
|
-
* Converts PostgreSQL-specific error codes to custom error types
|
|
1679
|
-
* @see https://www.postgresql.org/docs/current/errcodes-appendix.html
|
|
1680
|
-
*/
|
|
1681
|
-
|
|
1682
|
-
/**
|
|
1683
|
-
* Convert PostgreSQL error to custom DatabaseError
|
|
1684
|
-
*
|
|
1685
|
-
* Maps PostgreSQL error codes to appropriate error classes with correct status codes
|
|
1686
|
-
*
|
|
1687
|
-
* @param error - PostgreSQL error object (from pg driver or Drizzle)
|
|
1688
|
-
* @returns Custom DatabaseError instance
|
|
1689
|
-
*
|
|
1690
|
-
* @example
|
|
1691
|
-
* ```typescript
|
|
1692
|
-
* import { fromPostgresError } from '@spfn/core/db';
|
|
1693
|
-
*
|
|
1694
|
-
* try {
|
|
1695
|
-
* await db.insert(users).values(data);
|
|
1696
|
-
* } catch (pgError) {
|
|
1697
|
-
* throw fromPostgresError(pgError);
|
|
1698
|
-
* }
|
|
1699
|
-
* ```
|
|
1700
|
-
*/
|
|
1701
|
-
declare function fromPostgresError(error: any): DatabaseError;
|
|
1702
|
-
|
|
1703
|
-
export { type SortDirection as $, timestamps as A, foreignKey as B, optionalForeignKey as C, type DbConnectionType as D, getTransaction as E, runWithTransaction as F, type TransactionContext as G, type TransactionDB as H, type TransactionalOptions as I, fromPostgresError as J, DatabaseError as K, buildFilters as L, buildSort as M, orFilters as N, applyPagination as O, type PoolConfig as P, QueryBuilder as Q, type RetryConfig as R, createPaginationMeta as S, Transactional as T, countTotal as U, type FilterOperator as V, WrappedDb as W, type FilterValue as X, type FilterCondition as Y, type Filters as Z, type FilterResult as _, getDb as a, type SortCondition as a0, type SortResult as a1, type PaginationParams as a2, type PaginationMeta as a3, type DrizzleTable as a4, ConnectionError as a5, QueryError as a6, NotFoundError as a7, ValidationError as a8, TransactionError as a9, DeadlockError as aa, DuplicateEntryError as ab, getDatabase as b, createDatabaseFromEnv as c, db as d, closeDatabase as e, getDatabaseInfo as f, getRawDb as g, type DatabaseClients as h, initDatabase as i, getDrizzleConfig as j, detectDialect as k, generateDrizzleConfigFile as l, type DrizzleConfigOptions as m, Repository as n, getRepository as o, clearRepositoryCache as p, getRepositoryCacheSize as q, getScopedRepository as r, setDatabase as s, RepositoryScope as t, getScopedCacheSize as u, isInRepositoryScope as v, withRepositoryScope as w, type Pageable as x, type Page as y, id as z };
|