@tanstack/router-core 1.132.0-alpha.4 → 1.132.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 (104) hide show
  1. package/dist/cjs/Matches.cjs +2 -1
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/Matches.d.cts +2 -2
  4. package/dist/cjs/config.cjs +10 -0
  5. package/dist/cjs/config.cjs.map +1 -0
  6. package/dist/cjs/config.d.cts +17 -0
  7. package/dist/cjs/fileRoute.d.cts +3 -2
  8. package/dist/cjs/index.cjs +9 -2
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/index.d.cts +10 -5
  11. package/dist/cjs/load-matches.cjs +5 -3
  12. package/dist/cjs/load-matches.cjs.map +1 -1
  13. package/dist/cjs/location.d.cts +38 -0
  14. package/dist/cjs/path.cjs +27 -64
  15. package/dist/cjs/path.cjs.map +1 -1
  16. package/dist/cjs/path.d.cts +6 -7
  17. package/dist/cjs/process-route-tree.cjs +144 -0
  18. package/dist/cjs/process-route-tree.cjs.map +1 -0
  19. package/dist/cjs/process-route-tree.d.cts +10 -0
  20. package/dist/cjs/redirect.cjs +1 -1
  21. package/dist/cjs/redirect.cjs.map +1 -1
  22. package/dist/cjs/rewrite.cjs +63 -0
  23. package/dist/cjs/rewrite.cjs.map +1 -0
  24. package/dist/cjs/rewrite.d.cts +22 -0
  25. package/dist/cjs/route.cjs.map +1 -1
  26. package/dist/cjs/route.d.cts +62 -44
  27. package/dist/cjs/router.cjs +102 -210
  28. package/dist/cjs/router.cjs.map +1 -1
  29. package/dist/cjs/router.d.cts +81 -44
  30. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  31. package/dist/cjs/scroll-restoration.d.cts +9 -0
  32. package/dist/cjs/ssr/createRequestHandler.cjs +4 -1
  33. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  34. package/dist/cjs/ssr/serializer/transformer.cjs +14 -12
  35. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  36. package/dist/cjs/ssr/serializer/transformer.d.cts +55 -15
  37. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  38. package/dist/cjs/ssr/ssr-server.cjs +5 -2
  39. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  40. package/dist/cjs/ssr/ssr-server.d.cts +4 -1
  41. package/dist/cjs/utils.cjs +68 -46
  42. package/dist/cjs/utils.cjs.map +1 -1
  43. package/dist/esm/Matches.d.ts +2 -2
  44. package/dist/esm/Matches.js +2 -1
  45. package/dist/esm/Matches.js.map +1 -1
  46. package/dist/esm/config.d.ts +17 -0
  47. package/dist/esm/config.js +10 -0
  48. package/dist/esm/config.js.map +1 -0
  49. package/dist/esm/fileRoute.d.ts +3 -2
  50. package/dist/esm/index.d.ts +10 -5
  51. package/dist/esm/index.js +10 -3
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/load-matches.js +5 -3
  54. package/dist/esm/load-matches.js.map +1 -1
  55. package/dist/esm/location.d.ts +38 -0
  56. package/dist/esm/path.d.ts +6 -7
  57. package/dist/esm/path.js +27 -64
  58. package/dist/esm/path.js.map +1 -1
  59. package/dist/esm/process-route-tree.d.ts +10 -0
  60. package/dist/esm/process-route-tree.js +144 -0
  61. package/dist/esm/process-route-tree.js.map +1 -0
  62. package/dist/esm/redirect.js +1 -1
  63. package/dist/esm/redirect.js.map +1 -1
  64. package/dist/esm/rewrite.d.ts +22 -0
  65. package/dist/esm/rewrite.js +63 -0
  66. package/dist/esm/rewrite.js.map +1 -0
  67. package/dist/esm/route.d.ts +62 -44
  68. package/dist/esm/route.js.map +1 -1
  69. package/dist/esm/router.d.ts +81 -44
  70. package/dist/esm/router.js +104 -212
  71. package/dist/esm/router.js.map +1 -1
  72. package/dist/esm/scroll-restoration.d.ts +9 -0
  73. package/dist/esm/scroll-restoration.js.map +1 -1
  74. package/dist/esm/ssr/createRequestHandler.js +4 -1
  75. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  76. package/dist/esm/ssr/serializer/transformer.d.ts +55 -15
  77. package/dist/esm/ssr/serializer/transformer.js +14 -12
  78. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  79. package/dist/esm/ssr/ssr-client.js.map +1 -1
  80. package/dist/esm/ssr/ssr-server.d.ts +4 -1
  81. package/dist/esm/ssr/ssr-server.js +5 -2
  82. package/dist/esm/ssr/ssr-server.js.map +1 -1
  83. package/dist/esm/utils.js +68 -46
  84. package/dist/esm/utils.js.map +1 -1
  85. package/package.json +2 -2
  86. package/src/Matches.ts +4 -3
  87. package/src/config.ts +42 -0
  88. package/src/fileRoute.ts +25 -3
  89. package/src/index.ts +23 -6
  90. package/src/load-matches.ts +31 -21
  91. package/src/location.ts +38 -0
  92. package/src/path.ts +44 -82
  93. package/src/process-route-tree.ts +233 -0
  94. package/src/redirect.ts +1 -1
  95. package/src/rewrite.ts +70 -0
  96. package/src/route.ts +311 -74
  97. package/src/router.ts +263 -389
  98. package/src/scroll-restoration.ts +1 -1
  99. package/src/ssr/createRequestHandler.ts +4 -1
  100. package/src/ssr/serializer/transformer.ts +168 -31
  101. package/src/ssr/server.ts +6 -0
  102. package/src/ssr/ssr-client.ts +2 -2
  103. package/src/ssr/ssr-server.ts +10 -7
  104. package/src/utils.ts +83 -61
