@spfn/core 0.2.0-beta.4 → 0.2.0-beta.42

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 (68) hide show
  1. package/README.md +260 -1175
  2. package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
  3. package/dist/cache/index.js +32 -29
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/codegen/index.d.ts +55 -8
  6. package/dist/codegen/index.js +179 -5
  7. package/dist/codegen/index.js.map +1 -1
  8. package/dist/config/index.d.ts +168 -6
  9. package/dist/config/index.js +29 -5
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/db/index.d.ts +128 -4
  12. package/dist/db/index.js +177 -50
  13. package/dist/db/index.js.map +1 -1
  14. package/dist/env/index.d.ts +55 -1
  15. package/dist/env/index.js +71 -3
  16. package/dist/env/index.js.map +1 -1
  17. package/dist/env/loader.d.ts +27 -19
  18. package/dist/env/loader.js +33 -25
  19. package/dist/env/loader.js.map +1 -1
  20. package/dist/event/index.d.ts +27 -1
  21. package/dist/event/index.js +6 -1
  22. package/dist/event/index.js.map +1 -1
  23. package/dist/event/sse/client.d.ts +77 -2
  24. package/dist/event/sse/client.js +87 -24
  25. package/dist/event/sse/client.js.map +1 -1
  26. package/dist/event/sse/index.d.ts +10 -4
  27. package/dist/event/sse/index.js +158 -12
  28. package/dist/event/sse/index.js.map +1 -1
  29. package/dist/job/index.d.ts +23 -8
  30. package/dist/job/index.js +96 -20
  31. package/dist/job/index.js.map +1 -1
  32. package/dist/logger/index.d.ts +5 -0
  33. package/dist/logger/index.js +14 -0
  34. package/dist/logger/index.js.map +1 -1
  35. package/dist/middleware/index.d.ts +23 -1
  36. package/dist/middleware/index.js +58 -5
  37. package/dist/middleware/index.js.map +1 -1
  38. package/dist/nextjs/index.d.ts +2 -2
  39. package/dist/nextjs/index.js +77 -31
  40. package/dist/nextjs/index.js.map +1 -1
  41. package/dist/nextjs/server.d.ts +44 -23
  42. package/dist/nextjs/server.js +83 -65
  43. package/dist/nextjs/server.js.map +1 -1
  44. package/dist/route/index.d.ts +158 -4
  45. package/dist/route/index.js +253 -17
  46. package/dist/route/index.js.map +1 -1
  47. package/dist/server/index.d.ts +251 -16
  48. package/dist/server/index.js +774 -228
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  51. package/dist/types-DKQ90YL7.d.ts +372 -0
  52. package/docs/cache.md +133 -0
  53. package/docs/codegen.md +74 -0
  54. package/docs/database.md +370 -0
  55. package/docs/entity.md +539 -0
  56. package/docs/env.md +499 -0
  57. package/docs/errors.md +319 -0
  58. package/docs/event.md +443 -0
  59. package/docs/file-upload.md +717 -0
  60. package/docs/job.md +131 -0
  61. package/docs/logger.md +108 -0
  62. package/docs/middleware.md +337 -0
  63. package/docs/nextjs.md +247 -0
  64. package/docs/repository.md +496 -0
  65. package/docs/route.md +497 -0
  66. package/docs/server.md +429 -0
  67. package/package.json +3 -2
  68. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -376,6 +376,12 @@ interface DrizzleConfigOptions {
376
376
  packageFilter?: string;
377
377
  /** Expand glob patterns to actual file paths (useful for Drizzle Studio) */
378
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;
383
+ /** Migration prefix strategy (default: 'timestamp') */
384
+ migrationPrefix?: 'index' | 'timestamp' | 'unix' | 'none';
379
385
  }
