@spfn/core 0.2.0-beta.5 → 0.2.0-beta.50

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 (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +181 -1281
  3. package/dist/{boss-BO8ty33K.d.ts → boss-Cxqc-Oiw.d.ts} +37 -7
  4. package/dist/cache/index.js +32 -29
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +55 -8
  7. package/dist/codegen/index.js +179 -5
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +168 -6
  10. package/dist/config/index.js +29 -5
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/db/index.d.ts +218 -4
  13. package/dist/db/index.js +351 -57
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +2 -1
  16. package/dist/env/index.js +2 -1
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/env/loader.d.ts +26 -19
  19. package/dist/env/loader.js +32 -25
  20. package/dist/env/loader.js.map +1 -1
  21. package/dist/errors/index.js.map +1 -1
  22. package/dist/event/index.d.ts +33 -3
  23. package/dist/event/index.js +17 -1
  24. package/dist/event/index.js.map +1 -1
  25. package/dist/event/sse/client.d.ts +42 -3
  26. package/dist/event/sse/client.js +128 -45
  27. package/dist/event/sse/client.js.map +1 -1
  28. package/dist/event/sse/index.d.ts +12 -5
  29. package/dist/event/sse/index.js +188 -20
  30. package/dist/event/sse/index.js.map +1 -1
  31. package/dist/event/ws/client.d.ts +59 -0
  32. package/dist/event/ws/client.js +273 -0
  33. package/dist/event/ws/client.js.map +1 -0
  34. package/dist/event/ws/index.d.ts +94 -0
  35. package/dist/event/ws/index.js +213 -0
  36. package/dist/event/ws/index.js.map +1 -0
  37. package/dist/job/index.d.ts +23 -8
  38. package/dist/job/index.js +154 -44
  39. package/dist/job/index.js.map +1 -1
  40. package/dist/logger/index.d.ts +5 -0
  41. package/dist/logger/index.js +14 -0
  42. package/dist/logger/index.js.map +1 -1
  43. package/dist/middleware/index.d.ts +23 -1
  44. package/dist/middleware/index.js +58 -5
  45. package/dist/middleware/index.js.map +1 -1
  46. package/dist/nextjs/index.d.ts +2 -2
  47. package/dist/nextjs/index.js +77 -31
  48. package/dist/nextjs/index.js.map +1 -1
  49. package/dist/nextjs/server.d.ts +44 -23
  50. package/dist/nextjs/server.js +83 -65
  51. package/dist/nextjs/server.js.map +1 -1
  52. package/dist/route/index.d.ts +158 -4
  53. package/dist/route/index.js +238 -22
  54. package/dist/route/index.js.map +1 -1
  55. package/dist/server/index.d.ts +308 -17
  56. package/dist/server/index.js +1128 -261
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/{router-Di7ENoah.d.ts → token-manager-CyG7la3p.d.ts} +116 -1
  59. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  60. package/dist/types-C1jMLGwK.d.ts +257 -0
  61. package/dist/types-Cfj--lfr.d.ts +151 -0
  62. package/docs/file-upload.md +717 -0
  63. package/package.json +18 -5
  64. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -273,6 +273,36 @@ declare function initDatabase(options?: DatabaseOptions): Promise<{
273
273
  * ```
274
274
  */
275
275
  declare function closeDatabase(): Promise<void>;
276
+ /**
277
+ * Force an immediate database pool rebuild
278
+ *
279
+ * Destroys the current postgres.js pool(s) and rebuilds them with the same
280
+ * configuration passed to the original `initDatabase()` call (or whatever
281
+ * was detected from environment variables). Uses the same atomic-swap
282
+ * strategy as the periodic health check: new connections are created and
283
+ * tested BEFORE the old ones are torn down, so `getDatabase()` callers never
284
+ * observe a missing instance.
285
+ *
286
+ * Use this when application code detects that the pool is stuck and does not
287
+ * want to wait for the next periodic health check tick. Concurrent calls are
288
+ * coalesced — if a reconnect is already in progress, this resolves to `false`
289
+ * without starting a second one.
290
+ *
291
+ * @param reason - Short label describing why the rebuild was requested (for logs)
292
+ * @returns `true` if a reconnection ran, `false` if one was already in-flight.
293
+ * Resolves after the rebuild completes (success or max retries exhausted).
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * import { forceReconnectDatabase } from '@spfn/core/db';
298
+ *
299
+ * app.post('/admin/db/reconnect', async (c) => {
300
+ * const ran = await forceReconnectDatabase('admin_request');
301
+ * return c.json({ reconnected: ran });
302
+ * });
303
+ * ```
304
+ */
305
+ declare function forceReconnectDatabase(reason?: string): Promise<boolean>;
276
306
  /**
277
307
  * Get database connection info (for debugging)
278
308
  *
@@ -308,6 +338,60 @@ declare function getDatabaseInfo(): {
308
338
  isReplica: boolean;
309
339
  };
310
340
 
341
+ /**
342
+ * Reconnect Trigger — Query-error driven pool rebuild
343
+ *
344
+ * Complements the periodic health check with a fast-path: when application
345
+ * queries start failing with connection-level errors, we do not wait up to
346
+ * DB_HEALTH_CHECK_INTERVAL (default 60s) to notice. A sliding-window counter
347
+ * trips a force-reconnect as soon as the failure rate crosses a threshold.
348
+ *
349
+ * Why this exists:
350
+ * - postgres.js transparently drops dead sockets and opens new ones on the
351
+ * next query. A single `SELECT 1` on the periodic interval can therefore
352
+ * false-pass while user-facing queries keep hitting the remaining dead
353
+ * sockets in the pool.
354
+ * - This module observes real query errors and, when it sees a burst of
355
+ * connection-level failures, calls triggerForceReconnect() which performs
356
+ * the same atomic-swap rebuild as the health check.
357
+ *
358
+ * Configuration (env vars, hardcoded defaults):
359
+ * - DB_RECONNECT_ERROR_THRESHOLD (default 3): errors needed in window
360
+ * - DB_RECONNECT_ERROR_WINDOW_MS (default 10000): sliding window size
361
+ */
362
+ /**
363
+ * Determine whether an error looks like a pool/connection failure
364
+ *
365
+ * Returns true when any layer in the error chain exposes a connection-level
366
+ * code (postgres.js driver code, Node network errno, PG SQLSTATE class 08 etc.)
367
+ * or is an instance of our own ConnectionError wrapper.
368
+ *
369
+ * Returns false for query errors (syntax, constraint violations, etc.) — those
370
+ * should NOT trigger a pool rebuild.
371
+ */
372
+ declare function isConnectionLevelError(error: unknown): boolean;
373
+ /**
374
+ * Reset the internal error counter
375
+ *
376
+ * Exposed for tests that need a clean slate between cases. Does not clear
377
+ * the WeakSet (which is GC-backed and self-cleans with error lifetimes).
378
+ */
379
+ declare function resetConnectionErrorCounter(): void;
380
+ /**
381
+ * Report a database error to the reconnect trigger
382
+ *
383
+ * Call this from any site that catches a query error before rethrowing.
384
+ * It is a no-op for non-connection-level errors. When the threshold is
385
+ * crossed it calls triggerForceReconnect() in the background — callers
386
+ * should NOT await it.
387
+ *
388
+ * Safe to call from any context: catches its own errors so it cannot
389
+ * disrupt the calling catch block. Deduplicates across error-chain
390
+ * re-wrapping so one failure counts exactly once regardless of how many
391
+ * catch layers it passes through.
392
+ */
393
+ declare function reportDatabaseError(error: unknown): void;
394
+
311
395
  /**
312
396
  * Create database connection with exponential backoff retry strategy
313
397
  *
@@ -376,6 +460,12 @@ interface DrizzleConfigOptions {
376
460
  packageFilter?: string;
377
461
  /** Expand glob patterns to actual file paths (useful for Drizzle Studio) */
378
462
  expandGlobs?: boolean;
463
+ /** PostgreSQL schema filter for push/introspect commands */
464
+ schemaFilter?: string[];
465
+ /** Auto-detect PostgreSQL schemas from entity files (requires expandGlobs: true) */
466
+ autoDetectSchemas?: boolean;
467
+ /** Migration prefix strategy (default: 'timestamp') */
468
+ migrationPrefix?: 'index' | 'timestamp' | 'unix' | 'none';
379
469
  }
380
470
  /**
381
471
  * Detect database dialect from connection URL
@@ -407,6 +497,21 @@ declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
407
497
  dbCredentials: {
408
498
  url: string;
409
499
  };
500
+ migrations: {
501
+ prefix: "timestamp" | "none" | "index" | "unix";
502
+ };
503
+ schemaFilter?: undefined;
504
+ } | {
505
+ schema: string | string[];
506
+ out: string;
507
+ dialect: "postgresql" | "mysql" | "sqlite";
508
+ dbCredentials: {
509
+ url: string;
510
+ };
511
+ schemaFilter: string[] | undefined;
512
+ migrations: {
513
+ prefix: "timestamp" | "none" | "index" | "unix";
514
+ };
410
515
  };
411
516
  /**
412
517
  * Generate drizzle.config.ts file content
@@ -462,7 +567,7 @@ declare function timestamps(): {
462
567
  /**
463
568
  * Foreign key reference to another table
464
569
  *
465
- * Creates a bigserial column with cascade delete.
570
+ * Creates a bigint column with cascade delete.
466
571
  * Type-safe: ensures the reference points to a valid PostgreSQL column.
467
572
  *
468
573
  * @param name - Column name (e.g., 'author' creates 'author_id')
@@ -482,7 +587,7 @@ declare function timestamps(): {
482
587
  */
483
588
  declare function foreignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
484
589
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
485
- }): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>>;
590
+ }): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigInt53BuilderInitial<`${string}_id`>>;
486
591
  /**
487
592
  * Optional foreign key reference (nullable)
488
593
  *
@@ -502,7 +607,7 @@ declare function foreignKey<T extends PgColumn>(name: string, reference: () => T
502
607
  */
