@spfn/core 0.1.0-alpha.88 → 0.2.0-beta.1

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 (71) hide show
  1. package/README.md +1046 -384
  2. package/dist/boss-D-fGtVgM.d.ts +187 -0
  3. package/dist/cache/index.d.ts +13 -33
  4. package/dist/cache/index.js +14 -703
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +167 -17
  7. package/dist/codegen/index.js +76 -1419
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +1191 -0
  10. package/dist/config/index.js +264 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/db/index.d.ts +728 -59
  13. package/dist/db/index.js +1028 -1225
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +579 -308
  16. package/dist/env/index.js +438 -930
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/errors/index.d.ts +417 -29
  19. package/dist/errors/index.js +359 -98
  20. package/dist/errors/index.js.map +1 -1
  21. package/dist/event/index.d.ts +108 -0
  22. package/dist/event/index.js +122 -0
  23. package/dist/event/index.js.map +1 -0
  24. package/dist/job/index.d.ts +172 -0
  25. package/dist/job/index.js +361 -0
  26. package/dist/job/index.js.map +1 -0
  27. package/dist/logger/index.d.ts +20 -79
  28. package/dist/logger/index.js +82 -387
  29. package/dist/logger/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +2 -11
  31. package/dist/middleware/index.js +49 -703
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/nextjs/index.d.ts +120 -0
  34. package/dist/nextjs/index.js +416 -0
  35. package/dist/nextjs/index.js.map +1 -0
  36. package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +288 -262
  37. package/dist/nextjs/server.js +568 -0
  38. package/dist/nextjs/server.js.map +1 -0
  39. package/dist/route/index.d.ts +667 -25
  40. package/dist/route/index.js +437 -1287
  41. package/dist/route/index.js.map +1 -1
  42. package/dist/route/types.d.ts +38 -0
  43. package/dist/route/types.js +3 -0
  44. package/dist/route/types.js.map +1 -0
  45. package/dist/server/index.d.ts +201 -67
  46. package/dist/server/index.js +921 -3182
  47. package/dist/server/index.js.map +1 -1
  48. package/dist/types-BGl4QL1w.d.ts +77 -0
  49. package/dist/types-DRG2XMTR.d.ts +157 -0
  50. package/package.json +52 -47
  51. package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
  52. package/dist/client/index.d.ts +0 -358
  53. package/dist/client/index.js +0 -357
  54. package/dist/client/index.js.map +0 -1
  55. package/dist/client/nextjs/index.js +0 -371
  56. package/dist/client/nextjs/index.js.map +0 -1
  57. package/dist/codegen/generators/index.d.ts +0 -19
  58. package/dist/codegen/generators/index.js +0 -1404
  59. package/dist/codegen/generators/index.js.map +0 -1
  60. package/dist/database-errors-BNNmLTJE.d.ts +0 -86
  61. package/dist/events/index.d.ts +0 -183
  62. package/dist/events/index.js +0 -77
  63. package/dist/events/index.js.map +0 -1
  64. package/dist/index-DHiAqhKv.d.ts +0 -101
  65. package/dist/index.d.ts +0 -8
  66. package/dist/index.js +0 -3674
  67. package/dist/index.js.map +0 -1
  68. package/dist/types/index.d.ts +0 -121
  69. package/dist/types/index.js +0 -38
  70. package/dist/types/index.js.map +0 -1
  71. package/dist/types-BXibIEyj.d.ts +0 -60
@@ -4,22 +4,23 @@ import * as drizzle_orm from 'drizzle-orm';
4
4
  import { SQL } from 'drizzle-orm';
5
5
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
6
6
  import { PgColumn, PgTable } from 'drizzle-orm/pg-core';
7
- import * as hono from 'hono';
8
- import { D as DatabaseError } from '../database-errors-BNNmLTJE.js';
7
+ import * as hono_types from 'hono/types';
8
+ import { DatabaseError } from '@spfn/core/errors';
9
9
 
10
10
  /**
11
11
  * Database Configuration
12
12
  *
13
- * DB 연결 Connection Pool 설정
13
+ * Database connection and connection pool configuration.
14
14
  *
15
- * ✅ 구현 완료:
16
- * - 환경별 Connection Pool 설정
17
- * - 재시도 설정 (Exponential Backoff)
18
- * - 환경변수 기반 설정
19
- *
20
- * 🔗 관련 파일:
21
- * - src/server/core/db/connection.ts (연결 로직)
22
- * - src/server/core/db/index.ts (메인 export)
15
+ * Features:
16
+ * - Environment-specific connection pool configuration
17
+ * - Retry configuration with exponential backoff
18
+ * - Environment variable-based configuration
19
+ * - Health check and monitoring configuration
20
+ *
21
+ * Related files:
22
+ * - src/server/core/db/connection.ts (connection logic)
23
+ * - src/server/core/db/index.ts (main exports)
23
24
  */
24
25
 
