@benjavicente/angular-router-experimental 1.142.11 → 1.142.12

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.
@@ -2,7 +2,7 @@ import * as Angular from '@angular/core'
2
2
  import { deepEqual, invariant } from '@benjavicente/router-core'
3
3
  import { MATCH_CONTEXT_INJECTOR_TOKEN } from './matchInjectorToken'
4
4
  import { injectRouter } from './injectRouter'
5
- import { injectStore } from './injectStore'
5
+ import { injectStore } from './store/injectStore'
6
6
  import type {
7
7
  AnyRouter,
8
8
  MakeRouteMatch,
@@ -13,6 +13,11 @@ import type {
13
13
  ThrowOrOptional,
14
14
  } from '@benjavicente/router-core'
15
15
 
16
+ const dummyStore = {
17
+ get: () => undefined,
18
+ subscribe: () => ({ unsubscribe: () => {} }),
19
+ } as any
20
+
16
21
  export interface InjectMatchBaseOptions<
17
22
  TRouter extends AnyRouter,
18
23
  TFrom,
@@ -49,8 +54,8 @@ export type InjectMatchResult<
49
54
  TSelected,
50
55
  > = unknown extends TSelected
51
56
  ? TStrict extends true
52
- ? MakeRouteMatch<TRouter['routeTree'], TFrom, TStrict>
53
- : MakeRouteMatchUnion<TRouter>
57
+ ? MakeRouteMatch<TRouter['routeTree'], TFrom, TStrict>
58
+ : MakeRouteMatchUnion<TRouter>
54
59
  : TSelected
55
60
 
56
61
  export function injectMatch<
@@ -75,48 +80,54 @@ export function injectMatch<
75
80
  ? undefined
76
81
  : Angular.inject(MATCH_CONTEXT_INJECTOR_TOKEN)
77
82
 
83
+ const match = injectStore(
84
+ opts.from
85
+ ? router.stores.getRouteMatchStore(opts.from)
86
+ : () => {
87
+ const matchId = nearestMatch?.matchId()
88
+ return matchId
89
+ ? (router.stores.matchStores.get(matchId) ?? dummyStore)
90
+ : dummyStore
91
+ },
92
+ (d) => d,
93
+ )
78
94
  const pendingRouteIds = injectStore(
79
95
  router.stores.pendingRouteIds,
80
- (s) => s,
96
+ (ids) => ids,
81
97
  )
82
98
  const isTransitioning = injectStore(
83
99
  router.stores.isTransitioning,
84
- (s) => s,
100
+ (value) => value,
85
101
  )
86
102
 
87
- const match = () => {
88
- if (opts.from) {
89
- return router.stores.getMatchStoreByRouteId(opts.from).state
90
- }
91
-
92
- return nearestMatch?.match()
93
- }
103
+ return Angular.computed(
104
+ () => {
105
+ const selectedMatch = match()
94
106
 
95
- return Angular.computed(() => {
96
- const selectedMatch = match()
107
+ if (selectedMatch !== undefined) {
108
+ return opts.select ? opts.select(selectedMatch as any) : selectedMatch
109
+ }
97
110
 
98
- if (selectedMatch !== undefined) {
99
- return opts.select ? opts.select(selectedMatch as any) : selectedMatch
100
- }
111
+ const hasPendingMatch = opts.from
112
+ ? Boolean(pendingRouteIds()[opts.from!])
113
+ : (nearestMatch?.hasPending() ?? false)
101
114
 
102
- const hasPendingMatch = opts.from
103
- ? Boolean(pendingRouteIds()[opts.from])
104
- : nearestMatch?.hasPending() ?? false
115
+ if (
116
+ !hasPendingMatch &&
117
+ !isTransitioning() &&
118
+ (opts.shouldThrow ?? true)
119
+ ) {
120
+ if (process.env.NODE_ENV !== 'production') {
121
+ throw new Error(
122
+ `Invariant failed: Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
123
+ )
124
+ }
105
125
 
106
- if (
107
- !hasPendingMatch &&
108
- !isTransitioning() &&
109
- (opts.shouldThrow ?? true)
110
- ) {
111
- if (process.env.NODE_ENV !== 'production') {
112
- throw new Error(
113
- `Invariant failed: Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
114
- )
126
+ invariant()
115
127
  }
116
128
 
117
- invariant()
118
- }
119
-
120
- return undefined
121
- }, { equal: deepEqual }) as any
129
+ return undefined
130
+ },
131
+ { equal: deepEqual },
132
+ ) as any
122
133
  }
