@tanstack/router-core 0.0.1-beta.45 → 0.0.1-beta.49

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 (44) hide show
  1. package/build/cjs/actions.js +94 -0
  2. package/build/cjs/actions.js.map +1 -0
  3. package/build/cjs/history.js +163 -0
  4. package/build/cjs/history.js.map +1 -0
  5. package/build/cjs/index.js +18 -20
  6. package/build/cjs/index.js.map +1 -1
  7. package/build/cjs/interop.js +175 -0
  8. package/build/cjs/interop.js.map +1 -0
  9. package/build/cjs/path.js +4 -5
  10. package/build/cjs/path.js.map +1 -1
  11. package/build/cjs/route.js +16 -138
  12. package/build/cjs/route.js.map +1 -1
  13. package/build/cjs/routeConfig.js +1 -7
  14. package/build/cjs/routeConfig.js.map +1 -1
  15. package/build/cjs/routeMatch.js +194 -199
  16. package/build/cjs/routeMatch.js.map +1 -1
  17. package/build/cjs/router.js +726 -703
  18. package/build/cjs/router.js.map +1 -1
  19. package/build/cjs/store.js +54 -0
  20. package/build/cjs/store.js.map +1 -0
  21. package/build/esm/index.js +1305 -1114
  22. package/build/esm/index.js.map +1 -1
  23. package/build/stats-html.html +1 -1
  24. package/build/stats-react.json +229 -192
  25. package/build/types/index.d.ts +172 -109
  26. package/build/umd/index.development.js +1381 -2331
  27. package/build/umd/index.development.js.map +1 -1
  28. package/build/umd/index.production.js +1 -1
  29. package/build/umd/index.production.js.map +1 -1
  30. package/package.json +3 -3
  31. package/src/actions.ts +157 -0
  32. package/src/history.ts +199 -0
  33. package/src/index.ts +4 -7
  34. package/src/interop.ts +169 -0
  35. package/src/link.ts +2 -2
  36. package/src/route.ts +34 -239
  37. package/src/routeConfig.ts +3 -34
  38. package/src/routeInfo.ts +6 -21
  39. package/src/routeMatch.ts +270 -285
  40. package/src/router.ts +967 -963
  41. package/src/store.ts +52 -0
  42. package/build/cjs/sharedClone.js +0 -122
  43. package/build/cjs/sharedClone.js.map +0 -1
  44. package/src/sharedClone.ts +0 -118
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.45",
4
+ "version": "0.0.1-beta.49",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -40,8 +40,8 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@babel/runtime": "^7.16.7",
43
- "@solidjs/reactivity": "^0.0.6",
44
- "history": "^5.2.0",
43
+ "@solidjs/reactivity": "^0.0.7",
44
+ "immer": "^9.0.15",
45
45
  "tiny-invariant": "^1.3.1"
46
46
  },
