@spfn/core 0.2.0-beta.64 → 0.2.0-beta.66

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.
package/dist/db/index.js CHANGED
@@ -441,8 +441,8 @@ async function createWriteReadClients(writeUrl, readUrl, poolConfig, retryConfig
441
441
  try {
442
442
  readClient = await createDatabaseConnection(readUrl, readPoolConfig, retryConfig);
443
443
  return {
444
- write: drizzle(writeClient),
445
- read: drizzle(readClient),
444
+ write: drizzle({ client: writeClient }),
445
+ read: drizzle({ client: readClient }),
446
446
  writeClient,
447
447
  readClient
448
448
  };
@@ -458,8 +458,8 @@ async function createWriteReadClients(writeUrl, readUrl, poolConfig, retryConfig
458
458
  }
459
459
  );
460
460
  return {
461
- write: drizzle(writeClient),
462
- read: drizzle(writeClient),
461
+ write: drizzle({ client: writeClient }),
462
+ read: drizzle({ client: writeClient }),
463
463
  writeClient,
464
464
  readClient: writeClient
465
465
  };
@@ -467,7 +467,7 @@ async function createWriteReadClients(writeUrl, readUrl, poolConfig, retryConfig
467
467
  }
468
468
  async function createSingleClient(url, poolConfig, retryConfig) {
469
469
  const client = await createDatabaseConnection(url, poolConfig, retryConfig);
470
- const db = drizzle(client);
470
+ const db = drizzle({ client });
471
471
  return {
472
472
  write: db,
473
473
  read: db,
@@ -528,6 +528,10 @@ var getReadInstance = () => globalThis.__SPFN_DB_READ__;
528
528
  var setReadInstance = (instance) => {
529
529
  globalThis.__SPFN_DB_READ__ = instance;
530
530
  };
531
+ var getDatabaseProvider = () => globalThis.__SPFN_DB_PROVIDER__;
532
+ var setDatabaseProviderInstance = (provider) => {
533
+ globalThis.__SPFN_DB_PROVIDER__ = provider;
534
+ };
531
535
  var getWriteClient = () => globalThis.__SPFN_DB_WRITE_CLIENT__;
532
536
  var setWriteClient = (client) => {
533
537
  globalThis.__SPFN_DB_WRITE_CLIENT__ = client;
@@ -547,6 +551,18 @@ var getInitOptions = () => globalThis.__SPFN_DB_INIT_OPTIONS__;
547
551
  var setInitOptions = (options) => {
548
552
  globalThis.__SPFN_DB_INIT_OPTIONS__ = options;
549
553
  };
554
+ var getInitPromise = () => globalThis.__SPFN_DB_INIT_PROMISE__;
555
+ var setInitPromise = (promise) => {
556
+ globalThis.__SPFN_DB_INIT_PROMISE__ = promise;
557
+ };
558
+ var getInitProvider = () => globalThis.__SPFN_DB_INIT_PROVIDER__;
559
+ var setInitProvider = (provider) => {
560
+ globalThis.__SPFN_DB_INIT_PROVIDER__ = provider;
561
+ };
562
+ var getClosePromise = () => globalThis.__SPFN_DB_CLOSE_PROMISE__;
563
+ var setClosePromise = (promise) => {
564
+ globalThis.__SPFN_DB_CLOSE_PROMISE__ = promise;
565
+ };
550
566
  var getIsClosing = () => globalThis.__SPFN_DB_CLOSING__ === true;
551
567
  var setIsClosing = (closing) => {
552
568
  globalThis.__SPFN_DB_CLOSING__ = closing;
@@ -641,6 +657,10 @@ function startHealthCheck(config, options, getDatabase2) {
641
657
  setHealthCheckInterval(interval);
642
658
  }
643
659
  async function triggerForceReconnect(reason) {
660
+ if (getDatabaseProvider()) {
661
+ dbLogger3.debug("Force reconnect skipped: database is externally provided", { reason });
662
+ return false;
663
+ }
644
664
  if (!getWriteInstance()) {
645
665
  dbLogger3.warn("Force reconnect skipped: database not initialized", { reason });
646
666
  return false;
@@ -655,6 +675,10 @@ async function triggerForceReconnect(reason) {
655
675
  return await attemptReconnection(config, options, reason);
656
676
  }
657
677
  async function attemptReconnection(config, options, reason) {
678
+ if (getDatabaseProvider()) {
679
+ dbLogger3.debug("Reconnection skipped: database is externally provided", { reason });
680
+ return false;
681
+ }
658
682
  if (isReconnecting) {
659
683
  dbLogger3.debug("Reconnection coalesced: attempt already in progress", { reason });
660
684
  return false;
@@ -714,7 +738,6 @@ var STACK_TRACE_PATTERNS = {
714
738
  withParens: /\((.+):(\d+):(\d+)\)/,
715
739
  withoutParens: /at (.+):(\d+):(\d+)/
716
740
  };
717
- var initPromise = null;
718
741
  async function cleanupDatabaseConnections(writeClient, readClient) {
719
742
  const cleanupPromises = [];
720
743
  if (writeClient) {
@@ -752,6 +775,33 @@ async function testDatabaseConnections(write, read) {
752
775
  }
753
776
  }
754
777
  }
778
+ function validateDatabaseProvider(provider) {
779
+ if (typeof provider.kind !== "string" || !provider.kind.trim()) {
780
+ throw new Error("Database provider kind must be a non-empty string");
781
+ }
782
+ }
783
+ function registerDatabaseProvider(provider) {
784
+ setDatabaseProviderInstance(provider);
785
+ setWriteInstance(provider.write);
786
+ setReadInstance(provider.read ?? provider.write);
787
+ return {
788
+ write: provider.write,
789
+ read: provider.read ?? provider.write
790
+ };
791
+ }
792
+ async function closeDatabaseProvider(provider, context) {
793
+ if (!provider.close) {
794
+ return;
795
+ }
796
+ try {
797
+ await provider.close();
798
+ } catch (error) {
799
+ const closeError = error instanceof Error ? error : new Error(String(error));
800
+ dbLogger4.error(`Error closing ${provider.kind || "unknown"} database provider`, closeError, {
801
+ context
802
+ });
803
+ }
804
+ }
755
805
  function getCallerInfo() {
756
806
  try {
757
807
  const stack = new Error().stack;
@@ -811,24 +861,107 @@ function getDatabase(type) {
811
861
  return writeInst;
812
862
  }
813
863
  function setDatabase(write, read) {
864
+ if (getIsClosing()) {
865
+ throw new Error("Cannot set database while closing");
866
+ }
867
+ if (getInitPromise()) {
868
+ throw new Error("Cannot set database while initialization is in progress");
869
+ }
870
+ if (getDatabaseProvider()) {
871
+ throw new Error(
872
+ "An external database provider is active. Call closeDatabase() before setting a database manually."
873
+ );
874
+ }
875
+ if (getWriteClient() || getReadClient()) {
876
+ throw new Error(
877
+ "Managed database connections are active. Call closeDatabase() before setting a database manually."
878
+ );
879
+ }
880
+ setDatabaseProviderInstance(void 0);
814
881
  setWriteInstance(write);
815
882
  setReadInstance(read ?? write);
816
883
  }
884
+ function setDatabaseProvider(provider) {
885
+ validateDatabaseProvider(provider);
886
+ const activeProvider = getDatabaseProvider();
887
+ if (getIsClosing()) {
888
+ throw new Error("Cannot set database provider while closing");
889
+ }
890
+ if (getInitPromise()) {
891
+ throw new Error("Cannot set database provider while initialization is in progress");
892
+ }
893
+ if (activeProvider === provider) {
894
+ return {
895
+ write: provider.write,
896
+ read: provider.read ?? provider.write
897
+ };
898
+ }
899
+ if (getWriteInstance() || getReadInstance() || getWriteClient() || getReadClient() || activeProvider) {
900
+ throw new Error(
901
+ "Database already initialized. Call closeDatabase() before registering a different provider."
902
+ );
903
+ }
904
+ return registerDatabaseProvider(provider);
905
+ }
817
906
  async function initDatabase(options) {
818
907
  if (getIsClosing()) {
819
908
  throw new Error("Cannot initialize database while closing");
820
909
  }
821
910
  const writeInst = getWriteInstance();
822
911
  if (writeInst) {
912
+ const activeProvider = getDatabaseProvider();
913
+ if (!activeProvider && !getWriteClient()) {
914
+ throw new Error(
915
+ "Database was registered manually. Use getDatabase() directly or call closeDatabase() before initDatabase()."
916
+ );
917
+ }
918
+ if (options?.provider !== activeProvider) {
919
+ const detail = options?.provider ? "a different provider was supplied" : "the active external provider was not supplied";
920
+ throw new Error(`Database already initialized: ${detail}`);
921
+ }
823
922
  dbLogger4.debug("Database already initialized");
824
- return { write: writeInst, read: getReadInstance() };
923
+ return {
924
+ write: writeInst,
925
+ read: getReadInstance()
926
+ };
825
927
  }
826
- if (initPromise) {
928
+ const activeInitPromise = getInitPromise();
929
+ if (activeInitPromise) {
930
+ if (options?.provider !== getInitProvider()) {
931
+ throw new Error("Database initialization already in progress with a different provider");
932
+ }
827
933
  dbLogger4.debug("Database initialization in progress, waiting...");
828
- return await initPromise;
934
+ return await activeInitPromise;
829
935
  }
830
- initPromise = (async () => {
936
+ setInitProvider(options?.provider);
937
+ const currentInit = (async () => {
831
938
  try {
939
+ if (options?.provider) {
940
+ const provider = options.provider;
941
+ try {
942
+ validateDatabaseProvider(provider);
943
+ await testDatabaseConnections(provider.write, provider.read);
944
+ if (getIsClosing()) {
945
+ throw new Error("Database closed during initialization");
946
+ }
947
+ const monConfig2 = buildMonitoringConfig(options.monitoring);
948
+ const result2 = registerDatabaseProvider(provider);
949
+ setInitOptions(options);
950
+ setMonitoringConfig(monConfig2);
951
+ dbLogger4.info("Database provider connected", {
952
+ kind: provider.kind,
953
+ hasReplica: !!(provider.read && provider.read !== provider.write)
954
+ });
955
+ return result2;
956
+ } catch (error) {
957
+ await closeDatabaseProvider(provider, "initialization_failed");
958
+ if (error instanceof Error && (error.message === "Database provider kind must be a non-empty string" || error.message === "Database closed during initialization")) {
959
+ throw error;
960
+ }
961
+ const message = error instanceof Error ? error.message : "Unknown error";
962
+ throw new Error(`Database connection test failed: ${message}`);
963
+ }
964
+ }
832
965
  const result = await createDatabaseFromEnv(options);
833
966
  try {
834
967
  await testDatabaseConnections(result.write, result.read);
@@ -865,56 +998,78 @@ async function initDatabase(options) {
865
998
  }
866
999
  return { write: getWriteInstance(), read: getReadInstance() };
867
1000
  } finally {
868
- initPromise = null;
1001
+ setInitPromise(void 0);
1002
+ setInitProvider(void 0);
869
1003
  }
870
1004
  })();
871
- return await initPromise;
1005
+ setInitPromise(currentInit);
1006
+ return await currentInit;
872
1007
  }
873
1008
  async function closeDatabase() {
874
- if (getIsClosing()) {
1009
+ const activeClosePromise = getClosePromise();
1010
+ if (activeClosePromise) {
875
1011
  dbLogger4.debug("Database close already in progress");
876
- return;
1012
+ return await activeClosePromise;
877
1013
  }
878
- setIsClosing(true);
879
- if (initPromise) {
880
- dbLogger4.debug("Waiting for database initialization to complete before closing...");
881
- try {
882
- await initPromise;
883
- } catch (_error) {
884
- dbLogger4.debug("Initialization failed during close, proceeding with cleanup");
1014
+ const currentClose = (async () => {
1015
+ setIsClosing(true);
1016
+ const activeInitPromise = getInitPromise();
1017
+ if (activeInitPromise) {
1018
+ dbLogger4.debug("Waiting for database initialization to complete before closing...");
1019
+ try {
1020
+ await activeInitPromise;
1021
+ } catch (_error) {
1022
+ dbLogger4.debug("Initialization failed during close, proceeding with cleanup");
1023
+ }
885
1024
  }
886
- }
887
- const writeInst = getWriteInstance();
888
- const readInst = getReadInstance();
889
- if (!writeInst && !readInst) {
890
- dbLogger4.debug("No database connections to close");
891
- setIsClosing(false);
892
- return;
893
- }
894
- try {
895
- stopHealthCheck();
896
- const closePromises = [];
897
- const writeC = getWriteClient();
898
- if (writeC) {
899
- closePromises.push(closeDatabaseClient(writeC, "write"));
1025
+ const writeInst = getWriteInstance();
1026
+ const readInst = getReadInstance();
1027
+ const provider = getDatabaseProvider();
1028
+ if (!writeInst && !readInst && !provider) {
1029
+ dbLogger4.debug("No database connections to close");
1030
+ return;
900
1031
  }
901
- const readC = getReadClient();
902
- if (readC && readC !== writeC) {
903
- closePromises.push(closeDatabaseClient(readC, "read"));
1032
+ try {
1033
+ stopHealthCheck();
1034
+ const closePromises = [];
1035
+ if (provider) {
1036
+ closePromises.push(closeDatabaseProvider(provider, "shutdown"));
1037
+ }
1038
+ const writeC = getWriteClient();
1039
+ if (writeC) {
1040
+ closePromises.push(closeDatabaseClient(writeC, "write"));
1041
+ }
1042
+ const readC = getReadClient();
1043
+ if (readC && readC !== writeC) {
1044
+ closePromises.push(closeDatabaseClient(readC, "read"));
1045
+ }
1046
+ await Promise.allSettled(closePromises);
1047
+ dbLogger4.info("All database connections closed");
1048
+ } finally {
1049
+ setWriteInstance(void 0);
1050
+ setReadInstance(void 0);
1051
+ setWriteClient(void 0);
1052
+ setReadClient(void 0);
1053
+ setDatabaseProviderInstance(void 0);
1054
+ setMonitoringConfig(void 0);
1055
+ setInitOptions(void 0);
904
1056
  }
905
- await Promise.allSettled(closePromises);
906
- dbLogger4.info("All database connections closed");
1057
+ })();
1058
+ setClosePromise(currentClose);
1059
+ try {
1060
+ await currentClose;
907
1061
  } finally {
908
- setWriteInstance(void 0);
909
- setReadInstance(void 0);
910
- setWriteClient(void 0);
911
- setReadClient(void 0);
912
- setMonitoringConfig(void 0);
913
- setInitOptions(void 0);
914
- setIsClosing(false);
1062
+ if (getClosePromise() === currentClose) {
1063
+ setClosePromise(void 0);
1064
+ setIsClosing(false);
1065
+ }
915
1066
  }
916
1067
  }
917
1068
  async function forceReconnectDatabase(reason = "manual") {
1069
+ if (getDatabaseProvider()) {
1070
+ dbLogger4.debug("Force reconnect skipped: database is externally provided", { reason });
1071
+ return false;
1072
+ }
918
1073
  return await triggerForceReconnect(reason);
919
1074
  }
920
1075
  function getDatabaseInfo() {
@@ -923,7 +1078,8 @@ function getDatabaseInfo() {
923
1078
  return {
924
1079
  hasWrite: !!writeInst,
925
1080
  hasRead: !!readInst,
926
- isReplica: !!(readInst && readInst !== writeInst)
1081
+ isReplica: !!(readInst && readInst !== writeInst),
1082
+ providerKind: getDatabaseProvider()?.kind
927
1083
  };
928
1084
  }
929
1085
  var dbLogger5 = logger.child("@spfn/core:database");
@@ -1411,7 +1567,10 @@ function runWithTransaction(tx, txId, callback) {
1411
1567
  txLogger.debug("Root transaction context set", { txId, level: newLevel });
1412
1568
  }
1413
1569
  const afterCommitCallbacks = existingContext ? existingContext.afterCommitCallbacks : [];
1414
- return asyncContext.run({ tx, txId, level: newLevel, afterCommitCallbacks }, callback);
1570
+ return asyncContext.run(
1571
+ { tx, txId, level: newLevel, afterCommitCallbacks },
1572
+ callback
1573
+ );
1415
1574
  }
1416
1575
  function onAfterCommit(callback) {
1417
1576
  const context = getTransactionContext();
@@ -1528,14 +1687,15 @@ async function runInTransaction(callback, options = {}) {
1528
1687
  let afterCommitCallbacks = [];
1529
1688
  try {
1530
1689
  const result = await writeDb.transaction(async (tx) => {
1690
+ const transaction = tx;
1531
1691
  if (timeout > 0 && !isNested) {
1532
- await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
1692
+ await transaction.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
1533
1693
  }
1534
1694
  if (idleTimeout > 0 && !isNested) {
1535
- await tx.execute(sql.raw(`SET LOCAL idle_in_transaction_session_timeout = ${idleTimeout}`));
1695
+ await transaction.execute(sql.raw(`SET LOCAL idle_in_transaction_session_timeout = ${idleTimeout}`));
1536
1696
  }
1537
- return await runWithTransaction(tx, txId, async () => {
1538
- const innerResult = await callback(tx);
1697
+ return await runWithTransaction(transaction, txId, async () => {
1698
+ const innerResult = await callback(transaction);
1539
1699
  if (!isNested) {
1540
1700
  const ctx = getTransactionContext();
1541
1701
  if (ctx) {
@@ -1698,7 +1858,8 @@ async function create(table, data) {
1698
1858
  if (!db) {
1699
1859
  throw new Error("Database not initialized. Call initDatabase() first.");
1700
1860
  }
1701
- const [result] = await db.insert(table).values(data).returning();
1861
+ const results = await db.insert(table).values(data).returning();
1862
+ const result = results[0];
1702
1863
  return result;
1703
1864
  }
1704
1865
  async function createMany(table, data) {
@@ -1714,10 +1875,11 @@ async function upsert(table, data, options) {
1714
1875
  if (!db) {
1715
1876
  throw new Error("Database not initialized. Call initDatabase() first.");
1716
1877
  }
1717
- const [result] = await db.insert(table).values(data).onConflictDoUpdate({
1878
+ const results = await db.insert(table).values(data).onConflictDoUpdate({
1718
1879
  target: options.target,
1719
1880
  set: options.set || data
1720
1881
  }).returning();
1882
+ const result = results[0];
1721
1883
  return result;
1722
1884
  }
1723
1885
  async function updateOne(table, where, data) {
@@ -1729,7 +1891,8 @@ async function updateOne(table, where, data) {
1729
1891
  if (!whereClause) {
1730
1892
  throw new Error("updateOne requires at least one where condition");
1731
1893
  }
1732
- const [result] = await db.update(table).set(data).where(whereClause).returning();
1894
+ const results = await db.update(table).set(data).where(whereClause).returning();
1895
+ const result = results[0];
1733
1896
  return result ?? null;
1734
1897
  }
1735
1898
  async function updateMany(table, where, data) {
@@ -1753,7 +1916,8 @@ async function deleteOne(table, where) {
1753
1916
  if (!whereClause) {
1754
1917
  throw new Error("deleteOne requires at least one where condition");
1755
1918
  }
1756
- const [result] = await db.delete(table).where(whereClause).returning();
1919
+ const results = await db.delete(table).where(whereClause).returning();
1920
+ const result = results[0];
1757
1921
  return result ?? null;
1758
1922
  }
1759
1923
  async function deleteMany(table, where) {
@@ -2004,7 +2168,8 @@ var BaseRepository = class {
2004
2168
  * ```
2005
2169
  */
2006
2170
  async _create(table, data) {
2007
- const [result] = await this.db.insert(table).values(data).returning();
2171
+ const results = await this.db.insert(table).values(data).returning();
2172
+ const result = results[0];
2008
2173
  return result;
2009
2174
  }
2010
2175
  /**
@@ -2046,10 +2211,11 @@ var BaseRepository = class {
2046
2211
  * ```
2047
2212
  */
2048
2213
  async _upsert(table, data, options) {
2049
- const [result] = await this.db.insert(table).values(data).onConflictDoUpdate({
2214
+ const results = await this.db.insert(table).values(data).onConflictDoUpdate({
2050
2215
  target: options.target,
2051
2216
  set: options.set || data
2052
2217
  }).returning();
2218
+ const result = results[0];
2053
2219
  return result;
2054
2220
  }
2055
2221
  /**
@@ -2073,7 +2239,8 @@ var BaseRepository = class {
2073
2239
  if (!whereClause) {
2074
2240
  throw new Error("updateOne requires at least one where condition");
2075
2241
  }
2076
- const [result] = await this.db.update(table).set(data).where(whereClause).returning();
2242
+ const results = await this.db.update(table).set(data).where(whereClause).returning();
2243
+ const result = results[0];
2077
2244
  return result ?? null;
2078
2245
  }
2079
2246
  /**
@@ -2117,7 +2284,8 @@ var BaseRepository = class {
2117
2284
  if (!whereClause) {
2118
2285
  throw new Error("deleteOne requires at least one where condition");
2119
2286
  }
2120
- const [result] = await this.db.delete(table).where(whereClause).returning();
2287
+ const results = await this.db.delete(table).where(whereClause).returning();
2288
+ const result = results[0];
2121
2289
  return result ?? null;
2122
2290
  }
2123
2291
  /**
@@ -2166,6 +2334,6 @@ var BaseRepository = class {
2166
2334
  }
2167
2335
  };
2168
2336
 
2169
- 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 };
2337
+ 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, setDatabaseProvider, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
2170
2338
  //# sourceMappingURL=index.js.map
2171
2339
  //# sourceMappingURL=index.js.map