@spfn/core 0.1.0-alpha.8 → 0.1.0-alpha.81

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.
Files changed (59) hide show
  1. package/README.md +169 -195
  2. package/dist/auto-loader-JFaZ9gON.d.ts +80 -0
  3. package/dist/cache/index.d.ts +211 -0
  4. package/dist/cache/index.js +992 -0
  5. package/dist/cache/index.js.map +1 -0
  6. package/dist/client/index.d.ts +131 -92
  7. package/dist/client/index.js +93 -85
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/codegen/generators/index.d.ts +19 -0
  10. package/dist/codegen/generators/index.js +1500 -0
  11. package/dist/codegen/generators/index.js.map +1 -0
  12. package/dist/codegen/index.d.ts +76 -60
  13. package/dist/codegen/index.js +1486 -736
  14. package/dist/codegen/index.js.map +1 -1
  15. package/dist/database-errors-BNNmLTJE.d.ts +86 -0
  16. package/dist/db/index.d.ts +844 -44
  17. package/dist/db/index.js +1262 -1309
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/env/index.d.ts +508 -0
  20. package/dist/env/index.js +1106 -0
  21. package/dist/env/index.js.map +1 -0
  22. package/dist/error-handler-wjLL3v-a.d.ts +44 -0
  23. package/dist/errors/index.d.ts +136 -0
  24. package/dist/errors/index.js +172 -0
  25. package/dist/errors/index.js.map +1 -0
  26. package/dist/index-DHiAqhKv.d.ts +101 -0
  27. package/dist/index.d.ts +3 -374
  28. package/dist/index.js +2404 -2179
  29. package/dist/index.js.map +1 -1
  30. package/dist/logger/index.d.ts +94 -0
  31. package/dist/logger/index.js +774 -0
  32. package/dist/logger/index.js.map +1 -0
  33. package/dist/middleware/index.d.ts +33 -0
  34. package/dist/middleware/index.js +897 -0
  35. package/dist/middleware/index.js.map +1 -0
  36. package/dist/route/index.d.ts +21 -53
  37. package/dist/route/index.js +1238 -219
  38. package/dist/route/index.js.map +1 -1
  39. package/dist/server/index.d.ts +18 -0
  40. package/dist/server/index.js +2400 -2061
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/types-DYueuoD6.d.ts +162 -0
  43. package/package.json +59 -15
  44. package/dist/auto-loader-C44TcLmM.d.ts +0 -125
  45. package/dist/bind-pssq1NRT.d.ts +0 -34
  46. package/dist/postgres-errors-CY_Es8EJ.d.ts +0 -1703
  47. package/dist/scripts/index.d.ts +0 -24
  48. package/dist/scripts/index.js +0 -1201
  49. package/dist/scripts/index.js.map +0 -1
  50. package/dist/scripts/templates/api-index.template.txt +0 -10
  51. package/dist/scripts/templates/api-tag.template.txt +0 -11
  52. package/dist/scripts/templates/contract.template.txt +0 -87
  53. package/dist/scripts/templates/entity-type.template.txt +0 -31
  54. package/dist/scripts/templates/entity.template.txt +0 -19
  55. package/dist/scripts/templates/index.template.txt +0 -10
  56. package/dist/scripts/templates/repository.template.txt +0 -37
  57. package/dist/scripts/templates/routes-id.template.txt +0 -59
  58. package/dist/scripts/templates/routes-index.template.txt +0 -44
  59. 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 };