25
26
  interface DatabaseClients {
@@ -71,19 +72,29 @@ interface DatabaseOptions {
71
72
  monitoring?: Partial<MonitoringConfig>;
72
73
  }
73
74
  /**
74
- * Connection Pool 설정
75
+ * Connection pool configuration
76
+ *
77
+ * Controls the maximum number of connections and idle timeout behavior.
75
78
  */
76
79
  interface PoolConfig {
80
+ /** Maximum number of connections in the pool */
77
81
  max: number;
82
+ /** Idle connection timeout in seconds */
78
83
  idleTimeout: number;
79
84
  }
80
85
  /**
81
- * 재시도 설정
86
+ * Retry configuration for exponential backoff algorithm
87
+ *
88
+ * Controls retry behavior when connection attempts fail.
82
89
  */
83
90
  interface RetryConfig {
91
+ /** Maximum number of retry attempts */
84
92
  maxRetries: number;
93
+ /** Initial delay between retries in milliseconds */
85
94
  initialDelay: number;
95
+ /** Maximum delay cap in milliseconds */
86
96
  maxDelay: number;
97
+ /** Exponential backoff factor (delay multiplier) */
87
98
  factor: number;
88
99
  }
89
100
 
@@ -96,12 +107,12 @@ interface RetryConfig {
96
107
  * Create database client(s) from environment variables
97
108
  *
98
109
  * Supported patterns (priority order):
99
- * 1. Single primary: DATABASE_URL
100
- * 2. Primary + Replica: DATABASE_WRITE_URL + DATABASE_READ_URL
101
- * 3. Legacy replica: DATABASE_URL + DATABASE_REPLICA_URL
110
+ * 1. Primary + Replica: DATABASE_WRITE_URL + DATABASE_READ_URL
111
+ * 2. Single primary: DATABASE_URL
102
112
  *
103
113
  * @param options - Optional database configuration (pool settings, etc.)
104
- * @returns Database client(s) or undefined if no configuration found
114
+ * @returns Database client(s)
115
+ * @throws {Error} If no database configuration is found or connection fails
105
116
  *
106
117
  * @example
107
118
  * ```bash
@@ -111,10 +122,6 @@ interface RetryConfig {
111
122
  * # Primary + Replica
112
123
  * DATABASE_WRITE_URL=postgresql://primary:5432/mydb
113
124
  * DATABASE_READ_URL=postgresql://replica:5432/mydb
114
- *
115
- * # Legacy (backward compatibility)
116
- * DATABASE_URL=postgresql://primary:5432/mydb
117
- * DATABASE_REPLICA_URL=postgresql://replica:5432/mydb
118
125
  * ```
119
126
  *
120
127
  * @example
@@ -128,36 +135,54 @@ interface RetryConfig {
128
135
  declare function createDatabaseFromEnv(options?: DatabaseOptions): Promise<DatabaseClients>;
129
136
 
130
137
  /**
131
- * Global Database instance manager
132
- * Provides singleton access to database across all modules
133
- * Supports Primary + Replica pattern with separate read/write instances
138
+ * Database Manager Types
134
139
  */
135
140
 
136
141
  /**
137
142
  * DB connection type
138
143
  */
139
144
  type DbConnectionType = 'read' | 'write';
145
+
146
+ /**
147
+ * Global Database instance manager
148
+ * Provides singleton access to database across all modules
149
+ * Supports Primary + Replica pattern with separate read/write instances
150
+ */
151
+
140
152
  /**
141
- * Get global database write instance
153
+ * Get global database instance
142
154
  *
143
- * @returns Database write instance or undefined if not initialized
155
+ * @param type - Connection type ('read' or 'write', defaults to 'write')
156
+ * @returns Database instance (never undefined)
157
+ * @throws Error if database is not initialized
144
158
  *
145
159
  * @example
146
160
  * ```typescript
147
161
  * import { getDatabase } from '@spfn/core/db';
148
162
  *
163
+ * // Always returns a valid instance or throws
149
164
  * const db = getDatabase();
150
- * if (db) {
151
- * const users = await db.select().from(usersTable);
152
- * }
165
+ * const users = await db.select().from(usersTable);
166
+ *
167
+ * // For read operations (uses replica if available, falls back to primary)
168
+ * const dbRead = getDatabase('read');
169
+ * const posts = await dbRead.select().from(postsTable);
153
170
  * ```
154
171
  */
155
- declare function getDatabase(type?: DbConnectionType): PostgresJsDatabase<Record<string, unknown>> | undefined;
172
+ declare function getDatabase(type?: DbConnectionType): PostgresJsDatabase<Record<string, unknown>>;
156
173
  /**
157
174
  * Set global database instances (for testing or manual configuration)
158
175
  *
159
- * @param write - Database write instance
160
- * @param read - Database read instance (optional, defaults to write)
176
+ * This function directly sets database instances without creating connections
177
+ * or performing validation. It's primarily intended for testing scenarios.
178
+ *
179
+ * @param write - Database write instance (pass undefined to clear)
180
+ * @param read - Database read instance (optional, defaults to write, pass undefined to clear)
181
+ *
182
+ * @remarks
183
+ * **Important:** To properly close database connections with cleanup, use `closeDatabase()` instead.
184
+ * This function only updates the global instances without closing the underlying connections.
185
+ * Setting both to undefined will clear the instances but leave connections open, which may cause resource leaks.
161
186
  *
162
187
  * @example
163
188
  * ```typescript
@@ -165,9 +190,13 @@ declare function getDatabase(type?: DbConnectionType): PostgresJsDatabase<Record
165
190
  * import { drizzle } from 'drizzle-orm/postgres-js';
166
191
  * import postgres from 'postgres';
167
192
  *
193
+ * // Set custom database instances (testing)
168
194
  * const writeClient = postgres('postgresql://primary:5432/mydb');
169
195
  * const readClient = postgres('postgresql://replica:5432/mydb');
170
196
  * setDatabase(drizzle(writeClient), drizzle(readClient));
197
+ *
198
+ * // Clear instances (not recommended - use closeDatabase() instead)
199
+ * setDatabase(undefined, undefined);
171
200
  * ```
172
201
  */
173
202
  declare function setDatabase(write: PostgresJsDatabase<Record<string, unknown>> | undefined, read?: PostgresJsDatabase<Record<string, unknown>> | undefined): void;
@@ -178,7 +207,6 @@ declare function setDatabase(write: PostgresJsDatabase<Record<string, unknown>>
178
207
  * Supported environment variables:
179
208
  * - DATABASE_URL (single primary)
180
209
  * - DATABASE_WRITE_URL + DATABASE_READ_URL (primary + replica)
181
- * - DATABASE_URL + DATABASE_REPLICA_URL (legacy replica)
182
210
  * - DB_POOL_MAX (connection pool max size)
183
211
  * - DB_POOL_IDLE_TIMEOUT (connection idle timeout in seconds)
184
212
  * - DB_HEALTH_CHECK_ENABLED (enable health checks, default: true)
@@ -247,6 +275,32 @@ declare function initDatabase(options?: DatabaseOptions): Promise<{
247
275
  declare function closeDatabase(): Promise<void>;
248
276
  /**
249
277
  * Get database connection info (for debugging)
278
+ *
279
+ * Returns the current state of database connections without throwing errors.
280
+ * Useful for health checks, monitoring, and debugging initialization issues.
281
+ *
282
+ * @returns Connection status information
283
+ * - `hasWrite`: Whether write database instance is initialized
284
+ * - `hasRead`: Whether read database instance is initialized
285
+ * - `isReplica`: Whether read and write are different instances (Primary + Replica setup)
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * import { getDatabaseInfo } from '@spfn/core/db';
290
+ *
291
+ * const info = getDatabaseInfo();
292
+ * console.log(`Write: ${info.hasWrite}, Read: ${info.hasRead}, Replica: ${info.isReplica}`);
293
+ *
294
+ * // Check before using database
295
+ * if (!info.hasWrite) {
296
+ * console.warn('Database not initialized');
297
+ * }
298
+ *
299
+ * // Detect Primary + Replica setup
300
+ * if (info.isReplica) {
301
+ * console.log('Using Primary + Replica configuration');
302
+ * }
303
+ * ```
250
304
  */
251
305
  declare function getDatabaseInfo(): {
252
306
  hasWrite: boolean;
@@ -255,19 +309,49 @@ declare function getDatabaseInfo(): {
255
309
  };
256
310
 
257
311
  /**
258
- * Exponential Backoff로 DB 연결 생성
312
+ * Create database connection with exponential backoff retry strategy
313
+ *
314
+ * Attempts to establish a database connection with automatic retries using
315
+ * exponential backoff with jitter. Non-retryable errors (authentication,
316
+ * database not found, SSL issues) fail immediately.
317
+ *
318
+ * Retry Strategy:
319
+ * - Exponential backoff: delay = initialDelay * (factor ^ attempt)
320
+ * - Jitter: randomized delay between 50-100% of calculated delay
321
+ * - Max delay cap: prevents excessive wait times
259
322
  *
260
- * @param connectionString - PostgreSQL 연결 문자열
261
- * @param poolConfig - Connection Pool 설정
262
- * @param retryConfig - 재시도 설정
263
- * @returns PostgreSQL 클라이언트
323
+ * @param connectionString - PostgreSQL connection string
324
+ * @param poolConfig - Connection pool configuration
325
+ * @param retryConfig - Retry configuration (max attempts, delays, etc.)
326
+ * @returns PostgreSQL client instance
327
+ * @throws ConnectionError if connection fails after all retries or on non-retryable errors
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const client = await createDatabaseConnection(
332
+ * 'postgresql://localhost:5432/mydb',
333
+ * { max: 20, idleTimeout: 30 },
334
+ * { maxRetries: 5, initialDelay: 100, maxDelay: 10000, factor: 2 }
335
+ * );
336
+ * ```
264
337
  */
265
338
  declare function createDatabaseConnection(connectionString: string, poolConfig: PoolConfig, retryConfig: RetryConfig): Promise<postgres.Sql<{}>>;
266
339
  /**
267
- * DB 연결 상태 확인
340
+ * Check database connection health
341
+ *
342
+ * Uses the client's configured timeout settings (connect_timeout, etc.).
343
+ * Executes a simple SELECT query to verify the connection is alive.
268
344
  *
269
- * @param client - PostgreSQL 클라이언트
270
- * @returns 연결 가능 여부
345
+ * @param client - PostgreSQL client to check
346
+ * @returns true if connection is healthy, false otherwise
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * const isHealthy = await checkConnection(client);
351
+ * if (!isHealthy) {
352
+ * console.error('Database connection is down');
353
+ * }
354
+ * ```
271
355
  */
272
356
  declare function checkConnection(client: Sql): Promise<boolean>;
273
357
 
@@ -290,6 +374,8 @@ interface DrizzleConfigOptions {
290
374
  disablePackageDiscovery?: boolean;
291
375
  /** Only include schemas from specific package (e.g., '@spfn/cms') */
292
376
  packageFilter?: string;
377
+ /** Expand glob patterns to actual file paths (useful for Drizzle Studio) */
378
+ expandGlobs?: boolean;
293
379
  }
294
380
  /**
295
381
  * Detect database dialect from connection URL
@@ -348,32 +434,28 @@ declare function id(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_
348
434
  * Standard timestamp fields (createdAt, updatedAt)
349
435
  *
350
436
  * Both fields are timezone-aware, auto-set to current time on creation.
351
- * When autoUpdate is enabled, updatedAt will be automatically updated on record updates.
437
+ * updatedAt must be manually updated in your application code.
352
438
  *
353
- * @param options - Optional configuration
354
- * @param options.autoUpdate - Automatically update updatedAt on record updates (default: false)
355
439
  * @returns Object with createdAt and updatedAt columns
356
440
  *
357
441
  * @example
358
442
  * ```typescript
359
- * // Without auto-update
360
443
  * export const users = pgTable('users', {
361
444
  * id: id(),
362
445
  * email: text('email'),
363
446
  * ...timestamps(),
364
447
  * });
365
448
  *
366
- * // With auto-update
367
- * export const posts = pgTable('posts', {
368
- * id: id(),
369
- * title: text('title'),
370
- * ...timestamps({ autoUpdate: true }),
371
- * });
449
+ * // Manual update
450
+ * await db.update(users)
451
+ * .set({
452
+ * email: 'new@example.com',
453
+ * updatedAt: new Date()
454
+ * })
455
+ * .where(eq(users.id, userId));
372
456
  * ```
373
457
  */
374
- declare function timestamps(options?: {
375
- autoUpdate?: boolean;
376
- }): {
458
+ declare function timestamps(): {
377
459
  createdAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"created_at">>>;
378
460
  updatedAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"updated_at">>>;
379
461
  };
@@ -421,6 +503,256 @@ declare function foreignKey<T extends PgColumn>(name: string, reference: () => T
421
503
  declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
422
504
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
423
505
  }): drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>;
506
+ /**
507
+ * UUID primary key
508
+ *
509
+ * Creates a UUID column as primary key with automatic default value generation.
510
+ * Useful for distributed systems or when you need globally unique identifiers.
511
+ *
512
+ * @returns uuid primary key column with gen_random_uuid() default
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * export const sessions = pgTable('sessions', {
517
+ * id: uuid(),
518
+ * userId: foreignKey('user', () => users.id),
519
+ * ...timestamps(),
520
+ * });
521
+ * ```
522
+ */
523
+ declare function uuid(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgUUIDBuilderInitial<"id">>>>;
524
+ /**
525
+ * Audit fields for tracking record creators and updaters
526
+ *
527
+ * Adds createdBy and updatedBy fields for user tracking.
528
+ * Typically stores user IDs, emails, or usernames.
529
+ *
530
+ * @returns Object with createdBy and updatedBy columns
531
+ *
532
+ * @example
533
+ * ```typescript
534
+ * export const posts = pgTable('posts', {
535
+ * id: id(),
536
+ * title: text('title'),
537
+ * ...timestamps(),
538
+ * ...auditFields(),
539
+ * });
540
+ *
541
+ * // Usage in route
542
+ * await db.insert(posts).values({
543
+ * title: 'New Post',
544
+ * createdBy: currentUser.email,
545
+ * });
546
+ * ```
547
+ */
548
+ declare function auditFields(): {
549
+ createdBy: drizzle_orm_pg_core.PgTextBuilderInitial<"created_by", [string, ...string[]]>;
550
+ updatedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"updated_by", [string, ...string[]]>;
551
+ };
552
+ /**
553
+ * Publishing fields for content management
554
+ *
555
+ * Tracks when and by whom content was published.
556
+ * Useful for CMS, blog posts, articles, etc.
557
+ *
558
+ * @returns Object with publishedAt and publishedBy columns
559
+ *
560
+ * @example
561
+ * ```typescript
562
+ * export const articles = pgTable('articles', {
563
+ * id: id(),
564
+ * title: text('title'),
565
+ * status: text('status'), // draft/published/archived
566
+ * ...publishingFields(),
567
+ * ...timestamps(),
568
+ * });
569
+ *
570
+ * // Publishing an article
571
+ * await db.update(articles)
572
+ * .set({
573
+ * status: 'published',
574
+ * publishedAt: new Date(),
575
+ * publishedBy: currentUser.email,
576
+ * })
577
+ * .where(eq(articles.id, articleId));
578
+ * ```
579
+ */
580
+ declare function publishingFields(): {
581
+ publishedAt: drizzle_orm_pg_core.PgTimestampBuilderInitial<"published_at">;
582
+ publishedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"published_by", [string, ...string[]]>;
583
+ };
584
+ /**
585
+ * Custom verification timestamp field
586
+ *
587
+ * Creates a nullable timestamp field for tracking verification status.
588
+ * Useful for email verification, phone verification, etc.
589
+ *
590
+ * @param fieldName - Field name in camelCase (e.g., 'emailVerified', 'phoneVerified')
591
+ * @returns Object with verification timestamp column (converts to snake_case + '_at')
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * export const users = pgTable('users', {
596
+ * id: id(),
597
+ * email: text('email'),
598
+ * phone: text('phone'),
599
+ * ...verificationTimestamp('emailVerified'), // emailVerifiedAt -> email_verified_at
600
+ * ...verificationTimestamp('phoneVerified'), // phoneVerifiedAt -> phone_verified_at
601
+ * ...timestamps(),
602
+ * });
603
+ *
604
+ * // Verify email
605
+ * await db.update(users)
606
+ * .set({ emailVerifiedAt: new Date() })
607
+ * .where(eq(users.email, userEmail));
608
+ * ```
609
+ */
610
+ declare function verificationTimestamp(fieldName: string): {
611
+ [x: string]: drizzle_orm_pg_core.PgTimestampBuilderInitial<string>;
612
+ };
613
+ /**
614
+ * Soft delete fields
615
+ *
616
+ * Adds deletedAt and deletedBy for logical deletion.
617
+ * Records are marked as deleted instead of being physically removed.
618
+ *
619
+ * @returns Object with deletedAt and deletedBy columns
620
+ *
621
+ * @example
622
+ * ```typescript
623
+ * export const posts = pgTable('posts', {
624
+ * id: id(),
625
+ * title: text('title'),
626
+ * ...timestamps(),
627
+ * ...softDelete(),
628
+ * });
629
+ *
630
+ * // Soft delete
631
+ * await db.update(posts)
632
+ * .set({
633
+ * deletedAt: new Date(),
634
+ * deletedBy: currentUser.email,
635
+ * })
636
+ * .where(eq(posts.id, postId));
637
+ *
638
+ * // Query only non-deleted records
639
+ * const activePosts = await db.select()
640
+ * .from(posts)
641
+ * .where(isNull(posts.deletedAt));
642
+ * ```
643
+ */
644
+ declare function softDelete(): {
645
+ deletedAt: drizzle_orm_pg_core.PgTimestampBuilderInitial<"deleted_at">;
646
+ deletedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"deleted_by", [string, ...string[]]>;
647
+ };
648
+ /**
649
+ * UTC timestamp field
650
+ *
651
+ * Creates a timezone-aware timestamp column (TIMESTAMPTZ) that stores values in UTC.
652
+ * PostgreSQL automatically converts between client timezone and UTC.
653
+ * Chain with .notNull(), .defaultNow(), etc. for additional constraints.
654
+ *
655
+ * @param fieldName - Database column name (in snake_case)
656
+ * @param mode - Data type mode: 'date' (Date object) or 'string' (ISO string)
657
+ * @returns Timestamp column with timezone support
658
+ *
659
+ * @example
660
+ * ```typescript
661
+ * export const users = pgTable('users', {
662
+ * id: id(),
663
+ * email: text('email'),
664
+ *
665
+ * // Nullable timestamp
666
+ * emailVerifiedAt: utcTimestamp('email_verified_at'),
667
+ *
668
+ * // Required with default
669
+ * lastLoginAt: utcTimestamp('last_login_at').defaultNow().notNull(),
670
+ *
671
+ * // String mode
672
+ * processedAt: utcTimestamp('processed_at', 'string'),
673
+ *
674
+ * ...timestamps(),
675
+ * });
676
+ * ```
677
+ */
678
+ declare function utcTimestamp(fieldName: string, mode?: 'date' | 'string'): drizzle_orm_pg_core.PgTimestampBuilderInitial<string>;
679
+ /**
680
+ * Type-safe enum text field
681
+ *
682
+ * Creates a text column with enum constraint.
683
+ * Chain with .notNull(), .default(), etc. for additional constraints.
684
+ *
685
+ * @param fieldName - Database column name (e.g., 'status', 'role', 'provider')
686
+ * @param values - Const array of allowed values
687
+ * @returns Text column with enum constraint
688
+ *
689
+ * @example
690
+ * ```typescript
691
+ * export const USER_STATUSES = ['active', 'inactive', 'suspended'] as const;
692
+ * export type UserStatus = typeof USER_STATUSES[number];
693
+ *
694
+ * export const SOCIAL_PROVIDERS = ['google', 'github', 'kakao'] as const;
695
+ * export type SocialProvider = typeof SOCIAL_PROVIDERS[number];
696
+ *
697
+ * export const users = pgTable('users', {
698
+ * id: id(),
699
+ * // Nullable
700
+ * role: enumText('role', USER_STATUSES),
701
+ *
702
+ * // Required
703
+ * provider: enumText('provider', SOCIAL_PROVIDERS).notNull(),
704
+ *
705
+ * // Required with default
706
+ * status: enumText('status', USER_STATUSES).default('active').notNull(),
707
+ *
708
+ * ...timestamps(),
709
+ * });
710
+ * ```
711
+ */
712
+ declare function enumText<T extends readonly [string, ...string[]]>(fieldName: string, values: T): drizzle_orm_pg_core.PgTextBuilderInitial<string, drizzle_orm.Writable<T & [string, ...string[]]>>;
713
+ /**
714
+ * Type-safe JSONB field
715
+ *
716
+ * Creates a JSONB column with required type parameter to ensure type safety.
717
+ * Prevents the common mistake of using jsonb without type annotation,
718
+ * which would result in `unknown` type and require type assertions.
719
+ *
720
+ * Chain with .notNull(), .default(), etc. for additional constraints.
721
+ *
722
+ * @param fieldName - Database column name (in snake_case)
723
+ * @returns JSONB column with specified type
724
+ *
725
+ * @example
726
+ * ```typescript
727
+ * // Define your types
728
+ * type LabelValue =
729
+ * | { type: 'text'; content: string }
730
+ * | { type: 'image'; url: string; alt?: string };
731
+ *
732
+ * type CachedContent = Record<string, LabelValue>;
733
+ *
734
+ * export const cmsPublishedCache = pgTable('published_cache', {
735
+ * id: id(),
736
+ * section: text('section').notNull(),
737
+ *
738
+ * // Type-safe JSONB field - no more `as any` needed!
739
+ * content: typedJsonb<CachedContent>('content').notNull(),
740
+ *
741
+ * ...timestamps(),
742
+ * });
743
+ *
744
+ * // Usage in route - no type assertion needed
745
+ * const cache = await db.select().from(cmsPublishedCache)...;
746
+ * const labels = cache.content; // Type: CachedContent ✅
747
+ *
748
+ * // For simple objects
749
+ * metadata: typedJsonb<Record<string, any>>('metadata'),
750
+ *
751
+ * // For arrays
752
+ * tags: typedJsonb<string[]>('tags').notNull(),
753
+ * ```
754
+ */
755
+ declare function typedJsonb<T>(fieldName: string): drizzle_orm.$Type<drizzle_orm_pg_core.PgJsonbBuilderInitial<string>, T>;
424
756
 
425
757
  /**
426
758
  * Database Schema Helper
@@ -436,9 +768,9 @@ declare function optionalForeignKey<T extends PgColumn>(name: string, reference:
436
768
  * @example
437
769
  * ```typescript
438
770
  * // @spfn/cms → spfn_cms schema
439
- * import { createFunctionSchema } from '@spfn/core/db';
771
+ * import { createSchema } from '@spfn/core/db';
440
772
  *
441
- * const schema = createFunctionSchema('@spfn/cms');
773
+ * const schema = createSchema('@spfn/cms');
442
774
  *
443
775
  * export const labels = schema.table('labels', {
444
776
  * id: id(),
@@ -447,7 +779,7 @@ declare function optionalForeignKey<T extends PgColumn>(name: string, reference:
447
779
  * // Creates table: spfn_cms.labels
448
780
  * ```
449
781
  */
450
- declare function createFunctionSchema(packageName: string): drizzle_orm_pg_core.PgSchema<string>;
782
+ declare function createSchema(packageName: string): drizzle_orm_pg_core.PgSchema<string>;
451
783
  /**
452
784
  * Convert package name to PostgreSQL schema name
453
785
  *
@@ -608,7 +940,7 @@ interface TransactionalOptions {
608
940
  * - Measures and records execution time
609
941
  * - Warns about slow transactions (default: > 1s)
610
942
  */
611
- declare function Transactional(options?: TransactionalOptions): hono.MiddlewareHandler<any, string, {}>;
943
+ declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
612
944
 
613
945
  /**
614
946
  * PostgreSQL Error Conversion Utilities
@@ -881,4 +1213,341 @@ declare function deleteMany<T extends PgTable>(table: T, where: WhereObject<Infe
881
1213
  */
882
1214
  declare function count<T extends PgTable>(table: T, where?: WhereObject<InferSelectModel<T>> | SQL | undefined): Promise<number>;
883
1215
 
884
- export { type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createFunctionSchema, createMany, deleteMany, deleteOne, detectDialect, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, optionalForeignKey, packageNameToSchema, runWithTransaction, setDatabase, timestamps, updateMany, updateOne, upsert };
1216
+ /**
1217
+ * Base Repository Pattern
1218
+ *
1219
+ * Provides automatic database instance management with transaction context support.
1220
+ * Eliminates the need for manual `getDatabaseOrThrow()` calls in repository classes.
1221
+ *
1222
+ * Features:
1223
+ * - Automatic transaction context detection and usage
1224
+ * - Read/Write connection separation (with replica support)
1225
+ * - Type-safe schema generics
1226
+ * - Consistent database access pattern across all repositories
1227
+ * - Enhanced error tracking with repository context
1228
+ *
1229
+ * @example Basic Repository
1230
+ * ```typescript
1231
+ * import { BaseRepository } from '@spfn/core/db';
1232
+ * import { users } from './schema';
1233
+ * import { eq } from 'drizzle-orm';
1234
+ *
1235
+ * export class UserRepository extends BaseRepository {
1236
+ * async findById(id: string) {
1237
+ * // Uses read replica when available
1238
+ * return await this.readDb
1239
+ * .select()
1240
+ * .from(users)
1241
+ * .where(eq(users.id, id));
1242
+ * }
1243
+ *
1244
+ * async create(data: NewUser) {
1245
+ * // Uses write primary
1246
+ * return await this.db
1247
+ * .insert(users)
1248
+ * .values(data)
1249
+ * .returning();
1250
+ * }
1251
+ * }
1252
+ * ```
1253
+ *
1254
+ * @example With Transactions
1255
+ * ```typescript
1256
+ * import { runWithTransaction } from '@spfn/core/db';
1257
+ *
1258
+ * const userRepo = new UserRepository();
1259
+ *
1260
+ * await runWithTransaction(async () => {
1261
+ * // Both db and readDb automatically use the transaction context
1262
+ * const user = await userRepo.create({ name: 'John' });
1263
+ * await userRepo.findById(user.id); // Uses same transaction
1264
+ * });
1265
+ * ```
1266
+ *
1267
+ * @example With Custom Schema Type
1268
+ * ```typescript
1269
+ * import type { AppSchema } from './schema';
1270
+ *
1271
+ * export class UserRepository extends BaseRepository<AppSchema> {
1272
+ * // Now this.db and this.readDb are typed with AppSchema
1273
+ * }
1274
+ * ```
1275
+ *
1276
+ * @example With Enhanced Error Tracking
1277
+ * ```typescript
1278
+ * export class UserRepository extends BaseRepository {
1279
+ * async updateStatus(id: string, status: string) {
1280
+ * // Errors will include repository context automatically
1281
+ * return await this.db
1282
+ * .update(users)
1283
+ * .set({ status })
1284
+ * .where(eq(users.id, id))
1285
+ * .returning();
1286
+ * }
1287
+ * }
1288
+ * // On error: logs will show "UserRepository" context
1289
+ * ```
1290
+ */
1291
+
1292
+ /**
1293
+ * Enhanced error class that includes repository context
1294
+ */
1295
+ declare class RepositoryError extends Error {
1296
+ readonly repository: string;
1297
+ readonly method?: string | undefined;
1298
+ readonly table?: string | undefined;
1299
+ readonly originalError?: Error | undefined;
1300
+ constructor(message: string, repository: string, method?: string | undefined, table?: string | undefined, originalError?: Error | undefined);
1301
+ }
1302
+ /**
1303
+ * Base Repository class for database operations
1304
+ *
1305
+ * Provides automatic database instance management with transaction support.
1306
+ * Extend this class to create domain-specific repositories.
1307
+ *
1308
+ * The repository automatically detects if running within a transaction context
1309
+ * and uses the appropriate database instance:
1310
+ * - Inside transaction: Uses transaction DB
1311
+ * - Outside transaction: Uses global DB instance (with read/write separation)
1312
+ *
1313
+ * @template TSchema - Database schema type (defaults to Record<string, unknown>)
1314
+ */
1315
+ declare abstract class BaseRepository<TSchema extends Record<string, unknown> = Record<string, unknown>> {
1316
+ /**
1317
+ * Write database instance
1318
+ *
1319
+ * Automatically resolves to:
1320
+ * - Transaction DB if running within transaction context
1321
+ * - Global write (primary) instance otherwise
1322
+ *
1323
+ * Use this for INSERT, UPDATE, DELETE operations.
1324
+ *
1325
+ * @example
1326
+ * ```typescript
1327
+ * async create(data: NewUser) {
1328
+ * return await this.db.insert(users).values(data).returning();
1329
+ * }
1330
+ * ```
1331
+ */
1332
+ protected get db(): PostgresJsDatabase<TSchema>;
1333
+ /**
1334
+ * Read database instance
1335
+ *
1336
+ * Automatically resolves to:
1337
+ * - Transaction DB if running within transaction context
1338
+ * - Global read (replica) instance otherwise
1339
+ *
1340
+ * Use this for SELECT operations to leverage read replicas
1341
+ * and reduce load on the primary database.
1342
+ *
1343
+ * @example
1344
+ * ```typescript
1345
+ * async findById(id: string) {
1346
+ * return await this.readDb
1347
+ * .select()
1348
+ * .from(users)
1349
+ * .where(eq(users.id, id));
1350
+ * }
1351
+ * ```
1352
+ */
1353
+ protected get readDb(): PostgresJsDatabase<TSchema>;
1354
+ /**
1355
+ * Wrap query execution with repository context
1356
+ *
1357
+ * Enhances error messages with repository information to make debugging easier.
1358
+ * When an error occurs, it will include:
1359
+ * - Repository class name
1360
+ * - Method name
1361
+ * - Table name (if provided)
1362
+ * - Original error details
1363
+ *
1364
+ * @param queryFn - Query function to execute
1365
+ * @param context - Context information (operation name, table name, etc.)
1366
+ * @returns Query result
1367
+ * @throws RepositoryError with enhanced context
1368
+ *
1369
+ * @example
1370
+ * ```typescript
1371
+ * async findById(id: number) {
1372
+ * return await this.withContext(
1373
+ * () => this.readDb.select().from(users).where(eq(users.id, id)),
1374
+ * { method: 'findById', table: 'users' }
1375
+ * );
1376
+ * }
1377
+ * ```
1378
+ */
1379
+ protected withContext<T>(queryFn: () => Promise<T>, context?: {
1380
+ method?: string;
1381
+ table?: string;
1382
+ }): Promise<T>;
1383
+ /**
1384
+ * Find a single record
1385
+ *
1386
+ * @param table - Drizzle table schema
1387
+ * @param where - Object or SQL condition
1388
+ * @returns Single record or null
1389
+ *
1390
+ * @example
1391
+ * ```typescript
1392
+ * // Object-based
1393
+ * const user = await this._findOne(users, { id: 1 });
1394
+ *
1395
+ * // SQL-based
1396
+ * const user = await this._findOne(users, eq(users.id, 1));
1397
+ * ```
1398
+ */
1399
+ protected _findOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'] | null>;
1400
+ /**
1401
+ * Find multiple records
1402
+ *
1403
+ * @param table - Drizzle table schema
1404
+ * @param options - Query options
1405
+ * @returns Array of records
1406
+ *
1407
+ * @example
1408
+ * ```typescript
1409
+ * const users = await this._findMany(users, {
1410
+ * where: { active: true },
1411
+ * orderBy: desc(users.createdAt),
1412
+ * limit: 10
1413
+ * });
1414
+ * ```
1415
+ */
1416
+ protected _findMany<T extends PgTable>(table: T, options?: {
1417
+ where?: Record<string, any> | SQL | undefined;
1418
+ orderBy?: SQL | SQL[];
1419
+ limit?: number;
1420
+ offset?: number;
1421
+ }): Promise<T['$inferSelect'][]>;
1422
+ /**
1423
+ * Create a new record
1424
+ *
1425
+ * @param table - Drizzle table schema
1426
+ * @param data - Insert data
1427
+ * @returns Created record
1428
+ *
1429
+ * @example
1430
+ * ```typescript
1431
+ * const user = await this._create(users, {
1432
+ * email: 'test@example.com',
1433
+ * name: 'Test User'
1434
+ * });
1435
+ * ```
1436
+ */
1437
+ protected _create<T extends PgTable>(table: T, data: T['$inferInsert']): Promise<T['$inferSelect']>;
1438
+ /**
1439
+ * Create multiple records
1440
+ *
1441
+ * @param table - Drizzle table schema
1442
+ * @param data - Array of insert data
1443
+ * @returns Array of created records
1444
+ *
1445
+ * @example
1446
+ * ```typescript
1447
+ * const users = await this._createMany(users, [
1448
+ * { email: 'user1@example.com', name: 'User 1' },
1449
+ * { email: 'user2@example.com', name: 'User 2' }
1450
+ * ]);
1451
+ * ```
1452
+ */
1453
+ protected _createMany<T extends PgTable>(table: T, data: T['$inferInsert'][]): Promise<T['$inferSelect'][]>;
1454
+ /**
1455
+ * Upsert a record (INSERT or UPDATE on conflict)
1456
+ *
1457
+ * @param table - Drizzle table schema
1458
+ * @param data - Insert data
1459
+ * @param options - Conflict resolution options
1460
+ * @returns Upserted record
1461
+ *
1462
+ * @example
1463
+ * ```typescript
1464
+ * const cache = await this._upsert(cache, {
1465
+ * key: 'config',
1466
+ * value: {...}
1467
+ * }, {
1468
+ * target: [cache.key],
1469
+ * set: { value: data.value, updatedAt: new Date() }
1470
+ * });
1471
+ * ```
1472
+ */
1473
+ protected _upsert<T extends PgTable>(table: T, data: T['$inferInsert'], options: {
1474
+ target: PgColumn[];
1475
+ set?: Partial<T['$inferInsert']> | Record<string, SQL | any>;
1476
+ }): Promise<T['$inferSelect']>;
1477
+ /**
1478
+ * Update a single record
1479
+ *
1480
+ * @param table - Drizzle table schema
1481
+ * @param where - Object or SQL condition
1482
+ * @param data - Update data
1483
+ * @returns Updated record or null
1484
+ *
1485
+ * @example
1486
+ * ```typescript
1487
+ * const user = await this._updateOne(users,
1488
+ * { id: 1 },
1489
+ * { name: 'Updated Name' }
1490
+ * );
1491
+ * ```
1492
+ */
1493
+ protected _updateOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<T['$inferInsert']>): Promise<T['$inferSelect'] | null>;
1494
+ /**
1495
+ * Update multiple records
1496
+ *
1497
+ * @param table - Drizzle table schema
1498
+ * @param where - Object or SQL condition
1499
+ * @param data - Update data
1500
+ * @returns Array of updated records
1501
+ *
1502
+ * @example
1503
+ * ```typescript
1504
+ * const users = await this._updateMany(users,
1505
+ * { role: 'user' },
1506
+ * { verified: true }
1507
+ * );
1508
+ * ```
1509
+ */
1510
+ protected _updateMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<T['$inferInsert']>): Promise<T['$inferSelect'][]>;
1511
+ /**
1512
+ * Delete a single record
1513
+ *
1514
+ * @param table - Drizzle table schema
1515
+ * @param where - Object or SQL condition
1516
+ * @returns Deleted record or null
1517
+ *
1518
+ * @example
1519
+ * ```typescript
1520
+ * const user = await this._deleteOne(users, { id: 1 });
1521
+ * ```
1522
+ */
1523
+ protected _deleteOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'] | null>;
1524
+ /**
1525
+ * Delete multiple records
1526
+ *
1527
+ * @param table - Drizzle table schema
1528
+ * @param where - Object or SQL condition
1529
+ * @returns Array of deleted records
1530
+ *
1531
+ * @example
1532
+ * ```typescript
1533
+ * const users = await this._deleteMany(users, { verified: false });
1534
+ * ```
1535
+ */
1536
+ protected _deleteMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'][]>;
1537
+ /**
1538
+ * Count records
1539
+ *
1540
+ * @param table - Drizzle table schema
1541
+ * @param where - Optional object or SQL condition
1542
+ * @returns Number of records
1543
+ *
1544
+ * @example
1545
+ * ```typescript
1546
+ * const total = await this._count(users);
1547
+ * const activeUsers = await this._count(users, { active: true });
1548
+ * ```
1549
+ */
1550
+ protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1551
+ }
1552
+
1553
+ export { BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };