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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +298 -466
  2. package/dist/boss-DI1r4kTS.d.ts +244 -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 +214 -17
  7. package/dist/codegen/index.js +231 -1420
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +1227 -0
  10. package/dist/config/index.js +273 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/db/index.d.ts +741 -59
  13. package/dist/db/index.js +1063 -1226
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +658 -308
  16. package/dist/env/index.js +503 -928
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/env/loader.d.ts +87 -0
  19. package/dist/env/loader.js +70 -0
  20. package/dist/env/loader.js.map +1 -0
  21. package/dist/errors/index.d.ts +417 -29
  22. package/dist/errors/index.js +359 -98
  23. package/dist/errors/index.js.map +1 -1
  24. package/dist/event/index.d.ts +41 -0
  25. package/dist/event/index.js +131 -0
  26. package/dist/event/index.js.map +1 -0
  27. package/dist/event/sse/client.d.ts +82 -0
  28. package/dist/event/sse/client.js +115 -0
  29. package/dist/event/sse/client.js.map +1 -0
  30. package/dist/event/sse/index.d.ts +40 -0
  31. package/dist/event/sse/index.js +92 -0
  32. package/dist/event/sse/index.js.map +1 -0
  33. package/dist/job/index.d.ts +218 -0
  34. package/dist/job/index.js +410 -0
  35. package/dist/job/index.js.map +1 -0
  36. package/dist/logger/index.d.ts +20 -79
  37. package/dist/logger/index.js +82 -387
  38. package/dist/logger/index.js.map +1 -1
  39. package/dist/middleware/index.d.ts +102 -20
  40. package/dist/middleware/index.js +51 -705
  41. package/dist/middleware/index.js.map +1 -1
  42. package/dist/nextjs/index.d.ts +120 -0
  43. package/dist/nextjs/index.js +448 -0
  44. package/dist/nextjs/index.js.map +1 -0
  45. package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +335 -262
  46. package/dist/nextjs/server.js +637 -0
  47. package/dist/nextjs/server.js.map +1 -0
  48. package/dist/route/index.d.ts +879 -25
  49. package/dist/route/index.js +697 -1271
  50. package/dist/route/index.js.map +1 -1
  51. package/dist/route/types.d.ts +9 -0
  52. package/dist/route/types.js +3 -0
  53. package/dist/route/types.js.map +1 -0
  54. package/dist/router-Di7ENoah.d.ts +151 -0
  55. package/dist/server/index.d.ts +345 -64
  56. package/dist/server/index.js +1174 -3233
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/types-B-e_f2dQ.d.ts +121 -0
  59. package/dist/types-BGl4QL1w.d.ts +77 -0
  60. package/dist/types-BOPTApC2.d.ts +245 -0
  61. package/docs/cache.md +133 -0
  62. package/docs/codegen.md +74 -0
  63. package/docs/database.md +346 -0
  64. package/docs/entity.md +539 -0
  65. package/docs/env.md +477 -0
  66. package/docs/errors.md +319 -0
  67. package/docs/event.md +116 -0
  68. package/docs/file-upload.md +717 -0
  69. package/docs/job.md +131 -0
  70. package/docs/logger.md +108 -0
  71. package/docs/middleware.md +337 -0
  72. package/docs/nextjs.md +241 -0
  73. package/docs/repository.md +496 -0
  74. package/docs/route.md +497 -0
  75. package/docs/server.md +307 -0
  76. package/package.json +68 -48
  77. package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
  78. package/dist/client/index.d.ts +0 -358
  79. package/dist/client/index.js +0 -357
  80. package/dist/client/index.js.map +0 -1
  81. package/dist/client/nextjs/index.js +0 -371
  82. package/dist/client/nextjs/index.js.map +0 -1
  83. package/dist/codegen/generators/index.d.ts +0 -19
  84. package/dist/codegen/generators/index.js +0 -1404
  85. package/dist/codegen/generators/index.js.map +0 -1
  86. package/dist/database-errors-BNNmLTJE.d.ts +0 -86
  87. package/dist/events/index.d.ts +0 -183
  88. package/dist/events/index.js +0 -77
  89. package/dist/events/index.js.map +0 -1
  90. package/dist/index-DHiAqhKv.d.ts +0 -101
  91. package/dist/index.d.ts +0 -8
  92. package/dist/index.js +0 -3674
  93. package/dist/index.js.map +0 -1
  94. package/dist/types/index.d.ts +0 -121
  95. package/dist/types/index.js +0 -38
  96. package/dist/types/index.js.map +0 -1
  97. 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,12 @@ 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;
