@spfn/core 0.1.0-alpha.88 → 0.2.0-beta.10
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/README.md +298 -466
- package/dist/boss-DI1r4kTS.d.ts +244 -0
- package/dist/cache/index.d.ts +13 -33
- package/dist/cache/index.js +14 -703
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +214 -17
- package/dist/codegen/index.js +231 -1420
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +1227 -0
- package/dist/config/index.js +273 -0
- package/dist/config/index.js.map +1 -0
- package/dist/db/index.d.ts +741 -59
- package/dist/db/index.js +1063 -1226
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +658 -308
- package/dist/env/index.js +503 -928
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +87 -0
- package/dist/env/loader.js +70 -0
- package/dist/env/loader.js.map +1 -0
- package/dist/errors/index.d.ts +417 -29
- package/dist/errors/index.js +359 -98
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +41 -0
- package/dist/event/index.js +131 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/sse/client.d.ts +82 -0
- package/dist/event/sse/client.js +115 -0
- package/dist/event/sse/client.js.map +1 -0
- package/dist/event/sse/index.d.ts +40 -0
- package/dist/event/sse/index.js +92 -0
- package/dist/event/sse/index.js.map +1 -0
- package/dist/job/index.d.ts +218 -0
- package/dist/job/index.js +410 -0
- package/dist/job/index.js.map +1 -0
- package/dist/logger/index.d.ts +20 -79
- package/dist/logger/index.js +82 -387
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +102 -20
- package/dist/middleware/index.js +51 -705
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +120 -0
- package/dist/nextjs/index.js +448 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +335 -262
- package/dist/nextjs/server.js +637 -0
- package/dist/nextjs/server.js.map +1 -0
- package/dist/route/index.d.ts +879 -25
- package/dist/route/index.js +697 -1271
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +9 -0
- package/dist/route/types.js +3 -0
- package/dist/route/types.js.map +1 -0
- package/dist/router-Di7ENoah.d.ts +151 -0
- package/dist/server/index.d.ts +345 -64
- package/dist/server/index.js +1174 -3233
- package/dist/server/index.js.map +1 -1
- package/dist/types-B-e_f2dQ.d.ts +121 -0
- package/dist/types-BGl4QL1w.d.ts +77 -0
- package/dist/types-BOPTApC2.d.ts +245 -0
- package/docs/cache.md +133 -0
- package/docs/codegen.md +74 -0
- package/docs/database.md +346 -0
- package/docs/entity.md +539 -0
- package/docs/env.md +477 -0
- package/docs/errors.md +319 -0
- package/docs/event.md +116 -0
- package/docs/file-upload.md +717 -0
- package/docs/job.md +131 -0
- package/docs/logger.md +108 -0
- package/docs/middleware.md +337 -0
- package/docs/nextjs.md +241 -0
- package/docs/repository.md +496 -0
- package/docs/route.md +497 -0
- package/docs/server.md +307 -0
- package/package.json +68 -48
- package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
- package/dist/client/index.d.ts +0 -358
- package/dist/client/index.js +0 -357
- package/dist/client/index.js.map +0 -1
- package/dist/client/nextjs/index.js +0 -371
- package/dist/client/nextjs/index.js.map +0 -1
- package/dist/codegen/generators/index.d.ts +0 -19
- package/dist/codegen/generators/index.js +0 -1404
- package/dist/codegen/generators/index.js.map +0 -1
- package/dist/database-errors-BNNmLTJE.d.ts +0 -86
- package/dist/events/index.d.ts +0 -183
- package/dist/events/index.js +0 -77
- package/dist/events/index.js.map +0 -1
- package/dist/index-DHiAqhKv.d.ts +0 -101
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -3674
- package/dist/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -121
- package/dist/types/index.js +0 -38
- package/dist/types/index.js.map +0 -1
- package/dist/types-BXibIEyj.d.ts +0 -60
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { E as EventRouterDef, I as InferEventNames, b as InferEventPayload } from './router-Di7ENoah.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SSE Types
|
|
5
|
+
*
|
|
6
|
+
* Type definitions for Server-Sent Events
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* SSE message sent from server
|
|
11
|
+
*/
|
|
12
|
+
interface SSEMessage<TEvent extends string = string, TPayload = unknown> {
|
|
13
|
+
/** Event name */
|
|
14
|
+
event: TEvent;
|
|
15
|
+
/** Event payload */
|
|
16
|
+
data: TPayload;
|
|
17
|
+
/** Optional message ID for reconnection */
|
|
18
|
+
id?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* SSE Handler configuration
|
|
22
|
+
*/
|
|
23
|
+
interface SSEHandlerConfig {
|
|
24
|
+
/**
|
|
25
|
+
* Keep-alive ping interval in milliseconds
|
|
26
|
+
* @default 30000
|
|
27
|
+
*/
|
|
28
|
+
pingInterval?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Custom headers for SSE response
|
|
31
|
+
*/
|
|
32
|
+
headers?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SSE Client configuration
|
|
36
|
+
*/
|
|
37
|
+
interface SSEClientConfig {
|
|
38
|
+
/**
|
|
39
|
+
* Backend API host URL
|
|
40
|
+
* @default NEXT_PUBLIC_SPFN_API_URL || 'http://localhost:8790'
|
|
41
|
+
* @example 'http://localhost:8790'
|
|
42
|
+
* @example 'https://api.example.com'
|
|
43
|
+
*/
|
|
44
|
+
host?: string;
|
|
45
|
+
/**
|
|
46
|
+
* SSE endpoint pathname
|
|
47
|
+
* @default '/events/stream'
|
|
48
|
+
*/
|
|
49
|
+
pathname?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Full URL (overrides host + pathname)
|
|
52
|
+
* @deprecated Use host and pathname instead
|
|
53
|
+
* @example 'http://localhost:8790/events/stream'
|
|
54
|
+
*/
|
|
55
|
+
url?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Auto reconnect on disconnect
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
reconnect?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Reconnect delay in milliseconds
|
|
63
|
+
* @default 3000
|
|
64
|
+
*/
|
|
65
|
+
reconnectDelay?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum reconnect attempts (0 = infinite)
|
|
68
|
+
* @default 0
|
|
69
|
+
*/
|
|
70
|
+
maxReconnectAttempts?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Include credentials (cookies) in request
|
|
73
|
+
* @default false
|
|
74
|
+
*/
|
|
75
|
+
withCredentials?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Event handler function
|
|
79
|
+
*/
|
|
80
|
+
type SSEEventHandler<TPayload> = (payload: TPayload) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Event handlers map for EventRouter
|
|
83
|
+
*/
|
|
84
|
+
type SSEEventHandlers<TRouter extends EventRouterDef<any>> = {
|
|
85
|
+
[K in InferEventNames<TRouter>]?: SSEEventHandler<InferEventPayload<TRouter, K>>;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Subscription options
|
|
89
|
+
*/
|
|
90
|
+
interface SSESubscribeOptions<TRouter extends EventRouterDef<any>> {
|
|
91
|
+
/**
|
|
92
|
+
* Events to subscribe
|
|
93
|
+
*/
|
|
94
|
+
events: InferEventNames<TRouter>[];
|
|
95
|
+
/**
|
|
96
|
+
* Event handlers
|
|
97
|
+
*/
|
|
98
|
+
handlers: SSEEventHandlers<TRouter>;
|
|
99
|
+
/**
|
|
100
|
+
* Called when connection opens
|
|
101
|
+
*/
|
|
102
|
+
onOpen?: () => void;
|
|
103
|
+
/**
|
|
104
|
+
* Called on connection error
|
|
105
|
+
*/
|
|
106
|
+
onError?: (error: Event) => void;
|
|
107
|
+
/**
|
|
108
|
+
* Called when reconnecting
|
|
109
|
+
*/
|
|
110
|
+
onReconnect?: (attempt: number) => void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* SSE connection state
|
|
114
|
+
*/
|
|
115
|
+
type SSEConnectionState = 'connecting' | 'open' | 'closed' | 'error';
|
|
116
|
+
/**
|
|
117
|
+
* Unsubscribe function
|
|
118
|
+
*/
|
|
119
|
+
type SSEUnsubscribe = () => void;
|
|
120
|
+
|
|
121
|
+
export type { SSEHandlerConfig as S, SSEMessage as a, SSEClientConfig as b, SSEEventHandler as c, SSEEventHandlers as d, SSESubscribeOptions as e, SSEConnectionState as f, SSEUnsubscribe as g };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* 로깅 시스템 타입 정의
|
|
5
|
+
*
|
|
6
|
+
* ✅ 구현 완료:
|
|
7
|
+
* - LogLevel 타입 정의
|
|
8
|
+
* - LogMetadata 인터페이스
|
|
9
|
+
* - Transport 인터페이스
|
|
10
|
+
* - 환경별 설정 타입
|
|
11
|
+
*
|
|
12
|
+
* 🔗 관련 파일:
|
|
13
|
+
* - src/logger/logger.ts (Logger 클래스)
|
|
14
|
+
* - src/logger/transports/ (Transport 구현체)
|
|
15
|
+
* - src/logger/config.ts (설정)
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* 로그 레벨
|
|
19
|
+
* debug < info < warn < error < fatal
|
|
20
|
+
*/
|
|
21
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
22
|
+
/**
|
|
23
|
+
* 로그 메타데이터
|
|
24
|
+
*/
|
|
25
|
+
interface LogMetadata {
|
|
26
|
+
timestamp: Date;
|
|
27
|
+
level: LogLevel;
|
|
28
|
+
message: string;
|
|
29
|
+
module?: string;
|
|
30
|
+
error?: Error;
|
|
31
|
+
context?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Transport 인터페이스
|
|
35
|
+
* 모든 Transport는 이 인터페이스를 구현해야 함
|
|
36
|
+
*/
|
|
37
|
+
interface Transport {
|
|
38
|
+
/**
|
|
39
|
+
* Transport 이름
|
|
40
|
+
*/
|
|
41
|
+
name: string;
|
|
42
|
+
/**
|
|
43
|
+
* 최소 로그 레벨 (이 레벨 이상만 처리)
|
|
44
|
+
*/
|
|
45
|
+
level: LogLevel;
|
|
46
|
+
/**
|
|
47
|
+
* 활성화 여부
|
|
48
|
+
*/
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 로그 처리 함수
|
|
52
|
+
*/
|
|
53
|
+
log(metadata: LogMetadata): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Transport 종료 (리소스 정리)
|
|
56
|
+
*/
|
|
57
|
+
close?(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Logger 설정
|
|
61
|
+
*/
|
|
62
|
+
interface LoggerConfig {
|
|
63
|
+
/**
|
|
64
|
+
* 기본 로그 레벨
|
|
65
|
+
*/
|
|
66
|
+
level: LogLevel;
|
|
67
|
+
/**
|
|
68
|
+
* 모듈명 (context)
|
|
69
|
+
*/
|
|
70
|
+
module?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Transport 리스트
|
|
73
|
+
*/
|
|
74
|
+
transports: Transport[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type { LogLevel as L, Transport as T, LoggerConfig as a };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { TSchema, Static } from '@sinclair/typebox';
|
|
2
|
+
import { ErrorRegistry, ErrorRegistryInput } from '@spfn/core/errors';
|
|
3
|
+
import { RouteDef, RouteInput } from '@spfn/core/route';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert File types in schema to actual File for client usage
|
|
7
|
+
*
|
|
8
|
+
* TypeBox File schemas become actual File objects on the client side.
|
|
9
|
+
*/
|
|
10
|
+
type ConvertFileTypes<T> = T extends File ? File : T extends File[] ? File[] : T;
|
|
11
|
+
/**
|
|
12
|
+
* Extract form data input type with File support
|
|
13
|
+
*
|
|
14
|
+
* Maps schema types to runtime types, converting FileSchema to File.
|
|
15
|
+
*/
|
|
16
|
+
type FormDataInput<T> = {
|
|
17
|
+
[K in keyof T]: ConvertFileTypes<T[K]>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Extract structured input from RouteInput
|
|
21
|
+
*
|
|
22
|
+
* Converts TypeBox schemas to their static types for each input field.
|
|
23
|
+
*/
|
|
24
|
+
type StructuredInput<TInput extends RouteInput> = {
|
|
25
|
+
params: TInput['params'] extends TSchema ? Static<TInput['params']> : {};
|
|
26
|
+
query: TInput['query'] extends TSchema ? Static<TInput['query']> : {};
|
|
27
|
+
body: TInput['body'] extends TSchema ? Static<TInput['body']> : {};
|
|
28
|
+
formData: TInput['formData'] extends TSchema ? FormDataInput<Static<TInput['formData']>> : {};
|
|
29
|
+
headers: TInput['headers'] extends TSchema ? Static<TInput['headers']> : {};
|
|
30
|
+
cookies: TInput['cookies'] extends TSchema ? Static<TInput['cookies']> : {};
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Infer route input type from RouteDef
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Server route definition
|
|
38
|
+
* const getUser = route.get('/users/:id')
|
|
39
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
40
|
+
* .handler(...);
|
|
41
|
+
*
|
|
42
|
+
* // Client: extract input type
|
|
43
|
+
* type Input = InferRouteInput<typeof getUser>;
|
|
44
|
+
* // { params: { id: string }, query: {}, body: {}, ... }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
type InferRouteInput<TRoute> = TRoute extends RouteDef<infer TInput, any, any> ? StructuredInput<TInput> : never;
|
|
48
|
+
/**
|
|
49
|
+
* Infer route output type from RouteDef
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Server route definition
|
|
54
|
+
* const getUser = route.get('/users/:id')
|
|
55
|
+
* .handler(async (c) => {
|
|
56
|
+
* return { id: '1', name: 'John' };
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* // Client: extract output type
|
|
60
|
+
* type Output = InferRouteOutput<typeof getUser>;
|
|
61
|
+
* // { id: string, name: string }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
type InferRouteOutput<TRoute> = TRoute extends RouteDef<any, any, infer TResponse> ? TResponse : never;
|
|
65
|
+
/**
|
|
66
|
+
* Extract routes from Router type
|
|
67
|
+
* Router<TRoutes> has routes in `_routes` property
|
|
68
|
+
*/
|
|
69
|
+
type ExtractRoutes<TRouter> = TRouter extends {
|
|
70
|
+
_routes: infer TRoutes;
|
|
71
|
+
} ? TRoutes : TRouter;
|
|
72
|
+
/**
|
|
73
|
+
* Extract output type for a specific route from router
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* import type { RouterOutput } from '@spfn/core/nextjs';
|
|
78
|
+
* import type { AppRouter } from '@/server/router';
|
|
79
|
+
*
|
|
80
|
+
* // Get output type for a specific route
|
|
81
|
+
* type ListData = RouterOutput<AppRouter, 'listExamples'>;
|
|
82
|
+
*
|
|
83
|
+
* // Use in props
|
|
84
|
+
* interface Props {
|
|
85
|
+
* data: RouterOutput<AppRouter, 'listExamples'>;
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* // Extract item type from paginated response
|
|
89
|
+
* type Example = RouterOutput<AppRouter, 'listExamples'>['items'][number];
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
type RouterOutput<TRouter, K extends keyof ExtractRoutes<TRouter>> = InferRouteOutput<ExtractRoutes<TRouter>[K]>;
|
|
93
|
+
/**
|
|
94
|
+
* Extract input type for a specific route from router
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* import type { RouterInput } from '@spfn/core/nextjs';
|
|
99
|
+
* import type { AppRouter } from '@/server/router';
|
|
100
|
+
*
|
|
101
|
+
* // Get input type for a specific route
|
|
102
|
+
* type CreateInput = RouterInput<AppRouter, 'createExample'>;
|
|
103
|
+
*
|
|
104
|
+
* // Use in function parameter
|
|
105
|
+
* function submitForm(data: RouterInput<AppRouter, 'createExample'>['body']) {
|
|
106
|
+
* // ...
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
type RouterInput<TRouter, K extends keyof ExtractRoutes<TRouter>> = InferRouteInput<ExtractRoutes<TRouter>[K]>;
|
|
111
|
+
/**
|
|
112
|
+
* Cookie options for setCookie
|
|
113
|
+
*/
|
|
114
|
+
interface CookieOptions {
|
|
115
|
+
httpOnly?: boolean;
|
|
116
|
+
secure?: boolean;
|
|
117
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
118
|
+
maxAge?: number;
|
|
119
|
+
path?: string;
|
|
120
|
+
domain?: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Cookie to set in response
|
|
124
|
+
*/
|
|
125
|
+
interface SetCookie {
|
|
126
|
+
name: string;
|
|
127
|
+
value: string;
|
|
128
|
+
options?: CookieOptions;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Request interceptor - called before fetch
|
|
132
|
+
*/
|
|
133
|
+
type RequestInterceptor = (url: string, init: RequestInit) => Promise<RequestInit> | RequestInit;
|
|
134
|
+
/**
|
|
135
|
+
* Response interceptor - called after fetch
|
|
136
|
+
*/
|
|
137
|
+
type ResponseInterceptor = (response: Response, body: any) => Promise<{
|
|
138
|
+
response: Response;
|
|
139
|
+
body: any;
|
|
140
|
+
}> | {
|
|
141
|
+
response: Response;
|
|
142
|
+
body: any;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Client configuration
|
|
146
|
+
*/
|
|
147
|
+
interface ApiConfig {
|
|
148
|
+
/**
|
|
149
|
+
* Base URL for RPC endpoint
|
|
150
|
+
*
|
|
151
|
+
* @default '/api/rpc'
|
|
152
|
+
* @example '/api/rpc', 'http://localhost:3000/api/rpc'
|
|
153
|
+
*/
|
|
154
|
+
baseUrl?: string;
|
|
155
|
+
/**
|
|
156
|
+
* Default headers for all requests
|
|
157
|
+
*/
|
|
158
|
+
headers?: Record<string, string>;
|
|
159
|
+
/**
|
|
160
|
+
* Request timeout in milliseconds
|
|
161
|
+
*
|
|
162
|
+
* @default 30000
|
|
163
|
+
*/
|
|
164
|
+
timeout?: number;
|
|
165
|
+
/**
|
|
166
|
+
* Custom fetch implementation
|
|
167
|
+
*/
|
|
168
|
+
fetch?: typeof fetch;
|
|
169
|
+
/**
|
|
170
|
+
* Global request interceptor
|
|
171
|
+
*/
|
|
172
|
+
onRequest?: RequestInterceptor;
|
|
173
|
+
/**
|
|
174
|
+
* Global response interceptor
|
|
175
|
+
*/
|
|
176
|
+
onResponse?: ResponseInterceptor;
|
|
177
|
+
/**
|
|
178
|
+
* Custom error registry for deserialization
|
|
179
|
+
*
|
|
180
|
+
* Core HTTP errors are automatically registered. Use this to add your custom application errors.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* import { errorRegistry } from '@spfn/core/errors';
|
|
185
|
+
* import { authErrorRegistry } from '@myapp/auth/errors';
|
|
186
|
+
* import { PaymentFailedError } from '@/server/errors';
|
|
187
|
+
*
|
|
188
|
+
* const api = createApi<AppRouter>({
|
|
189
|
+
* errorRegistry: [errorRegistry, authErrorRegistry, PaymentFailedError]
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
errorRegistry?: ErrorRegistry | ErrorRegistryInput[];
|
|
194
|
+
/**
|
|
195
|
+
* Enable debug logging
|
|
196
|
+
*
|
|
197
|
+
* @default false
|
|
198
|
+
*/
|
|
199
|
+
debug?: boolean;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Per-call options
|
|
203
|
+
*/
|
|
204
|
+
interface CallOptions {
|
|
205
|
+
/**
|
|
206
|
+
* Additional headers for this request
|
|
207
|
+
*/
|
|
208
|
+
headers?: Record<string, string>;
|
|
209
|
+
/**
|
|
210
|
+
* Override cookies for this request
|
|
211
|
+
*
|
|
212
|
+
* Note: Cookies are automatically forwarded by the proxy.
|
|
213
|
+
* Use this only when you need to override them.
|
|
214
|
+
*/
|
|
215
|
+
cookies?: Record<string, string>;
|
|
216
|
+
/**
|
|
217
|
+
* Request-specific interceptor
|
|
218
|
+
*/
|
|
219
|
+
onRequest?: RequestInterceptor;
|
|
220
|
+
/**
|
|
221
|
+
* Response-specific interceptor
|
|
222
|
+
*/
|
|
223
|
+
onResponse?: ResponseInterceptor;
|
|
224
|
+
/**
|
|
225
|
+
* Next.js-specific fetch options
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* // Time-based revalidation
|
|
229
|
+
* { next: { revalidate: 60 } }
|
|
230
|
+
*
|
|
231
|
+
* // Disable cache
|
|
232
|
+
* { cache: 'no-store' }
|
|
233
|
+
*
|
|
234
|
+
* // Tag-based revalidation
|
|
235
|
+
* { next: { tags: ['users'] } }
|
|
236
|
+
*/
|
|
237
|
+
fetchOptions?: RequestInit & {
|
|
238
|
+
next?: {
|
|
239
|
+
revalidate?: number | false;
|
|
240
|
+
tags?: string[];
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export type { ApiConfig as A, CallOptions as C, InferRouteInput as I, RequestInterceptor as R, StructuredInput as S, ResponseInterceptor as a, InferRouteOutput as b, RouterInput as c, RouterOutput as d, CookieOptions as e, SetCookie as f };
|
package/docs/cache.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Cache
|
|
2
|
+
|
|
3
|
+
Redis caching with type-safe operations.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
REDIS_URL=redis://localhost:6379
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { cache } from '@spfn/core/cache';
|
|
15
|
+
|
|
16
|
+
// Set
|
|
17
|
+
await cache.set('user:123', { id: '123', name: 'John' });
|
|
18
|
+
await cache.set('session:abc', data, { ttl: 3600 }); // 1 hour
|
|
19
|
+
|
|
20
|
+
// Get
|
|
21
|
+
const user = await cache.get<User>('user:123');
|
|
22
|
+
|
|
23
|
+
// Delete
|
|
24
|
+
await cache.del('user:123');
|
|
25
|
+
|
|
26
|
+
// Check existence
|
|
27
|
+
const exists = await cache.exists('user:123');
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## TTL Options
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// Set with TTL (seconds)
|
|
34
|
+
await cache.set('key', value, { ttl: 60 }); // 1 minute
|
|
35
|
+
await cache.set('key', value, { ttl: 3600 }); // 1 hour
|
|
36
|
+
await cache.set('key', value, { ttl: 86400 }); // 1 day
|
|
37
|
+
|
|
38
|
+
// No expiration
|
|
39
|
+
await cache.set('key', value); // Permanent until deleted
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Patterns
|
|
43
|
+
|
|
44
|
+
### Cache-Aside
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
async function getUser(id: string): Promise<User>
|
|
48
|
+
{
|
|
49
|
+
const cached = await cache.get<User>(`user:${id}`);
|
|
50
|
+
if (cached)
|
|
51
|
+
{
|
|
52
|
+
return cached;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const user = await userRepo.findById(id);
|
|
56
|
+
if (user)
|
|
57
|
+
{
|
|
58
|
+
await cache.set(`user:${id}`, user, { ttl: 3600 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return user;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Cache Invalidation
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
async function updateUser(id: string, data: Partial<User>)
|
|
69
|
+
{
|
|
70
|
+
const user = await userRepo.update(id, data);
|
|
71
|
+
await cache.del(`user:${id}`); // Invalidate cache
|
|
72
|
+
return user;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Cache with Prefix
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const userCache = cache.prefix('user');
|
|
80
|
+
|
|
81
|
+
await userCache.set('123', user); // Key: user:123
|
|
82
|
+
await userCache.get('123');
|
|
83
|
+
await userCache.del('123');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Hash Operations
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Set hash field
|
|
90
|
+
await cache.hset('user:123', 'name', 'John');
|
|
91
|
+
|
|
92
|
+
// Get hash field
|
|
93
|
+
const name = await cache.hget('user:123', 'name');
|
|
94
|
+
|
|
95
|
+
// Get all hash fields
|
|
96
|
+
const user = await cache.hgetall('user:123');
|
|
97
|
+
|
|
98
|
+
// Delete hash field
|
|
99
|
+
await cache.hdel('user:123', 'name');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## List Operations
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Push to list
|
|
106
|
+
await cache.lpush('queue', item);
|
|
107
|
+
await cache.rpush('queue', item);
|
|
108
|
+
|
|
109
|
+
// Pop from list
|
|
110
|
+
const item = await cache.lpop('queue');
|
|
111
|
+
const item = await cache.rpop('queue');
|
|
112
|
+
|
|
113
|
+
// Get list range
|
|
114
|
+
const items = await cache.lrange('queue', 0, -1); // All items
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Best Practices
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// 1. Use consistent key naming
|
|
121
|
+
`user:${id}`
|
|
122
|
+
`session:${token}`
|
|
123
|
+
`cache:posts:${page}`
|
|
124
|
+
|
|
125
|
+
// 2. Set appropriate TTL
|
|
126
|
+
{ ttl: 300 } // 5 min for frequently changing data
|
|
127
|
+
{ ttl: 3600 } // 1 hour for stable data
|
|
128
|
+
{ ttl: 86400 } // 1 day for rarely changing data
|
|
129
|
+
|
|
130
|
+
// 3. Invalidate on write
|
|
131
|
+
await userRepo.update(id, data);
|
|
132
|
+
await cache.del(`user:${id}`);
|
|
133
|
+
```
|
package/docs/codegen.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Codegen
|
|
2
|
+
|
|
3
|
+
Automatic API client generation from route definitions.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Configure Generator
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// codegen.config.ts
|
|
11
|
+
import { defineCodegenConfig } from '@spfn/core/codegen';
|
|
12
|
+
|
|
13
|
+
export default defineCodegenConfig({
|
|
14
|
+
generators: [
|
|
15
|
+
{
|
|
16
|
+
name: 'api-client',
|
|
17
|
+
output: './src/client/api.ts',
|
|
18
|
+
router: './src/server/server.config.ts'
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Generate Client
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Generate once
|
|
28
|
+
pnpm spfn codegen run
|
|
29
|
+
|
|
30
|
+
# Watch mode (dev server includes this)
|
|
31
|
+
pnpm spfn:dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Generated Client
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// src/client/api.ts (generated)
|
|
38
|
+
import { createApi } from '@spfn/core/nextjs';
|
|
39
|
+
import type { AppRouter } from '@/server/server.config';
|
|
40
|
+
|
|
41
|
+
export const api = createApi<AppRouter>();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { api } from '@/client/api';
|
|
48
|
+
|
|
49
|
+
// Type-safe API calls
|
|
50
|
+
const user = await api.getUser.call({
|
|
51
|
+
params: { id: '123' }
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const users = await api.getUsers.call({
|
|
55
|
+
query: { page: 1, limit: 20 }
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const created = await api.createUser.call({
|
|
59
|
+
body: { email: 'user@example.com', name: 'User' }
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## CLI Commands
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Generate API client
|
|
67
|
+
pnpm spfn codegen run
|
|
68
|
+
|
|
69
|
+
# List registered generators
|
|
70
|
+
pnpm spfn codegen list
|
|
71
|
+
|
|
72
|
+
# Run specific generator
|
|
73
|
+
pnpm spfn codegen run --name api-client
|
|
74
|
+
```
|