@tanstack/router-core 1.125.4 → 1.127.0

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.
Files changed (77) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +1 -1
  3. package/dist/cjs/RouterProvider.d.cts +1 -0
  4. package/dist/cjs/index.d.cts +1 -2
  5. package/dist/cjs/router.cjs +26 -33
  6. package/dist/cjs/router.cjs.map +1 -1
  7. package/dist/cjs/router.d.cts +10 -57
  8. package/dist/cjs/ssr/client.cjs +0 -2
  9. package/dist/cjs/ssr/client.cjs.map +1 -1
  10. package/dist/cjs/ssr/client.d.cts +1 -2
  11. package/dist/cjs/ssr/createRequestHandler.cjs +2 -1
  12. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  13. package/dist/cjs/ssr/seroval-plugins.cjs +34 -0
  14. package/dist/cjs/ssr/seroval-plugins.cjs.map +1 -0
  15. package/dist/cjs/ssr/seroval-plugins.d.cts +10 -0
  16. package/dist/cjs/ssr/server.cjs +0 -4
  17. package/dist/cjs/ssr/server.cjs.map +1 -1
  18. package/dist/cjs/ssr/server.d.cts +1 -3
  19. package/dist/cjs/ssr/ssr-client.cjs +18 -56
  20. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  21. package/dist/cjs/ssr/ssr-client.d.cts +17 -57
  22. package/dist/cjs/ssr/ssr-server.cjs +75 -220
  23. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  24. package/dist/cjs/ssr/ssr-server.d.cts +14 -28
  25. package/dist/cjs/ssr/transformStreamWithRouter.cjs +1 -0
  26. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  27. package/dist/cjs/ssr/tsrScript.cjs +1 -1
  28. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  29. package/dist/cjs/ssr/tsrScript.d.cts +0 -1
  30. package/dist/esm/Matches.d.ts +1 -1
  31. package/dist/esm/Matches.js.map +1 -1
  32. package/dist/esm/RouterProvider.d.ts +1 -0
  33. package/dist/esm/index.d.ts +1 -2
  34. package/dist/esm/router.d.ts +10 -57
  35. package/dist/esm/router.js +26 -33
  36. package/dist/esm/router.js.map +1 -1
  37. package/dist/esm/ssr/client.d.ts +1 -2
  38. package/dist/esm/ssr/client.js +1 -3
  39. package/dist/esm/ssr/client.js.map +1 -1
  40. package/dist/esm/ssr/createRequestHandler.js +3 -2
  41. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  42. package/dist/esm/ssr/seroval-plugins.d.ts +10 -0
  43. package/dist/esm/ssr/seroval-plugins.js +34 -0
  44. package/dist/esm/ssr/seroval-plugins.js.map +1 -0
  45. package/dist/esm/ssr/server.d.ts +1 -3
  46. package/dist/esm/ssr/server.js +1 -5
  47. package/dist/esm/ssr/ssr-client.d.ts +17 -57
  48. package/dist/esm/ssr/ssr-client.js +18 -56
  49. package/dist/esm/ssr/ssr-client.js.map +1 -1
  50. package/dist/esm/ssr/ssr-server.d.ts +14 -28
  51. package/dist/esm/ssr/ssr-server.js +76 -221
  52. package/dist/esm/ssr/ssr-server.js.map +1 -1
  53. package/dist/esm/ssr/transformStreamWithRouter.js +1 -0
  54. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  55. package/dist/esm/ssr/tsrScript.d.ts +0 -1
  56. package/dist/esm/ssr/tsrScript.js +1 -1
  57. package/dist/esm/ssr/tsrScript.js.map +1 -1
  58. package/package.json +3 -1
  59. package/src/Matches.ts +1 -1
  60. package/src/RouterProvider.ts +1 -0
  61. package/src/index.ts +0 -18
  62. package/src/router.ts +41 -89
  63. package/src/ssr/client.ts +1 -11
  64. package/src/ssr/createRequestHandler.ts +2 -2
  65. package/src/ssr/seroval-plugins.ts +43 -0
  66. package/src/ssr/server.ts +1 -14
  67. package/src/ssr/ssr-client.ts +35 -128
  68. package/src/ssr/ssr-server.ts +89 -307
  69. package/src/ssr/transformStreamWithRouter.ts +1 -0
  70. package/src/ssr/tsrScript.ts +4 -88
  71. package/dist/cjs/serializer.cjs +0 -146
  72. package/dist/cjs/serializer.cjs.map +0 -1
  73. package/dist/cjs/serializer.d.cts +0 -28
  74. package/dist/esm/serializer.d.ts +0 -28
  75. package/dist/esm/serializer.js +0 -146
  76. package/dist/esm/serializer.js.map +0 -1
  77. package/src/serializer.ts +0 -205
