@spfn/core 0.2.0-beta.8 → 0.3.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 (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +444 -305
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +810 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +121 -13
  10. package/dist/codegen/index.js +212 -15
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +615 -6
  13. package/dist/config/index.js +124 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/contract/index.d.ts +220 -0
  16. package/dist/contract/index.js +558 -0
  17. package/dist/contract/index.js.map +1 -0
  18. package/dist/db/index.d.ts +528 -85
  19. package/dist/db/index.js +831 -122
  20. package/dist/db/index.js.map +1 -1
  21. package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
  22. package/dist/env/index.d.ts +26 -2
  23. package/dist/env/index.js +15 -5
  24. package/dist/env/index.js.map +1 -1
  25. package/dist/env/loader.d.ts +26 -19
  26. package/dist/env/loader.js +32 -25
  27. package/dist/env/loader.js.map +1 -1
  28. package/dist/errors/index.d.ts +10 -0
  29. package/dist/errors/index.js +418 -5
  30. package/dist/errors/index.js.map +1 -1
  31. package/dist/event/index.d.ts +33 -3
  32. package/dist/event/index.js +24 -3
  33. package/dist/event/index.js.map +1 -1
  34. package/dist/event/sse/client.d.ts +42 -3
  35. package/dist/event/sse/client.js +128 -45
  36. package/dist/event/sse/client.js.map +1 -1
  37. package/dist/event/sse/index.d.ts +12 -5
  38. package/dist/event/sse/index.js +280 -32
  39. package/dist/event/sse/index.js.map +1 -1
  40. package/dist/event/ws/client.d.ts +59 -0
  41. package/dist/event/ws/client.js +273 -0
  42. package/dist/event/ws/client.js.map +1 -0
  43. package/dist/event/ws/index.d.ts +94 -0
  44. package/dist/event/ws/index.js +272 -0
  45. package/dist/event/ws/index.js.map +1 -0
  46. package/dist/job/index.d.ts +2 -2
  47. package/dist/job/index.js +155 -42
  48. package/dist/job/index.js.map +1 -1
  49. package/dist/logger/index.d.ts +5 -0
  50. package/dist/logger/index.js +14 -0
  51. package/dist/logger/index.js.map +1 -1
  52. package/dist/middleware/index.d.ts +347 -9
  53. package/dist/middleware/index.js +1462 -15
  54. package/dist/middleware/index.js.map +1 -1
  55. package/dist/nextjs/index.d.ts +2 -2
  56. package/dist/nextjs/index.js +42 -28
  57. package/dist/nextjs/index.js.map +1 -1
  58. package/dist/nextjs/server.d.ts +35 -51
  59. package/dist/nextjs/server.js +126 -60
  60. package/dist/nextjs/server.js.map +1 -1
  61. package/dist/ops/index.d.ts +107 -0
  62. package/dist/ops/index.js +476 -0
  63. package/dist/ops/index.js.map +1 -0
  64. package/dist/route/index.d.ts +8 -694
  65. package/dist/route/index.js +111 -22
  66. package/dist/route/index.js.map +1 -1
  67. package/dist/router-ukNdAZcN.d.ts +676 -0
  68. package/dist/security/index.d.ts +83 -0
  69. package/dist/security/index.js +173 -0
  70. package/dist/security/index.js.map +1 -0
  71. package/dist/server/index.d.ts +491 -22
  72. package/dist/server/index.js +1887 -308
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/token-manager-BT5EnUAR.d.ts +278 -0
  75. package/dist/types-2AbaW4Ie.d.ts +205 -0
  76. package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
  77. package/dist/types-Bvvig_tT.d.ts +115 -0
  78. package/dist/types-ZQODsBft.d.ts +282 -0
  79. package/package.json +244 -208
  80. package/dist/router-Di7ENoah.d.ts +0 -151
  81. package/dist/types-B-e_f2dQ.d.ts +0 -121
  82. package/docs/cache.md +0 -133
  83. package/docs/codegen.md +0 -74
  84. package/docs/database.md +0 -346
  85. package/docs/entity.md +0 -539
  86. package/docs/env.md +0 -477
  87. package/docs/errors.md +0 -319
  88. package/docs/event.md +0 -116
  89. package/docs/job.md +0 -131
  90. package/docs/logger.md +0 -108
  91. package/docs/middleware.md +0 -337
  92. package/docs/nextjs.md +0 -241
  93. package/docs/repository.md +0 -496
  94. package/docs/route.md +0 -497
  95. package/docs/server.md +0 -307
@@ -1,12 +1,48 @@
1
1
  import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
2
  import postgres, { Sql } from 'postgres';
3
- import * as drizzle_orm from 'drizzle-orm';
4
- import { SQL } from 'drizzle-orm';
5
3
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
6
- import { PgColumn, PgTable } from 'drizzle-orm/pg-core';
4
+ import { PgAsyncDatabase, PgAsyncTransaction, PgColumn, PgTable } from 'drizzle-orm/pg-core';
5
+ import * as drizzle_orm from 'drizzle-orm';
6
+ import { SQL, InferSelectModel, InferInsertModel, AnyRelations, EmptyRelations } from 'drizzle-orm';
7
7
  import * as hono_types from 'hono/types';
8
8
  import { DatabaseError } from '@spfn/core/errors';
9
9
 
10
+ /**
11
+ * Database Manager Types
12
+ */
13
+
14
+ /**
15
+ * DB connection type
16
+ */
17
+ type DbConnectionType = 'read' | 'write';
18
+ /**
19
+ * Common base for PostgreSQL Drizzle drivers.
20
+ *
21
+ * Both the built-in postgres.js database and externally supplied drivers such
22
+ * as PGlite extend this class.
23
+ */
24
+ type DrizzleDatabase = PgAsyncDatabase<any, any>;
25
+ /** Default database type used by the environment-backed postgres.js path. */
26
+ type DefaultDatabase = PostgresJsDatabase;
27
+ /** Resolve the transaction type belonging to a PostgreSQL Drizzle database. */
28
+ type DatabaseTransaction<TDatabase extends DrizzleDatabase> = TDatabase extends PgAsyncDatabase<infer TQueryResult, infer TRelations> ? PgAsyncTransaction<TQueryResult, TRelations> : never;
29
+ /**
30
+ * An externally owned PostgreSQL Drizzle database.
31
+ *
32
+ * The provider owns connection creation. SPFN calls `close` at most once when
33
+ * `closeDatabase()` or server shutdown releases the registered provider.
34
+ */
35
+ interface DatabaseProvider<TDatabase extends DrizzleDatabase = DrizzleDatabase> {
36
+ /** Primary database for writes (and reads when no replica is supplied). */
37
+ write: TDatabase;
38
+ /** Optional read replica. Falls back to `write`. */
39
+ read?: TDatabase;
40
+ /** Driver identifier used for diagnostics, for example `pglite`. */
41
+ kind: string;
42
+ /** Release provider-owned resources. */
43
+ close?: () => void | Promise<void>;
44
+ }
45
+
10
46
  /**
11
47
  * Database Configuration
12
48
  *
@@ -23,11 +59,11 @@ import { DatabaseError } from '@spfn/core/errors';
23
59
  * - src/server/core/db/index.ts (main exports)
24
60
  */
25
61
 
26
- interface DatabaseClients {
62
+ interface DatabaseClients<TDatabase extends DrizzleDatabase = PostgresJsDatabase> {
27
63
  /** Primary database for writes (or both read/write if no replica) */
28
- write?: PostgresJsDatabase;
64
+ write?: TDatabase;
29
65
  /** Replica database for reads (optional, falls back to write) */
30
- read?: PostgresJsDatabase;
66
+ read?: TDatabase;
31
67
  /** Raw postgres client for write operations (for cleanup) */
32
68
  writeClient?: Sql;
33
69
  /** Raw postgres client for read operations (for cleanup) */
@@ -71,6 +107,17 @@ interface DatabaseOptions {
71
107
  */
72
108
  monitoring?: Partial<MonitoringConfig>;
73
109
  }
110
+ /** Database initialization options, including an optional external provider. */
111
+ interface DatabaseInitOptions<TDatabase extends DrizzleDatabase = PostgresJsDatabase> extends DatabaseOptions {
112
+ /**
113
+ * Externally owned PostgreSQL Drizzle provider.
114
+ *
115
+ * When set, environment-based postgres.js initialization, health checks,
116
+ * and automatic reconnect are skipped. The provider's `close` callback is
117
+ * used during shutdown.
118
+ */
119
+ provider?: DatabaseProvider<TDatabase>;
120
+ }
74
121
  /**
75
122
  * Connection pool configuration
76
123
  *
@@ -81,6 +128,21 @@ interface PoolConfig {
81
128
  max: number;
82
129
  /** Idle connection timeout in seconds */
83
130
  idleTimeout: number;
131
+ /**
132
+ * Maximum connections for the read-replica pool. Defaults to `max`. Set
133
+ * separately (DB_POOL_READ_MAX) so a master-replica app can size the two
134
+ * pools independently and keep `write.max + read.max` under the server's
135
+ * `max_connections` (otherwise each process opens up to 2 × max).
136
+ */
137
+ readMax?: number;
138
+ /**
139
+ * Use server-side prepared statements (postgres-js `prepare`). When
140
+ * `undefined`, the connection layer auto-detects: it is disabled for
141
+ * transaction-mode poolers (PgBouncer/Supavisor, e.g. Supabase :6543),
142
+ * where cached statements break as the pooler rotates backends, and
143
+ * enabled otherwise. Set explicitly (or via `SPFN_DB_PREPARE`) to override.
144
+ */
145
+ prepare?: boolean;
84
146
  }
85
147
  /**
86
148
  * Retry configuration for exponential backoff algorithm
@@ -134,15 +196,6 @@ interface RetryConfig {
134
196
  */
135
197
  declare function createDatabaseFromEnv(options?: DatabaseOptions): Promise<DatabaseClients>;
136
198
 
137
- /**
138
- * Database Manager Types
139
- */
140
-
141
- /**
142
- * DB connection type
143
- */
144
- type DbConnectionType = 'read' | 'write';
145
-
146
199
  /**
147
200
  * Global Database instance manager
148
201
  * Provides singleton access to database across all modules
@@ -169,7 +222,7 @@ type DbConnectionType = 'read' | 'write';
169
222
  * const posts = await dbRead.select().from(postsTable);
170
223
  * ```
171
224
  */
172
- declare function getDatabase(type?: DbConnectionType): PostgresJsDatabase<Record<string, unknown>>;
225
+ declare function getDatabase<TDatabase extends DrizzleDatabase = DefaultDatabase>(type?: DbConnectionType): TDatabase;
173
226
  /**
174
227
  * Set global database instances (for testing or manual configuration)
175
228
  *
@@ -199,9 +252,17 @@ declare function getDatabase(type?: DbConnectionType): PostgresJsDatabase<Record
199
252
  * setDatabase(undefined, undefined);
200
253
  * ```
201
254
  */
202
- declare function setDatabase(write: PostgresJsDatabase<Record<string, unknown>> | undefined, read?: PostgresJsDatabase<Record<string, unknown>> | undefined): void;
255
+ declare function setDatabase<TDatabase extends DrizzleDatabase = DefaultDatabase>(write: TDatabase | undefined, read?: TDatabase | undefined): void;
256
+ /**
257
+ * Register an externally owned PostgreSQL Drizzle provider.
258
+ *
259
+ * This is the synchronous/manual counterpart to `initDatabase({ provider })`.
260
+ * It performs no connection test. Use `closeDatabase()` to invoke the
261
+ * provider's close callback and clear the global instances.
262
+ */
263
+ declare function setDatabaseProvider<TDatabase extends DrizzleDatabase>(provider: DatabaseProvider<TDatabase>): DatabaseClients<TDatabase>;
203
264
  /**
204
- * Initialize database from environment variables
265
+ * Initialize a database provider or create postgres.js clients from environment variables
205
266
  * Automatically called by server startup
206
267
  *
207
268
  * Supported environment variables:
@@ -246,14 +307,11 @@ declare function setDatabase(write: PostgresJsDatabase<Record<string, unknown>>
246
307
  * });
247
308
  * ```
248
309
  */
249
- declare function initDatabase(options?: DatabaseOptions): Promise<{
250
- write?: PostgresJsDatabase<Record<string, unknown>>;
251
- read?: PostgresJsDatabase<Record<string, unknown>>;
252
- }>;
310
+ declare function initDatabase<TDatabase extends DrizzleDatabase = DefaultDatabase>(options?: DatabaseInitOptions<TDatabase>): Promise<DatabaseClients<TDatabase>>;
253
311
  /**
254
- * Close all database connections and cleanup
312
+ * Close the active database provider or postgres.js connections and clean up
255
313
  *
256
- * Properly closes postgres connection pools with timeout.
314
+ * Invokes an external provider's close callback, or closes postgres.js pools with timeout.
257
315
  * Should be called during graceful shutdown or after tests.
258
316
  *
259
317
  * @example
@@ -273,6 +331,36 @@ declare function initDatabase(options?: DatabaseOptions): Promise<{
273
331
  * ```
274
332
  */
275
333
  declare function closeDatabase(): Promise<void>;
334
+ /**
335
+ * Force an immediate database pool rebuild
336
+ *
337
+ * Destroys the current postgres.js pool(s) and rebuilds them with the same
338
+ * configuration passed to the original `initDatabase()` call (or whatever
339
+ * was detected from environment variables). Uses the same atomic-swap
340
+ * strategy as the periodic health check: new connections are created and
341
+ * tested BEFORE the old ones are torn down, so `getDatabase()` callers never
342
+ * observe a missing instance.
343
+ *
344
+ * Use this when application code detects that the pool is stuck and does not
345
+ * want to wait for the next periodic health check tick. Concurrent calls are
346
+ * coalesced — if a reconnect is already in progress, this resolves to `false`
347
+ * without starting a second one.
348
+ *
349
+ * @param reason - Short label describing why the rebuild was requested (for logs)
350
+ * @returns `true` if a reconnection ran, `false` if one was already in-flight.
351
+ * Resolves after the rebuild completes (success or max retries exhausted).
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * import { forceReconnectDatabase } from '@spfn/core/db';
356
+ *
357
+ * app.post('/admin/db/reconnect', async (c) => {
358
+ * const ran = await forceReconnectDatabase('admin_request');
359
+ * return c.json({ reconnected: ran });
360
+ * });
361
+ * ```
362
+ */
363
+ declare function forceReconnectDatabase(reason?: string): Promise<boolean>;
276
364
  /**
277
365
  * Get database connection info (for debugging)
278
366
  *
@@ -306,8 +394,63 @@ declare function getDatabaseInfo(): {
306
394
  hasWrite: boolean;
307
395
  hasRead: boolean;
308
396
  isReplica: boolean;
397
+ providerKind?: string;
309
398
  };
310
399
 
400
+ /**
401
+ * Reconnect Trigger — Query-error driven pool rebuild
402
+ *
403
+ * Complements the periodic health check with a fast-path: when application
404
+ * queries start failing with connection-level errors, we do not wait up to
405
+ * DB_HEALTH_CHECK_INTERVAL (default 60s) to notice. A sliding-window counter
406
+ * trips a force-reconnect as soon as the failure rate crosses a threshold.
407
+ *
408
+ * Why this exists:
409
+ * - postgres.js transparently drops dead sockets and opens new ones on the
410
+ * next query. A single `SELECT 1` on the periodic interval can therefore
411
+ * false-pass while user-facing queries keep hitting the remaining dead
412
+ * sockets in the pool.
413
+ * - This module observes real query errors and, when it sees a burst of
414
+ * connection-level failures, calls triggerForceReconnect() which performs
415
+ * the same atomic-swap rebuild as the health check.
416
+ *
417
+ * Configuration (env vars, hardcoded defaults):
418
+ * - DB_RECONNECT_ERROR_THRESHOLD (default 3): errors needed in window
419
+ * - DB_RECONNECT_ERROR_WINDOW_MS (default 10000): sliding window size
420
+ */
421
+ /**
422
+ * Determine whether an error looks like a pool/connection failure
423
+ *
424
+ * Returns true when any layer in the error chain exposes a connection-level
425
+ * code (postgres.js driver code, Node network errno, PG SQLSTATE class 08 etc.)
426
+ * or is an instance of our own ConnectionError wrapper.
427
+ *
428
+ * Returns false for query errors (syntax, constraint violations, etc.) — those
429
+ * should NOT trigger a pool rebuild.
430
+ */
431
+ declare function isConnectionLevelError(error: unknown): boolean;
432
+ /**
433
+ * Reset the internal error counter
434
+ *
435
+ * Exposed for tests that need a clean slate between cases. Does not clear
436
+ * the WeakSet (which is GC-backed and self-cleans with error lifetimes).
437
+ */
438
+ declare function resetConnectionErrorCounter(): void;
439
+ /**
440
+ * Report a database error to the reconnect trigger
441
+ *
442
+ * Call this from any site that catches a query error before rethrowing.
443
+ * It is a no-op for non-connection-level errors. When the threshold is
444
+ * crossed it calls triggerForceReconnect() in the background — callers
445
+ * should NOT await it.
446
+ *
447
+ * Safe to call from any context: catches its own errors so it cannot
448
+ * disrupt the calling catch block. Deduplicates across error-chain
449
+ * re-wrapping so one failure counts exactly once regardless of how many
450
+ * catch layers it passes through.
451
+ */
452
+ declare function reportDatabaseError(error: unknown): void;
453
+
311
454
  /**
312
455
  * Create database connection with exponential backoff retry strategy
313
456
  *
@@ -380,6 +523,8 @@ interface DrizzleConfigOptions {
380
523
  schemaFilter?: string[];
381
524
  /** Auto-detect PostgreSQL schemas from entity files (requires expandGlobs: true) */
382
525
  autoDetectSchemas?: boolean;
526
+ /** Migration prefix strategy (default: 'timestamp') */
527
+ migrationPrefix?: 'index' | 'timestamp' | 'unix' | 'none';
383
528
  }
384
529
  /**
385
530
  * Detect database dialect from connection URL
@@ -411,6 +556,9 @@ declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
411
556
  dbCredentials: {
412
557
  url: string;
413
558
  };
559
+ migrations: {
560
+ prefix: "timestamp" | "none" | "index" | "unix";
561
+ };
414
562
  schemaFilter?: undefined;
415
563
  } | {
416
564
  schema: string | string[];
@@ -420,6 +568,9 @@ declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
420
568
  url: string;
421
569
  };
422
570
  schemaFilter: string[] | undefined;
571
+ migrations: {
572
+ prefix: "timestamp" | "none" | "index" | "unix";
573
+ };
423
574
  };
424
575
  /**
425
576
  * Generate drizzle.config.ts file content
@@ -442,12 +593,23 @@ declare function generateDrizzleConfigFile(options?: DrizzleConfigOptions): stri
442
593
  * });
443
594
  * ```
444
595
  */
445
- declare function id(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigSerial53BuilderInitial<"id">>>;
596
+ declare function id(): drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.PgBigSerial53Builder>;
446
597
  /**
447
598
  * Standard timestamp fields (createdAt, updatedAt)
448
599
  *
449
- * Both fields are timezone-aware, auto-set to current time on creation.
450
- * updatedAt must be manually updated in your application code.
600
+ * Both fields are timezone-aware and come from the database clock, never from the
601
+ * application process. On insert both take `now()` through their column default, so a
602
+ * new row's two timestamps are equal. On update Drizzle sets `updated_at = now()` for
603
+ * you — do not pass `new Date()` yourself.
604
+ *
605
+ * Stamping `updated_at` from the application would read a second clock: the app host
606
+ * and the database host are the same machine in development and different machines in
607
+ * production, so a row could record an update earlier than its own creation, and
608
+ * ordering rows by `updated_at` would order by whichever host's clock ran ahead.
609
+ *
610
+ * `now()` is the transaction's start time, matching what `.defaultNow()` writes. It is
611
+ * not a commit-order sequence — anything that needs a true sequence needs a sequence
612
+ * column, not a timestamp.
451
613
  *
452
614
  * @returns Object with createdAt and updatedAt columns
453
615
  *
@@ -459,23 +621,20 @@ declare function id(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_
459
621
  * ...timestamps(),
460
622
  * });
461
623
  *
462
- * // Manual update
624
+ * // updated_at is stamped by the database — it is not named here
463
625
  * await db.update(users)
464
- * .set({
465
- * email: 'new@example.com',
466
- * updatedAt: new Date()
467
- * })
626
+ * .set({ email: 'new@example.com' })
468
627
  * .where(eq(users.id, userId));
469
628
  * ```
470
629
  */
471
630
  declare function timestamps(): {
472
- createdAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"created_at">>>;
473
- updatedAt: drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgTimestampBuilderInitial<"updated_at">>>;
631
+ createdAt: drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>;
632
+ updatedAt: drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgTimestampBuilder>>>;
474
633
  };
475
634
  /**
476
635
  * Foreign key reference to another table
477
636
  *
478
- * Creates a bigserial column with cascade delete.
637
+ * Creates a bigint column with cascade delete.
479
638
  * Type-safe: ensures the reference points to a valid PostgreSQL column.
480
639
  *
481
640
  * @param name - Column name (e.g., 'author' creates 'author_id')
@@ -495,7 +654,7 @@ declare function timestamps(): {
495
654
  */
496
655
  declare function foreignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
497
656
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
498
- }): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>>;
657
+ }): drizzle_orm_pg_core.SetNotNull<drizzle_orm_pg_core.PgBigInt53Builder>;
499
658
  /**
500
659
  * Optional foreign key reference (nullable)
501
660
  *
@@ -515,7 +674,7 @@ declare function foreignKey<T extends PgColumn>(name: string, reference: () => T
515
674
  */
516
675
  declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
517
676
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
518
- }): drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>;
677
+ }): drizzle_orm_pg_core.PgBigInt53Builder;
519
678
  /**
520
679
  * UUID primary key
521
680
  *
@@ -533,7 +692,7 @@ declare function optionalForeignKey<T extends PgColumn>(name: string, reference:
533
692
  * });
534
693
  * ```
535
694
  */
536
- declare function uuid(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm.HasDefault<drizzle_orm_pg_core.PgUUIDBuilderInitial<"id">>>>;
695
+ declare function uuid(): drizzle_orm_pg_core.SetIsPrimaryKey<drizzle_orm_pg_core.SetHasDefault<drizzle_orm_pg_core.PgUUIDBuilder>>;
537
696
  /**
538
697
  * Audit fields for tracking record creators and updaters
539
698
  *
@@ -559,8 +718,8 @@ declare function uuid(): drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_or
559
718
  * ```
560
719
  */
561
720
  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[]]>;
721
+ createdBy: drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>;
722
+ updatedBy: drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>;
564
723
  };
565
724
  /**
566
725
  * Publishing fields for content management
@@ -591,8 +750,8 @@ declare function auditFields(): {
591
750
  * ```
592
751
  */
593
752
  declare function publishingFields(): {
594
- publishedAt: drizzle_orm_pg_core.PgTimestampBuilderInitial<"published_at">;
595
- publishedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"published_by", [string, ...string[]]>;
753
+ publishedAt: drizzle_orm_pg_core.PgTimestampBuilder;
754
+ publishedBy: drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>;
596
755
  };
597
756
  /**
598
757
  * Custom verification timestamp field
@@ -621,7 +780,7 @@ declare function publishingFields(): {
621
780
  * ```
622
781
  */
623
782
  declare function verificationTimestamp(fieldName: string): {
624
- [x: string]: drizzle_orm_pg_core.PgTimestampBuilderInitial<string>;
783
+ [x: string]: drizzle_orm_pg_core.PgTimestampBuilder;
625
784
  };
626
785
  /**
627
786
  * Soft delete fields
@@ -655,8 +814,8 @@ declare function verificationTimestamp(fieldName: string): {
655
814
  * ```
656
815
  */
657
816
  declare function softDelete(): {
658
- deletedAt: drizzle_orm_pg_core.PgTimestampBuilderInitial<"deleted_at">;
659
- deletedBy: drizzle_orm_pg_core.PgTextBuilderInitial<"deleted_by", [string, ...string[]]>;
817
+ deletedAt: drizzle_orm_pg_core.PgTimestampBuilder;
818
+ deletedBy: drizzle_orm_pg_core.PgTextBuilder<[string, ...string[]]>;
660
819
  };
661
820
  /**
662
821
  * UTC timestamp field
@@ -688,7 +847,7 @@ declare function softDelete(): {
688
847
  * });
689
848
  * ```
690
849
  */
691
- declare function utcTimestamp(fieldName: string, mode?: 'date' | 'string'): drizzle_orm_pg_core.PgTimestampBuilderInitial<string>;
850
+ declare function utcTimestamp(fieldName: string, mode?: 'date' | 'string'): drizzle_orm_pg_core.PgTimestampBuilder;
692
851
  /**
693
852
  * Type-safe enum text field
694
853
  *
@@ -722,7 +881,7 @@ declare function utcTimestamp(fieldName: string, mode?: 'date' | 'string'): driz
722
881
  * });
723
882
  * ```
724
883
  */
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[]]>>;
884
+ declare function enumText<T extends readonly [string, ...string[]]>(fieldName: string, values: T): drizzle_orm_pg_core.PgTextBuilder<drizzle_orm.Writable<T & [string, ...string[]]>>;
726
885
  /**
727
886
  * Type-safe JSONB field
728
887
  *
@@ -765,7 +924,7 @@ declare function enumText<T extends readonly [string, ...string[]]>(fieldName: s
765
924
  * tags: typedJsonb<string[]>('tags').notNull(),
766
925
  * ```
767
926
  */
768
- declare function typedJsonb<T>(fieldName: string): drizzle_orm.$Type<drizzle_orm_pg_core.PgJsonbBuilderInitial<string>, T>;
927
+ declare function typedJsonb<T>(fieldName: string): drizzle_orm_pg_core.Set$Type<drizzle_orm_pg_core.PgJsonbBuilder, T>;
769
928
 
770
929
  /**
771
930
  * Database Schema Helper
@@ -828,6 +987,137 @@ declare function getSchemaInfo(packageName: string): {
828
987
  scope: string | null;
829
988
  };
830
989
 
990
+ /**
991
+ * Function Package Migration Discovery
992
+ *
993
+ * Finds the migrations shipped by installed SPFN function packages (e.g.
994
+ * `@spfn/auth`) and reads their entries. Both drizzle-kit layouts are
995
+ * supported — `NNNN_name.sql` + `meta/_journal.json` (≤0.31) and
996
+ * `<YYYYMMDDHHMMSS>_name/migration.sql` (1.0) — so an installed package keeps
997
+ * working regardless of which drizzle-kit generated it.
998
+ *
999
+ * This module only reads the filesystem. Applying migrations is the CLI's job
1000
+ * (`spfn db migrate`); the server and `spfn db status` share the reading half.
1001
+ */
1002
+ type FunctionMigrationInfo = {
1003
+ packageName: string;
1004
+ migrationsDir: string;
1005
+ packagePath: string;
1006
+ };
1007
+ type FunctionMigrationEntry = {
1008
+ name: string;
1009
+ statements: string[];
1010
+ hash: string;
1011
+ millis: number;
1012
+ };
1013
+ /**
1014
+ * Per-package migrations table name — must match the CLI's migration runner.
1015
+ */
1016
+ declare function functionMigrationsTable(packageName: string): string;
1017
+ /**
1018
+ * Discover all installed SPFN function packages that ship migrations.
1019
+ *
1020
+ * A package opts in with a `spfn.migrations.dir` field in its package.json.
1021
+ * Returns an empty list when the project has no `node_modules/@spfn` at all,
1022
+ * which is also the "nothing to check" answer for apps without function packages.
1023
+ */
1024
+ declare function discoverFunctionMigrations(cwd?: string): FunctionMigrationInfo[];
1025
+ /**
1026
+ * Read a package's migration entries, auto-detecting the folder layout.
1027
+ *
1028
+ * A `meta/_journal.json` marks the drizzle-kit ≤0.31 layout; without it the
1029
+ * directory is read as the drizzle-kit 1.0 layout.
1030
+ */
1031
+ declare function readMigrationEntries(migrationsDir: string, packageName: string): FunctionMigrationEntry[];
1032
+
1033
+ /**
1034
+ * Migration Status Inspection
1035
+ *
1036
+ * Compares what each migration folder ships (function packages, plus the
1037
+ * project's own `src/server/drizzle`) against what the database records as
1038
+ * applied. `spfn db status`, the server's boot gate and the detailed health
1039
+ * endpoint all read this one implementation.
1040
+ */
1041
+
1042
+ /**
1043
+ * Project migrations use drizzle's default table, in the `drizzle` schema.
1044
+ */
1045
+ declare const PROJECT_MIGRATIONS_TABLE = "__drizzle_migrations";
1046
+ /**
1047
+ * The name `spfn db status` and health use for the project's own migrations.
1048
+ */
1049
+ declare const PROJECT_TARGET_NAME = "project (src/server/drizzle)";
1050
+ type MigrationTargetStatus = {
1051
+ name: string;
1052
+ total: number;
1053
+ applied: number;
1054
+ pending: number;
1055
+ pendingTags: string[];
1056
+ };
1057
+ type MigrationStatus = {
1058
+ packages: MigrationTargetStatus[];
1059
+ project: MigrationTargetStatus | null;
1060
+ };
1061
+ /**
1062
+ * The minimum surface needed to read migration bookkeeping: anything that can
1063
+ * run a drizzle `sql` fragment. The server passes its own pool, the CLI passes
1064
+ * a short-lived connection.
1065
+ */
1066
+ interface MigrationStatusDb {
1067
+ execute(query: SQL): Promise<unknown>;
1068
+ }
1069
+ /**
1070
+ * An entry counts as applied when its name is recorded (drizzle-orm 1.0
1071
+ * projects) or its timestamp is not newer than the last applied record —
1072
+ * the rule the CLI's function-migration runner and drizzle-orm ≤0.45 share.
1073
+ */
1074
+ declare function filterPendingEntries(entries: FunctionMigrationEntry[], lastAppliedMillis: number, appliedNames: Set<string>): FunctionMigrationEntry[];
1075
+ /**
1076
+ * Every target the status covers, packages first, in the order `spfn db status`
1077
+ * prints them.
1078
+ */
1079
+ declare function migrationTargets(status: MigrationStatus): MigrationTargetStatus[];
1080
+ /**
1081
+ * The targets that still have migrations waiting.
1082
+ */
1083
+ declare function pendingMigrationTargets(status: MigrationStatus): MigrationTargetStatus[];
1084
+ /**
1085
+ * Total number of migrations waiting across every target.
1086
+ */
1087
+ declare function countPendingMigrations(status: MigrationStatus): number;
1088
+ /**
1089
+ * True when the project has nothing whose migration state could be inspected —
1090
+ * no function package ships migrations and there is no project migrations
1091
+ * folder. Callers use this to skip the database round-trip entirely.
1092
+ */
1093
+ declare function hasMigrationTargets(cwd?: string): boolean;
1094
+ declare function projectMigrationsDir(cwd?: string): string;
1095
+ /**
1096
+ * Read applied/pending counts for every function package and for the project.
1097
+ *
1098
+ * The project's own migrations are included when `src/server/drizzle` exists.
1099
+ * A built server image usually does not ship that folder, so the project target
1100
+ * is simply absent there — never reported as drift.
1101
+ */
1102
+ declare function collectMigrationStatus(db: MigrationStatusDb, cwd?: string): Promise<MigrationStatus>;
1103
+
1104
+ /**
1105
+ * One wording for pending migrations, shared by the server's boot gate and the
1106
+ * CLI's pre-flight check — an operator should read the same lines wherever the
1107
+ * refusal comes from.
1108
+ */
1109
+
1110
+ declare const RUN_MIGRATIONS_HINT = "Run: pnpm spfn db migrate";
1111
+ /**
1112
+ * Plain (uncoloured) lines listing every target with pending migrations and the
1113
+ * name of each migration still waiting.
1114
+ */
1115
+ declare function formatPendingMigrations(targets: MigrationTargetStatus[]): string[];
1116
+ /**
1117
+ * The single-sentence reason a boot was refused.
1118
+ */
1119
+ declare function pendingMigrationsSummary(targets: MigrationTargetStatus[]): string;
1120
+
831
1121
  /**
832
1122
  * AsyncLocalStorage-based Transaction Context
833
1123
  *
@@ -842,26 +1132,37 @@ declare function getSchemaInfo(packageName: string): {
842
1132
  */
843
1133
 
844
1134
  /**
845
- * Transaction database type
846
- * Uses Record<string, unknown> to accept any schema shape
1135
+ * Transaction type belonging to a PostgreSQL Drizzle database.
847
1136
  */
848
- type TransactionDB = PostgresJsDatabase<Record<string, unknown>>;
1137
+ type TransactionDB<TDatabase extends DrizzleDatabase = DefaultDatabase> = DatabaseTransaction<TDatabase>;
1138
+ /**
1139
+ * afterCommit callback type
1140
+ */
1141
+ type AfterCommitCallback = () => void | Promise<void>;
849
1142
  /**
850
1143
  * Transaction context stored in AsyncLocalStorage
851
1144
  */
852
- type TransactionContext = {
1145
+ type TransactionContext<TDatabase extends DrizzleDatabase = DrizzleDatabase> = {
853
1146
  /** The actual Drizzle transaction object */
854
- tx: TransactionDB;
1147
+ tx: TransactionDB<TDatabase>;
855
1148
  /** Unique transaction ID for logging and tracing */
856
1149
  txId: string;
857
1150
  level: number;
1151
+ /** Callbacks to execute after root transaction commits */
1152
+ afterCommitCallbacks: AfterCommitCallback[];
858
1153
  };
1154
+ /**
1155
+ * Get current transaction object and metadata from AsyncLocalStorage
1156
+ *
1157
+ * @returns TransactionContext if available, null otherwise
1158
+ */
1159
+ declare function getTransactionContext<TDatabase extends DrizzleDatabase = DefaultDatabase>(): TransactionContext<TDatabase> | null;
859
1160
  /**
860
1161
  * Get current transaction from AsyncLocalStorage
861
1162
  *
862
1163
  * @returns Transaction if available, null otherwise
863
1164
  */
864
- declare function getTransaction(): TransactionDB | null;
1165
+ declare function getTransaction<TDatabase extends DrizzleDatabase = DefaultDatabase>(): TransactionDB<TDatabase> | null;
865
1166
  /**
866
1167
  * Run a function within a transaction context
867
1168
  *
@@ -873,8 +1174,33 @@ declare function getTransaction(): TransactionDB | null;
873
1174
  * @param callback - Function to run within transaction context
874
1175
  * @returns Result of the callback
875
1176
  */
876
- declare function runWithTransaction<T>(tx: TransactionDB, txId: string, // Add txId parameter
1177
+ declare function runWithTransaction<T, TDatabase extends DrizzleDatabase = DefaultDatabase>(tx: TransactionDB<TDatabase>, txId: string, // Add txId parameter
877
1178
  callback: () => Promise<T>): Promise<T>;
1179
+ /**
1180
+ * Register a callback to run after the current transaction commits
1181
+ *
1182
+ * - Inside a transaction: queued and executed after root transaction commits
1183
+ * - Outside a transaction: executed immediately (already "committed")
1184
+ * - Nested transactions: callbacks bubble up to root transaction
1185
+ * - Callbacks run outside transaction context (new connection for DB access)
1186
+ * - Errors are logged but never thrown (commit already succeeded)
1187
+ *
1188
+ * @example
1189
+ * ```typescript
1190
+ * import { onAfterCommit } from '@spfn/core/db/transaction';
1191
+ *
1192
+ * async function submit(spaceId: string, chatId: string)
1193
+ * {
1194
+ * const publication = await publicationRepo.create({...});
1195
+ * await requestRepo.updateStatusAtomically(...);
1196
+ *
1197
+ * onAfterCommit(() => generateArticle(spaceId, chatId, publication.id));
1198
+ *
1199
+ * return publication;
1200
+ * }
1201
+ * ```
1202
+ */
1203
+ declare function onAfterCommit(callback: AfterCommitCallback): void;
878
1204
 
879
1205
  /**
880
1206
  * Transaction middleware options
@@ -910,6 +1236,15 @@ interface TransactionalOptions {
910
1236
  * ```
911
1237
  */
912
1238
  timeout?: number;
1239
+ /**
1240
+ * Idle-in-transaction timeout in milliseconds — Postgres reclaims the pooled
1241
+ * connection if the transaction sits idle (e.g. the handler awaits external
1242
+ * I/O) longer than this. A backstop against pool starvation, not a license
1243
+ * to do non-DB work inside a transaction. `0` disables it.
1244
+ *
1245
+ * @default 30000 (30s) or TRANSACTION_IDLE_TIMEOUT environment variable
1246
+ */
1247
+ idleTimeout?: number;
913
1248
  }
914
1249
  /**
915
1250
  * Transaction middleware for Hono routes
@@ -955,6 +1290,93 @@ interface TransactionalOptions {
955
1290
  */
956
1291
  declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
957
1292
 
1293
+ /**
1294
+ * Transaction runner options
1295
+ */
1296
+ interface RunInTransactionOptions {
1297
+ /**
1298
+ * Slow transaction warning threshold in milliseconds
1299
+ * @default 1000 (1 second)
1300
+ */
1301
+ slowThreshold?: number;
1302
+ /**
1303
+ * Enable transaction logging
1304
+ * @default true
1305
+ */
1306
+ enableLogging?: boolean;
1307
+ /**
1308
+ * Transaction timeout in milliseconds
1309
+ *
1310
+ * Sets PostgreSQL `statement_timeout` to enforce database-level timeout.
1311
+ * If transaction exceeds this duration, PostgreSQL will automatically cancel
1312
+ * the query and rollback the transaction, ensuring data consistency.
1313
+ *
1314
+ * Behavior:
1315
+ * - `timeout: 0` - Disables timeout (unlimited execution time)
1316
+ * - `timeout: null` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1317
+ * - `timeout: undefined` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1318
+ * - `timeout: N` - Sets timeout to N milliseconds (1 to 2147483647)
1319
+ *
1320
+ * Note: Timeout is only applied to root transactions. Nested transactions
1321
+ * (SAVEPOINTs) inherit the timeout from the outer transaction.
1322
+ *
1323
+ * @default 30000 (30 seconds) or TRANSACTION_TIMEOUT environment variable
1324
+ *
1325
+ * @example
1326
+ * ```typescript
1327
+ * // Use default timeout (30s)
1328
+ * await runInTransaction(callback);
1329
+ *
1330
+ * // Disable timeout for long-running operations
1331
+ * await runInTransaction(callback, { timeout: 0 });
1332
+ *
1333
+ * // Set custom timeout (60s)
1334
+ * await runInTransaction(callback, { timeout: 60000 });
1335
+ * ```
1336
+ */
1337
+ timeout?: number;
1338
+ /**
1339
+ * Idle-in-transaction timeout in milliseconds (root transactions only).
1340
+ *
1341
+ * Sets PostgreSQL `idle_in_transaction_session_timeout`: if the transaction
1342
+ * sits open without running a query for longer than this — e.g. while the
1343
+ * handler awaits external I/O inside the transaction — Postgres terminates
1344
+ * the session and rolls back, reclaiming the pooled connection instead of
1345
+ * letting one stuck request hold it (and its row locks) indefinitely.
1346
+ *
1347
+ * Do not put external I/O inside a transaction; this is a backstop, not a
1348
+ * license. `0` disables it.
1349
+ *
1350
+ * @default 30000 (30s) or TRANSACTION_IDLE_TIMEOUT environment variable
1351
+ */
1352
+ idleTimeout?: number;
1353
+ /**
1354
+ * Context string for logging (e.g., 'migration:add-user', 'script:cleanup')
1355
+ * @default 'transaction'
1356
+ */
1357
+ context?: string;
1358
+ }
1359
+ /**
1360
+ * Run a callback function within a database transaction
1361
+ *
1362
+ * Automatically manages transaction lifecycle:
1363
+ * - Commits on success
1364
+ * - Rolls back on error
1365
+ * - Tracks execution time
1366
+ * - Warns about slow transactions
1367
+ * - Enforces timeout if configured
1368
+ *
1369
+ * Errors are propagated to the caller without modification.
1370
+ * Caller is responsible for error handling and conversion.
1371
+ *
1372
+ * @param callback - Function to execute within transaction
1373
+ * @param options - Transaction options
1374
+ * @returns Result of callback function
1375
+ * @throws TransactionError if database not initialized or timeout exceeded
1376
+ * @throws Any error thrown by callback function
1377
+ */
1378
+ declare function runInTransaction<T, TDatabase extends DrizzleDatabase = DefaultDatabase>(callback: (tx: TransactionDB<TDatabase>) => Promise<T>, options?: RunInTransactionOptions): Promise<T>;
1379
+
958
1380
  /**
959
1381
  * PostgreSQL Error Conversion Utilities
960
1382
  *
@@ -1007,14 +1429,6 @@ declare function fromPostgresError(error: any): DatabaseError;
1007
1429
  * ```
1008
1430
  */
1009
1431
 
1010
- /**
1011
- * Infer SELECT model from PgTable
1012
- */
1013
- type InferSelectModel<T extends PgTable> = T['$inferSelect'];
1014
- /**
1015
- * Infer INSERT model from PgTable
1016
- */
1017
- type InferInsertModel<T extends PgTable> = T['$inferInsert'];
1018
1432
  /**
1019
1433
  * Object-based where condition (AND only, equality only)
1020
1434
  */
@@ -1126,8 +1540,8 @@ declare function createMany<T extends PgTable>(table: T, data: InferInsertModel<
1126
1540
  * }, {
1127
1541
  * target: [cmsPublishedCache.section, cmsPublishedCache.locale],
1128
1542
  * set: {
1129
- * content: data.content,
1130
- * updatedAt: new Date()
1543
+ * content: data.content
1544
+ * // updatedAt is stamped by the database — do not name it here
1131
1545
  * }
1132
1546
  * });