503
608
  declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
504
609
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
505
- }): drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>;
610
+ }): drizzle_orm_pg_core.PgBigInt53BuilderInitial<`${string}_id`>;
506
611
  /**
507
612
  * UUID primary key
508
613
  *
@@ -833,6 +938,10 @@ declare function getSchemaInfo(packageName: string): {
833
938
  * Uses Record<string, unknown> to accept any schema shape
834
939
  */
835
940
  type TransactionDB = PostgresJsDatabase<Record<string, unknown>>;
941
+ /**
942
+ * afterCommit callback type
943
+ */
944
+ type AfterCommitCallback = () => void | Promise<void>;
836
945
  /**
837
946
  * Transaction context stored in AsyncLocalStorage
838
947
  */
@@ -842,7 +951,15 @@ type TransactionContext = {
842
951
  /** Unique transaction ID for logging and tracing */
843
952
  txId: string;
844
953
  level: number;
954
+ /** Callbacks to execute after root transaction commits */
955
+ afterCommitCallbacks: AfterCommitCallback[];
845
956
  };
957
+ /**
958
+ * Get current transaction object and metadata from AsyncLocalStorage
959
+ *
960
+ * @returns TransactionContext if available, null otherwise
961
+ */
962
+ declare function getTransactionContext(): TransactionContext | null;
846
963
  /**
847
964
  * Get current transaction from AsyncLocalStorage
848
965
  *
@@ -862,6 +979,31 @@ declare function getTransaction(): TransactionDB | null;
862
979
  */
863
980
  declare function runWithTransaction<T>(tx: TransactionDB, txId: string, // Add txId parameter
864
981
  callback: () => Promise<T>): Promise<T>;
982
+ /**
983
+ * Register a callback to run after the current transaction commits
984
+ *
985
+ * - Inside a transaction: queued and executed after root transaction commits
986
+ * - Outside a transaction: executed immediately (already "committed")
987
+ * - Nested transactions: callbacks bubble up to root transaction
988
+ * - Callbacks run outside transaction context (new connection for DB access)
989
+ * - Errors are logged but never thrown (commit already succeeded)
990
+ *
991
+ * @example
992
+ * ```typescript
993
+ * import { onAfterCommit } from '@spfn/core/db/transaction';
994
+ *
995
+ * async function submit(spaceId: string, chatId: string)
996
+ * {
997
+ * const publication = await publicationRepo.create({...});
998
+ * await requestRepo.updateStatusAtomically(...);
999
+ *
1000
+ * onAfterCommit(() => generateArticle(spaceId, chatId, publication.id));
1001
+ *
1002
+ * return publication;
1003
+ * }
1004
+ * ```
1005
+ */
1006
+ declare function onAfterCommit(callback: AfterCommitCallback): void;
865
1007
 
866
1008
  /**
867
1009
  * Transaction middleware options
@@ -942,6 +1084,78 @@ interface TransactionalOptions {
942
1084
  */
