@spfn/core 0.2.0-beta.13 → 0.2.0-beta.15

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.
@@ -20,9 +20,9 @@ import { parsePostgresUrl, parseRedisUrl } from '@spfn/core/env';
20
20
  * ```
21
21
  */
22
22
  declare const coreEnvSchema: {
23
- NODE_ENV: Omit<_spfn_core_env.EnvVarSchema<"production" | "local" | "development" | "test">, "key" | "type" | "validator"> & {
23
+ NODE_ENV: Omit<_spfn_core_env.EnvVarSchema<"production" | "local" | "development" | "staging" | "test">, "key" | "type" | "validator"> & {
24
24
  type: "enum";
25
- validator: (val: string) => "production" | "local" | "development" | "test";
25
+ validator: (val: string) => "production" | "local" | "development" | "staging" | "test";
26
26
  } & {
27
27
  key: "NODE_ENV";
28
28
  };
@@ -427,9 +427,9 @@ declare const coreEnvSchema: {
427
427
  * ```
428
428
  */
429
429
  declare const registry: _spfn_core_env.EnvRegistry<{
430
- NODE_ENV: Omit<_spfn_core_env.EnvVarSchema<"production" | "local" | "development" | "test">, "key" | "type" | "validator"> & {
430
+ NODE_ENV: Omit<_spfn_core_env.EnvVarSchema<"production" | "local" | "development" | "staging" | "test">, "key" | "type" | "validator"> & {
431
431
  type: "enum";
432
- validator: (val: string) => "production" | "local" | "development" | "test";
432
+ validator: (val: string) => "production" | "local" | "development" | "staging" | "test";
433
433
  } & {
434
434
  key: "NODE_ENV";
435
435
  };
@@ -827,9 +827,9 @@ declare const registry: _spfn_core_env.EnvRegistry<{
827
827
  * Validated environment configuration
828
828
  */
829
829
  declare const env: _spfn_core_env.InferEnvType<{
830
- NODE_ENV: Omit<_spfn_core_env.EnvVarSchema<"production" | "local" | "development" | "test">, "key" | "type" | "validator"> & {
830
+ NODE_ENV: Omit<_spfn_core_env.EnvVarSchema<"production" | "local" | "development" | "staging" | "test">, "key" | "type" | "validator"> & {
831
831
  type: "enum";
832
- validator: (val: string) => "production" | "local" | "development" | "test";
832
+ validator: (val: string) => "production" | "local" | "development" | "staging" | "test";
833
833
  } & {
834
834
  key: "NODE_ENV";
835
835
  };
@@ -5,7 +5,7 @@ var coreEnvSchema = defineEnvSchema({
5
5
  // ========================================================================
6
6
  // Core Environment
7
7
  // ========================================================================
8
- NODE_ENV: envEnum(["local", "development", "production", "test"], {
8
+ NODE_ENV: envEnum(["local", "development", "staging", "production", "test"], {
9
9
  description: "Node.js runtime environment",
10
10
  default: "local",
11
11
  nextjs: true
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config/schema.ts","../../src/config/index.ts"],"names":[],"mappings":";;;AAsCO,IAAM,gBAAgB,eAAA,CAAgB;AAAA;AAAA;AAAA;AAAA,EAKzC,UAAU,OAAA,CAAQ,CAAC,SAAS,aAAA,EAAe,YAAA,EAAc,MAAM,CAAA,EAAY;AAAA,IACvE,WAAA,EAAa,6BAAA;AAAA,IACb,OAAA,EAAS,OAAA;AAAA,IACT,MAAA,EAAQ;AAAA,GACX,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,cAAc,SAAA,CAAU;AAAA,IACpB,WAAA,EAAa,iCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,SAAA,EAAW,gBAAA;AAAA,IACX,QAAA,EAAU,CAAC,kDAAkD;AAAA,GAChE,CAAA;AAAA,EAED,oBAAoB,SAAA,CAAU;AAAA,IAC1B,WAAA,EAAa,6CAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,SAAA,EAAW,gBAAA;AAAA,IACX,QAAA,EAAU,CAAC,+CAA+C;AAAA,GAC7D,CAAA;AAAA,EAED,mBAAmB,SAAA,CAAU;AAAA,IACzB,WAAA,EAAa,4CAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,SAAA,EAAW,gBAAA;AAAA,IACX,QAAA,EAAU,CAAC,gDAAgD;AAAA,GAC9D,CAAA;AAAA;AAAA;AAAA;AAAA,EAOD,aAAa,SAAA,CAAU;AAAA,IACnB,WAAA,EAAa,gDAAA;AAAA,IACb,OAAA,EAAS,EAAA;AAAA,IACT,QAAA,EAAU,CAAC,EAAA,EAAI,EAAA,EAAI,EAAE;AAAA,GACxB,CAAA;AAAA,EAED,sBAAsB,SAAA,CAAU;AAAA,IAC5B,WAAA,EAAa,6CAAA;AAAA,IACb,OAAA,EAAS,EAAA;AAAA,IACT,QAAA,EAAU,CAAC,EAAA,EAAI,EAAA,EAAI,EAAE;AAAA,GACxB,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,cAAc,SAAA,CAAU;AAAA,IACpB,WAAA,EAAa,sDAAA;AAAA,IACb,OAAA,EAAS,CAAA;AAAA,IACT,QAAA,EAAU,CAAC,CAAA,EAAG,CAAA,EAAG,EAAE;AAAA,GACtB,CAAA;AAAA,EAED,wBAAwB,SAAA,CAAU;AAAA,IAC9B,WAAA,EAAa,8DAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,EAAA,EAAI,GAAA,EAAK,GAAG;AAAA,GAC1B,CAAA;AAAA,EAED,oBAAoB,SAAA,CAAU;AAAA,IAC1B,WAAA,EAAa,8DAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAM,GAAA,EAAO,GAAK;AAAA,GAChC,CAAA;AAAA,EAED,iBAAiB,SAAA,CAAU;AAAA,IACvB,WAAA,EAAa,sDAAA;AAAA,IACb,OAAA,EAAS,CAAA;AAAA,IACT,QAAA,EAAU,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC;AAAA,GACvB,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,yBAAyB,UAAA,CAAW;AAAA,IAChC,WAAA,EAAa,wCAAA;AAAA,IACb,OAAA,EAAS,IAAA;AAAA,IACT,QAAA,EAAU,CAAC,IAAA,EAAM,KAAK;AAAA,GACzB,CAAA;AAAA,EAED,0BAA0B,SAAA,CAAU;AAAA,IAChC,WAAA,EAAa,+CAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAO,GAAA,EAAO,IAAM;AAAA,GAClC,CAAA;AAAA,EAED,2BAA2B,UAAA,CAAW;AAAA,IAClC,WAAA,EAAa,+CAAA;AAAA,IACb,OAAA,EAAS,IAAA;AAAA,IACT,QAAA,EAAU,CAAC,IAAA,EAAM,KAAK;AAAA,GACzB,CAAA;AAAA,EAED,6BAA6B,SAAA,CAAU;AAAA,IACnC,WAAA,EAAa,8DAAA;AAAA,IACb,OAAA,EAAS,CAAA;AAAA,IACT,QAAA,EAAU,CAAC,CAAA,EAAG,CAAA,EAAG,EAAE;AAAA,GACtB,CAAA;AAAA,EAED,gCAAgC,SAAA,CAAU;AAAA,IACtC,WAAA,EAAa,6DAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAM,GAAA,EAAO,IAAK;AAAA,GAChC,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,uBAAuB,UAAA,CAAW;AAAA,IAC9B,WAAA,EAAa,8CAAA;AAAA,IACb,OAAA,EAAS,KAAA;AAAA,IACT,QAAA,EAAU,CAAC,IAAA,EAAM,KAAK;AAAA,GACzB,CAAA;AAAA,EAED,8BAA8B,SAAA,CAAU;AAAA,IACpC,WAAA,EAAa,oDAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAK,GAAA,EAAM,GAAI;AAAA,GAC7B,CAAA;AAAA,EAED,2BAA2B,UAAA,CAAW;AAAA,IAClC,WAAA,EAAa,kDAAA;AAAA,IACb,OAAA,EAAS,KAAA;AAAA,IACT,QAAA,EAAU,CAAC,IAAA,EAAM,KAAK;AAAA,GACzB,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,qBAAqB,SAAA,CAAU;AAAA,IAC3B,WAAA,EAAa,qCAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAO,GAAA,EAAO,GAAK;AAAA,GACjC,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,gBAAgB,UAAA,CAAW;AAAA,IACvB,WAAA,EAAa,uDAAA;AAAA,IACb,OAAA,EAAS,KAAA;AAAA,IACT,QAAA,EAAU,CAAC,IAAA,EAAM,KAAK;AAAA,GACzB,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,qBAAqB,SAAA,CAAU;AAAA,IAC3B,WAAA,EAAa,sCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,OAAA,EAAS,iCAAA;AAAA,IACT,QAAA,EAAU,CAAC,oBAAA,EAAsB,iCAAiC;AAAA,GACrE,CAAA;AAAA,EAED,iBAAiB,SAAA,CAAU;AAAA,IACvB,WAAA,EAAa,yCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,OAAA,EAAS,WAAA;AAAA,IACT,QAAA,EAAU,CAAC,WAAA,EAAa,cAAc;AAAA,GACzC,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,cAAA,EAAgB,QAAQ,CAAC,OAAA,EAAS,QAAQ,MAAA,EAAQ,OAAA,EAAS,OAAO,CAAA,EAAY;AAAA,IAC1E,WAAA,EAAa,6BAAA;AAAA,IACb,OAAA,EAAS;AAAA,GACZ,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,WAAW,SAAA,CAAU;AAAA,IACjB,WAAA,EAAa,kCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,SAAA,EAAW,aAAA;AAAA,IACX,QAAA,EAAU,CAAC,wBAAA,EAA0B,gCAAgC;AAAA,GACxE,CAAA;AAAA,EAED,iBAAiB,SAAA,CAAU;AAAA,IACvB,WAAA,EAAa,6DAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,SAAA,EAAW,aAAA;AAAA,IACX,QAAA,EAAU,CAAC,qBAAqB;AAAA,GACnC,CAAA;AAAA,EAED,gBAAgB,SAAA,CAAU;AAAA,IACtB,WAAA,EAAa,6DAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,SAAA,EAAW,aAAA;AAAA,IACX,QAAA,EAAU,CAAC,sBAAsB;AAAA,GACpC,CAAA;AAAA,EAED,sBAAsB,SAAA,CAAU;AAAA,IAC5B,WAAA,EAAa,sCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,QAAA,EAAU,CAAC,iCAAiC;AAAA,GAC/C,CAAA;AAAA,EAED,qBAAqB,SAAA,CAAU;AAAA,IAC3B,WAAA,EAAa,qCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,QAAA,EAAU,CAAC,kCAAkC;AAAA,GAChD,CAAA;AAAA,EAED,mBAAmB,SAAA,CAAU;AAAA,IACzB,WAAA,EAAa,4BAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,QAAA,EAAU,CAAC,UAAU;AAAA,GACxB,CAAA;AAAA,EAED,gBAAgB,SAAA,CAAU;AAAA,IACtB,WAAA,EAAa,sCAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,QAAA,EAAU,CAAC,qBAAqB;AAAA,GACnC,CAAA;AAAA,EAED,+BAA+B,UAAA,CAAW;AAAA,IACtC,WAAA,EAAa,sDAAA;AAAA,IACb,OAAA,EAAS,IAAA;AAAA,IACT,QAAA,EAAU,CAAC,IAAA,EAAM,KAAK;AAAA,GACzB,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM,SAAA,CAAU;AAAA,IACZ,WAAA,EAAa,oBAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAM,GAAA,EAAM,IAAI;AAAA,GAC9B,CAAA;AAAA,EAED,MAAM,SAAA,CAAU;AAAA,IACZ,WAAA,EAAa,iBAAA;AAAA,IACb,OAAA,EAAS,WAAA;AAAA,IACT,QAAA,EAAU,KAAA;AAAA,IACV,QAAA,EAAU,CAAC,WAAA,EAAa,SAAA,EAAW,WAAW;AAAA,GACjD,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,gBAAgB,SAAA,CAAU;AAAA,IACtB,WAAA,EAAa,iCAAA;AAAA,IACb,OAAA,EAAS,IAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAO,IAAA,EAAQ,GAAM;AAAA,GACnC,CAAA;AAAA,EAED,0BAA0B,SAAA,CAAU;AAAA,IAChC,WAAA,EAAa,oCAAA;AAAA,IACb,OAAA,EAAS,IAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAO,IAAA,EAAO,IAAM;AAAA,GAClC,CAAA;AAAA,EAED,wBAAwB,SAAA,CAAU;AAAA,IAC9B,WAAA,EAAa,iCAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAO,GAAA,EAAO,IAAM;AAAA,GAClC,CAAA;AAAA,EAED,kBAAkB,SAAA,CAAU;AAAA,IACxB,WAAA,EAAa,2CAAA;AAAA,IACb,OAAA,EAAS,GAAA;AAAA,IACT,QAAA,EAAU,CAAC,GAAA,EAAO,GAAA,EAAO,GAAK;AAAA,GACjC,CAAA;AAAA;AAAA;AAAA;AAAA,EAMD,cAAc,MAAA,CAAO;AAAA,IACjB,WAAA,EAAa,gDAAA;AAAA,IACb,QAAA,EAAU,IAAA;AAAA,IACV,MAAA,EAAQ,IAAA;AAAA,IACR,QAAA,EAAU,CAAC,uBAAA,EAAyB,0BAA0B;AAAA,GACjE,CAAA;AAAA,EAED,0BAA0B,MAAA,CAAO;AAAA,IAC7B,WAAA,EAAa,gDAAA;AAAA,IACb,QAAA,EAAU,IAAA;AAAA,IACV,MAAA,EAAQ,IAAA;AAAA,IACR,QAAA,EAAU,CAAC,uBAAA,EAAyB,0BAA0B;AAAA,GACjE,CAAA;AAAA,EAED,cAAc,MAAA,CAAO;AAAA,IACjB,WAAA,EAAa,+CAAA;AAAA,IACb,QAAA,EAAU,KAAA;AAAA,IACV,MAAA,EAAQ,IAAA;AAAA,IACR,QAAA,EAAU,CAAC,uBAAA,EAAyB,sBAAsB;AAAA,GAC7D;AACL,CAAC;;;AClUM,IAAM,QAAA,GAAW,kBAAkB,aAAa;AAKhD,IAAM,GAAA,GAAM,SAAS,QAAA","file":"index.js","sourcesContent":["/**\n * Core Package Environment Variable Schema\n *\n * Centralized schema definition for all environment variables used in @spfn/core.\n * This provides type safety, validation, and documentation for environment configuration.\n *\n * @module config/schema\n */\n\nimport {\n defineEnvSchema,\n envEnum,\n envNumber,\n envBoolean,\n envUrl,\n envString,\n parsePostgresUrl,\n parseRedisUrl,\n} from '@spfn/core/env';\n\n/**\n * Core package environment variable schema\n *\n * Defines all environment variables with:\n * - Type information\n * - Default values\n * - Validation rules\n * - Documentation\n *\n * @example\n * ```typescript\n * import { coreEnvSchema } from '@spfn/core/config';\n *\n * // Access schema information\n * console.log(coreEnvSchema.DB_POOL_MAX.description);\n * console.log(coreEnvSchema.DB_POOL_MAX.default);\n * ```\n */\nexport const coreEnvSchema = defineEnvSchema({\n // ========================================================================\n // Core Environment\n // ========================================================================\n\n NODE_ENV: envEnum(['local', 'development', 'production', 'test'] as const, {\n description: 'Node.js runtime environment',\n default: 'local',\n nextjs: true,\n }),\n\n // ========================================================================\n // Database - Connection\n // ========================================================================\n\n DATABASE_URL: envString({\n description: 'Primary database connection URL',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@localhost:5432/dbname'],\n }),\n\n DATABASE_WRITE_URL: envString({\n description: 'Write database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@master:5432/dbname'],\n }),\n\n DATABASE_READ_URL: envString({\n description: 'Read database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@replica:5432/dbname'],\n }),\n\n\n // ========================================================================\n // Database - Connection Pool\n // ========================================================================\n\n DB_POOL_MAX: envNumber({\n description: 'Maximum number of database connections in pool',\n default: 10,\n examples: [10, 20, 50],\n }),\n\n DB_POOL_IDLE_TIMEOUT: envNumber({\n description: 'Database connection idle timeout in seconds',\n default: 30,\n examples: [20, 30, 60],\n }),\n\n // ========================================================================\n // Database - Retry Configuration\n // ========================================================================\n\n DB_RETRY_MAX: envNumber({\n description: 'Maximum number of database connection retry attempts',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_RETRY_INITIAL_DELAY: envNumber({\n description: 'Initial delay between database retry attempts (milliseconds)',\n default: 100,\n examples: [50, 100, 200],\n }),\n\n DB_RETRY_MAX_DELAY: envNumber({\n description: 'Maximum delay cap for database retry attempts (milliseconds)',\n default: 10000,\n examples: [5000, 10000, 30000],\n }),\n\n DB_RETRY_FACTOR: envNumber({\n description: 'Exponential backoff factor for database retry delays',\n default: 2,\n examples: [2, 1.5, 3],\n }),\n\n // ========================================================================\n // Database - Health Check\n // ========================================================================\n\n DB_HEALTH_CHECK_ENABLED: envBoolean({\n description: 'Enable periodic database health checks',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_INTERVAL: envNumber({\n description: 'Database health check interval (milliseconds)',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n DB_HEALTH_CHECK_RECONNECT: envBoolean({\n description: 'Reconnect to database on health check failure',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_MAX_RETRIES: envNumber({\n description: 'Maximum health check retry attempts before marking as failed',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_HEALTH_CHECK_RETRY_INTERVAL: envNumber({\n description: 'Interval between health check retry attempts (milliseconds)',\n default: 5000,\n examples: [5000, 10000, 15000],\n }),\n\n // ========================================================================\n // Database - Monitoring\n // ========================================================================\n\n DB_MONITORING_ENABLED: envBoolean({\n description: 'Enable database query performance monitoring',\n default: false,\n examples: [true, false],\n }),\n\n DB_MONITORING_SLOW_THRESHOLD: envNumber({\n description: 'Slow query threshold for monitoring (milliseconds)',\n default: 1000,\n examples: [500, 1000, 2000],\n }),\n\n DB_MONITORING_LOG_QUERIES: envBoolean({\n description: 'Log all database queries (not just slow queries)',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Database - Transaction\n // ========================================================================\n\n TRANSACTION_TIMEOUT: envNumber({\n description: 'Transaction timeout in milliseconds',\n default: 30000,\n examples: [10000, 30000, 60000],\n }),\n\n // ========================================================================\n // Database - Development\n // ========================================================================\n\n DB_DEBUG_TRACE: envBoolean({\n description: 'Enable detailed debug tracing for database operations',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Drizzle ORM\n // ========================================================================\n\n DRIZZLE_SCHEMA_PATH: envString({\n description: 'Path to Drizzle schema configuration',\n required: false,\n default: './src/server/entities/config.ts',\n examples: ['./src/db/schema.ts', './src/server/entities/config.ts'],\n }),\n\n DRIZZLE_OUT_DIR: envString({\n description: 'Output directory for Drizzle migrations',\n required: false,\n default: './drizzle',\n examples: ['./drizzle', './migrations'],\n }),\n\n // ========================================================================\n // Logger - Core\n // ========================================================================\n\n SPFN_LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error', 'fatal'] as const, {\n description: 'Minimum log level to output',\n default: 'info'\n }),\n\n // ========================================================================\n // Cache (Redis/Valkey)\n // ========================================================================\n\n CACHE_URL: envString({\n description: 'Single Redis/Valkey instance URL',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://localhost:6379', 'rediss://secure.cache.com:6380'],\n }),\n\n CACHE_WRITE_URL: envString({\n description: 'Master Redis/Valkey URL for writes (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://master:6379'],\n }),\n\n CACHE_READ_URL: envString({\n description: 'Replica Redis/Valkey URL for reads (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://replica:6379'],\n }),\n\n CACHE_SENTINEL_HOSTS: envString({\n description: 'Comma-separated Redis Sentinel hosts',\n required: false,\n examples: ['sentinel1:26379,sentinel2:26379'],\n }),\n\n CACHE_CLUSTER_NODES: envString({\n description: 'Comma-separated Redis Cluster nodes',\n required: false,\n examples: ['node1:6379,node2:6379,node3:6379'],\n }),\n\n CACHE_MASTER_NAME: envString({\n description: 'Redis Sentinel master name',\n required: false,\n examples: ['mymaster'],\n }),\n\n CACHE_PASSWORD: envString({\n description: 'Redis/Valkey authentication password',\n required: false,\n sensitive: true,\n examples: ['your-redis-password'],\n }),\n\n CACHE_TLS_REJECT_UNAUTHORIZED: envBoolean({\n description: 'Verify TLS certificates for secure Redis connections',\n default: true,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Server - Core\n // ========================================================================\n\n PORT: envNumber({\n description: 'Server port number',\n default: 4000,\n examples: [3000, 4000, 8080],\n }),\n\n HOST: envString({\n description: 'Server hostname',\n default: 'localhost',\n required: false,\n examples: ['localhost', '0.0.0.0', '127.0.0.1'],\n }),\n\n // ========================================================================\n // Server - Timeout\n // ========================================================================\n\n SERVER_TIMEOUT: envNumber({\n description: 'Request timeout in milliseconds',\n default: 120000,\n examples: [60000, 120000, 300000],\n }),\n\n SERVER_KEEPALIVE_TIMEOUT: envNumber({\n description: 'Keep-alive timeout in milliseconds',\n default: 65000,\n examples: [30000, 65000, 120000],\n }),\n\n SERVER_HEADERS_TIMEOUT: envNumber({\n description: 'Headers timeout in milliseconds',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n SHUTDOWN_TIMEOUT: envNumber({\n description: 'Graceful shutdown timeout in milliseconds',\n default: 30000,\n examples: [10000, 30000, 60000],\n }),\n\n // ========================================================================\n // Next.js Integration\n // ========================================================================\n\n SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n NEXT_PUBLIC_SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n SPFN_APP_URL: envUrl({\n description: 'Next.js application URL (used by SPFN server)',\n required: false,\n nextjs: true,\n examples: ['http://localhost:3790', 'https://your-app.com'],\n }),\n});","/**\n * Core Package Configuration\n *\n * @example\n * ```typescript\n * import { registry } from '@spfn/core/config';\n *\n * const env = registry.validate();\n * console.log(env.DB_POOL_MAX);\n * ```\n *\n * @module config\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { coreEnvSchema } from './schema';\n\n/**\n * Core environment schema\n */\nexport { coreEnvSchema as envSchema } from './schema';\n\n/**\n * Environment registry\n *\n * @example\n * ```typescript\n * // Reset for testing\n * registry.reset();\n * ```\n */\nexport const registry = createEnvRegistry(coreEnvSchema);\n\n/**\n * Validated environment configuration\n */\nexport const env = registry.validate();"]}
1
+ {"version":3,"sources":["../../src/config/schema.ts","../../src/config/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core Package Environment Variable Schema\n *\n * Centralized schema definition for all environment variables used in @spfn/core.\n * This provides type safety, validation, and documentation for environment configuration.\n *\n * @module config/schema\n */\n\nimport {\n defineEnvSchema,\n envEnum,\n envNumber,\n envBoolean,\n envUrl,\n envString,\n parsePostgresUrl,\n parseRedisUrl,\n} from '@spfn/core/env';\n\n/**\n * Core package environment variable schema\n *\n * Defines all environment variables with:\n * - Type information\n * - Default values\n * - Validation rules\n * - Documentation\n *\n * @example\n * ```typescript\n * import { coreEnvSchema } from '@spfn/core/config';\n *\n * // Access schema information\n * console.log(coreEnvSchema.DB_POOL_MAX.description);\n * console.log(coreEnvSchema.DB_POOL_MAX.default);\n * ```\n */\nexport const coreEnvSchema = defineEnvSchema({\n // ========================================================================\n // Core Environment\n // ========================================================================\n\n NODE_ENV: envEnum(['local', 'development', 'staging', 'production', 'test'] as const, {\n description: 'Node.js runtime environment',\n default: 'local',\n nextjs: true,\n }),\n\n // ========================================================================\n // Database - Connection\n // ========================================================================\n\n DATABASE_URL: envString({\n description: 'Primary database connection URL',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@localhost:5432/dbname'],\n }),\n\n DATABASE_WRITE_URL: envString({\n description: 'Write database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@master:5432/dbname'],\n }),\n\n DATABASE_READ_URL: envString({\n description: 'Read database URL (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parsePostgresUrl,\n examples: ['postgresql://user:password@replica:5432/dbname'],\n }),\n\n\n // ========================================================================\n // Database - Connection Pool\n // ========================================================================\n\n DB_POOL_MAX: envNumber({\n description: 'Maximum number of database connections in pool',\n default: 10,\n examples: [10, 20, 50],\n }),\n\n DB_POOL_IDLE_TIMEOUT: envNumber({\n description: 'Database connection idle timeout in seconds',\n default: 30,\n examples: [20, 30, 60],\n }),\n\n // ========================================================================\n // Database - Retry Configuration\n // ========================================================================\n\n DB_RETRY_MAX: envNumber({\n description: 'Maximum number of database connection retry attempts',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_RETRY_INITIAL_DELAY: envNumber({\n description: 'Initial delay between database retry attempts (milliseconds)',\n default: 100,\n examples: [50, 100, 200],\n }),\n\n DB_RETRY_MAX_DELAY: envNumber({\n description: 'Maximum delay cap for database retry attempts (milliseconds)',\n default: 10000,\n examples: [5000, 10000, 30000],\n }),\n\n DB_RETRY_FACTOR: envNumber({\n description: 'Exponential backoff factor for database retry delays',\n default: 2,\n examples: [2, 1.5, 3],\n }),\n\n // ========================================================================\n // Database - Health Check\n // ========================================================================\n\n DB_HEALTH_CHECK_ENABLED: envBoolean({\n description: 'Enable periodic database health checks',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_INTERVAL: envNumber({\n description: 'Database health check interval (milliseconds)',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n DB_HEALTH_CHECK_RECONNECT: envBoolean({\n description: 'Reconnect to database on health check failure',\n default: true,\n examples: [true, false],\n }),\n\n DB_HEALTH_CHECK_MAX_RETRIES: envNumber({\n description: 'Maximum health check retry attempts before marking as failed',\n default: 3,\n examples: [3, 5, 10],\n }),\n\n DB_HEALTH_CHECK_RETRY_INTERVAL: envNumber({\n description: 'Interval between health check retry attempts (milliseconds)',\n default: 5000,\n examples: [5000, 10000, 15000],\n }),\n\n // ========================================================================\n // Database - Monitoring\n // ========================================================================\n\n DB_MONITORING_ENABLED: envBoolean({\n description: 'Enable database query performance monitoring',\n default: false,\n examples: [true, false],\n }),\n\n DB_MONITORING_SLOW_THRESHOLD: envNumber({\n description: 'Slow query threshold for monitoring (milliseconds)',\n default: 1000,\n examples: [500, 1000, 2000],\n }),\n\n DB_MONITORING_LOG_QUERIES: envBoolean({\n description: 'Log all database queries (not just slow queries)',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Database - Transaction\n // ========================================================================\n\n TRANSACTION_TIMEOUT: envNumber({\n description: 'Transaction timeout in milliseconds',\n default: 30000,\n examples: [10000, 30000, 60000],\n }),\n\n // ========================================================================\n // Database - Development\n // ========================================================================\n\n DB_DEBUG_TRACE: envBoolean({\n description: 'Enable detailed debug tracing for database operations',\n default: false,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Drizzle ORM\n // ========================================================================\n\n DRIZZLE_SCHEMA_PATH: envString({\n description: 'Path to Drizzle schema configuration',\n required: false,\n default: './src/server/entities/config.ts',\n examples: ['./src/db/schema.ts', './src/server/entities/config.ts'],\n }),\n\n DRIZZLE_OUT_DIR: envString({\n description: 'Output directory for Drizzle migrations',\n required: false,\n default: './drizzle',\n examples: ['./drizzle', './migrations'],\n }),\n\n // ========================================================================\n // Logger - Core\n // ========================================================================\n\n SPFN_LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error', 'fatal'] as const, {\n description: 'Minimum log level to output',\n default: 'info'\n }),\n\n // ========================================================================\n // Cache (Redis/Valkey)\n // ========================================================================\n\n CACHE_URL: envString({\n description: 'Single Redis/Valkey instance URL',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://localhost:6379', 'rediss://secure.cache.com:6380'],\n }),\n\n CACHE_WRITE_URL: envString({\n description: 'Master Redis/Valkey URL for writes (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://master:6379'],\n }),\n\n CACHE_READ_URL: envString({\n description: 'Replica Redis/Valkey URL for reads (master-replica pattern)',\n required: false,\n sensitive: true,\n validator: parseRedisUrl,\n examples: ['redis://replica:6379'],\n }),\n\n CACHE_SENTINEL_HOSTS: envString({\n description: 'Comma-separated Redis Sentinel hosts',\n required: false,\n examples: ['sentinel1:26379,sentinel2:26379'],\n }),\n\n CACHE_CLUSTER_NODES: envString({\n description: 'Comma-separated Redis Cluster nodes',\n required: false,\n examples: ['node1:6379,node2:6379,node3:6379'],\n }),\n\n CACHE_MASTER_NAME: envString({\n description: 'Redis Sentinel master name',\n required: false,\n examples: ['mymaster'],\n }),\n\n CACHE_PASSWORD: envString({\n description: 'Redis/Valkey authentication password',\n required: false,\n sensitive: true,\n examples: ['your-redis-password'],\n }),\n\n CACHE_TLS_REJECT_UNAUTHORIZED: envBoolean({\n description: 'Verify TLS certificates for secure Redis connections',\n default: true,\n examples: [true, false],\n }),\n\n // ========================================================================\n // Server - Core\n // ========================================================================\n\n PORT: envNumber({\n description: 'Server port number',\n default: 4000,\n examples: [3000, 4000, 8080],\n }),\n\n HOST: envString({\n description: 'Server hostname',\n default: 'localhost',\n required: false,\n examples: ['localhost', '0.0.0.0', '127.0.0.1'],\n }),\n\n // ========================================================================\n // Server - Timeout\n // ========================================================================\n\n SERVER_TIMEOUT: envNumber({\n description: 'Request timeout in milliseconds',\n default: 120000,\n examples: [60000, 120000, 300000],\n }),\n\n SERVER_KEEPALIVE_TIMEOUT: envNumber({\n description: 'Keep-alive timeout in milliseconds',\n default: 65000,\n examples: [30000, 65000, 120000],\n }),\n\n SERVER_HEADERS_TIMEOUT: envNumber({\n description: 'Headers timeout in milliseconds',\n default: 60000,\n examples: [30000, 60000, 120000],\n }),\n\n SHUTDOWN_TIMEOUT: envNumber({\n description: 'Graceful shutdown timeout in milliseconds',\n default: 30000,\n examples: [10000, 30000, 60000],\n }),\n\n // ========================================================================\n // Next.js Integration\n // ========================================================================\n\n SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n NEXT_PUBLIC_SPFN_API_URL: envUrl({\n description: 'SPFN API URL (used by Next.js to call backend)',\n required: true,\n nextjs: true,\n examples: ['http://localhost:8790', 'https://api.your-app.com'],\n }),\n\n SPFN_APP_URL: envUrl({\n description: 'Next.js application URL (used by SPFN server)',\n required: false,\n nextjs: true,\n examples: ['http://localhost:3790', 'https://your-app.com'],\n }),\n});","/**\n * Core Package Configuration\n *\n * @example\n * ```typescript\n * import { registry } from '@spfn/core/config';\n *\n * const env = registry.validate();\n * console.log(env.DB_POOL_MAX);\n * ```\n *\n * @module config\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { coreEnvSchema } from './schema';\n\n/**\n * Core environment schema\n */\nexport { coreEnvSchema as envSchema } from './schema';\n\n/**\n * Environment registry\n *\n * @example\n * ```typescript\n * // Reset for testing\n * registry.reset();\n * ```\n */\nexport const registry = createEnvRegistry(coreEnvSchema);\n\n/**\n * Validated environment configuration\n */\nexport const env = registry.validate();"]}
@@ -1,17 +1,28 @@
1
1
  /**
2
2
  * Environment Variable Loader
3
3
  *
4
- * Next.js 스타일의 환경변수 파일 로딩
4
+ * Next.js 스타일의 환경변수 파일 로딩 (환경별 분리 지원)
5
+ *
6
+ * 로딩 우선순위 (낮음 -> 높음, 나중 파일이 덮어씀):
7
+ * 1. .env - 기본값 (committed)
8
+ * 2. .env.{NODE_ENV} - 환경별 오버라이드 (committed)
9
+ * 3. .env.local - 로컬 오버라이드 (gitignored, test에서 스킵)
10
+ * 4. .env.{NODE_ENV}.local - 환경별 시크릿 (gitignored)
11
+ * 5. .env.server - 서버 전용 기본값 (committed)
12
+ * 6. .env.server.local - 서버 전용 시크릿 (gitignored)
5
13
  *
6
14
  * @example
7
15
  * ```typescript
8
16
  * import { loadEnv } from '@spfn/core/env/loader';
9
17
  *
10
- * // SPFN 서버 진입점에서 호출
18
+ * // 기본 사용 (NODE_ENV 자동 감지)
11
19
  * loadEnv();
12
20
  *
13
- * // 이후 스키마 검증
14
- * const env = createEnvRegistry(envSchema).validate();
21
+ * // 특정 환경 지정
22
+ * loadEnv({ nodeEnv: 'production' });
23
+ *
24
+ * // 서버 레이어 제외 (Next.js 클라이언트용)
25
+ * loadEnv({ server: false });
15
26
  * ```
16
27
  *
17
28
  * @module env/loader
@@ -25,6 +36,16 @@ interface LoadEnvOptions {
25
36
  * @default process.cwd()
26
37
  */
27
38
  cwd?: string;
39
+ /**
40
+ * NODE_ENV 값 (환경별 .env 파일 결정)
41
+ * @default process.env.NODE_ENV || 'local'
42
+ */
43
+ nodeEnv?: string;
44
+ /**
45
+ * 서버 전용 파일 포함 여부 (.env.server, .env.server.local)
46
+ * @default true
47
+ */
48
+ server?: boolean;
28
49
  /**
29
50
  * 디버그 모드 (로드된 파일 로깅)
30
51
  * @default false
@@ -52,24 +73,11 @@ interface LoadEnvResult {
52
73
  /**
53
74
  * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드
54
75
  *
55
- * Next.js 스타일의 우선순위를 따름:
56
- * - .env .env.local .env.server .env.server.local
57
- * - 나중에 로드된 값이 이전 값을 덮어씀
76
+ * 모든 파일을 파싱 후 머지한 뒤 process.env에 한번에 적용.
77
+ * 이미 process.env 존재하는 키는 덮어쓰지 않음 (플랫폼 주입 보호).
58
78
  *
59
79
  * @param options - 로드 옵션
60
80
  * @returns 로드 결과 (로드된 파일, 키 목록)
61
- *
62
- * @example
63
- * ```typescript
64
- * // 기본 사용
65
- * loadEnv();
66
- *
67
- * // 커스텀 경로
68
- * loadEnv({ cwd: '/path/to/project' });
69
- *
70
- * // 디버그 모드
71
- * loadEnv({ debug: true });
72
- * ```
73
81
  */
74
82
  declare function loadEnv(options?: LoadEnvOptions): LoadEnvResult;
75
83
  /**
@@ -5,58 +5,66 @@ import { logger } from '@spfn/core/logger';
5
5
 
6
6
  // src/env/loader.ts
7
7
  var envLogger = logger.child("@spfn/core:env-loader");
8
- var ENV_FILES = [
9
- ".env",
10
- ".env.local",
11
- ".env.server",
12
- ".env.server.local"
13
- ];
8
+ function getEnvFiles(nodeEnv, server) {
9
+ const files = [
10
+ ".env",
11
+ `.env.${nodeEnv}`
12
+ ];
13
+ if (nodeEnv !== "test") {
14
+ files.push(".env.local");
15
+ }
16
+ files.push(`.env.${nodeEnv}.local`);
17
+ if (server) {
18
+ files.push(".env.server");
19
+ files.push(".env.server.local");
20
+ }
21
+ return files;
22
+ }
14
23
  function parseEnvFile(filePath) {
15
24
  if (!existsSync(filePath)) {
16
25
  return null;
17
26
  }
18
- const content = readFileSync(filePath, "utf-8");
19
- return parse(content);
27
+ return parse(readFileSync(filePath, "utf-8"));
20
28
  }
21
29
  function loadEnv(options = {}) {
22
30
  const {
23
31
  cwd = process.cwd(),
32
+ nodeEnv = process.env.NODE_ENV || "local",
33
+ server = true,
24
34
  debug = false,
25
35
  override = false
26
36
  } = options;
37
+ const envFiles = getEnvFiles(nodeEnv, server);
27
38
  const loadedFiles = [];
28
- const loadedKeys = /* @__PURE__ */ new Set();
29
- for (const fileName of ENV_FILES) {
39
+ const existingKeys = new Set(Object.keys(process.env));
40
+ const merged = {};
41
+ for (const fileName of envFiles) {
30
42
  const filePath = resolve(cwd, fileName);
31
43
  const parsed = parseEnvFile(filePath);
32
44
  if (parsed === null) {
33
45
  continue;
34
46
  }
35
47
  loadedFiles.push(fileName);
36
- for (const [key, value] of Object.entries(parsed)) {
37
- if (!override && process.env[key] !== void 0) {
38
- continue;
39
- }
40
- process.env[key] = value;
41
- loadedKeys.add(key);
48
+ Object.assign(merged, parsed);
49
+ }
50
+ const loadedKeys = [];
51
+ for (const [key, value] of Object.entries(merged)) {
52
+ if (!override && existingKeys.has(key)) {
53
+ continue;
42
54
  }
55
+ process.env[key] = value;
56
+ loadedKeys.push(key);
43
57
  }
44
58
  if (debug && loadedFiles.length > 0) {
45
59
  envLogger.debug(`Loaded env files: ${loadedFiles.join(", ")}`);
46
- envLogger.debug(`Loaded ${loadedKeys.size} environment variables`);
60
+ envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);
47
61
  }
48
- return {
49
- loadedFiles,
50
- loadedKeys: Array.from(loadedKeys)
51
- };
62
+ return { loadedFiles, loadedKeys };
52
63
  }
53
64
  var isEnvLoaded = false;
54
65
  function loadEnvOnce(options = {}) {
55
66
  if (isEnvLoaded) {
56
- return {
57
- loadedFiles: [],
58
- loadedKeys: []
59
- };
67
+ return { loadedFiles: [], loadedKeys: [] };
60
68
  }
61
69
  isEnvLoaded = true;
62
70
  return loadEnv(options);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/env/loader.ts"],"names":[],"mappings":";;;;;;AAwBA,IAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,uBAAuB,CAAA;AAkCtD,IAAM,SAAA,GAAY;AAAA,EACd,MAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA;AACJ,CAAA;AAKA,SAAS,aAAa,QAAA,EACtB;AACI,EAAA,IAAI,CAAC,UAAA,CAAW,QAAQ,CAAA,EACxB;AACI,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAC9C,EAAA,OAAO,MAAM,OAAO,CAAA;AACxB;AAwCO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EACnD;AACI,EAAA,MAAM;AAAA,IACF,GAAA,GAAM,QAAQ,GAAA,EAAI;AAAA,IAClB,KAAA,GAAQ,KAAA;AAAA,IACR,QAAA,GAAW;AAAA,GACf,GAAI,OAAA;AAEJ,EAAA,MAAM,cAAwB,EAAC;AAC/B,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAY;AAEnC,EAAA,KAAA,MAAW,YAAY,SAAA,EACvB;AACI,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,EAAK,QAAQ,CAAA;AACtC,IAAA,MAAM,MAAA,GAAS,aAAa,QAAQ,CAAA;AAEpC,IAAA,IAAI,WAAW,IAAA,EACf;AACI,MAAA;AAAA,IACJ;AAEA,IAAA,WAAA,CAAY,KAAK,QAAQ,CAAA;AAEzB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAChD;AAEI,MAAA,IAAI,CAAC,QAAA,IAAY,OAAA,CAAQ,GAAA,CAAI,GAAG,MAAM,MAAA,EACtC;AACI,QAAA;AAAA,MACJ;AAEA,MAAA,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AACnB,MAAA,UAAA,CAAW,IAAI,GAAG,CAAA;AAAA,IACtB;AAAA,EACJ;AAEA,EAAA,IAAI,KAAA,IAAS,WAAA,CAAY,MAAA,GAAS,CAAA,EAClC;AACI,IAAA,SAAA,CAAU,MAAM,CAAA,kBAAA,EAAqB,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAC7D,IAAA,SAAA,CAAU,KAAA,CAAM,CAAA,OAAA,EAAU,UAAA,CAAW,IAAI,CAAA,sBAAA,CAAwB,CAAA;AAAA,EACrE;AAEA,EAAA,OAAO;AAAA,IACH,WAAA;AAAA,IACA,UAAA,EAAY,KAAA,CAAM,IAAA,CAAK,UAAU;AAAA,GACrC;AACJ;AAKA,IAAI,WAAA,GAAc,KAAA;AAQX,SAAS,WAAA,CAAY,OAAA,GAA0B,EAAC,EACvD;AACI,EAAA,IAAI,WAAA,EACJ;AACI,IAAA,OAAO;AAAA,MACH,aAAa,EAAC;AAAA,MACd,YAAY;AAAC,KACjB;AAAA,EACJ;AAEA,EAAA,WAAA,GAAc,IAAA;AACd,EAAA,OAAO,QAAQ,OAAO,CAAA;AAC1B;AAKO,SAAS,iBAAA,GAChB;AACI,EAAA,WAAA,GAAc,KAAA;AAClB","file":"loader.js","sourcesContent":["/**\n * Environment Variable Loader\n *\n * Next.js 스타일의 환경변수 파일 로딩\n *\n * @example\n * ```typescript\n * import { loadEnv } from '@spfn/core/env/loader';\n *\n * // SPFN 서버 진입점에서 호출\n * loadEnv();\n *\n * // 이후 스키마 검증\n * const env = createEnvRegistry(envSchema).validate();\n * ```\n *\n * @module env/loader\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse } from 'dotenv';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-loader');\n\n/**\n * loadEnv 옵션\n */\nexport interface LoadEnvOptions\n{\n /**\n * 프로젝트 루트 경로\n * @default process.cwd()\n */\n cwd?: string;\n\n /**\n * 디버그 모드 (로드된 파일 로깅)\n * @default false\n */\n debug?: boolean;\n\n /**\n * 기존 process.env 값 덮어쓰기 허용\n * @default false\n */\n override?: boolean;\n}\n\n/**\n * 환경변수 파일 로딩 순서 (우선순위 낮음 높음)\n *\n * 1. .env - 기본값 (커밋 O)\n * 2. .env.local - 로컬 오버라이드 (커밋 X)\n * 3. .env.server - 서버 전용 기본값 (커밋 O)\n * 4. .env.server.local - 서버 전용 민감정보 (커밋 X)\n */\nconst ENV_FILES = [\n '.env',\n '.env.local',\n '.env.server',\n '.env.server.local',\n] as const;\n\n/**\n * 단일 .env 파일 파싱\n */\nfunction parseEnvFile(filePath: string): Record<string, string> | null\n{\n if (!existsSync(filePath))\n {\n return null;\n }\n\n const content = readFileSync(filePath, 'utf-8');\n return parse(content);\n}\n\n/**\n * 환경변수 로드 결과\n */\nexport interface LoadEnvResult\n{\n /**\n * 로드된 파일 목록\n */\n loadedFiles: string[];\n\n /**\n * 로드된 환경변수 키 목록\n */\n loadedKeys: string[];\n}\n\n/**\n * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드\n *\n * Next.js 스타일의 우선순위를 따름:\n * - .env .env.local .env.server .env.server.local\n * - 나중에 로드된 값이 이전 값을 덮어씀\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (로드된 파일, 키 목록)\n *\n * @example\n * ```typescript\n * // 기본 사용\n * loadEnv();\n *\n * // 커스텀 경로\n * loadEnv({ cwd: '/path/to/project' });\n *\n * // 디버그 모드\n * loadEnv({ debug: true });\n * ```\n */\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult\n{\n const {\n cwd = process.cwd(),\n debug = false,\n override = false,\n } = options;\n\n const loadedFiles: string[] = [];\n const loadedKeys = new Set<string>();\n\n for (const fileName of ENV_FILES)\n {\n const filePath = resolve(cwd, fileName);\n const parsed = parseEnvFile(filePath);\n\n if (parsed === null)\n {\n continue;\n }\n\n loadedFiles.push(fileName);\n\n for (const [key, value] of Object.entries(parsed))\n {\n // 기존 값이 있고 override가 false 스킵\n if (!override && process.env[key] !== undefined)\n {\n continue;\n }\n\n process.env[key] = value;\n loadedKeys.add(key);\n }\n }\n\n if (debug && loadedFiles.length > 0)\n {\n envLogger.debug(`Loaded env files: ${loadedFiles.join(', ')}`);\n envLogger.debug(`Loaded ${loadedKeys.size} environment variables`);\n }\n\n return {\n loadedFiles,\n loadedKeys: Array.from(loadedKeys),\n };\n}\n\n/**\n * 환경변수가 이미 로드되었는지 확인하는 플래그\n */\nlet isEnvLoaded = false;\n\n/**\n * 환경변수를 한 번만 로드 (중복 호출 방지)\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (이미 로드된 경우 빈 결과)\n */\nexport function loadEnvOnce(options: LoadEnvOptions = {}): LoadEnvResult\n{\n if (isEnvLoaded)\n {\n return {\n loadedFiles: [],\n loadedKeys: [],\n };\n }\n\n isEnvLoaded = true;\n return loadEnv(options);\n}\n\n/**\n * 환경변수 로드 상태 리셋 (테스트용)\n */\nexport function resetEnvLoadState(): void\n{\n isEnvLoaded = false;\n}\n"]}
1
+ {"version":3,"sources":["../../src/env/loader.ts"],"names":[],"mappings":"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","file":"loader.js","sourcesContent":["/**\n * Environment Variable Loader\n *\n * Next.js 스타일의 환경변수 파일 로딩 (환경별 분리 지원)\n *\n * 로딩 우선순위 (낮음 -> 높음, 나중 파일이 덮어씀):\n * 1. .env - 기본값 (committed)\n * 2. .env.{NODE_ENV} - 환경별 오버라이드 (committed)\n * 3. .env.local - 로컬 오버라이드 (gitignored, test에서 스킵)\n * 4. .env.{NODE_ENV}.local - 환경별 시크릿 (gitignored)\n * 5. .env.server - 서버 전용 기본값 (committed)\n * 6. .env.server.local - 서버 전용 시크릿 (gitignored)\n *\n * @example\n * ```typescript\n * import { loadEnv } from '@spfn/core/env/loader';\n *\n * // 기본 사용 (NODE_ENV 자동 감지)\n * loadEnv();\n *\n * // 특정 환경 지정\n * loadEnv({ nodeEnv: 'production' });\n *\n * // 서버 레이어 제외 (Next.js 클라이언트용)\n * loadEnv({ server: false });\n * ```\n *\n * @module env/loader\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse } from 'dotenv';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-loader');\n\n/**\n * loadEnv 옵션\n */\nexport interface LoadEnvOptions\n{\n /**\n * 프로젝트 루트 경로\n * @default process.cwd()\n */\n cwd?: string;\n\n /**\n * NODE_ENV 값 (환경별 .env 파일 결정)\n * @default process.env.NODE_ENV || 'local'\n */\n nodeEnv?: string;\n\n /**\n * 서버 전용 파일 포함 여부 (.env.server, .env.server.local)\n * @default true\n */\n server?: boolean;\n\n /**\n * 디버그 모드 (로드된 파일 로깅)\n * @default false\n */\n debug?: boolean;\n\n /**\n * 기존 process.env 값 덮어쓰기 허용\n * @default false\n */\n override?: boolean;\n}\n\n/**\n * 환경변수 로드 결과\n */\nexport interface LoadEnvResult\n{\n /**\n * 로드된 파일 목록\n */\n loadedFiles: string[];\n\n /**\n * 로드된 환경변수 키 목록\n */\n loadedKeys: string[];\n}\n\n/**\n * NODE_ENV에 따른 .env 파일 목록 생성 (우선순위 낮음 -> 높음)\n *\n * 구체적인 파일이 승리:\n * - environment > base\n * - server > shared\n * - local > committed\n */\nfunction getEnvFiles(nodeEnv: string, server: boolean): string[]\n{\n const files: string[] = [\n '.env',\n `.env.${nodeEnv}`,\n ];\n\n // test 환경에서는 .env.local 스킵 (테스트 결정론성 보장)\n if (nodeEnv !== 'test')\n {\n files.push('.env.local');\n }\n\n files.push(`.env.${nodeEnv}.local`);\n\n if (server)\n {\n files.push('.env.server');\n files.push('.env.server.local');\n }\n\n return files;\n}\n\n/**\n * 단일 .env 파일 파싱\n */\nfunction parseEnvFile(filePath: string): Record<string, string> | null\n{\n if (!existsSync(filePath))\n {\n return null;\n }\n\n return parse(readFileSync(filePath, 'utf-8'));\n}\n\n/**\n * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드\n *\n * 모든 파일을 파싱 머지한 process.env 한번에 적용.\n * 이미 process.env 존재하는 키는 덮어쓰지 않음 (플랫폼 주입 보호).\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (로드된 파일, 키 목록)\n */\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult\n{\n const {\n cwd = process.cwd(),\n nodeEnv = process.env.NODE_ENV || 'local',\n server = true,\n debug = false,\n override = false,\n } = options;\n\n const envFiles = getEnvFiles(nodeEnv, server);\n const loadedFiles: string[] = [];\n\n // 1) 기존 process.env 키 스냅샷 저장\n const existingKeys = new Set(Object.keys(process.env));\n\n // 2) 모든 .env 파일 파싱 후 머지 (나중 파일이 승리)\n const merged: Record<string, string> = {};\n\n for (const fileName of envFiles)\n {\n const filePath = resolve(cwd, fileName);\n const parsed = parseEnvFile(filePath);\n\n if (parsed === null)\n {\n continue;\n }\n\n loadedFiles.push(fileName);\n Object.assign(merged, parsed);\n }\n\n // 3) 머지된 결과를 process.env에 적용\n const loadedKeys: string[] = [];\n\n for (const [key, value] of Object.entries(merged))\n {\n // 기존 process.env에 이미 있는 키는 스킵 (override가 false 때)\n if (!override && existingKeys.has(key))\n {\n continue;\n }\n\n process.env[key] = value;\n loadedKeys.push(key);\n }\n\n if (debug && loadedFiles.length > 0)\n {\n envLogger.debug(`Loaded env files: ${loadedFiles.join(', ')}`);\n envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);\n }\n\n return { loadedFiles, loadedKeys };\n}\n\n/**\n * 환경변수가 이미 로드되었는지 확인하는 플래그\n */\nlet isEnvLoaded = false;\n\n/**\n * 환경변수를 한 번만 로드 (중복 호출 방지)\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (이미 로드된 경우 빈 결과)\n */\nexport function loadEnvOnce(options: LoadEnvOptions = {}): LoadEnvResult\n{\n if (isEnvLoaded)\n {\n return { loadedFiles: [], loadedKeys: [] };\n }\n\n isEnvLoaded = true;\n return loadEnv(options);\n}\n\n/**\n * 환경변수 로드 상태 리셋 (테스트용)\n */\nexport function resetEnvLoadState(): void\n{\n isEnvLoaded = false;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { Router, RouteDef } from '@spfn/core/route';
2
- import { R as RequestInterceptor, a as ResponseInterceptor, I as InferRouteInput, b as InferRouteOutput, A as ApiConfig } from '../types-DP8nzQcY.js';
3
- export { C as CallOptions, e as CookieOptions, c as RouterInput, d as RouterOutput, f as SetCookie, S as StructuredInput } from '../types-DP8nzQcY.js';
2
+ import { R as RequestInterceptor, a as ResponseInterceptor, I as InferRouteInput, b as InferRouteOutput, A as ApiConfig } from '../types-7Mhoxnnt.js';
3
+ export { C as CallOptions, e as CookieOptions, c as RouterInput, d as RouterOutput, f as SetCookie, S as StructuredInput } from '../types-7Mhoxnnt.js';
4
4
  import '@sinclair/typebox';
5
5
  import '@spfn/core/errors';
6
6
 
@@ -267,7 +267,7 @@ function createApi(config = {}) {
267
267
  const {
268
268
  baseUrl = "/api/rpc",
269
269
  headers: defaultHeaders = {},
270
- timeout = 3e4,
270
+ timeout = env.SERVER_TIMEOUT,
271
271
  fetch: customFetch = fetch,
272
272
  onRequest: globalOnRequest,
273
273
  onResponse: globalOnResponse,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/nextjs/client/debug-logs.ts","../../src/nextjs/client/errors.ts","../../src/nextjs/shared.ts","../../src/nextjs/client/helpers.ts","../../src/nextjs/client/builder.ts","../../src/nextjs/client/core.ts"],"names":["logger","errorRegistry","coreErrorRegistry","headers"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Debug logging utilities for API client\n * Separates debug logging logic from main client code for better maintainability\n */\nimport type { Logger } from '@spfn/core/logger';\n\nexport function logCookieAutoDetection(\n logger: Logger,\n cookies: Array<{ name: string; value: string }>\n): void\n{\n logger.debug('Auto-detected server environment, forwarding cookies', {\n cookieCount: cookies.length,\n cookieNames: cookies.map(c => c.name),\n });\n}\n\nexport function logRequest(\n logger: Logger,\n routeName: string,\n method: string,\n url: string,\n hasBody: boolean\n): void\n{\n logger.debug('→ Request', {\n route: routeName,\n method,\n url,\n hasBody,\n });\n}\n\nexport function logResponse(\n logger: Logger,\n routeName: string,\n status: number,\n hasBody: boolean\n): void\n{\n logger.debug('← Response', {\n route: routeName,\n status,\n hasBody,\n });\n}\n\nexport function logErrorResponse(\n logger: Logger,\n status: number,\n body: any\n): void\n{\n logger.debug('Error response received', {\n status,\n hasBody: !!body,\n bodyType: typeof body,\n hasTypeField: body && typeof body === 'object' && '__type' in body,\n typeValue: body?.__type,\n });\n}\n\nexport function logErrorDeserializationAttempt(\n logger: Logger,\n errorType: string,\n registeredTypes: string[]\n): void\n{\n logger.debug('Attempting error deserialization', {\n errorType,\n hasRegistry: true,\n registeredTypes,\n });\n}\n\nexport function logErrorDeserializationSuccess(\n logger: Logger,\n error: Error | null\n): void\n{\n logger.debug('Error deserialized successfully', {\n errorName: error?.name,\n errorConstructor: error?.constructor.name,\n message: error?.message,\n });\n}\n\nexport function logErrorDeserializationFailure(\n logger: Logger,\n error: unknown\n): void\n{\n logger.debug('Deserialization failed', {\n errorName: error instanceof Error ? error.name : 'unknown',\n errorMessage: error instanceof Error ? error.message : String(error),\n });\n}\n\nexport function logErrorDeserializationSkipped(\n logger: Logger,\n errorRegistry: any,\n body: any\n): void\n{\n const reason = !errorRegistry\n ? 'no registry'\n : !body\n ? 'no body'\n : typeof body !== 'object'\n ? 'body not object'\n : !('__type' in body)\n ? 'no __type field'\n : 'unknown';\n\n logger.debug('Skipping error deserialization', { reason });\n}\n\nexport function logThrowingDeserializedError(\n logger: Logger,\n error: Error\n): void\n{\n logger.debug('Throwing deserialized error', {\n errorName: error.name,\n errorConstructorName: error.constructor.name,\n prototype: Object.getPrototypeOf(error).constructor.name,\n });\n}","// ============================================================================\n// Client Error\n// ============================================================================\n\n/**\n * Typed client error\n */\nexport class ApiError extends Error\n{\n constructor(\n message: string,\n public readonly status: number,\n public readonly url: string,\n public readonly response?: unknown,\n public readonly errorType?: 'http' | 'network' | 'timeout'\n )\n {\n super(message);\n this.name = 'ApiError';\n }\n}","/**\n * Shared utilities for Next.js client and proxy modules\n *\n * Contains common functions used by both client and proxy to avoid code duplication.\n */\n\n/**\n * Build URL with path parameters replaced\n *\n * @example\n * buildUrlWithParams('/users/:id/posts/:postId', { id: '123', postId: '456' })\n * // Returns: '/users/123/posts/456'\n */\nexport function buildUrlWithParams(path: string, params: Record<string, any>): string\n{\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n }\n\n return url;\n}\n\n/**\n * Build query string from object\n *\n * @example\n * buildQueryString({ page: '1', limit: '10', tags: ['foo', 'bar'] })\n * // Returns: '?page=1&limit=10&tags=foo&tags=bar'\n */\nexport function buildQueryString(query: Record<string, any>): string\n{\n if (Object.keys(query).length === 0)\n {\n return '';\n }\n\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => searchParams.append(key, String(v)));\n }\n else\n {\n searchParams.append(key, String(value));\n }\n }\n\n return `?${searchParams.toString()}`;\n}\n\n/**\n * Build Cookie header string from cookies object\n *\n * @example\n * buildCookieHeader({ session: 'abc123', theme: 'dark' })\n * // Returns: 'session=abc123; theme=dark'\n */\nexport function buildCookieHeader(cookies: Record<string, string>): string\n{\n return Object.entries(cookies)\n .map(([key, value]) => `${key}=${value}`)\n .join('; ');\n}\n\n/**\n * Parse response body based on content type\n *\n * Handles:\n * - 204 No Content: returns null (no body expected)\n * - application/json: parses JSON body\n * - Other content types: returns raw text\n */\nexport async function parseResponseBody(response: Response): Promise<any>\n{\n // 204 No Content has no body\n if (response.status === 204)\n {\n return null;\n }\n\n const contentType = response.headers.get('content-type');\n\n if (contentType?.includes('application/json'))\n {\n const text = await response.text();\n return text ? JSON.parse(text) : null;\n }\n else\n {\n return await response.text();\n }\n}","import type { Logger } from '@spfn/core/logger';\nimport type { ErrorRegistry } from '@spfn/core/errors';\nimport { ApiError } from './errors';\nimport * as debugLogs from './debug-logs';\n\n// Re-export shared utilities\nexport { buildCookieHeader, parseResponseBody } from '../shared';\n\n/**\n * Auto-detect cookies from Next.js server environment\n * Returns empty object if not in server environment or if cookies are not accessible\n */\nexport async function autoDetectServerCookies(): Promise<Record<string, string>>\n{\n try\n {\n // Next.js cookies() API is only available in server environment\n const { cookies } = await import('next/headers');\n const cookieStore = await cookies();\n const allCookies = cookieStore.getAll();\n\n return Object.fromEntries(\n allCookies.map(cookie => [cookie.name, cookie.value])\n );\n }\n catch (error)\n {\n // Client environment or cookies not accessible\n // Browser automatically sends cookies in client components\n return {};\n }\n}\n\n/**\n * Execute fetch with timeout and abort controller\n */\nexport async function executeFetchWithTimeout(\n url: string,\n init: RequestInit,\n timeout: number,\n customFetch: typeof fetch = fetch\n): Promise<Response>\n{\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try\n {\n const response = await customFetch(url, {\n ...init,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n return response;\n }\n catch (error)\n {\n clearTimeout(timeoutId);\n throw error;\n }\n}\n\n/**\n * Handle error response with deserialization support\n * Attempts to deserialize custom errors if errorRegistry is provided\n * Falls back to ApiError if deserialization fails or is not available\n */\nexport async function handleErrorResponse(\n response: Response,\n body: any,\n fullUrl: string,\n errorRegistry: ErrorRegistry | undefined,\n debug: boolean,\n logger: Logger\n): Promise<never>\n{\n if (debug)\n {\n debugLogs.logErrorResponse(logger, response.status, body);\n }\n\n // Try to deserialize error if registry is provided\n let deserializedError: Error | null = null;\n\n if (errorRegistry && body && typeof body === 'object' && '__type' in body)\n {\n if (debug)\n {\n debugLogs.logErrorDeserializationAttempt(logger, body.__type, errorRegistry.getRegisteredTypes());\n }\n\n try\n {\n deserializedError = errorRegistry.deserialize(body as any);\n\n if (debug)\n {\n debugLogs.logErrorDeserializationSuccess(logger, deserializedError);\n }\n }\n catch (deserializeError)\n {\n // Deserialization itself failed (type not found, invalid data, etc.)\n if (debug)\n {\n debugLogs.logErrorDeserializationFailure(logger, deserializeError);\n }\n // Fall through to ApiError below\n }\n }\n else if (debug)\n {\n debugLogs.logErrorDeserializationSkipped(logger, errorRegistry, body);\n }\n\n // If deserialization succeeded, throw the deserialized error\n if (deserializedError)\n {\n if (debug)\n {\n debugLogs.logThrowingDeserializedError(logger, deserializedError);\n }\n\n throw deserializedError;\n }\n\n // Fallback to generic ApiError\n if (response.status === 404 && process.env.NODE_ENV !== 'production')\n {\n logger.warn(\n '\\n⚠️ 404 Not Found\\n\\n' +\n 'Check the following:\\n' +\n ' 1. Routes are registered in server.config.ts:\\n' +\n ' → defineServerConfig().routes(appRouter)\\n' +\n ' 2. Delete .spfn cache if you recently added new routes:\\n' +\n ' → rm -rf .spfn\\n'\n );\n }\n\n throw new ApiError(\n body?.message || `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n fullUrl,\n body,\n 'http'\n );\n}","// ============================================================================\n// Route Call Builder (Structured Input API)\n// ============================================================================\n\nimport type { RouteDef, Router } from \"@spfn/core/route\";\nimport type {\n CallOptions,\n InferRouteInput,\n InferRouteOutput,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types\";\n\n/**\n * Pick only non-empty fields from StructuredInput\n *\n * This removes fields that are empty objects `{}` from the input type,\n * so users only need to provide fields that are actually defined in the route.\n */\ntype PickNonEmpty<T> = {\n [K in keyof T as T[K] extends Record<string, never> ? never : K]: T[K];\n};\n\n/**\n * Make fields that can be undefined into optional fields\n *\n * When a field is defined as `Type.Optional(Type.Object({...}))`,\n * the resulting type is `T | undefined`. This utility converts such fields\n * into proper optional fields (`field?: T`) so users don't need to pass them.\n */\ntype MakeOptionalIfUndefinable<T> =\n // Required fields (undefined is not assignable)\n { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }\n // Optional fields (undefined is assignable)\n & { [K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined> };\n\n/**\n * Clean structured input - only include fields that have actual schema,\n * and make fields optional if they accept undefined\n */\ntype CleanStructuredInput<TInput> = MakeOptionalIfUndefinable<PickNonEmpty<TInput>>;\n\n/**\n * Check if input has any required fields\n *\n * Returns false if all fields are optional (i.e., {} is assignable to the input type)\n */\ntype HasAnyRequiredFields<TInput> = {} extends CleanStructuredInput<TInput> ? false : true;\n\n/**\n * Route call builder with structured input API\n *\n * Input is structured with explicit params, query, body fields\n * that match the server-side route definition.\n *\n * @example\n * ```typescript\n * // GET /users/:id - params only\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // GET /users/:id?include=posts - params + query\n * const user = await api.getUser.call({\n * params: { id: '1' },\n * query: { include: 'posts' }\n * });\n *\n * // POST /users - body only\n * const user = await api.createUser.call({\n * body: { name: 'John', email: 'john@example.com' }\n * });\n *\n * // PUT /users/:id - params + body\n * const user = await api.updateUser.call({\n * params: { id: '1' },\n * body: { name: 'Jane' }\n * });\n *\n * // With options (headers, cookies, Next.js caching)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nexport class RouteCallBuilder<\n TInput,\n TOutput\n>\n{\n private _headers?: Record<string, string>;\n private _cookies?: Record<string, string>;\n private _fetchOptions?: RequestInit;\n private _onRequest?: RequestInterceptor;\n private _onResponse?: ResponseInterceptor;\n\n constructor(\n private readonly executor: (input: any, options: CallOptions) => Promise<TOutput>,\n private readonly routeName: string\n ) {}\n\n /**\n * Clone builder\n */\n private clone(): RouteCallBuilder<TInput, TOutput>\n {\n const builder = new RouteCallBuilder<TInput, TOutput>(\n this.executor,\n this.routeName\n );\n builder._headers = this._headers;\n builder._cookies = this._cookies;\n builder._fetchOptions = this._fetchOptions;\n builder._onRequest = this._onRequest;\n builder._onResponse = this._onResponse;\n return builder;\n }\n\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._headers = { ...this._headers, ...headers };\n return builder;\n }\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._cookies = { ...this._cookies, ...cookies };\n return builder;\n }\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._fetchOptions = { ...this._fetchOptions, ...options };\n return builder;\n }\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onRequest = interceptor;\n return builder;\n }\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onResponse = interceptor;\n return builder;\n }\n\n /**\n * Execute the API call with structured input\n *\n * Input structure matches the server-side route definition:\n * - params: Path parameters (e.g., { id: '123' } for /users/:id)\n * - query: Query string parameters\n * - body: Request body (for POST, PUT, PATCH)\n */\n call(input?: CleanStructuredInput<TInput>): Promise<TOutput>\n {\n const options: CallOptions = {};\n\n if (this._headers)\n {\n options.headers = this._headers;\n }\n\n if (this._cookies)\n {\n options.cookies = this._cookies;\n }\n\n if (this._fetchOptions)\n {\n options.fetchOptions = this._fetchOptions;\n }\n\n if (this._onRequest)\n {\n options.onRequest = this._onRequest;\n }\n\n if (this._onResponse)\n {\n options.onResponse = this._onResponse;\n }\n\n return this.executor(input || {}, options);\n }\n}\n\n/**\n * Individual route client with structured input API\n */\nexport type RouteClient<TRoute extends RouteDef<any, any>> = {\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteClient<TRoute>;\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteClient<TRoute>;\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteClient<TRoute>;\n\n /**\n * Execute the API call with structured input\n *\n * @example\n * ```typescript\n * // GET /users/:id\n * api.getUser.call({ params: { id: '123' } });\n *\n * // PUT /users/:id\n * api.updateUser.call({ params: { id: '123' }, body: { name: 'Jane' } });\n * ```\n */\n call: HasAnyRequiredFields<InferRouteInput<TRoute>> extends true\n ? (input: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>\n : (input?: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>;\n};\n\n/**\n * Typed client for entire router\n */\nexport type Client<TRouter extends Router<any>> = {\n [K in keyof TRouter['routes']]: TRouter['routes'][K] extends RouteDef<any, any, any>\n ? RouteClient<TRouter['routes'][K]>\n : TRouter['routes'][K] extends Router<any>\n ? Client<TRouter['routes'][K]>\n : never;\n};","/**\n * Type-Safe RPC-Style Client with Structured Input API\n *\n * Provides full end-to-end type safety from server routes to client calls.\n * No metadata codegen required - method/path resolution happens at the proxy layer.\n *\n * @example\n * ```typescript\n * // Server\n * export const appRouter = defineRouter({\n * getUser: route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => { ... }),\n * createUser: route.post('/users')\n * .input({ body: Type.Object({ name: Type.String() }) })\n * .handler(async (c) => { ... }),\n * });\n *\n * export type AppRouter = typeof appRouter;\n *\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // ✅ GET (no body) - becomes GET /api/rpc/getUser?input={...}\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // ✅ POST (has body) - becomes POST /api/rpc/createUser\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n *\n * // ✅ With options (headers, cookies, interceptors)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .cookies({ session: 'xxx' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nimport { env } from '@spfn/core/config';\nimport { ErrorRegistry, errorRegistry as coreErrorRegistry } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport type { Router } from '@spfn/core/route';\nimport * as debugLogs from './debug-logs';\nimport { ApiError } from \"./errors\";\nimport {\n parseResponseBody,\n executeFetchWithTimeout,\n handleErrorResponse,\n buildCookieHeader,\n autoDetectServerCookies,\n} from './helpers';\nimport { RouteCallBuilder } from './builder';\nimport type { ApiConfig, CallOptions } from \"./types\";\nimport type { Client } from \"./builder\";\n\nconst apiLogger = logger.child('@spfn/core:api-client');\n\n// ============================================================================\n// Client Implementation\n// ============================================================================\n\n/**\n * Create type-safe RPC client\n *\n * No metadata required - the client sends routeName + input to the proxy,\n * and the proxy resolves the actual HTTP method and path from the router.\n *\n * @example\n * ```typescript\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // GET request (no body) - browser cacheable\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // POST request (has body)\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n * ```\n */\nexport function createApi<TRouter extends Router<any>>(\n config: ApiConfig = {}\n): Client<TRouter>\n{\n const {\n baseUrl = '/api/rpc',\n headers: defaultHeaders = {},\n timeout = 30000,\n fetch: customFetch = fetch,\n onRequest: globalOnRequest,\n onResponse: globalOnResponse,\n errorRegistry: errorRegistryConfig,\n debug = false,\n } = config;\n\n // Normalize errorRegistry: always include coreErrorRegistry\n const errorRegistry = Array.isArray(errorRegistryConfig)\n ? new ErrorRegistry([coreErrorRegistry, ...errorRegistryConfig])\n : errorRegistryConfig ?? coreErrorRegistry;\n\n if (debug)\n {\n apiLogger.debug('API client initialized', { baseUrl });\n }\n\n /**\n * Execute API call\n *\n * Determines GET vs POST based on body/formData presence:\n * - No body/formData → GET /api/rpc/{routeName}?input={...}\n * - Has body → POST /api/rpc/{routeName} with JSON body\n * - Has formData → POST /api/rpc/{routeName} with multipart/form-data\n */\n async function executeCall(\n routeName: string,\n input: any = {},\n options: CallOptions = {}\n ): Promise<any>\n {\n const hasBody = input.body !== undefined;\n const hasFormData = input.formData !== undefined && Object.keys(input.formData).length > 0;\n const method = (hasBody || hasFormData) ? 'POST' : 'GET';\n\n // Build full URL - handle SSR case where SPFN_APP_URL might not be set\n let appUrl = env.SPFN_APP_URL || '';\n\n // In SSR environment, if SPFN_APP_URL is not set, try to get host from request headers\n if (!appUrl && typeof window === 'undefined')\n {\n try\n {\n const { headers } = await import('next/headers');\n const headersList = await headers();\n const host = headersList.get('host');\n const protocol = headersList.get('x-forwarded-proto') || 'http';\n if (host)\n {\n appUrl = `${protocol}://${host}`;\n if (debug)\n {\n apiLogger.debug(`Auto-detected app URL from headers: ${appUrl}`);\n }\n }\n }\n catch\n {\n // Fallback: use relative URL and let fetch handle it\n if (debug)\n {\n apiLogger.warn('Could not determine app URL in SSR environment, using relative URL');\n }\n }\n }\n\n // Build URL based on method\n let fullUrl: string;\n if (method === 'GET')\n {\n // GET: encode input in query string\n const inputParam = encodeURIComponent(JSON.stringify(input));\n fullUrl = `${appUrl}${baseUrl}/${routeName}?input=${inputParam}`;\n }\n else\n {\n // POST: input goes in body\n fullUrl = `${appUrl}${baseUrl}/${routeName}`;\n }\n\n // Prepare headers\n // Note: Don't set Content-Type for formData - browser sets it with boundary\n const headers: Record<string, string> = {\n ...(hasFormData ? {} : { 'Content-Type': 'application/json' }),\n ...defaultHeaders,\n ...options.headers,\n };\n\n // Auto-detect server cookies and merge with user-provided cookies\n const autoDetectedCookies = await autoDetectServerCookies();\n const cookiesToSend = {\n ...autoDetectedCookies,\n ...(options.cookies || {}),\n };\n\n // Add Cookie header if we have cookies to send\n if (Object.keys(cookiesToSend).length > 0)\n {\n headers['Cookie'] = buildCookieHeader(cookiesToSend);\n }\n\n // Log cookie auto-detection if debug enabled\n if (debug && Object.keys(autoDetectedCookies).length > 0)\n {\n const cookieArray = Object.entries(autoDetectedCookies).map(([name, value]) => ({ name, value }));\n debugLogs.logCookieAutoDetection(apiLogger, cookieArray);\n }\n\n // Build request init\n const requestInit: RequestInit = {\n method,\n headers,\n ...options.fetchOptions,\n };\n\n // Add body for POST\n if (method === 'POST')\n {\n if (hasFormData)\n {\n // Build FormData for file uploads\n const formData = new FormData();\n\n // Add non-formData fields (params, query, headers, cookies) as JSON metadata\n const metadata: Record<string, any> = {};\n if (input.params) metadata.params = input.params;\n if (input.query) metadata.query = input.query;\n if (input.headers) metadata.headers = input.headers;\n if (input.cookies) metadata.cookies = input.cookies;\n\n if (Object.keys(metadata).length > 0)\n {\n formData.append('__metadata', JSON.stringify(metadata));\n }\n\n // Add formData fields\n for (const [key, value] of Object.entries(input.formData))\n {\n if (value instanceof File)\n {\n formData.append(key, value);\n }\n else if (Array.isArray(value))\n {\n // Handle array of files or values\n for (const item of value)\n {\n if (item instanceof File)\n {\n formData.append(key, item);\n }\n else\n {\n formData.append(key, String(item));\n }\n }\n }\n else if (value !== undefined && value !== null)\n {\n formData.append(key, String(value));\n }\n }\n\n requestInit.body = formData;\n }\n else\n {\n requestInit.body = JSON.stringify(input);\n }\n }\n\n // Execute request interceptors\n let init = requestInit;\n if (globalOnRequest)\n {\n init = await globalOnRequest(fullUrl, init);\n }\n if (options.onRequest)\n {\n init = await options.onRequest(fullUrl, init);\n }\n\n if (debug)\n {\n debugLogs.logRequest(apiLogger, routeName, method, fullUrl, !!init.body);\n }\n\n // Execute fetch with timeout\n let response: Response;\n let body: any;\n\n try\n {\n response = await executeFetchWithTimeout(fullUrl, init, timeout, customFetch);\n\n // Parse response\n body = await parseResponseBody(response);\n\n // Execute global + local response interceptors\n if (globalOnResponse)\n {\n const result = await globalOnResponse(response, body);\n response = result.response;\n body = result.body;\n }\n if (options.onResponse)\n {\n const result = await options.onResponse(response, body);\n response = result.response;\n body = result.body;\n }\n\n if (debug)\n {\n debugLogs.logResponse(apiLogger, routeName, response.status, !!body);\n }\n }\n catch (error)\n {\n // Handle timeout specifically\n if (error instanceof Error && error.name === 'AbortError')\n {\n apiLogger.error('Request timeout', {\n route: routeName,\n method,\n url: fullUrl,\n timeout,\n });\n\n throw new ApiError(\n `Request timeout after ${timeout}ms`,\n 408,\n fullUrl,\n undefined,\n 'timeout'\n );\n }\n\n // Network error\n const errorMessage = error instanceof Error ? error.message : 'Network error';\n apiLogger.error('Network error', {\n route: routeName,\n method,\n url: fullUrl,\n error: errorMessage,\n errorName: error instanceof Error ? error.name : 'unknown',\n });\n\n throw new ApiError(\n errorMessage,\n 0,\n fullUrl,\n undefined,\n 'network'\n );\n }\n\n // Handle error responses\n if (!response.ok)\n {\n await handleErrorResponse(response, body, fullUrl, errorRegistry, debug, apiLogger);\n }\n\n return body;\n }\n\n /**\n * Build client proxy\n *\n * Every property access returns a RouteCallBuilder.\n * Nested routers are supported via dot notation in routeName.\n */\n function buildProxy(prefix = ''): any\n {\n return new Proxy(\n {},\n {\n get(_target, prop: string)\n {\n const currentPath = prefix ? `${prefix}.${prop}` : prop;\n\n // Return RouteCallBuilder that can either be called or chained\n return new RouteCallBuilder(\n (input: any, options: CallOptions) => executeCall(currentPath, input, options),\n currentPath\n );\n },\n }\n );\n }\n\n return buildProxy() as Client<TRouter>;\n}"]}
1
+ {"version":3,"sources":["../../src/nextjs/client/debug-logs.ts","../../src/nextjs/client/errors.ts","../../src/nextjs/shared.ts","../../src/nextjs/client/helpers.ts","../../src/nextjs/client/builder.ts","../../src/nextjs/client/core.ts"],"names":["logger","errorRegistry","coreErrorRegistry","headers"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Debug logging utilities for API client\n * Separates debug logging logic from main client code for better maintainability\n */\nimport type { Logger } from '@spfn/core/logger';\n\nexport function logCookieAutoDetection(\n logger: Logger,\n cookies: Array<{ name: string; value: string }>\n): void\n{\n logger.debug('Auto-detected server environment, forwarding cookies', {\n cookieCount: cookies.length,\n cookieNames: cookies.map(c => c.name),\n });\n}\n\nexport function logRequest(\n logger: Logger,\n routeName: string,\n method: string,\n url: string,\n hasBody: boolean\n): void\n{\n logger.debug('→ Request', {\n route: routeName,\n method,\n url,\n hasBody,\n });\n}\n\nexport function logResponse(\n logger: Logger,\n routeName: string,\n status: number,\n hasBody: boolean\n): void\n{\n logger.debug('← Response', {\n route: routeName,\n status,\n hasBody,\n });\n}\n\nexport function logErrorResponse(\n logger: Logger,\n status: number,\n body: any\n): void\n{\n logger.debug('Error response received', {\n status,\n hasBody: !!body,\n bodyType: typeof body,\n hasTypeField: body && typeof body === 'object' && '__type' in body,\n typeValue: body?.__type,\n });\n}\n\nexport function logErrorDeserializationAttempt(\n logger: Logger,\n errorType: string,\n registeredTypes: string[]\n): void\n{\n logger.debug('Attempting error deserialization', {\n errorType,\n hasRegistry: true,\n registeredTypes,\n });\n}\n\nexport function logErrorDeserializationSuccess(\n logger: Logger,\n error: Error | null\n): void\n{\n logger.debug('Error deserialized successfully', {\n errorName: error?.name,\n errorConstructor: error?.constructor.name,\n message: error?.message,\n });\n}\n\nexport function logErrorDeserializationFailure(\n logger: Logger,\n error: unknown\n): void\n{\n logger.debug('Deserialization failed', {\n errorName: error instanceof Error ? error.name : 'unknown',\n errorMessage: error instanceof Error ? error.message : String(error),\n });\n}\n\nexport function logErrorDeserializationSkipped(\n logger: Logger,\n errorRegistry: any,\n body: any\n): void\n{\n const reason = !errorRegistry\n ? 'no registry'\n : !body\n ? 'no body'\n : typeof body !== 'object'\n ? 'body not object'\n : !('__type' in body)\n ? 'no __type field'\n : 'unknown';\n\n logger.debug('Skipping error deserialization', { reason });\n}\n\nexport function logThrowingDeserializedError(\n logger: Logger,\n error: Error\n): void\n{\n logger.debug('Throwing deserialized error', {\n errorName: error.name,\n errorConstructorName: error.constructor.name,\n prototype: Object.getPrototypeOf(error).constructor.name,\n });\n}","// ============================================================================\n// Client Error\n// ============================================================================\n\n/**\n * Typed client error\n */\nexport class ApiError extends Error\n{\n constructor(\n message: string,\n public readonly status: number,\n public readonly url: string,\n public readonly response?: unknown,\n public readonly errorType?: 'http' | 'network' | 'timeout'\n )\n {\n super(message);\n this.name = 'ApiError';\n }\n}","/**\n * Shared utilities for Next.js client and proxy modules\n *\n * Contains common functions used by both client and proxy to avoid code duplication.\n */\n\n/**\n * Build URL with path parameters replaced\n *\n * @example\n * buildUrlWithParams('/users/:id/posts/:postId', { id: '123', postId: '456' })\n * // Returns: '/users/123/posts/456'\n */\nexport function buildUrlWithParams(path: string, params: Record<string, any>): string\n{\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n }\n\n return url;\n}\n\n/**\n * Build query string from object\n *\n * @example\n * buildQueryString({ page: '1', limit: '10', tags: ['foo', 'bar'] })\n * // Returns: '?page=1&limit=10&tags=foo&tags=bar'\n */\nexport function buildQueryString(query: Record<string, any>): string\n{\n if (Object.keys(query).length === 0)\n {\n return '';\n }\n\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => searchParams.append(key, String(v)));\n }\n else\n {\n searchParams.append(key, String(value));\n }\n }\n\n return `?${searchParams.toString()}`;\n}\n\n/**\n * Build Cookie header string from cookies object\n *\n * @example\n * buildCookieHeader({ session: 'abc123', theme: 'dark' })\n * // Returns: 'session=abc123; theme=dark'\n */\nexport function buildCookieHeader(cookies: Record<string, string>): string\n{\n return Object.entries(cookies)\n .map(([key, value]) => `${key}=${value}`)\n .join('; ');\n}\n\n/**\n * Parse response body based on content type\n *\n * Handles:\n * - 204 No Content: returns null (no body expected)\n * - application/json: parses JSON body\n * - Other content types: returns raw text\n */\nexport async function parseResponseBody(response: Response): Promise<any>\n{\n // 204 No Content has no body\n if (response.status === 204)\n {\n return null;\n }\n\n const contentType = response.headers.get('content-type');\n\n if (contentType?.includes('application/json'))\n {\n const text = await response.text();\n return text ? JSON.parse(text) : null;\n }\n else\n {\n return await response.text();\n }\n}","import type { Logger } from '@spfn/core/logger';\nimport type { ErrorRegistry } from '@spfn/core/errors';\nimport { ApiError } from './errors';\nimport * as debugLogs from './debug-logs';\n\n// Re-export shared utilities\nexport { buildCookieHeader, parseResponseBody } from '../shared';\n\n/**\n * Auto-detect cookies from Next.js server environment\n * Returns empty object if not in server environment or if cookies are not accessible\n */\nexport async function autoDetectServerCookies(): Promise<Record<string, string>>\n{\n try\n {\n // Next.js cookies() API is only available in server environment\n const { cookies } = await import('next/headers');\n const cookieStore = await cookies();\n const allCookies = cookieStore.getAll();\n\n return Object.fromEntries(\n allCookies.map(cookie => [cookie.name, cookie.value])\n );\n }\n catch (error)\n {\n // Client environment or cookies not accessible\n // Browser automatically sends cookies in client components\n return {};\n }\n}\n\n/**\n * Execute fetch with timeout and abort controller\n */\nexport async function executeFetchWithTimeout(\n url: string,\n init: RequestInit,\n timeout: number,\n customFetch: typeof fetch = fetch\n): Promise<Response>\n{\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try\n {\n const response = await customFetch(url, {\n ...init,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n return response;\n }\n catch (error)\n {\n clearTimeout(timeoutId);\n throw error;\n }\n}\n\n/**\n * Handle error response with deserialization support\n * Attempts to deserialize custom errors if errorRegistry is provided\n * Falls back to ApiError if deserialization fails or is not available\n */\nexport async function handleErrorResponse(\n response: Response,\n body: any,\n fullUrl: string,\n errorRegistry: ErrorRegistry | undefined,\n debug: boolean,\n logger: Logger\n): Promise<never>\n{\n if (debug)\n {\n debugLogs.logErrorResponse(logger, response.status, body);\n }\n\n // Try to deserialize error if registry is provided\n let deserializedError: Error | null = null;\n\n if (errorRegistry && body && typeof body === 'object' && '__type' in body)\n {\n if (debug)\n {\n debugLogs.logErrorDeserializationAttempt(logger, body.__type, errorRegistry.getRegisteredTypes());\n }\n\n try\n {\n deserializedError = errorRegistry.deserialize(body as any);\n\n if (debug)\n {\n debugLogs.logErrorDeserializationSuccess(logger, deserializedError);\n }\n }\n catch (deserializeError)\n {\n // Deserialization itself failed (type not found, invalid data, etc.)\n if (debug)\n {\n debugLogs.logErrorDeserializationFailure(logger, deserializeError);\n }\n // Fall through to ApiError below\n }\n }\n else if (debug)\n {\n debugLogs.logErrorDeserializationSkipped(logger, errorRegistry, body);\n }\n\n // If deserialization succeeded, throw the deserialized error\n if (deserializedError)\n {\n if (debug)\n {\n debugLogs.logThrowingDeserializedError(logger, deserializedError);\n }\n\n throw deserializedError;\n }\n\n // Fallback to generic ApiError\n if (response.status === 404 && process.env.NODE_ENV !== 'production')\n {\n logger.warn(\n '\\n⚠️ 404 Not Found\\n\\n' +\n 'Check the following:\\n' +\n ' 1. Routes are registered in server.config.ts:\\n' +\n ' → defineServerConfig().routes(appRouter)\\n' +\n ' 2. Delete .spfn cache if you recently added new routes:\\n' +\n ' → rm -rf .spfn\\n'\n );\n }\n\n throw new ApiError(\n body?.message || `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n fullUrl,\n body,\n 'http'\n );\n}","// ============================================================================\n// Route Call Builder (Structured Input API)\n// ============================================================================\n\nimport type { RouteDef, Router } from \"@spfn/core/route\";\nimport type {\n CallOptions,\n InferRouteInput,\n InferRouteOutput,\n RequestInterceptor,\n ResponseInterceptor,\n} from \"./types\";\n\n/**\n * Pick only non-empty fields from StructuredInput\n *\n * This removes fields that are empty objects `{}` from the input type,\n * so users only need to provide fields that are actually defined in the route.\n */\ntype PickNonEmpty<T> = {\n [K in keyof T as T[K] extends Record<string, never> ? never : K]: T[K];\n};\n\n/**\n * Make fields that can be undefined into optional fields\n *\n * When a field is defined as `Type.Optional(Type.Object({...}))`,\n * the resulting type is `T | undefined`. This utility converts such fields\n * into proper optional fields (`field?: T`) so users don't need to pass them.\n */\ntype MakeOptionalIfUndefinable<T> =\n // Required fields (undefined is not assignable)\n { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }\n // Optional fields (undefined is assignable)\n & { [K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined> };\n\n/**\n * Clean structured input - only include fields that have actual schema,\n * and make fields optional if they accept undefined\n */\ntype CleanStructuredInput<TInput> = MakeOptionalIfUndefinable<PickNonEmpty<TInput>>;\n\n/**\n * Check if input has any required fields\n *\n * Returns false if all fields are optional (i.e., {} is assignable to the input type)\n */\ntype HasAnyRequiredFields<TInput> = {} extends CleanStructuredInput<TInput> ? false : true;\n\n/**\n * Route call builder with structured input API\n *\n * Input is structured with explicit params, query, body fields\n * that match the server-side route definition.\n *\n * @example\n * ```typescript\n * // GET /users/:id - params only\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // GET /users/:id?include=posts - params + query\n * const user = await api.getUser.call({\n * params: { id: '1' },\n * query: { include: 'posts' }\n * });\n *\n * // POST /users - body only\n * const user = await api.createUser.call({\n * body: { name: 'John', email: 'john@example.com' }\n * });\n *\n * // PUT /users/:id - params + body\n * const user = await api.updateUser.call({\n * params: { id: '1' },\n * body: { name: 'Jane' }\n * });\n *\n * // With options (headers, cookies, Next.js caching)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nexport class RouteCallBuilder<\n TInput,\n TOutput\n>\n{\n private _headers?: Record<string, string>;\n private _cookies?: Record<string, string>;\n private _fetchOptions?: RequestInit;\n private _onRequest?: RequestInterceptor;\n private _onResponse?: ResponseInterceptor;\n\n constructor(\n private readonly executor: (input: any, options: CallOptions) => Promise<TOutput>,\n private readonly routeName: string\n ) {}\n\n /**\n * Clone builder\n */\n private clone(): RouteCallBuilder<TInput, TOutput>\n {\n const builder = new RouteCallBuilder<TInput, TOutput>(\n this.executor,\n this.routeName\n );\n builder._headers = this._headers;\n builder._cookies = this._cookies;\n builder._fetchOptions = this._fetchOptions;\n builder._onRequest = this._onRequest;\n builder._onResponse = this._onResponse;\n return builder;\n }\n\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._headers = { ...this._headers, ...headers };\n return builder;\n }\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._cookies = { ...this._cookies, ...cookies };\n return builder;\n }\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._fetchOptions = { ...this._fetchOptions, ...options };\n return builder;\n }\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onRequest = interceptor;\n return builder;\n }\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteCallBuilder<TInput, TOutput>\n {\n const builder = this.clone();\n builder._onResponse = interceptor;\n return builder;\n }\n\n /**\n * Execute the API call with structured input\n *\n * Input structure matches the server-side route definition:\n * - params: Path parameters (e.g., { id: '123' } for /users/:id)\n * - query: Query string parameters\n * - body: Request body (for POST, PUT, PATCH)\n */\n call(input?: CleanStructuredInput<TInput>): Promise<TOutput>\n {\n const options: CallOptions = {};\n\n if (this._headers)\n {\n options.headers = this._headers;\n }\n\n if (this._cookies)\n {\n options.cookies = this._cookies;\n }\n\n if (this._fetchOptions)\n {\n options.fetchOptions = this._fetchOptions;\n }\n\n if (this._onRequest)\n {\n options.onRequest = this._onRequest;\n }\n\n if (this._onResponse)\n {\n options.onResponse = this._onResponse;\n }\n\n return this.executor(input || {}, options);\n }\n}\n\n/**\n * Individual route client with structured input API\n */\nexport type RouteClient<TRoute extends RouteDef<any, any>> = {\n /**\n * Set request headers\n */\n headers(headers: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set cookies\n */\n cookies(cookies: Record<string, string>): RouteClient<TRoute>;\n\n /**\n * Set Next.js fetch options\n */\n fetchOptions(options: RequestInit & { next?: { revalidate?: number | false; tags?: string[] } }): RouteClient<TRoute>;\n\n /**\n * Set request interceptor\n */\n onRequest(interceptor: RequestInterceptor): RouteClient<TRoute>;\n\n /**\n * Set response interceptor\n */\n onResponse(interceptor: ResponseInterceptor): RouteClient<TRoute>;\n\n /**\n * Execute the API call with structured input\n *\n * @example\n * ```typescript\n * // GET /users/:id\n * api.getUser.call({ params: { id: '123' } });\n *\n * // PUT /users/:id\n * api.updateUser.call({ params: { id: '123' }, body: { name: 'Jane' } });\n * ```\n */\n call: HasAnyRequiredFields<InferRouteInput<TRoute>> extends true\n ? (input: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>\n : (input?: CleanStructuredInput<InferRouteInput<TRoute>>) => Promise<InferRouteOutput<TRoute>>;\n};\n\n/**\n * Typed client for entire router\n */\nexport type Client<TRouter extends Router<any>> = {\n [K in keyof TRouter['routes']]: TRouter['routes'][K] extends RouteDef<any, any, any>\n ? RouteClient<TRouter['routes'][K]>\n : TRouter['routes'][K] extends Router<any>\n ? Client<TRouter['routes'][K]>\n : never;\n};","/**\n * Type-Safe RPC-Style Client with Structured Input API\n *\n * Provides full end-to-end type safety from server routes to client calls.\n * No metadata codegen required - method/path resolution happens at the proxy layer.\n *\n * @example\n * ```typescript\n * // Server\n * export const appRouter = defineRouter({\n * getUser: route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => { ... }),\n * createUser: route.post('/users')\n * .input({ body: Type.Object({ name: Type.String() }) })\n * .handler(async (c) => { ... }),\n * });\n *\n * export type AppRouter = typeof appRouter;\n *\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // ✅ GET (no body) - becomes GET /api/rpc/getUser?input={...}\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // ✅ POST (has body) - becomes POST /api/rpc/createUser\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n *\n * // ✅ With options (headers, cookies, interceptors)\n * const user = await api.getUser\n * .headers({ 'X-Custom': 'value' })\n * .cookies({ session: 'xxx' })\n * .fetchOptions({ next: { revalidate: 60 } })\n * .call({ params: { id: '1' } });\n * ```\n */\nimport { env } from '@spfn/core/config';\nimport { ErrorRegistry, errorRegistry as coreErrorRegistry } from '@spfn/core/errors';\nimport { logger } from '@spfn/core/logger';\nimport type { Router } from '@spfn/core/route';\nimport * as debugLogs from './debug-logs';\nimport { ApiError } from \"./errors\";\nimport {\n parseResponseBody,\n executeFetchWithTimeout,\n handleErrorResponse,\n buildCookieHeader,\n autoDetectServerCookies,\n} from './helpers';\nimport { RouteCallBuilder } from './builder';\nimport type { ApiConfig, CallOptions } from \"./types\";\nimport type { Client } from \"./builder\";\n\nconst apiLogger = logger.child('@spfn/core:api-client');\n\n// ============================================================================\n// Client Implementation\n// ============================================================================\n\n/**\n * Create type-safe RPC client\n *\n * No metadata required - the client sends routeName + input to the proxy,\n * and the proxy resolves the actual HTTP method and path from the router.\n *\n * @example\n * ```typescript\n * // Client - no metadata needed!\n * const api = createApi<AppRouter>();\n *\n * // GET request (no body) - browser cacheable\n * const user = await api.getUser.call({ params: { id: '1' } });\n *\n * // POST request (has body)\n * const newUser = await api.createUser.call({ body: { name: 'John' } });\n * ```\n */\nexport function createApi<TRouter extends Router<any>>(\n config: ApiConfig = {}\n): Client<TRouter>\n{\n const {\n baseUrl = '/api/rpc',\n headers: defaultHeaders = {},\n timeout = env.SERVER_TIMEOUT,\n fetch: customFetch = fetch,\n onRequest: globalOnRequest,\n onResponse: globalOnResponse,\n errorRegistry: errorRegistryConfig,\n debug = false,\n } = config;\n\n // Normalize errorRegistry: always include coreErrorRegistry\n const errorRegistry = Array.isArray(errorRegistryConfig)\n ? new ErrorRegistry([coreErrorRegistry, ...errorRegistryConfig])\n : errorRegistryConfig ?? coreErrorRegistry;\n\n if (debug)\n {\n apiLogger.debug('API client initialized', { baseUrl });\n }\n\n /**\n * Execute API call\n *\n * Determines GET vs POST based on body/formData presence:\n * - No body/formData → GET /api/rpc/{routeName}?input={...}\n * - Has body → POST /api/rpc/{routeName} with JSON body\n * - Has formData → POST /api/rpc/{routeName} with multipart/form-data\n */\n async function executeCall(\n routeName: string,\n input: any = {},\n options: CallOptions = {}\n ): Promise<any>\n {\n const hasBody = input.body !== undefined;\n const hasFormData = input.formData !== undefined && Object.keys(input.formData).length > 0;\n const method = (hasBody || hasFormData) ? 'POST' : 'GET';\n\n // Build full URL - handle SSR case where SPFN_APP_URL might not be set\n let appUrl = env.SPFN_APP_URL || '';\n\n // In SSR environment, if SPFN_APP_URL is not set, try to get host from request headers\n if (!appUrl && typeof window === 'undefined')\n {\n try\n {\n const { headers } = await import('next/headers');\n const headersList = await headers();\n const host = headersList.get('host');\n const protocol = headersList.get('x-forwarded-proto') || 'http';\n if (host)\n {\n appUrl = `${protocol}://${host}`;\n if (debug)\n {\n apiLogger.debug(`Auto-detected app URL from headers: ${appUrl}`);\n }\n }\n }\n catch\n {\n // Fallback: use relative URL and let fetch handle it\n if (debug)\n {\n apiLogger.warn('Could not determine app URL in SSR environment, using relative URL');\n }\n }\n }\n\n // Build URL based on method\n let fullUrl: string;\n if (method === 'GET')\n {\n // GET: encode input in query string\n const inputParam = encodeURIComponent(JSON.stringify(input));\n fullUrl = `${appUrl}${baseUrl}/${routeName}?input=${inputParam}`;\n }\n else\n {\n // POST: input goes in body\n fullUrl = `${appUrl}${baseUrl}/${routeName}`;\n }\n\n // Prepare headers\n // Note: Don't set Content-Type for formData - browser sets it with boundary\n const headers: Record<string, string> = {\n ...(hasFormData ? {} : { 'Content-Type': 'application/json' }),\n ...defaultHeaders,\n ...options.headers,\n };\n\n // Auto-detect server cookies and merge with user-provided cookies\n const autoDetectedCookies = await autoDetectServerCookies();\n const cookiesToSend = {\n ...autoDetectedCookies,\n ...(options.cookies || {}),\n };\n\n // Add Cookie header if we have cookies to send\n if (Object.keys(cookiesToSend).length > 0)\n {\n headers['Cookie'] = buildCookieHeader(cookiesToSend);\n }\n\n // Log cookie auto-detection if debug enabled\n if (debug && Object.keys(autoDetectedCookies).length > 0)\n {\n const cookieArray = Object.entries(autoDetectedCookies).map(([name, value]) => ({ name, value }));\n debugLogs.logCookieAutoDetection(apiLogger, cookieArray);\n }\n\n // Build request init\n const requestInit: RequestInit = {\n method,\n headers,\n ...options.fetchOptions,\n };\n\n // Add body for POST\n if (method === 'POST')\n {\n if (hasFormData)\n {\n // Build FormData for file uploads\n const formData = new FormData();\n\n // Add non-formData fields (params, query, headers, cookies) as JSON metadata\n const metadata: Record<string, any> = {};\n if (input.params) metadata.params = input.params;\n if (input.query) metadata.query = input.query;\n if (input.headers) metadata.headers = input.headers;\n if (input.cookies) metadata.cookies = input.cookies;\n\n if (Object.keys(metadata).length > 0)\n {\n formData.append('__metadata', JSON.stringify(metadata));\n }\n\n // Add formData fields\n for (const [key, value] of Object.entries(input.formData))\n {\n if (value instanceof File)\n {\n formData.append(key, value);\n }\n else if (Array.isArray(value))\n {\n // Handle array of files or values\n for (const item of value)\n {\n if (item instanceof File)\n {\n formData.append(key, item);\n }\n else\n {\n formData.append(key, String(item));\n }\n }\n }\n else if (value !== undefined && value !== null)\n {\n formData.append(key, String(value));\n }\n }\n\n requestInit.body = formData;\n }\n else\n {\n requestInit.body = JSON.stringify(input);\n }\n }\n\n // Execute request interceptors\n let init = requestInit;\n if (globalOnRequest)\n {\n init = await globalOnRequest(fullUrl, init);\n }\n if (options.onRequest)\n {\n init = await options.onRequest(fullUrl, init);\n }\n\n if (debug)\n {\n debugLogs.logRequest(apiLogger, routeName, method, fullUrl, !!init.body);\n }\n\n // Execute fetch with timeout\n let response: Response;\n let body: any;\n\n try\n {\n response = await executeFetchWithTimeout(fullUrl, init, timeout, customFetch);\n\n // Parse response\n body = await parseResponseBody(response);\n\n // Execute global + local response interceptors\n if (globalOnResponse)\n {\n const result = await globalOnResponse(response, body);\n response = result.response;\n body = result.body;\n }\n if (options.onResponse)\n {\n const result = await options.onResponse(response, body);\n response = result.response;\n body = result.body;\n }\n\n if (debug)\n {\n debugLogs.logResponse(apiLogger, routeName, response.status, !!body);\n }\n }\n catch (error)\n {\n // Handle timeout specifically\n if (error instanceof Error && error.name === 'AbortError')\n {\n apiLogger.error('Request timeout', {\n route: routeName,\n method,\n url: fullUrl,\n timeout,\n });\n\n throw new ApiError(\n `Request timeout after ${timeout}ms`,\n 408,\n fullUrl,\n undefined,\n 'timeout'\n );\n }\n\n // Network error\n const errorMessage = error instanceof Error ? error.message : 'Network error';\n apiLogger.error('Network error', {\n route: routeName,\n method,\n url: fullUrl,\n error: errorMessage,\n errorName: error instanceof Error ? error.name : 'unknown',\n });\n\n throw new ApiError(\n errorMessage,\n 0,\n fullUrl,\n undefined,\n 'network'\n );\n }\n\n // Handle error responses\n if (!response.ok)\n {\n await handleErrorResponse(response, body, fullUrl, errorRegistry, debug, apiLogger);\n }\n\n return body;\n }\n\n /**\n * Build client proxy\n *\n * Every property access returns a RouteCallBuilder.\n * Nested routers are supported via dot notation in routeName.\n */\n function buildProxy(prefix = ''): any\n {\n return new Proxy(\n {},\n {\n get(_target, prop: string)\n {\n const currentPath = prefix ? `${prefix}.${prop}` : prop;\n\n // Return RouteCallBuilder that can either be called or chained\n return new RouteCallBuilder(\n (input: any, options: CallOptions) => executeCall(currentPath, input, options),\n currentPath\n );\n },\n }\n );\n }\n\n return buildProxy() as Client<TRouter>;\n}"]}