380
386
  /**
381
387
  * Detect database dialect from connection URL
@@ -407,6 +413,21 @@ declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
407
413
  dbCredentials: {
408
414
  url: string;
409
415
  };
416
+ migrations: {
417
+ prefix: "timestamp" | "none" | "index" | "unix";
418
+ };
419
+ schemaFilter?: undefined;
420
+ } | {
421
+ schema: string | string[];
422
+ out: string;
423
+ dialect: "postgresql" | "mysql" | "sqlite";
424
+ dbCredentials: {
425
+ url: string;
426
+ };
427
+ schemaFilter: string[] | undefined;
428
+ migrations: {
429
+ prefix: "timestamp" | "none" | "index" | "unix";
430
+ };
410
431
  };
411
432
  /**
412
433
  * Generate drizzle.config.ts file content
@@ -462,7 +483,7 @@ declare function timestamps(): {
462
483
  /**
463
484
  * Foreign key reference to another table
464
485
  *
465
- * Creates a bigserial column with cascade delete.
486
+ * Creates a bigint column with cascade delete.
466
487
  * Type-safe: ensures the reference points to a valid PostgreSQL column.
467
488
  *
468
489
  * @param name - Column name (e.g., 'author' creates 'author_id')
@@ -482,7 +503,7 @@ declare function timestamps(): {
482
503
  */
483
504
  declare function foreignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
484
505
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
485
- }): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>>;
506
+ }): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigInt53BuilderInitial<`${string}_id`>>;
486
507
  /**
487
508
  * Optional foreign key reference (nullable)
488
509
  *
@@ -502,7 +523,7 @@ declare function foreignKey<T extends PgColumn>(name: string, reference: () => T
502
523
  */
503
524
  declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
504
525
  onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
505
- }): drizzle_orm_pg_core.PgBigSerial53BuilderInitial<`${string}_id`>;
526
+ }): drizzle_orm_pg_core.PgBigInt53BuilderInitial<`${string}_id`>;
506
527
  /**
507
528
  * UUID primary key
508
529
  *
@@ -833,6 +854,10 @@ declare function getSchemaInfo(packageName: string): {
833
854
  * Uses Record<string, unknown> to accept any schema shape
834
855
  */
835
856
  type TransactionDB = PostgresJsDatabase<Record<string, unknown>>;
857
+ /**
858
+ * afterCommit callback type
859
+ */
860
+ type AfterCommitCallback = () => void | Promise<void>;
836
861
  /**
837
862
  * Transaction context stored in AsyncLocalStorage
838
863
  */
@@ -842,6 +867,8 @@ type TransactionContext = {
842
867
  /** Unique transaction ID for logging and tracing */
843
868
  txId: string;
844
869
  level: number;
870
+ /** Callbacks to execute after root transaction commits */
871
+ afterCommitCallbacks: AfterCommitCallback[];
845
872
  };
846
873
  /**
847
874
  * Get current transaction from AsyncLocalStorage
@@ -862,6 +889,31 @@ declare function getTransaction(): TransactionDB | null;
862
889
  */
863
890
  declare function runWithTransaction<T>(tx: TransactionDB, txId: string, // Add txId parameter
864
891
  callback: () => Promise<T>): Promise<T>;
892
+ /**
893
+ * Register a callback to run after the current transaction commits
894
+ *
895
+ * - Inside a transaction: queued and executed after root transaction commits
896
+ * - Outside a transaction: executed immediately (already "committed")
897
+ * - Nested transactions: callbacks bubble up to root transaction
898
+ * - Callbacks run outside transaction context (new connection for DB access)
899
+ * - Errors are logged but never thrown (commit already succeeded)
900
+ *
901
+ * @example
902
+ * ```typescript
903
+ * import { onAfterCommit } from '@spfn/core/db/transaction';
904
+ *
905
+ * async function submit(spaceId: string, chatId: string)
906
+ * {
907
+ * const publication = await publicationRepo.create({...});
908
+ * await requestRepo.updateStatusAtomically(...);
909
+ *
910
+ * onAfterCommit(() => generateArticle(spaceId, chatId, publication.id));
911
+ *
912
+ * return publication;
913
+ * }
914
+ * ```
915
+ */
916
+ declare function onAfterCommit(callback: AfterCommitCallback): void;
865
917
 
866
918
  /**
867
919
  * Transaction middleware options
@@ -942,6 +994,78 @@ interface TransactionalOptions {
942
994
  */
943
995
  declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
944
996
 
997
+ /**
998
+ * Transaction runner options
999
+ */
1000
+ interface RunInTransactionOptions {
1001
+ /**
1002
+ * Slow transaction warning threshold in milliseconds
1003
+ * @default 1000 (1 second)
1004
+ */
1005
+ slowThreshold?: number;
1006
+ /**
1007
+ * Enable transaction logging
1008
+ * @default true
1009
+ */
1010
+ enableLogging?: boolean;
1011
+ /**
1012
+ * Transaction timeout in milliseconds
1013
+ *
1014
+ * Sets PostgreSQL `statement_timeout` to enforce database-level timeout.
1015
+ * If transaction exceeds this duration, PostgreSQL will automatically cancel
1016
+ * the query and rollback the transaction, ensuring data consistency.
1017
+ *
1018
+ * Behavior:
1019
+ * - `timeout: 0` - Disables timeout (unlimited execution time)
1020
+ * - `timeout: null` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1021
+ * - `timeout: undefined` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1022
+ * - `timeout: N` - Sets timeout to N milliseconds (1 to 2147483647)
1023
+ *
1024
+ * Note: Timeout is only applied to root transactions. Nested transactions
1025
+ * (SAVEPOINTs) inherit the timeout from the outer transaction.
1026
+ *
1027
+ * @default 30000 (30 seconds) or TRANSACTION_TIMEOUT environment variable
1028
+ *
1029
+ * @example
1030
+ * ```typescript
1031
+ * // Use default timeout (30s)
1032
+ * await runInTransaction(callback);
1033
+ *
1034
+ * // Disable timeout for long-running operations
1035
+ * await runInTransaction(callback, { timeout: 0 });
1036
+ *
1037
+ * // Set custom timeout (60s)
1038
+ * await runInTransaction(callback, { timeout: 60000 });
1039
+ * ```
1040
+ */
1041
+ timeout?: number;
1042
+ /**
1043
+ * Context string for logging (e.g., 'migration:add-user', 'script:cleanup')
1044
+ * @default 'transaction'
1045
+ */
1046
+ context?: string;
1047
+ }
1048
+ /**
1049
+ * Run a callback function within a database transaction
1050
+ *
1051
+ * Automatically manages transaction lifecycle:
1052
+ * - Commits on success
1053
+ * - Rolls back on error
1054
+ * - Tracks execution time
1055
+ * - Warns about slow transactions
1056
+ * - Enforces timeout if configured
1057
+ *
1058
+ * Errors are propagated to the caller without modification.
1059
+ * Caller is responsible for error handling and conversion.
1060
+ *
1061
+ * @param callback - Function to execute within transaction
1062
+ * @param options - Transaction options
1063
+ * @returns Result of callback function
1064
+ * @throws TransactionError if database not initialized or timeout exceeded
1065
+ * @throws Any error thrown by callback function
1066
+ */
1067
+ declare function runInTransaction<T>(callback: (tx: TransactionDB) => Promise<T>, options?: RunInTransactionOptions): Promise<T>;
1068
+
945
1069
  /**
946
1070
  * PostgreSQL Error Conversion Utilities
947
1071
  *
@@ -1550,4 +1674,4 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1550
1674
  protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1551
1675
  }
1552
1676
 
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 };
1677
+ 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, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
package/dist/db/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { drizzle } from 'drizzle-orm/postgres-js';
2
2
  import { env } from '@spfn/core/config';
3
3
  import { logger } from '@spfn/core/logger';
4
+ import net from 'net';
4
5
  import postgres from 'postgres';
5
6
  import { QueryError, ConnectionError, DeadlockError, TransactionError, ConstraintViolationError, DuplicateEntryError, DatabaseError } from '@spfn/core/errors';
6
7
  import { parseNumber, parseBoolean } from '@spfn/core/env';
7
8
  import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
8
9
  import { join, dirname, basename } from 'path';
9
- import { bigserial, timestamp, uuid as uuid$1, text, jsonb, pgSchema } from 'drizzle-orm/pg-core';
10
+ import { bigserial, timestamp, bigint, uuid as uuid$1, text, jsonb, pgSchema } from 'drizzle-orm/pg-core';
10
11
  import { AsyncLocalStorage } from 'async_hooks';
11
12
  import { createMiddleware } from 'hono/factory';
12
13
  import { randomUUID } from 'crypto';
13
- import { count as count$1, sql, eq, and } from 'drizzle-orm';
14
+ import { sql, count as count$1, eq, and } from 'drizzle-orm';
14
15
 
15
16
  // src/db/manager/factory.ts
16
17
  function parseUniqueViolation(message) {
@@ -129,6 +130,12 @@ function fromPostgresError(error) {
129
130
  }
130
131
 
131
132
  // src/db/manager/connection.ts
133
+ function getSocketFamily() {
134
+ const family = process.env.DATABASE_SOCKET_FAMILY;
135
+ if (family === "4") return 4;
136
+ if (family === "6") return 6;
137
+ return void 0;
138
+ }
132
139
  var dbLogger = logger.child("@spfn/core:database");
133
140
  var DEFAULT_CONNECT_TIMEOUT = 10;
134
141
  function delay(ms) {
@@ -189,10 +196,21 @@ async function createDatabaseConnection(connectionString, poolConfig, retryConfi
189
196
  let client;
190
197
  for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
191
198
  try {
199
+ const socketFamily = getSocketFamily();
192
200
  client = postgres(connectionString, {
193
201
  max: poolConfig.max,
194
202
  idle_timeout: poolConfig.idleTimeout,
195
- connect_timeout: DEFAULT_CONNECT_TIMEOUT
203
+ connect_timeout: DEFAULT_CONNECT_TIMEOUT,
204
+ ...socketFamily && {
205
+ socket: ({ host, port }) => new Promise((resolve, reject) => {
206
+ const socket = new net.Socket();
207
+ socket.on("error", reject);
208
+ socket.connect(
209
+ { port: port[0], host: host[0], family: socketFamily },
210
+ () => resolve(socket)
211
+ );
212
+ })
213
+ }
196
214
  });
197
215
  await client`SELECT 1 as test`;
198
216
  if (attempt > 0) {
@@ -503,6 +521,8 @@ var setMonitoringConfig = (config) => {
503
521
  globalThis.__SPFN_DB_MONITORING__ = config;
504
522
  };
505
523
  var dbLogger3 = logger.child("@spfn/core:database");
524
+ var CLIENT_CLOSE_TIMEOUT = 5;
525
+ var isReconnecting = false;
506
526
  async function testDatabaseConnection(db) {
507
527
  await db.execute("SELECT 1");
508
528
  }
@@ -514,8 +534,13 @@ async function performHealthCheck(getDatabase2) {
514
534
  await testDatabaseConnection(read);
515
535
  }
516
536
  }
517
- async function reconnectAndRestore(options, closeDatabase2) {
518
- await closeDatabase2();
537
+ async function closeClient(client) {
538
+ try {
539
+ await client.end({ timeout: CLIENT_CLOSE_TIMEOUT });
540
+ } catch {
541
+ }
542
+ }
543
+ async function reconnectAndRestore(options) {
519
544
  const result = await createDatabaseFromEnv(options);
520
545
  if (!result.write) {
521
546
  return false;
@@ -524,15 +549,23 @@ async function reconnectAndRestore(options, closeDatabase2) {
524
549
  if (result.read && result.read !== result.write) {
525
550
  await testDatabaseConnection(result.read);
526
551
  }
552
+ const oldWriteClient = getWriteClient();
553
+ const oldReadClient = getReadClient();
527
554
  setWriteInstance(result.write);
528
555
  setReadInstance(result.read);
529
556
  setWriteClient(result.writeClient);
530
557
  setReadClient(result.readClient);
531
558
  const monConfig = buildMonitoringConfig(options?.monitoring);
532
559
  setMonitoringConfig(monConfig);
560
+ if (oldWriteClient) {
561
+ closeClient(oldWriteClient);
562
+ }
563
+ if (oldReadClient && oldReadClient !== oldWriteClient) {
564
+ closeClient(oldReadClient);
565
+ }
533
566
  return true;
534
567
  }
535
- function startHealthCheck(config, options, getDatabase2, closeDatabase2) {
568
+ function startHealthCheck(config, options, getDatabase2) {
536
569
  const healthCheck = getHealthCheckInterval();
537
570
  if (healthCheck) {
538
571
  dbLogger3.debug("Health check already running");
@@ -543,47 +576,56 @@ function startHealthCheck(config, options, getDatabase2, closeDatabase2) {
543
576
  reconnect: config.reconnect
544
577
  });
545
578
  const interval = setInterval(async () => {
579
+ if (isReconnecting) {
580
+ dbLogger3.debug("Health check skipped: reconnection in progress");
581
+ return;
582
+ }
546
583
  try {
547
584
  await performHealthCheck(getDatabase2);
548
585
  } catch (error) {
549
586
  const message = error instanceof Error ? error.message : "Unknown error";
550
587
  dbLogger3.error("Database health check failed", { error: message });
551
588
  if (config.reconnect) {
552
- await attemptReconnection(config, options, closeDatabase2);
589
+ await attemptReconnection(config, options);
553
590
  }
554
591
  }
555
592
  }, config.interval);
556
593
  setHealthCheckInterval(interval);
557
594
  }
558
- async function attemptReconnection(config, options, closeDatabase2) {
595
+ async function attemptReconnection(config, options) {
596
+ isReconnecting = true;
559
597
  dbLogger3.warn("Attempting database reconnection", {
560
598
  maxRetries: config.maxRetries,
561
599
  retryInterval: `${config.retryInterval}ms`
562
600
  });
563
- for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
564
- try {
565
- dbLogger3.debug(`Reconnection attempt ${attempt}/${config.maxRetries}`);
566
- if (attempt > 1) {
567
- await new Promise((resolve) => setTimeout(resolve, config.retryInterval));
601
+ try {
602
+ for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
603
+ try {
604
+ dbLogger3.debug(`Reconnection attempt ${attempt}/${config.maxRetries}`);
605
+ if (attempt > 1) {
606
+ await new Promise((resolve) => setTimeout(resolve, config.retryInterval));
607
+ }
608
+ const success = await reconnectAndRestore(options);
609
+ if (success) {
610
+ dbLogger3.info("Database reconnection successful", { attempt });
611
+ return;
612
+ } else {
613
+ dbLogger3.error(`Reconnection attempt ${attempt} failed: No write database instance created`);
614
+ }
615
+ } catch (error) {
616
+ const message = error instanceof Error ? error.message : "Unknown error";
617
+ dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
618
+ error: message,
619
+ attempt,
620
+ maxRetries: config.maxRetries
621
+ });
568
622
  }
569
- const success = await reconnectAndRestore(options, closeDatabase2);
570
- if (success) {
571
- dbLogger3.info("Database reconnection successful", { attempt });
572
- return;
573
- } else {
574
- dbLogger3.error(`Reconnection attempt ${attempt} failed: No write database instance created`);
623
+ if (attempt === config.maxRetries) {
624
+ dbLogger3.error("Max reconnection attempts reached, will retry on next health check");
575
625
  }
576
- } catch (error) {
577
- const message = error instanceof Error ? error.message : "Unknown error";
578
- dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
579
- error: message,
580
- attempt,
581
- maxRetries: config.maxRetries
582
- });
583
- }
584
- if (attempt === config.maxRetries) {
585
- dbLogger3.error("Max reconnection attempts reached, giving up");
586
626
  }
627
+ } finally {
628
+ isReconnecting = false;
587
629
  }
588
630
  }
589
631
  function stopHealthCheck() {
@@ -593,6 +635,7 @@ function stopHealthCheck() {
593
635
  setHealthCheckInterval(void 0);
594
636
  dbLogger3.info("Database health check stopped");
595
637
  }
638
+ isReconnecting = false;
596
639
  }
597
640
 
598
641
  // src/db/manager/manager.ts
@@ -742,7 +785,7 @@ async function initDatabase(options) {
742
785
  );
743
786
  const healthCheckConfig = buildHealthCheckConfig(options?.healthCheck);
744
787
  if (healthCheckConfig.enabled) {
745
- startHealthCheck(healthCheckConfig, options, getDatabase, closeDatabase);
788
+ startHealthCheck(healthCheckConfig, options, getDatabase);
746
789
  }
747
790
  const monConfig = buildMonitoringConfig(options?.monitoring);
748
791
  setMonitoringConfig(monConfig);
@@ -811,7 +854,7 @@ function getDatabaseInfo() {
811
854
  isReplica: !!(readInst && readInst !== writeInst)
812
855
  };
813
856
  }
814
- var INDEX_FILE_PATTERNS = [
857
+ var BARREL_FILE_PATTERNS = [
815
858
  "/index",
816
859
  "/index.ts",
817
860
  "/index.js",
@@ -819,11 +862,14 @@ var INDEX_FILE_PATTERNS = [
819
862
  "\\index",
820
863
  "\\index.ts",
821
864
  "\\index.js",
822
- "\\index.mjs"
865
+ "\\index.mjs",
866
+ "\\config.ts",
867
+ "\\config.js",
868
+ "\\config.mjs"
823
869
  ];
824
870
  var SUPPORTED_EXTENSIONS = [".ts", ".js", ".mjs"];
825
- function isIndexFile(filePath) {
826
- return INDEX_FILE_PATTERNS.some((pattern) => filePath.endsWith(pattern));
871
+ function isBarrelFile(filePath) {
872
+ return BARREL_FILE_PATTERNS.some((pattern) => filePath.endsWith(pattern));
827
873
  }
828
874
  function isAbsolutePath(path) {
829
875
  if (path.startsWith("/")) return true;
@@ -833,8 +879,8 @@ function hasSupportedExtension(filePath) {
833
879
  if (filePath.endsWith(".d.ts")) return false;
834
880
  return SUPPORTED_EXTENSIONS.some((ext) => filePath.endsWith(ext));
835
881
  }
836
- function filterIndexFiles(files) {
837
- return files.filter((file) => !isIndexFile(file));
882
+ function filterBarrelFiles(files) {
883
+ return files.filter((file) => !isBarrelFile(file));
838
884
  }
839
885
  function scanDirectoryRecursive(dir, extension) {
840
886
  const files = [];
@@ -880,6 +926,27 @@ function scanDirectorySingleLevel(dir, filePattern) {
880
926
  }
881
927
  return files;
882
928
  }
929
+ function detectSchemasFromFiles(files) {
930
+ const schemas = /* @__PURE__ */ new Set(["public"]);
931
+ const pgSchemaPattern = /pgSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
932
+ const createSchemaPattern = /createSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
933
+ for (const filePath of files) {
934
+ try {
935
+ const content = readFileSync(filePath, "utf-8");
936
+ let match;
937
+ while ((match = pgSchemaPattern.exec(content)) !== null) {
938
+ schemas.add(match[1]);
939
+ }
940
+ while ((match = createSchemaPattern.exec(content)) !== null) {
941
+ const packageName = match[1];
942
+ const schemaName = packageName.replace(/@/g, "").replace(/\//g, "_").replace(/-/g, "_");
943
+ schemas.add(schemaName);
944
+ }
945
+ } catch {
946
+ }
947
+ }
948
+ return Array.from(schemas);
949
+ }
883
950
  function expandGlobPattern(pattern) {
884
951
  if (!pattern.includes("*")) {
885
952
  return existsSync(pattern) ? [pattern] : [];
@@ -922,7 +989,7 @@ function discoverPackageSchemas(cwd) {
922
989
  for (const schema of packageSchemas) {
923
990
  const absolutePath = join(pkgPath, schema);
924
991
  const expandedFiles = expandGlobPattern(absolutePath);
925
- const schemaFiles = filterIndexFiles(expandedFiles);
992
+ const schemaFiles = filterBarrelFiles(expandedFiles);
926
993
  schemas.push(...schemaFiles);
927
994
  }
928
995
  }
@@ -984,28 +1051,45 @@ function getDrizzleConfig(options = {}) {
984
1051
  schema: schema2,
985
1052
  out,
986
1053
  dialect,
987
- dbCredentials: getDbCredentials(dialect, databaseUrl)
1054
+ dbCredentials: getDbCredentials(dialect, databaseUrl),
1055
+ migrations: {
1056
+ prefix: options.migrationPrefix ?? "timestamp"
1057
+ }
988
1058
  };
989
1059
  }
990
1060
  const userSchema = options.schema ?? "./src/server/entities/**/*.ts";
991
1061
  const userSchemas = Array.isArray(userSchema) ? userSchema : [userSchema];
992
1062
  const packageSchemas = options.disablePackageDiscovery ? [] : discoverPackageSchemas(options.cwd ?? process.cwd());
993
1063
  let allSchemas = [...userSchemas, ...packageSchemas];
1064
+ const cwd = options.cwd ?? process.cwd();
1065
+ let expandedFiles = [];
994
1066
  if (options.expandGlobs) {
995
- const expandedSchemas = [];
996
1067
  for (const schema2 of allSchemas) {
997
- const expanded = expandGlobPattern(schema2);
998
- const filtered = filterIndexFiles(expanded);
999
- expandedSchemas.push(...filtered);
1068
+ const absoluteSchema = isAbsolutePath(schema2) ? schema2 : join(cwd, schema2);
1069
+ const expanded = expandGlobPattern(absoluteSchema);
1070
+ const filtered = filterBarrelFiles(expanded);
1071
+ expandedFiles.push(...filtered);
1000
1072
  }
1001
- allSchemas = expandedSchemas;
1073
+ allSchemas = expandedFiles;
1002
1074
  }
1003
1075
  const schema = allSchemas.length === 1 ? allSchemas[0] : allSchemas;
1076
+ let schemaFilter;
1077
+ if (dialect === "postgresql") {
1078
+ if (options.schemaFilter) {
1079
+ schemaFilter = options.schemaFilter;
1080
+ } else if (options.autoDetectSchemas && expandedFiles.length > 0) {
1081
+ schemaFilter = detectSchemasFromFiles(expandedFiles);
1082
+ }
1083
+ }
1004
1084
  return {
1005
1085
  schema,
1006
1086
  out,
1007
1087
  dialect,
1008
- dbCredentials: getDbCredentials(dialect, databaseUrl)
1088
+ dbCredentials: getDbCredentials(dialect, databaseUrl),
1089
+ schemaFilter,
1090
+ migrations: {
1091
+ prefix: options.migrationPrefix ?? "timestamp"
1092
+ }
1009
1093
  };
1010
1094
  }