@@ -1,37 +1,50 @@
1
- import { default as warning } from 'tiny-warning'
2
- import jsesc from 'jsesc'
3
- import { TSR_DEFERRED_PROMISE, defer } from '../defer'
4
- import { isPlainArray, isPlainObject, pick } from '../utils'
5
- import { tsrSerializer } from '../serializer'
1
+ import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
2
+ import { ReadableStreamPlugin } from 'seroval-plugins/web'
3
+ import invariant from 'tiny-invariant'
4
+ import { createControlledPromise } from '../utils'
6
5
  import minifiedTsrBootStrapScript from './tsrScript?script-string'
7
- import type { DeferredPromise } from '../defer'
8
- import type {
9
- ClientExtractedBaseEntry,
10
- DehydratedRouter,
11
- ResolvePromiseState,
12
- SsrMatch,
13
- } from './ssr-client'
6
+ import { ShallowErrorPlugin } from './seroval-plugins'
14
7
  import type { AnyRouter } from '../router'
15
- import type { Manifest } from '../manifest'
8
+ import type { DehydratedMatch } from './ssr-client'
9
+ import type { DehydratedRouter } from './client'
16
10
  import type { AnyRouteMatch } from '../Matches'
11
+ import type { Manifest } from '../manifest'
17
12
 
18
- export type ServerExtractedEntry =
19
- | ServerExtractedStream
20
- | ServerExtractedPromise
21
-
22
- export interface ServerExtractedBaseEntry extends ClientExtractedBaseEntry {
23
- id: number
24
- matchIndex: number
13
+ declare module '../router' {
14
+ interface ServerSsr {
15
+ setRenderFinished: () => void
16
+ }
17
+ interface RouterEvents {
18
+ onInjectedHtml: {
19
+ type: 'onInjectedHtml'
20
+ promise: Promise<string>
21
+ }
22
+ }
25
23
  }
26
24
 
27
- export interface ServerExtractedStream extends ServerExtractedBaseEntry {
28
- type: 'stream'
29
- stream: ReadableStream
30
- }
25
+ export const GLOBAL_TSR = '$_TSR'
26
+ const SCOPE_ID = 'tsr'
27
+
28
+ export function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {
29
+ const dehydratedMatch: DehydratedMatch = {
30
+ i: match.id,
31
+ u: match.updatedAt,
32
+ s: match.status,
33
+ }
34
+
35
+ const properties = [
36
+ ['__beforeLoadContext', 'b'],
37
+ ['loaderData', 'l'],
38
+ ['error', 'e'],
39
+ ['ssr', 'ssr'],
40
+ ] as const
31
41
 
32
- export interface ServerExtractedPromise extends ServerExtractedBaseEntry {
33
- type: 'promise'
34
- promise: DeferredPromise<any>
42
+ for (const [key, shorthand] of properties) {
43
+ if (match[key] !== undefined) {
44
+ dehydratedMatch[shorthand] = match[key]
45
+ }
46
+ }
47
+ return dehydratedMatch
35
48
  }
36
49
 
37
50
  export function attachRouterServerSsrUtils(
@@ -40,12 +53,22 @@ export function attachRouterServerSsrUtils(
40
53
  ) {
41
54
  router.ssr = {
42
55
  manifest,
43
- serializer: tsrSerializer,
44
56
  }
57
+ const serializationRefs = new Map<unknown, number>()
58
+
59
+ let initialScriptSent = false
60
+ const getInitialScript = () => {
61
+ if (initialScriptSent) {
62
+ return ''
63
+ }
64
+ initialScriptSent = true
65
+ return `${getCrossReferenceHeader(SCOPE_ID)};${minifiedTsrBootStrapScript};`
66
+ }
67
+ let _dehydrated = false
68
+ const listeners: Array<() => void> = []
45
69
 
46
70
  router.serverSsr = {
47
71
  injectedHtml: [],
48
- streamedKeys: new Set(),
49
72
  injectHtml: (getHtml) => {
50
73
  const promise = Promise.resolve().then(getHtml)
51
74
  router.serverSsr!.injectedHtml.push(promise)
@@ -56,291 +79,50 @@ export function attachRouterServerSsrUtils(
56
79
 
57
80
  return promise.then(() => {})
58
81
  },
59
- injectScript: (getScript, opts) => {
82
+ injectScript: (getScript) => {
60
83
  return router.serverSsr!.injectHtml(async () => {
61
84
  const script = await getScript()
62
- return `<script class='tsr-once'>${script}${
63
- process.env.NODE_ENV === 'development' && (opts?.logScript ?? true)
64
- ? `; console.info(\`Injected From Server:
65
- ${jsesc(script, { quotes: 'backtick' })}\`)`
66
- : ''
67
- }; if (typeof __TSR_SSR__ !== 'undefined') __TSR_SSR__.cleanScripts()</script>`
85
+ return `<script class='$tsr'>${getInitialScript()}${script};if (typeof $_TSR !== 'undefined') $_TSR.c()</script>`
68
86
  })
69
87
  },
70
- streamValue: (key, value) => {
71
- warning(
72
- !router.serverSsr!.streamedKeys.has(key),
73
- 'Key has already been streamed: ' + key,
74
- )
88
+ dehydrate: async () => {
89
+ invariant(!_dehydrated, 'router is already dehydrated!')
90
+ const matches = router.state.matches.map(dehydrateMatch)
75
91
 
76
- router.serverSsr!.streamedKeys.add(key)
77
- router.serverSsr!.injectScript(
78
- () =>
79
- `__TSR_SSR__.streamedValues['${key}'] = { value: ${jsesc(
80
- router.ssr!.serializer.stringify(value),
81
- {
82
- isScriptContext: true,
83
- wrap: true,
84
- json: true,
85
- },
86
- )}}`,
87
- )
88
- },
89
- onMatchSettled,
90
- }
91
-
92
- router.serverSsr.injectScript(() => minifiedTsrBootStrapScript, {
93
- logScript: false,
94
- })
95
- }
96
-
97
- export function dehydrateRouter(router: AnyRouter) {
98
- const dehydratedRouter: DehydratedRouter = {
99
- manifest: router.ssr!.manifest,
100
- dehydratedData: router.options.dehydrate?.(),
101
- lastMatchId:
102
- router.state.matches[router.state.matches.length - 1]?.id || '',
103
- }
104
-
105
- router.serverSsr!.injectScript(
106
- () =>
107
- `__TSR_SSR__.dehydrated = ${jsesc(
108
- router.ssr!.serializer.stringify(dehydratedRouter),
109
- {
110
- isScriptContext: true,
111
- wrap: true,
112
- json: true,
113
- },
114
- )}`,
115
- )
116
- }
117
-
118
- export function extractAsyncLoaderData(
119
- loaderData: any,
120
- ctx: {
121
- match: AnyRouteMatch
122
- router: AnyRouter
123
- },
124
- ) {
125
- const extracted: Array<ServerExtractedEntry> = []
126
-
127
- const replaced = replaceBy(loaderData, (value, path) => {
128
- // If it's a stream, we need to tee it so we can read it multiple times
129
- if (value instanceof ReadableStream) {
130
- const [copy1, copy2] = value.tee()
131
- const entry: ServerExtractedStream = {
132
- type: 'stream',
133
- path,
134
- id: extracted.length,
135
- matchIndex: ctx.match.index,
136
- stream: copy2,
92
+ const dehydratedRouter: DehydratedRouter = {
93
+ manifest: router.ssr!.manifest,
94
+ matches,
137
95
  }
138
-
139
- extracted.push(entry)
140
- return copy1
141
- } else if (value instanceof Promise) {
142
- const deferredPromise = defer(value)
143
- const entry: ServerExtractedPromise = {
144
- type: 'promise',
145
- path,
146
- id: extracted.length,
147
- matchIndex: ctx.match.index,
148
- promise: deferredPromise,
96
+ const lastMatchId =
97
+ router.state.matches[router.state.matches.length - 1]?.id
98
+ if (lastMatchId) {
99
+ dehydratedRouter.lastMatchId = lastMatchId
149
100
  }
150
- extracted.push(entry)
151
- }
152
-
153
- return value
154
- })
155
-
156
- return { replaced, extracted }
157
- }
158
-
159
- export function onMatchSettled(opts: {
160
- router: AnyRouter
161
- match: AnyRouteMatch
162
- }) {
163
- const { router, match } = opts
164
-
165
- let extracted: Array<ServerExtractedEntry> | undefined = undefined
166
- let serializedLoaderData: any = undefined
167
- if (match.loaderData !== undefined) {
168
- const result = extractAsyncLoaderData(match.loaderData, {
169
- router,
170
- match,
171
- })
172
- match.loaderData = result.replaced
173
- extracted = result.extracted
174
- serializedLoaderData = extracted.reduce(
175
- (acc: any, entry: ServerExtractedEntry) => {
176
- return deepImmutableSetByPath(acc, ['temp', ...entry.path], undefined)
177
- },
178
- { temp: result.replaced },
179
- ).temp
180
- }
181
-
182
- const initCode = `__TSR_SSR__.initMatch(${jsesc(
183
- {
184
- id: match.id,
185
- __beforeLoadContext: router.ssr!.serializer.stringify(
186
- match.__beforeLoadContext,
187
- ),
188
- loaderData: router.ssr!.serializer.stringify(serializedLoaderData),
189
- error: router.ssr!.serializer.stringify(match.error),
190
- extracted: extracted?.map((entry) => pick(entry, ['type', 'path'])),
191
- updatedAt: match.updatedAt,
192
- status: match.status,
193
- ssr: match.ssr,
194
- } satisfies SsrMatch,
195
- {
196
- isScriptContext: true,
197
- wrap: true,
198
- json: true,
199
- },
200
- )})`
201
-
202
- router.serverSsr!.injectScript(() => initCode)
203
-
204
- if (extracted) {
205
- extracted.forEach((entry) => {
206
- if (entry.type === 'promise') return injectPromise(entry)
207
- return injectStream(entry)
208
- })
209
- }
210
-
211
- function injectPromise(entry: ServerExtractedPromise) {
212
- router.serverSsr!.injectScript(async () => {
213
- await entry.promise
214
-
215
- return `__TSR_SSR__.resolvePromise(${jsesc(
216
- {
217
- matchId: match.id,
218
- id: entry.id,
219
- promiseState: entry.promise[TSR_DEFERRED_PROMISE],
220
- } satisfies ResolvePromiseState,
221
- {
222
- isScriptContext: true,
223
- wrap: true,
224
- json: true,
101
+ dehydratedRouter.dehydratedData = await router.options.dehydrate?.()
102
+ _dehydrated = true
103
+
104
+ const p = createControlledPromise<string>()
105
+ crossSerializeStream(dehydratedRouter, {
106
+ refs: serializationRefs,
107
+ // TODO make plugins configurable
108
+ plugins: [ReadableStreamPlugin, ShallowErrorPlugin],
109
+ onSerialize: (data, initial) => {
110
+ const serialized = initial ? `${GLOBAL_TSR}["router"]=` + data : data
111
+ router.serverSsr!.injectScript(() => serialized)
225
112
  },
226
- )})`
227
- })
228
- }
229
-
230
- function injectStream(entry: ServerExtractedStream) {
231
- // Inject a promise that resolves when the stream is done
232
- // We do this to keep the stream open until we're done
233
- router.serverSsr!.injectHtml(async () => {
234
- //
235
- try {
236
- const reader = entry.stream.getReader()
237
- let chunk: ReadableStreamReadResult<any> | null = null
238
- while (!(chunk = await reader.read()).done) {
239
- if (chunk.value) {
240
- const code = `__TSR_SSR__.injectChunk(${jsesc(
241
- {
242
- matchId: match.id,
243
- id: entry.id,
244
- chunk: chunk.value,
245
- },
246
- {
247
- isScriptContext: true,
248
- wrap: true,
249
- json: true,
250
- },
251
- )})`
252
-
253
- router.serverSsr!.injectScript(() => code)
254
- }
255
- }
256
-
257
- router.serverSsr!.injectScript(
258
- () =>
259
- `__TSR_SSR__.closeStream(${jsesc(
260
- {
261
- matchId: match.id,
262
- id: entry.id,
263
- },
264
- {
265
- isScriptContext: true,
266
- wrap: true,
267
- json: true,
268
- },
269
- )})`,
270
- )
271
- } catch (err) {
272
- console.error('stream read error', err)
273
- }
274
-
275
- return ''
276
- })
277
- }
278
- }
279
-
280
- function deepImmutableSetByPath<T>(obj: T, path: Array<string>, value: any): T {
281
- // immutable set by path retaining array and object references
282
- if (path.length === 0) {
283
- return value
284
- }
285
-
286
- const [key, ...rest] = path
287
-
288
- if (Array.isArray(obj)) {
289
- return obj.map((item, i) => {
290
- if (i === Number(key)) {
291
- return deepImmutableSetByPath(item, rest, value)
292
- }
293
- return item
294
- }) as T
295
- }
296
-
297
- if (isPlainObject(obj)) {
298
- return {
299
- ...obj,
300
- [key!]: deepImmutableSetByPath((obj as any)[key!], rest, value),
301
- }
302
- }
303
-
304
- return obj
305
- }
306
-
307
- export function replaceBy<T>(
308
- obj: T,
309
- cb: (value: any, path: Array<string>) => any,
310
- path: Array<string> = [],
311
- ): T {
312
- if (isPlainArray(obj)) {
313
- return obj.map((value, i) => replaceBy(value, cb, [...path, `${i}`])) as any
314
- }
315
-
316
- if (isPlainObject(obj)) {
317
- // Do not allow objects with illegal
318
- const newObj: any = {}
319
-
320
- for (const key in obj) {
321
- newObj[key] = replaceBy(obj[key], cb, [...path, key])
322
- }
323
-
324
- return newObj
325
- }
326
-
327
- // // Detect classes, functions, and other non-serializable objects
328
- // // and return undefined. Exclude some known types that are serializable
329
- // if (
330
- // typeof obj === 'function' ||
331
- // (typeof obj === 'object' &&
332
- // ![Object, Promise, ReadableStream].includes((obj as any)?.constructor))
333
- // ) {
334
- // console.info(obj)
335
- // warning(false, `Non-serializable value ☝️ found at ${path.join('.')}`)
336
- // return undefined as any
337
- // }
338
-
339
- const newObj = cb(obj, path)
340
-
341
- if (newObj !== obj) {
342
- return newObj
113
+ scopeId: SCOPE_ID,
114
+ onDone: () => p.resolve(''),
115
+ onError: (err) => p.reject(err),
116
+ })
117
+ // make sure the stream is kept open until the promise is resolved
118
+ router.serverSsr!.injectHtml(() => p)
119
+ },
120
+ isDehydrated() {
121
+ return _dehydrated
122
+ },
123
+ onRenderFinished: (listener) => listeners.push(listener),
124
+ setRenderFinished: () => {
125
+ listeners.forEach((l) => l())
126
+ },
343
127
  }
344
-
345
- return obj
346
128
  }
@@ -242,6 +242,7 @@ export function transformStreamWithRouter(
242
242
  onEnd: () => {
243
243
  // Mark the app as done rendering
244
244
  isAppRendering = false
245
+ router.serverSsr!.setRenderFinished()
245
246
 
246
247
  // If there are no pending promises, resolve the injectedHtmlDonePromise
247
248
  if (processingCount === 0) {
@@ -1,91 +1,7 @@
1
- import type { ControllablePromise } from '../router'
2
- import type { TsrSsrGlobal } from './ssr-client'
3
-
4
- const __TSR_SSR__: TsrSsrGlobal = {
5
- matches: [],
6
- streamedValues: {},
7
- initMatch: (match) => {
8
- __TSR_SSR__.matches.push(match)
9
-
10
- match.extracted?.forEach((ex) => {
11
- if (ex.type === 'stream') {
12
- let controller
13
- ex.value = new ReadableStream({
14
- start(c) {
15
- controller = {
16
- enqueue: (chunk: unknown) => {
17
- try {
18
- c.enqueue(chunk)
19
- } catch {}
20
- },
21
- close: () => {
22
- try {
23
- c.close()
24
- } catch {}
25
- },
26
- }
27
- },
28
- })
29
- ex.value.controller = controller
30
- } else {
31
- let resolve: ControllablePromise['reject'] | undefined
32
- let reject: ControllablePromise['reject'] | undefined
33
-
34
- ex.value = new Promise((_resolve, _reject) => {
35
- reject = _reject
36
- resolve = _resolve
37
- }) as ControllablePromise
38
- ex.value.reject = reject!
39
- ex.value.resolve = resolve!
40
- }
41
- })
42
-
43
- return true
44
- },
45
- resolvePromise: ({ matchId, id, promiseState }) => {
46
- const match = __TSR_SSR__.matches.find((m) => m.id === matchId)
47
- if (match) {
48
- const ex = match.extracted?.[id]
49
- if (
50
- ex &&
51
- ex.type === 'promise' &&
52
- ex.value &&
53
- promiseState.status === 'success'
54
- ) {
55
- ex.value.resolve(promiseState.data)
56
- return true
57
- }
58
- }
59
- return false
60
- },
61
- injectChunk: ({ matchId, id, chunk }) => {
62
- const match = __TSR_SSR__.matches.find((m) => m.id === matchId)
63
-
64
- if (match) {
65
- const ex = match.extracted?.[id]
66
- if (ex && ex.type === 'stream' && ex.value?.controller) {
67
- ex.value.controller.enqueue(new TextEncoder().encode(chunk.toString()))
68
- return true
69
- }
70
- }
71
- return false
72
- },
73
- closeStream: ({ matchId, id }) => {
74
- const match = __TSR_SSR__.matches.find((m) => m.id === matchId)
75
- if (match) {
76
- const ex = match.extracted?.[id]
77
- if (ex && ex.type === 'stream' && ex.value?.controller) {
78
- ex.value.controller.close()
79
- return true
80
- }
81
- }
82
- return false
83
- },
84
- cleanScripts: () => {
85
- document.querySelectorAll('.tsr-once').forEach((el) => {
86
- el.remove()
1
+ self.$_TSR = {
2
+ c: () => {
3
+ document.querySelectorAll('.\\$tsr').forEach((o) => {
4
+ o.remove()
87
5
  })
88
6
  },
89
7
  }
90
-
91
- window.__TSR_SSR__ = __TSR_SSR__
@@ -1,146 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const utils = require("./utils.cjs");
4
- const tsrSerializer = {
5
- stringify: (value) => JSON.stringify(value, function replacer(key, val) {
6
- const ogVal = this[key];
7
- const serializer = serializers.find((t) => t.stringifyCondition(ogVal));
8
- if (serializer) {
9
- return serializer.stringify(ogVal);
10
- }
11
- return val;
12
- }),
13
- parse: (value) => JSON.parse(value, function parser(key, val) {
14
- const ogVal = this[key];
15
- if (utils.isPlainObject(ogVal)) {
16
- const serializer = serializers.find((t) => t.parseCondition(ogVal));
17
- if (serializer) {
18
- return serializer.parse(ogVal);
19
- }
20
- }
21
- return val;
22
- }),
23
- encode: (value) => {
24
- if (Array.isArray(value)) {
25
- return value.map((v) => tsrSerializer.encode(v));
26
- }
27
- if (utils.isPlainObject(value)) {
28
- return Object.fromEntries(
29
- Object.entries(value).map(([key, v]) => [key, tsrSerializer.encode(v)])
30
- );
31
- }
32
- const serializer = serializers.find((t) => t.stringifyCondition(value));
33
- if (serializer) {
34
- return serializer.stringify(value);
35
- }
36
- return value;
37
- },
38
- decode: (value) => {
39
- if (utils.isPlainObject(value)) {
40
- const serializer = serializers.find((t) => t.parseCondition(value));
41
- if (serializer) {
42
- return serializer.parse(value);
43
- }
44
- }
45
- if (Array.isArray(value)) {
46
- return value.map((v) => tsrSerializer.decode(v));
47
- }
48
- if (utils.isPlainObject(value)) {
49
- return Object.fromEntries(
50
- Object.entries(value).map(([key, v]) => [key, tsrSerializer.decode(v)])
51
- );
52
- }
53
- return value;
54
- }
55
- };
56
- const createSerializer = (key, check, toValue, fromValue) => ({
57
- key,
58
- stringifyCondition: check,
59
- stringify: (value) => ({ [`$${key}`]: toValue(value) }),
60
- parseCondition: (value) => Object.hasOwn(value, `$${key}`),
61
- parse: (value) => fromValue(value[`$${key}`])
62
- });
63
- const serializers = [
64
- createSerializer(
65
- // Key
66
- "undefined",
67
- // Check
68
- (v) => v === void 0,
69
- // To
70
- () => 0,
71
- // From
72
- () => void 0
73
- ),
74
- createSerializer(
75
- // Key
76
- "date",
77
- // Check
78
- (v) => v instanceof Date,
79
- // To
80
- (v) => v.toISOString(),
81
- // From
82
- (v) => new Date(v)
83
- ),
84
- createSerializer(
85
- // Key
86
- "error",
87
- // Check
88
- (v) => v instanceof Error,
89
- // To
90
- (v) => ({
91
- ...v,
92
- message: v.message,
93
- stack: process.env.NODE_ENV === "development" ? v.stack : void 0,
94
- cause: v.cause
95
- }),
96
- // From
97
- (v) => Object.assign(new Error(v.message), v)
98
- ),
99
- createSerializer(
100
- // Key
101
- "formData",
102
- // Check
103
- (v) => v instanceof FormData,
104
- // To
105
- (v) => {
106
- const entries = {};
107
- v.forEach((value, key) => {
108
- const entry = entries[key];
109
- if (entry !== void 0) {
110
- if (Array.isArray(entry)) {
111
- entry.push(value);
112
- } else {
113
- entries[key] = [entry, value];
114
- }
115
- } else {
116
- entries[key] = value;
117
- }
118
- });
119
- return entries;
120
- },
121
- // From
122
- (v) => {
123
- const formData = new FormData();
124
- Object.entries(v).forEach(([key, value]) => {
125
- if (Array.isArray(value)) {
126
- value.forEach((val) => formData.append(key, val));
127
- } else {
128
- formData.append(key, value);
129
- }
130
- });
131
- return formData;
132
- }
133
- ),
134
- createSerializer(
135
- // Key
136
- "bigint",
137
- // Check
138
- (v) => typeof v === "bigint",
139
- // To
140
- (v) => v.toString(),
141
- // From
142
- (v) => BigInt(v)
143
- )
144
- ];
145
- exports.tsrSerializer = tsrSerializer;
146
- //# sourceMappingURL=serializer.cjs.map