@tanstack/react-start-client 1.111.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/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/cjs/Meta.cjs +14 -0
- package/dist/cjs/Meta.cjs.map +1 -0
- package/dist/cjs/Meta.d.cts +1 -0
- package/dist/cjs/Scripts.cjs +12 -0
- package/dist/cjs/Scripts.cjs.map +1 -0
- package/dist/cjs/Scripts.d.cts +1 -0
- package/dist/cjs/StartClient.cjs +24 -0
- package/dist/cjs/StartClient.cjs.map +1 -0
- package/dist/cjs/StartClient.d.cts +4 -0
- package/dist/cjs/createIsomorphicFn.cjs +7 -0
- package/dist/cjs/createIsomorphicFn.cjs.map +1 -0
- package/dist/cjs/createIsomorphicFn.d.cts +12 -0
- package/dist/cjs/createMiddleware.cjs +34 -0
- package/dist/cjs/createMiddleware.cjs.map +1 -0
- package/dist/cjs/createMiddleware.d.cts +129 -0
- package/dist/cjs/createServerFn.cjs +369 -0
- package/dist/cjs/createServerFn.cjs.map +1 -0
- package/dist/cjs/createServerFn.d.cts +137 -0
- package/dist/cjs/envOnly.cjs +7 -0
- package/dist/cjs/envOnly.cjs.map +1 -0
- package/dist/cjs/envOnly.d.cts +4 -0
- package/dist/cjs/headers.cjs +30 -0
- package/dist/cjs/headers.cjs.map +1 -0
- package/dist/cjs/headers.d.cts +5 -0
- package/dist/cjs/index.cjs +31 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +14 -0
- package/dist/cjs/json.cjs +14 -0
- package/dist/cjs/json.cjs.map +1 -0
- package/dist/cjs/json.d.cts +2 -0
- package/dist/cjs/registerGlobalMiddleware.cjs +9 -0
- package/dist/cjs/registerGlobalMiddleware.cjs.map +1 -0
- package/dist/cjs/registerGlobalMiddleware.d.cts +5 -0
- package/dist/cjs/renderRSC.cjs +29 -0
- package/dist/cjs/renderRSC.cjs.map +1 -0
- package/dist/cjs/renderRSC.d.cts +2 -0
- package/dist/cjs/routesManifest.d.cts +0 -0
- package/dist/cjs/serializer.cjs +152 -0
- package/dist/cjs/serializer.cjs.map +1 -0
- package/dist/cjs/serializer.d.cts +2 -0
- package/dist/cjs/ssr-client.cjs +114 -0
- package/dist/cjs/ssr-client.cjs.map +1 -0
- package/dist/cjs/ssr-client.d.cts +65 -0
- package/dist/cjs/tests/createIsomorphicFn.test-d.d.cts +1 -0
- package/dist/cjs/tests/createServerFn.test-d.d.cts +1 -0
- package/dist/cjs/tests/createServerMiddleware.test-d.d.cts +1 -0
- package/dist/cjs/tests/envOnly.test-d.d.cts +1 -0
- package/dist/cjs/tests/json.test.d.cts +1 -0
- package/dist/cjs/tests/transformer.test.d.cts +1 -0
- package/dist/cjs/useServerFn.cjs +26 -0
- package/dist/cjs/useServerFn.cjs.map +1 -0
- package/dist/cjs/useServerFn.d.cts +1 -0
- package/dist/esm/Meta.d.ts +1 -0
- package/dist/esm/Meta.js +14 -0
- package/dist/esm/Meta.js.map +1 -0
- package/dist/esm/Scripts.d.ts +1 -0
- package/dist/esm/Scripts.js +12 -0
- package/dist/esm/Scripts.js.map +1 -0
- package/dist/esm/StartClient.d.ts +4 -0
- package/dist/esm/StartClient.js +24 -0
- package/dist/esm/StartClient.js.map +1 -0
- package/dist/esm/createIsomorphicFn.d.ts +12 -0
- package/dist/esm/createIsomorphicFn.js +7 -0
- package/dist/esm/createIsomorphicFn.js.map +1 -0
- package/dist/esm/createMiddleware.d.ts +129 -0
- package/dist/esm/createMiddleware.js +34 -0
- package/dist/esm/createMiddleware.js.map +1 -0
- package/dist/esm/createServerFn.d.ts +137 -0
- package/dist/esm/createServerFn.js +347 -0
- package/dist/esm/createServerFn.js.map +1 -0
- package/dist/esm/envOnly.d.ts +4 -0
- package/dist/esm/envOnly.js +7 -0
- package/dist/esm/envOnly.js.map +1 -0
- package/dist/esm/headers.d.ts +5 -0
- package/dist/esm/headers.js +30 -0
- package/dist/esm/headers.js.map +1 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.js +31 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/json.d.ts +2 -0
- package/dist/esm/json.js +14 -0
- package/dist/esm/json.js.map +1 -0
- package/dist/esm/registerGlobalMiddleware.d.ts +5 -0
- package/dist/esm/registerGlobalMiddleware.js +9 -0
- package/dist/esm/registerGlobalMiddleware.js.map +1 -0
- package/dist/esm/renderRSC.d.ts +2 -0
- package/dist/esm/renderRSC.js +29 -0
- package/dist/esm/renderRSC.js.map +1 -0
- package/dist/esm/routesManifest.d.ts +0 -0
- package/dist/esm/serializer.d.ts +2 -0
- package/dist/esm/serializer.js +152 -0
- package/dist/esm/serializer.js.map +1 -0
- package/dist/esm/ssr-client.d.ts +65 -0
- package/dist/esm/ssr-client.js +114 -0
- package/dist/esm/ssr-client.js.map +1 -0
- package/dist/esm/tests/createIsomorphicFn.test-d.d.ts +1 -0
- package/dist/esm/tests/createServerFn.test-d.d.ts +1 -0
- package/dist/esm/tests/createServerMiddleware.test-d.d.ts +1 -0
- package/dist/esm/tests/envOnly.test-d.d.ts +1 -0
- package/dist/esm/tests/json.test.d.ts +1 -0
- package/dist/esm/tests/transformer.test.d.ts +1 -0
- package/dist/esm/useServerFn.d.ts +1 -0
- package/dist/esm/useServerFn.js +26 -0
- package/dist/esm/useServerFn.js.map +1 -0
- package/package.json +68 -0
- package/src/Meta.tsx +10 -0
- package/src/Scripts.tsx +8 -0
- package/src/StartClient.tsx +21 -0
- package/src/createIsomorphicFn.ts +36 -0
- package/src/createMiddleware.ts +517 -0
- package/src/createServerFn.ts +790 -0
- package/src/envOnly.ts +9 -0
- package/src/headers.ts +50 -0
- package/src/index.tsx +74 -0
- package/src/json.ts +15 -0
- package/src/registerGlobalMiddleware.ts +9 -0
- package/src/renderRSC.tsx +91 -0
- package/src/routesManifest.ts +0 -0
- package/src/serializer.ts +177 -0
- package/src/ssr-client.tsx +221 -0
- package/src/tests/createIsomorphicFn.test-d.ts +72 -0
- package/src/tests/createServerFn.test-d.tsx +390 -0
- package/src/tests/createServerMiddleware.test-d.ts +611 -0
- package/src/tests/envOnly.test-d.ts +34 -0
- package/src/tests/json.test.ts +37 -0
- package/src/tests/transformer.test.tsx +147 -0
- package/src/useServerFn.ts +30 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { default as invariant } from 'tiny-invariant'
|
|
2
|
+
import { default as warning } from 'tiny-warning'
|
|
3
|
+
import { isNotFound, isRedirect } from '@tanstack/react-router'
|
|
4
|
+
import { mergeHeaders } from './headers'
|
|
5
|
+
import { globalMiddleware } from './registerGlobalMiddleware'
|
|
6
|
+
import { startSerializer } from './serializer'
|
|
7
|
+
import type {
|
|
8
|
+
AnyValidator,
|
|
9
|
+
Constrain,
|
|
10
|
+
Expand,
|
|
11
|
+
ResolveValidatorInput,
|
|
12
|
+
SerializerParse,
|
|
13
|
+
SerializerStringify,
|
|
14
|
+
SerializerStringifyBy,
|
|
15
|
+
Validator,
|
|
16
|
+
} from '@tanstack/router-core'
|
|
17
|
+
import type {
|
|
18
|
+
AnyMiddleware,
|
|
19
|
+
AssignAllClientSendContext,
|
|
20
|
+
AssignAllServerContext,
|
|
21
|
+
IntersectAllValidatorInputs,
|
|
22
|
+
IntersectAllValidatorOutputs,
|
|
23
|
+
MiddlewareClientFnResult,
|
|
24
|
+
MiddlewareServerFnResult,
|
|
25
|
+
} from './createMiddleware'
|
|
26
|
+
|
|
27
|
+
export interface JsonResponse<TData> extends Response {
|
|
28
|
+
json: () => Promise<TData>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type CompiledFetcherFnOptions = {
|
|
32
|
+
method: Method
|
|
33
|
+
data: unknown
|
|
34
|
+
headers?: HeadersInit
|
|
35
|
+
signal?: AbortSignal
|
|
36
|
+
context?: any
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type Fetcher<TMiddlewares, TValidator, TResponse> =
|
|
40
|
+
undefined extends IntersectAllValidatorInputs<TMiddlewares, TValidator>
|
|
41
|
+
? OptionalFetcher<TMiddlewares, TValidator, TResponse>
|
|
42
|
+
: RequiredFetcher<TMiddlewares, TValidator, TResponse>
|
|
43
|
+
|
|
44
|
+
export interface FetcherBase {
|
|
45
|
+
url: string
|
|
46
|
+
__executeServer: (opts: {
|
|
47
|
+
method: Method
|
|
48
|
+
data: unknown
|
|
49
|
+
headers?: HeadersInit
|
|
50
|
+
context?: any
|
|
51
|
+
signal: AbortSignal
|
|
52
|
+
}) => Promise<unknown>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type FetchResult<
|
|
56
|
+
TMiddlewares,
|
|
57
|
+
TResponse,
|
|
58
|
+
TFullResponse extends boolean,
|
|
59
|
+
> = false extends TFullResponse
|
|
60
|
+
? Promise<FetcherData<TResponse>>
|
|
61
|
+
: Promise<FullFetcherData<TMiddlewares, TResponse>>
|
|
62
|
+
|
|
63
|
+
export interface OptionalFetcher<TMiddlewares, TValidator, TResponse>
|
|
64
|
+
extends FetcherBase {
|
|
65
|
+
<TFullResponse extends boolean>(
|
|
66
|
+
options?: OptionalFetcherDataOptions<
|
|
67
|
+
TMiddlewares,
|
|
68
|
+
TValidator,
|
|
69
|
+
TFullResponse
|
|
70
|
+
>,
|
|
71
|
+
): FetchResult<TMiddlewares, TResponse, TFullResponse>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RequiredFetcher<TMiddlewares, TValidator, TResponse>
|
|
75
|
+
extends FetcherBase {
|
|
76
|
+
<TFullResponse extends boolean>(
|
|
77
|
+
opts: RequiredFetcherDataOptions<TMiddlewares, TValidator, TFullResponse>,
|
|
78
|
+
): FetchResult<TMiddlewares, TResponse, TFullResponse>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type FetcherBaseOptions<TFullResponse extends boolean = false> = {
|
|
82
|
+
headers?: HeadersInit
|
|
83
|
+
type?: ServerFnType
|
|
84
|
+
signal?: AbortSignal
|
|
85
|
+
fullResponse?: TFullResponse
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ServerFnType = 'static' | 'dynamic'
|
|
89
|
+
|
|
90
|
+
export interface OptionalFetcherDataOptions<
|
|
91
|
+
TMiddlewares,
|
|
92
|
+
TValidator,
|
|
93
|
+
TFullResponse extends boolean,
|
|
94
|
+
> extends FetcherBaseOptions<TFullResponse> {
|
|
95
|
+
data?: Expand<IntersectAllValidatorInputs<TMiddlewares, TValidator>>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface RequiredFetcherDataOptions<
|
|
99
|
+
TMiddlewares,
|
|
100
|
+
TValidator,
|
|
101
|
+
TFullResponse extends boolean,
|
|
102
|
+
> extends FetcherBaseOptions<TFullResponse> {
|
|
103
|
+
data: Expand<IntersectAllValidatorInputs<TMiddlewares, TValidator>>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface FullFetcherData<TMiddlewares, TResponse> {
|
|
107
|
+
error: unknown
|
|
108
|
+
result: FetcherData<TResponse>
|
|
109
|
+
context: AssignAllClientSendContext<TMiddlewares>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type FetcherData<TResponse> =
|
|
113
|
+
TResponse extends JsonResponse<any>
|
|
114
|
+
? SerializerParse<ReturnType<TResponse['json']>>
|
|
115
|
+
: SerializerParse<TResponse>
|
|
116
|
+
|
|
117
|
+
export type RscStream<T> = {
|
|
118
|
+
__cacheState: T
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type Method = 'GET' | 'POST'
|
|
122
|
+
|
|
123
|
+
export type ServerFn<TMethod, TMiddlewares, TValidator, TResponse> = (
|
|
124
|
+
ctx: ServerFnCtx<TMethod, TMiddlewares, TValidator>,
|
|
125
|
+
) => Promise<SerializerStringify<TResponse>> | SerializerStringify<TResponse>
|
|
126
|
+
|
|
127
|
+
export interface ServerFnCtx<TMethod, TMiddlewares, TValidator> {
|
|
128
|
+
method: TMethod
|
|
129
|
+
data: Expand<IntersectAllValidatorOutputs<TMiddlewares, TValidator>>
|
|
130
|
+
context: Expand<AssignAllServerContext<TMiddlewares>>
|
|
131
|
+
signal: AbortSignal
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type CompiledFetcherFn<TResponse> = {
|
|
135
|
+
(
|
|
136
|
+
opts: CompiledFetcherFnOptions & ServerFnBaseOptions<Method>,
|
|
137
|
+
): Promise<TResponse>
|
|
138
|
+
url: string
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type ServerFnBaseOptions<
|
|
142
|
+
TMethod extends Method = 'GET',
|
|
143
|
+
TResponse = unknown,
|
|
144
|
+
TMiddlewares = unknown,
|
|
145
|
+
TInput = unknown,
|
|
146
|
+
> = {
|
|
147
|
+
method: TMethod
|
|
148
|
+
validateClient?: boolean
|
|
149
|
+
middleware?: Constrain<TMiddlewares, ReadonlyArray<AnyMiddleware>>
|
|
150
|
+
validator?: ConstrainValidator<TInput>
|
|
151
|
+
extractedFn?: CompiledFetcherFn<TResponse>
|
|
152
|
+
serverFn?: ServerFn<TMethod, TMiddlewares, TInput, TResponse>
|
|
153
|
+
functionId: string
|
|
154
|
+
type: ServerFnTypeOrTypeFn<TMethod, TMiddlewares, AnyValidator>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type ValidatorSerializerStringify<TValidator> = Validator<
|
|
158
|
+
SerializerStringifyBy<
|
|
159
|
+
ResolveValidatorInput<TValidator>,
|
|
160
|
+
Date | undefined | FormData
|
|
161
|
+
>,
|
|
162
|
+
any
|
|
163
|
+
>
|
|
164
|
+
|
|
165
|
+
export type ConstrainValidator<TValidator> = unknown extends TValidator
|
|
166
|
+
? TValidator
|
|
167
|
+
: Constrain<TValidator, ValidatorSerializerStringify<TValidator>>
|
|
168
|
+
|
|
169
|
+
export interface ServerFnMiddleware<TMethod extends Method, TValidator> {
|
|
170
|
+
middleware: <const TNewMiddlewares = undefined>(
|
|
171
|
+
middlewares: Constrain<TNewMiddlewares, ReadonlyArray<AnyMiddleware>>,
|
|
172
|
+
) => ServerFnAfterMiddleware<TMethod, TNewMiddlewares, TValidator>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ServerFnAfterMiddleware<
|
|
176
|
+
TMethod extends Method,
|
|
177
|
+
TMiddlewares,
|
|
178
|
+
TValidator,
|
|
179
|
+
> extends ServerFnValidator<TMethod, TMiddlewares>,
|
|
180
|
+
ServerFnTyper<TMethod, TMiddlewares, TValidator>,
|
|
181
|
+
ServerFnHandler<TMethod, TMiddlewares, TValidator> {}
|
|
182
|
+
|
|
183
|
+
export type ValidatorFn<TMethod extends Method, TMiddlewares> = <TValidator>(
|
|
184
|
+
validator: ConstrainValidator<TValidator>,
|
|
185
|
+
) => ServerFnAfterValidator<TMethod, TMiddlewares, TValidator>
|
|
186
|
+
|
|
187
|
+
export interface ServerFnValidator<TMethod extends Method, TMiddlewares> {
|
|
188
|
+
validator: ValidatorFn<TMethod, TMiddlewares>
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface ServerFnAfterValidator<
|
|
192
|
+
TMethod extends Method,
|
|
193
|
+
TMiddlewares,
|
|
194
|
+
TValidator,
|
|
195
|
+
> extends ServerFnMiddleware<TMethod, TValidator>,
|
|
196
|
+
ServerFnTyper<TMethod, TMiddlewares, TValidator>,
|
|
197
|
+
ServerFnHandler<TMethod, TMiddlewares, TValidator> {}
|
|
198
|
+
|
|
199
|
+
// Typer
|
|
200
|
+
export interface ServerFnTyper<
|
|
201
|
+
TMethod extends Method,
|
|
202
|
+
TMiddlewares,
|
|
203
|
+
TValidator,
|
|
204
|
+
> {
|
|
205
|
+
type: (
|
|
206
|
+
typer: ServerFnTypeOrTypeFn<TMethod, TMiddlewares, TValidator>,
|
|
207
|
+
) => ServerFnAfterTyper<TMethod, TMiddlewares, TValidator>
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type ServerFnTypeOrTypeFn<
|
|
211
|
+
TMethod extends Method,
|
|
212
|
+
TMiddlewares,
|
|
213
|
+
TValidator,
|
|
214
|
+
> =
|
|
215
|
+
| ServerFnType
|
|
216
|
+
| ((ctx: ServerFnCtx<TMethod, TMiddlewares, TValidator>) => ServerFnType)
|
|
217
|
+
|
|
218
|
+
export interface ServerFnAfterTyper<
|
|
219
|
+
TMethod extends Method,
|
|
220
|
+
TMiddlewares,
|
|
221
|
+
TValidator,
|
|
222
|
+
> extends ServerFnHandler<TMethod, TMiddlewares, TValidator> {}
|
|
223
|
+
|
|
224
|
+
// Handler
|
|
225
|
+
export interface ServerFnHandler<
|
|
226
|
+
TMethod extends Method,
|
|
227
|
+
TMiddlewares,
|
|
228
|
+
TValidator,
|
|
229
|
+
> {
|
|
230
|
+
handler: <TNewResponse>(
|
|
231
|
+
fn?: ServerFn<TMethod, TMiddlewares, TValidator, TNewResponse>,
|
|
232
|
+
) => Fetcher<TMiddlewares, TValidator, TNewResponse>
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface ServerFnBuilder<TMethod extends Method = 'GET'>
|
|
236
|
+
extends ServerFnMiddleware<TMethod, undefined>,
|
|
237
|
+
ServerFnValidator<TMethod, undefined>,
|
|
238
|
+
ServerFnTyper<TMethod, undefined, undefined>,
|
|
239
|
+
ServerFnHandler<TMethod, undefined, undefined> {
|
|
240
|
+
options: ServerFnBaseOptions<TMethod, unknown, undefined, undefined>
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
type StaticCachedResult = {
|
|
244
|
+
ctx?: {
|
|
245
|
+
result: any
|
|
246
|
+
context: any
|
|
247
|
+
}
|
|
248
|
+
error?: any
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export type ServerFnStaticCache = {
|
|
252
|
+
getItem: (
|
|
253
|
+
ctx: MiddlewareResult,
|
|
254
|
+
) => StaticCachedResult | Promise<StaticCachedResult | undefined>
|
|
255
|
+
setItem: (
|
|
256
|
+
ctx: MiddlewareResult,
|
|
257
|
+
response: StaticCachedResult,
|
|
258
|
+
) => Promise<void>
|
|
259
|
+
fetchItem: (
|
|
260
|
+
ctx: MiddlewareResult,
|
|
261
|
+
) => StaticCachedResult | Promise<StaticCachedResult | undefined>
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let serverFnStaticCache: ServerFnStaticCache | undefined
|
|
265
|
+
|
|
266
|
+
export function setServerFnStaticCache(
|
|
267
|
+
cache?: ServerFnStaticCache | (() => ServerFnStaticCache | undefined),
|
|
268
|
+
) {
|
|
269
|
+
const previousCache = serverFnStaticCache
|
|
270
|
+
serverFnStaticCache = typeof cache === 'function' ? cache() : cache
|
|
271
|
+
|
|
272
|
+
return () => {
|
|
273
|
+
serverFnStaticCache = previousCache
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function createServerFnStaticCache(
|
|
278
|
+
serverFnStaticCache: ServerFnStaticCache,
|
|
279
|
+
) {
|
|
280
|
+
return serverFnStaticCache
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
setServerFnStaticCache(() => {
|
|
284
|
+
const getStaticCacheUrl = (options: MiddlewareResult, hash: string) => {
|
|
285
|
+
return `/__tsr/staticServerFnCache/${options.functionId}__${hash}.json`
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const jsonToFilenameSafeString = (json: any) => {
|
|
289
|
+
// Custom replacer to sort keys
|
|
290
|
+
const sortedKeysReplacer = (key: string, value: any) =>
|
|
291
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
292
|
+
? Object.keys(value)
|
|
293
|
+
.sort()
|
|
294
|
+
.reduce((acc: any, curr: string) => {
|
|
295
|
+
acc[curr] = value[curr]
|
|
296
|
+
return acc
|
|
297
|
+
}, {})
|
|
298
|
+
: value
|
|
299
|
+
|
|
300
|
+
// Convert JSON to string with sorted keys
|
|
301
|
+
const jsonString = JSON.stringify(json ?? '', sortedKeysReplacer)
|
|
302
|
+
|
|
303
|
+
// Replace characters invalid in filenames
|
|
304
|
+
return jsonString
|
|
305
|
+
.replace(/[/\\?%*:|"<>]/g, '-') // Replace invalid characters with a dash
|
|
306
|
+
.replace(/\s+/g, '_') // Optionally replace whitespace with underscores
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const staticClientCache =
|
|
310
|
+
typeof document !== 'undefined' ? new Map<string, any>() : null
|
|
311
|
+
|
|
312
|
+
return createServerFnStaticCache({
|
|
313
|
+
getItem: async (ctx) => {
|
|
314
|
+
if (typeof document === 'undefined') {
|
|
315
|
+
const hash = jsonToFilenameSafeString(ctx.data)
|
|
316
|
+
const url = getStaticCacheUrl(ctx, hash)
|
|
317
|
+
const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
|
|
318
|
+
|
|
319
|
+
// Use fs instead of fetch to read from filesystem
|
|
320
|
+
const { promises: fs } = await import('node:fs')
|
|
321
|
+
const path = await import('node:path')
|
|
322
|
+
const filePath = path.join(publicUrl, url)
|
|
323
|
+
|
|
324
|
+
const [cachedResult, readError] = await fs
|
|
325
|
+
.readFile(filePath, 'utf-8')
|
|
326
|
+
.then((c) => [
|
|
327
|
+
startSerializer.parse(c) as {
|
|
328
|
+
ctx: unknown
|
|
329
|
+
error: any
|
|
330
|
+
},
|
|
331
|
+
null,
|
|
332
|
+
])
|
|
333
|
+
.catch((e) => [null, e])
|
|
334
|
+
|
|
335
|
+
if (readError && readError.code !== 'ENOENT') {
|
|
336
|
+
throw readError
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return cachedResult as StaticCachedResult
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return undefined
|
|
343
|
+
},
|
|
344
|
+
setItem: async (ctx, response) => {
|
|
345
|
+
const { promises: fs } = await import('node:fs')
|
|
346
|
+
const path = await import('node:path')
|
|
347
|
+
|
|
348
|
+
const hash = jsonToFilenameSafeString(ctx.data)
|
|
349
|
+
const url = getStaticCacheUrl(ctx, hash)
|
|
350
|
+
const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
|
|
351
|
+
const filePath = path.join(publicUrl, url)
|
|
352
|
+
|
|
353
|
+
// Ensure the directory exists
|
|
354
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
355
|
+
|
|
356
|
+
// Store the result with fs
|
|
357
|
+
await fs.writeFile(filePath, startSerializer.stringify(response))
|
|
358
|
+
},
|
|
359
|
+
fetchItem: async (ctx) => {
|
|
360
|
+
const hash = jsonToFilenameSafeString(ctx.data)
|
|
361
|
+
const url = getStaticCacheUrl(ctx, hash)
|
|
362
|
+
|
|
363
|
+
let result: any = staticClientCache?.get(url)
|
|
364
|
+
|
|
365
|
+
if (!result) {
|
|
366
|
+
result = await fetch(url, {
|
|
367
|
+
method: 'GET',
|
|
368
|
+
})
|
|
369
|
+
.then((r) => r.text())
|
|
370
|
+
.then((d) => startSerializer.parse(d))
|
|
371
|
+
|
|
372
|
+
staticClientCache?.set(url, result)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return result
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
export function createServerFn<
|
|
381
|
+
TMethod extends Method,
|
|
382
|
+
TResponse = unknown,
|
|
383
|
+
TMiddlewares = undefined,
|
|
384
|
+
TValidator = undefined,
|
|
385
|
+
>(
|
|
386
|
+
options?: {
|
|
387
|
+
method?: TMethod
|
|
388
|
+
type?: ServerFnType
|
|
389
|
+
},
|
|
390
|
+
__opts?: ServerFnBaseOptions<TMethod, TResponse, TMiddlewares, TValidator>,
|
|
391
|
+
): ServerFnBuilder<TMethod> {
|
|
392
|
+
const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<
|
|
393
|
+
TMethod,
|
|
394
|
+
TResponse,
|
|
395
|
+
TMiddlewares,
|
|
396
|
+
TValidator
|
|
397
|
+
>
|
|
398
|
+
|
|
399
|
+
if (typeof resolvedOptions.method === 'undefined') {
|
|
400
|
+
resolvedOptions.method = 'GET' as TMethod
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
options: resolvedOptions as any,
|
|
405
|
+
middleware: (middleware) => {
|
|
406
|
+
return createServerFn<TMethod, TResponse, TMiddlewares, TValidator>(
|
|
407
|
+
undefined,
|
|
408
|
+
Object.assign(resolvedOptions, { middleware }),
|
|
409
|
+
) as any
|
|
410
|
+
},
|
|
411
|
+
validator: (validator) => {
|
|
412
|
+
return createServerFn<TMethod, TResponse, TMiddlewares, TValidator>(
|
|
413
|
+
undefined,
|
|
414
|
+
Object.assign(resolvedOptions, { validator }),
|
|
415
|
+
) as any
|
|
416
|
+
},
|
|
417
|
+
type: (type) => {
|
|
418
|
+
return createServerFn<TMethod, TResponse, TMiddlewares, TValidator>(
|
|
419
|
+
undefined,
|
|
420
|
+
Object.assign(resolvedOptions, { type }),
|
|
421
|
+
) as any
|
|
422
|
+
},
|
|
423
|
+
handler: (...args) => {
|
|
424
|
+
// This function signature changes due to AST transformations
|
|
425
|
+
// in the babel plugin. We need to cast it to the correct
|
|
426
|
+
// function signature post-transformation
|
|
427
|
+
const [extractedFn, serverFn] = args as unknown as [
|
|
428
|
+
CompiledFetcherFn<TResponse>,
|
|
429
|
+
ServerFn<TMethod, TMiddlewares, TValidator, TResponse>,
|
|
430
|
+
]
|
|
431
|
+
|
|
432
|
+
// Keep the original function around so we can use it
|
|
433
|
+
// in the server environment
|
|
434
|
+
Object.assign(resolvedOptions, {
|
|
435
|
+
...extractedFn,
|
|
436
|
+
extractedFn,
|
|
437
|
+
serverFn,
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
const resolvedMiddleware = [
|
|
441
|
+
...(resolvedOptions.middleware || []),
|
|
442
|
+
serverFnBaseToMiddleware(resolvedOptions),
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
// We want to make sure the new function has the same
|
|
446
|
+
// properties as the original function
|
|
447
|
+
return Object.assign(
|
|
448
|
+
async (opts?: CompiledFetcherFnOptions) => {
|
|
449
|
+
// Start by executing the client-side middleware chain
|
|
450
|
+
return executeMiddleware(resolvedMiddleware, 'client', {
|
|
451
|
+
...extractedFn,
|
|
452
|
+
...resolvedOptions,
|
|
453
|
+
data: opts?.data as any,
|
|
454
|
+
headers: opts?.headers,
|
|
455
|
+
signal: opts?.signal,
|
|
456
|
+
context: {},
|
|
457
|
+
}).then((d) => {
|
|
458
|
+
if (d.error) throw d.error
|
|
459
|
+
return d.result
|
|
460
|
+
})
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
// This copies over the URL, function ID
|
|
464
|
+
...extractedFn,
|
|
465
|
+
// The extracted function on the server-side calls
|
|
466
|
+
// this function
|
|
467
|
+
__executeServer: async (opts_: any, signal: AbortSignal) => {
|
|
468
|
+
const opts =
|
|
469
|
+
opts_ instanceof FormData ? extractFormDataContext(opts_) : opts_
|
|
470
|
+
|
|
471
|
+
opts.type =
|
|
472
|
+
typeof resolvedOptions.type === 'function'
|
|
473
|
+
? resolvedOptions.type(opts)
|
|
474
|
+
: resolvedOptions.type
|
|
475
|
+
|
|
476
|
+
const ctx = {
|
|
477
|
+
...extractedFn,
|
|
478
|
+
...opts,
|
|
479
|
+
signal,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const run = () =>
|
|
483
|
+
executeMiddleware(resolvedMiddleware, 'server', ctx).then(
|
|
484
|
+
(d) => ({
|
|
485
|
+
// Only send the result and sendContext back to the client
|
|
486
|
+
result: d.result,
|
|
487
|
+
error: d.error,
|
|
488
|
+
context: d.sendContext,
|
|
489
|
+
}),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if (ctx.type === 'static') {
|
|
493
|
+
let response: StaticCachedResult | undefined
|
|
494
|
+
|
|
495
|
+
// If we can get the cached item, try to get it
|
|
496
|
+
if (serverFnStaticCache?.getItem) {
|
|
497
|
+
// If this throws, it's okay to let it bubble up
|
|
498
|
+
response = await serverFnStaticCache.getItem(ctx)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!response) {
|
|
502
|
+
// If there's no cached item, execute the server function
|
|
503
|
+
response = await run()
|
|
504
|
+
.then((d) => {
|
|
505
|
+
return {
|
|
506
|
+
ctx: d,
|
|
507
|
+
error: null,
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
.catch((e) => {
|
|
511
|
+
return {
|
|
512
|
+
ctx: undefined,
|
|
513
|
+
error: e,
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
if (serverFnStaticCache?.setItem) {
|
|
518
|
+
await serverFnStaticCache.setItem(ctx, response)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
invariant(
|
|
523
|
+
response,
|
|
524
|
+
'No response from both server and static cache!',
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
if (response.error) {
|
|
528
|
+
throw response.error
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return response.ctx
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return run()
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
) as any
|
|
538
|
+
},
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function extractFormDataContext(formData: FormData) {
|
|
543
|
+
const serializedContext = formData.get('__TSR_CONTEXT')
|
|
544
|
+
formData.delete('__TSR_CONTEXT')
|
|
545
|
+
|
|
546
|
+
if (typeof serializedContext !== 'string') {
|
|
547
|
+
return {
|
|
548
|
+
context: {},
|
|
549
|
+
data: formData,
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
const context = startSerializer.parse(serializedContext)
|
|
555
|
+
return {
|
|
556
|
+
context,
|
|
557
|
+
data: formData,
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
return {
|
|
561
|
+
data: formData,
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function flattenMiddlewares(
|
|
567
|
+
middlewares: Array<AnyMiddleware>,
|
|
568
|
+
): Array<AnyMiddleware> {
|
|
569
|
+
const seen = new Set<AnyMiddleware>()
|
|
570
|
+
const flattened: Array<AnyMiddleware> = []
|
|
571
|
+
|
|
572
|
+
const recurse = (middleware: Array<AnyMiddleware>) => {
|
|
573
|
+
middleware.forEach((m) => {
|
|
574
|
+
if (m.options.middleware) {
|
|
575
|
+
recurse(m.options.middleware)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!seen.has(m)) {
|
|
579
|
+
seen.add(m)
|
|
580
|
+
flattened.push(m)
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
recurse(middlewares)
|
|
586
|
+
|
|
587
|
+
return flattened
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export type MiddlewareOptions = {
|
|
591
|
+
method: Method
|
|
592
|
+
data: any
|
|
593
|
+
headers?: HeadersInit
|
|
594
|
+
signal?: AbortSignal
|
|
595
|
+
sendContext?: any
|
|
596
|
+
context?: any
|
|
597
|
+
type: ServerFnTypeOrTypeFn<any, any, any>
|
|
598
|
+
functionId: string
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export type MiddlewareResult = MiddlewareOptions & {
|
|
602
|
+
result?: unknown
|
|
603
|
+
error?: unknown
|
|
604
|
+
type: ServerFnTypeOrTypeFn<any, any, any>
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export type NextFn = (ctx: MiddlewareResult) => Promise<MiddlewareResult>
|
|
608
|
+
|
|
609
|
+
export type MiddlewareFn = (
|
|
610
|
+
ctx: MiddlewareOptions & {
|
|
611
|
+
next: NextFn
|
|
612
|
+
},
|
|
613
|
+
) => Promise<MiddlewareResult>
|
|
614
|
+
|
|
615
|
+
const applyMiddleware = async (
|
|
616
|
+
middlewareFn: MiddlewareFn,
|
|
617
|
+
ctx: MiddlewareOptions,
|
|
618
|
+
nextFn: NextFn,
|
|
619
|
+
) => {
|
|
620
|
+
return middlewareFn({
|
|
621
|
+
...ctx,
|
|
622
|
+
next: (async (userCtx: MiddlewareResult | undefined = {} as any) => {
|
|
623
|
+
// Return the next middleware
|
|
624
|
+
return nextFn({
|
|
625
|
+
...ctx,
|
|
626
|
+
...userCtx,
|
|
627
|
+
context: {
|
|
628
|
+
...ctx.context,
|
|
629
|
+
...userCtx.context,
|
|
630
|
+
},
|
|
631
|
+
sendContext: {
|
|
632
|
+
...ctx.sendContext,
|
|
633
|
+
...(userCtx.sendContext ?? {}),
|
|
634
|
+
},
|
|
635
|
+
headers: mergeHeaders(ctx.headers, userCtx.headers),
|
|
636
|
+
result:
|
|
637
|
+
userCtx.result !== undefined ? userCtx.result : (ctx as any).result,
|
|
638
|
+
error: userCtx.error ?? (ctx as any).error,
|
|
639
|
+
})
|
|
640
|
+
}) as any,
|
|
641
|
+
} as any)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function execValidator(validator: AnyValidator, input: unknown): unknown {
|
|
645
|
+
if (validator == null) return {}
|
|
646
|
+
|
|
647
|
+
if ('~standard' in validator) {
|
|
648
|
+
const result = validator['~standard'].validate(input)
|
|
649
|
+
|
|
650
|
+
if (result instanceof Promise)
|
|
651
|
+
throw new Error('Async validation not supported')
|
|
652
|
+
|
|
653
|
+
if (result.issues)
|
|
654
|
+
throw new Error(JSON.stringify(result.issues, undefined, 2))
|
|
655
|
+
|
|
656
|
+
return result.value
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if ('parse' in validator) {
|
|
660
|
+
return validator.parse(input)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (typeof validator === 'function') {
|
|
664
|
+
return validator(input)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
throw new Error('Invalid validator type!')
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function executeMiddleware(
|
|
671
|
+
middlewares: Array<AnyMiddleware>,
|
|
672
|
+
env: 'client' | 'server',
|
|
673
|
+
opts: MiddlewareOptions,
|
|
674
|
+
): Promise<MiddlewareResult> {
|
|
675
|
+
const flattenedMiddlewares = flattenMiddlewares([
|
|
676
|
+
...globalMiddleware,
|
|
677
|
+
...middlewares,
|
|
678
|
+
])
|
|
679
|
+
|
|
680
|
+
const next: NextFn = async (ctx) => {
|
|
681
|
+
// Get the next middleware
|
|
682
|
+
const nextMiddleware = flattenedMiddlewares.shift()
|
|
683
|
+
|
|
684
|
+
// If there are no more middlewares, return the context
|
|
685
|
+
if (!nextMiddleware) {
|
|
686
|
+
return ctx
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (
|
|
690
|
+
nextMiddleware.options.validator &&
|
|
691
|
+
(env === 'client' ? nextMiddleware.options.validateClient : true)
|
|
692
|
+
) {
|
|
693
|
+
// Execute the middleware's input function
|
|
694
|
+
ctx.data = await execValidator(nextMiddleware.options.validator, ctx.data)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const middlewareFn = (
|
|
698
|
+
env === 'client'
|
|
699
|
+
? nextMiddleware.options.client
|
|
700
|
+
: nextMiddleware.options.server
|
|
701
|
+
) as MiddlewareFn | undefined
|
|
702
|
+
|
|
703
|
+
if (middlewareFn) {
|
|
704
|
+
// Execute the middleware
|
|
705
|
+
return applyMiddleware(middlewareFn, ctx, async (newCtx) => {
|
|
706
|
+
return next(newCtx).catch((error) => {
|
|
707
|
+
if (isRedirect(error) || isNotFound(error)) {
|
|
708
|
+
return {
|
|
709
|
+
...newCtx,
|
|
710
|
+
error,
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
throw error
|
|
715
|
+
})
|
|
716
|
+
})
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return next(ctx)
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Start the middleware chain
|
|
723
|
+
return next({
|
|
724
|
+
...opts,
|
|
725
|
+
headers: opts.headers || {},
|
|
726
|
+
sendContext: opts.sendContext || {},
|
|
727
|
+
context: opts.context || {},
|
|
728
|
+
})
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function serverFnBaseToMiddleware(
|
|
732
|
+
options: ServerFnBaseOptions<any, any, any, any>,
|
|
733
|
+
): AnyMiddleware {
|
|
734
|
+
return {
|
|
735
|
+
_types: undefined!,
|
|
736
|
+
options: {
|
|
737
|
+
validator: options.validator,
|
|
738
|
+
validateClient: options.validateClient,
|
|
739
|
+
client: async ({ next, sendContext, ...ctx }) => {
|
|
740
|
+
const payload = {
|
|
741
|
+
...ctx,
|
|
742
|
+
// switch the sendContext over to context
|
|
743
|
+
context: sendContext,
|
|
744
|
+
type: typeof ctx.type === 'function' ? ctx.type(ctx) : ctx.type,
|
|
745
|
+
} as any
|
|
746
|
+
|
|
747
|
+
if (
|
|
748
|
+
ctx.type === 'static' &&
|
|
749
|
+
process.env.NODE_ENV === 'production' &&
|
|
750
|
+
typeof document !== 'undefined'
|
|
751
|
+
) {
|
|
752
|
+
invariant(
|
|
753
|
+
serverFnStaticCache,
|
|
754
|
+
'serverFnStaticCache.fetchItem is not available!',
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
const result = await serverFnStaticCache.fetchItem(payload)
|
|
758
|
+
|
|
759
|
+
if (result) {
|
|
760
|
+
if (result.error) {
|
|
761
|
+
throw result.error
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return next(result.ctx)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
warning(
|
|
768
|
+
result,
|
|
769
|
+
`No static cache item found for ${payload.functionId}__${JSON.stringify(payload.data)}, falling back to server function...`,
|
|
770
|
+
)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Execute the extracted function
|
|
774
|
+
// but not before serializing the context
|
|
775
|
+
const res = await options.extractedFn?.(payload)
|
|
776
|
+
|
|
777
|
+
return next(res) as unknown as MiddlewareClientFnResult<any, any, any>
|
|
778
|
+
},
|
|
779
|
+
server: async ({ next, ...ctx }) => {
|
|
780
|
+
// Execute the server function
|
|
781
|
+
const result = await options.serverFn?.(ctx)
|
|
782
|
+
|
|
783
|
+
return next({
|
|
784
|
+
...ctx,
|
|
785
|
+
result,
|
|
786
|
+
} as any) as unknown as MiddlewareServerFnResult<any, any, any, any>
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
}
|
|
790
|
+
}
|