@spfn/core 0.2.0-beta.9 → 0.3.0-beta.2
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/LICENSE +1 -1
- package/README.md +467 -305
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +810 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
- package/dist/cache/index.js +42 -30
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +121 -13
- package/dist/codegen/index.js +212 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +615 -6
- package/dist/config/index.js +124 -5
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +220 -0
- package/dist/contract/index.js +558 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/index.d.ts +528 -85
- package/dist/db/index.js +831 -122
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
- package/dist/env/index.d.ts +26 -2
- package/dist/env/index.js +15 -5
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +32 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +418 -5
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +24 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +42 -3
- package/dist/event/sse/client.js +128 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +280 -32
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +272 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +2 -2
- package/dist/job/index.js +155 -42
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -0
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +347 -9
- package/dist/middleware/index.js +1462 -15
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +42 -28
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +35 -51
- package/dist/nextjs/server.js +126 -60
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +152 -0
- package/dist/ops/index.js +500 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/route/index.d.ts +8 -694
- package/dist/route/index.js +111 -22
- package/dist/route/index.js.map +1 -1
- package/dist/router-Qbssr11H.d.ts +676 -0
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +491 -22
- package/dist/server/index.js +1887 -308
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-BT5EnUAR.d.ts +278 -0
- package/dist/types-2AbaW4Ie.d.ts +205 -0
- package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
- package/dist/types-D1c57Ko-.d.ts +115 -0
- package/dist/types-ZQODsBft.d.ts +282 -0
- package/package.json +244 -208
- package/dist/router-Di7ENoah.d.ts +0 -151
- package/dist/types-B-e_f2dQ.d.ts +0 -121
- package/docs/cache.md +0 -133
- package/docs/codegen.md +0 -74
- package/docs/database.md +0 -346
- package/docs/entity.md +0 -539
- package/docs/env.md +0 -477
- package/docs/errors.md +0 -319
- package/docs/event.md +0 -116
- package/docs/job.md +0 -131
- package/docs/logger.md +0 -108
- package/docs/middleware.md +0 -337
- package/docs/nextjs.md +0 -241
- package/docs/repository.md +0 -496
- package/docs/route.md +0 -497
- package/docs/server.md +0 -307
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import { a as NamedMiddleware } from './define-middleware-DfDP39Nq.js';
|
|
2
|
+
import { Context, MiddlewareHandler } from 'hono';
|
|
3
|
+
import { TSchema, Static } from '@sinclair/typebox';
|
|
4
|
+
import { ContentfulStatusCode, RedirectStatusCode } from 'hono/utils/http-status';
|
|
5
|
+
import { HttpMethod } from './route/types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Route Input Types
|
|
9
|
+
*
|
|
10
|
+
* Defines the structure for route input validation schemas
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Route input schemas
|
|
15
|
+
*
|
|
16
|
+
* Defines validation schemas for different parts of an HTTP request
|
|
17
|
+
*/
|
|
18
|
+
type RouteInput = {
|
|
19
|
+
/** Path parameters (e.g., /users/:id) */
|
|
20
|
+
params?: TSchema;
|
|
21
|
+
/** Query string parameters (e.g., ?page=1&limit=20) */
|
|
22
|
+
query?: TSchema;
|
|
23
|
+
/** Request body (JSON) */
|
|
24
|
+
body?: TSchema;
|
|
25
|
+
/** Form data (multipart/form-data) for file uploads */
|
|
26
|
+
formData?: TSchema;
|
|
27
|
+
/** HTTP headers */
|
|
28
|
+
headers?: TSchema;
|
|
29
|
+
/** Cookies */
|
|
30
|
+
cookies?: TSchema;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Route Builder Context
|
|
35
|
+
*
|
|
36
|
+
* Provides structured input access and response helpers for route handlers
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Paginated response structure
|
|
41
|
+
*/
|
|
42
|
+
type PaginatedResult<T> = {
|
|
43
|
+
items: T[];
|
|
44
|
+
pagination: {
|
|
45
|
+
page: number;
|
|
46
|
+
limit: number;
|
|
47
|
+
total: number;
|
|
48
|
+
totalPages: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Merge input with interceptor-injected fields
|
|
53
|
+
* Server receives both client input and interceptor-injected fields
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* type ClientInput = { body: { email: string, password: string } };
|
|
58
|
+
* type InterceptorInput = { body: { publicKey: string, keyId: string } };
|
|
59
|
+
* // MergedInput = { body: { email: string, password: string, publicKey: string, keyId: string } }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
type MergedInput<TInput extends RouteInput, TInterceptor extends RouteInput> = {
|
|
63
|
+
params: (TInput['params'] extends TSchema ? Static<TInput['params']> : {}) & (TInterceptor['params'] extends TSchema ? Static<TInterceptor['params']> : {});
|
|
64
|
+
query: (TInput['query'] extends TSchema ? Static<TInput['query']> : {}) & (TInterceptor['query'] extends TSchema ? Static<TInterceptor['query']> : {});
|
|
65
|
+
body: (TInput['body'] extends TSchema ? Static<TInput['body']> : {}) & (TInterceptor['body'] extends TSchema ? Static<TInterceptor['body']> : {});
|
|
66
|
+
formData: (TInput['formData'] extends TSchema ? Static<TInput['formData']> : {}) & (TInterceptor['formData'] extends TSchema ? Static<TInterceptor['formData']> : {});
|
|
67
|
+
headers: (TInput['headers'] extends TSchema ? Static<TInput['headers']> : {}) & (TInterceptor['headers'] extends TSchema ? Static<TInterceptor['headers']> : {});
|
|
68
|
+
cookies: (TInput['cookies'] extends TSchema ? Static<TInput['cookies']> : {}) & (TInterceptor['cookies'] extends TSchema ? Static<TInterceptor['cookies']> : {});
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* RouteBuilderContext - define-route dedicated context
|
|
72
|
+
*
|
|
73
|
+
* Provides structured input access through data() method
|
|
74
|
+
*/
|
|
75
|
+
type RouteBuilderContext<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}> = {
|
|
76
|
+
/**
|
|
77
|
+
* Get structured input data
|
|
78
|
+
*
|
|
79
|
+
* Returns an object with separate params, query, body, headers, cookies
|
|
80
|
+
* If interceptor fields are defined, they are merged with input fields
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* // GET /users/:id?page=1
|
|
85
|
+
* const { params, query } = await c.data();
|
|
86
|
+
* // params = { id: string }
|
|
87
|
+
* // query = { page: number }
|
|
88
|
+
*
|
|
89
|
+
* // POST /users with headers
|
|
90
|
+
* const { body, headers } = await c.data();
|
|
91
|
+
* // body = { name: string }
|
|
92
|
+
* // headers = { authorization: string }
|
|
93
|
+
*
|
|
94
|
+
* // With interceptor-injected fields
|
|
95
|
+
* const { body } = await c.data();
|
|
96
|
+
* // body = { email: string, password: string, publicKey: string, keyId: string }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
data(): Promise<MergedInput<TInput, TInterceptor>>;
|
|
100
|
+
/**
|
|
101
|
+
* Return JSON response with custom status and headers
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* return c.json({ message: 'Custom response' }, 200);
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
json(data: unknown, status?: ContentfulStatusCode, headers?: Record<string, string | string[]>): Response;
|
|
109
|
+
/**
|
|
110
|
+
* Return 201 Created response with optional Location header
|
|
111
|
+
* Returns data directly for type inference
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const user = await createUser(body);
|
|
116
|
+
* return c.created(user, `/users/${user.id}`);
|
|
117
|
+
* // Response: 201 Created
|
|
118
|
+
* // Header: Location: /users/123
|
|
119
|
+
* // Body: { id: '123', name: 'John' }
|
|
120
|
+
* // Type: User (inferred from data)
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
created<T>(data: T, location?: string): T;
|
|
124
|
+
/**
|
|
125
|
+
* Return 202 Accepted response
|
|
126
|
+
* Returns data directly for type inference
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* // With data
|
|
131
|
+
* return c.accepted({ jobId: '123' });
|
|
132
|
+
* // Response: 202 Accepted, Body: { jobId: '123' }
|
|
133
|
+
* // Type: { jobId: string }
|
|
134
|
+
*
|
|
135
|
+
* // Without data
|
|
136
|
+
* return c.accepted();
|
|
137
|
+
* // Response: 202 Accepted, Body: (empty)
|
|
138
|
+
* // Type: void
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
accepted(): void;
|
|
142
|
+
accepted<T>(data: T): T;
|
|
143
|
+
/**
|
|
144
|
+
* Return 204 No Content response (empty body)
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* await deleteUser(id);
|
|
149
|
+
* return c.noContent();
|
|
150
|
+
* // Response: 204 No Content, Body: (empty)
|
|
151
|
+
* // Type: void
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
noContent(): void;
|
|
155
|
+
/**
|
|
156
|
+
* Return 304 Not Modified response (empty body)
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* if (etag === requestEtag) {
|
|
161
|
+
* return c.notModified();
|
|
162
|
+
* }
|
|
163
|
+
* // Response: 304 Not Modified, Body: (empty)
|
|
164
|
+
* // Type: void
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
notModified(): void;
|
|
168
|
+
/**
|
|
169
|
+
* Return paginated response with metadata
|
|
170
|
+
* Returns `{ items: [...], pagination: {...} }` format with type inference
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const users = await getUsers(page, limit);
|
|
175
|
+
* const total = await countUsers();
|
|
176
|
+
* return c.paginated(users, page, limit, total);
|
|
177
|
+
* // Response: {
|
|
178
|
+
* // items: [...],
|
|
179
|
+
* // pagination: {
|
|
180
|
+
* // page: 1,
|
|
181
|
+
* // limit: 20,
|
|
182
|
+
* // total: 100,
|
|
183
|
+
* // totalPages: 5
|
|
184
|
+
* // }
|
|
185
|
+
* // }
|
|
186
|
+
* // Type: PaginatedResult<User>
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
paginated<T>(data: T[], page: number, limit: number, total: number): PaginatedResult<T>;
|
|
190
|
+
/**
|
|
191
|
+
* Redirect to another URL
|
|
192
|
+
*
|
|
193
|
+
* @param url - Target URL to redirect to
|
|
194
|
+
* @param status - HTTP status code (301, 302, 303, 307, 308). Default: 302
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* // Temporary redirect (302)
|
|
199
|
+
* return c.redirect('/login');
|
|
200
|
+
*
|
|
201
|
+
* // Permanent redirect (301)
|
|
202
|
+
* return c.redirect('/new-path', 301);
|
|
203
|
+
*
|
|
204
|
+
* // See Other (303) - useful after POST
|
|
205
|
+
* return c.redirect('/success', 303);
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
redirect(url: string, status?: RedirectStatusCode): Response;
|
|
209
|
+
raw: Context;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Route Contract
|
|
214
|
+
*
|
|
215
|
+
* A contract marks a route as a versioned public promise to clients that are
|
|
216
|
+
* compiled and deployed separately from the server — a mobile app, an external
|
|
217
|
+
* API consumer. Those clients cannot be fixed by redeploying the server, so the
|
|
218
|
+
* shape they read has to survive server changes.
|
|
219
|
+
*
|
|
220
|
+
* A web client does not need this. `createApi<AppRouter>()` derives its types
|
|
221
|
+
* from the router in the same build, so a removed response field breaks the
|
|
222
|
+
* TypeScript compile instead of a running app.
|
|
223
|
+
*
|
|
224
|
+
* The response shape is declared here rather than inferred from the handler's
|
|
225
|
+
* return type: a declared schema exists at runtime, which is what the generator
|
|
226
|
+
* and the compatibility gate read. `_response` on RouteDef disappears after
|
|
227
|
+
* compilation.
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Authentication profile a contracted operation is admitted under.
|
|
232
|
+
*
|
|
233
|
+
* - `none` — the operation is called before any key exists to sign with
|
|
234
|
+
* (enrollment, login), so it carries neither proof nor session headers.
|
|
235
|
+
* - `clientProofV1` — admitted by the @spfn/auth client-proof admission order.
|
|
236
|
+
*
|
|
237
|
+
* The union is deliberately closed. A profile name is part of what the contract
|
|
238
|
+
* publishes to external clients, so adding one is a change to this file rather
|
|
239
|
+
* than a string a route can invent.
|
|
240
|
+
*/
|
|
241
|
+
type RouteAuthProfile = 'none' | 'clientProofV1';
|
|
242
|
+
/**
|
|
243
|
+
* The public promise a contracted route makes.
|
|
244
|
+
*/
|
|
245
|
+
interface RouteContract {
|
|
246
|
+
/** Contract version this operation first appeared in (e.g. '1.2.0'). */
|
|
247
|
+
since: string;
|
|
248
|
+
/**
|
|
249
|
+
* Response shape. TypeBox schema, declared — not inferred.
|
|
250
|
+
*
|
|
251
|
+
* An operation that answers with no body declares `Type.Null()`.
|
|
252
|
+
*/
|
|
253
|
+
response: TSchema;
|
|
254
|
+
/** Authentication profile. Defaults to `'none'`. */
|
|
255
|
+
auth?: RouteAuthProfile;
|
|
256
|
+
/** Whether the call carries a session. Defaults to `false`. */
|
|
257
|
+
requiresSession?: boolean;
|
|
258
|
+
/** Contract version this operation was announced for removal in. */
|
|
259
|
+
deprecatedIn?: string;
|
|
260
|
+
/**
|
|
261
|
+
* Contract version this operation was removed in.
|
|
262
|
+
*
|
|
263
|
+
* Set on a route kept alive only to carry the record. A client generated
|
|
264
|
+
* before the removal still calls it, and this is what tells that client the
|
|
265
|
+
* operation went and when — a route that simply disappears says nothing.
|
|
266
|
+
*/
|
|
267
|
+
removedIn?: string;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Route Builder
|
|
272
|
+
*
|
|
273
|
+
* Provides tRPC-style chainable API for route definition
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Route handler function
|
|
278
|
+
*/
|
|
279
|
+
type RouteHandlerFn<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = (c: RouteBuilderContext<TInput, TInterceptor>) => Response | Promise<Response> | TResponse | Promise<TResponse>;
|
|
280
|
+
/**
|
|
281
|
+
* Route definition result
|
|
282
|
+
*
|
|
283
|
+
* Contains all information needed for type inference and registration
|
|
284
|
+
*/
|
|
285
|
+
type RouteDef<TInput extends RouteInput = RouteInput, TInterceptor extends RouteInput = {}, TResponse = unknown> = {
|
|
286
|
+
method?: HttpMethod;
|
|
287
|
+
path?: string;
|
|
288
|
+
input?: TInput;
|
|
289
|
+
interceptor?: TInterceptor;
|
|
290
|
+
middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
|
|
291
|
+
skipMiddlewares?: string[] | '*';
|
|
292
|
+
/**
|
|
293
|
+
* Public promise this route makes to separately deployed clients.
|
|
294
|
+
*
|
|
295
|
+
* Present as a runtime value, unlike `_response`: the contract generator and
|
|
296
|
+
* the compatibility gate read it.
|
|
297
|
+
*/
|
|
298
|
+
contract?: RouteContract;
|
|
299
|
+
handler: RouteHandlerFn<TInput, TInterceptor, TResponse>;
|
|
300
|
+
_input: TInput;
|
|
301
|
+
_interceptor: TInterceptor;
|
|
302
|
+
_response: TResponse;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Route builder with chainable API (tRPC-style)
|
|
306
|
+
*/
|
|
307
|
+
declare class RouteBuilder<TInput extends RouteInput = {}, TInterceptor extends RouteInput = {}, TResponse = never> {
|
|
308
|
+
_method?: HttpMethod;
|
|
309
|
+
_path?: string;
|
|
310
|
+
_input?: TInput;
|
|
311
|
+
_interceptor?: TInterceptor;
|
|
312
|
+
_middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];
|
|
313
|
+
_skipMiddlewares?: string[] | '*';
|
|
314
|
+
_contract?: RouteContract;
|
|
315
|
+
/**
|
|
316
|
+
* Create a new RouteBuilder with copied properties and optional overrides
|
|
317
|
+
*/
|
|
318
|
+
private clone;
|
|
319
|
+
/**
|
|
320
|
+
* Define input schemas
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* route.get('/users/:id')
|
|
325
|
+
* .input({
|
|
326
|
+
* params: Type.Object({ id: Type.String() }),
|
|
327
|
+
* query: Type.Object({ page: Type.Number() }),
|
|
328
|
+
* headers: Type.Object({ authorization: Type.String() })
|
|
329
|
+
* })
|
|
330
|
+
* .handler(async (c) => {
|
|
331
|
+
* const { params, query, headers } = await c.data();
|
|
332
|
+
* // params = { id: string }
|
|
333
|
+
* // query = { page: number }
|
|
334
|
+
* // headers = { authorization: string }
|
|
335
|
+
* })
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
input<TNewInput extends RouteInput>(input: TNewInput): RouteBuilder<TNewInput, TInterceptor, TResponse>;
|
|
339
|
+
/**
|
|
340
|
+
* Define fields injected by interceptors
|
|
341
|
+
*
|
|
342
|
+
* These fields are:
|
|
343
|
+
* - Available in the handler (merged with input)
|
|
344
|
+
* - Excluded from client types (codegen uses only input)
|
|
345
|
+
* - Not validated by route input schema (injected by middleware)
|
|
346
|
+
*
|
|
347
|
+
* Use this when middleware/interceptors add fields to the request
|
|
348
|
+
* before it reaches the handler.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* // Auth interceptor injects crypto key fields
|
|
353
|
+
* route.post('/_auth/login')
|
|
354
|
+
* .input({
|
|
355
|
+
* body: Type.Object({
|
|
356
|
+
* email: Type.String(),
|
|
357
|
+
* password: Type.String()
|
|
358
|
+
* })
|
|
359
|
+
* })
|
|
360
|
+
* .interceptor({
|
|
361
|
+
* body: Type.Object({
|
|
362
|
+
* publicKey: Type.String(),
|
|
363
|
+
* keyId: Type.String(),
|
|
364
|
+
* fingerprint: Type.String()
|
|
365
|
+
* })
|
|
366
|
+
* })
|
|
367
|
+
* .handler(async (c) => {
|
|
368
|
+
* const { body } = await c.data();
|
|
369
|
+
* // body type: { email, password, publicKey, keyId, fingerprint }
|
|
370
|
+
* // Client only sees: { email, password }
|
|
371
|
+
* return loginService(body);
|
|
372
|
+
* });
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
interceptor<TNewInterceptor extends RouteInput>(interceptor: TNewInterceptor): RouteBuilder<TInput, TNewInterceptor, TResponse>;
|
|
376
|
+
/**
|
|
377
|
+
* Add middlewares to the route
|
|
378
|
+
*
|
|
379
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
380
|
+
* Named middlewares that are already registered globally will be automatically
|
|
381
|
+
* deduplicated to prevent double execution.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
386
|
+
*
|
|
387
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
388
|
+
* route.get('/users')
|
|
389
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
390
|
+
*
|
|
391
|
+
* // With regular middleware handlers
|
|
392
|
+
* route.get('/users')
|
|
393
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
middleware(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
|
|
397
|
+
/**
|
|
398
|
+
* Add middlewares to the route (alias for `.middleware()`)
|
|
399
|
+
*
|
|
400
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
401
|
+
* Named middlewares that are already registered globally will be automatically
|
|
402
|
+
* deduplicated to prevent double execution.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```ts
|
|
406
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
407
|
+
*
|
|
408
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
409
|
+
* route.get('/users')
|
|
410
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
411
|
+
*
|
|
412
|
+
* // With regular middleware handlers
|
|
413
|
+
* route.get('/users')
|
|
414
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
use(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>;
|
|
418
|
+
/**
|
|
419
|
+
* Skip server-level named middlewares
|
|
420
|
+
*
|
|
421
|
+
* Useful for public endpoints that should bypass auth or rate limiting
|
|
422
|
+
*
|
|
423
|
+
* @param middlewareNames - Array of middleware names to skip, or '*' to skip all
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```ts
|
|
427
|
+
* // Skip specific middlewares
|
|
428
|
+
* route.get('/health')
|
|
429
|
+
* .skip(['auth', 'rateLimit'])
|
|
430
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
431
|
+
*
|
|
432
|
+
* // Skip only auth (still apply rate limiting)
|
|
433
|
+
* route.get('/public-data')
|
|
434
|
+
* .skip(['auth'])
|
|
435
|
+
* .handler(async (c) => { ... });
|
|
436
|
+
*
|
|
437
|
+
* // Skip all middlewares
|
|
438
|
+
* route.get('/public-health')
|
|
439
|
+
* .skip('*')
|
|
440
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
skip(middlewareNames: string[] | '*'): RouteBuilder<TInput, TInterceptor, TResponse>;
|
|
444
|
+
/**
|
|
445
|
+
* Publish this route as a versioned contract operation
|
|
446
|
+
*
|
|
447
|
+
* Marks the route as a promise to clients that are compiled and deployed
|
|
448
|
+
* separately from the server — a mobile app, an external API consumer.
|
|
449
|
+
* The `@spfn/core:contract` generator writes every contracted route into
|
|
450
|
+
* `contracts/current.json`, and the build refuses a change that would break
|
|
451
|
+
* an already-released client.
|
|
452
|
+
*
|
|
453
|
+
* Routes without `.contract()` are unaffected: they simply do not appear in
|
|
454
|
+
* the contract. A web client needs nothing here — it derives its types from
|
|
455
|
+
* the router in the same build.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```ts
|
|
459
|
+
* export const getUser = route.get('/users/:id')
|
|
460
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
461
|
+
* .contract({
|
|
462
|
+
* since: '1.2.0',
|
|
463
|
+
* auth: 'clientProofV1',
|
|
464
|
+
* requiresSession: true,
|
|
465
|
+
* response: Type.Object({
|
|
466
|
+
* id: Type.String(),
|
|
467
|
+
* name: Type.String(),
|
|
468
|
+
* email: Type.Optional(Type.String()),
|
|
469
|
+
* }),
|
|
470
|
+
* })
|
|
471
|
+
* .handler(async (c) => { ... });
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
contract(contract: RouteContract): RouteBuilder<TInput, TInterceptor, TResponse>;
|
|
475
|
+
/**
|
|
476
|
+
* Define handler function
|
|
477
|
+
*
|
|
478
|
+
* Response type is automatically inferred from the return value.
|
|
479
|
+
* Use helper methods like `c.created()`, `c.paginated()` for proper type inference.
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```ts
|
|
483
|
+
* // Direct return - type inferred from data
|
|
484
|
+
* route.get('/users/:id')
|
|
485
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
486
|
+
* .handler(async (c) => {
|
|
487
|
+
* const { params } = await c.data();
|
|
488
|
+
* return await getUser(params.id); // Type: User
|
|
489
|
+
* })
|
|
490
|
+
*
|
|
491
|
+
* // Using c.created() - returns data with 201 status, type preserved
|
|
492
|
+
* route.post('/users')
|
|
493
|
+
* .input({ body: Type.Object({ name: Type.String() }) })
|
|
494
|
+
* .handler(async (c) => {
|
|
495
|
+
* const { body } = await c.data();
|
|
496
|
+
* return c.created(await createUser(body)); // Type: User
|
|
497
|
+
* })
|
|
498
|
+
*
|
|
499
|
+
* // Using c.paginated() - returns PaginatedResult<T>
|
|
500
|
+
* route.get('/users')
|
|
501
|
+
* .handler(async (c) => {
|
|
502
|
+
* const users = await getUsers();
|
|
503
|
+
* return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>
|
|
504
|
+
* })
|
|
505
|
+
*
|
|
506
|
+
* // Using c.noContent() - returns void
|
|
507
|
+
* route.delete('/users/:id')
|
|
508
|
+
* .handler(async (c) => {
|
|
509
|
+
* await deleteUser(params.id);
|
|
510
|
+
* return c.noContent(); // Type: void
|
|
511
|
+
* })
|
|
512
|
+
*
|
|
513
|
+
* // Using c.json() - returns Response (type inference lost)
|
|
514
|
+
* // Use only when you need custom status codes not covered by helpers
|
|
515
|
+
* route.get('/custom')
|
|
516
|
+
* .handler(async (c) => {
|
|
517
|
+
* return c.json({ data }, 418); // Type: Response
|
|
518
|
+
* })
|
|
519
|
+
* ```
|
|
520
|
+
*/
|
|
521
|
+
handler<THandlerResponse>(fn: RouteHandlerFn<TInput, TInterceptor, THandlerResponse>): RouteDef<TInput, TInterceptor, THandlerResponse>;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Route builder entry point
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* // GET request
|
|
529
|
+
* export const getUser = route.get('/users/:id')
|
|
530
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
531
|
+
* .handler(async (c) => {
|
|
532
|
+
* const { params } = await c.data();
|
|
533
|
+
* return await db.user.findUnique({ where: { id: params.id } });
|
|
534
|
+
* });
|
|
535
|
+
*
|
|
536
|
+
* // POST request
|
|
537
|
+
* export const createUser = route.post('/users')
|
|
538
|
+
* .input({ body: Type.Object({ name: Type.String(), email: Type.String() }) })
|
|
539
|
+
* .handler(async (c) => {
|
|
540
|
+
* const { body } = await c.data();
|
|
541
|
+
* return c.created(await db.user.create({ data: body }));
|
|
542
|
+
* });
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
declare const route: {
|
|
546
|
+
get: (path: string) => RouteBuilder;
|
|
547
|
+
post: (path: string) => RouteBuilder;
|
|
548
|
+
put: (path: string) => RouteBuilder;
|
|
549
|
+
patch: (path: string) => RouteBuilder;
|
|
550
|
+
delete: (path: string) => RouteBuilder;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Router Definition
|
|
555
|
+
*
|
|
556
|
+
* Provides router composition and middleware management
|
|
557
|
+
*/
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Router definition - holds all routes
|
|
561
|
+
*/
|
|
562
|
+
interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>> {
|
|
563
|
+
routes: TRoutes;
|
|
564
|
+
_routes: TRoutes;
|
|
565
|
+
_packageRouters: Router<any>[];
|
|
566
|
+
_globalMiddlewares: NamedMiddleware<string>[];
|
|
567
|
+
/** The contract version these routes publish, or null when uncontracted. */
|
|
568
|
+
_contractVersion: string | null;
|
|
569
|
+
/**
|
|
570
|
+
* Register package routers (type-hidden)
|
|
571
|
+
*
|
|
572
|
+
* Package routes are:
|
|
573
|
+
* - Recognized by RPC proxy and backend
|
|
574
|
+
* - NOT exposed in client types (use package's own API like authApi, cmsApi)
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```ts
|
|
578
|
+
* import { authRouter } from '@spfn/auth/server';
|
|
579
|
+
* import { cmsAppRouter } from '@spfn/cms/server';
|
|
580
|
+
*
|
|
581
|
+
* export const appRouter = defineRouter({
|
|
582
|
+
* getRoot,
|
|
583
|
+
* getHealth,
|
|
584
|
+
* })
|
|
585
|
+
* .packages([authRouter, cmsAppRouter]);
|
|
586
|
+
*
|
|
587
|
+
* // Client usage:
|
|
588
|
+
* // api.getRoot.call({}) - app routes
|
|
589
|
+
* // authApi.login.call({}) - package API
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
packages(routers: Router<any>[]): Router<TRoutes>;
|
|
593
|
+
/**
|
|
594
|
+
* Register global middlewares
|
|
595
|
+
*
|
|
596
|
+
* Applied to all routes unless explicitly skipped via .skip()
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* ```ts
|
|
600
|
+
* import { authMiddleware, loggingMiddleware } from './middlewares';
|
|
601
|
+
*
|
|
602
|
+
* export const appRouter = defineRouter({
|
|
603
|
+
* getRoot,
|
|
604
|
+
* getHealth,
|
|
605
|
+
* })
|
|
606
|
+
* .packages([authRouter])
|
|
607
|
+
* .use([authMiddleware, loggingMiddleware]);
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>;
|
|
611
|
+
/**
|
|
612
|
+
* Declare the contract version these routes publish.
|
|
613
|
+
*
|
|
614
|
+
* A client compiled against this server — a mobile app in a store — is
|
|
615
|
+
* generated from one version of the contract and cannot be updated when the
|
|
616
|
+
* server changes. The server announces this version on every response so
|
|
617
|
+
* that client can tell whether the two ends still agree.
|
|
618
|
+
*
|
|
619
|
+
* This is the version's source. A released snapshot is written to
|
|
620
|
+
* `contracts/released/<version>.json` from what is declared here, so the
|
|
621
|
+
* filename follows the code rather than the code having to be told what the
|
|
622
|
+
* filename said.
|
|
623
|
+
*
|
|
624
|
+
* Only a server with contracted routes needs it. Without it the contract
|
|
625
|
+
* generator still writes `current.json` and still runs the compatibility
|
|
626
|
+
* gate; what it cannot do is cut a release or announce a version.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```ts
|
|
630
|
+
* export const appRouter = defineRouter({ getRoot, listItems })
|
|
631
|
+
* .contractVersion('1.2.0')
|
|
632
|
+
* .packages([authRouter]);
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
contractVersion(version: string): Router<TRoutes>;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Define a router with multiple routes (tRPC-style)
|
|
639
|
+
*
|
|
640
|
+
* Supports chainable API for packages and middlewares:
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* // Basic usage
|
|
645
|
+
* export const appRouter = defineRouter({
|
|
646
|
+
* getRoot,
|
|
647
|
+
* getHealth,
|
|
648
|
+
* listExamples,
|
|
649
|
+
* });
|
|
650
|
+
*
|
|
651
|
+
* // With package routers (type-hidden)
|
|
652
|
+
* export const appRouter = defineRouter({
|
|
653
|
+
* getRoot,
|
|
654
|
+
* getHealth,
|
|
655
|
+
* })
|
|
656
|
+
* .packages([authRouter, cmsAppRouter]);
|
|
657
|
+
*
|
|
658
|
+
* // With global middlewares
|
|
659
|
+
* export const appRouter = defineRouter({
|
|
660
|
+
* getRoot,
|
|
661
|
+
* getHealth,
|
|
662
|
+
* })
|
|
663
|
+
* .packages([authRouter])
|
|
664
|
+
* .use([authMiddleware, loggingMiddleware]);
|
|
665
|
+
*
|
|
666
|
+
* export type AppRouter = typeof appRouter;
|
|
667
|
+
* ```
|
|
668
|
+
*
|
|
669
|
+
* Package routes:
|
|
670
|
+
* - Recognized by RPC proxy and backend for routing
|
|
671
|
+
* - NOT included in AppRouter type (use authApi, cmsApi instead)
|
|
672
|
+
* - Prevents confusion between app API and package APIs
|
|
673
|
+
*/
|
|
674
|
+
declare function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(routes: TRoutes): Router<TRoutes>;
|
|
675
|
+
|
|
676
|
+
export { type MergedInput as M, type PaginatedResult as P, type Router as R, type RouteDef as a, RouteBuilder as b, type RouteAuthProfile as c, type RouteBuilderContext as d, type RouteContract as e, type RouteHandlerFn as f, type RouteInput as g, defineRouter as h, route as r };
|