943
1085
  declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
944
1086
 
1087
+ /**
1088
+ * Transaction runner options
1089
+ */
1090
+ interface RunInTransactionOptions {
1091
+ /**
1092
+ * Slow transaction warning threshold in milliseconds
1093
+ * @default 1000 (1 second)
1094
+ */
1095
+ slowThreshold?: number;
1096
+ /**
1097
+ * Enable transaction logging
1098
+ * @default true
1099
+ */
1100
+ enableLogging?: boolean;
1101
+ /**
1102
+ * Transaction timeout in milliseconds
1103
+ *
1104
+ * Sets PostgreSQL `statement_timeout` to enforce database-level timeout.
1105
+ * If transaction exceeds this duration, PostgreSQL will automatically cancel
1106
+ * the query and rollback the transaction, ensuring data consistency.
1107
+ *
1108
+ * Behavior:
1109
+ * - `timeout: 0` - Disables timeout (unlimited execution time)
1110
+ * - `timeout: null` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1111
+ * - `timeout: undefined` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1112
+ * - `timeout: N` - Sets timeout to N milliseconds (1 to 2147483647)
1113
+ *
1114
+ * Note: Timeout is only applied to root transactions. Nested transactions
1115
+ * (SAVEPOINTs) inherit the timeout from the outer transaction.
1116
+ *
1117
+ * @default 30000 (30 seconds) or TRANSACTION_TIMEOUT environment variable
1118
+ *
1119
+ * @example
1120
+ * ```typescript
1121
+ * // Use default timeout (30s)
1122
+ * await runInTransaction(callback);
1123
+ *
1124
+ * // Disable timeout for long-running operations
1125
+ * await runInTransaction(callback, { timeout: 0 });
1126
+ *
1127
+ * // Set custom timeout (60s)
1128
+ * await runInTransaction(callback, { timeout: 60000 });
1129
+ * ```
1130
+ */
1131
+ timeout?: number;
1132
+ /**
1133
+ * Context string for logging (e.g., 'migration:add-user', 'script:cleanup')
1134
+ * @default 'transaction'
1135
+ */
1136
+ context?: string;
1137
+ }
1138
+ /**
1139
+ * Run a callback function within a database transaction
1140
+ *
1141
+ * Automatically manages transaction lifecycle:
1142
+ * - Commits on success
1143
+ * - Rolls back on error
1144
+ * - Tracks execution time
1145
+ * - Warns about slow transactions
1146
+ * - Enforces timeout if configured
1147
+ *
1148
+ * Errors are propagated to the caller without modification.
1149
+ * Caller is responsible for error handling and conversion.
1150
+ *
1151
+ * @param callback - Function to execute within transaction
1152
+ * @param options - Transaction options
1153
+ * @returns Result of callback function
1154
+ * @throws TransactionError if database not initialized or timeout exceeded
1155
+ * @throws Any error thrown by callback function
1156
+ */
1157
+ declare function runInTransaction<T>(callback: (tx: TransactionDB) => Promise<T>, options?: RunInTransactionOptions): Promise<T>;
1158
+
945
1159
  /**
946
1160
  * PostgreSQL Error Conversion Utilities
947
1161
  *
@@ -1550,4 +1764,4 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1550
1764
  protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1551
1765
  }
1552
1766
 
1553
- export { BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1767
+ export { type AfterCommitCallback, BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type RunInTransactionOptions, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, forceReconnectDatabase, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, getTransactionContext, id, initDatabase, isConnectionLevelError, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, reportDatabaseError, resetConnectionErrorCounter, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };