@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
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) {
@@ -502,7 +520,20 @@ var setHealthCheckInterval = (interval) => {
502
520
  var setMonitoringConfig = (config) => {
503
521
  globalThis.__SPFN_DB_MONITORING__ = config;
504
522
  };
523
+ var getInitOptions = () => globalThis.__SPFN_DB_INIT_OPTIONS__;
524
+ var setInitOptions = (options) => {
525
+ globalThis.__SPFN_DB_INIT_OPTIONS__ = options;
526
+ };
527
+ var getIsClosing = () => globalThis.__SPFN_DB_CLOSING__ === true;
528
+ var setIsClosing = (closing) => {
529
+ globalThis.__SPFN_DB_CLOSING__ = closing;
530
+ };
505
531
  var dbLogger3 = logger.child("@spfn/core:database");
532
+ var CLIENT_CLOSE_TIMEOUT = 5;
533
+ var isReconnecting = false;
534
+ function isReconnectingNow() {
535
+ return isReconnecting;
536
+ }
506
537
  async function testDatabaseConnection(db) {
507
538
  await db.execute("SELECT 1");
508
539
  }
@@ -514,8 +545,17 @@ async function performHealthCheck(getDatabase2) {
514
545
  await testDatabaseConnection(read);
515
546
  }
516
547
  }
517
- async function reconnectAndRestore(options, closeDatabase2) {
518
- await closeDatabase2();
548
+ async function closeClient(client) {
549
+ try {
550
+ await client.end({ timeout: CLIENT_CLOSE_TIMEOUT });
551
+ } catch {
552
+ }
553
+ }
554
+ async function reconnectAndRestore(options) {
555
+ if (getIsClosing()) {
556
+ dbLogger3.debug("reconnectAndRestore aborted: database is closing");
557
+ return false;
558
+ }
519
559
  const result = await createDatabaseFromEnv(options);
520
560
  if (!result.write) {
521
561
  return false;
@@ -524,15 +564,33 @@ async function reconnectAndRestore(options, closeDatabase2) {
524
564
  if (result.read && result.read !== result.write) {
525
565
  await testDatabaseConnection(result.read);
526
566
  }
567
+ if (getIsClosing()) {
568
+ dbLogger3.warn("reconnectAndRestore: close started mid-rebuild, discarding new pool");
569
+ if (result.writeClient) {
570
+ await closeClient(result.writeClient);
571
+ }
572
+ if (result.readClient && result.readClient !== result.writeClient) {
573
+ await closeClient(result.readClient);
574
+ }
575
+ return false;
576
+ }
577
+ const oldWriteClient = getWriteClient();
578
+ const oldReadClient = getReadClient();
527
579
  setWriteInstance(result.write);
528
580
  setReadInstance(result.read);
529
581
  setWriteClient(result.writeClient);
530
582
  setReadClient(result.readClient);
531
583
  const monConfig = buildMonitoringConfig(options?.monitoring);
532
584
  setMonitoringConfig(monConfig);
585
+ if (oldWriteClient) {
586
+ closeClient(oldWriteClient);
587
+ }
588
+ if (oldReadClient && oldReadClient !== oldWriteClient) {
589
+ closeClient(oldReadClient);
590
+ }
533
591
  return true;
534
592
  }
535
- function startHealthCheck(config, options, getDatabase2, closeDatabase2) {
593
+ function startHealthCheck(config, options, getDatabase2) {
536
594
  const healthCheck = getHealthCheckInterval();
537
595
  if (healthCheck) {
538
596
  dbLogger3.debug("Health check already running");
@@ -543,48 +601,77 @@ function startHealthCheck(config, options, getDatabase2, closeDatabase2) {
543
601
  reconnect: config.reconnect
544
602
  });
545
603
  const interval = setInterval(async () => {
604
+ if (isReconnecting) {
605
+ dbLogger3.debug("Health check skipped: reconnection in progress");
606
+ return;
607
+ }
546
608
  try {
547
609
  await performHealthCheck(getDatabase2);
548
610
  } catch (error) {
549
611
  const message = error instanceof Error ? error.message : "Unknown error";
550
612
  dbLogger3.error("Database health check failed", { error: message });
551
613
  if (config.reconnect) {
552
- await attemptReconnection(config, options, closeDatabase2);
614
+ await attemptReconnection(config, options, "health_check_failed");
553
615
  }
554
616
  }
555
617
  }, config.interval);
556
618
  setHealthCheckInterval(interval);
557
619
  }
558
- async function attemptReconnection(config, options, closeDatabase2) {
620
+ async function triggerForceReconnect(reason) {
621
+ if (!getWriteInstance()) {
622
+ dbLogger3.warn("Force reconnect skipped: database not initialized", { reason });
623
+ return false;
624
+ }
625
+ if (getIsClosing()) {
626
+ dbLogger3.debug("Force reconnect skipped: database is closing", { reason });
627
+ return false;
628
+ }
629
+ const options = getInitOptions();
630
+ const config = buildHealthCheckConfig(options?.healthCheck);
631
+ dbLogger3.warn("Force reconnect triggered", { reason });
632
+ return await attemptReconnection(config, options, reason);
633
+ }
634
+ async function attemptReconnection(config, options, reason) {
635
+ if (isReconnecting) {
636
+ dbLogger3.debug("Reconnection coalesced: attempt already in progress", { reason });
637
+ return false;
638
+ }
639
+ isReconnecting = true;
559
640
  dbLogger3.warn("Attempting database reconnection", {
641
+ reason,
560
642
  maxRetries: config.maxRetries,
561
643
  retryInterval: `${config.retryInterval}ms`
562
644
  });
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));
645
+ try {
646
+ for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
647
+ try {
648
+ dbLogger3.debug(`Reconnection attempt ${attempt}/${config.maxRetries}`);
649
+ if (attempt > 1) {
650
+ await new Promise((resolve) => setTimeout(resolve, config.retryInterval));
651
+ }
652
+ const success = await reconnectAndRestore(options);
653
+ if (success) {
654
+ dbLogger3.info("Database reconnection successful", { attempt });
655
+ return true;
656
+ } else {
657
+ dbLogger3.error(`Reconnection attempt ${attempt} failed: No write database instance created`);
658
+ }
659
+ } catch (error) {
660
+ const message = error instanceof Error ? error.message : "Unknown error";
661
+ dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
662
+ error: message,
663
+ attempt,
664
+ maxRetries: config.maxRetries
665
+ });
568
666
  }
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`);
667
+ if (attempt === config.maxRetries) {
668
+ dbLogger3.error("Max reconnection attempts reached, will retry on next health check");
575
669
  }
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
670
  }
671
+ } finally {
672
+ isReconnecting = false;
587
673
  }
674
+ return true;
588
675
  }
589
676
  function stopHealthCheck() {
590
677
  const healthCheck = getHealthCheckInterval();
@@ -593,6 +680,7 @@ function stopHealthCheck() {
593
680
  setHealthCheckInterval(void 0);
594
681
  dbLogger3.info("Database health check stopped");
595
682
  }
683
+ isReconnecting = false;
596
684
  }
597
685
 
598
686
  // src/db/manager/manager.ts
@@ -604,7 +692,6 @@ var STACK_TRACE_PATTERNS = {
604
692
  withoutParens: /at (.+):(\d+):(\d+)/
605
693
  };
606
694
  var initPromise = null;
607
- var isClosing = false;
608
695
  async function cleanupDatabaseConnections(writeClient, readClient) {
609
696
  const cleanupPromises = [];
610
697
  if (writeClient) {
@@ -705,7 +792,7 @@ function setDatabase(write, read) {
705
792
  setReadInstance(read ?? write);
706
793
  }
707
794
  async function initDatabase(options) {
708
- if (isClosing) {
795
+ if (getIsClosing()) {
709
796
  throw new Error("Cannot initialize database while closing");
710
797
  }
711
798
  const writeInst = getWriteInstance();
@@ -727,7 +814,7 @@ async function initDatabase(options) {
727
814
  const message = error instanceof Error ? error.message : "Unknown error";
728
815
  throw new Error(`Database connection test failed: ${message}`);
729
816
  }
730
- if (isClosing) {
817
+ if (getIsClosing()) {
731
818
  dbLogger4.warn("Database closed during initialization, cleaning up...");
732
819
  await cleanupDatabaseConnections(result.writeClient, result.readClient);
733
820
  throw new Error("Database closed during initialization");
@@ -736,13 +823,14 @@ async function initDatabase(options) {
736
823
  setReadInstance(result.read);
737
824
  setWriteClient(result.writeClient);
738
825
  setReadClient(result.readClient);
826
+ setInitOptions(options);
739
827
  const hasReplica = result.read && result.read !== result.write;
740
828
  dbLogger4.info(
741
829
  hasReplica ? "Database connected (Primary + Replica)" : "Database connected"
742
830
  );
743
831
  const healthCheckConfig = buildHealthCheckConfig(options?.healthCheck);
744
832
  if (healthCheckConfig.enabled) {
745
- startHealthCheck(healthCheckConfig, options, getDatabase, closeDatabase);
833
+ startHealthCheck(healthCheckConfig, options, getDatabase);
746
834
  }
747
835
  const monConfig = buildMonitoringConfig(options?.monitoring);
748
836
  setMonitoringConfig(monConfig);
@@ -760,11 +848,11 @@ async function initDatabase(options) {
760
848
  return await initPromise;
761
849
  }
762
850
  async function closeDatabase() {
763
- if (isClosing) {
851
+ if (getIsClosing()) {
764
852
  dbLogger4.debug("Database close already in progress");
765
853
  return;
766
854
  }
767
- isClosing = true;
855
+ setIsClosing(true);
768
856
  if (initPromise) {
769
857
  dbLogger4.debug("Waiting for database initialization to complete before closing...");
770
858
  try {
@@ -777,7 +865,7 @@ async function closeDatabase() {
777
865
  const readInst = getReadInstance();
778
866
  if (!writeInst && !readInst) {
779
867
  dbLogger4.debug("No database connections to close");
780
- isClosing = false;
868
+ setIsClosing(false);
781
869
  return;
782
870
  }
783
871
  try {
@@ -799,9 +887,13 @@ async function closeDatabase() {
799
887
  setWriteClient(void 0);
800
888
  setReadClient(void 0);
801
889
  setMonitoringConfig(void 0);
802
- isClosing = false;
890
+ setInitOptions(void 0);
891
+ setIsClosing(false);
803
892
  }
804
893
  }
894
+ async function forceReconnectDatabase(reason = "manual") {
895
+ return await triggerForceReconnect(reason);
896
+ }
805
897
  function getDatabaseInfo() {
806
898
  const writeInst = getWriteInstance();
807
899
  const readInst = getReadInstance();
@@ -811,7 +903,123 @@ function getDatabaseInfo() {
811
903
  isReplica: !!(readInst && readInst !== writeInst)
812
904
  };
813
905
  }
814
- var INDEX_FILE_PATTERNS = [
906
+ var dbLogger5 = logger.child("@spfn/core:database");
907
+ var POSTGRES_JS_CONNECTION_CODES = /* @__PURE__ */ new Set([
908
+ "CONNECTION_ENDED",
909
+ "CONNECTION_CLOSED",
910
+ "CONNECTION_DESTROYED",
911
+ "CONNECT_TIMEOUT",
912
+ "CONNECTION_CONNECT_TIMEOUT"
913
+ ]);
914
+ var NODE_NET_ERROR_CODES = /* @__PURE__ */ new Set([
915
+ "ECONNRESET",
916
+ "ECONNREFUSED",
917
+ "EPIPE",
918
+ "ETIMEDOUT",
919
+ "EHOSTUNREACH",
920
+ "ENETUNREACH",
921
+ "ENOTFOUND"
922
+ ]);
923
+ function isConnectionSqlState(code) {
924
+ if (code.startsWith("08")) return true;
925
+ if (code === "53300") return true;
926
+ if (code === "57P01" || code === "57P02" || code === "57P03") return true;
927
+ return false;
928
+ }
929
+ function* unwrap(error) {
930
+ const seen = /* @__PURE__ */ new Set();
931
+ const stack = [error];
932
+ while (stack.length > 0) {
933
+ const current = stack.pop();
934
+ if (!current || typeof current !== "object" || seen.has(current)) {
935
+ continue;
936
+ }
937
+ seen.add(current);
938
+ const obj = current;
939
+ yield obj;
940
+ for (const key of ["cause", "original", "error", "err", "inner"]) {
941
+ const nested = obj[key];
942
+ if (nested && typeof nested === "object" && !seen.has(nested)) {
943
+ stack.push(nested);
944
+ }
945
+ }
946
+ }
947
+ }
948
+ function isConnectionLevelError(error) {
949
+ if (!error) return false;
950
+ if (error instanceof ConnectionError) return true;
951
+ for (const candidate of unwrap(error)) {
952
+ if (candidate instanceof ConnectionError) return true;
953
+ const code = candidate.code;
954
+ if (typeof code === "string") {
955
+ if (POSTGRES_JS_CONNECTION_CODES.has(code)) return true;
956
+ if (NODE_NET_ERROR_CODES.has(code)) return true;
957
+ if (isConnectionSqlState(code)) return true;
958
+ }
959
+ }
960
+ return false;
961
+ }
962
+ var DEFAULT_THRESHOLD = 3;
963
+ var DEFAULT_WINDOW_MS = 1e4;
964
+ var MIN_WINDOW_MS = 1e3;
965
+ function readPositiveIntEnv(key, defaultValue, min) {
966
+ const raw = process.env[key];
967
+ if (raw === void 0) return defaultValue;
968
+ try {
969
+ return parseNumber(raw, { min, integer: true });
970
+ } catch {
971
+ return defaultValue;
972
+ }
973
+ }
974
+ var ERROR_THRESHOLD = readPositiveIntEnv("DB_RECONNECT_ERROR_THRESHOLD", DEFAULT_THRESHOLD, 1);
975
+ var ERROR_WINDOW_MS = readPositiveIntEnv("DB_RECONNECT_ERROR_WINDOW_MS", DEFAULT_WINDOW_MS, MIN_WINDOW_MS);
976
+ var errorTimestamps = [];
977
+ var reportedErrors = /* @__PURE__ */ new WeakSet();
978
+ function resetConnectionErrorCounter() {
979
+ errorTimestamps.length = 0;
980
+ }
981
+ function checkAndMarkReported(error) {
982
+ let alreadySeen = false;
983
+ const toMark = [];
984
+ for (const candidate of unwrap(error)) {
985
+ if (reportedErrors.has(candidate)) {
986
+ alreadySeen = true;
987
+ }
988
+ toMark.push(candidate);
989
+ }
990
+ if (alreadySeen) return true;
991
+ for (const obj of toMark) {
992
+ reportedErrors.add(obj);
993
+ }
994
+ return false;
995
+ }
996
+ function reportDatabaseError(error) {
997
+ try {
998
+ if (!isConnectionLevelError(error)) return;
999
+ if (isReconnectingNow()) return;
1000
+ if (checkAndMarkReported(error)) return;
1001
+ const now = Date.now();
1002
+ errorTimestamps.push(now);
1003
+ const cutoff = now - ERROR_WINDOW_MS;
1004
+ while (errorTimestamps.length > 0 && errorTimestamps[0] < cutoff) {
1005
+ errorTimestamps.shift();
1006
+ }
1007
+ if (errorTimestamps.length < ERROR_THRESHOLD) return;
1008
+ errorTimestamps.length = 0;
1009
+ dbLogger5.error("Connection-error threshold crossed, forcing pool rebuild", {
1010
+ threshold: ERROR_THRESHOLD,
1011
+ windowMs: ERROR_WINDOW_MS
1012
+ });
1013
+ triggerForceReconnect("query_error_threshold").catch((err) => {
1014
+ const message = err instanceof Error ? err.message : String(err);
1015
+ dbLogger5.error("Forced reconnect after error threshold failed", { error: message });
1016
+ });
1017
+ } catch (err) {
1018
+ const message = err instanceof Error ? err.message : String(err);
1019
+ dbLogger5.debug("reportDatabaseError itself threw, ignoring", { error: message });
1020
+ }
1021
+ }
1022
+ var BARREL_FILE_PATTERNS = [
815
1023
  "/index",
816
1024
  "/index.ts",
817
1025
  "/index.js",
@@ -819,11 +1027,14 @@ var INDEX_FILE_PATTERNS = [
819
1027
  "\\index",
820
1028
  "\\index.ts",
821
1029
  "\\index.js",
822
- "\\index.mjs"
1030
+ "\\index.mjs",
1031
+ "\\config.ts",
1032
+ "\\config.js",
1033
+ "\\config.mjs"
823
1034
  ];
824
1035
  var SUPPORTED_EXTENSIONS = [".ts", ".js", ".mjs"];
825
- function isIndexFile(filePath) {
826
- return INDEX_FILE_PATTERNS.some((pattern) => filePath.endsWith(pattern));
1036
+ function isBarrelFile(filePath) {
1037
+ return BARREL_FILE_PATTERNS.some((pattern) => filePath.endsWith(pattern));
827
1038
  }
828
1039
  function isAbsolutePath(path) {
829
1040
  if (path.startsWith("/")) return true;
@@ -833,8 +1044,8 @@ function hasSupportedExtension(filePath) {
833
1044
  if (filePath.endsWith(".d.ts")) return false;
834
1045
  return SUPPORTED_EXTENSIONS.some((ext) => filePath.endsWith(ext));
835
1046
  }
836
- function filterIndexFiles(files) {
837
- return files.filter((file) => !isIndexFile(file));
1047
+ function filterBarrelFiles(files) {
1048
+ return files.filter((file) => !isBarrelFile(file));
838
1049
  }
839
1050
  function scanDirectoryRecursive(dir, extension) {
840
1051
  const files = [];
@@ -880,6 +1091,27 @@ function scanDirectorySingleLevel(dir, filePattern) {
880
1091
  }
881
1092
  return files;
882
1093
  }
1094
+ function detectSchemasFromFiles(files) {
1095
+ const schemas = /* @__PURE__ */ new Set(["public"]);
1096
+ const pgSchemaPattern = /pgSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1097
+ const createSchemaPattern = /createSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1098
+ for (const filePath of files) {
1099
+ try {
1100
+ const content = readFileSync(filePath, "utf-8");
1101
+ let match;
1102
+ while ((match = pgSchemaPattern.exec(content)) !== null) {
1103
+ schemas.add(match[1]);
1104
+ }
1105
+ while ((match = createSchemaPattern.exec(content)) !== null) {
1106
+ const packageName = match[1];
1107
+ const schemaName = packageName.replace(/@/g, "").replace(/\//g, "_").replace(/-/g, "_");
1108
+ schemas.add(schemaName);
1109
+ }
1110
+ } catch {
1111
+ }
1112
+ }
1113
+ return Array.from(schemas);
1114
+ }
883
1115
  function expandGlobPattern(pattern) {
884
1116
  if (!pattern.includes("*")) {
885
1117
  return existsSync(pattern) ? [pattern] : [];
@@ -922,7 +1154,7 @@ function discoverPackageSchemas(cwd) {
922
1154
  for (const schema of packageSchemas) {
923
1155
  const absolutePath = join(pkgPath, schema);
924
1156
  const expandedFiles = expandGlobPattern(absolutePath);
925
- const schemaFiles = filterIndexFiles(expandedFiles);
1157
+ const schemaFiles = filterBarrelFiles(expandedFiles);
926
1158
  schemas.push(...schemaFiles);
927
1159
  }
928
1160
  }
@@ -984,28 +1216,45 @@ function getDrizzleConfig(options = {}) {
984
1216
  schema: schema2,
985
1217
  out,
986
1218
  dialect,
987
- dbCredentials: getDbCredentials(dialect, databaseUrl)
1219
+ dbCredentials: getDbCredentials(dialect, databaseUrl),
1220
+ migrations: {
1221
+ prefix: options.migrationPrefix ?? "timestamp"
1222
+ }
988
1223
  };
989
1224
  }
990
1225
  const userSchema = options.schema ?? "./src/server/entities/**/*.ts";
991
1226
  const userSchemas = Array.isArray(userSchema) ? userSchema : [userSchema];
992
1227
  const packageSchemas = options.disablePackageDiscovery ? [] : discoverPackageSchemas(options.cwd ?? process.cwd());
993
1228
  let allSchemas = [...userSchemas, ...packageSchemas];
1229
+ const cwd = options.cwd ?? process.cwd();
1230
+ let expandedFiles = [];
994
1231
  if (options.expandGlobs) {
995
- const expandedSchemas = [];
996
1232
  for (const schema2 of allSchemas) {
997
- const expanded = expandGlobPattern(schema2);
998
- const filtered = filterIndexFiles(expanded);
999
- expandedSchemas.push(...filtered);
1233
+ const absoluteSchema = isAbsolutePath(schema2) ? schema2 : join(cwd, schema2);
1234
+ const expanded = expandGlobPattern(absoluteSchema);
1235
+ const filtered = filterBarrelFiles(expanded);
1236
+ expandedFiles.push(...filtered);
1000
1237
  }
1001
- allSchemas = expandedSchemas;
1238
+ allSchemas = expandedFiles;
1002
1239
  }
1003
1240
  const schema = allSchemas.length === 1 ? allSchemas[0] : allSchemas;
1241
+ let schemaFilter;
1242
+ if (dialect === "postgresql") {
1243
+ if (options.schemaFilter) {
1244
+ schemaFilter = options.schemaFilter;
1245
+ } else if (options.autoDetectSchemas && expandedFiles.length > 0) {
1246
+ schemaFilter = detectSchemasFromFiles(expandedFiles);
1247
+ }
1248
+ }
1004
1249
  return {
1005
1250
  schema,
1006
1251
  out,
1007
1252
  dialect,
1008
- dbCredentials: getDbCredentials(dialect, databaseUrl)
1253
+ dbCredentials: getDbCredentials(dialect, databaseUrl),
1254
+ schemaFilter,
1255
+ migrations: {
1256
+ prefix: options.migrationPrefix ?? "timestamp"
1257
+ }
1009
1258
  };
1010
1259
  }
1011
1260
  function getDbCredentials(dialect, url) {
@@ -1032,13 +1281,17 @@ function generateDrizzleConfigFile(options = {}) {
1032
1281
  const schemaValue = Array.isArray(config.schema) ? `[
1033
1282
  ${config.schema.map((s) => `'${normalizeSchemaPath(s)}'`).join(",\n ")}
1034
1283
  ]` : `'${normalizeSchemaPath(config.schema)}'`;
1284
+ const schemaFilterLine = config.schemaFilter && config.schemaFilter.length > 0 ? `
1285
+ schemaFilter: ${JSON.stringify(config.schemaFilter)},` : "";
1286
+ const migrationsLine = config.migrations ? `
1287
+ migrations: ${JSON.stringify(config.migrations)},` : "";
1035
1288
  return `import { defineConfig } from 'drizzle-kit';
1036
1289
 
1037
1290
  export default defineConfig({
1038
1291
  schema: ${schemaValue},
1039
1292
  out: '${config.out}',
1040
1293
  dialect: '${config.dialect}',
1041
- dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},
1294
+ dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},${schemaFilterLine}${migrationsLine}
1042
1295
  });
1043
1296
  `;
1044
1297
  }
@@ -1052,10 +1305,10 @@ function timestamps() {
1052
1305
  };
1053
1306
  }
1054
1307
  function foreignKey(name, reference, options) {
1055
- return bigserial(`${name}_id`, { mode: "number" }).notNull().references(reference, { onDelete: options?.onDelete ?? "cascade" });
1308
+ return bigint(`${name}_id`, { mode: "number" }).notNull().references(reference, { onDelete: options?.onDelete ?? "cascade" });
1056
1309
  }
1057
1310
  function optionalForeignKey(name, reference, options) {
1058
- return bigserial(`${name}_id`, { mode: "number" }).references(reference, { onDelete: options?.onDelete ?? "set null" });
1311
+ return bigint(`${name}_id`, { mode: "number" }).references(reference, { onDelete: options?.onDelete ?? "set null" });
1059
1312
  }
1060
1313
  function uuid() {
1061
1314
  return uuid$1("id").defaultRandom().primaryKey();
@@ -1134,7 +1387,20 @@ function runWithTransaction(tx, txId, callback) {
1134
1387
  } else {
1135
1388
  txLogger.debug("Root transaction context set", { txId, level: newLevel });
1136
1389
  }
1137
- return asyncContext.run({ tx, txId, level: newLevel }, callback);
1390
+ const afterCommitCallbacks = existingContext ? existingContext.afterCommitCallbacks : [];
1391
+ return asyncContext.run({ tx, txId, level: newLevel, afterCommitCallbacks }, callback);
1392
+ }
1393
+ function onAfterCommit(callback) {
1394
+ const context = getTransactionContext();
1395
+ if (!context) {
1396
+ Promise.resolve().then(callback).catch((err) => {
1397
+ txLogger.error("afterCommit callback failed (no transaction)", {
1398
+ error: err instanceof Error ? err.message : String(err)
1399
+ });
1400
+ });
1401
+ return;
1402
+ }
1403
+ context.afterCommitCallbacks.push(callback);
1138
1404
  }
1139
1405
  var MAX_TIMEOUT_MS = 2147483647;
1140
1406
  var txLogger2 = logger.child("@spfn/core:transaction");
@@ -1217,13 +1483,21 @@ async function runInTransaction(callback, options = {}) {
1217
1483
  txLogger2.debug("Transaction started", { txId, context });
1218
1484
  }
1219
1485
  const startTime = Date.now();
1486
+ let afterCommitCallbacks = [];
1220
1487
  try {
1221
1488
  const result = await writeDb.transaction(async (tx) => {
1222
1489
  if (timeout > 0 && !isNested) {
1223
1490
  await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
1224
1491
  }
1225
1492
  return await runWithTransaction(tx, txId, async () => {
1226
- return await callback(tx);
1493
+ const innerResult = await callback(tx);
1494
+ if (!isNested) {
1495
+ const ctx = getTransactionContext();
1496
+ if (ctx) {
1497
+ afterCommitCallbacks = [...ctx.afterCommitCallbacks];
1498
+ }
1499
+ }
1500
+ return innerResult;
1227
1501
  });
1228
1502
  });
1229
1503
  const duration = Date.now() - startTime;
@@ -1243,6 +1517,24 @@ async function runInTransaction(callback, options = {}) {
1243
1517
  });
1244
1518
  }
1245
1519
  }
1520
+ if (!isNested && afterCommitCallbacks.length > 0) {
1521
+ if (enableLogging) {
1522
+ txLogger2.debug("Executing afterCommit callbacks", {
1523
+ txId,
1524
+ context,
1525
+ count: afterCommitCallbacks.length
1526
+ });
1527
+ }
1528
+ for (const cb of afterCommitCallbacks) {
1529
+ Promise.resolve().then(cb).catch((err) => {
1530
+ txLogger2.error("afterCommit callback failed", {
1531
+ txId,
1532
+ context,
1533
+ error: err instanceof Error ? err.message : String(err)
1534
+ });
1535
+ });
1536
+ }
1537
+ }
1246
1538
  return result;
1247
1539
  } catch (error) {
1248
1540
  const duration = Date.now() - startTime;
@@ -1289,6 +1581,7 @@ function Transactional(options = {}) {
1289
1581
  }
1290
1582
  );
1291
1583
  } catch (error) {
1584
+ reportDatabaseError(error);
1292
1585
  if (error instanceof DatabaseError) {
1293
1586
  throw error;
1294
1587
  }
@@ -1536,6 +1829,7 @@ var BaseRepository = class {
1536
1829
  try {
1537
1830
  return await queryFn();
1538
1831
  } catch (error) {
1832
+ reportDatabaseError(error);
1539
1833
  const err = error instanceof Error ? error : new Error(String(error));
1540
1834
  const repositoryName = this.constructor.name;
1541
1835
  throw new RepositoryError(
@@ -1788,6 +2082,6 @@ var BaseRepository = class {
1788
2082
  }
1789
2083
  };
1790
2084
 
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 };
2085
+ export { BaseRepository, RepositoryError, Transactional, 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 };
1792
2086
  //# sourceMappingURL=index.js.map
1793
2087
  //# sourceMappingURL=index.js.map