@spfn/core 0.2.0-beta.35 → 0.2.0-beta.37

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.
@@ -994,6 +994,78 @@ interface TransactionalOptions {
994
994
  */
995
995
  declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
996
996
 
997
+ /**
998
+ * Transaction runner options
999
+ */
1000
+ interface RunInTransactionOptions {
1001
+ /**
1002
+ * Slow transaction warning threshold in milliseconds
1003
+ * @default 1000 (1 second)
1004
+ */
1005
+ slowThreshold?: number;
1006
+ /**
1007
+ * Enable transaction logging
1008
+ * @default true
1009
+ */
1010
+ enableLogging?: boolean;
1011
+ /**
1012
+ * Transaction timeout in milliseconds
1013
+ *
1014
+ * Sets PostgreSQL `statement_timeout` to enforce database-level timeout.
1015
+ * If transaction exceeds this duration, PostgreSQL will automatically cancel
1016
+ * the query and rollback the transaction, ensuring data consistency.
1017
+ *
1018
+ * Behavior:
1019
+ * - `timeout: 0` - Disables timeout (unlimited execution time)
1020
+ * - `timeout: null` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1021
+ * - `timeout: undefined` - Uses default (30s or TRANSACTION_TIMEOUT env var)
1022
+ * - `timeout: N` - Sets timeout to N milliseconds (1 to 2147483647)
1023
+ *
1024
+ * Note: Timeout is only applied to root transactions. Nested transactions
1025
+ * (SAVEPOINTs) inherit the timeout from the outer transaction.
1026
+ *
1027
+ * @default 30000 (30 seconds) or TRANSACTION_TIMEOUT environment variable
1028
+ *
1029
+ * @example
1030
+ * ```typescript
1031
+ * // Use default timeout (30s)
1032
+ * await runInTransaction(callback);
1033
+ *
1034
+ * // Disable timeout for long-running operations
1035
+ * await runInTransaction(callback, { timeout: 0 });
1036
+ *
1037
+ * // Set custom timeout (60s)
1038
+ * await runInTransaction(callback, { timeout: 60000 });
1039
+ * ```
1040
+ */
1041
+ timeout?: number;
1042
+ /**
1043
+ * Context string for logging (e.g., 'migration:add-user', 'script:cleanup')
1044
+ * @default 'transaction'
1045
+ */
1046
+ context?: string;
1047
+ }
1048
+ /**
1049
+ * Run a callback function within a database transaction
1050
+ *
1051
+ * Automatically manages transaction lifecycle:
1052
+ * - Commits on success
1053
+ * - Rolls back on error
1054
+ * - Tracks execution time
1055
+ * - Warns about slow transactions
1056
+ * - Enforces timeout if configured
1057
+ *
1058
+ * Errors are propagated to the caller without modification.
1059
+ * Caller is responsible for error handling and conversion.
1060
+ *
1061
+ * @param callback - Function to execute within transaction
1062
+ * @param options - Transaction options
1063
+ * @returns Result of callback function
1064
+ * @throws TransactionError if database not initialized or timeout exceeded
1065
+ * @throws Any error thrown by callback function
1066
+ */
1067
+ declare function runInTransaction<T>(callback: (tx: TransactionDB) => Promise<T>, options?: RunInTransactionOptions): Promise<T>;
1068
+
997
1069
  /**
998
1070
  * PostgreSQL Error Conversion Utilities
999
1071
  *
@@ -1602,4 +1674,4 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
1602
1674
  protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
1603
1675
  }
1604
1676
 
1605
- export { type AfterCommitCallback, BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1677
+ export { type AfterCommitCallback, BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type RunInTransactionOptions, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
package/dist/db/index.js CHANGED
@@ -11,7 +11,7 @@ import { bigserial, timestamp, bigint, uuid as uuid$1, text, jsonb, pgSchema } f
11
11
  import { AsyncLocalStorage } from 'async_hooks';
12
12
  import { createMiddleware } from 'hono/factory';
13
13
  import { randomUUID } from 'crypto';
14
- import { count as count$1, sql, eq, and } from 'drizzle-orm';
14
+ import { sql, count as count$1, eq, and } from 'drizzle-orm';
15
15
 
16
16
  // src/db/manager/factory.ts
17
17
  function parseUniqueViolation(message) {
@@ -1918,6 +1918,6 @@ var BaseRepository = class {
1918
1918
  }
1919
1919
  };
1920
1920
 
1921
- export { BaseRepository, RepositoryError, Transactional, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1921
+ export { BaseRepository, RepositoryError, Transactional, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
1922
1922
  //# sourceMappingURL=index.js.map
1923
1923
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  import { E as EventRouterDef, I as InferEventNames } from '../../router-Di7ENoah.js';
2
- import { k as SSESubscribeOptions, m as SSEUnsubscribe, l as SSEConnectionState, h as SSEClientConfig } from '../../types-BUmrohqx.js';
2
+ import { k as SSESubscribeOptions, m as SSEUnsubscribe, l as SSEConnectionState, h as SSEClientConfig } from '../../types-DKQ90YL7.js';
3
3
  import '@sinclair/typebox';
4
4
  import 'hono';
5
5
 
@@ -19,8 +19,10 @@ function createSSEClient(config = {}) {
19
19
  let state = "closed";
20
20
  let reconnectAttempts = 0;
21
21
  let reconnectTimer = null;
22
+ let activeOnClose;
22
23
  function subscribe(options) {
23
- const { events, handlers, onOpen, onError, onReconnect } = options;
24
+ const { events, handlers, onOpen, onError, onReconnect, onClose } = options;
25
+ activeOnClose = onClose;
24
26
  const eventNames = events;
25
27
  function connect() {
26
28
  state = "connecting";
@@ -86,7 +88,10 @@ function createSSEClient(config = {}) {
86
88
  if (originalOnError) {
87
89
  originalOnError(error);
88
90
  }
89
- if (reconnect && currentEs.readyState === EventSource.CLOSED) {
91
+ if (reconnect && acquireToken) {
92
+ currentEs.close();
93
+ attemptReconnect(onReconnectCb);
94
+ } else if (reconnect && currentEs.readyState === EventSource.CLOSED) {
90
95
  attemptReconnect(onReconnectCb);
91
96
  }
92
97
  };
@@ -96,6 +101,8 @@ function createSSEClient(config = {}) {
96
101
  return;
97
102
  }
98
103
  if (maxReconnectAttempts > 0 && reconnectAttempts >= maxReconnectAttempts) {
104
+ state = "closed";
105
+ onClose?.();
99
106
  return;
100
107
  }
101
108
  reconnectAttempts++;
@@ -115,6 +122,7 @@ function createSSEClient(config = {}) {
115
122
  eventSource = null;
116
123
  }
117
124
  state = "closed";
125
+ onClose?.();
118
126
  };
119
127
  }
120
128
  function getState() {
@@ -130,6 +138,7 @@ function createSSEClient(config = {}) {
130
138
  eventSource = null;
131
139
  }
132
140
  state = "closed";
141
+ activeOnClose?.();
133
142
  }
134
143
  return {
135
144
  subscribe,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/event/sse/client.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["/**\n * SSE Client\n *\n * Type-safe EventSource wrapper for event subscription\n *\n * @example\n * ```typescript\n * import { createSSEClient } from '@spfn/core/event/sse/client';\n * import type { EventRouter } from '@/server/events';\n *\n * // Uses defaults: NEXT_PUBLIC_SPFN_API_URL + /events/stream\n * const client = createSSEClient<EventRouter>();\n *\n * // Or with custom host/pathname\n * const client = createSSEClient<EventRouter>({\n * host: 'https://api.example.com',\n * pathname: '/sse',\n * });\n *\n * // With token authentication (recommended: use createAuthSSEClient)\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * const client = createAuthSSEClient<EventRouter>();\n *\n * const unsubscribe = client.subscribe({\n * events: ['userCreated', 'orderPlaced'],\n * handlers: {\n * userCreated: (payload) => console.log('User:', payload.userId),\n * orderPlaced: (payload) => console.log('Order:', payload.orderId),\n * },\n * });\n *\n * // Later: cleanup\n * unsubscribe();\n * ```\n */\n\nimport type { EventRouterDef, InferEventNames } from '../router';\nimport type {\n SSEClientConfig,\n SSESubscribeOptions,\n SSEUnsubscribe,\n SSEConnectionState,\n SSEMessage,\n} from './types';\n\n/**\n * SSE Client instance\n */\nexport interface SSEClient<TRouter extends EventRouterDef<any>>\n{\n /**\n * Subscribe to events\n */\n subscribe(options: SSESubscribeOptions<TRouter>): SSEUnsubscribe;\n\n /**\n * Get current connection state\n */\n getState(): SSEConnectionState;\n\n /**\n * Close all connections\n */\n close(): void;\n}\n\n/**\n * Default SSE configuration\n */\nconst SSE_DEFAULTS = {\n host: typeof process !== 'undefined'\n ? (process.env.NEXT_PUBLIC_SPFN_API_URL || 'http://localhost:8790')\n : 'http://localhost:8790',\n pathname: '/events/stream',\n} as const;\n\n/**\n * Create type-safe SSE client\n *\n * @example\n * ```typescript\n * // Uses defaults (NEXT_PUBLIC_SPFN_API_URL + /events/stream)\n * const client = createSSEClient<EventRouter>();\n *\n * // Or with custom configuration\n * const client = createSSEClient<EventRouter>({\n * host: 'https://api.example.com',\n * pathname: '/sse',\n * reconnect: true,\n * reconnectDelay: 3000,\n * });\n *\n * // Subscribe to events\n * const unsubscribe = client.subscribe({\n * events: ['userCreated', 'orderPlaced'],\n * handlers: {\n * userCreated: (payload) => {\n * console.log('New user:', payload.userId);\n * },\n * orderPlaced: (payload) => {\n * console.log('New order:', payload.orderId);\n * },\n * },\n * onOpen: () => console.log('Connected'),\n * onError: (err) => console.error('Error:', err),\n * onReconnect: (attempt) => console.log('Reconnecting...', attempt),\n * });\n *\n * // Cleanup\n * unsubscribe();\n * ```\n */\nexport function createSSEClient<TRouter extends EventRouterDef<any>>(\n config: SSEClientConfig = {}\n): SSEClient<TRouter>\n{\n const {\n url,\n host = SSE_DEFAULTS.host,\n pathname = SSE_DEFAULTS.pathname,\n reconnect = true,\n reconnectDelay = 3000,\n maxReconnectAttempts = 0,\n withCredentials = false,\n acquireToken,\n } = config;\n\n // Build base URL: url takes precedence, otherwise host + pathname\n const baseUrl = url || `${host}${pathname}`;\n\n let eventSource: EventSource | null = null;\n let state: SSEConnectionState = 'closed';\n let reconnectAttempts = 0;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n function subscribe(options: SSESubscribeOptions<TRouter>): SSEUnsubscribe\n {\n const { events, handlers, onOpen, onError, onReconnect } = options;\n\n const eventNames = events as string[];\n\n function connect()\n {\n state = 'connecting';\n\n const init = async () =>\n {\n let tokenParam = '';\n\n if (acquireToken)\n {\n const token = await acquireToken();\n tokenParam = `&token=${encodeURIComponent(token)}`;\n }\n\n const streamUrl = `${baseUrl}?events=${eventNames.join(',')}${tokenParam}`;\n\n eventSource = new EventSource(streamUrl, {\n withCredentials,\n });\n\n setupEventHandlers(eventSource, eventNames, handlers, onOpen, onError);\n setupReconnect(onReconnect);\n };\n\n init().catch(() =>\n {\n state = 'error';\n attemptReconnect(onReconnect);\n });\n }\n\n function setupEventHandlers(\n es: EventSource,\n names: string[],\n handlerMap: SSESubscribeOptions<TRouter>['handlers'],\n onOpenCb?: () => void,\n onErrorCb?: (error: Event) => void\n )\n {\n es.onopen = () =>\n {\n state = 'open';\n reconnectAttempts = 0;\n onOpenCb?.();\n };\n\n es.onerror = (error) =>\n {\n state = 'error';\n onErrorCb?.(error);\n };\n\n // Handle connected event (server sends this on connection)\n es.addEventListener('connected', (e: MessageEvent) =>\n {\n try\n {\n const data = JSON.parse(e.data);\n console.debug('[SSE] Connected:', data);\n }\n catch\n {\n // Ignore parse errors\n }\n });\n\n // Handle ping (keep-alive)\n es.addEventListener('ping', () =>\n {\n // Ping received, connection is alive\n });\n\n // Register handlers for each event\n for (const eventName of names)\n {\n const handler = (handlerMap as Record<string, ((payload: unknown) => void) | undefined>)[eventName];\n\n if (!handler)\n {\n continue;\n }\n\n es.addEventListener(eventName, (e: MessageEvent) =>\n {\n try\n {\n const message: SSEMessage = JSON.parse(e.data);\n handler(message.data);\n }\n catch (err)\n {\n console.error(`[SSE] Failed to parse event \"${eventName}\":`, err);\n }\n });\n }\n }\n\n function setupReconnect(onReconnectCb?: (attempt: number) => void)\n {\n if (!eventSource)\n {\n return;\n }\n\n const currentEs = eventSource;\n const originalOnError = currentEs.onerror;\n\n currentEs.onerror = (error) =>\n {\n if (originalOnError)\n {\n (originalOnError as (ev: Event) => void)(error);\n }\n\n if (reconnect && currentEs.readyState === EventSource.CLOSED)\n {\n attemptReconnect(onReconnectCb);\n }\n };\n }\n\n function attemptReconnect(onReconnectCb?: (attempt: number) => void)\n {\n if (!reconnect)\n {\n return;\n }\n\n if (maxReconnectAttempts > 0 && reconnectAttempts >= maxReconnectAttempts)\n {\n return;\n }\n\n reconnectAttempts++;\n onReconnectCb?.(reconnectAttempts);\n\n reconnectTimer = setTimeout(() =>\n {\n connect();\n }, reconnectDelay);\n }\n\n // Start connection\n connect();\n\n // Return unsubscribe function\n return () =>\n {\n if (reconnectTimer)\n {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n\n if (eventSource)\n {\n eventSource.close();\n eventSource = null;\n }\n\n state = 'closed';\n };\n }\n\n function getState(): SSEConnectionState\n {\n return state;\n }\n\n function close()\n {\n if (reconnectTimer)\n {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n\n if (eventSource)\n {\n eventSource.close();\n eventSource = null;\n }\n\n state = 'closed';\n }\n\n return {\n subscribe,\n getState,\n close,\n };\n}\n\n/**\n * Simple subscribe function for one-off subscriptions\n *\n * @example\n * ```typescript\n * import { subscribeToEvents } from '@spfn/core/event/sse/client';\n * import type { EventRouter } from '@/server/events';\n *\n * // Using defaults\n * const unsubscribe = subscribeToEvents<EventRouter>(\n * ['userCreated', 'orderPlaced'],\n * {\n * userCreated: (payload) => console.log('User:', payload),\n * orderPlaced: (payload) => console.log('Order:', payload),\n * }\n * );\n *\n * // With custom host\n * const unsubscribe = subscribeToEvents<EventRouter>(\n * ['userCreated'],\n * { userCreated: (payload) => console.log('User:', payload) },\n * { host: 'https://api.example.com' }\n * );\n * ```\n */\nexport function subscribeToEvents<TRouter extends EventRouterDef<any>>(\n events: InferEventNames<TRouter>[],\n handlers: SSESubscribeOptions<TRouter>['handlers'],\n options?: SSEClientConfig\n): SSEUnsubscribe\n{\n const client = createSSEClient<TRouter>(options);\n\n return client.subscribe({\n events,\n handlers,\n });\n}\n\n// ============================================================================\n// Auth SSE Client\n// ============================================================================\n\n/**\n * SSE client configuration for authenticated connections\n *\n * Same as SSEClientConfig but without acquireToken (auto-configured).\n */\nexport interface AuthSSEClientConfig extends Omit<SSEClientConfig, 'acquireToken'>\n{\n /**\n * RPC proxy base URL for token acquisition\n * @default '/api/rpc'\n */\n rpcBaseUrl?: string;\n}\n\n/**\n * Create SSE client with built-in token authentication\n *\n * Acquires one-time SSE tokens via RPC proxy automatically.\n * Requires eventRouteMap to be merged into RPC proxy config.\n *\n * @example\n * ```typescript\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * import type { EventRouter } from '@/server/events';\n *\n * const client = createAuthSSEClient<EventRouter>();\n *\n * client.subscribe({\n * events: ['userCreated'],\n * handlers: {\n * userCreated: (payload) => console.log(payload),\n * },\n * });\n * ```\n */\nexport function createAuthSSEClient<TRouter extends EventRouterDef<any>>(\n config: AuthSSEClientConfig = {}\n): SSEClient<TRouter>\n{\n const { rpcBaseUrl = '/api/rpc', ...sseConfig } = config;\n\n return createSSEClient<TRouter>({\n ...sseConfig,\n acquireToken: async () =>\n {\n const res = await fetch(`${rpcBaseUrl}/eventsToken`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n if (!res.ok)\n {\n throw new Error(`Failed to acquire SSE token: ${res.status}`);\n }\n\n const data = await res.json();\n return data.token;\n },\n });\n}\n"]}
1
+ {"version":3,"sources":["../../../src/event/sse/client.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["/**\n * SSE Client\n *\n * Type-safe EventSource wrapper for event subscription\n *\n * @example\n * ```typescript\n * import { createSSEClient } from '@spfn/core/event/sse/client';\n * import type { EventRouter } from '@/server/events';\n *\n * // Uses defaults: NEXT_PUBLIC_SPFN_API_URL + /events/stream\n * const client = createSSEClient<EventRouter>();\n *\n * // Or with custom host/pathname\n * const client = createSSEClient<EventRouter>({\n * host: 'https://api.example.com',\n * pathname: '/sse',\n * });\n *\n * // With token authentication (recommended: use createAuthSSEClient)\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * const client = createAuthSSEClient<EventRouter>();\n *\n * const unsubscribe = client.subscribe({\n * events: ['userCreated', 'orderPlaced'],\n * handlers: {\n * userCreated: (payload) => console.log('User:', payload.userId),\n * orderPlaced: (payload) => console.log('Order:', payload.orderId),\n * },\n * });\n *\n * // Later: cleanup\n * unsubscribe();\n * ```\n */\n\nimport type { EventRouterDef, InferEventNames } from '../router';\nimport type {\n SSEClientConfig,\n SSESubscribeOptions,\n SSEUnsubscribe,\n SSEConnectionState,\n SSEMessage,\n} from './types';\n\n/**\n * SSE Client instance\n */\nexport interface SSEClient<TRouter extends EventRouterDef<any>>\n{\n /**\n * Subscribe to events\n */\n subscribe(options: SSESubscribeOptions<TRouter>): SSEUnsubscribe;\n\n /**\n * Get current connection state\n */\n getState(): SSEConnectionState;\n\n /**\n * Close all connections\n */\n close(): void;\n}\n\n/**\n * Default SSE configuration\n */\nconst SSE_DEFAULTS = {\n host: typeof process !== 'undefined'\n ? (process.env.NEXT_PUBLIC_SPFN_API_URL || 'http://localhost:8790')\n : 'http://localhost:8790',\n pathname: '/events/stream',\n} as const;\n\n/**\n * Create type-safe SSE client\n *\n * @example\n * ```typescript\n * // Uses defaults (NEXT_PUBLIC_SPFN_API_URL + /events/stream)\n * const client = createSSEClient<EventRouter>();\n *\n * // Or with custom configuration\n * const client = createSSEClient<EventRouter>({\n * host: 'https://api.example.com',\n * pathname: '/sse',\n * reconnect: true,\n * reconnectDelay: 3000,\n * });\n *\n * // Subscribe to events\n * const unsubscribe = client.subscribe({\n * events: ['userCreated', 'orderPlaced'],\n * handlers: {\n * userCreated: (payload) => {\n * console.log('New user:', payload.userId);\n * },\n * orderPlaced: (payload) => {\n * console.log('New order:', payload.orderId);\n * },\n * },\n * onOpen: () => console.log('Connected'),\n * onError: (err) => console.error('Error:', err),\n * onReconnect: (attempt) => console.log('Reconnecting...', attempt),\n * });\n *\n * // Cleanup\n * unsubscribe();\n * ```\n */\nexport function createSSEClient<TRouter extends EventRouterDef<any>>(\n config: SSEClientConfig = {}\n): SSEClient<TRouter>\n{\n const {\n url,\n host = SSE_DEFAULTS.host,\n pathname = SSE_DEFAULTS.pathname,\n reconnect = true,\n reconnectDelay = 3000,\n maxReconnectAttempts = 0,\n withCredentials = false,\n acquireToken,\n } = config;\n\n // Build base URL: url takes precedence, otherwise host + pathname\n const baseUrl = url || `${host}${pathname}`;\n\n let eventSource: EventSource | null = null;\n let state: SSEConnectionState = 'closed';\n let reconnectAttempts = 0;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let activeOnClose: (() => void) | undefined;\n\n function subscribe(options: SSESubscribeOptions<TRouter>): SSEUnsubscribe\n {\n const { events, handlers, onOpen, onError, onReconnect, onClose } = options;\n activeOnClose = onClose;\n\n const eventNames = events as string[];\n\n function connect()\n {\n state = 'connecting';\n\n const init = async () =>\n {\n let tokenParam = '';\n\n if (acquireToken)\n {\n const token = await acquireToken();\n tokenParam = `&token=${encodeURIComponent(token)}`;\n }\n\n const streamUrl = `${baseUrl}?events=${eventNames.join(',')}${tokenParam}`;\n\n eventSource = new EventSource(streamUrl, {\n withCredentials,\n });\n\n setupEventHandlers(eventSource, eventNames, handlers, onOpen, onError);\n setupReconnect(onReconnect);\n };\n\n init().catch(() =>\n {\n state = 'error';\n attemptReconnect(onReconnect);\n });\n }\n\n function setupEventHandlers(\n es: EventSource,\n names: string[],\n handlerMap: SSESubscribeOptions<TRouter>['handlers'],\n onOpenCb?: () => void,\n onErrorCb?: (error: Event) => void\n )\n {\n es.onopen = () =>\n {\n state = 'open';\n reconnectAttempts = 0;\n onOpenCb?.();\n };\n\n es.onerror = (error) =>\n {\n state = 'error';\n onErrorCb?.(error);\n };\n\n // Handle connected event (server sends this on connection)\n es.addEventListener('connected', (e: MessageEvent) =>\n {\n try\n {\n const data = JSON.parse(e.data);\n console.debug('[SSE] Connected:', data);\n }\n catch\n {\n // Ignore parse errors\n }\n });\n\n // Handle ping (keep-alive)\n es.addEventListener('ping', () =>\n {\n // Ping received, connection is alive\n });\n\n // Register handlers for each event\n for (const eventName of names)\n {\n const handler = (handlerMap as Record<string, ((payload: unknown) => void) | undefined>)[eventName];\n\n if (!handler)\n {\n continue;\n }\n\n es.addEventListener(eventName, (e: MessageEvent) =>\n {\n try\n {\n const message: SSEMessage = JSON.parse(e.data);\n handler(message.data);\n }\n catch (err)\n {\n console.error(`[SSE] Failed to parse event \"${eventName}\":`, err);\n }\n });\n }\n }\n\n function setupReconnect(onReconnectCb?: (attempt: number) => void)\n {\n if (!eventSource)\n {\n return;\n }\n\n const currentEs = eventSource;\n const originalOnError = currentEs.onerror;\n\n currentEs.onerror = (error) =>\n {\n if (originalOnError)\n {\n (originalOnError as (ev: Event) => void)(error);\n }\n\n // Token-auth 사용 시 브라우저 auto-retry는 소비된 토큰으로 재시도하므로\n // 즉시 close하고 우리 reconnect로 새 토큰 발급받아 재연결\n if (reconnect && acquireToken)\n {\n currentEs.close();\n attemptReconnect(onReconnectCb);\n }\n else if (reconnect && currentEs.readyState === EventSource.CLOSED)\n {\n attemptReconnect(onReconnectCb);\n }\n };\n }\n\n function attemptReconnect(onReconnectCb?: (attempt: number) => void)\n {\n if (!reconnect)\n {\n return;\n }\n\n if (maxReconnectAttempts > 0 && reconnectAttempts >= maxReconnectAttempts)\n {\n state = 'closed';\n onClose?.();\n return;\n }\n\n reconnectAttempts++;\n onReconnectCb?.(reconnectAttempts);\n\n reconnectTimer = setTimeout(() =>\n {\n connect();\n }, reconnectDelay);\n }\n\n // Start connection\n connect();\n\n // Return unsubscribe function\n return () =>\n {\n if (reconnectTimer)\n {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n\n if (eventSource)\n {\n eventSource.close();\n eventSource = null;\n }\n\n state = 'closed';\n onClose?.();\n };\n }\n\n function getState(): SSEConnectionState\n {\n return state;\n }\n\n function close()\n {\n if (reconnectTimer)\n {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n\n if (eventSource)\n {\n eventSource.close();\n eventSource = null;\n }\n\n state = 'closed';\n activeOnClose?.();\n }\n\n return {\n subscribe,\n getState,\n close,\n };\n}\n\n/**\n * Simple subscribe function for one-off subscriptions\n *\n * @example\n * ```typescript\n * import { subscribeToEvents } from '@spfn/core/event/sse/client';\n * import type { EventRouter } from '@/server/events';\n *\n * // Using defaults\n * const unsubscribe = subscribeToEvents<EventRouter>(\n * ['userCreated', 'orderPlaced'],\n * {\n * userCreated: (payload) => console.log('User:', payload),\n * orderPlaced: (payload) => console.log('Order:', payload),\n * }\n * );\n *\n * // With custom host\n * const unsubscribe = subscribeToEvents<EventRouter>(\n * ['userCreated'],\n * { userCreated: (payload) => console.log('User:', payload) },\n * { host: 'https://api.example.com' }\n * );\n * ```\n */\nexport function subscribeToEvents<TRouter extends EventRouterDef<any>>(\n events: InferEventNames<TRouter>[],\n handlers: SSESubscribeOptions<TRouter>['handlers'],\n options?: SSEClientConfig\n): SSEUnsubscribe\n{\n const client = createSSEClient<TRouter>(options);\n\n return client.subscribe({\n events,\n handlers,\n });\n}\n\n// ============================================================================\n// Auth SSE Client\n// ============================================================================\n\n/**\n * SSE client configuration for authenticated connections\n *\n * Same as SSEClientConfig but without acquireToken (auto-configured).\n */\nexport interface AuthSSEClientConfig extends Omit<SSEClientConfig, 'acquireToken'>\n{\n /**\n * RPC proxy base URL for token acquisition\n * @default '/api/rpc'\n */\n rpcBaseUrl?: string;\n}\n\n/**\n * Create SSE client with built-in token authentication\n *\n * Acquires one-time SSE tokens via RPC proxy automatically.\n * Requires eventRouteMap to be merged into RPC proxy config.\n *\n * @example\n * ```typescript\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * import type { EventRouter } from '@/server/events';\n *\n * const client = createAuthSSEClient<EventRouter>();\n *\n * client.subscribe({\n * events: ['userCreated'],\n * handlers: {\n * userCreated: (payload) => console.log(payload),\n * },\n * });\n * ```\n */\nexport function createAuthSSEClient<TRouter extends EventRouterDef<any>>(\n config: AuthSSEClientConfig = {}\n): SSEClient<TRouter>\n{\n const { rpcBaseUrl = '/api/rpc', ...sseConfig } = config;\n\n return createSSEClient<TRouter>({\n ...sseConfig,\n acquireToken: async () =>\n {\n const res = await fetch(`${rpcBaseUrl}/eventsToken`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n });\n\n if (!res.ok)\n {\n throw new Error(`Failed to acquire SSE token: ${res.status}`);\n }\n\n const data = await res.json();\n return data.token;\n },\n });\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { Context } from 'hono';
2
2
  import { E as EventRouterDef } from '../../router-Di7ENoah.js';
3
- import { S as SSEHandlerConfig, b as SSETokenManager } from '../../types-BUmrohqx.js';
4
- export { C as CacheTokenStore, a as SSEAuthConfig, h as SSEClientConfig, l as SSEConnectionState, i as SSEEventHandler, j as SSEEventHandlers, g as SSEHandlerAuthConfig, f as SSEMessage, k as SSESubscribeOptions, c as SSEToken, e as SSETokenManagerConfig, d as SSETokenStore, m as SSEUnsubscribe } from '../../types-BUmrohqx.js';
3
+ import { S as SSEHandlerConfig, b as SSETokenManager } from '../../types-DKQ90YL7.js';
4
+ export { C as CacheTokenStore, a as SSEAuthConfig, h as SSEClientConfig, l as SSEConnectionState, i as SSEEventHandler, j as SSEEventHandlers, g as SSEHandlerAuthConfig, f as SSEMessage, k as SSESubscribeOptions, c as SSEToken, e as SSETokenManagerConfig, d as SSETokenStore, m as SSEUnsubscribe } from '../../types-DKQ90YL7.js';
5
5
  import '@sinclair/typebox';
6
6
 
7
7
  /**
@@ -5,29 +5,29 @@ import { logger } from '@spfn/core/logger';
5
5
  // src/nextjs/client/core.ts
6
6
 
7
7
  // src/nextjs/client/debug-logs.ts
8
- function logCookieAutoDetection(logger2, cookies) {
9
- logger2.debug("Auto-detected server environment, forwarding cookies", {
8
+ function logCookieAutoDetection(logger3, cookies) {
9
+ logger3.debug("Auto-detected server environment, forwarding cookies", {
10
10
  cookieCount: cookies.length,
11
11
  cookieNames: cookies.map((c) => c.name)
12
12
  });
13
13
  }
14
- function logRequest(logger2, routeName, method, url, hasBody) {
15
- logger2.debug("\u2192 Request", {
14
+ function logRequest(logger3, routeName, method, url, hasBody) {
15
+ logger3.debug("\u2192 Request", {
16
16
  route: routeName,
17
17
  method,
18
18
  url,
19
19
  hasBody
20
20
  });
21
21
  }
22
- function logResponse(logger2, routeName, status, hasBody) {
23
- logger2.debug("\u2190 Response", {
22
+ function logResponse(logger3, routeName, status, hasBody) {
23
+ logger3.debug("\u2190 Response", {
24
24
  route: routeName,
25
25
  status,
26
26
  hasBody
27
27
  });
28
28
  }
29
- function logErrorResponse(logger2, status, body) {
30
- logger2.debug("Error response received", {
29
+ function logErrorResponse(logger3, status, body) {
30
+ logger3.debug("Error response received", {
31
31
  status,
32
32
  hasBody: !!body,
33
33
  bodyType: typeof body,
@@ -35,32 +35,32 @@ function logErrorResponse(logger2, status, body) {
35
35
  typeValue: body?.__type
36
36
  });
37
37
  }
38
- function logErrorDeserializationAttempt(logger2, errorType, registeredTypes) {
39
- logger2.debug("Attempting error deserialization", {
38
+ function logErrorDeserializationAttempt(logger3, errorType, registeredTypes) {
39
+ logger3.debug("Attempting error deserialization", {
40
40
  errorType,
41
41
  hasRegistry: true,
42
42
  registeredTypes
43
43
  });
44
44
  }
45
- function logErrorDeserializationSuccess(logger2, error) {
46
- logger2.debug("Error deserialized successfully", {
45
+ function logErrorDeserializationSuccess(logger3, error) {
46
+ logger3.debug("Error deserialized successfully", {
47
47
  errorName: error?.name,
48
48
  errorConstructor: error?.constructor.name,
49
49
  message: error?.message
50
50
  });
51
51
  }
52
- function logErrorDeserializationFailure(logger2, error) {
53
- logger2.debug("Deserialization failed", {
52
+ function logErrorDeserializationFailure(logger3, error) {
53
+ logger3.debug("Deserialization failed", {
54
54
  errorName: error instanceof Error ? error.name : "unknown",
55
55
  errorMessage: error instanceof Error ? error.message : String(error)
56
56
  });
57
57
  }
58
- function logErrorDeserializationSkipped(logger2, errorRegistry, body) {
58
+ function logErrorDeserializationSkipped(logger3, errorRegistry, body) {
59
59
  const reason = !errorRegistry ? "no registry" : !body ? "no body" : typeof body !== "object" ? "body not object" : !("__type" in body) ? "no __type field" : "unknown";
60
- logger2.debug("Skipping error deserialization", { reason });
60
+ logger3.debug("Skipping error deserialization", { reason });
61
61
  }
62
- function logThrowingDeserializedError(logger2, error) {
63
- logger2.debug("Throwing deserialized error", {
62
+ function logThrowingDeserializedError(logger3, error) {
63
+ logger3.debug("Throwing deserialized error", {
64
64
  errorName: error.name,
65
65
  errorConstructorName: error.constructor.name,
66
66
  prototype: Object.getPrototypeOf(error).constructor.name
@@ -97,15 +97,29 @@ async function parseResponseBody(response) {
97
97
  }
98
98
 
99
99
  // src/nextjs/client/helpers.ts
100
+ var cookieLogger = logger.child("@spfn/core:auto-cookies");
100
101
  async function autoDetectServerCookies() {
102
+ if (typeof window !== "undefined") {
103
+ return {};
104
+ }
101
105
  try {
102
106
  const { cookies } = await import('next/headers');
103
107
  const cookieStore = await cookies();
104
108
  const allCookies = cookieStore.getAll();
105
- return Object.fromEntries(
109
+ const result = Object.fromEntries(
106
110
  allCookies.map((cookie) => [cookie.name, cookie.value])
107
111
  );
112
+ cookieLogger.debug("Server cookies detected", {
113
+ count: allCookies.length,
114
+ names: allCookies.map((c) => c.name)
115
+ });
116
+ return result;
108
117
  } catch (error) {
118
+ const err = error;
119
+ cookieLogger.warn("Failed to read server cookies", {
120
+ message: err.message,
121
+ name: err.name
122
+ });
109
123
  return {};
110
124
  }
111
125
  }
@@ -124,36 +138,36 @@ async function executeFetchWithTimeout(url, init, timeout, customFetch = fetch)
124
138
  throw error;
125
139
  }
126
140
  }
127
- async function handleErrorResponse(response, body, fullUrl, errorRegistry, debug, logger2) {
141
+ async function handleErrorResponse(response, body, fullUrl, errorRegistry, debug, logger3) {
128
142
  if (debug) {
129
- logErrorResponse(logger2, response.status, body);
143
+ logErrorResponse(logger3, response.status, body);
130
144
  }
131
145
  let deserializedError = null;
132
146
  if (errorRegistry && body && typeof body === "object" && "__type" in body) {
133
147
  if (debug) {
134
- logErrorDeserializationAttempt(logger2, body.__type, errorRegistry.getRegisteredTypes());
148
+ logErrorDeserializationAttempt(logger3, body.__type, errorRegistry.getRegisteredTypes());
135
149
  }
136
150
  try {
137
151
  deserializedError = errorRegistry.deserialize(body);
138
152
  if (debug) {
139
- logErrorDeserializationSuccess(logger2, deserializedError);
153
+ logErrorDeserializationSuccess(logger3, deserializedError);
140
154
  }
141
155
  } catch (deserializeError) {
142
156
  if (debug) {
143
- logErrorDeserializationFailure(logger2, deserializeError);
157
+ logErrorDeserializationFailure(logger3, deserializeError);
144
158
  }
145
159
  }
146
160
  } else if (debug) {
147
- logErrorDeserializationSkipped(logger2, errorRegistry, body);
161
+ logErrorDeserializationSkipped(logger3, errorRegistry, body);
148
162
  }
149
163
  if (deserializedError) {
150
164
  if (debug) {
151
- logThrowingDeserializedError(logger2, deserializedError);
165
+ logThrowingDeserializedError(logger3, deserializedError);
152
166
  }
153
167
  throw deserializedError;
154
168
  }
155
169
  if (response.status === 404 && process.env.NODE_ENV !== "production") {
156
- logger2.warn(
170
+ logger3.warn(
157
171
  "\n\u26A0\uFE0F 404 Not Found\n\nCheck the following:\n 1. Routes are registered in server.config.ts:\n \u2192 defineServerConfig().routes(appRouter)\n 2. Delete .spfn cache if you recently added new routes:\n \u2192 rm -rf .spfn\n"
158
172
  );
159
173
  }
@@ -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 = 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}"]}
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 { logger } from '@spfn/core/logger';\nimport { ApiError } from './errors';\nimport * as debugLogs from './debug-logs';\n\n// Re-export shared utilities\nexport { buildCookieHeader, parseResponseBody } from '../shared';\n\nconst cookieLogger = logger.child('@spfn/core:auto-cookies');\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 // Client environment — browser sends cookies automatically\n if (typeof window !== 'undefined')\n {\n return {};\n }\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 const result = Object.fromEntries(\n allCookies.map(cookie => [cookie.name, cookie.value])\n );\n\n cookieLogger.debug('Server cookies detected', {\n count: allCookies.length,\n names: allCookies.map(c => c.name),\n });\n\n return result;\n }\n catch (error)\n {\n // Server environment but cookies() not accessible\n // (e.g. static generation, build time, or outside request context)\n const err = error as Error;\n cookieLogger.warn('Failed to read server cookies', {\n message: err.message,\n name: err.name,\n });\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}"]}
@@ -6,7 +6,7 @@ import { NamedMiddleware, Router } from '@spfn/core/route';
6
6
  import { OnErrorContext } from '@spfn/core/middleware';
7
7
  import { J as JobRouter, B as BossOptions } from '../boss-DI1r4kTS.js';
8
8
  import { E as EventRouterDef } from '../router-Di7ENoah.js';
9
- import { S as SSEHandlerConfig, a as SSEAuthConfig } from '../types-BUmrohqx.js';
9
+ import { S as SSEHandlerConfig, a as SSEAuthConfig } from '../types-DKQ90YL7.js';
10
10
  import '@sinclair/typebox';
11
11
  import 'pg-boss';
12
12
 
@@ -346,6 +346,15 @@ interface SSESubscribeOptions<TRouter extends EventRouterDef<any>> {
346
346
  * Called on connection error
347
347
  */
348
348
  onError?: (error: Event) => void;
349
+ /**
350
+ * Called when connection is permanently closed
351
+ *
352
+ * Triggered when:
353
+ * - unsubscribe() is called
354
+ * - client.close() is called
355
+ * - Max reconnect attempts exceeded
356
+ */
357
+ onClose?: () => void;
349
358
  /**
350
359
  * Called when reconnecting
351
360
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfn/core",
3
- "version": "0.2.0-beta.35",
3
+ "version": "0.2.0-beta.37",
4
4
  "description": "SPFN Framework Core - File-based routing, transactions, repository pattern",
5
5
  "type": "module",
6
6
  "exports": {