1011
1095
  function getDbCredentials(dialect, url) {
@@ -1032,13 +1116,17 @@ function generateDrizzleConfigFile(options = {}) {
1032
1116
  const schemaValue = Array.isArray(config.schema) ? `[
1033
1117
  ${config.schema.map((s) => `'${normalizeSchemaPath(s)}'`).join(",\n ")}
1034
1118
  ]` : `'${normalizeSchemaPath(config.schema)}'`;
1119
+ const schemaFilterLine = config.schemaFilter && config.schemaFilter.length > 0 ? `
1120
+ schemaFilter: ${JSON.stringify(config.schemaFilter)},` : "";
1121
+ const migrationsLine = config.migrations ? `
1122
+ migrations: ${JSON.stringify(config.migrations)},` : "";
1035
1123
  return `import { defineConfig } from 'drizzle-kit';
1036
1124
 
1037
1125
  export default defineConfig({
1038
1126
  schema: ${schemaValue},
1039
1127
  out: '${config.out}',
1040
1128
  dialect: '${config.dialect}',
1041
- dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},
1129
+ dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},${schemaFilterLine}${migrationsLine}
1042
1130
  });
1043
1131
  `;
1044
1132
  }
@@ -1052,10 +1140,10 @@ function timestamps() {
1052
1140
  };
1053
1141
  }
1054
1142
  function foreignKey(name, reference, options) {
1055
- return bigserial(`${name}_id`, { mode: "number" }).notNull().references(reference, { onDelete: options?.onDelete ?? "cascade" });
1143
+ return bigint(`${name}_id`, { mode: "number" }).notNull().references(reference, { onDelete: options?.onDelete ?? "cascade" });
1056
1144
  }
1057
1145
  function optionalForeignKey(name, reference, options) {
1058
- return bigserial(`${name}_id`, { mode: "number" }).references(reference, { onDelete: options?.onDelete ?? "set null" });
1146
+ return bigint(`${name}_id`, { mode: "number" }).references(reference, { onDelete: options?.onDelete ?? "set null" });
1059
1147
  }
1060
1148
  function uuid() {
1061
1149
  return uuid$1("id").defaultRandom().primaryKey();
@@ -1134,7 +1222,20 @@ function runWithTransaction(tx, txId, callback) {
1134
1222
  } else {
1135
1223
  txLogger.debug("Root transaction context set", { txId, level: newLevel });
1136
1224
  }
1137
- return asyncContext.run({ tx, txId, level: newLevel }, callback);
1225
+ const afterCommitCallbacks = existingContext ? existingContext.afterCommitCallbacks : [];
1226
+ return asyncContext.run({ tx, txId, level: newLevel, afterCommitCallbacks }, callback);
1227
+ }
1228
+ function onAfterCommit(callback) {
1229
+ const context = getTransactionContext();
1230
+ if (!context) {
1231
+ Promise.resolve().then(callback).catch((err) => {
1232
+ txLogger.error("afterCommit callback failed (no transaction)", {
1233
+ error: err instanceof Error ? err.message : String(err)
1234
+ });
1235
+ });
1236
+ return;
1237
+ }
1238
+ context.afterCommitCallbacks.push(callback);
1138
1239
  }
1139
1240
  var MAX_TIMEOUT_MS = 2147483647;
1140
1241
  var txLogger2 = logger.child("@spfn/core:transaction");
@@ -1217,13 +1318,21 @@ async function runInTransaction(callback, options = {}) {
1217
1318
  txLogger2.debug("Transaction started", { txId, context });
1218
1319
  }
1219
1320
  const startTime = Date.now();
1321
+ let afterCommitCallbacks = [];
1220
1322
  try {
1221
1323
  const result = await writeDb.transaction(async (tx) => {
1222
1324
  if (timeout > 0 && !isNested) {
1223
1325
  await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
1224
1326
  }
1225
1327
  return await runWithTransaction(tx, txId, async () => {
1226
- return await callback(tx);
1328
+ const innerResult = await callback(tx);
1329
+ if (!isNested) {
1330
+ const ctx = getTransactionContext();
1331
+ if (ctx) {
1332
+ afterCommitCallbacks = [...ctx.afterCommitCallbacks];
1333
+ }
1334
+ }
1335
+ return innerResult;
1227
1336
  });
1228
1337
  });
1229
1338
  const duration = Date.now() - startTime;
@@ -1243,6 +1352,24 @@ async function runInTransaction(callback, options = {}) {
1243
1352
  });
1244
1353
  }
1245
1354
  }
1355
+ if (!isNested && afterCommitCallbacks.length > 0) {
1356
+ if (enableLogging) {
1357
+ txLogger2.debug("Executing afterCommit callbacks", {
1358
+ txId,
1359
+ context,
1360
+ count: afterCommitCallbacks.length
1361
+ });
1362
+ }
1363
+ for (const cb of afterCommitCallbacks) {
1364
+ Promise.resolve().then(cb).catch((err) => {
1365
+ txLogger2.error("afterCommit callback failed", {
1366
+ txId,
1367
+ context,
1368
+ error: err instanceof Error ? err.message : String(err)
1369
+ });
1370
+ });
1371
+ }
1372
+ }
1246
1373
  return result;
1247
1374
  } catch (error) {
1248
1375
  const duration = Date.now() - startTime;
@@ -1788,6 +1915,6 @@ var BaseRepository = class {
1788
1915
  }
1789
1916
  };
1790
1917
 
1791
- export { BaseRepository, RepositoryError, Transactional, 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 };
1918
+ export { BaseRepository, RepositoryError, Transactional, 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, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1792
1919
  //# sourceMappingURL=index.js.map
1793
1920
  //# sourceMappingURL=index.js.map