@@ -358,7 +358,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
358
358
  }
359
359
 
360
360
  /**
361
- * @internal
361
+ * @private
362
362
  * Handles hash-based scrolling after navigation completes.
363
363
  * To be used in framework-specific <Transitioner> components during the onResolved event.
364
364
  *
@@ -21,7 +21,10 @@ export function createRequestHandler<TRouter extends AnyRouter>({
21
21
  return async (cb) => {
22
22
  const router = createRouter()
23
23
 
24
- attachRouterServerSsrUtils(router, await getRouterManifest?.())
24
+ attachRouterServerSsrUtils({
25
+ router,
26
+ manifest: await getRouterManifest?.(),
27
+ })
25
28
 
26
29
  const url = new URL(request.url, 'http://localhost')
27
30
 
@@ -1,39 +1,111 @@
1
1
  import { createPlugin } from 'seroval'
2
2
  import { GLOBAL_TSR } from '../constants'
3
- import type { SerovalNode } from 'seroval'
3
+ import type { Plugin, SerovalNode } from 'seroval'
4
+ import type { RegisteredConfigType, SSROption } from '../../router'
5
+ import type { LooseReturnType } from '../../utils'
6
+ import type { AnyRoute, ResolveAllSSR } from '../../route'
4
7
 
5
- export type Transformer<TInput, TTransformed> = {
8
+ export type Serializable =
9
+ | number
10
+ | string
11
+ | boolean
12
+ | null
13
+ | undefined
14
+ | bigint
15
+ | Date
16
+
17
+ export function createSerializationAdapter<
18
+ TInput = unknown,
19
+ TOutput = unknown /* we need to check that this type is actually serializable taking into account all seroval native types and any custom plugin WE=router/start add!!! */,
20
+ >(
21
+ opts: CreateSerializationAdapterOptions<TInput, TOutput>,
22
+ ): SerializationAdapter<TInput, TOutput> {
23
+ return opts as unknown as SerializationAdapter<TInput, TOutput>
24
+ }
25
+
26
+ export interface CreateSerializationAdapterOptions<TInput, TOutput> {
6
27
  key: string
7
- test: (value: any) => value is TInput
8
- toSerializable: (value: TInput) => TTransformed
9
- fromSerializable: (value: TTransformed) => TInput
28
+ test: (value: unknown) => value is TInput
29
+ toSerializable: (value: TInput) => ValidateSerializable<TOutput, Serializable>
30
+ fromSerializable: (value: TOutput) => TInput
10
31
  }
