@spfn/core 0.3.0-beta.3 → 0.3.0-beta.31
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 +304 -12
- package/dist/app-config/index.d.ts +39 -1
- package/dist/app-config/index.js.map +1 -1
- package/dist/authz/index.js +47 -391
- package/dist/authz/index.js.map +1 -1
- package/dist/{boss-D16fO2oG.d.ts → boss-BiDCbbAQ.d.ts} +60 -7
- package/dist/codegen/index.d.ts +157 -16
- package/dist/codegen/index.js +772 -166
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.js +4 -4
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +39 -16
- package/dist/contract/index.js +147 -23
- package/dist/contract/index.js.map +1 -1
- package/dist/db/index.d.ts +230 -35
- package/dist/db/index.js +316 -160
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +6 -442
- package/dist/env/index.js +13 -16
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.js +24 -1
- package/dist/env/loader.js.map +1 -1
- package/dist/error-CIZlYJlL.d.ts +157 -0
- package/dist/errors/index.d.ts +18 -1
- package/dist/errors/index.js +47 -391
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +5 -5
- package/dist/event/index.js +202 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +18 -4
- package/dist/event/sse/client.js +94 -18
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +18 -5
- package/dist/event/sse/index.js +452 -114
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +3 -2
- package/dist/event/ws/index.d.ts +4 -3
- package/dist/job/index.d.ts +48 -3
- package/dist/job/index.js +131 -52
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.js +0 -12
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.js +52 -397
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +75 -14
- package/dist/nextjs/index.js +200 -84
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +46 -8
- package/dist/nextjs/server.js +15 -1
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +61 -42
- package/dist/ops/index.js +648 -97
- package/dist/ops/index.js.map +1 -1
- package/dist/registry-C2KQCh0m.d.ts +473 -0
- package/dist/route/index.d.ts +5 -2
- package/dist/route/index.js +107 -28
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +7 -1
- package/dist/{router-Cy7rAfmj.d.ts → route-builder-DRW-MSCJ.d.ts} +51 -126
- package/dist/router-C57y-vz1.d.ts +184 -0
- package/dist/router-CX5uVjmU.d.ts +406 -0
- package/dist/server/index.d.ts +130 -7
- package/dist/server/index.js +1174 -164
- package/dist/server/index.js.map +1 -1
- package/dist/state/client.d.ts +206 -0
- package/dist/state/client.js +678 -0
- package/dist/state/client.js.map +1 -0
- package/dist/state/index.d.ts +443 -0
- package/dist/state/index.js +644 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/react.d.ts +49 -0
- package/dist/state/react.js +50 -0
- package/dist/state/react.js.map +1 -0
- package/dist/surface/index.d.ts +115 -0
- package/dist/surface/index.js +1013 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/token-manager-Ckjt2-qq.d.ts +122 -0
- package/dist/{types-CfYVhIQ9.d.ts → types-5Rl11_Nb.d.ts} +43 -4
- package/dist/types-CKH0D3Dy.d.ts +441 -0
- package/dist/{types-ZQODsBft.d.ts → types-CurMab2Q.d.ts} +52 -2
- package/dist/{types-2AbaW4Ie.d.ts → types-DncqXfWN.d.ts} +4 -3
- package/docs/file-upload.md +195 -333
- package/package.json +41 -11
- package/src/cache/README.md +330 -0
- package/src/codegen/README.md +721 -0
- package/src/config/README.md +333 -0
- package/src/contract/README.md +442 -0
- package/src/db/README.md +589 -0
- package/src/db/manager/README.md +500 -0
- package/src/db/schema/README.md +354 -0
- package/src/db/transaction/README.md +845 -0
- package/src/env/README.md +787 -0
- package/src/errors/README.md +445 -0
- package/src/event/README.md +994 -0
- package/src/job/README.md +613 -0
- package/src/logger/README.md +321 -0
- package/src/middleware/README.md +634 -0
- package/src/nextjs/README.md +756 -0
- package/src/route/README.md +763 -0
- package/src/security/README.md +100 -0
- package/src/server/README.md +704 -0
- package/src/state/README.md +806 -0
- package/dist/token-manager-BT5EnUAR.d.ts +0 -278
- package/dist/types-9oszaJqp.d.ts +0 -250
|
@@ -0,0 +1,441 @@
|
|
|
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
|
+
* Typed client error
|
|
7
|
+
*
|
|
8
|
+
* `errorType` tells apart why a call failed:
|
|
9
|
+
* - `'http'`: the server answered with a non-2xx status
|
|
10
|
+
* - `'network'`: the fetch or the body read failed (status 0)
|
|
11
|
+
* - `'timeout'`: the call's timeout elapsed before response headers arrived (status 408)
|
|
12
|
+
* - `'aborted'`: the caller's own signal aborted the call (status 0); `cause` is the signal's reason
|
|
13
|
+
*/
|
|
14
|
+
declare class ApiError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly url: string;
|
|
17
|
+
readonly response?: unknown | undefined;
|
|
18
|
+
readonly errorType?: "http" | "network" | "timeout" | "aborted" | undefined;
|
|
19
|
+
constructor(message: string, status: number, url: string, response?: unknown | undefined, errorType?: "http" | "network" | "timeout" | "aborted" | undefined, options?: {
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert File types in schema to actual File for client usage
|
|
26
|
+
*
|
|
27
|
+
* TypeBox File schemas become actual File objects on the client side.
|
|
28
|
+
*/
|
|
29
|
+
type ConvertFileTypes<T> = T extends File ? File : T extends File[] ? File[] : T;
|
|
30
|
+
/**
|
|
31
|
+
* Extract form data input type with File support
|
|
32
|
+
*
|
|
33
|
+
* Maps schema types to runtime types, converting FileSchema to File.
|
|
34
|
+
*/
|
|
35
|
+
type FormDataInput<T> = {
|
|
36
|
+
[K in keyof T]: ConvertFileTypes<T[K]>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Extract structured input from RouteInput
|
|
40
|
+
*
|
|
41
|
+
* Converts TypeBox schemas to their static types for each input field.
|
|
42
|
+
*/
|
|
43
|
+
type StructuredInput<TInput extends RouteInput> = {
|
|
44
|
+
params: TInput['params'] extends TSchema ? Static<TInput['params']> : {};
|
|
45
|
+
query: TInput['query'] extends TSchema ? Static<TInput['query']> : {};
|
|
46
|
+
body: TInput['body'] extends TSchema ? Static<TInput['body']> : {};
|
|
47
|
+
formData: TInput['formData'] extends TSchema ? FormDataInput<Static<TInput['formData']>> : {};
|
|
48
|
+
headers: TInput['headers'] extends TSchema ? Static<TInput['headers']> : {};
|
|
49
|
+
cookies: TInput['cookies'] extends TSchema ? Static<TInput['cookies']> : {};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Infer route input type from RouteDef
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Server route definition
|
|
57
|
+
* const getUser = route.get('/users/:id')
|
|
58
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
59
|
+
* .handler(...);
|
|
60
|
+
*
|
|
61
|
+
* // Client: extract input type
|
|
62
|
+
* type Input = InferRouteInput<typeof getUser>;
|
|
63
|
+
* // { params: { id: string }, query: {}, body: {}, ... }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
type InferRouteInput<TRoute> = TRoute extends RouteDef<infer TInput, any, any> ? StructuredInput<TInput> : never;
|
|
67
|
+
/**
|
|
68
|
+
* Infer route output type from RouteDef
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // Server route definition
|
|
73
|
+
* const getUser = route.get('/users/:id')
|
|
74
|
+
* .handler(async (c) => {
|
|
75
|
+
* return { id: '1', name: 'John' };
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* // Client: extract output type
|
|
79
|
+
* type Output = InferRouteOutput<typeof getUser>;
|
|
80
|
+
* // { id: string, name: string }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
type InferRouteOutput<TRoute> = TRoute extends RouteDef<any, any, infer TResponse> ? TResponse : never;
|
|
84
|
+
/**
|
|
85
|
+
* True only for `any`.
|
|
86
|
+
*
|
|
87
|
+
* `any` satisfies both branches of a conditional type, so a router whose routes are
|
|
88
|
+
* unresolved (`Router<any>`) would otherwise flatten into "every name exists" and
|
|
89
|
+
* accept a misspelled route name. A route record that is not known contributes no
|
|
90
|
+
* names instead.
|
|
91
|
+
*/
|
|
92
|
+
type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
93
|
+
/**
|
|
94
|
+
* True when `T` is a union of two or more members.
|
|
95
|
+
*/
|
|
96
|
+
type IsUnion<T, TCopy = T> = T extends unknown ? ([TCopy] extends [T] ? false : true) : false;
|
|
97
|
+
/**
|
|
98
|
+
* One leaf of a router tree: the name a route is registered under, its definition, and
|
|
99
|
+
* the path it was reached by.
|
|
100
|
+
*
|
|
101
|
+
* A route is identified by its own key whatever depth it sits at — `registerRoutes`
|
|
102
|
+
* mounts a nested route under that key, and the contract collector reports a collision
|
|
103
|
+
* on it. Nesting groups the source; it does not qualify the name. The trail is carried
|
|
104
|
+
* so that one name reached through two branches stays two leaves.
|
|
105
|
+
*/
|
|
106
|
+
interface RouteLeaf<TName extends PropertyKey, TDef, TTrail extends PropertyKey[]> {
|
|
107
|
+
name: TName;
|
|
108
|
+
def: TDef;
|
|
109
|
+
trail: TTrail;
|
|
110
|
+
}
|
|
111
|
+
/** Decrements the nesting budget; `PrevDepth[0]` is `never`, which ends the walk. */
|
|
112
|
+
type PrevDepth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
113
|
+
/**
|
|
114
|
+
* How many levels of nesting are followed.
|
|
115
|
+
*
|
|
116
|
+
* A budget rather than open recursion: a router type that refers to itself would
|
|
117
|
+
* otherwise never terminate, and TypeScript would report an instantiation-depth error
|
|
118
|
+
* in whichever component happened to read a route type. A route below the budget is not
|
|
119
|
+
* visible to these types; ten levels is far past any hand-written tree.
|
|
120
|
+
*/
|
|
121
|
+
type MaxNesting = 10;
|
|
122
|
+
/**
|
|
123
|
+
* Collect every route in a route record as a leaf, descending into nested routers
|
|
124
|
+
* exactly as `registerRoutes` does.
|
|
125
|
+
*/
|
|
126
|
+
type RouteLeaves<TRoutes, TTrail extends PropertyKey[] = [], TDepth extends number = MaxNesting> = [
|
|
127
|
+
TDepth
|
|
128
|
+
] extends [never] ? never : IsAny<TRoutes> extends true ? never : {
|
|
129
|
+
[K in keyof TRoutes]-?: RouteLeafOf<K, Exclude<TRoutes[K], undefined>, [...TTrail, K], TDepth>;
|
|
130
|
+
}[keyof TRoutes];
|
|
131
|
+
/**
|
|
132
|
+
* A nested router contributes its own leaves; anything else is a leaf.
|
|
133
|
+
*
|
|
134
|
+
* `{ _routes: ... }` is the runtime's own test for a router (`isRouter`), and a
|
|
135
|
+
* `RouteDef` carries `handler`, `_input` and `_response` but never `_routes`, so a
|
|
136
|
+
* route cannot be mistaken for a router.
|
|
137
|
+
*/
|
|
138
|
+
type RouteLeafOf<TName extends PropertyKey, TValue, TTrail extends PropertyKey[], TDepth extends number> = TValue extends {
|
|
139
|
+
_routes: infer TNested;
|
|
140
|
+
} ? RouteLeaves<TNested, TTrail, PrevDepth[TDepth]> : RouteLeaf<TName, TValue, TTrail>;
|
|
141
|
+
/** Every name the tree declares. */
|
|
142
|
+
type LeafNames<TLeaves> = Extract<TLeaves extends RouteLeaf<infer TName, any, any> ? TName : never, PropertyKey>;
|
|
143
|
+
/**
|
|
144
|
+
* The leaves declaring exactly this name.
|
|
145
|
+
*
|
|
146
|
+
* Exactly, not assignably: a record typed `Record<string, RouteDef>` contributes the
|
|
147
|
+
* name `string`, which every literal name is assignable to, and a loose sibling must
|
|
148
|
+
* not make each of its siblings look doubly declared.
|
|
149
|
+
*/
|
|
150
|
+
type LeavesNamed<TLeaves, TName extends PropertyKey> = TLeaves extends RouteLeaf<infer TLeafName, any, any> ? [TLeafName] extends [TName] ? [TName] extends [TLeafName] ? TLeaves : never : never : never;
|
|
151
|
+
/** The definition a leaf carries. */
|
|
152
|
+
type LeafDef<TLeaves> = TLeaves extends RouteLeaf<any, infer TDef, any> ? TDef : never;
|
|
153
|
+
/** Where a leaf was found — one distinct trail per place the name is declared. */
|
|
154
|
+
type LeafTrail<TLeaves> = TLeaves extends RouteLeaf<any, any, infer TTrail> ? TTrail : never;
|
|
155
|
+
/**
|
|
156
|
+
* Flatten the leaves into one namespace of name → definition.
|
|
157
|
+
*
|
|
158
|
+
* A name that two branches both declare is left out rather than resolved to one of
|
|
159
|
+
* them: the tree gives it no single meaning, and picking a winner silently would hand
|
|
160
|
+
* a component another route's types. Naming it then fails the `K` constraint on
|
|
161
|
+
* `RouterOutput`/`RouterInput`, on the line that named it. Two places is counted by
|
|
162
|
+
* trail, so one slot holding a union of route definitions stays one route and still
|
|
163
|
+
* infers the union.
|
|
164
|
+
*
|
|
165
|
+
* The result is a fresh mapped type, so no key carries an optional or readonly modifier
|
|
166
|
+
* out of the source record — a route key that turned optional would change what
|
|
167
|
+
* `InferRouteInput` returns for it.
|
|
168
|
+
*/
|
|
169
|
+
type FlattenLeaves<TLeaves> = {
|
|
170
|
+
[TName in LeafNames<TLeaves> as UnambiguousName<TLeaves, TName>]: LeafDef<LeavesNamed<TLeaves, TName>>;
|
|
171
|
+
};
|
|
172
|
+
/** The name itself when one place declares it, and nothing when two do. */
|
|
173
|
+
type UnambiguousName<TLeaves, TName extends PropertyKey> = IsUnion<LeafTrail<LeavesNamed<TLeaves, TName>>> extends true ? never : TName;
|
|
174
|
+
/**
|
|
175
|
+
* Extract routes from Router type
|
|
176
|
+
*
|
|
177
|
+
* `Router<TRoutes>` holds its routes in `_routes`, and a value in that record is either
|
|
178
|
+
* a route or another router. Nested routers are flattened away, so every route appears
|
|
179
|
+
* here under its own name at one level — the same set of names the server registers.
|
|
180
|
+
*
|
|
181
|
+
* Exported so that the typed client (`Client`, in `./builder`) names a route the same
|
|
182
|
+
* way `RouterOutput` and `RouterInput` do; the two must not diverge, or a route would
|
|
183
|
+
* type in one place and not the other. It stays internal to the package: the
|
|
184
|
+
* `@spfn/core/nextjs` entry point exports a fixed list of type names, and this is not
|
|
185
|
+
* one of them.
|
|
186
|
+
*/
|
|
187
|
+
type ExtractRoutes<TRouter> = TRouter extends {
|
|
188
|
+
_routes: infer TRoutes;
|
|
189
|
+
} ? FlattenLeaves<RouteLeaves<TRoutes>> : FlattenLeaves<RouteLeaves<TRouter>>;
|
|
190
|
+
/**
|
|
191
|
+
* Extract output type for a specific route from router
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* import type { RouterOutput } from '@spfn/core/nextjs';
|
|
196
|
+
* import type { AppRouter } from '@/server/router';
|
|
197
|
+
*
|
|
198
|
+
* // Get output type for a specific route
|
|
199
|
+
* type ListData = RouterOutput<AppRouter, 'listExamples'>;
|
|
200
|
+
*
|
|
201
|
+
* // Use in props
|
|
202
|
+
* interface Props {
|
|
203
|
+
* data: RouterOutput<AppRouter, 'listExamples'>;
|
|
204
|
+
* }
|
|
205
|
+
*
|
|
206
|
+
* // Extract item type from paginated response
|
|
207
|
+
* type Example = RouterOutput<AppRouter, 'listExamples'>['items'][number];
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* A route declared inside a nested router is named the same way. The server registers
|
|
211
|
+
* a nested route under its own key, so `K` is the flat set of route names in the tree
|
|
212
|
+
* and the depth the route was declared at never appears here:
|
|
213
|
+
*
|
|
214
|
+
* ```typescript
|
|
215
|
+
* const appRouter = defineRouter({
|
|
216
|
+
* getRoot,
|
|
217
|
+
* examples: defineRouter({ listExamples, createExample }),
|
|
218
|
+
* });
|
|
219
|
+
*
|
|
220
|
+
* type ListData = RouterOutput<typeof appRouter, 'listExamples'>;
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* Two branches declaring the same name make that name ambiguous, and it is left out of
|
|
224
|
+
* `K` rather than resolved to one of them — naming it is a compile error here instead
|
|
225
|
+
* of the wrong route's types reaching a component.
|
|
226
|
+
*/
|
|
227
|
+
type RouterOutput<TRouter, K extends keyof ExtractRoutes<TRouter>> = InferRouteOutput<ExtractRoutes<TRouter>[K]>;
|
|
228
|
+
/**
|
|
229
|
+
* Extract input type for a specific route from router
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* import type { RouterInput } from '@spfn/core/nextjs';
|
|
234
|
+
* import type { AppRouter } from '@/server/router';
|
|
235
|
+
*
|
|
236
|
+
* // Get input type for a specific route
|
|
237
|
+
* type CreateInput = RouterInput<AppRouter, 'createExample'>;
|
|
238
|
+
*
|
|
239
|
+
* // Use in function parameter
|
|
240
|
+
* function submitForm(data: RouterInput<AppRouter, 'createExample'>['body']) {
|
|
241
|
+
* // ...
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* `K` is the flat set of route names, so a route declared inside a nested router is
|
|
246
|
+
* named by its own key here too — see {@link RouterOutput}.
|
|
247
|
+
*/
|
|
248
|
+
type RouterInput<TRouter, K extends keyof ExtractRoutes<TRouter>> = InferRouteInput<ExtractRoutes<TRouter>[K]>;
|
|
249
|
+
/**
|
|
250
|
+
* Cookie options for setCookie
|
|
251
|
+
*/
|
|
252
|
+
interface CookieOptions {
|
|
253
|
+
httpOnly?: boolean;
|
|
254
|
+
secure?: boolean;
|
|
255
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
256
|
+
maxAge?: number;
|
|
257
|
+
path?: string;
|
|
258
|
+
domain?: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Cookie to set in response
|
|
262
|
+
*/
|
|
263
|
+
interface SetCookie {
|
|
264
|
+
name: string;
|
|
265
|
+
value: string;
|
|
266
|
+
options?: CookieOptions;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Request interceptor - called before fetch
|
|
270
|
+
*/
|
|
271
|
+
type RequestInterceptor = (url: string, init: RequestInit) => Promise<RequestInit> | RequestInit;
|
|
272
|
+
/**
|
|
273
|
+
* Response interceptor - called after fetch
|
|
274
|
+
*
|
|
275
|
+
* It runs outside the client's transport error handling, so throwing from it is a
|
|
276
|
+
* supported way to end the call: the error reaches the caller unchanged, which is what
|
|
277
|
+
* lets `redirect()` and `notFound()` navigate from one (issue #104). Returning
|
|
278
|
+
* `{ response, body }` replaces both for everything after it, error-status handling
|
|
279
|
+
* included.
|
|
280
|
+
*/
|
|
281
|
+
type ResponseInterceptor = (response: Response, body: any) => Promise<{
|
|
282
|
+
response: Response;
|
|
283
|
+
body: any;
|
|
284
|
+
}> | {
|
|
285
|
+
response: Response;
|
|
286
|
+
body: any;
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* How one call ended, as `ApiConfig.onOutcome` is told it
|
|
290
|
+
*
|
|
291
|
+
* - `ok: true`: the server answered 2xx; `response` and `body` are what the response
|
|
292
|
+
* interceptors left, and `body` is what the call returns
|
|
293
|
+
* - `'http'`: the server answered with an error status; `error` is the object the call
|
|
294
|
+
* throws — the registered class a `__type` body names (`NotFoundError`,
|
|
295
|
+
* `UnauthorizedError`, …) or else an `ApiError`, hence `Error`
|
|
296
|
+
* - `'network'` / `'timeout'` / `'aborted'`: no answer; `error` is the `ApiError` the call
|
|
297
|
+
* throws, `status` its status (0, 408, 0)
|
|
298
|
+
*/
|
|
299
|
+
type ApiOutcome = {
|
|
300
|
+
ok: true;
|
|
301
|
+
response: Response;
|
|
302
|
+
body: unknown;
|
|
303
|
+
} | {
|
|
304
|
+
ok: false;
|
|
305
|
+
errorType: 'http';
|
|
306
|
+
status: number;
|
|
307
|
+
error: Error;
|
|
308
|
+
} | {
|
|
309
|
+
ok: false;
|
|
310
|
+
errorType: 'network' | 'timeout' | 'aborted';
|
|
311
|
+
status: number;
|
|
312
|
+
error: ApiError;
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Client configuration
|
|
316
|
+
*/
|
|
317
|
+
interface ApiConfig {
|
|
318
|
+
/**
|
|
319
|
+
* Base URL for RPC endpoint
|
|
320
|
+
*
|
|
321
|
+
* @default '/api/rpc'
|
|
322
|
+
* @example '/api/rpc', 'http://localhost:3000/api/rpc'
|
|
323
|
+
*/
|
|
324
|
+
baseUrl?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Default headers for all requests
|
|
327
|
+
*/
|
|
328
|
+
headers?: Record<string, string>;
|
|
329
|
+
/**
|
|
330
|
+
* Request timeout in milliseconds
|
|
331
|
+
*
|
|
332
|
+
* @default env.SERVER_TIMEOUT (120000)
|
|
333
|
+
*/
|
|
334
|
+
timeout?: number;
|
|
335
|
+
/**
|
|
336
|
+
* Custom fetch implementation
|
|
337
|
+
*/
|
|
338
|
+
fetch?: typeof fetch;
|
|
339
|
+
/**
|
|
340
|
+
* Global request interceptor
|
|
341
|
+
*/
|
|
342
|
+
onRequest?: RequestInterceptor;
|
|
343
|
+
/**
|
|
344
|
+
* Global response interceptor
|
|
345
|
+
*/
|
|
346
|
+
onResponse?: ResponseInterceptor;
|
|
347
|
+
/**
|
|
348
|
+
* Told once how each call that reached fetch ended — after the response interceptors
|
|
349
|
+
* ran, before the call returns or throws
|
|
350
|
+
*
|
|
351
|
+
* Observed: a transport failure (`'network'`, `'timeout'`, `'aborted'`) and every
|
|
352
|
+
* answer, error statuses included; `error` is the very object the call then throws.
|
|
353
|
+
* Not observed: a throw from an `onRequest` or `onResponse` interceptor, or from
|
|
354
|
+
* building the request — those are the app's own and reach the caller unchanged.
|
|
355
|
+
*
|
|
356
|
+
* Called synchronously and never awaited. A throw from it, or a rejection of the
|
|
357
|
+
* promise it returns, is logged and never changes what the call returns or throws.
|
|
358
|
+
* Config only: there is no per-call observer.
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```typescript
|
|
362
|
+
* const api = createApi<AppRouter>({
|
|
363
|
+
* onOutcome: (outcome) => setServerStatus(outcome.ok ? 'up' : outcome.errorType),
|
|
364
|
+
* });
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
onOutcome?: (outcome: ApiOutcome) => void;
|
|
368
|
+
/**
|
|
369
|
+
* Custom error registry for deserialization
|
|
370
|
+
*
|
|
371
|
+
* Core HTTP errors are automatically registered. Use this to add your custom application errors.
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* import { errorRegistry } from '@spfn/core/errors';
|
|
376
|
+
* import { authErrorRegistry } from '@myapp/auth/errors';
|
|
377
|
+
* import { PaymentFailedError } from '@/server/errors';
|
|
378
|
+
*
|
|
379
|
+
* const api = createApi<AppRouter>({
|
|
380
|
+
* errorRegistry: [errorRegistry, authErrorRegistry, PaymentFailedError]
|
|
381
|
+
* });
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
errorRegistry?: ErrorRegistry | ErrorRegistryInput[];
|
|
385
|
+
/**
|
|
386
|
+
* Enable debug logging
|
|
387
|
+
*
|
|
388
|
+
* @default false
|
|
389
|
+
*/
|
|
390
|
+
debug?: boolean;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Per-call options
|
|
394
|
+
*/
|
|
395
|
+
interface CallOptions {
|
|
396
|
+
/**
|
|
397
|
+
* Request timeout in milliseconds, covering the wait for response headers
|
|
398
|
+
* Overrides the global timeout set in ApiConfig; set it with `.timeout(ms)`
|
|
399
|
+
*/
|
|
400
|
+
timeout?: number;
|
|
401
|
+
/**
|
|
402
|
+
* Additional headers for this request
|
|
403
|
+
*/
|
|
404
|
+
headers?: Record<string, string>;
|
|
405
|
+
/**
|
|
406
|
+
* Override cookies for this request
|
|
407
|
+
*
|
|
408
|
+
* Note: Cookies are automatically forwarded by the proxy.
|
|
409
|
+
* Use this only when you need to override them.
|
|
410
|
+
*/
|
|
411
|
+
cookies?: Record<string, string>;
|
|
412
|
+
/**
|
|
413
|
+
* Request-specific interceptor
|
|
414
|
+
*/
|
|
415
|
+
onRequest?: RequestInterceptor;
|
|
416
|
+
/**
|
|
417
|
+
* Response-specific interceptor
|
|
418
|
+
*/
|
|
419
|
+
onResponse?: ResponseInterceptor;
|
|
420
|
+
/**
|
|
421
|
+
* Next.js-specific fetch options
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* // Time-based revalidation
|
|
425
|
+
* { next: { revalidate: 60 } }
|
|
426
|
+
*
|
|
427
|
+
* // Disable cache
|
|
428
|
+
* { cache: 'no-store' }
|
|
429
|
+
*
|
|
430
|
+
* // Tag-based revalidation
|
|
431
|
+
* { next: { tags: ['users'] } }
|
|
432
|
+
*/
|
|
433
|
+
fetchOptions?: RequestInit & {
|
|
434
|
+
next?: {
|
|
435
|
+
revalidate?: number | false;
|
|
436
|
+
tags?: string[];
|
|
437
|
+
};
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export { type ApiConfig as A, type CallOptions as C, type ExtractRoutes as E, type InferRouteInput as I, type RequestInterceptor as R, type SetCookie as S, type ResponseInterceptor as a, type InferRouteOutput as b, ApiError as c, type ApiOutcome as d, type CookieOptions as e, type RouterInput as f, type RouterOutput as g, type StructuredInput as h };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Context } from 'hono';
|
|
2
|
-
import {
|
|
2
|
+
import { E as EventRouterDef, I as InferEventNames, i as InferEventPayload, l as SubscribeRequest } from './router-CX5uVjmU.js';
|
|
3
|
+
import { c as SSETokenStore, S as SSETokenManager } from './token-manager-Ckjt2-qq.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SSE Types
|
|
@@ -78,6 +79,17 @@ interface SSEHandlerAuthConfig {
|
|
|
78
79
|
* Return false to skip sending the event to this user.
|
|
79
80
|
*/
|
|
80
81
|
filter?: Record<string, (subject: string, payload: unknown) => boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the principal every `.canSubscribe()` of a connection receives
|
|
84
|
+
* (called once per connection, after the subject is known).
|
|
85
|
+
*
|
|
86
|
+
* The stream request carries no `AuthContext`: the subject comes from the
|
|
87
|
+
* one-time token, and the stream is mounted without the app's middleware.
|
|
88
|
+
* So an app that decides on roles or memberships loads them here, once,
|
|
89
|
+
* instead of in every predicate. Absent → predicates see
|
|
90
|
+
* `principal: undefined`. A throw refuses the connection with 403.
|
|
91
|
+
*/
|
|
92
|
+
resolvePrincipal?: (subject: string, request: SubscribeRequest) => unknown | Promise<unknown>;
|
|
81
93
|
}
|
|
82
94
|
/**
|
|
83
95
|
* SSE auth configuration (user-facing, generic)
|
|
@@ -113,6 +125,7 @@ interface SSEAuthConfig<TRouter extends EventRouterDef<any>> {
|
|
|
113
125
|
filter?: {
|
|
114
126
|
[K in InferEventNames<TRouter>]?: (subject: string, payload: InferEventPayload<TRouter, K>) => boolean;
|
|
115
127
|
};
|
|
128
|
+
resolvePrincipal?: (subject: string, request: SubscribeRequest) => unknown | Promise<unknown>;
|
|
116
129
|
}
|
|
117
130
|
/**
|
|
118
131
|
* SSE Handler configuration
|
|
@@ -248,6 +261,13 @@ interface SSESubscribeOptions<TRouter extends EventRouterDef<any>> {
|
|
|
248
261
|
* Event handlers
|
|
249
262
|
*/
|
|
250
263
|
handlers: SSEEventHandlers<TRouter>;
|
|
264
|
+
/**
|
|
265
|
+
* Extra connect-query fields, e.g. the key a state event is filtered on
|
|
266
|
+
* (`{ workspaceId: 'w1' }`). Appended to the stream URL after `events=`,
|
|
267
|
+
* both sides URI-encoded, and reused on every reconnect. `events` and
|
|
268
|
+
* `token` are reserved: subscribe throws naming them.
|
|
269
|
+
*/
|
|
270
|
+
query?: Record<string, string>;
|
|
251
271
|
/**
|
|
252
272
|
* Called when connection opens
|
|
253
273
|
*/
|
|
@@ -269,6 +289,36 @@ interface SSESubscribeOptions<TRouter extends EventRouterDef<any>> {
|
|
|
269
289
|
* Called when reconnecting
|
|
270
290
|
*/
|
|
271
291
|
onReconnect?: (attempt: number) => void;
|
|
292
|
+
/**
|
|
293
|
+
* Called when the server grants or revokes one event on the open connection
|
|
294
|
+
* (`subscription.granted` / `subscription.revoked` frames). Only events in
|
|
295
|
+
* `events` can change: an event never requested cannot be granted.
|
|
296
|
+
*/
|
|
297
|
+
onSubscriptionChange?: (change: SSESubscriptionChange<TRouter>) => void;
|
|
298
|
+
/**
|
|
299
|
+
* Called with the server's `connected` frame on every connection — the
|
|
300
|
+
* first open and each reconnect — after `onOpen`.
|
|
301
|
+
*/
|
|
302
|
+
onConnected?: (payload: ConnectedPayload) => void;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The `connected` frame, the first a connection receives: which requested
|
|
306
|
+
* events it is subscribed to, which are denied, and which were served their
|
|
307
|
+
* initial frames (written right after this one). `initial` is absent from a
|
|
308
|
+
* server that predates it.
|
|
309
|
+
*/
|
|
310
|
+
interface ConnectedPayload {
|
|
311
|
+
subscribedEvents: string[];
|
|
312
|
+
deniedEvents: string[];
|
|
313
|
+
initial?: string[];
|
|
314
|
+
timestamp: number;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* A `grant` / `revoke` the server applied to the open connection.
|
|
318
|
+
*/
|
|
319
|
+
interface SSESubscriptionChange<TRouter extends EventRouterDef<any>> {
|
|
320
|
+
event: InferEventNames<TRouter>;
|
|
321
|
+
status: 'granted' | 'revoked';
|
|
272
322
|
}
|
|
273
323
|
/**
|
|
274
324
|
* SSE connection state
|
|
@@ -279,4 +329,4 @@ type SSEConnectionState = 'connecting' | 'open' | 'closed' | 'error';
|
|
|
279
329
|
*/
|
|
280
330
|
type SSEUnsubscribe = () => void;
|
|
281
331
|
|
|
282
|
-
export type { SSEClientConfig as S, SSESubscribeOptions as a, SSEUnsubscribe as b, SSEConnectionState as c, SSEHandlerConfig as d, SSEAuthConfig as e, SSEEventHandler as f, SSEEventHandlers as g, SSEHandlerAuthConfig as h, SSEMessage as i };
|
|
332
|
+
export type { ConnectedPayload as C, SSEClientConfig as S, SSESubscribeOptions as a, SSEUnsubscribe as b, SSEConnectionState as c, SSEHandlerConfig as d, SSEAuthConfig as e, SSEEventHandler as f, SSEEventHandlers as g, SSEHandlerAuthConfig as h, SSEMessage as i, SSESubscriptionChange as j };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Context } from 'hono';
|
|
2
|
-
import {
|
|
2
|
+
import { a as EventDef, o as EventRouterBase, I as InferEventNames, i as InferEventPayload } from './router-CX5uVjmU.js';
|
|
3
|
+
import { c as SSETokenStore, S as SSETokenManager } from './token-manager-Ckjt2-qq.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* WebSocket Types
|
|
@@ -8,9 +9,9 @@ import { E as EventDef, b as EventRouterDef, I as InferEventNames, e as InferEve
|
|
|
8
9
|
/**
|
|
9
10
|
* WebSocket Router Definition
|
|
10
11
|
*
|
|
11
|
-
* Extends
|
|
12
|
+
* Extends the event router shape with client→server message handlers.
|
|
12
13
|
*/
|
|
13
|
-
interface WSRouterDef<TEvents extends Record<string, EventDef<any>>, TMessages extends WSMessageHandlers = WSMessageHandlers> extends
|
|
14
|
+
interface WSRouterDef<TEvents extends Record<string, EventDef<any>>, TMessages extends WSMessageHandlers = WSMessageHandlers> extends EventRouterBase<TEvents> {
|
|
14
15
|
messages: TMessages;
|
|
15
16
|
}
|
|
16
17
|
/**
|