@@ -1,6 +1,6 @@
1
1
  import * as Angular from '@angular/core'
2
2
  import { injectRouter } from './injectRouter'
3
- import { injectStore } from './injectStore'
3
+ import { injectStore } from './store/injectStore'
4
4
  import type {
5
5
  AnyRouter,
6
6
  DeepPartial,
@@ -9,7 +9,6 @@ import type {
9
9
  MakeOptionalSearchParams,
10
10
  MaskOptions,
11
11
  MatchRouteOptions,
12
- NoInfer,
13
12
  RegisteredRouter,
14
13
  ResolveRoute,
15
14
  ToSubOptionsProps,
@@ -31,7 +30,7 @@ export function injectMatchRoute<
31
30
  TRouter extends AnyRouter = RegisteredRouter,
32
31
  >() {
33
32
  const router = injectRouter<TRouter>()
34
- const reactivity = injectStore(router.stores.matchRouteReactivity, (d) => d)
33
+ const reactivity = injectStore(router.stores.matchRouteDeps, (d) => d)
35
34
 
36
35
  return <
37
36
  const TFrom extends string = string,
@@ -41,12 +40,12 @@ export function injectMatchRoute<
41
40
  >(
42
41
  opts: InjectMatchRouteOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
43
42
  ): Angular.Signal<
44
- false | Expand<ResolveRoute<TRouter, TFrom, NoInfer<TTo>>['types']['allParams']>
43
+ false | Expand<ResolveRoute<TRouter, TFrom, TTo>['types']['allParams']>
45
44
  > => {
46
45
  return Angular.computed(() => {
47
- reactivity()
48
46
  const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts
49
47
 
48
+ reactivity()
50
49
  return router.matchRoute(rest as any, {
51
50
  pending,
52
51
  caseSensitive,
@@ -1,7 +1,7 @@
1
1
  import * as Angular from '@angular/core'
2
2
  import { deepEqual } from '@benjavicente/router-core'
3
3
  import { injectRouter } from './injectRouter'
4
- import { injectStore } from './injectStore'
4
+ import { injectStore } from './store/injectStore'
5
5
  import { MATCH_CONTEXT_INJECTOR_TOKEN } from './matchInjectorToken'
6
6
  import type {
7
7
  AnyRouter,
@@ -28,15 +28,15 @@ export function injectMatches<
28
28
  opts?: InjectMatchesBaseOptions<TRouter, TSelected>,
29
29
  ): Angular.Signal<InjectMatchesResult<TRouter, TSelected>> {
30
30
  const router = injectRouter<TRouter>()
31
- const matches = injectStore(router.stores.activeMatchesSnapshot, (value) => {
32
- return value as Array<MakeRouteMatchUnion<TRouter>>
33
- })
34
31
 
35
- return Angular.computed(() => {
36
- const currentMatches = matches()
37
- const result = opts?.select ? opts.select(currentMatches) : currentMatches
38
- return result as InjectMatchesResult<TRouter, TSelected>
39
- }, { equal: deepEqual }) as any
32
+ return injectStore(
33
+ router.stores.matches,
34
+ (currentMatches) => {
35
+ const matches = currentMatches as Array<MakeRouteMatchUnion<TRouter>>
36
+ return opts?.select ? opts.select(matches) : matches
37
+ },
38
+ { equal: deepEqual },
39
+ ) as Angular.Signal<InjectMatchesResult<TRouter, TSelected>>
40
40
  }
41
41
 
42
42
  export function injectParentMatches<
@@ -2,7 +2,7 @@ import { isServer } from '@benjavicente/router-core/isServer'
2
2
  import * as Angular from '@angular/core'
3
3
  import { deepEqual } from '@benjavicente/router-core'
4
4
  import { injectRouter } from './injectRouter'
5
- import { injectStore } from './injectStore'
5
+ import { injectStore } from './store/injectStore'
6
6
  import type {
7
7
  AnyRouter,
8
8
  RegisteredRouter,
@@ -29,25 +29,30 @@ export function injectRouterState<
29
29
  warn: opts?.router === undefined,
30
30
  })
31
31
  const router = opts?.router ?? contextRouter
32
- const state = injectStore(router.stores.__store, (state) => state)
33
32
 
34
33
  // During SSR we render exactly once and do not need reactivity.
35
34
  // Avoid subscribing to the store on the server since the server store
36
35
  // implementation does not provide subscribe() semantics.
37
- const _isServer =
38
- typeof isServer === 'boolean' ? isServer : router.isServer
36
+ const _isServer = isServer ?? router.isServer
39
37
  if (_isServer) {
40
- const state = router.stores.__store.state as RouterState<
38
+ const state = router.stores.__store.get() as RouterState<
41
39
  TRouter['routeTree']
42
40
  >
43
41
  const selected = (
44
42
  opts?.select ? opts.select(state) : state
45
43
  ) as InjectRouterStateResult<TRouter, TSelected>
46
- return (() => selected) as Angular.Signal<InjectRouterStateResult<TRouter, TSelected>>
44
+ return (() => selected) as Angular.Signal<
45
+ InjectRouterStateResult<TRouter, TSelected>
46
+ >
47
+ }
48
+
49
+ if (!opts?.select) {
50
+ return injectStore(router.stores.__store) as Angular.Signal<
51
+ InjectRouterStateResult<TRouter, TSelected>
52
+ >
47
53
  }
48
54
 
49
- return Angular.computed(() => {
50
- const result = opts?.select ? opts.select(state()) : state()
51
- return result as InjectRouterStateResult<TRouter, TSelected>
52
- }, { equal: deepEqual }) as any;
55
+ return injectStore(router.stores.__store, opts.select, {
56
+ equal: deepEqual,
57
+ }) as Angular.Signal<InjectRouterStateResult<TRouter, TSelected>>
53
58
  }
@@ -1,9 +1,10 @@
1
- import * as Angular from '@angular/core'
1
+ import { batch, createAtom } from '@tanstack/store'
2
2
  import {
3
3
  createNonReactiveMutableStore,
4
4
  createNonReactiveReadonlyStore,
5
5
  } from '@benjavicente/router-core'
6
6
  import { isServer } from '@benjavicente/router-core/isServer'
7
+ import type { Readable } from '@tanstack/store'
7
8
  import type {
8
9
  AnyRoute,
9
10
  GetStoreConfig,
@@ -13,9 +14,13 @@ import type {
13
14
  } from '@benjavicente/router-core'
14
15
 
15
16
  declare module '@benjavicente/router-core' {
17
+ export interface RouterReadableStore<TValue> extends Readable<TValue> {}
18
+
16
19
  // eslint-disable-next-line unused-imports/no-unused-vars -- generic must match upstream `RouterStores<TRouteTree>` for augmentation
17
20
  export interface RouterStores<in out TRouteTree extends AnyRoute> {
21
+ /** Maps each active routeId to the matchId of its child in the match tree. */
18
22
  childMatchIdByRouteId: RouterReadableStore<Record<string, string>>
23
+ /** Maps each pending routeId to true for quick lookup. */
19
24
  pendingRouteIds: RouterReadableStore<Record<string, boolean>>
20
25
  }
21
26
  }
@@ -27,68 +32,32 @@ function initRouterStores(
27
32
  ) => RouterReadableStore<TValue>,
28
33
  ) {
29
34
  stores.childMatchIdByRouteId = createReadonlyStore(() => {
30
- const ids = stores.matchesId.state
31
- const result: Record<string, string> = {}
32
-
35
+ const ids = stores.matchesId.get()
36
+ const obj: Record<string, string> = {}
33
37
  for (let i = 0; i < ids.length - 1; i++) {
34
- const matchId = ids[i]
35
- const childId = ids[i + 1]
36
- if (matchId === undefined || childId === undefined) continue
37
- const parentStore = stores.activeMatchStoresById.get(matchId)
38
+ const parentStore = stores.matchStores.get(ids[i]!)
38
39
  if (parentStore?.routeId) {
39
- result[parentStore.routeId] = childId
40
+ obj[parentStore.routeId] = ids[i + 1]!
40
41
  }
41
42
  }
42
-
43
- return result
43
+ return obj
44
44
  })
45
45
 
46
46
  stores.pendingRouteIds = createReadonlyStore(() => {
47
- const ids = stores.pendingMatchesId.state
48
- const result: Record<string, boolean> = {}
49
-
47
+ const ids = stores.pendingIds.get()
48
+ const obj: Record<string, boolean> = {}
50
49
  for (const id of ids) {
51
- const store = stores.pendingMatchStoresById.get(id)
50
+ const store = stores.pendingMatchStores.get(id)
52
51
  if (store?.routeId) {
53
- result[store.routeId] = true
52
+ obj[store.routeId] = true
54
53
  }
55
54
  }
56
-
57
- return result
55
+ return obj
58
56
  })
59
57
  }
60
58
 
61
- function createAngularMutableStore<TValue>(
62
- initialValue: TValue,
63
- ): RouterWritableStore<TValue> {
64
- const signal = Angular.signal(initialValue)
65
-
66
- return {
67
- get state() {
68
- return signal()
69
- },
70
- setState(updater) {
71
- signal.update(updater)
72
- },
73
- }
74
- }
75
-
76
- function createAngularReadonlyStore<TValue>(
77
- read: () => TValue,
78
- ): RouterReadableStore<TValue> {
79
- const computed = Angular.computed(read)
80
-
81
- return {
82
- get state() {
83
- return computed()
84
- },
85
- }
86
- }
87
-
88
59
  export const getStoreFactory: GetStoreConfig = (opts) => {
89
- const useNonReactive =
90
- typeof isServer === 'boolean' ? isServer : !!opts.isServer
91
- if (useNonReactive) {
60
+ if (isServer ?? opts.isServer) {
92
61
  return {
93
62
  createMutableStore: createNonReactiveMutableStore,
94
63
  createReadonlyStore: createNonReactiveReadonlyStore,
@@ -99,9 +68,13 @@ export const getStoreFactory: GetStoreConfig = (opts) => {
99
68
  }
100
69
 
101
70
  return {
102
- createMutableStore: createAngularMutableStore,
103
- createReadonlyStore: createAngularReadonlyStore,
104
- batch: (fn) => fn(),
105
- init: (stores) => initRouterStores(stores, createAngularReadonlyStore),
71
+ createMutableStore: createAtom as <TValue>(
72
+ initialValue: TValue,
73
+ ) => RouterWritableStore<TValue>,
74
+ createReadonlyStore: createAtom as <TValue>(
75
+ read: () => TValue,
76
+ ) => RouterReadableStore<TValue>,
77
+ batch,
78
+ init: (stores) => initRouterStores(stores, createAtom),
106
79
  }
107
80
  }
@@ -0,0 +1,62 @@
1
+ import {
2
+ Injector,
3
+ assertInInjectionContext,
4
+ effect,
5
+ inject,
6
+ linkedSignal,
7
+ runInInjectionContext,
8
+ } from '@angular/core'
9
+ import type { CreateSignalOptions, Signal } from '@angular/core'
10
+
11
+ export interface InjectSelectorOptions<TSelected> extends Omit<
12
+ CreateSignalOptions<TSelected>,
13
+ 'equal'
14
+ > {
15
+ compare?: (a: TSelected, b: TSelected) => boolean
16
+ injector?: Injector
17
+ }
18
+
19
+ export type SelectionSource<T> = {
20
+ get: () => T
21
+ subscribe: (listener: (value: T) => void) => {
22
+ unsubscribe: () => void
23
+ }
24
+ }
25
+
26
+ function resolveInjector(
27
+ fn: (...args: Array<never>) => unknown,
28
+ injector?: Injector,
29
+ ) {
30
+ if (!injector) {
31
+ assertInInjectionContext(fn)
32
+ return inject(Injector)
33
+ }
34
+
35
+ return injector
36
+ }
37
+
38
+ export function injectSelector<TState, TSelected = NoInfer<TState>>(
39
+ source: SelectionSource<TState> | (() => SelectionSource<TState>),
40
+ selector: (state: NoInfer<TState>) => TSelected = (d) =>
41
+ d as unknown as TSelected,
42
+ options?: InjectSelectorOptions<TSelected>,
43
+ ): Signal<TSelected> {
44
+ const injector = resolveInjector(injectSelector, options?.injector)
45
+
46
+ return runInInjectionContext(injector, () => {
47
+ const _source = typeof source === 'function' ? source : () => source
48
+
49
+ const slice = linkedSignal(() => selector(_source().get()), {
50
+ equal: options?.compare,
51
+ })
52
+
53
+ effect((onCleanup) => {
54
+ const { unsubscribe } = _source().subscribe((state) => {
55
+ slice.set(selector(state))
56
+ })
57
+ onCleanup(unsubscribe)
58
+ })
59
+
60
+ return slice.asReadonly()
61
+ })
62
+ }
@@ -0,0 +1,33 @@
1
+ import { injectSelector } from './injectSelector'
2
+ import type { CreateSignalOptions, Injector, Signal } from '@angular/core'
3
+ import type { SelectionSource } from './injectSelector'
4
+
5
+ type CompatibilityInjectStoreOptions<TSelected> =
6
+ CreateSignalOptions<TSelected> & {
7
+ injector?: Injector
8
+ }
9
+
10
+ export function injectStore<TState, TSelected = NoInfer<TState>>(
11
+ store: SelectionSource<TState>,
12
+ selector?: (state: NoInfer<TState>) => TSelected,
13
+ options?: CompatibilityInjectStoreOptions<TSelected>,
14
+ ): Signal<TSelected>
15
+ export function injectStore<TState, TSelected = NoInfer<TState>>(
16
+ store: SelectionSource<TState> | (() => SelectionSource<TState>),
17
+ selector?: (state: NoInfer<TState>) => TSelected,
18
+ options?: CompatibilityInjectStoreOptions<TSelected>,
19
+ ): Signal<TSelected>
20
+ export function injectStore<TState, TSelected = NoInfer<TState>>(
21
+ store: SelectionSource<TState> | (() => SelectionSource<TState>),
22
+ selector: (state: NoInfer<TState>) => TSelected = (d) =>
23
+ d as unknown as TSelected,
24
+ options?: CompatibilityInjectStoreOptions<TSelected>,
25
+ ): Signal<TSelected> {
26
+ const { equal, injector, ...signalOptions } = options ?? {}
27
+
28
+ return injectSelector(store, selector, {
29
+ ...signalOptions,
30
+ compare: equal,
31
+ injector,
32
+ })
33
+ }
@@ -5,7 +5,7 @@ import {
5
5
  trimPathRight,
6
6
  } from '@benjavicente/router-core'
7
7
  import { injectRouter } from './injectRouter'
8
- import { injectStore } from './injectStore'
8
+ import { injectStore } from './store/injectStore'
9
9
  import type { AnyRouter } from '@benjavicente/router-core'
10
10
 
11
11
  // Track mount state per router to avoid double-loading
@@ -46,7 +46,7 @@ export function injectTransitionerSetup() {
46
46
 
47
47
  // Track pending state changes
48
48
  const hasPendingMatches = injectStore(
49
- router.stores.hasPendingMatches,
49
+ router.stores.hasPending,
50
50
  (value) => value,
51
51
  )
52
52
  const status = injectStore(router.stores.status, (value) => value)
@@ -72,7 +72,7 @@ export function injectTransitionerSetup() {
72
72
  // Implement startTransition similar to React/Solid
73
73
  // Angular doesn't have a native startTransition like React 18, so we simulate it
74
74
  router.startTransition = (fn: () => void | Promise<void>) => {
75
- router.stores.isTransitioning.setState(() => true)
75
+ router.stores.isTransitioning.set(true)
76
76
 
77
77
  // Helper to end the transition
78
78
  const endTransition = () => {
@@ -84,7 +84,7 @@ export function injectTransitionerSetup() {
84
84
  {
85
85
  read: () => {
86
86
  try {
87
- router.stores.isTransitioning.setState(() => false)
87
+ router.stores.isTransitioning.set(false)
88
88
  } catch {
89
89
  // Ignore errors if component is unmounted
90
90
  }
@@ -133,8 +133,8 @@ export function injectTransitionerSetup() {
133
133
  isMounted.set(true)
134
134
  if (!isAnyPending()) {
135
135
  if (status() === 'pending') {
136
- router.stores.status.setState(() => 'idle')
137
- router.stores.resolvedLocation.setState(() => location())
136
+ router.stores.status.set('idle')
137
+ router.stores.resolvedLocation.set(location())
138
138
  }
139
139
  }
140
140
  })
@@ -176,10 +176,7 @@ export function injectTransitionerSetup() {
176
176
  if (prevIsLoading() && !isLoading()) {
177
177
  router.emit({
178
178
  type: 'onLoad',
179
- ...getLocationChangeInfo(
180
- location(),
181
- resolvedLocation(),
182
- ),
179
+ ...getLocationChangeInfo(location(), resolvedLocation()),
183
180
  })
184
181
  }
185
182
  } catch {
@@ -194,10 +191,7 @@ export function injectTransitionerSetup() {
194
191
  if (prevIsPagePending() && !isPagePending()) {
195
192
  router.emit({
196
193
  type: 'onBeforeRouteMount',
197
- ...getLocationChangeInfo(
198
- location(),
199
- resolvedLocation(),
200
- ),
194
+ ...getLocationChangeInfo(location(), resolvedLocation()),
201
195
  })
202
196
  }
203
197
  } catch {
@@ -209,21 +203,14 @@ export function injectTransitionerSetup() {
209
203
  Angular.effect(() => {
210
204
  if (!isMounted()) return
211
205
  try {
212
- if (
213
- prevIsAnyPending() &&
214
- !isAnyPending() &&
215
- status() === 'pending'
216
- ) {
217
- router.stores.status.setState(() => 'idle')
218
- router.stores.resolvedLocation.setState(() => location())
206
+ if (prevIsAnyPending() && !isAnyPending() && status() === 'pending') {
207
+ router.stores.status.set('idle')
208
+ router.stores.resolvedLocation.set(location())
219
209
  }
220
210
 
221
211
  // The router was pending and now it's not
222
212
  if (prevIsAnyPending() && !isAnyPending()) {
223
- const changeInfo = getLocationChangeInfo(
224
- location(),
225
- resolvedLocation(),
226
- )
213
+ const changeInfo = getLocationChangeInfo(location(), resolvedLocation())
227
214
  router.emit({
228
215
  type: 'onResolved',
229
216
  ...changeInfo,
@@ -1,87 +0,0 @@
1
- import {
2
- assertInInjectionContext,
3
- effect,
4
- linkedSignal,
5
- } from '@angular/core'
6
- import type { CreateSignalOptions, Signal } from '@angular/core'
7
-
8
- type ReadableStore<TState> = {
9
- state: TState
10
- }
11
-
12
- export function injectStore<TState, TSelected = NoInfer<TState>>(
13
- storeOrStoreSignal:
14
- | ReadableStore<TState>
15
- | (() => ReadableStore<TState>),
16
- selector: (state: NoInfer<TState>) => TSelected = (d) =>
17
- d as unknown as TSelected,
18
- options: CreateSignalOptions<TSelected> = {
19
- equal: shallow,
20
- },
21
- ): Signal<TSelected> {
22
- assertInInjectionContext(injectStore)
23
-
24
- const storeSignal =
25
- typeof storeOrStoreSignal === 'function'
26
- ? storeOrStoreSignal
27
- : () => storeOrStoreSignal
28
-
29
- const slice = linkedSignal(() => selector(storeSignal().state), options)
30
-
31
- effect(() => {
32
- slice()
33
- })
34
-
35
- return slice.asReadonly()
36
- }
37
-
38
- function shallow<T>(objA: T, objB: T) {
39
- if (Object.is(objA, objB)) {
40
- return true
41
- }
42
-
43
- if (
44
- typeof objA !== 'object' ||
45
- objA === null ||
46
- typeof objB !== 'object' ||
47
- objB === null
48
- ) {
49
- return false
50
- }
51
-
52
- if (objA instanceof Map && objB instanceof Map) {
53
- if (objA.size !== objB.size) return false
54
- for (const [k, v] of objA) {
55
- if (!objB.has(k) || !Object.is(v, objB.get(k))) return false
56
- }
57
- return true
58
- }
59
-
60
- if (objA instanceof Set && objB instanceof Set) {
61
- if (objA.size !== objB.size) return false
62
- for (const v of objA) {
63
- if (!objB.has(v)) return false
64
- }
65
- return true
66
- }
67
-
68
- if (objA instanceof Date && objB instanceof Date) {
69
- return objA.getTime() === objB.getTime()
70
- }
71
-
72
- const keysA = Object.keys(objA)
73
- if (keysA.length !== Object.keys(objB).length) {
74
- return false
75
- }
76
-
77
- for (const key of keysA) {
78
- if (
79
- !Object.prototype.hasOwnProperty.call(objB, key) ||
80
- !Object.is(objA[key as keyof T], objB[key as keyof T])
81
- ) {
82
- return false
83
- }
84
- }
85
-
86
- return true
87
- }