1133
1547
  *
@@ -1279,10 +1693,10 @@ declare function count<T extends PgTable>(table: T, where?: WhereObject<InferSel
1279
1693
  *
1280
1694
  * @example With Custom Schema Type
1281
1695
  * ```typescript
1282
- * import type { AppSchema } from './schema';
1696
+ * import type { AppRelations } from './relations';
1283
1697
  *
1284
- * export class UserRepository extends BaseRepository<AppSchema> {
1285
- * // Now this.db and this.readDb are typed with AppSchema
1698
+ * export class UserRepository extends BaseRepository<AppRelations> {
1699
+ * // Now this.db and this.readDb preserve the app's Drizzle relations
1286
1700
  * }
1287
1701
  * ```
1288
1702
  *
@@ -1302,6 +1716,8 @@ declare function count<T extends PgTable>(table: T, where?: WhereObject<InferSel
1302
1716
  * ```
1303
1717
  */
1304
1718
 
1719
+ /** Database surface available to repositories inside and outside transactions. */
1720
+ type RepositoryDatabase<TDatabase extends DrizzleDatabase> = TDatabase extends PgAsyncDatabase<infer TQueryResult, infer TRelations> ? PgAsyncDatabase<TQueryResult, TRelations> : never;
1305
1721
  /**
1306
1722
  * Enhanced error class that includes repository context
1307
1723
  */
@@ -1323,9 +1739,10 @@ declare class RepositoryError extends Error {
1323
1739
  * - Inside transaction: Uses transaction DB
1324
1740
  * - Outside transaction: Uses global DB instance (with read/write separation)
1325
1741
  *
1326
- * @template TSchema - Database schema type (defaults to Record<string, unknown>)
1742
+ * @template TRelations - Drizzle relations type (defaults to no relations)
1743
+ * @template TDatabase - PostgreSQL Drizzle driver type (defaults to postgres.js)
1327
1744
  */
1328
- declare abstract class BaseRepository<TSchema extends Record<string, unknown> = Record<string, unknown>> {
1745
+ declare abstract class BaseRepository<TRelations extends AnyRelations = EmptyRelations, TDatabase extends DrizzleDatabase = PostgresJsDatabase<TRelations>> {
1329
1746
  /**
1330
1747
  * Write database instance
1331
1748
  *
@@ -1342,7 +1759,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1342
1759
  * }
1343
1760
  * ```
1344
1761
  */
1345
- protected get db(): PostgresJsDatabase<TSchema>;
1762
+ protected get db(): RepositoryDatabase<TDatabase>;
1346
1763
  /**
1347
1764
  * Read database instance
1348
1765
  *
@@ -1363,7 +1780,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1363
1780
  * }
1364
1781
  * ```
1365
1782
  */
1366
- protected get readDb(): PostgresJsDatabase<TSchema>;
1783
+ protected get readDb(): RepositoryDatabase<TDatabase>;
1367
1784
  /**
1368
1785
  * Wrap query execution with repository context
1369
1786
  *
@@ -1409,7 +1826,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1409
1826
  * const user = await this._findOne(users, eq(users.id, 1));
1410
1827
  * ```
1411
1828
  */
1412
- protected _findOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'] | null>;
1829
+ protected _findOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<InferSelectModel<T> | null>;
1413
1830
  /**
1414
1831
  * Find multiple records
1415
1832
  *
@@ -1431,7 +1848,33 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1431
1848
  orderBy?: SQL | SQL[];
1432
1849
  limit?: number;
1433
1850
  offset?: number;
1434
- }): Promise<T['$inferSelect'][]>;
1851
+ }): Promise<InferSelectModel<T>[]>;
1852
+ /**
1853
+ * Keyset (cursor) pagination — O(limit) instead of OFFSET's O(offset).
1854
+ *
1855
+ * Pages by a strictly-ordered, unique column instead of a numeric offset, so a
1856
+ * deep page doesn't scan and discard everything before it. Pass the cursor
1857
+ * column's value from the last row of the previous page as `after`; omit it for
1858
+ * the first page. The column must be unique and the sole sort key (e.g. an
1859
+ * auto-increment id or a ULID).
1860
+ *
1861
+ * @example
1862
+ * ```typescript
1863
+ * const page1 = await this._findManyKeyset(users, { cursorColumn: users.id, limit: 20 });
1864
+ * const page2 = await this._findManyKeyset(users, {
1865
+ * cursorColumn: users.id,
1866
+ * after: page1.at(-1)?.id,
1867
+ * limit: 20,
1868
+ * });
1869
+ * ```
1870
+ */
1871
+ protected _findManyKeyset<T extends PgTable>(table: T, options: {
1872
+ cursorColumn: PgColumn;
1873
+ limit: number;
1874
+ after?: string | number | bigint | Date;
1875
+ order?: 'asc' | 'desc';
1876
+ where?: Record<string, any> | SQL | undefined;
1877
+ }): Promise<InferSelectModel<T>[]>;
1435
1878
  /**
1436
1879
  * Create a new record
1437
1880
  *
@@ -1447,7 +1890,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1447
1890
  * });
1448
1891
  * ```
1449
1892
  */
1450
- protected _create<T extends PgTable>(table: T, data: T['$inferInsert']): Promise<T['$inferSelect']>;
1893
+ protected _create<T extends PgTable>(table: T, data: InferInsertModel<T>): Promise<InferSelectModel<T>>;
1451
1894
  /**
1452
1895
  * Create multiple records
1453
1896
  *
@@ -1463,7 +1906,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1463
1906
  * ]);
1464
1907
  * ```
1465
1908
  */
1466
- protected _createMany<T extends PgTable>(table: T, data: T['$inferInsert'][]): Promise<T['$inferSelect'][]>;
1909
+ protected _createMany<T extends PgTable>(table: T, data: InferInsertModel<T>[]): Promise<InferSelectModel<T>[]>;
1467
1910
  /**
1468
1911
  * Upsert a record (INSERT or UPDATE on conflict)
1469
1912
  *
@@ -1479,14 +1922,14 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1479
1922
  * value: {...}
1480
1923
  * }, {
1481
1924
  * target: [cache.key],
1482
- * set: { value: data.value, updatedAt: new Date() }
1925
+ * set: { value: data.value } // updatedAt is stamped by the database
1483
1926
  * });
1484
1927
  * ```
1485
1928
  */
1486
- protected _upsert<T extends PgTable>(table: T, data: T['$inferInsert'], options: {
1929
+ protected _upsert<T extends PgTable>(table: T, data: InferInsertModel<T>, options: {
1487
1930
  target: PgColumn[];
1488
- set?: Partial<T['$inferInsert']> | Record<string, SQL | any>;
1489
- }): Promise<T['$inferSelect']>;
1931
+ set?: Partial<InferInsertModel<T>> | Record<string, SQL | any>;
1932
+ }): Promise<InferSelectModel<T>>;
1490
1933
  /**
1491
1934
  * Update a single record
1492
1935
  *
@@ -1503,7 +1946,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1503
1946
  * );
1504
1947
  * ```
1505
1948
  */
1506
- protected _updateOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<T['$inferInsert']>): Promise<T['$inferSelect'] | null>;
1949
+ protected _updateOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<InferInsertModel<T>>): Promise<InferSelectModel<T> | null>;
1507
1950
  /**
1508
1951
  * Update multiple records
1509
1952
  *
@@ -1520,7 +1963,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1520
1963
  * );
1521
1964
  * ```
1522
1965
  */
