@spfn/core 0.1.0-alpha.21 → 0.1.0-alpha.23

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
@@ -1,9 +1,9 @@
1
- import { config } from 'dotenv';
2
1
  import { drizzle } from 'drizzle-orm/postgres-js';
3
2
  import postgres from 'postgres';
4
3
  import pino from 'pino';
5
4
  import { existsSync, mkdirSync, createWriteStream } from 'fs';
6
5
  import { join } from 'path';
6
+ import { config } from 'dotenv';
7
7
  import { AsyncLocalStorage } from 'async_hooks';
8
8
  import { createMiddleware } from 'hono/factory';
9
9
  import { and, desc, asc, sql, isNull, isNotNull, notInArray, inArray, like, lte, lt, gte, gt, ne, eq } from 'drizzle-orm';
@@ -12,7 +12,7 @@ import { bigserial, timestamp } from 'drizzle-orm/pg-core';
12
12
  // src/db/manager/factory.ts
13
13
  var PinoAdapter = class _PinoAdapter {
14
14
  logger;
15
- constructor(config2) {
15
+ constructor(config) {
16
16
  const isProduction = process.env.NODE_ENV === "production";
17
17
  const isDevelopment = process.env.NODE_ENV === "development";
18
18
  const fileLoggingEnabled = process.env.LOGGER_FILE_ENABLED === "true";
@@ -45,11 +45,11 @@ var PinoAdapter = class _PinoAdapter {
45
45
  });
46
46
  }
47
47
  this.logger = pino({
48
- level: config2.level,
48
+ level: config.level,
49
49
  // Transport 설정 (targets가 있으면 사용, 없으면 기본 stdout)
50
50
  transport: targets.length > 0 ? { targets } : void 0,
51
51
  // 기본 필드
52
- base: config2.module ? { module: config2.module } : void 0
52
+ base: config.module ? { module: config.module } : void 0
53
53
  });
54
54
  }
55
55
  child(module) {
@@ -92,9 +92,9 @@ var PinoAdapter = class _PinoAdapter {
92
92
  var Logger = class _Logger {
93
93
  config;
94
94
  module;
95
- constructor(config2) {
96
- this.config = config2;
97
- this.module = config2.module;
95
+ constructor(config) {
96
+ this.config = config;
97
+ this.module = config.module;
98
98
  }
99
99
  /**
100
100
  * Get current log level
@@ -310,10 +310,10 @@ var ConsoleTransport = class {
310
310
  level;
311
311
  enabled;
312
312
  colorize;
313
- constructor(config2) {
314
- this.level = config2.level;
315
- this.enabled = config2.enabled;
316
- this.colorize = config2.colorize ?? true;
313
+ constructor(config) {
314
+ this.level = config.level;
315
+ this.enabled = config.enabled;
316
+ this.colorize = config.colorize ?? true;
317
317
  }
318
318
  async log(metadata) {
319
319
  if (!this.enabled) {
@@ -337,10 +337,10 @@ var FileTransport = class {
337
337
  logDir;
338
338
  currentStream = null;
339
339
  currentFilename = null;
340
- constructor(config2) {
341
- this.level = config2.level;
342
- this.enabled = config2.enabled;
343
- this.logDir = config2.logDir;
340
+ constructor(config) {
341
+ this.level = config.level;
342
+ this.enabled = config.enabled;
343
+ this.logDir = config.logDir;
344
344
  if (!existsSync(this.logDir)) {
345
345
  mkdirSync(this.logDir, { recursive: true });
346
346
  }
@@ -472,10 +472,10 @@ function initializeTransports() {
472
472
  }
473
473
  var CustomAdapter = class _CustomAdapter {
474
474
  logger;
475
- constructor(config2) {
475
+ constructor(config) {
476
476
  this.logger = new Logger({
477
- level: config2.level,
478
- module: config2.module,
477
+ level: config.level,
478
+ module: config.module,
479
479
  transports: initializeTransports()
480
480
  });
481
481
  }
@@ -790,6 +790,178 @@ function getRetryConfig() {
790
790
  };
791
791
  }
792
792
 
793
+ // src/env/config.ts
794
+ var ENV_FILE_PRIORITY = [
795
+ ".env",
796
+ // Base configuration (lowest priority)
797
+ ".env.{NODE_ENV}",
798
+ // Environment-specific
799
+ ".env.local",
800
+ // Local overrides
801
+ ".env.{NODE_ENV}.local"
802
+ // Local environment-specific (highest priority)
803
+ ];
804
+ var TEST_ONLY_FILES = [
805
+ ".env.test",
806
+ ".env.test.local"
807
+ ];
808
+
809
+ // src/env/loader.ts
810
+ var envLogger = logger.child("environment");
811
+ var environmentLoaded = false;
812
+ var cachedLoadResult;
813
+ function buildFileList(basePath, nodeEnv) {
814
+ const files = [];
815
+ for (const pattern of ENV_FILE_PRIORITY) {
816
+ const fileName = pattern.replace("{NODE_ENV}", nodeEnv);
817
+ if (nodeEnv !== "test" && TEST_ONLY_FILES.includes(fileName)) {
818
+ continue;
819
+ }
820
+ files.push(join(basePath, fileName));
821
+ }
822
+ return files;
823
+ }
824
+ function loadSingleFile(filePath, debug) {
825
+ if (!existsSync(filePath)) {
826
+ if (debug) {
827
+ envLogger.debug("Environment file not found (optional)", {
828
+ path: filePath
829
+ });
830
+ }
831
+ return { success: false, parsed: {}, error: "File not found" };
832
+ }
833
+ try {
834
+ const result = config({ path: filePath });
835
+ if (result.error) {
836
+ envLogger.warn("Failed to parse environment file", {
837
+ path: filePath,
838
+ error: result.error.message
839
+ });
840
+ return {
841
+ success: false,
842
+ parsed: {},
843
+ error: result.error.message
844
+ };
845
+ }
846
+ const parsed = result.parsed || {};
847
+ if (debug) {
848
+ envLogger.debug("Environment file loaded successfully", {
849
+ path: filePath,
850
+ variables: Object.keys(parsed),
851
+ count: Object.keys(parsed).length
852
+ });
853
+ }
854
+ return { success: true, parsed };
855
+ } catch (error) {
856
+ const message = error instanceof Error ? error.message : "Unknown error";
857
+ envLogger.error("Error loading environment file", {
858
+ path: filePath,
859
+ error: message
860
+ });
861
+ return { success: false, parsed: {}, error: message };
862
+ }
863
+ }
864
+ function validateRequiredVars(required, debug) {
865
+ const missing = [];
866
+ for (const varName of required) {
867
+ if (!process.env[varName]) {
868
+ missing.push(varName);
869
+ }
870
+ }
871
+ if (missing.length > 0) {
872
+ const error = `Required environment variables missing: ${missing.join(", ")}`;
873
+ envLogger.error("Environment validation failed", {
874
+ missing,
875
+ required
876
+ });
877
+ throw new Error(error);
878
+ }
879
+ if (debug) {
880
+ envLogger.debug("Required environment variables validated", {
881
+ required,
882
+ allPresent: true
883
+ });
884
+ }
885
+ }
886
+ function loadEnvironment(options = {}) {
887
+ const {
888
+ basePath = process.cwd(),
889
+ customPaths = [],
890
+ debug = false,
891
+ nodeEnv = process.env.NODE_ENV || "development",
892
+ required = [],
893
+ useCache = true
894
+ } = options;
895
+ if (useCache && environmentLoaded && cachedLoadResult) {
896
+ if (debug) {
897
+ envLogger.debug("Returning cached environment", {
898
+ loaded: cachedLoadResult.loaded.length,
899
+ variables: Object.keys(cachedLoadResult.parsed).length
900
+ });
901
+ }
902
+ return cachedLoadResult;
903
+ }
904
+ if (debug) {
905
+ envLogger.debug("Loading environment variables", {
906
+ basePath,
907
+ nodeEnv,
908
+ customPaths,
909
+ required
910
+ });
911
+ }
912
+ const result = {
913
+ success: true,
914
+ loaded: [],
915
+ failed: [],
916
+ parsed: {},
917
+ warnings: []
918
+ };
919
+ const standardFiles = buildFileList(basePath, nodeEnv);
920
+ const allFiles = [...standardFiles, ...customPaths];
921
+ if (debug) {
922
+ envLogger.debug("Environment files to load", {
923
+ standardFiles,
924
+ customPaths,
925
+ total: allFiles.length
926
+ });
927
+ }
928
+ const reversedFiles = [...allFiles].reverse();
929
+ for (const filePath of reversedFiles) {
930
+ const fileResult = loadSingleFile(filePath, debug);
931
+ if (fileResult.success) {
932
+ result.loaded.push(filePath);
933
+ Object.assign(result.parsed, fileResult.parsed);
934
+ } else if (fileResult.error) {
935
+ result.failed.push({
936
+ path: filePath,
937
+ reason: fileResult.error
938
+ });
939
+ }
940
+ }
941
+ if (debug || result.loaded.length > 0) {
942
+ envLogger.info("Environment loading complete", {
943
+ loaded: result.loaded.length,
944
+ failed: result.failed.length,
945
+ variables: Object.keys(result.parsed).length,
946
+ files: result.loaded
947
+ });
948
+ }
949
+ if (required.length > 0) {
950
+ try {
951
+ validateRequiredVars(required, debug);
952
+ } catch (error) {
953
+ result.success = false;
954
+ result.errors = [
955
+ error instanceof Error ? error.message : "Validation failed"
956
+ ];
957
+ throw error;
958
+ }
959
+ }
960
+ environmentLoaded = true;
961
+ cachedLoadResult = result;
962
+ return result;
963
+ }
964
+
793
965
  // src/db/manager/factory.ts
794
966
  var dbLogger2 = logger.child("database");
795
967
  function hasDatabaseConfig() {
@@ -797,9 +969,24 @@ function hasDatabaseConfig() {
797
969
  }
798
970
  async function createDatabaseFromEnv(options) {
799
971
  if (!hasDatabaseConfig()) {
800
- config({ path: ".env.local" });
972
+ dbLogger2.debug("No DATABASE_URL found, loading environment variables");
973
+ const result = loadEnvironment({
974
+ debug: true
975
+ });
976
+ dbLogger2.debug("Environment variables loaded", {
977
+ success: result.success,
978
+ loaded: result.loaded.length,
979
+ hasDatabaseUrl: !!process.env.DATABASE_URL,
980
+ hasWriteUrl: !!process.env.DATABASE_WRITE_URL,
981
+ hasReadUrl: !!process.env.DATABASE_READ_URL
982
+ });
801
983
  }
802
984
  if (!hasDatabaseConfig()) {
985
+ dbLogger2.warn("No database configuration found", {
986
+ cwd: process.cwd(),
987
+ nodeEnv: process.env.NODE_ENV,
988
+ checkedVars: ["DATABASE_URL", "DATABASE_WRITE_URL", "DATABASE_READ_URL"]
989
+ });
803
990
  return { write: void 0, read: void 0 };
804
991
  }
805
992
  try {
@@ -880,7 +1067,7 @@ async function createDatabaseFromEnv(options) {
880
1067
  hasUrl: !!process.env.DATABASE_URL,
881
1068
  hasReplicaUrl: !!process.env.DATABASE_REPLICA_URL
882
1069
  });
883
- return { write: void 0, read: void 0 };
1070
+ throw new Error(`Database connection failed: ${message}`, { cause: error });
884
1071
  }
885
1072
  }
886
1073
 
@@ -903,28 +1090,28 @@ function setDatabase(write, read) {
903
1090
  readInstance = read ?? write;
904
1091
  }
905
1092
  function getHealthCheckConfig(options) {
906
- const parseBoolean = (value, defaultValue) => {
1093
+ const parseBoolean2 = (value, defaultValue) => {
907
1094
  if (value === void 0) return defaultValue;
908
1095
  return value.toLowerCase() === "true";
909
1096
  };
910
1097
  return {
911
- enabled: options?.enabled ?? parseBoolean(process.env.DB_HEALTH_CHECK_ENABLED, true),
1098
+ enabled: options?.enabled ?? parseBoolean2(process.env.DB_HEALTH_CHECK_ENABLED, true),
912
1099
  interval: options?.interval ?? (parseInt(process.env.DB_HEALTH_CHECK_INTERVAL || "", 10) || 6e4),
913
- reconnect: options?.reconnect ?? parseBoolean(process.env.DB_HEALTH_CHECK_RECONNECT, true),
1100
+ reconnect: options?.reconnect ?? parseBoolean2(process.env.DB_HEALTH_CHECK_RECONNECT, true),
914
1101
  maxRetries: options?.maxRetries ?? (parseInt(process.env.DB_HEALTH_CHECK_MAX_RETRIES || "", 10) || 3),
915
1102
  retryInterval: options?.retryInterval ?? (parseInt(process.env.DB_HEALTH_CHECK_RETRY_INTERVAL || "", 10) || 5e3)
916
1103
  };
917
1104
  }
918
1105
  function getMonitoringConfig(options) {
919
1106
  const isDevelopment = process.env.NODE_ENV !== "production";
920
- const parseBoolean = (value, defaultValue) => {
1107
+ const parseBoolean2 = (value, defaultValue) => {
921
1108
  if (value === void 0) return defaultValue;
922
1109
  return value.toLowerCase() === "true";
923
1110
  };
924
1111
  return {
925
- enabled: options?.enabled ?? parseBoolean(process.env.DB_MONITORING_ENABLED, isDevelopment),
1112
+ enabled: options?.enabled ?? parseBoolean2(process.env.DB_MONITORING_ENABLED, isDevelopment),
926
1113
  slowThreshold: options?.slowThreshold ?? (parseInt(process.env.DB_MONITORING_SLOW_THRESHOLD || "", 10) || 1e3),
927
- logQueries: options?.logQueries ?? parseBoolean(process.env.DB_MONITORING_LOG_QUERIES, false)
1114
+ logQueries: options?.logQueries ?? parseBoolean2(process.env.DB_MONITORING_LOG_QUERIES, false)
928
1115
  };
929
1116
  }
930
1117
  async function initDatabase(options) {
@@ -962,7 +1149,7 @@ async function initDatabase(options) {
962
1149
  const message = error instanceof Error ? error.message : "Unknown error";
963
1150
  dbLogger3.error("Database connection failed", { error: message });
964
1151
  await closeDatabase();
965
- return { write: void 0, read: void 0 };
1152
+ throw new Error(`Database connection test failed: ${message}`, { cause: error });
966
1153
  }
967
1154
  } else {
968
1155
  dbLogger3.warn("No database configuration found");
@@ -1010,14 +1197,14 @@ function getDatabaseInfo() {
1010
1197
  isReplica: !!(readInstance && readInstance !== writeInstance)
1011
1198
  };
1012
1199
  }
1013
- function startHealthCheck(config2) {
1200
+ function startHealthCheck(config) {
1014
1201
  if (healthCheckInterval) {
1015
1202
  dbLogger3.debug("Health check already running");
1016
1203
  return;
1017
1204
  }
1018
1205
  dbLogger3.info("Starting database health check", {
1019
- interval: `${config2.interval}ms`,
1020
- reconnect: config2.reconnect
1206
+ interval: `${config.interval}ms`,
1207
+ reconnect: config.reconnect
1021
1208
  });
1022
1209
  healthCheckInterval = setInterval(async () => {
1023
1210
  try {
@@ -1033,22 +1220,22 @@ function startHealthCheck(config2) {
1033
1220
  } catch (error) {
1034
1221
  const message = error instanceof Error ? error.message : "Unknown error";
1035
1222
  dbLogger3.error("Database health check failed", { error: message });
1036
- if (config2.reconnect) {
1037
- await attemptReconnection(config2);
1223
+ if (config.reconnect) {
1224
+ await attemptReconnection(config);
1038
1225
  }
1039
1226
  }
1040
- }, config2.interval);
1227
+ }, config.interval);
1041
1228
  }
1042
- async function attemptReconnection(config2) {
1229
+ async function attemptReconnection(config) {
1043
1230
  dbLogger3.warn("Attempting database reconnection", {
1044
- maxRetries: config2.maxRetries,
1045
- retryInterval: `${config2.retryInterval}ms`
1231
+ maxRetries: config.maxRetries,
1232
+ retryInterval: `${config.retryInterval}ms`
1046
1233
  });
1047
- for (let attempt = 1; attempt <= config2.maxRetries; attempt++) {
1234
+ for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
1048
1235
  try {
1049
- dbLogger3.debug(`Reconnection attempt ${attempt}/${config2.maxRetries}`);
1236
+ dbLogger3.debug(`Reconnection attempt ${attempt}/${config.maxRetries}`);
1050
1237
  await closeDatabase();
1051
- await new Promise((resolve) => setTimeout(resolve, config2.retryInterval));
1238
+ await new Promise((resolve) => setTimeout(resolve, config.retryInterval));
1052
1239
  const result = await createDatabaseFromEnv();
1053
1240
  if (result.write) {
1054
1241
  await result.write.execute("SELECT 1");
@@ -1064,9 +1251,9 @@ async function attemptReconnection(config2) {
1064
1251
  dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
1065
1252
  error: message,
1066
1253
  attempt,
1067
- maxRetries: config2.maxRetries
1254
+ maxRetries: config.maxRetries
1068
1255
  });
1069
- if (attempt === config2.maxRetries) {
1256
+ if (attempt === config.maxRetries) {
1070
1257
  dbLogger3.error("Max reconnection attempts reached, giving up");
1071
1258
  }
1072
1259
  }
@@ -1569,21 +1756,21 @@ var Repository = class {
1569
1756
  * @returns Result of the operation
1570
1757
  */
1571
1758
  async executeWithMonitoring(operation, fn) {
1572
- const config2 = getDatabaseMonitoringConfig();
1573
- if (!config2?.enabled) {
1759
+ const config = getDatabaseMonitoringConfig();
1760
+ if (!config?.enabled) {
1574
1761
  return fn();
1575
1762
  }
1576
1763
  const startTime = performance.now();
1577
1764
  try {
1578
1765
  const result = await fn();
1579
1766
  const duration = performance.now() - startTime;
1580
- if (duration >= config2.slowThreshold) {
1767
+ if (duration >= config.slowThreshold) {
1581
1768
  const dbLogger4 = logger.child("database");
1582
1769
  const logData = {
1583
1770
  operation,
1584
1771
  table: this.table._.name,
1585
1772
  duration: `${duration.toFixed(2)}ms`,
1586
- threshold: `${config2.slowThreshold}ms`
1773
+ threshold: `${config.slowThreshold}ms`
1587
1774
  };
1588
1775
  dbLogger4.warn("Slow query detected", logData);
1589
1776
  }
@@ -2063,14 +2250,14 @@ function getDbCredentials(dialect, url) {
2063
2250
  }
2064
2251
  }
2065
2252
  function generateDrizzleConfigFile(options = {}) {
2066
- const config2 = getDrizzleConfig(options);
2253
+ const config = getDrizzleConfig(options);
2067
2254
  return `import { defineConfig } from 'drizzle-kit';
2068
2255
 
2069
2256
  export default defineConfig({
2070
- schema: '${config2.schema}',
2071
- out: '${config2.out}',
2072
- dialect: '${config2.dialect}',
2073
- dbCredentials: ${JSON.stringify(config2.dbCredentials, null, 4)},
2257
+ schema: '${config.schema}',
2258
+ out: '${config.out}',
2259
+ dialect: '${config.dialect}',
2260
+ dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},
2074
2261
  });
2075
2262
  `;
2076
2263
  }