379
+ /** PostgreSQL schema filter for push/introspect commands */
380
+ schemaFilter?: string[];
381
+ /** Auto-detect PostgreSQL schemas from entity files (requires expandGlobs: true) */
382
+ autoDetectSchemas?: boolean;
293
383
  }
294
384
  /**
295
385
  * Detect database dialect from connection URL
@@ -321,6 +411,15 @@ declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
321
411
  dbCredentials: {
322
412
  url: string;
323
413
  };
414
+ schemaFilter?: undefined;
415
+ } | {
416
+ schema: string | string[];
417
+ out: string;
418
+ dialect: "postgresql" | "mysql" | "sqlite";
419
+ dbCredentials: {
420
+ url: string;
421
+ };
422
+ schemaFilter: string[] | undefined;
324
423
  };
325
424
  /**
326
425
  * Generate drizzle.config.ts file content
@@ -348,32 +447,28 @@ declare function id(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_
348
447
  * Standard timestamp fields (createdAt, updatedAt)
349
448
  *
350
449
  * 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.
450
+ * updatedAt must be manually updated in your application code.
352
451
  *
353
- * @param options - Optional configuration
354
- * @param options.autoUpdate - Automatically update updatedAt on record updates (default: false)
355
452
  * @returns Object with createdAt and updatedAt columns
356
453
  *
357
454
  * @example
358
455
  * ```typescript
359
- * // Without auto-update
360
456
  * export const users = pgTable('users', {
361
457
  * id: id(),
362
458
  * email: text('email'),
363
459
  * ...timestamps(),
364
460
  * });
365
461
  *
366
- * // With auto-update
367
- * export const posts = pgTable('posts', {
368
- * id: id(),
369
- * title: text('title'),
370
- * ...timestamps({ autoUpdate: true }),
371
- * });
462
+ * // Manual update
463
+ * await db.update(users)
464
+ * .set({
465
+ * email: 'new@example.com',
466
+ * updatedAt: new Date()
467
+ * })
468
+ * .where(eq(users.id, userId));
372
469
  * ```
373
470
  */
374
- declare function timestamps(options?: {
375
- autoUpdate?: boolean;
376
- }): {
471
+ declare function timestamps(): {
377
472
  createdAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"created_at">>>;
378
473
  updatedAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"updated_at">>>;
379
474
  };
@@ -421,6 +516,256 @@ declare function foreignKey<T extends PgColumn>(name: string, reference: () => T
421
516
  declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
422
517
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
423
518
  }): drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>;