11
32
 
12
- export type AnyTransformer = Transformer<any, any>
33
+ export type ValidateSerializable<T, TSerializable> = T extends TSerializable
34
+ ? T
35
+ : T extends (...args: Array<any>) => any
36
+ ? 'Function is not serializable'
37
+ : T extends Promise<any>
38
+ ? ValidateSerializablePromise<T, TSerializable>
39
+ : T extends ReadableStream<any>
40
+ ? ValidateReadableStream<T, TSerializable>
41
+ : T extends Set<any>
42
+ ? ValidateSerializableSet<T, TSerializable>
43
+ : T extends Map<any, any>
44
+ ? ValidateSerializableMap<T, TSerializable>
45
+ : {
46
+ [K in keyof T]: ValidateSerializable<T[K], TSerializable>
47
+ }
13
48
 
14
- export function createSerializationAdapter<
15
- TKey extends string,
16
- TInput,
17
- TTransformed /* we need to check that this type is actually serializable taking into account all seroval native types and any custom plugin WE=router/start add!!! */,
18
- >(opts: {
19
- key: TKey
20
- test: (value: any) => value is TInput
21
- toSerializable: (value: TInput) => TTransformed
22
- fromSerializable: (value: TTransformed) => TInput
23
- }): Transformer<TInput, TTransformed> {
24
- return opts
49
+ export type ValidateSerializablePromise<T, TSerializable> =
50
+ T extends Promise<infer TAwaited>
51
+ ? Promise<ValidateSerializable<TAwaited, TSerializable>>
52
+ : never
53
+
54
+ export type ValidateReadableStream<T, TSerializable> =
55
+ T extends ReadableStream<infer TStreamed>
56
+ ? ReadableStream<ValidateSerializable<TStreamed, TSerializable>>
57
+ : never
58
+
59
+ export type ValidateSerializableSet<T, TSerializable> =
60
+ T extends Set<infer TItem>
61
+ ? Set<ValidateSerializable<TItem, TSerializable>>
62
+ : never
63
+
64
+ export type ValidateSerializableMap<T, TSerializable> =
65
+ T extends Map<infer TKey, infer TValue>
66
+ ? Map<
67
+ ValidateSerializable<TKey, TSerializable>,
68
+ ValidateSerializable<TValue, TSerializable>
69
+ >
70
+ : never
71
+
72
+ export type RegisteredReadableStream =
73
+ unknown extends SerializerExtensions['ReadableStream']
74
+ ? never
75
+ : SerializerExtensions['ReadableStream']
76
+
77
+ export interface DefaultSerializerExtensions {
78
+ ReadableStream: unknown
79
+ }
80
+
81
+ export interface SerializerExtensions extends DefaultSerializerExtensions {}
82
+
83
+ export interface SerializationAdapter<TInput, TOutput> {
84
+ '~types': SerializationAdapterTypes<TInput, TOutput>
85
+ key: string
86
+ test: (value: unknown) => value is TInput
87
+ toSerializable: (value: TInput) => TOutput
88
+ fromSerializable: (value: TOutput) => TInput
89
+ makePlugin: (options: { didRun: boolean }) => Plugin<TInput, SerovalNode>
90
+ }
91
+
92
+ export interface SerializationAdapterTypes<TInput, TOutput> {
93
+ input: TInput
94
+ output: TOutput
25
95
  }
26
96
 
27
- export function makeSsrSerovalPlugin<TInput, TTransformed>(
28
- transformer: Transformer<TInput, TTransformed>,
97
+ export type AnySerializationAdapter = SerializationAdapter<any, any>
98
+
99
+ export function makeSsrSerovalPlugin<TInput, TOutput>(
100
+ serializationAdapter: SerializationAdapter<TInput, TOutput>,
29
101
  options: { didRun: boolean },
30
102
  ) {
31
103
  return createPlugin<TInput, SerovalNode>({
32
- tag: '$TSR/t/' + transformer.key,
33
- test: transformer.test,
104
+ tag: '$TSR/t/' + serializationAdapter.key,
105
+ test: serializationAdapter.test,
34
106
  parse: {
35
107
  stream(value, ctx) {
36
- return ctx.parse(transformer.toSerializable(value))
108
+ return ctx.parse(serializationAdapter.toSerializable(value))
37
109
  },
38
110
  },
39
111
  serialize(node, ctx) {
@@ -41,7 +113,7 @@ export function makeSsrSerovalPlugin<TInput, TTransformed>(
41
113
  return (
42
114
  GLOBAL_TSR +
43
115
  '.t.get("' +
44
- transformer.key +
116
+ serializationAdapter.key +
45
117
  '")(' +
46
118
  ctx.serialize(node) +
47
119
  ')'
@@ -52,27 +124,92 @@ export function makeSsrSerovalPlugin<TInput, TTransformed>(
52
124
  })
53
125
  }
54
126
 
55
- export function makeSerovalPlugin<TInput, TTransformed>(
56
- transformer: Transformer<TInput, TTransformed>,
127
+ export function makeSerovalPlugin<TInput, TOutput>(
128
+ serializationAdapter: SerializationAdapter<TInput, TOutput>,
57
129
  ) {
58
130
  return createPlugin<TInput, SerovalNode>({
59
- tag: '$TSR/t/' + transformer.key,
60
- test: transformer.test,
131
+ tag: '$TSR/t/' + serializationAdapter.key,
132
+ test: serializationAdapter.test,
61
133
  parse: {
62
134
  sync(value, ctx) {
63
- return ctx.parse(transformer.toSerializable(value))
135
+ return ctx.parse(serializationAdapter.toSerializable(value))
64
136
  },
65
137
  async async(value, ctx) {
66
- return await ctx.parse(transformer.toSerializable(value))
138
+ return await ctx.parse(serializationAdapter.toSerializable(value))
67
139
  },
68
140
  stream(value, ctx) {
69
- return ctx.parse(transformer.toSerializable(value))
141
+ return ctx.parse(serializationAdapter.toSerializable(value))
70
142
  },
71
143
  },
72
144
  // we don't generate JS code outside of SSR (for now)
73
145
  serialize: undefined as never,
74
146
  deserialize(node, ctx) {
75
- return transformer.fromSerializable(ctx.deserialize(node) as TTransformed)
147
+ return serializationAdapter.fromSerializable(
148
+ ctx.deserialize(node) as TOutput,
149
+ )
76
150
  },
77
151
  })
78
152
  }
153
+
154
+ export type ValidateSerializableInput<TRegister, T> = ValidateSerializable<
155
+ T,
156
+ RegisteredSerializableInput<TRegister>
157
+ >
158
+
159
+ export type RegisteredSerializableInput<TRegister> =
160
+ | (unknown extends RegisteredSerializationAdapters<TRegister>
161
+ ? never
162
+ : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter>
163
+ ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input']
164
+ : never)
165
+ | Serializable
166
+
167
+ export type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<
168
+ TRegister,
169
+ 'serializationAdapters'
170
+ >
171
+
172
+ export type ValidateSerializableInputResult<TRegister, T> =
173
+ ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>
174
+
175
+ export type ValidateSerializableResult<T, TSerializable> =
176
+ T extends TSerializable
177
+ ? T
178
+ : unknown extends SerializerExtensions['ReadableStream']
179
+ ? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
180
+ : T extends SerializerExtensions['ReadableStream']
181
+ ? ReadableStream
182
+ : { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
183
+
184
+ export type RegisteredSSROption<TRegister> =
185
+ unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>
186
+ ? SSROption
187
+ : RegisteredConfigType<TRegister, 'defaultSsr'>
188
+
189
+ export type ValidateSerializableLifecycleResult<
190
+ TRegister,
191
+ TParentRoute extends AnyRoute,
192
+ TSSR,
193
+ TFn,
194
+ > = false extends (TRegister extends { ssr: infer TSSR } ? TSSR : never)
195
+ ? any
196
+ : ValidateSerializableLifecycleResultSSR<
197
+ TRegister,
198
+ TParentRoute,
199
+ TSSR,
200
+ TFn
201
+ > extends infer TInput
202
+ ? TInput
203
+ : never
204
+
205
+ export type ValidateSerializableLifecycleResultSSR<
206
+ TRegister,
207
+ TParentRoute extends AnyRoute,
208
+ TSSR,
209
+ TFn,
210
+ > =
211
+ ResolveAllSSR<TParentRoute, TSSR> extends false
212
+ ? any
213
+ : RegisteredSSROption<TRegister> extends false
214
+ ? any
215
+ : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>
package/src/ssr/server.ts CHANGED
@@ -8,3 +8,9 @@ export {
8
8
  transformReadableStreamWithRouter,
9
9
  } from './transformStreamWithRouter'
10
10
  export { attachRouterServerSsrUtils } from './ssr-server'
11
+
12
+ // declare module '../router' {
13
+ // export interface RegisterSsr {
14
+ // ssr: true
15
+ // }
16
+ // }
@@ -5,7 +5,7 @@ import type { AnyRouteMatch, MakeRouteMatch } from '../Matches'
5
5
  import type { AnyRouter } from '../router'
6
6
  import type { Manifest } from '../manifest'
7
7
  import type { RouteContextOptions } from '../route'
8
- import type { AnyTransformer } from './serializer/transformer'
8
+ import type { AnySerializationAdapter } from './serializer/transformer'
9
9
  import type { GLOBAL_TSR } from './constants'
10
10
 
11
11
  declare global {
@@ -63,7 +63,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
63
63
  )
64
64
 
65
65
  const serializationAdapters = router.options.serializationAdapters as
66
- | Array<AnyTransformer>
66
+ | Array<AnySerializationAdapter>
67
67
  | undefined
68
68
 
69
69
  if (serializationAdapters?.length) {
@@ -10,7 +10,7 @@ import type { DehydratedMatch } from './ssr-client'
10
10
  import type { DehydratedRouter } from './client'
11
11
  import type { AnyRouteMatch } from '../Matches'
12
12
  import type { Manifest } from '../manifest'
13
- import type { AnyTransformer } from './serializer/transformer'
13
+ import type { AnySerializationAdapter } from './serializer/transformer'
14
14
 
15
15
  declare module '../router' {
16
16
  interface ServerSsr {
@@ -48,10 +48,13 @@ export function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {
48
48
  return dehydratedMatch
49
49
  }
50
50
 
51
- export function attachRouterServerSsrUtils(
52
- router: AnyRouter,
53
- manifest: Manifest | undefined,
54
- ) {
51
+ export function attachRouterServerSsrUtils({
52
+ router,
53
+ manifest,
54
+ }: {
55
+ router: AnyRouter
56
+ manifest: Manifest | undefined
57
+ }) {
55
58
  router.ssr = {
56
59
  manifest,
57
60
  }
@@ -81,7 +84,7 @@ export function attachRouterServerSsrUtils(
81
84
  injectScript: (getScript) => {
82
85
  return router.serverSsr!.injectHtml(async () => {
83
86
  const script = await getScript()
84
- return `<script class='$tsr'>${getInitialScript()}${script};$_TSR.c()</script>`
87
+ return `<script ${router.options.ssr?.nonce ? `nonce='${router.options.ssr.nonce}'` : ''} class='$tsr'>${getInitialScript()}${script};$_TSR.c()</script>`
85
88
  })
86
89
  },
87
90
  dehydrate: async () => {
@@ -109,7 +112,7 @@ export function attachRouterServerSsrUtils(
109
112
  const plugins =
110
113
  (
111
114
  router.options.serializationAdapters as
112
- | Array<AnyTransformer>
115
+ | Array<AnySerializationAdapter>
113
116
  | undefined
114
117
  )?.map((t) => makeSsrSerovalPlugin(t, trackPlugins)) ?? []
115
118
  crossSerializeStream(dehydratedRouter, {
package/src/utils.ts CHANGED
@@ -203,6 +203,8 @@ export function functionalUpdate<TPrevious, TResult = TPrevious>(
203
203
  return updater
204
204
  }
205
205
 
206
+ const hasOwn = Object.prototype.hasOwnProperty
207
+
206
208
  /**
207
209
  * This function returns `prev` if `_next` is deeply equal.
208
210
  * If not, it will replace any deeply equal children of `b` with those of `a`.
@@ -218,57 +220,64 @@ export function replaceEqualDeep<T>(prev: any, _next: T): T {
218
220
 
219
221
  const array = isPlainArray(prev) && isPlainArray(next)
220
222
 
221
- if (array || (isSimplePlainObject(prev) && isSimplePlainObject(next))) {
222
- const prevItems = array
223
- ? prev
224
- : (Object.keys(prev) as Array<unknown>).concat(
225
- Object.getOwnPropertySymbols(prev),
226
- )
227
- const prevSize = prevItems.length
228
- const nextItems = array
229
- ? next
230
- : (Object.keys(next) as Array<unknown>).concat(
231
- Object.getOwnPropertySymbols(next),
232
- )
233
- const nextSize = nextItems.length
234
- const copy: any = array ? [] : {}
235
-
236
- let equalItems = 0
237
-
238
- for (let i = 0; i < nextSize; i++) {
239
- const key = array ? i : (nextItems[i] as any)
240
- if (
241
- ((!array && prevItems.includes(key)) || array) &&
242
- prev[key] === undefined &&
243
- next[key] === undefined
244
- ) {
245
- copy[key] = undefined
246
- equalItems++
247
- } else {
248
- copy[key] = replaceEqualDeep(prev[key], next[key])
249
- if (copy[key] === prev[key] && prev[key] !== undefined) {
250
- equalItems++
251
- }
252
- }
223
+ if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next
224
+
225
+ const prevItems = array ? prev : getEnumerableOwnKeys(prev)
226
+ if (!prevItems) return next
227
+ const nextItems = array ? next : getEnumerableOwnKeys(next)
228
+ if (!nextItems) return next
229
+ const prevSize = prevItems.length
230
+ const nextSize = nextItems.length
231
+ const copy: any = array ? new Array(nextSize) : {}
232
+
233
+ let equalItems = 0
234
+
235
+ for (let i = 0; i < nextSize; i++) {
236
+ const key = array ? i : (nextItems[i] as any)
237
+ const p = prev[key]
238
+ const n = next[key]
239
+
240
+ if (p === n) {
241
+ copy[key] = p
242
+ if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++
243
+ continue
244
+ }
245
+
246
+ if (
247
+ p === null ||
248
+ n === null ||
249
+ typeof p !== 'object' ||
250
+ typeof n !== 'object'
251
+ ) {
252
+ copy[key] = n
253
+ continue
253
254
  }
254
255
 
255
- return prevSize === nextSize && equalItems === prevSize ? prev : copy
256
+ const v = replaceEqualDeep(p, n)
257
+ copy[key] = v
258
+ if (v === p) equalItems++
256
259
  }
257
260
 
258
- return next
261
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy
259
262
  }
260
263
 
261
264
  /**
262
- * A wrapper around `isPlainObject` with additional checks to ensure that it is not
263
- * only a plain object, but also one that is "clone-friendly" (doesn't have any
264
- * non-enumerable properties).
265
+ * Equivalent to `Reflect.ownKeys`, but ensures that objects are "clone-friendly":
266
+ * will return false if object has any non-enumerable properties.
265
267
  */
266
- function isSimplePlainObject(o: any) {
267
- return (
268
- // all the checks from isPlainObject are more likely to hit so we perform them first
269
- isPlainObject(o) &&
270
- Object.getOwnPropertyNames(o).length === Object.keys(o).length
271
- )
268
+ function getEnumerableOwnKeys(o: object) {
269
+ const keys = []
270
+ const names = Object.getOwnPropertyNames(o)
271
+ for (const name of names) {
272
+ if (!Object.prototype.propertyIsEnumerable.call(o, name)) return false
273
+ keys.push(name)
274
+ }
275
+ const symbols = Object.getOwnPropertySymbols(o)
276
+ for (const symbol of symbols) {
277
+ if (!Object.prototype.propertyIsEnumerable.call(o, symbol)) return false
278
+ keys.push(symbol)
279
+ }
280
+ return keys
272
281
  }
273
282
 
274
283
  // Copied from: https://github.com/jonschlinkert/is-plain-object
@@ -306,14 +315,6 @@ export function isPlainArray(value: unknown): value is Array<unknown> {
306
315
  return Array.isArray(value) && value.length === Object.keys(value).length
307
316
  }
308
317
 
309
- function getObjectKeys(obj: any, ignoreUndefined: boolean) {
310
- let keys = Object.keys(obj)
311
- if (ignoreUndefined) {
312
- keys = keys.filter((key) => obj[key] !== undefined)
313
- }
314
- return keys
315
- }
316
-
317
318
  export function deepEqual(
318
319
  a: any,
319
320
  b: any,
@@ -327,23 +328,44 @@ export function deepEqual(
327
328
  return false
328
329
  }
329
330
 
331
+ if (Array.isArray(a) && Array.isArray(b)) {
332
+ if (a.length !== b.length) return false
333
+ for (let i = 0, l = a.length; i < l; i++) {
334
+ if (!deepEqual(a[i], b[i], opts)) return false
335
+ }
336
+ return true
337
+ }
338
+
330
339
  if (isPlainObject(a) && isPlainObject(b)) {
331
340
  const ignoreUndefined = opts?.ignoreUndefined ?? true
332
- const aKeys = getObjectKeys(a, ignoreUndefined)
333
- const bKeys = getObjectKeys(b, ignoreUndefined)
334
341
 
335
- if (!opts?.partial && aKeys.length !== bKeys.length) {
336
- return false
342
+ if (opts?.partial) {
343
+ for (const k in b) {
344
+ if (!ignoreUndefined || b[k] !== undefined) {
345
+ if (!deepEqual(a[k], b[k], opts)) return false
346
+ }
347
+ }
348
+ return true
337
349
  }
338
350
 
339
- return bKeys.every((key) => deepEqual(a[key], b[key], opts))
340
- }
351
+ let aCount = 0
352
+ if (!ignoreUndefined) {
353
+ aCount = Object.keys(a).length
354
+ } else {
355
+ for (const k in a) {
356
+ if (a[k] !== undefined) aCount++
357
+ }
358
+ }
341
359
 
342
- if (Array.isArray(a) && Array.isArray(b)) {
343
- if (a.length !== b.length) {
344
- return false
360
+ let bCount = 0
361
+ for (const k in b) {
362
+ if (!ignoreUndefined || b[k] !== undefined) {
363
+ bCount++
364
+ if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false
365
+ }
345
366
  }
346
- return !a.some((item, index) => !deepEqual(item, b[index], opts))
367
+
368
+ return aCount === bCount
347
369
  }
348
370
 
349
371
  return false