47
47
  "devDependencies": {
package/src/actions.ts ADDED
@@ -0,0 +1,157 @@
1
+ // createRouterAction is a constrained identify function that takes options: key, action, onSuccess, onError, onSettled, etc
2
+
3
+ import invariant from 'tiny-invariant'
4
+ import { batch, createStore, Store } from './store'
5
+
6
+ export interface ActionOptions<
7
+ TKey extends string = string,
8
+ TPayload = unknown,
9
+ TResponse = unknown,
10
+ TError = Error,
11
+ > {
12
+ key?: TKey
13
+ action: (payload: TPayload) => TResponse | Promise<TResponse>
14
+ onLatestSuccess?: ActionCallback<TPayload, TResponse, TError>
15
+ onEachSuccess?: ActionCallback<TPayload, TResponse, TError>
16
+ onLatestError?: ActionCallback<TPayload, TResponse, TError>
17
+ onEachError?: ActionCallback<TPayload, TResponse, TError>
18
+ onLatestSettled?: ActionCallback<TPayload, TResponse, TError>
19
+ onEachSettled?: ActionCallback<TPayload, TResponse, TError>
20
+ maxSubmissions?: number
21
+ debug?: boolean
22
+ }
23
+
24
+ type ActionCallback<TPayload, TResponse, TError> = (
25
+ submission: ActionSubmission<TPayload, TResponse, TError>,
26
+ ) => void | Promise<void>
27
+
28
+ export interface Action<
29
+ TKey extends string = string,
30
+ TPayload = unknown,
31
+ TResponse = unknown,
32
+ TError = Error,
33
+ > {
34
+ options: ActionOptions<TKey, TPayload, TResponse, TError>
35
+ submit: (payload?: TPayload) => Promise<TResponse>
36
+ reset: () => void
37
+ store: Store<ActionStore<TPayload, TResponse, TError>>
38
+ }
39
+
40
+ export interface ActionStore<
41
+ TPayload = unknown,
42
+ TResponse = unknown,
43
+ TError = Error,
44
+ > {
45
+ submissions: ActionSubmission<TPayload, TResponse, TError>[]
46
+ }
47
+
48
+ export type ActionFn<TActionPayload = unknown, TActionResponse = unknown> = (
49
+ submission: TActionPayload,
50
+ ) => TActionResponse | Promise<TActionResponse>
51
+
52
+ export interface ActionSubmission<
53
+ TPayload = unknown,
54
+ TResponse = unknown,
55
+ TError = Error,
56
+ > {
57
+ submittedAt: number
58
+ status: 'idle' | 'pending' | 'success' | 'error'
59
+ payload: TPayload
60
+ response?: TResponse
61
+ error?: TError
62
+ isInvalid?: boolean
63
+ invalidate: () => void
64
+ getIsLatest: () => boolean
65
+ }
66
+
67
+ export function createAction<TKey extends string, TPayload, TResponse, TError>(
68
+ options: ActionOptions<TKey, TPayload, TResponse, TError>,
69
+ ): Action<TKey, TPayload, TResponse, TError> {
70
+ const store = createStore<ActionStore<TPayload, TResponse, TError>>(
71
+ {
72
+ submissions: [],
73
+ },
74
+ options.debug,
75
+ )
76
+
77
+ return {
78
+ options,
79
+ store,
80
+ reset: () => {
81
+ store.setState((s) => {
82
+ s.submissions = []
83
+ })
84
+ },
85
+ submit: async (payload) => {
86
+ const submission: ActionSubmission<TPayload, TResponse, TError> = {
87
+ submittedAt: Date.now(),
88
+ status: 'pending',
89
+ payload: payload as TPayload,
90
+ invalidate: () => {
91
+ setSubmission((s) => {
92
+ s.isInvalid = true
93
+ })
94
+ },
95
+ getIsLatest: () =>
96
+ store.state.submissions[store.state.submissions.length - 1]
97
+ ?.submittedAt === submission.submittedAt,
98
+ }
99
+
100
+ const setSubmission = (
101
+ updater: (
102
+ submission: ActionSubmission<TPayload, TResponse, TError>,
103
+ ) => void,
104
+ ) => {
105
+ store.setState((s) => {
106
+ const a = s.submissions.find(
107
+ (d) => d.submittedAt === submission.submittedAt,
108
+ )
109
+
110
+ invariant(a, 'Could not find submission in store')
111
+
112
+ updater(a)
113
+ })
114
+ }
115
+
116
+ store.setState((s) => {
117
+ s.submissions.push(submission)
118
+ s.submissions.reverse()
119
+ s.submissions = s.submissions.slice(0, options.maxSubmissions ?? 10)
120
+ s.submissions.reverse()
121
+ })
122
+
123
+ const after = async () => {
124
+ options.onEachSettled?.(submission)
125
+ if (submission.getIsLatest())
126
+ await options.onLatestSettled?.(submission)
127
+ }
128
+
129
+ try {
130
+ const res = await options.action?.(submission.payload)
131
+ setSubmission((s) => {
132
+ s.response = res
133
+ })
134
+ await options.onEachSuccess?.(submission)
135
+ if (submission.getIsLatest())
136
+ await options.onLatestSuccess?.(submission)
137
+ await after()
138
+ setSubmission((s) => {
139
+ s.status = 'success'
140
+ })
141
+ return res
142
+ } catch (err: any) {
143
+ console.error(err)
144
+ setSubmission((s) => {
145
+ s.error = err
146
+ })
147
+ await options.onEachError?.(submission)
148
+ if (submission.getIsLatest()) await options.onLatestError?.(submission)
149
+ await after()
150
+ setSubmission((s) => {
151
+ s.status = 'error'
152
+ })
153
+ throw err
154
+ }
155
+ },
156
+ }
157
+ }
package/src/history.ts ADDED
@@ -0,0 +1,199 @@
1
+ // While the public API was clearly inspired by the "history" npm package,
2
+ // This implementation attempts to be more lightweight by
3
+ // making assumptions about the way TanStack Router works
4
+
5
+ export interface RouterHistory {
6
+ location: RouterLocation
7
+ listen: (cb: () => void) => () => void
8
+ push: (path: string, state: any) => void
9
+ replace: (path: string, state: any) => void
10
+ go: (index: number) => void
11
+ back: () => void
12
+ forward: () => void
13
+ }
14
+
15
+ export interface ParsedPath {
16
+ href: string
17
+ pathname: string
18
+ search: string
19
+ hash: string
20
+ }
21
+
22
+ export interface RouterLocation extends ParsedPath {
23
+ state: any
24
+ }
25
+
26
+ const popStateEvent = 'popstate'
27
+
28
+ function createHistory(opts: {
29
+ getLocation: () => RouterLocation
30
+ listener: (onUpdate: () => void) => () => void
31
+ pushState: (path: string, state: any) => void
32
+ replaceState: (path: string, state: any) => void
33
+ go: (n: number) => void
34
+ back: () => void
35
+ forward: () => void
36
+ }): RouterHistory {
37
+ let currentLocation = opts.getLocation()
38
+ let unsub = () => {}
39
+ let listeners = new Set<() => void>()
40
+
41
+ const onUpdate = () => {
42
+ currentLocation = opts.getLocation()
43
+
44
+ listeners.forEach((listener) => listener())
45
+ }
46
+
47
+ return {
48
+ get location() {
49
+ return currentLocation
50
+ },
51
+ listen: (cb: () => void) => {
52
+ if (listeners.size === 0) {
53
+ unsub = opts.listener(onUpdate)
54
+ }
55
+ listeners.add(cb)
56
+
57
+ return () => {
58
+ listeners.delete(cb)
59
+ if (listeners.size === 0) {
60
+ unsub()
61
+ }
62
+ }
63
+ },
64
+ push: (path: string, state: any) => {
65
+ opts.pushState(path, state)
66
+ onUpdate()
67
+ },
68
+ replace: (path: string, state: any) => {
69
+ opts.replaceState(path, state)
70
+ onUpdate()
71
+ },
72
+ go: (index) => {
73
+ opts.go(index)
74
+ onUpdate()
75
+ },
76
+ back: () => {
77
+ opts.back()
78
+ onUpdate()
79
+ },
80
+ forward: () => {
81
+ opts.forward()
82
+ onUpdate()
83
+ },
84
+ }
85
+ }
86
+
87
+ export function createBrowserHistory(opts?: {
88
+ getHref?: () => string
89
+ createHref?: (path: string) => string
90
+ }): RouterHistory {
91
+ const getHref =
92
+ opts?.getHref ??
93
+ (() =>
94
+ `${window.location.pathname}${window.location.hash}${window.location.search}`)
95
+ const createHref = opts?.createHref ?? ((path) => path)
96
+ const getLocation = () => parseLocation(getHref(), history.state)
97
+
98
+ return createHistory({
99
+ getLocation,
100
+ listener: (onUpdate) => {
101
+ window.addEventListener(popStateEvent, onUpdate)
102
+ return () => {
103
+ window.removeEventListener(popStateEvent, onUpdate)
104
+ }
105
+ },
106
+ pushState: (path, state) => {
107
+ window.history.pushState(
108
+ { ...state, key: createRandomKey() },
109
+ '',
110
+ createHref(path),
111
+ )
112
+ },
113
+ replaceState: (path, state) => {
114
+ window.history.replaceState(
115
+ { ...state, key: createRandomKey() },
116
+ '',
117
+ createHref(path),
118
+ )
119
+ },
120
+ back: () => window.history.back(),
121
+ forward: () => window.history.forward(),
122
+ go: (n) => window.history.go(n),
123
+ })
124
+ }
125
+
126
+ export function createHashHistory(): RouterHistory {
127
+ return createBrowserHistory({
128
+ getHref: () => window.location.hash.substring(1),
129
+ createHref: (path) => `#${path}`,
130
+ })
131
+ }
132
+
133
+ export function createMemoryHistory(
134
+ opts: {
135
+ initialEntries: string[]
136
+ initialIndex?: number
137
+ } = {
138
+ initialEntries: ['/'],
139
+ },
140
+ ): RouterHistory {
141
+ const entries = opts.initialEntries
142
+ let index = opts.initialIndex ?? entries.length - 1
143
+ let currentState = {}
144
+
145
+ const getLocation = () => parseLocation(entries[index]!, currentState)
146
+
147
+ return createHistory({
148
+ getLocation,
149
+ listener: (onUpdate) => {
150
+ window.addEventListener(popStateEvent, onUpdate)
151
+ // We might need to handle the hashchange event in the future
152
+ // window.addEventListener(hashChangeEvent, onUpdate)
153
+ return () => {
154
+ window.removeEventListener(popStateEvent, onUpdate)
155
+ }
156
+ },
157
+ pushState: (path, state) => {
158
+ currentState = {
159
+ ...state,
160
+ key: createRandomKey(),
161
+ }
162
+ entries.push(path)
163
+ index++
164
+ },
165
+ replaceState: (path, state) => {
166
+ currentState = {
167
+ ...state,
168
+ key: createRandomKey(),
169
+ }
170
+ entries[index] = path
171
+ },
172
+ back: () => {
173
+ index--
174
+ },
175
+ forward: () => {
176
+ index = Math.min(index + 1, entries.length - 1)
177
+ },
178
+ go: (n) => window.history.go(n),
179
+ })
180
+ }
181
+
182
+ function parseLocation(href: string, state: any): RouterLocation {
183
+ let hashIndex = href.indexOf('#')
184
+ let searchIndex = href.indexOf('?')
185
+ const pathEnd = Math.min(hashIndex, searchIndex)
186
+
187
+ return {
188
+ href,
189
+ pathname: pathEnd > -1 ? href.substring(0, pathEnd) : href,
190
+ hash: hashIndex > -1 ? href.substring(hashIndex, searchIndex) : '',
191
+ search: searchIndex > -1 ? href.substring(searchIndex) : '',
192
+ state,
193
+ }
194
+ }
195
+
196
+ // Thanks co-pilot!
197
+ function createRandomKey() {
198
+ return (Math.random() + 1).toString(36).substring(7)
199
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,5 @@
1
- export {
2
- createHashHistory,
3
- createBrowserHistory,
4
- createMemoryHistory,
5
- } from 'history'
6
-
7
1
  export { default as invariant } from 'tiny-invariant'
2
+ export * from './history'
8
3
 
9
4
  export * from './frameworks'
10
5
  export * from './link'
@@ -17,4 +12,6 @@ export * from './routeMatch'
17
12
  export * from './router'
18
13
  export * from './searchParams'
19
14
  export * from './utils'
20
- export * from './sharedClone'
15
+ export * from './interop'
16
+ export * from './actions'
17
+ export * from './store'
package/src/interop.ts ADDED
@@ -0,0 +1,169 @@
1
+ // /**
2
+ // * This function converts a store to an immutable value, which is
3
+ // * more complex than you think. On first read, (when prev is undefined)
4
+ // * every value must be recursively touched so tracking is "deep".
5
+ // * Every object/array structure must also be cloned to
6
+ // * have a new reference, otherwise it will get mutated by subsequent
7
+ // * store updates.
8
+ // *
9
+ // * In the case that prev is supplied, we have to do deep comparisons
10
+ // * between prev and next objects/array references and if they are deeply
11
+ // * equal, we can return the prev version for referential equality.
12
+ // */
13
+ // export function storeToImmutable<T>(prev: any, next: T): T {
14
+ // const cache = new Map()
15
+
16
+ // // Visit all nodes
17
+ // // clone all next structures
18
+ // // from bottom up, if prev === next, return prev
19
+
20
+ // function recurse(prev: any, next: any) {
21
+ // if (cache.has(next)) {
22
+ // return cache.get(next)
23
+ // }
24
+
25
+ // const prevIsArray = Array.isArray(prev)
26
+ // const nextIsArray = Array.isArray(next)
27
+ // const prevIsObj = isPlainObject(prev)
28
+ // const nextIsObj = isPlainObject(next)
29
+ // const nextIsComplex = nextIsArray || nextIsObj
30
+
31
+ // const isArray = prevIsArray && nextIsArray
32
+ // const isObj = prevIsObj && nextIsObj
33
+
34
+ // const isSameStructure = isArray || isObj
35
+
36
+ // if (nextIsComplex) {
37
+ // const prevSize = isArray
38
+ // ? prev.length
39
+ // : isObj
40
+ // ? Object.keys(prev).length
41
+ // : -1
42
+ // const nextKeys = isArray ? next : Object.keys(next)
43
+ // const nextSize = nextKeys.length
44
+
45
+ // let changed = false
46
+ // const copy: any = nextIsArray ? [] : {}
47
+
48
+ // for (let i = 0; i < nextSize; i++) {
49
+ // const key = isArray ? i : nextKeys[i]
50
+ // const prevValue = isSameStructure ? prev[key] : undefined
51
+ // const nextValue = next[key]
52
+
53
+ // // Recurse the new value
54
+ // try {
55
+ // console.count(key)
56
+ // copy[key] = recurse(prevValue, nextValue)
57
+ // } catch {}
58
+
59
+ // // If the new value has changed reference,
60
+ // // mark the obj/array as changed
61
+ // if (!changed && copy[key] !== prevValue) {
62
+ // changed = true
63
+ // }
64
+ // }
65
+
66
+ // // No items have changed!
67
+ // // If something has changed, return a clone of the next obj/array
68
+ // if (changed || prevSize !== nextSize) {
69
+ // cache.set(next, copy)
70
+ // return copy
71
+ // }
72
+
73
+ // // If they are exactly the same, return the prev obj/array
74
+ // cache.set(next, prev)
75
+ // return prev
76
+ // }
77
+
78
+ // cache.set(next, next)
79
+ // return next
80
+ // }
81
+
82
+ // return recurse(prev, next)
83
+ // }
84
+
85
+ /**
86
+ * This function returns `a` if `b` is deeply equal.
87
+ * If not, it will replace any deeply equal children of `b` with those of `a`.
88
+ * This can be used for structural sharing between immutable JSON values for example.
89
+ * Do not use this with signals
90
+ */
91
+ export function replaceEqualDeep<T>(prev: any, _next: T): T {
92
+ if (prev === _next) {
93
+ return prev
94
+ }
95
+
96
+ const next = _next as any
97
+
98
+ const array = Array.isArray(prev) && Array.isArray(next)
99
+
100
+ if (array || (isPlainObject(prev) && isPlainObject(next))) {
101
+ const prevSize = array ? prev.length : Object.keys(prev).length
102
+ const nextItems = array ? next : Object.keys(next)
103
+ const nextSize = nextItems.length
104
+ const copy: any = array ? [] : {}
105
+
106
+ let equalItems = 0
107
+
108
+ for (let i = 0; i < nextSize; i++) {
109
+ const key = array ? i : nextItems[i]
110
+ copy[key] = replaceEqualDeep(prev[key], next[key])
111
+ if (copy[key] === prev[key]) {
112
+ equalItems++
113
+ }
114
+ }
115
+
116
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy
117
+ }
118
+
119
+ return next
120
+ }
121
+
122
+ // Copied from: https://github.com/jonschlinkert/is-plain-object
123
+ function isPlainObject(o: any) {
124
+ if (!hasObjectPrototype(o)) {
125
+ return false
126
+ }
127
+
128
+ // If has modified constructor
129
+ const ctor = o.constructor
130
+ if (typeof ctor === 'undefined') {
131
+ return true
132
+ }
133
+
134
+ // If has modified prototype
135
+ const prot = ctor.prototype
136
+ if (!hasObjectPrototype(prot)) {
137
+ return false
138
+ }
139
+
140
+ // If constructor does not have an Object-specific method
141
+ if (!prot.hasOwnProperty('isPrototypeOf')) {
142
+ return false
143
+ }
144
+
145
+ // Most likely a plain Object
146
+ return true
147
+ }
148
+
149
+ function hasObjectPrototype(o: any) {
150
+ return Object.prototype.toString.call(o) === '[object Object]'
151
+ }
152
+
153
+ export function trackDeep<T>(obj: T): T {
154
+ const seen = new Set()
155
+
156
+ JSON.stringify(obj, (_, value) => {
157
+ if (typeof value === 'function') {
158
+ return undefined
159
+ }
160
+ if (typeof value === 'object' && value !== null) {
161
+ if (seen.has(value)) return
162
+ seen.add(value)
163
+ }
164
+
165
+ return value
166
+ })
167
+
168
+ return obj
169
+ }
package/src/link.ts CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DefaultAllRouteInfo,
4
4
  RouteInfoByPath,
5
5
  } from './routeInfo'
6
- import { Location, LocationState } from './router'
6
+ import { ParsedLocation, LocationState } from './router'
7
7
  import {
8
8
  Expand,
9
9
  NoInfer,
@@ -19,7 +19,7 @@ export type LinkInfo =
19
19
  }
20
20
  | {
21
21
  type: 'internal'
22
- next: Location
22
+ next: ParsedLocation
23
23
  handleFocus: (e: any) => void
24
24
  handleClick: (e: any) => void
25
25
  handleEnter: (e: any) => void