@spfn/core 0.1.0-alpha.84 → 0.1.0-alpha.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/index.js +12 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/index.d.ts +192 -46
- package/dist/client/index.js +181 -9
- package/dist/client/index.js.map +1 -1
- package/dist/client/nextjs/index.d.ts +557 -0
- package/dist/client/nextjs/index.js +371 -0
- package/dist/client/nextjs/index.js.map +1 -0
- package/dist/codegen/generators/index.js +12 -1
- package/dist/codegen/generators/index.js.map +1 -1
- package/dist/codegen/index.js +12 -1
- package/dist/codegen/index.js.map +1 -1
- package/dist/db/index.js +12 -1
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.js +12 -1
- package/dist/env/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +148 -13
- package/dist/index.js.map +1 -1
- package/dist/logger/index.js +12 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.js +12 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/route/index.js +15 -3
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +53 -2
- package/dist/server/index.js +148 -13
- package/dist/server/index.js.map +1 -1
- package/package.json +7 -1
package/dist/client/index.d.ts
CHANGED
|
@@ -36,6 +36,24 @@ interface CallOptions<TContract extends RouteContract> {
|
|
|
36
36
|
body?: InferContract<TContract>['body'];
|
|
37
37
|
headers?: Record<string, string>;
|
|
38
38
|
baseUrl?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Additional fetch options (extends RequestInit)
|
|
41
|
+
*
|
|
42
|
+
* Can be used for environment-specific options like Next.js cache/next
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* // Next.js time-based revalidation
|
|
47
|
+
* { fetchOptions: { next: { revalidate: 60 } } }
|
|
48
|
+
*
|
|
49
|
+
* // Next.js disable cache
|
|
50
|
+
* { fetchOptions: { cache: 'no-store' } }
|
|
51
|
+
*
|
|
52
|
+
* // Next.js on-demand revalidation
|
|
53
|
+
* { fetchOptions: { next: { tags: ['products'] } } }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
fetchOptions?: Record<string, any>;
|
|
39
57
|
}
|
|
40
58
|
/**
|
|
41
59
|
* API Client Error
|
|
@@ -74,51 +92,6 @@ declare class ContractClient {
|
|
|
74
92
|
private static getHttpMethod;
|
|
75
93
|
private static isFormData;
|
|
76
94
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Create a new contract-based API client
|
|
79
|
-
*/
|
|
80
|
-
declare function createClient(config?: ClientConfig): ContractClient;
|
|
81
|
-
/**
|
|
82
|
-
* Configure the global client instance
|
|
83
|
-
*
|
|
84
|
-
* Call this in your app initialization to set default configuration
|
|
85
|
-
* for all auto-generated API calls.
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* ```ts
|
|
89
|
-
* // In app initialization (layout.tsx, _app.tsx, etc)
|
|
90
|
-
* import { configureClient } from '@spfn/core/client';
|
|
91
|
-
*
|
|
92
|
-
* configureClient({
|
|
93
|
-
* baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',
|
|
94
|
-
* timeout: 60000,
|
|
95
|
-
* headers: {
|
|
96
|
-
* 'X-App-Version': '1.0.0'
|
|
97
|
-
* }
|
|
98
|
-
* });
|
|
99
|
-
*
|
|
100
|
-
* // Add interceptors
|
|
101
|
-
* import { client } from '@spfn/core/client';
|
|
102
|
-
* client.use(async (url, init) => {
|
|
103
|
-
* // Add auth header
|
|
104
|
-
* return {
|
|
105
|
-
* ...init,
|
|
106
|
-
* headers: {
|
|
107
|
-
* ...init.headers,
|
|
108
|
-
* Authorization: `Bearer ${getToken()}`
|
|
109
|
-
* }
|
|
110
|
-
* };
|
|
111
|
-
* });
|
|
112
|
-
* ```
|
|
113
|
-
*/
|
|
114
|
-
declare function configureClient(config: ClientConfig): void;
|
|
115
|
-
/**
|
|
116
|
-
* Global client singleton with Proxy
|
|
117
|
-
*
|
|
118
|
-
* This client can be configured using configureClient() before use.
|
|
119
|
-
* Used by auto-generated API client code.
|
|
120
|
-
*/
|
|
121
|
-
declare const client: ContractClient;
|
|
122
95
|
/**
|
|
123
96
|
* Type guard for timeout errors
|
|
124
97
|
*
|
|
@@ -209,4 +182,177 @@ declare function getServerErrorType(error: ApiClientError): string | undefined;
|
|
|
209
182
|
*/
|
|
210
183
|
declare function getServerErrorDetails<T = any>(error: ApiClientError): T | undefined;
|
|
211
184
|
|
|
212
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Universal API Client
|
|
187
|
+
*
|
|
188
|
+
* Automatically routes requests based on execution environment:
|
|
189
|
+
* - Server Environment: Direct call to SPFN API (internal network)
|
|
190
|
+
* - Browser Environment: Proxies through Next.js API Route (cookie forwarding)
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Universal Client Configuration
|
|
195
|
+
*/
|
|
196
|
+
interface UniversalClientConfig {
|
|
197
|
+
/**
|
|
198
|
+
* SPFN API server URL (for server-side direct calls)
|
|
199
|
+
*
|
|
200
|
+
* @default process.env.SERVER_API_URL || process.env.SPFN_API_URL || 'http://localhost:8790'
|
|
201
|
+
*/
|
|
202
|
+
apiUrl?: string;
|
|
203
|
+
/**
|
|
204
|
+
* Next.js API route base path (for client-side proxy calls)
|
|
205
|
+
*
|
|
206
|
+
* @default '/api/actions'
|
|
207
|
+
* @example '/api/proxy'
|
|
208
|
+
*/
|
|
209
|
+
proxyBasePath?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Additional headers to include in all requests
|
|
212
|
+
*/
|
|
213
|
+
headers?: Record<string, string>;
|
|
214
|
+
/**
|
|
215
|
+
* Request timeout in milliseconds
|
|
216
|
+
*
|
|
217
|
+
* @default 30000
|
|
218
|
+
*/
|
|
219
|
+
timeout?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Custom fetch implementation
|
|
222
|
+
*/
|
|
223
|
+
fetch?: typeof fetch;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Universal API Client
|
|
227
|
+
*
|
|
228
|
+
* Automatically detects execution environment and routes requests accordingly:
|
|
229
|
+
*
|
|
230
|
+
* **Server Environment** (Next.js Server Components, API Routes):
|
|
231
|
+
* - Direct HTTP call to SPFN API server
|
|
232
|
+
* - Uses internal network (e.g., http://localhost:8790)
|
|
233
|
+
* - No proxy overhead
|
|
234
|
+
*
|
|
235
|
+
* **Browser Environment** (Next.js Client Components):
|
|
236
|
+
* - Routes through Next.js API Route proxy (e.g., /api/proxy/*)
|
|
237
|
+
* - Enables HttpOnly cookie forwarding
|
|
238
|
+
* - Maintains CORS security
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* // Server Component - direct call
|
|
243
|
+
* import { createUniversalClient } from '@spfn/core/client';
|
|
244
|
+
* const client = createUniversalClient();
|
|
245
|
+
* const result = await client.call(loginContract, { body: {...} });
|
|
246
|
+
*
|
|
247
|
+
* // Client Component - proxied call (automatic)
|
|
248
|
+
* 'use client';
|
|
249
|
+
* const client = createUniversalClient();
|
|
250
|
+
* const result = await client.call(loginContract, { body: {...} }); // Goes through /api/proxy
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
declare class UniversalClient {
|
|
254
|
+
private readonly directClient;
|
|
255
|
+
private readonly proxyBasePath;
|
|
256
|
+
private readonly isServer;
|
|
257
|
+
private readonly fetchImpl;
|
|
258
|
+
constructor(config?: UniversalClientConfig);
|
|
259
|
+
/**
|
|
260
|
+
* Make a type-safe API call using a contract
|
|
261
|
+
*
|
|
262
|
+
* Automatically routes based on environment:
|
|
263
|
+
* - Server: Direct SPFN API call
|
|
264
|
+
* - Browser: Next.js API Route proxy
|
|
265
|
+
*
|
|
266
|
+
* @param contract - Route contract with absolute path
|
|
267
|
+
* @param options - Call options (params, query, body, headers)
|
|
268
|
+
*/
|
|
269
|
+
call<TContract extends RouteContract>(contract: TContract, options?: CallOptions<TContract>): Promise<InferContract<TContract>['response']>;
|
|
270
|
+
/**
|
|
271
|
+
* Call via Next.js API Route proxy (client-side)
|
|
272
|
+
*
|
|
273
|
+
* Routes request through /api/proxy/[...path] to enable:
|
|
274
|
+
* - HttpOnly cookie forwarding
|
|
275
|
+
* - CORS security
|
|
276
|
+
* - Server-side session management
|
|
277
|
+
*
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
280
|
+
private callViaProxy;
|
|
281
|
+
/**
|
|
282
|
+
* Build URL path with parameter substitution
|
|
283
|
+
*/
|
|
284
|
+
private buildUrlPath;
|
|
285
|
+
/**
|
|
286
|
+
* Build query string from query parameters
|
|
287
|
+
*/
|
|
288
|
+
private buildQueryString;
|
|
289
|
+
/**
|
|
290
|
+
* Get HTTP method from contract or infer from options
|
|
291
|
+
*/
|
|
292
|
+
private getHttpMethod;
|
|
293
|
+
/**
|
|
294
|
+
* Check if body is FormData
|
|
295
|
+
*/
|
|
296
|
+
private isFormData;
|
|
297
|
+
/**
|
|
298
|
+
* Check if currently running in server environment
|
|
299
|
+
*/
|
|
300
|
+
isServerEnv(): boolean;
|
|
301
|
+
/**
|
|
302
|
+
* Create a new client with merged configuration
|
|
303
|
+
*/
|
|
304
|
+
withConfig(config: Partial<UniversalClientConfig>): UniversalClient;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Create a new universal API client
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* // Default configuration
|
|
312
|
+
* const client = createUniversalClient();
|
|
313
|
+
*
|
|
314
|
+
* // Custom configuration
|
|
315
|
+
* const client = createUniversalClient({
|
|
316
|
+
* apiUrl: 'http://localhost:4000',
|
|
317
|
+
* proxyBasePath: '/api/spfn',
|
|
318
|
+
* headers: { 'X-App-Version': '1.0.0' },
|
|
319
|
+
* });
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
declare function createUniversalClient(config?: UniversalClientConfig): UniversalClient;
|
|
323
|
+
/**
|
|
324
|
+
* Configure the global universal client instance
|
|
325
|
+
*
|
|
326
|
+
* Call this in your app initialization to set default configuration
|
|
327
|
+
* for all auto-generated API calls.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```typescript
|
|
331
|
+
* // In app initialization (layout.tsx, _app.tsx, etc)
|
|
332
|
+
* import { configureUniversalClient } from '@spfn/core/client';
|
|
333
|
+
*
|
|
334
|
+
* configureUniversalClient({
|
|
335
|
+
* apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8790',
|
|
336
|
+
* proxyBasePath: '/api/proxy',
|
|
337
|
+
* headers: {
|
|
338
|
+
* 'X-App-Version': '1.0.0'
|
|
339
|
+
* }
|
|
340
|
+
* });
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
declare function configureUniversalClient(config: UniversalClientConfig): void;
|
|
344
|
+
/**
|
|
345
|
+
* Get the global universal client instance
|
|
346
|
+
*
|
|
347
|
+
* Creates a default instance if not configured
|
|
348
|
+
*/
|
|
349
|
+
declare function getUniversalClient(): UniversalClient;
|
|
350
|
+
/**
|
|
351
|
+
* Global universal client singleton with Proxy
|
|
352
|
+
*
|
|
353
|
+
* This client can be configured using configureUniversalClient() before use.
|
|
354
|
+
* Used by auto-generated API client code.
|
|
355
|
+
*/
|
|
356
|
+
declare const universalClient: UniversalClient;
|
|
357
|
+
|
|
358
|
+
export { ApiClientError, type CallOptions, type UniversalClientConfig as ClientConfig, ContractClient, type RequestInterceptor, UniversalClient, type UniversalClientConfig, universalClient as client, configureUniversalClient as configureClient, configureUniversalClient, createUniversalClient as createClient, createUniversalClient, getServerErrorDetails, getServerErrorType, getUniversalClient, isHttpError, isNetworkError, isServerError, isTimeoutError, universalClient };
|
package/dist/client/index.js
CHANGED
|
@@ -53,7 +53,9 @@ var ContractClient = class _ContractClient {
|
|
|
53
53
|
}
|
|
54
54
|
let init = {
|
|
55
55
|
method,
|
|
56
|
-
headers
|
|
56
|
+
headers,
|
|
57
|
+
...options?.fetchOptions
|
|
58
|
+
// Spread environment-specific options (e.g., Next.js cache/next)
|
|
57
59
|
};
|
|
58
60
|
if (options?.body !== void 0) {
|
|
59
61
|
init.body = isFormData ? options.body : JSON.stringify(options.body);
|
|
@@ -145,14 +147,8 @@ var ContractClient = class _ContractClient {
|
|
|
145
147
|
return body instanceof FormData;
|
|
146
148
|
}
|
|
147
149
|
};
|
|
148
|
-
function createClient(config) {
|
|
149
|
-
return new ContractClient(config);
|
|
150
|
-
}
|
|
151
150
|
var _clientInstance = new ContractClient();
|
|
152
|
-
|
|
153
|
-
_clientInstance = new ContractClient(config);
|
|
154
|
-
}
|
|
155
|
-
var client = new Proxy({}, {
|
|
151
|
+
new Proxy({}, {
|
|
156
152
|
get(_target, prop) {
|
|
157
153
|
return _clientInstance[prop];
|
|
158
154
|
}
|
|
@@ -180,6 +176,182 @@ function getServerErrorDetails(error) {
|
|
|
180
176
|
return response?.error?.details;
|
|
181
177
|
}
|
|
182
178
|
|
|
183
|
-
|
|
179
|
+
// src/client/universal-client.ts
|
|
180
|
+
function isServerEnvironment() {
|
|
181
|
+
return typeof window === "undefined";
|
|
182
|
+
}
|
|
183
|
+
var UniversalClient = class _UniversalClient {
|
|
184
|
+
directClient;
|
|
185
|
+
proxyBasePath;
|
|
186
|
+
isServer;
|
|
187
|
+
fetchImpl;
|
|
188
|
+
constructor(config = {}) {
|
|
189
|
+
this.isServer = isServerEnvironment();
|
|
190
|
+
this.directClient = new ContractClient({
|
|
191
|
+
baseUrl: config.apiUrl,
|
|
192
|
+
headers: config.headers,
|
|
193
|
+
timeout: config.timeout,
|
|
194
|
+
fetch: config.fetch
|
|
195
|
+
});
|
|
196
|
+
this.proxyBasePath = config.proxyBasePath || "/api/actions";
|
|
197
|
+
this.fetchImpl = config.fetch || globalThis.fetch.bind(globalThis);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Make a type-safe API call using a contract
|
|
201
|
+
*
|
|
202
|
+
* Automatically routes based on environment:
|
|
203
|
+
* - Server: Direct SPFN API call
|
|
204
|
+
* - Browser: Next.js API Route proxy
|
|
205
|
+
*
|
|
206
|
+
* @param contract - Route contract with absolute path
|
|
207
|
+
* @param options - Call options (params, query, body, headers)
|
|
208
|
+
*/
|
|
209
|
+
async call(contract, options) {
|
|
210
|
+
if (this.isServer) {
|
|
211
|
+
return this.directClient.call(contract, options);
|
|
212
|
+
} else {
|
|
213
|
+
return this.callViaProxy(contract, options);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Call via Next.js API Route proxy (client-side)
|
|
218
|
+
*
|
|
219
|
+
* Routes request through /api/proxy/[...path] to enable:
|
|
220
|
+
* - HttpOnly cookie forwarding
|
|
221
|
+
* - CORS security
|
|
222
|
+
* - Server-side session management
|
|
223
|
+
*
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
async callViaProxy(contract, options) {
|
|
227
|
+
const path = this.buildUrlPath(
|
|
228
|
+
contract.path,
|
|
229
|
+
options?.params
|
|
230
|
+
);
|
|
231
|
+
const queryString = this.buildQueryString(
|
|
232
|
+
options?.query
|
|
233
|
+
);
|
|
234
|
+
const proxyUrl = `${this.proxyBasePath}${path}${queryString}`;
|
|
235
|
+
const method = this.getHttpMethod(contract, options);
|
|
236
|
+
const headers = {
|
|
237
|
+
...options?.headers
|
|
238
|
+
};
|
|
239
|
+
const isFormData = this.isFormData(options?.body);
|
|
240
|
+
if (options?.body !== void 0 && !isFormData && !headers["Content-Type"]) {
|
|
241
|
+
headers["Content-Type"] = "application/json";
|
|
242
|
+
}
|
|
243
|
+
const init = {
|
|
244
|
+
method,
|
|
245
|
+
headers,
|
|
246
|
+
credentials: "include",
|
|
247
|
+
// Important: Include cookies for session
|
|
248
|
+
...options?.fetchOptions
|
|
249
|
+
// Spread environment-specific options (e.g., Next.js cache/next)
|
|
250
|
+
};
|
|
251
|
+
if (options?.body !== void 0) {
|
|
252
|
+
init.body = isFormData ? options.body : JSON.stringify(options.body);
|
|
253
|
+
}
|
|
254
|
+
const response = await this.fetchImpl(proxyUrl, init);
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
const errorBody = await response.json().catch(() => null);
|
|
257
|
+
throw new ApiClientError(
|
|
258
|
+
`${method} ${path} failed: ${response.status} ${response.statusText}`,
|
|
259
|
+
response.status,
|
|
260
|
+
proxyUrl,
|
|
261
|
+
errorBody,
|
|
262
|
+
"http"
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const data = await response.json();
|
|
266
|
+
return data;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Build URL path with parameter substitution
|
|
270
|
+
*/
|
|
271
|
+
buildUrlPath(path, params) {
|
|
272
|
+
if (!params) return path;
|
|
273
|
+
let url = path;
|
|
274
|
+
for (const [key, value] of Object.entries(params)) {
|
|
275
|
+
url = url.replace(`:${key}`, String(value));
|
|
276
|
+
}
|
|
277
|
+
return url;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Build query string from query parameters
|
|
281
|
+
*/
|
|
282
|
+
buildQueryString(query) {
|
|
283
|
+
if (!query || Object.keys(query).length === 0) return "";
|
|
284
|
+
const params = new URLSearchParams();
|
|
285
|
+
for (const [key, value] of Object.entries(query)) {
|
|
286
|
+
if (Array.isArray(value)) {
|
|
287
|
+
value.forEach((v) => params.append(key, String(v)));
|
|
288
|
+
} else if (value !== void 0 && value !== null) {
|
|
289
|
+
params.append(key, String(value));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const queryString = params.toString();
|
|
293
|
+
return queryString ? `?${queryString}` : "";
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get HTTP method from contract or infer from options
|
|
297
|
+
*/
|
|
298
|
+
getHttpMethod(contract, options) {
|
|
299
|
+
if ("method" in contract && typeof contract.method === "string") {
|
|
300
|
+
return contract.method.toUpperCase();
|
|
301
|
+
}
|
|
302
|
+
if (options?.body !== void 0) {
|
|
303
|
+
return "POST";
|
|
304
|
+
}
|
|
305
|
+
return "GET";
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Check if body is FormData
|
|
309
|
+
*/
|
|
310
|
+
isFormData(body) {
|
|
311
|
+
return typeof FormData !== "undefined" && body instanceof FormData;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Check if currently running in server environment
|
|
315
|
+
*/
|
|
316
|
+
isServerEnv() {
|
|
317
|
+
return this.isServer;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Create a new client with merged configuration
|
|
321
|
+
*/
|
|
322
|
+
withConfig(config) {
|
|
323
|
+
return new _UniversalClient({
|
|
324
|
+
apiUrl: config.apiUrl || this.directClient["config"].baseUrl,
|
|
325
|
+
proxyBasePath: config.proxyBasePath || this.proxyBasePath,
|
|
326
|
+
headers: {
|
|
327
|
+
...this.directClient["config"].headers,
|
|
328
|
+
...config.headers
|
|
329
|
+
},
|
|
330
|
+
timeout: config.timeout || this.directClient["config"].timeout,
|
|
331
|
+
fetch: config.fetch || this.fetchImpl
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
function createUniversalClient(config) {
|
|
336
|
+
return new UniversalClient(config);
|
|
337
|
+
}
|
|
338
|
+
var _universalClientInstance = null;
|
|
339
|
+
function configureUniversalClient(config) {
|
|
340
|
+
_universalClientInstance = new UniversalClient(config);
|
|
341
|
+
}
|
|
342
|
+
function getUniversalClient() {
|
|
343
|
+
if (!_universalClientInstance) {
|
|
344
|
+
_universalClientInstance = new UniversalClient();
|
|
345
|
+
}
|
|
346
|
+
return _universalClientInstance;
|
|
347
|
+
}
|
|
348
|
+
var universalClient = new Proxy({}, {
|
|
349
|
+
get(_target, prop) {
|
|
350
|
+
const instance = getUniversalClient();
|
|
351
|
+
return instance[prop];
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
export { ApiClientError, ContractClient, UniversalClient, universalClient as client, configureUniversalClient as configureClient, configureUniversalClient, createUniversalClient as createClient, createUniversalClient, getServerErrorDetails, getServerErrorType, getUniversalClient, isHttpError, isNetworkError, isServerError, isTimeoutError, universalClient };
|
|
184
356
|
//# sourceMappingURL=index.js.map
|
|
185
357
|
//# sourceMappingURL=index.js.map
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/contract-client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Contract-Based API Client\n *\n * Type-safe HTTP client that works with RouteContract for full end-to-end type safety\n */\nimport type { RouteContract, InferContract } from '../route';\n\nexport type RequestInterceptor = (\n url: string,\n init: RequestInit\n) => Promise<RequestInit> | RequestInit;\n\nexport interface ClientConfig\n{\n /**\n * API base URL (e.g., http://localhost:4000)\n */\n baseUrl?: string;\n\n /**\n * Default headers to include in all requests\n */\n headers?: Record<string, string>;\n\n /**\n * Request timeout in milliseconds\n */\n timeout?: number;\n\n /**\n * Custom fetch implementation\n */\n fetch?: typeof fetch;\n}\n\nexport interface CallOptions<TContract extends RouteContract>\n{\n params?: InferContract<TContract>['params'];\n query?: InferContract<TContract>['query'];\n body?: InferContract<TContract>['body'];\n headers?: Record<string, string>;\n baseUrl?: string;\n}\n\n/**\n * API Client Error\n */\nexport class ApiClientError 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?: 'timeout' | 'network' | 'http'\n )\n {\n super(message);\n this.name = 'ApiClientError';\n }\n}\n\n/**\n * Contract-based API Client\n */\nexport class ContractClient\n{\n private readonly config: Required<ClientConfig>;\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(config: ClientConfig = {})\n {\n this.config = {\n baseUrl: config.baseUrl || process.env.SERVER_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',\n headers: config.headers || {},\n timeout: config.timeout || 30000,\n fetch: config.fetch || globalThis.fetch.bind(globalThis),\n };\n }\n\n /**\n * Add request interceptor\n */\n use(interceptor: RequestInterceptor): void\n {\n this.interceptors.push(interceptor);\n }\n\n /**\n * Make a type-safe API call using a contract\n *\n * @param contract - Route contract with absolute path\n * @param options - Call options (params, query, body, headers)\n */\n async call<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): Promise<InferContract<TContract>['response']>\n {\n const baseUrl = options?.baseUrl || this.config.baseUrl;\n\n // Use contract.path directly (contracts use absolute paths)\n const urlPath = ContractClient.buildUrl(\n contract.path,\n options?.params as Record<string, string | number> | undefined\n );\n\n const queryString = ContractClient.buildQuery(\n options?.query as Record<string, string | string[] | number | boolean> | undefined\n );\n\n const url = `${baseUrl}${urlPath}${queryString}`;\n\n const method = ContractClient.getHttpMethod(contract, options);\n\n const headers: Record<string, string> = {\n ...this.config.headers,\n ...options?.headers,\n };\n\n const isFormData = ContractClient.isFormData(options?.body);\n\n if (options?.body !== undefined && !isFormData && !headers['Content-Type'])\n {\n headers['Content-Type'] = 'application/json';\n }\n\n let init: RequestInit = {\n method,\n headers,\n };\n\n if (options?.body !== undefined)\n {\n init.body = isFormData ? (options.body as FormData) : JSON.stringify(options.body);\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n init.signal = controller.signal;\n\n for (const interceptor of this.interceptors)\n {\n init = await interceptor(url, init);\n }\n\n const response = await this.config.fetch(url, init).catch((error) =>\n {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name === 'AbortError')\n {\n throw new ApiClientError(\n `Request timed out after ${this.config.timeout}ms`,\n 0,\n url,\n undefined,\n 'timeout'\n );\n }\n\n if (error instanceof Error)\n {\n throw new ApiClientError(\n `Network error: ${error.message}`,\n 0,\n url,\n undefined,\n 'network'\n );\n }\n\n throw error;\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok)\n {\n const errorBody = await response.json().catch(() => null);\n throw new ApiClientError(\n `${method} ${urlPath} failed: ${response.status} ${response.statusText}`,\n response.status,\n url,\n errorBody,\n 'http'\n );\n }\n\n const data = await response.json();\n return data as InferContract<TContract>['response'];\n }\n\n /**\n * Create a new client with merged configuration\n */\n withConfig(config: Partial<ClientConfig>): ContractClient\n {\n return new ContractClient({\n baseUrl: config.baseUrl || this.config.baseUrl,\n headers: { ...this.config.headers, ...config.headers },\n timeout: config.timeout || this.config.timeout,\n fetch: config.fetch || this.config.fetch,\n });\n }\n\n private static buildUrl(path: string, params?: Record<string, string | number>): string\n {\n if (!params) return path;\n\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, String(value));\n }\n\n return url;\n }\n\n private static buildQuery(query?: Record<string, string | string[] | number | boolean>): string\n {\n if (!query || Object.keys(query).length === 0) return '';\n\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => params.append(key, String(v)));\n }\n else if (value !== undefined && value !== null)\n {\n params.append(key, String(value));\n }\n }\n\n const queryString = params.toString();\n return queryString ? `?${queryString}` : '';\n }\n\n private static getHttpMethod<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): string\n {\n if ('method' in contract && typeof contract.method === 'string')\n {\n return contract.method.toUpperCase();\n }\n\n if (options?.body !== undefined)\n {\n return 'POST';\n }\n\n return 'GET';\n }\n\n private static isFormData(body: unknown): body is FormData\n {\n return body instanceof FormData;\n }\n}\n\n/**\n * Create a new contract-based API client\n */\nexport function createClient(config?: ClientConfig): ContractClient\n{\n return new ContractClient(config);\n}\n\n/**\n * Global client singleton instance\n */\nlet _clientInstance: ContractClient = new ContractClient();\n\n/**\n * Configure the global client instance\n *\n * Call this in your app initialization to set default configuration\n * for all auto-generated API calls.\n *\n * @example\n * ```ts\n * // In app initialization (layout.tsx, _app.tsx, etc)\n * import { configureClient } from '@spfn/core/client';\n *\n * configureClient({\n * baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',\n * timeout: 60000,\n * headers: {\n * 'X-App-Version': '1.0.0'\n * }\n * });\n *\n * // Add interceptors\n * import { client } from '@spfn/core/client';\n * client.use(async (url, init) => {\n * // Add auth header\n * return {\n * ...init,\n * headers: {\n * ...init.headers,\n * Authorization: `Bearer ${getToken()}`\n * }\n * };\n * });\n * ```\n */\nexport function configureClient(config: ClientConfig): void\n{\n _clientInstance = new ContractClient(config);\n}\n\n/**\n * Global client singleton with Proxy\n *\n * This client can be configured using configureClient() before use.\n * Used by auto-generated API client code.\n */\nexport const client = new Proxy({} as ContractClient, {\n get(_target, prop)\n {\n return _clientInstance[prop as keyof ContractClient];\n }\n});\n\n/**\n * Type guard for timeout errors\n *\n * @example\n * ```ts\n * try {\n * await api.users.getById({ params: { id: '123' } });\n * } catch (error) {\n * if (isTimeoutError(error)) {\n * console.error('Request timed out, retrying...');\n * // Implement retry logic\n * }\n * }\n * ```\n */\nexport function isTimeoutError(error: unknown): error is ApiClientError\n{\n return error instanceof ApiClientError && error.errorType === 'timeout';\n}\n\n/**\n * Type guard for network errors\n *\n * @example\n * ```ts\n * try {\n * await api.users.list();\n * } catch (error) {\n * if (isNetworkError(error)) {\n * showOfflineMessage();\n * }\n * }\n * ```\n */\nexport function isNetworkError(error: unknown): error is ApiClientError\n{\n return error instanceof ApiClientError && error.errorType === 'network';\n}\n\n/**\n * Type guard for HTTP errors (4xx, 5xx)\n *\n * @example\n * ```ts\n * try {\n * await api.users.create({ body: userData });\n * } catch (error) {\n * if (isHttpError(error)) {\n * if (error.status === 401) {\n * redirectToLogin();\n * } else if (error.status === 404) {\n * showNotFoundMessage();\n * }\n * }\n * }\n * ```\n */\nexport function isHttpError(error: unknown): error is ApiClientError\n{\n return error instanceof ApiClientError && error.errorType === 'http';\n}\n\n/**\n * Check if error is a specific server error type\n *\n * @example\n * ```ts\n * try {\n * await api.workflows.getById({ params: { uuid: 'xxx' } });\n * } catch (error) {\n * if (isServerError(error, 'NotFoundError')) {\n * showNotFoundMessage();\n * } else if (isServerError(error, 'ValidationError')) {\n * showValidationErrors(getServerErrorDetails(error));\n * }\n * }\n * ```\n */\nexport function isServerError(error: unknown, errorType: string): error is ApiClientError\n{\n if (!isHttpError(error)) return false;\n const response = error.response as any;\n return response?.error?.type === errorType;\n}\n\n/**\n * Get server error type from ApiClientError\n *\n * @example\n * ```ts\n * const errorType = getServerErrorType(error);\n * // 'NotFoundError', 'ValidationError', 'PaymentFailedError', etc.\n * ```\n */\nexport function getServerErrorType(error: ApiClientError): string | undefined\n{\n const response = error.response as any;\n return response?.error?.type;\n}\n\n/**\n * Get server error details from ApiClientError\n *\n * @example\n * ```ts\n * if (isServerError(error, 'PaymentFailedError')) {\n * const details = getServerErrorDetails(error);\n * console.log('Payment ID:', details.paymentId);\n * }\n * ```\n */\nexport function getServerErrorDetails<T = any>(error: ApiClientError): T | undefined\n{\n const response = error.response as any;\n return response?.error?.details;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/contract-client.ts","../../src/client/universal-client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Contract-Based API Client\n *\n * Type-safe HTTP client that works with RouteContract for full end-to-end type safety\n */\nimport type { RouteContract, InferContract } from '../route';\n\nexport type RequestInterceptor = (\n url: string,\n init: RequestInit\n) => Promise<RequestInit> | RequestInit;\n\nexport interface ClientConfig\n{\n /**\n * API base URL (e.g., http://localhost:4000)\n */\n baseUrl?: string;\n\n /**\n * Default headers to include in all requests\n */\n headers?: Record<string, string>;\n\n /**\n * Request timeout in milliseconds\n */\n timeout?: number;\n\n /**\n * Custom fetch implementation\n */\n fetch?: typeof fetch;\n}\n\nexport interface CallOptions<TContract extends RouteContract>\n{\n params?: InferContract<TContract>['params'];\n query?: InferContract<TContract>['query'];\n body?: InferContract<TContract>['body'];\n headers?: Record<string, string>;\n baseUrl?: string;\n\n /**\n * Additional fetch options (extends RequestInit)\n *\n * Can be used for environment-specific options like Next.js cache/next\n *\n * @example\n * ```ts\n * // Next.js time-based revalidation\n * { fetchOptions: { next: { revalidate: 60 } } }\n *\n * // Next.js disable cache\n * { fetchOptions: { cache: 'no-store' } }\n *\n * // Next.js on-demand revalidation\n * { fetchOptions: { next: { tags: ['products'] } } }\n * ```\n */\n fetchOptions?: Record<string, any>;\n}\n\n/**\n * API Client Error\n */\nexport class ApiClientError 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?: 'timeout' | 'network' | 'http'\n )\n {\n super(message);\n this.name = 'ApiClientError';\n }\n}\n\n/**\n * Contract-based API Client\n */\nexport class ContractClient\n{\n private readonly config: Required<ClientConfig>;\n private readonly interceptors: RequestInterceptor[] = [];\n\n constructor(config: ClientConfig = {})\n {\n this.config = {\n baseUrl: config.baseUrl || process.env.SERVER_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',\n headers: config.headers || {},\n timeout: config.timeout || 30000,\n fetch: config.fetch || globalThis.fetch.bind(globalThis),\n };\n }\n\n /**\n * Add request interceptor\n */\n use(interceptor: RequestInterceptor): void\n {\n this.interceptors.push(interceptor);\n }\n\n /**\n * Make a type-safe API call using a contract\n *\n * @param contract - Route contract with absolute path\n * @param options - Call options (params, query, body, headers)\n */\n async call<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): Promise<InferContract<TContract>['response']>\n {\n const baseUrl = options?.baseUrl || this.config.baseUrl;\n\n // Use contract.path directly (contracts use absolute paths)\n const urlPath = ContractClient.buildUrl(\n contract.path,\n options?.params as Record<string, string | number> | undefined\n );\n\n const queryString = ContractClient.buildQuery(\n options?.query as Record<string, string | string[] | number | boolean> | undefined\n );\n\n const url = `${baseUrl}${urlPath}${queryString}`;\n\n const method = ContractClient.getHttpMethod(contract, options);\n\n const headers: Record<string, string> = {\n ...this.config.headers,\n ...options?.headers,\n };\n\n const isFormData = ContractClient.isFormData(options?.body);\n\n if (options?.body !== undefined && !isFormData && !headers['Content-Type'])\n {\n headers['Content-Type'] = 'application/json';\n }\n\n let init: RequestInit = {\n method,\n headers,\n ...options?.fetchOptions, // Spread environment-specific options (e.g., Next.js cache/next)\n };\n\n if (options?.body !== undefined)\n {\n init.body = isFormData ? (options.body as FormData) : JSON.stringify(options.body);\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n init.signal = controller.signal;\n\n for (const interceptor of this.interceptors)\n {\n init = await interceptor(url, init);\n }\n\n const response = await this.config.fetch(url, init).catch((error) =>\n {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name === 'AbortError')\n {\n throw new ApiClientError(\n `Request timed out after ${this.config.timeout}ms`,\n 0,\n url,\n undefined,\n 'timeout'\n );\n }\n\n if (error instanceof Error)\n {\n throw new ApiClientError(\n `Network error: ${error.message}`,\n 0,\n url,\n undefined,\n 'network'\n );\n }\n\n throw error;\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok)\n {\n const errorBody = await response.json().catch(() => null);\n throw new ApiClientError(\n `${method} ${urlPath} failed: ${response.status} ${response.statusText}`,\n response.status,\n url,\n errorBody,\n 'http'\n );\n }\n\n const data = await response.json();\n return data as InferContract<TContract>['response'];\n }\n\n /**\n * Create a new client with merged configuration\n */\n withConfig(config: Partial<ClientConfig>): ContractClient\n {\n return new ContractClient({\n baseUrl: config.baseUrl || this.config.baseUrl,\n headers: { ...this.config.headers, ...config.headers },\n timeout: config.timeout || this.config.timeout,\n fetch: config.fetch || this.config.fetch,\n });\n }\n\n private static buildUrl(path: string, params?: Record<string, string | number>): string\n {\n if (!params) return path;\n\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, String(value));\n }\n\n return url;\n }\n\n private static buildQuery(query?: Record<string, string | string[] | number | boolean>): string\n {\n if (!query || Object.keys(query).length === 0) return '';\n\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => params.append(key, String(v)));\n }\n else if (value !== undefined && value !== null)\n {\n params.append(key, String(value));\n }\n }\n\n const queryString = params.toString();\n return queryString ? `?${queryString}` : '';\n }\n\n private static getHttpMethod<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): string\n {\n if ('method' in contract && typeof contract.method === 'string')\n {\n return contract.method.toUpperCase();\n }\n\n if (options?.body !== undefined)\n {\n return 'POST';\n }\n\n return 'GET';\n }\n\n private static isFormData(body: unknown): body is FormData\n {\n return body instanceof FormData;\n }\n}\n\n/**\n * Create a new contract-based API client\n */\nexport function createClient(config?: ClientConfig): ContractClient\n{\n return new ContractClient(config);\n}\n\n/**\n * Global client singleton instance\n */\nlet _clientInstance: ContractClient = new ContractClient();\n\n/**\n * Configure the global client instance\n *\n * Call this in your app initialization to set default configuration\n * for all auto-generated API calls.\n *\n * @example\n * ```ts\n * // In app initialization (layout.tsx, _app.tsx, etc)\n * import { configureClient } from '@spfn/core/client';\n *\n * configureClient({\n * baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',\n * timeout: 60000,\n * headers: {\n * 'X-App-Version': '1.0.0'\n * }\n * });\n *\n * // Add interceptors\n * import { client } from '@spfn/core/client';\n * client.use(async (url, init) => {\n * // Add auth header\n * return {\n * ...init,\n * headers: {\n * ...init.headers,\n * Authorization: `Bearer ${getToken()}`\n * }\n * };\n * });\n * ```\n */\nexport function configureClient(config: ClientConfig): void\n{\n _clientInstance = new ContractClient(config);\n}\n\n/**\n * Global client singleton with Proxy\n *\n * This client can be configured using configureClient() before use.\n * Used by auto-generated API client code.\n */\nexport const client = new Proxy({} as ContractClient, {\n get(_target, prop)\n {\n return _clientInstance[prop as keyof ContractClient];\n }\n});\n\n/**\n * Type guard for timeout errors\n *\n * @example\n * ```ts\n * try {\n * await api.users.getById({ params: { id: '123' } });\n * } catch (error) {\n * if (isTimeoutError(error)) {\n * console.error('Request timed out, retrying...');\n * // Implement retry logic\n * }\n * }\n * ```\n */\nexport function isTimeoutError(error: unknown): error is ApiClientError\n{\n return error instanceof ApiClientError && error.errorType === 'timeout';\n}\n\n/**\n * Type guard for network errors\n *\n * @example\n * ```ts\n * try {\n * await api.users.list();\n * } catch (error) {\n * if (isNetworkError(error)) {\n * showOfflineMessage();\n * }\n * }\n * ```\n */\nexport function isNetworkError(error: unknown): error is ApiClientError\n{\n return error instanceof ApiClientError && error.errorType === 'network';\n}\n\n/**\n * Type guard for HTTP errors (4xx, 5xx)\n *\n * @example\n * ```ts\n * try {\n * await api.users.create({ body: userData });\n * } catch (error) {\n * if (isHttpError(error)) {\n * if (error.status === 401) {\n * redirectToLogin();\n * } else if (error.status === 404) {\n * showNotFoundMessage();\n * }\n * }\n * }\n * ```\n */\nexport function isHttpError(error: unknown): error is ApiClientError\n{\n return error instanceof ApiClientError && error.errorType === 'http';\n}\n\n/**\n * Check if error is a specific server error type\n *\n * @example\n * ```ts\n * try {\n * await api.workflows.getById({ params: { uuid: 'xxx' } });\n * } catch (error) {\n * if (isServerError(error, 'NotFoundError')) {\n * showNotFoundMessage();\n * } else if (isServerError(error, 'ValidationError')) {\n * showValidationErrors(getServerErrorDetails(error));\n * }\n * }\n * ```\n */\nexport function isServerError(error: unknown, errorType: string): error is ApiClientError\n{\n if (!isHttpError(error)) return false;\n const response = error.response as any;\n return response?.error?.type === errorType;\n}\n\n/**\n * Get server error type from ApiClientError\n *\n * @example\n * ```ts\n * const errorType = getServerErrorType(error);\n * // 'NotFoundError', 'ValidationError', 'PaymentFailedError', etc.\n * ```\n */\nexport function getServerErrorType(error: ApiClientError): string | undefined\n{\n const response = error.response as any;\n return response?.error?.type;\n}\n\n/**\n * Get server error details from ApiClientError\n *\n * @example\n * ```ts\n * if (isServerError(error, 'PaymentFailedError')) {\n * const details = getServerErrorDetails(error);\n * console.log('Payment ID:', details.paymentId);\n * }\n * ```\n */\nexport function getServerErrorDetails<T = any>(error: ApiClientError): T | undefined\n{\n const response = error.response as any;\n return response?.error?.details;\n}\n","/**\n * Universal API Client\n *\n * Automatically routes requests based on execution environment:\n * - Server Environment: Direct call to SPFN API (internal network)\n * - Browser Environment: Proxies through Next.js API Route (cookie forwarding)\n */\n\nimport type { RouteContract, InferContract } from '../route';\nimport { ContractClient, type CallOptions, ApiClientError } from './contract-client';\n\n// Type declaration for window (available in browser)\ndeclare const window: unknown | undefined;\n\n/**\n * Detect if code is running in server environment\n *\n * Uses typeof window check for reliable browser detection\n */\nfunction isServerEnvironment(): boolean\n{\n return typeof window === 'undefined';\n}\n\n/**\n * Request interceptor function\n *\n * Called before each request to modify headers dynamically\n * Useful for adding authentication tokens, session data, etc.\n *\n * @param headers - Current request headers (mutable)\n * @param contract - Route contract being called\n * @returns Modified headers or void (modify in place)\n */\nexport type RequestInterceptor = (\n headers: Record<string, string>,\n contract: RouteContract\n) => Promise<void> | void;\n\n/**\n * Universal Client Configuration\n */\nexport interface UniversalClientConfig\n{\n /**\n * SPFN API server URL (for server-side direct calls)\n *\n * @default process.env.SERVER_API_URL || process.env.SPFN_API_URL || 'http://localhost:8790'\n */\n apiUrl?: string;\n\n /**\n * Next.js API route base path (for client-side proxy calls)\n *\n * @default '/api/actions'\n * @example '/api/proxy'\n */\n proxyBasePath?: string;\n\n /**\n * Additional headers to include in all requests\n */\n headers?: Record<string, string>;\n\n /**\n * Request timeout in milliseconds\n *\n * @default 30000\n */\n timeout?: number;\n\n /**\n * Custom fetch implementation\n */\n fetch?: typeof fetch;\n}\n\n/**\n * Universal API Client\n *\n * Automatically detects execution environment and routes requests accordingly:\n *\n * **Server Environment** (Next.js Server Components, API Routes):\n * - Direct HTTP call to SPFN API server\n * - Uses internal network (e.g., http://localhost:8790)\n * - No proxy overhead\n *\n * **Browser Environment** (Next.js Client Components):\n * - Routes through Next.js API Route proxy (e.g., /api/proxy/*)\n * - Enables HttpOnly cookie forwarding\n * - Maintains CORS security\n *\n * @example\n * ```typescript\n * // Server Component - direct call\n * import { createUniversalClient } from '@spfn/core/client';\n * const client = createUniversalClient();\n * const result = await client.call(loginContract, { body: {...} });\n *\n * // Client Component - proxied call (automatic)\n * 'use client';\n * const client = createUniversalClient();\n * const result = await client.call(loginContract, { body: {...} }); // Goes through /api/proxy\n * ```\n */\nexport class UniversalClient\n{\n private readonly directClient: ContractClient;\n private readonly proxyBasePath: string;\n private readonly isServer: boolean;\n private readonly fetchImpl: typeof fetch;\n\n constructor(config: UniversalClientConfig = {})\n {\n // Detect environment once during construction\n this.isServer = isServerEnvironment();\n\n // Direct client for server-side calls\n this.directClient = new ContractClient({\n baseUrl: config.apiUrl,\n headers: config.headers,\n timeout: config.timeout,\n fetch: config.fetch,\n });\n\n // Proxy configuration for client-side calls\n this.proxyBasePath = config.proxyBasePath || '/api/actions';\n\n // Fetch implementation\n this.fetchImpl = config.fetch || globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Make a type-safe API call using a contract\n *\n * Automatically routes based on environment:\n * - Server: Direct SPFN API call\n * - Browser: Next.js API Route proxy\n *\n * @param contract - Route contract with absolute path\n * @param options - Call options (params, query, body, headers)\n */\n async call<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): Promise<InferContract<TContract>['response']>\n {\n if (this.isServer)\n {\n // Server environment: Direct call to SPFN API\n return this.directClient.call(contract, options);\n }\n else\n {\n // Browser environment: Proxy through Next.js API Route\n return this.callViaProxy(contract, options);\n }\n }\n\n /**\n * Call via Next.js API Route proxy (client-side)\n *\n * Routes request through /api/proxy/[...path] to enable:\n * - HttpOnly cookie forwarding\n * - CORS security\n * - Server-side session management\n *\n * @private\n */\n private async callViaProxy<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): Promise<InferContract<TContract>['response']>\n {\n // Build proxy URL: /api/proxy + contract.path\n // Example: /_auth/login -> /api/proxy/_auth/login\n const path = this.buildUrlPath(\n contract.path,\n options?.params as Record<string, string | number> | undefined\n );\n const queryString = this.buildQueryString(\n options?.query as Record<string, string | string[] | number | boolean> | undefined\n );\n const proxyUrl = `${this.proxyBasePath}${path}${queryString}`;\n\n const method = this.getHttpMethod(contract, options);\n\n const headers: Record<string, string> = {\n ...options?.headers,\n };\n\n const isFormData = this.isFormData(options?.body);\n\n // Set Content-Type for JSON bodies\n if (options?.body !== undefined && !isFormData && !headers['Content-Type'])\n {\n headers['Content-Type'] = 'application/json';\n }\n\n const init: RequestInit = {\n method,\n headers,\n credentials: 'include', // Important: Include cookies for session\n ...options?.fetchOptions, // Spread environment-specific options (e.g., Next.js cache/next)\n };\n\n // Add body for POST/PUT/PATCH\n if (options?.body !== undefined)\n {\n init.body = isFormData\n ? (options.body as FormData)\n : JSON.stringify(options.body);\n }\n\n const response = await this.fetchImpl(proxyUrl, init);\n\n if (!response.ok)\n {\n const errorBody = await response.json().catch(() => null);\n throw new ApiClientError(\n `${method} ${path} failed: ${response.status} ${response.statusText}`,\n response.status,\n proxyUrl,\n errorBody,\n 'http'\n );\n }\n\n const data = await response.json();\n return data as InferContract<TContract>['response'];\n }\n\n /**\n * Build URL path with parameter substitution\n */\n private buildUrlPath(\n path: string,\n params?: Record<string, string | number>\n ): string\n {\n if (!params) return path;\n\n let url = path;\n for (const [key, value] of Object.entries(params))\n {\n url = url.replace(`:${key}`, String(value));\n }\n\n return url;\n }\n\n /**\n * Build query string from query parameters\n */\n private buildQueryString(\n query?: Record<string, string | string[] | number | boolean>\n ): string\n {\n if (!query || Object.keys(query).length === 0) return '';\n\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query))\n {\n if (Array.isArray(value))\n {\n value.forEach((v) => params.append(key, String(v)));\n }\n else if (value !== undefined && value !== null)\n {\n params.append(key, String(value));\n }\n }\n\n const queryString = params.toString();\n return queryString ? `?${queryString}` : '';\n }\n\n /**\n * Get HTTP method from contract or infer from options\n */\n private getHttpMethod<TContract extends RouteContract>(\n contract: TContract,\n options?: CallOptions<TContract>\n ): string\n {\n if ('method' in contract && typeof contract.method === 'string')\n {\n return contract.method.toUpperCase();\n }\n\n if (options?.body !== undefined)\n {\n return 'POST';\n }\n\n return 'GET';\n }\n\n /**\n * Check if body is FormData\n */\n private isFormData(body: unknown): body is FormData\n {\n return typeof FormData !== 'undefined' && body instanceof FormData;\n }\n\n /**\n * Check if currently running in server environment\n */\n isServerEnv(): boolean\n {\n return this.isServer;\n }\n\n /**\n * Create a new client with merged configuration\n */\n withConfig(config: Partial<UniversalClientConfig>): UniversalClient\n {\n return new UniversalClient({\n apiUrl: config.apiUrl || this.directClient['config'].baseUrl,\n proxyBasePath: config.proxyBasePath || this.proxyBasePath,\n headers: {\n ...this.directClient['config'].headers,\n ...config.headers,\n },\n timeout: config.timeout || this.directClient['config'].timeout,\n fetch: config.fetch || this.fetchImpl,\n });\n }\n}\n\n/**\n * Create a new universal API client\n *\n * @example\n * ```typescript\n * // Default configuration\n * const client = createUniversalClient();\n *\n * // Custom configuration\n * const client = createUniversalClient({\n * apiUrl: 'http://localhost:4000',\n * proxyBasePath: '/api/spfn',\n * headers: { 'X-App-Version': '1.0.0' },\n * });\n * ```\n */\nexport function createUniversalClient(config?: UniversalClientConfig): UniversalClient\n{\n return new UniversalClient(config);\n}\n\n/**\n * Global universal client singleton instance\n */\nlet _universalClientInstance: UniversalClient | null = null;\n\n/**\n * Configure the global universal client instance\n *\n * Call this in your app initialization to set default configuration\n * for all auto-generated API calls.\n *\n * @example\n * ```typescript\n * // In app initialization (layout.tsx, _app.tsx, etc)\n * import { configureUniversalClient } from '@spfn/core/client';\n *\n * configureUniversalClient({\n * apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8790',\n * proxyBasePath: '/api/proxy',\n * headers: {\n * 'X-App-Version': '1.0.0'\n * }\n * });\n * ```\n */\nexport function configureUniversalClient(config: UniversalClientConfig): void\n{\n _universalClientInstance = new UniversalClient(config);\n}\n\n/**\n * Get the global universal client instance\n *\n * Creates a default instance if not configured\n */\nexport function getUniversalClient(): UniversalClient\n{\n if (!_universalClientInstance)\n {\n _universalClientInstance = new UniversalClient();\n }\n\n return _universalClientInstance;\n}\n\n/**\n * Global universal client singleton with Proxy\n *\n * This client can be configured using configureUniversalClient() before use.\n * Used by auto-generated API client code.\n */\nexport const universalClient = new Proxy({} as UniversalClient, {\n get(_target, prop)\n {\n const instance = getUniversalClient();\n return instance[prop as keyof UniversalClient];\n }\n});"]}
|