519
+ /**
520
+ * UUID primary key
521
+ *
522
+ * Creates a UUID column as primary key with automatic default value generation.
523
+ * Useful for distributed systems or when you need globally unique identifiers.
524
+ *
525
+ * @returns uuid primary key column with gen_random_uuid() default
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * export const sessions = pgTable('sessions', {
530
+ * id: uuid(),
531
+ * userId: foreignKey('user', () => users.id),
532
+ * ...timestamps(),
533
+ * });
534
+ * ```
535
+ */
536
+ declare function uuid(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgUUIDBuilderInitial<"id">>>>;
537
+ /**
538
+ * Audit fields for tracking record creators and updaters
539
+ *
540
+ * Adds createdBy and updatedBy fields for user tracking.
541
+ * Typically stores user IDs, emails, or usernames.
542
+ *
543
+ * @returns Object with createdBy and updatedBy columns
544
+ *
545
+ * @example
546
+ * ```typescript
547
+ * export const posts = pgTable('posts', {
548
+ * id: id(),
549
+ * title: text('title'),
550
+ * ...timestamps(),
551
+ * ...auditFields(),
552
+ * });
553
+ *
554
+ * // Usage in route
555
+ * await db.insert(posts).values({
556
+ * title: 'New Post',
557
+ * createdBy: currentUser.email,
558
+ * });
559
+ * ```
560
+ */
561
+ declare function auditFields(): {
562
+ createdBy: drizzle_orm_pg_core.PgTextBuilderInitial<"created_by", [string, ...string[]]>;
563
+ updatedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"updated_by", [string, ...string[]]>;
564
+ };
565
+ /**
566
+ * Publishing fields for content management
567
+ *
568
+ * Tracks when and by whom content was published.
569
+ * Useful for CMS, blog posts, articles, etc.
570
+ *
571
+ * @returns Object with publishedAt and publishedBy columns
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * export const articles = pgTable('articles', {
576
+ * id: id(),
577
+ * title: text('title'),
578
+ * status: text('status'), // draft/published/archived
579
+ * ...publishingFields(),
580
+ * ...timestamps(),
581
+ * });
582
+ *
583
+ * // Publishing an article
584
+ * await db.update(articles)
585
+ * .set({
586
+ * status: 'published',
587
+ * publishedAt: new Date(),
588
+ * publishedBy: currentUser.email,
589
+ * })
590
+ * .where(eq(articles.id, articleId));
591
+ * ```
592
+ */
593
+ declare function publishingFields(): {
594
+ publishedAt: drizzle_orm_pg_core.PgTimestampBuilderInitial<"published_at">;
595
+ publishedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"published_by", [string, ...string[]]>;
596
+ };
597
+ /**
598
+ * Custom verification timestamp field
599
+ *
600
+ * Creates a nullable timestamp field for tracking verification status.
601
+ * Useful for email verification, phone verification, etc.
602
+ *
603
+ * @param fieldName - Field name in camelCase (e.g., 'emailVerified', 'phoneVerified')
604
+ * @returns Object with verification timestamp column (converts to snake_case + '_at')
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * export const users = pgTable('users', {
609
+ * id: id(),
610
+ * email: text('email'),
611
+ * phone: text('phone'),
612
+ * ...verificationTimestamp('emailVerified'), // emailVerifiedAt -> email_verified_at
613
+ * ...verificationTimestamp('phoneVerified'), // phoneVerifiedAt -> phone_verified_at
614
+ * ...timestamps(),
615
+ * });
616
+ *
617
+ * // Verify email
618
+ * await db.update(users)
619
+ * .set({ emailVerifiedAt: new Date() })
620
+ * .where(eq(users.email, userEmail));
621
+ * ```
622
+ */
623
+ declare function verificationTimestamp(fieldName: string): {
624
+ [x: string]: drizzle_orm_pg_core.PgTimestampBuilderInitial<string>;
625
+ };
626
+ /**
627
+ * Soft delete fields
628
+ *
629
+ * Adds deletedAt and deletedBy for logical deletion.
630
+ * Records are marked as deleted instead of being physically removed.
631
+ *
632
+ * @returns Object with deletedAt and deletedBy columns
633
+ *
634
+ * @example
635
+ * ```typescript
636
+ * export const posts = pgTable('posts', {
637
+ * id: id(),
638
+ * title: text('title'),
639
+ * ...timestamps(),
640
+ * ...softDelete(),
641
+ * });
642
+ *
643
+ * // Soft delete
644
+ * await db.update(posts)
645
+ * .set({
646
+ * deletedAt: new Date(),
647
+ * deletedBy: currentUser.email,
648
+ * })
649
+ * .where(eq(posts.id, postId));
650
+ *
651
+ * // Query only non-deleted records
652
+ * const activePosts = await db.select()
653
+ * .from(posts)
654
+ * .where(isNull(posts.deletedAt));
655
+ * ```
656
+ */
657
+ declare function softDelete(): {
658
+ deletedAt: drizzle_orm_pg_core.PgTimestampBuilderInitial<"deleted_at">;
659
+ deletedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"deleted_by", [string, ...string[]]>;
660
+ };
661
+ /**
662
+ * UTC timestamp field
663
+ *
664
+ * Creates a timezone-aware timestamp column (TIMESTAMPTZ) that stores values in UTC.
665
+ * PostgreSQL automatically converts between client timezone and UTC.
666
+ * Chain with .notNull(), .defaultNow(), etc. for additional constraints.
667
+ *
668
+ * @param fieldName - Database column name (in snake_case)
669
+ * @param mode - Data type mode: 'date' (Date object) or 'string' (ISO string)
670
+ * @returns Timestamp column with timezone support
671
+ *
672
+ * @example
673
+ * ```typescript
674
+ * export const users = pgTable('users', {
675
+ * id: id(),
676
+ * email: text('email'),
677
+ *
678
+ * // Nullable timestamp
679
+ * emailVerifiedAt: utcTimestamp('email_verified_at'),
680
+ *
681
+ * // Required with default
682
+ * lastLoginAt: utcTimestamp('last_login_at').defaultNow().notNull(),
683
+ *
684
+ * // String mode
685
+ * processedAt: utcTimestamp('processed_at', 'string'),
686
+ *
687
+ * ...timestamps(),
688
+ * });
689
+ * ```
690
+ */
691
+ declare function utcTimestamp(fieldName: string, mode?: 'date' | 'string'): drizzle_orm_pg_core.PgTimestampBuilderInitial<string>;
692
+ /**
693
+ * Type-safe enum text field
694
+ *
695
+ * Creates a text column with enum constraint.
696
+ * Chain with .notNull(), .default(), etc. for additional constraints.
697
+ *
698
+ * @param fieldName - Database column name (e.g., 'status', 'role', 'provider')
699
+ * @param values - Const array of allowed values
700
+ * @returns Text column with enum constraint
701
+ *
702
+ * @example
703
+ * ```typescript
704
+ * export const USER_STATUSES = ['active', 'inactive', 'suspended'] as const;
705
+ * export type UserStatus = typeof USER_STATUSES[number];
706
+ *
707
+ * export const SOCIAL_PROVIDERS = ['google', 'github', 'kakao'] as const;
708
+ * export type SocialProvider = typeof SOCIAL_PROVIDERS[number];
709
+ *
710
+ * export const users = pgTable('users', {
711
+ * id: id(),
712
+ * // Nullable
713
+ * role: enumText('role', USER_STATUSES),
714
+ *
715
+ * // Required
716
+ * provider: enumText('provider', SOCIAL_PROVIDERS).notNull(),
717
+ *
718
+ * // Required with default
719
+ * status: enumText('status', USER_STATUSES).default('active').notNull(),
720
+ *
721
+ * ...timestamps(),
722
+ * });
723
+ * ```
724
+ */
725
+ declare function enumText<T extends readonly [string, ...string[]]>(fieldName: string, values: T): drizzle_orm_pg_core.PgTextBuilderInitial<string, drizzle_orm.Writable<T & [string, ...string[]]>>;
726
+ /**
727
+ * Type-safe JSONB field
728
+ *
729
+ * Creates a JSONB column with required type parameter to ensure type safety.
730
+ * Prevents the common mistake of using jsonb without type annotation,
731
+ * which would result in `unknown` type and require type assertions.
732
+ *
733
+ * Chain with .notNull(), .default(), etc. for additional constraints.
734
+ *
735
+ * @param fieldName - Database column name (in snake_case)
736
+ * @returns JSONB column with specified type
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * // Define your types
741
+ * type LabelValue =
742
+ * | { type: 'text'; content: string }
743
+ * | { type: 'image'; url: string; alt?: string };
744
+ *
745
+ * type CachedContent = Record<string, LabelValue>;
746
+ *
747
+ * export const cmsPublishedCache = pgTable('published_cache', {
748
+ * id: id(),
749
+ * section: text('section').notNull(),
750
+ *
751
+ * // Type-safe JSONB field - no more `as any` needed!
752
+ * content: typedJsonb<CachedContent>('content').notNull(),
753
+ *
754
+ * ...timestamps(),
755
+ * });
756
+ *
757
+ * // Usage in route - no type assertion needed
758
+ * const cache = await db.select().from(cmsPublishedCache)...;
759
+ * const labels = cache.content; // Type: CachedContent ✅
760
+ *
761
+ * // For simple objects
762
+ * metadata: typedJsonb<Record<string, any>>('metadata'),
763
+ *
764
+ * // For arrays
765
+ * tags: typedJsonb<string[]>('tags').notNull(),
766
+ * ```
767
+ */
768
+ declare function typedJsonb<T>(fieldName: string): drizzle_orm.$Type<drizzle_orm_pg_core.PgJsonbBuilderInitial<string>, T>;
424
769
 
425
770
  /**
426
771
  * Database Schema Helper
@@ -436,9 +781,9 @@ declare function optionalForeignKey<T extends PgColumn>(name: string, reference:
436
781
  * @example
437
782
  * ```typescript
438
783
  * // @spfn/cms → spfn_cms schema
439
- * import { createFunctionSchema } from '@spfn/core/db';
784
+ * import { createSchema } from '@spfn/core/db';
440
785
  *
441
- * const schema = createFunctionSchema('@spfn/cms');
786
+ * const schema = createSchema('@spfn/cms');
442
787
  *
443
788
  * export const labels = schema.table('labels', {
444
789
  * id: id(),
@@ -447,7 +792,7 @@ declare function optionalForeignKey<T extends PgColumn>(name: string, reference:
447
792
  * // Creates table: spfn_cms.labels
448
793
  * ```
449
794
  */
450
- declare function createFunctionSchema(packageName: string): drizzle_orm_pg_core.PgSchema<string>;
795
+ declare function createSchema(packageName: string): drizzle_orm_pg_core.PgSchema<string>;
451
796
  /**
452
797
  * Convert package name to PostgreSQL schema name
453
798
  *
@@ -608,7 +953,7 @@ interface TransactionalOptions {
608
953
  * - Measures and records execution time
609
954
  * - Warns about slow transactions (default: > 1s)
610
955
  */
611
- declare function Transactional(options?: TransactionalOptions): hono.MiddlewareHandler<any, string, {}>;
956
+ declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
612
957
 
613
958
  /**
614
959
  * PostgreSQL Error Conversion Utilities
@@ -881,4 +1226,341 @@ declare function deleteMany<T extends PgTable>(table: T, where: WhereObject<Infe
881
1226
  */
882
1227
  declare function count<T extends PgTable>(table: T, where?: WhereObject<InferSelectModel<T>> | SQL | undefined): Promise<number>;
883
1228
 
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 };
1229
+ /**
1230
+ * Base Repository Pattern
1231
+ *
1232
+ * Provides automatic database instance management with transaction context support.
1233
+ * Eliminates the need for manual `getDatabaseOrThrow()` calls in repository classes.
1234
+ *
1235
+ * Features:
1236
+ * - Automatic transaction context detection and usage
1237
+ * - Read/Write connection separation (with replica support)
1238
+ * - Type-safe schema generics
1239
+ * - Consistent database access pattern across all repositories
1240
+ * - Enhanced error tracking with repository context
1241
+ *
1242
+ * @example Basic Repository
1243
+ * ```typescript
1244
+ * import { BaseRepository } from '@spfn/core/db';
1245
+ * import { users } from './schema';
1246
+ * import { eq } from 'drizzle-orm';
1247
+ *
1248
+ * export class UserRepository extends BaseRepository {
1249
+ * async findById(id: string) {
1250
+ * // Uses read replica when available
1251
+ * return await this.readDb
1252
+ * .select()
1253
+ * .from(users)
1254
+ * .where(eq(users.id, id));
1255
+ * }
1256
+ *
1257
+ * async create(data: NewUser) {
1258
+ * // Uses write primary
1259
+ * return await this.db
1260
+ * .insert(users)
1261
+ * .values(data)
1262
+ * .returning();
1263
+ * }
1264
+ * }
1265
+ * ```
1266
+ *
1267
+ * @example With Transactions
1268
+ * ```typescript
1269
+ * import { runWithTransaction } from '@spfn/core/db';
1270
+ *
1271
+ * const userRepo = new UserRepository();
1272
+ *
1273
+ * await runWithTransaction(async () => {
1274
+ * // Both db and readDb automatically use the transaction context
1275
+ * const user = await userRepo.create({ name: 'John' });
1276
+ * await userRepo.findById(user.id); // Uses same transaction
1277
+ * });
1278
+ * ```
1279
+ *
1280
+ * @example With Custom Schema Type
1281
+ * ```typescript
1282
+ * import type { AppSchema } from './schema';
1283
+ *
1284
+ * export class UserRepository extends BaseRepository<AppSchema> {
1285
+ * // Now this.db and this.readDb are typed with AppSchema
1286
+ * }
1287
+ * ```
1288
+ *
1289
+ * @example With Enhanced Error Tracking
1290
+ * ```typescript
1291
+ * export class UserRepository extends BaseRepository {
1292
+ * async updateStatus(id: string, status: string) {
1293
+ * // Errors will include repository context automatically
1294
+ * return await this.db
1295
+ * .update(users)
1296
+ * .set({ status })
1297
+ * .where(eq(users.id, id))
1298
+ * .returning();
1299
+ * }
1300
+ * }
1301
+ * // On error: logs will show "UserRepository" context
1302
+ * ```
1303
+ */
1304
+
1305
+ /**
1306
+ * Enhanced error class that includes repository context
1307
+ */
1308
+ declare class RepositoryError extends Error {
1309
+ readonly repository: string;
1310
+ readonly method?: string | undefined;
1311
+ readonly table?: string | undefined;
1312
+ readonly originalError?: Error | undefined;
1313
+ constructor(message: string, repository: string, method?: string | undefined, table?: string | undefined, originalError?: Error | undefined);
1314
+ }
1315
+ /**
1316
+ * Base Repository class for database operations
1317
+ *
1318
+ * Provides automatic database instance management with transaction support.
1319
+ * Extend this class to create domain-specific repositories.
1320
+ *
1321
+ * The repository automatically detects if running within a transaction context
1322
+ * and uses the appropriate database instance:
1323
+ * - Inside transaction: Uses transaction DB
1324
+ * - Outside transaction: Uses global DB instance (with read/write separation)
1325
+ *
1326
+ * @template TSchema - Database schema type (defaults to Record<string, unknown>)
1327
+ */
1328
+ declare abstract class BaseRepository<TSchema extends Record<string, unknown> = Record<string, unknown>> {
1329
+ /**
1330
+ * Write database instance
1331
+ *
1332
+ * Automatically resolves to:
1333
+ * - Transaction DB if running within transaction context
1334
+ * - Global write (primary) instance otherwise
1335
+ *
1336
+ * Use this for INSERT, UPDATE, DELETE operations.
1337
+ *
1338
+ * @example
1339
+ * ```typescript
1340
+ * async create(data: NewUser) {
1341
+ * return await this.db.insert(users).values(data).returning();
1342
+ * }
1343
+ * ```
1344
+ */
1345
+ protected get db(): PostgresJsDatabase<TSchema>;
1346
+ /**
1347
+ * Read database instance
1348
+ *
1349
+ * Automatically resolves to:
1350
+ * - Transaction DB if running within transaction context
1351
+ * - Global read (replica) instance otherwise
1352
+ *
1353
+ * Use this for SELECT operations to leverage read replicas
1354
+ * and reduce load on the primary database.
1355
+ *
1356
+ * @example
1357
+ * ```typescript
1358
+ * async findById(id: string) {
1359
+ * return await this.readDb
1360
+ * .select()
1361
+ * .from(users)
1362
+ * .where(eq(users.id, id));
1363
+ * }
1364
+ * ```
1365
+ */
1366
+ protected get readDb(): PostgresJsDatabase<TSchema>;
1367
+ /**
1368
+ * Wrap query execution with repository context
1369
+ *
1370
+ * Enhances error messages with repository information to make debugging easier.
1371
+ * When an error occurs, it will include:
1372
+ * - Repository class name
1373
+ * - Method name
1374
+ * - Table name (if provided)
1375
+ * - Original error details
1376
+ *
1377
+ * @param queryFn - Query function to execute
1378
+ * @param context - Context information (operation name, table name, etc.)
1379
+ * @returns Query result
1380
+ * @throws RepositoryError with enhanced context
1381
+ *
1382
+ * @example
1383
+ * ```typescript
1384
+ * async findById(id: number) {
1385
+ * return await this.withContext(
1386
+ * () => this.readDb.select().from(users).where(eq(users.id, id)),
1387
+ * { method: 'findById', table: 'users' }
1388
+ * );
1389
+ * }
1390
+ * ```
1391
+ */
1392
+ protected withContext<T>(queryFn: () => Promise<T>, context?: {
1393
+ method?: string;
1394
+ table?: string;
1395
+ }): Promise<T>;
1396
+ /**
1397
+ * Find a single record
1398
+ *
1399
+ * @param table - Drizzle table schema
1400
+ * @param where - Object or SQL condition
1401
+ * @returns Single record or null
1402
+ *
1403
+ * @example
1404
+ * ```typescript
1405
+ * // Object-based
1406
+ * const user = await this._findOne(users, { id: 1 });
1407
+ *
1408
+ * // SQL-based
1409
+ * const user = await this._findOne(users, eq(users.id, 1));
1410
+ * ```
1411
+ */
1412
+ protected _findOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'] | null>;
1413
+ /**
1414
+ * Find multiple records
1415
+ *
1416
+ * @param table - Drizzle table schema
1417
+ * @param options - Query options
1418
+ * @returns Array of records
1419
+ *
1420
+ * @example
1421
+ * ```typescript
1422
+ * const users = await this._findMany(users, {
1423
+ * where: { active: true },
1424
+ * orderBy: desc(users.createdAt),
1425
+ * limit: 10
1426
+ * });
1427
+ * ```
1428
+ */
1429
+ protected _findMany<T extends PgTable>(table: T, options?: {
1430
+ where?: Record<string, any> | SQL | undefined;
1431
+ orderBy?: SQL | SQL[];
1432
+ limit?: number;
1433
+ offset?: number;
1434
+ }): Promise<T['$inferSelect'][]>;
1435
+ /**
1436
+ * Create a new record
1437
+ *
1438
+ * @param table - Drizzle table schema
1439
+ * @param data - Insert data
1440
+ * @returns Created record
1441
+ *
1442
+ * @example
1443
+ * ```typescript
1444
+ * const user = await this._create(users, {
1445
+ * email: 'test@example.com',
1446
+ * name: 'Test User'
1447
+ * });
1448
+ * ```
1449
+ */
1450
+ protected _create<T extends PgTable>(table: T, data: T['$inferInsert']): Promise<T['$inferSelect']>;
1451
+ /**
1452
+ * Create multiple records
1453
+ *
1454
+ * @param table - Drizzle table schema
1455
+ * @param data - Array of insert data
1456
+ * @returns Array of created records
1457
+ *
1458
+ * @example
1459
+ * ```typescript
1460
+ * const users = await this._createMany(users, [
1461
+ * { email: 'user1@example.com', name: 'User 1' },
1462
+ * { email: 'user2@example.com', name: 'User 2' }
1463
+ * ]);
1464
+ * ```
1465
+ */
1466
+ protected _createMany<T extends PgTable>(table: T, data: T['$inferInsert'][]): Promise<T['$inferSelect'][]>;
1467
+ /**
1468
+ * Upsert a record (INSERT or UPDATE on conflict)
1469
+ *
1470
+ * @param table - Drizzle table schema
1471
+ * @param data - Insert data
1472
+ * @param options - Conflict resolution options
1473
+ * @returns Upserted record
1474
+ *
1475
+ * @example
1476
+ * ```typescript
1477
+ * const cache = await this._upsert(cache, {
1478
+ * key: 'config',
1479
+ * value: {...}
1480
+ * }, {
1481
+ * target: [cache.key],
1482
+ * set: { value: data.value, updatedAt: new Date() }
1483
+ * });
1484
+ * ```
1485
+ */
1486
+ protected _upsert<T extends PgTable>(table: T, data: T['$inferInsert'], options: {
1487
+ target: PgColumn[];
1488
+ set?: Partial<T['$inferInsert']> | Record<string, SQL | any>;
1489
+ }): Promise<T['$inferSelect']>;
1490
+ /**
1491
+ * Update a single record
1492
+ *
1493
+ * @param table - Drizzle table schema
1494
+ * @param where - Object or SQL condition
1495
+ * @param data - Update data
1496
+ * @returns Updated record or null
1497
+ *
1498
+ * @example
1499
+ * ```typescript
1500
+ * const user = await this._updateOne(users,
1501
+ * { id: 1 },
1502
+ * { name: 'Updated Name' }
1503
+ * );
1504
+ * ```
1505
+ */
1506
+ protected _updateOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<T['$inferInsert']>): Promise<T['$inferSelect'] | null>;
1507
+ /**
1508
+ * Update multiple records
1509
+ *
1510
+ * @param table - Drizzle table schema
1511
+ * @param where - Object or SQL condition
1512
+ * @param data - Update data
1513
+ * @returns Array of updated records
1514
+ *
1515
+ * @example
1516
+ * ```typescript
1517
+ * const users = await this._updateMany(users,
1518
+ * { role: 'user' },
1519
+ * { verified: true }
1520
+ * );
1521
+ * ```
1522
+ */
1523
+ protected _updateMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<T['$inferInsert']>): Promise<T['$inferSelect'][]>;
1524
+ /**
1525
+ * Delete a single record
1526
+ *
1527
+ * @param table - Drizzle table schema
1528
+ * @param where - Object or SQL condition
1529
+ * @returns Deleted record or null
1530
+ *
1531
+ * @example
1532
+ * ```typescript
1533
+ * const user = await this._deleteOne(users, { id: 1 });
1534
+ * ```
1535
+ */
1536
+ protected _deleteOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'] | null>;
1537
+ /**
1538
+ * Delete multiple records
1539
+ *
1540
+ * @param table - Drizzle table schema
1541
+ * @param where - Object or SQL condition
1542
+ * @returns Array of deleted records
1543
+ *
1544
+ * @example
1545
+ * ```typescript
1546
+ * const users = await this._deleteMany(users, { verified: false });
1547
+ * ```
1548
+ */
1549
+ protected _deleteMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'][]>;
1550
+ /**
1551
+ * Count records
1552
+ *
1553
+ * @param table - Drizzle table schema
1554
+ * @param where - Optional object or SQL condition
1555
+ * @returns Number of records
1556
+ *
1557
+ * @example
1558
+ * ```typescript
1559
+ * const total = await this._count(users);
1560
+ * const activeUsers = await this._count(users, { active: true });
1561
+ * ```
1562
+ */
1563
+ protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1564
+ }
1565
+
1566
+ 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 };