@spfn/core 0.1.0-alpha.85 → 0.1.0-alpha.88

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.
@@ -0,0 +1,557 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * SPFN Next.js Proxy Interceptor Types
5
+ */
6
+
7
+ /**
8
+ * Request Interceptor Context
9
+ *
10
+ * Available before calling SPFN API
11
+ */
12
+ interface RequestInterceptorContext {
13
+ /**
14
+ * Request path (e.g., '/_auth/login')
15
+ */
16
+ path: string;
17
+ /**
18
+ * HTTP method (e.g., 'POST')
19
+ */
20
+ method: string;
21
+ /**
22
+ * Request headers (mutable)
23
+ */
24
+ headers: Record<string, string>;
25
+ /**
26
+ * Request body (mutable)
27
+ */
28
+ body?: any;
29
+ /**
30
+ * Query parameters from original request
31
+ */
32
+ query: Record<string, string | string[]>;
33
+ /**
34
+ * Cookies from Next.js request
35
+ */
36
+ cookies: Map<string, string>;
37
+ /**
38
+ * Original Next.js request
39
+ */
40
+ request: NextRequest;
41
+ /**
42
+ * Metadata for sharing data between interceptors
43
+ */
44
+ metadata: Record<string, any>;
45
+ }
46
+ /**
47
+ * Response Interceptor Context
48
+ *
49
+ * Available after SPFN API responds
50
+ */
51
+ interface ResponseInterceptorContext {
52
+ /**
53
+ * Request path
54
+ */
55
+ path: string;
56
+ /**
57
+ * HTTP method
58
+ */
59
+ method: string;
60
+ /**
61
+ * Original request data (immutable)
62
+ */
63
+ request: {
64
+ headers: Record<string, string>;
65
+ body?: any;
66
+ };
67
+ /**
68
+ * Response data (mutable)
69
+ */
70
+ response: {
71
+ status: number;
72
+ statusText: string;
73
+ headers: Headers;
74
+ body: any;
75
+ };
76
+ /**
77
+ * Cookies to set in response
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * ctx.setCookies.push({
82
+ * name: 'session',
83
+ * value: 'xxx',
84
+ * options: { httpOnly: true, maxAge: 3600 }
85
+ * });
86
+ * ```
87
+ */
88
+ setCookies: Array<{
89
+ name: string;
90
+ value: string;
91
+ options?: {
92
+ httpOnly?: boolean;
93
+ secure?: boolean;
94
+ sameSite?: 'strict' | 'lax' | 'none';
95
+ maxAge?: number;
96
+ path?: string;
97
+ domain?: string;
98
+ };
99
+ }>;
100
+ /**
101
+ * Metadata shared from request interceptors
102
+ */
103
+ metadata: Record<string, any>;
104
+ }
105
+ /**
106
+ * Request Interceptor Function
107
+ *
108
+ * @param context - Request context (mutable)
109
+ * @param next - Call to continue to next interceptor
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * const interceptor: RequestInterceptor = async (ctx, next) => {
114
+ * // Modify headers
115
+ * ctx.headers['Authorization'] = 'Bearer token';
116
+ *
117
+ * // Store data for response interceptor
118
+ * ctx.metadata.userId = '123';
119
+ *
120
+ * // Continue to next interceptor
121
+ * await next();
122
+ * };
123
+ * ```
124
+ */
125
+ type RequestInterceptor = (context: RequestInterceptorContext, next: () => Promise<void>) => Promise<void>;
126
+ /**
127
+ * Response Interceptor Function
128
+ *
129
+ * @param context - Response context (mutable)
130
+ * @param next - Call to continue to next interceptor
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const interceptor: ResponseInterceptor = async (ctx, next) => {
135
+ * // Modify response body
136
+ * ctx.response.body = { ...ctx.response.body, extra: 'data' };
137
+ *
138
+ * // Set cookie
139
+ * ctx.setCookies.push({
140
+ * name: 'session',
141
+ * value: 'xxx',
142
+ * options: { httpOnly: true }
143
+ * });
144
+ *
145
+ * // Continue to next interceptor
146
+ * await next();
147
+ * };
148
+ * ```
149
+ */
150
+ type ResponseInterceptor = (context: ResponseInterceptorContext, next: () => Promise<void>) => Promise<void>;
151
+ /**
152
+ * Interceptor Rule
153
+ *
154
+ * Defines when and how to intercept requests/responses
155
+ */
156
+ interface InterceptorRule {
157
+ /**
158
+ * Path pattern to match
159
+ *
160
+ * - String with wildcards: '/_auth/*', '/users/:id'
161
+ * - RegExp: /^\/_auth\/.+$/
162
+ * - '*' matches all paths
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * pathPattern: '/_auth/*' // matches /_auth/login, /_auth/register
167
+ * pathPattern: '/users/:id' // matches /users/123, /users/456
168
+ * pathPattern: /^\/_auth\/.+$/ // regex match
169
+ * pathPattern: '*' // matches all paths
170
+ * ```
171
+ */
172
+ pathPattern: string | RegExp;
173
+ /**
174
+ * HTTP method(s) to match (optional)
175
+ *
176
+ * - Single method: 'POST'
177
+ * - Multiple methods: ['POST', 'PUT']
178
+ * - Omit to match all methods
179
+ *
180
+ * @default undefined (matches all methods)
181
+ */
182
+ method?: string | string[];
183
+ /**
184
+ * Request interceptor
185
+ *
186
+ * Called before SPFN API request
187
+ */
188
+ request?: RequestInterceptor;
189
+ /**
190
+ * Response interceptor
191
+ *
192
+ * Called after SPFN API response
193
+ */
194
+ response?: ResponseInterceptor;
195
+ }
196
+ /**
197
+ * Proxy Configuration
198
+ */
199
+ interface ProxyConfig {
200
+ /**
201
+ * SPFN API base URL
202
+ *
203
+ * @default process.env.SERVER_API_URL || process.env.SPFN_API_URL || 'http://localhost:8790'
204
+ */
205
+ apiUrl?: string;
206
+ /**
207
+ * Additional custom interceptors
208
+ *
209
+ * These are executed after auto-discovered interceptors
210
+ *
211
+ * Executed in order: first registered -> last registered
212
+ */
213
+ interceptors?: InterceptorRule[];
214
+ /**
215
+ * Enable automatic interceptor discovery from registry
216
+ *
217
+ * When enabled, all interceptors registered via registerInterceptors()
218
+ * are automatically applied to the proxy.
219
+ *
220
+ * @default true
221
+ */
222
+ autoDiscoverInterceptors?: boolean;
223
+ /**
224
+ * Disable interceptors from specific packages
225
+ *
226
+ * Use this to exclude auto-discovered interceptors from certain packages
227
+ * when you want to provide custom implementations.
228
+ *
229
+ * @example ['auth', 'storage']
230
+ */
231
+ disableAutoInterceptors?: string[];
232
+ /**
233
+ * Enable debug logging
234
+ *
235
+ * @default false
236
+ */
237
+ debug?: boolean;
238
+ }
239
+
240
+ /**
241
+ * SPFN Next.js API Route Proxy with Interceptor Pattern
242
+ *
243
+ * Automatically proxies requests to SPFN API server with:
244
+ * - Cookie forwarding
245
+ * - Request/Response interceptors
246
+ * - Flexible header manipulation
247
+ *
248
+ * Usage:
249
+ * ```typescript
250
+ * // Basic usage (no interceptors)
251
+ * // app/api/actions/[...path]/route.ts
252
+ * export { GET, POST, PUT, DELETE, PATCH } from '@spfn/core/nextjs';
253
+ *
254
+ * // With interceptors
255
+ * import { createProxy } from '@spfn/core/nextjs';
256
+ *
257
+ * export const { GET, POST } = createProxy({
258
+ * interceptors: [
259
+ * {
260
+ * pathPattern: '/_auth/*',
261
+ * request: async (ctx, next) => {
262
+ * ctx.headers['Authorization'] = 'Bearer token';
263
+ * await next();
264
+ * }
265
+ * }
266
+ * ]
267
+ * });
268
+ * ```
269
+ */
270
+
271
+ /**
272
+ * Create proxy with custom configuration and interceptors
273
+ *
274
+ * @param config - Proxy configuration with interceptors
275
+ * @returns HTTP method handlers for Next.js API routes
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * // app/api/actions/[...path]/route.ts
280
+ * import { createProxy } from '@spfn/core/nextjs';
281
+ *
282
+ * export const { GET, POST, PUT, DELETE, PATCH } = createProxy({
283
+ * apiUrl: 'http://localhost:8790',
284
+ * debug: true,
285
+ * interceptors: [
286
+ * {
287
+ * pathPattern: '/_auth/*',
288
+ * method: 'POST',
289
+ * request: async (ctx, next) => {
290
+ * const session = await getSession();
291
+ * if (session) {
292
+ * ctx.headers['Authorization'] = `Bearer ${session.token}`;
293
+ * }
294
+ * await next();
295
+ * },
296
+ * response: async (ctx, next) => {
297
+ * if (ctx.response.status === 200) {
298
+ * ctx.setCookies.push({
299
+ * name: 'session',
300
+ * value: ctx.response.body.token,
301
+ * options: { httpOnly: true, maxAge: 3600 }
302
+ * });
303
+ * }
304
+ * await next();
305
+ * }
306
+ * }
307
+ * ]
308
+ * });
309
+ * ```
310
+ */
311
+ declare function createProxy(config?: ProxyConfig): {
312
+ GET: (request: NextRequest, context: {
313
+ params: Promise<{
314
+ path: string[];
315
+ }> | {
316
+ path: string[];
317
+ };
318
+ }) => Promise<NextResponse<unknown>>;
319
+ POST: (request: NextRequest, context: {
320
+ params: Promise<{
321
+ path: string[];
322
+ }> | {
323
+ path: string[];
324
+ };
325
+ }) => Promise<NextResponse<unknown>>;
326
+ PUT: (request: NextRequest, context: {
327
+ params: Promise<{
328
+ path: string[];
329
+ }> | {
330
+ path: string[];
331
+ };
332
+ }) => Promise<NextResponse<unknown>>;
333
+ PATCH: (request: NextRequest, context: {
334
+ params: Promise<{
335
+ path: string[];
336
+ }> | {
337
+ path: string[];
338
+ };
339
+ }) => Promise<NextResponse<unknown>>;
340
+ DELETE: (request: NextRequest, context: {
341
+ params: Promise<{
342
+ path: string[];
343
+ }> | {
344
+ path: string[];
345
+ };
346
+ }) => Promise<NextResponse<unknown>>;
347
+ };
348
+ declare const GET: (request: NextRequest, context: {
349
+ params: Promise<{
350
+ path: string[];
351
+ }> | {
352
+ path: string[];
353
+ };
354
+ }) => Promise<NextResponse<unknown>>;
355
+ declare const POST: (request: NextRequest, context: {
356
+ params: Promise<{
357
+ path: string[];
358
+ }> | {
359
+ path: string[];
360
+ };
361
+ }) => Promise<NextResponse<unknown>>;
362
+ declare const PUT: (request: NextRequest, context: {
363
+ params: Promise<{
364
+ path: string[];
365
+ }> | {
366
+ path: string[];
367
+ };
368
+ }) => Promise<NextResponse<unknown>>;
369
+ declare const PATCH: (request: NextRequest, context: {
370
+ params: Promise<{
371
+ path: string[];
372
+ }> | {
373
+ path: string[];
374
+ };
375
+ }) => Promise<NextResponse<unknown>>;
376
+ declare const DELETE: (request: NextRequest, context: {
377
+ params: Promise<{
378
+ path: string[];
379
+ }> | {
380
+ path: string[];
381
+ };
382
+ }) => Promise<NextResponse<unknown>>;
383
+
384
+ /**
385
+ * Global Interceptor Registry
386
+ *
387
+ * Allows packages to automatically register their interceptors
388
+ * for Next.js proxy without manual configuration.
389
+ */
390
+
391
+ /**
392
+ * Global interceptor registry
393
+ *
394
+ * Packages register their interceptors on import,
395
+ * and proxy automatically discovers and applies them.
396
+ */
397
+ declare class InterceptorRegistry {
398
+ private interceptors;
399
+ /**
400
+ * Register interceptors for a package
401
+ *
402
+ * @param packageName - Unique package identifier (e.g., 'auth', 'storage')
403
+ * @param interceptors - Array of interceptor rules
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * registerInterceptors('auth', [
408
+ * {
409
+ * pathPattern: '/_auth/*',
410
+ * request: async (ctx, next) => { ... }
411
+ * }
412
+ * ]);
413
+ * ```
414
+ */
415
+ register(packageName: string, interceptors: InterceptorRule[]): void;
416
+ /**
417
+ * Get all registered interceptors
418
+ *
419
+ * @param exclude - Package names to exclude
420
+ * @returns Flat array of all interceptor rules
421
+ */
422
+ getAll(exclude?: string[]): InterceptorRule[];
423
+ /**
424
+ * Get interceptors for specific package
425
+ *
426
+ * @param packageName - Package identifier
427
+ * @returns Interceptor rules or undefined
428
+ */
429
+ get(packageName: string): InterceptorRule[] | undefined;
430
+ /**
431
+ * Get list of registered package names
432
+ */
433
+ getPackageNames(): string[];
434
+ /**
435
+ * Check if package has registered interceptors
436
+ */
437
+ has(packageName: string): boolean;
438
+ /**
439
+ * Unregister interceptors for a package
440
+ *
441
+ * @param packageName - Package identifier
442
+ */
443
+ unregister(packageName: string): void;
444
+ /**
445
+ * Clear all registered interceptors
446
+ *
447
+ * Useful for testing
448
+ */
449
+ clear(): void;
450
+ /**
451
+ * Get total count of registered interceptors
452
+ */
453
+ count(): number;
454
+ }
455
+ /**
456
+ * Global singleton registry instance
457
+ */
458
+ declare const interceptorRegistry: InterceptorRegistry;
459
+ /**
460
+ * Register interceptors for a package
461
+ *
462
+ * This should be called during package initialization (on import).
463
+ * The interceptors will be automatically applied by the Next.js proxy.
464
+ *
465
+ * @param packageName - Unique package identifier (e.g., 'auth', 'storage')
466
+ * @param interceptors - Array of interceptor rules
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * // packages/auth/src/adapters/nextjs/interceptors/index.ts
471
+ * import { registerInterceptors } from '@spfn/core/client/nextjs';
472
+ *
473
+ * const authInterceptors = [
474
+ * {
475
+ * pathPattern: '/_auth/*',
476
+ * request: async (ctx, next) => {
477
+ * // Add JWT token
478
+ * ctx.headers['Authorization'] = 'Bearer token';
479
+ * await next();
480
+ * }
481
+ * }
482
+ * ];
483
+ *
484
+ * // Auto-register on import
485
+ * registerInterceptors('auth', authInterceptors);
486
+ * ```
487
+ */
488
+ declare function registerInterceptors(packageName: string, interceptors: InterceptorRule[]): void;
489
+
490
+ /**
491
+ * SPFN Next.js Proxy Interceptor Execution Engine
492
+ */
493
+
494
+ /**
495
+ * Check if path matches pattern
496
+ *
497
+ * Supports:
498
+ * - Wildcards: '/_auth/*' matches '/_auth/login'
499
+ * - Path params: '/users/:id' matches '/users/123'
500
+ * - RegExp: /^\/_auth\/.+$/ matches '/_auth/login'
501
+ * - Exact match: '/_auth/login' matches '/_auth/login'
502
+ * - All: '*' matches any path
503
+ *
504
+ * @param path - Request path to test
505
+ * @param pattern - Pattern to match against
506
+ * @returns True if path matches pattern
507
+ */
508
+ declare function matchPath(path: string, pattern: string | RegExp): boolean;
509
+ /**
510
+ * Check if method matches pattern
511
+ *
512
+ * @param method - Request method (e.g., 'POST')
513
+ * @param pattern - Method pattern (e.g., 'POST' or ['POST', 'PUT'])
514
+ * @returns True if method matches pattern
515
+ */
516
+ declare function matchMethod(method: string, pattern?: string | string[]): boolean;
517
+ /**
518
+ * Filter interceptors that match the request
519
+ *
520
+ * @param rules - All interceptor rules
521
+ * @param path - Request path
522
+ * @param method - Request method
523
+ * @returns Matched interceptors
524
+ */
525
+ declare function filterMatchingInterceptors(rules: InterceptorRule[], path: string, method: string): InterceptorRule[];
526
+ /**
527
+ * Execute request interceptors in chain
528
+ *
529
+ * Interceptors are executed in order:
530
+ * 1. First registered interceptor
531
+ * 2. Second registered interceptor
532
+ * 3. ... and so on
533
+ *
534
+ * Each interceptor must call next() to continue the chain.
535
+ * If next() is not called, the chain stops and remaining interceptors are skipped.
536
+ *
537
+ * @param context - Request interceptor context
538
+ * @param interceptors - Interceptors to execute
539
+ */
540
+ declare function executeRequestInterceptors(context: RequestInterceptorContext, interceptors: RequestInterceptor[]): Promise<void>;
541
+ /**
542
+ * Execute response interceptors in chain
543
+ *
544
+ * Interceptors are executed in order:
545
+ * 1. First registered interceptor
546
+ * 2. Second registered interceptor
547
+ * 3. ... and so on
548
+ *
549
+ * Each interceptor must call next() to continue the chain.
550
+ * If next() is not called, the chain stops and remaining interceptors are skipped.
551
+ *
552
+ * @param context - Response interceptor context
553
+ * @param interceptors - Interceptors to execute
554
+ */
555
+ declare function executeResponseInterceptors(context: ResponseInterceptorContext, interceptors: ResponseInterceptor[]): Promise<void>;
556
+
557
+ export { DELETE, GET, type InterceptorRule, PATCH, POST, PUT, type ProxyConfig, type RequestInterceptor, type RequestInterceptorContext, type ResponseInterceptor, type ResponseInterceptorContext, createProxy, executeRequestInterceptors, executeResponseInterceptors, filterMatchingInterceptors, interceptorRegistry, matchMethod, matchPath, registerInterceptors };