1523
- protected _updateMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<T['$inferInsert']>): Promise<T['$inferSelect'][]>;
1966
+ protected _updateMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined, data: Partial<InferInsertModel<T>>): Promise<InferSelectModel<T>[]>;
1524
1967
  /**
1525
1968
  * Delete a single record
1526
1969
  *
@@ -1533,7 +1976,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1533
1976
  * const user = await this._deleteOne(users, { id: 1 });
1534
1977
  * ```
1535
1978
  */
1536
- protected _deleteOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'] | null>;
1979
+ protected _deleteOne<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<InferSelectModel<T> | null>;
1537
1980
  /**
1538
1981
  * Delete multiple records
1539
1982
  *
@@ -1546,7 +1989,7 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1546
1989
  * const users = await this._deleteMany(users, { verified: false });
1547
1990
  * ```
1548
1991
  */
1549
- protected _deleteMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<T['$inferSelect'][]>;
1992
+ protected _deleteMany<T extends PgTable>(table: T, where: Record<string, any> | SQL | undefined): Promise<InferSelectModel<T>[]>;
1550
1993
  /**
1551
1994
  * Count records
1552
1995
  *
@@ -1563,4 +2006,4 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1563
2006
  protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1564
2007
  }
1565
2008
 
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 };
2009
+ export { type AfterCommitCallback, BaseRepository, type DatabaseClients, type DatabaseInitOptions, type DatabaseOptions, type DatabaseProvider, type DatabaseTransaction, type DefaultDatabase, type DrizzleConfigOptions, type DrizzleDatabase, type FunctionMigrationEntry, type FunctionMigrationInfo, type MigrationStatus, type MigrationStatusDb, type MigrationTargetStatus, PROJECT_MIGRATIONS_TABLE, PROJECT_TARGET_NAME, type PoolConfig, RUN_MIGRATIONS_HINT, type RepositoryDatabase, RepositoryError, type RetryConfig, type RunInTransactionOptions, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, collectMigrationStatus, count, countPendingMigrations, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, discoverFunctionMigrations, enumText, filterPendingEntries, findMany, findOne, forceReconnectDatabase, foreignKey, formatPendingMigrations, fromPostgresError, functionMigrationsTable, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, getTransactionContext, hasMigrationTargets, id, initDatabase, isConnectionLevelError, migrationTargets, onAfterCommit, optionalForeignKey, packageNameToSchema, pendingMigrationTargets, pendingMigrationsSummary, projectMigrationsDir, publishingFields, readMigrationEntries, reportDatabaseError, resetConnectionErrorCounter, runInTransaction, runWithTransaction, setDatabase, setDatabaseProvider, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };