houdini-react 2.0.0-next.9 → 2.0.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 (185) hide show
  1. package/bin/houdini-react +173 -0
  2. package/package.json +63 -39
  3. package/postInstall.js +360 -0
  4. package/runtime/Link.tsx +50 -0
  5. package/runtime/client.ts +5 -0
  6. package/runtime/clientPlugin.ts +17 -0
  7. package/runtime/componentFields.ts +79 -0
  8. package/runtime/escape.ts +15 -0
  9. package/runtime/hooks/index.ts +17 -0
  10. package/runtime/hooks/recycleNodesInto.ts +60 -0
  11. package/runtime/hooks/useDeepCompareEffect.ts +93 -0
  12. package/runtime/hooks/useDocumentHandle.ts +243 -0
  13. package/runtime/hooks/useDocumentStore.ts +112 -0
  14. package/runtime/hooks/useDocumentSubscription.ts +65 -0
  15. package/runtime/hooks/useFragment.ts +269 -0
  16. package/runtime/hooks/useFragmentHandle.ts +274 -0
  17. package/runtime/hooks/useIsMounted.ts +14 -0
  18. package/runtime/hooks/useLogoutForm.tsx +60 -0
  19. package/runtime/hooks/useMutation.ts +80 -0
  20. package/runtime/hooks/useMutationForm.tsx +163 -0
  21. package/runtime/hooks/useQuery.ts +12 -0
  22. package/runtime/hooks/useQueryHandle.ts +194 -0
  23. package/runtime/hooks/useSubscription.ts +12 -0
  24. package/runtime/hooks/useSubscriptionHandle.ts +42 -0
  25. package/runtime/hydration.tsx +186 -0
  26. package/runtime/index.tsx +77 -0
  27. package/runtime/login.ts +22 -0
  28. package/runtime/manifest.ts +6 -0
  29. package/runtime/mock.ts +9 -0
  30. package/runtime/package.json +1 -0
  31. package/runtime/resolve-href.ts +151 -0
  32. package/runtime/routes.ts +93 -0
  33. package/runtime/routing/Router.tsx +1101 -0
  34. package/runtime/routing/cache.ts +57 -0
  35. package/runtime/routing/errors.tsx +145 -0
  36. package/runtime/routing/index.ts +17 -0
  37. package/runtime/testing.tsx +163 -0
  38. package/runtime/tsconfig.json +39 -0
  39. package/server/index.d.ts +1 -0
  40. package/server/index.js +4 -0
  41. package/server/react-streaming.d.js +0 -0
  42. package/vite/index.d.ts +3 -0
  43. package/vite/index.js +397 -0
  44. package/vite/strip-headers.d.ts +1 -0
  45. package/vite/strip-headers.js +35 -0
  46. package/vite/transform.d.ts +13 -0
  47. package/vite/transform.js +105 -0
  48. package/README.md +0 -36
  49. package/build/plugin/codegen/entries/documentWrappers.d.ts +0 -6
  50. package/build/plugin/codegen/entries/fallbacks.d.ts +0 -5
  51. package/build/plugin/codegen/entries/index.d.ts +0 -16
  52. package/build/plugin/codegen/entries/pages.d.ts +0 -2
  53. package/build/plugin/codegen/index.d.ts +0 -17
  54. package/build/plugin/codegen/manifest.d.ts +0 -5
  55. package/build/plugin/codegen/render.d.ts +0 -7
  56. package/build/plugin/codegen/router.d.ts +0 -7
  57. package/build/plugin/codegen/typeRoot.d.ts +0 -5
  58. package/build/plugin/config.d.ts +0 -4
  59. package/build/plugin/dedent.d.ts +0 -1
  60. package/build/plugin/extract.d.ts +0 -6
  61. package/build/plugin/index.d.ts +0 -5
  62. package/build/plugin/state.d.ts +0 -3
  63. package/build/plugin/transform.d.ts +0 -6
  64. package/build/plugin/vite.d.ts +0 -27
  65. package/build/plugin-cjs/index.js +0 -90119
  66. package/build/plugin-cjs/package.json +0 -1
  67. package/build/plugin-esm/index.js +0 -90115
  68. package/build/runtime/client.d.ts +0 -3
  69. package/build/runtime/clientPlugin.d.ts +0 -3
  70. package/build/runtime/componentFields.d.ts +0 -9
  71. package/build/runtime/hooks/index.d.ts +0 -8
  72. package/build/runtime/hooks/useDeepCompareEffect.d.ts +0 -35
  73. package/build/runtime/hooks/useDocumentHandle.d.ts +0 -36
  74. package/build/runtime/hooks/useDocumentStore.d.ts +0 -11
  75. package/build/runtime/hooks/useDocumentSubscription.d.ts +0 -11
  76. package/build/runtime/hooks/useFragment.d.ts +0 -16
  77. package/build/runtime/hooks/useFragmentHandle.d.ts +0 -8
  78. package/build/runtime/hooks/useIsMounted.d.ts +0 -3
  79. package/build/runtime/hooks/useMutation.d.ts +0 -14
  80. package/build/runtime/hooks/useQuery.d.ts +0 -5
  81. package/build/runtime/hooks/useQueryHandle.d.ts +0 -10
  82. package/build/runtime/hooks/useSubscription.d.ts +0 -4
  83. package/build/runtime/hooks/useSubscriptionHandle.d.ts +0 -25
  84. package/build/runtime/index.d.ts +0 -14
  85. package/build/runtime/manifest.d.ts +0 -3
  86. package/build/runtime/routing/Router.d.ts +0 -62
  87. package/build/runtime/routing/cache.d.ts +0 -7
  88. package/build/runtime/routing/hooks.d.ts +0 -40
  89. package/build/runtime/routing/index.d.ts +0 -3
  90. package/build/runtime-cjs/client.d.ts +0 -3
  91. package/build/runtime-cjs/client.js +0 -25
  92. package/build/runtime-cjs/clientPlugin.d.ts +0 -3
  93. package/build/runtime-cjs/clientPlugin.js +0 -37
  94. package/build/runtime-cjs/componentFields.d.ts +0 -9
  95. package/build/runtime-cjs/componentFields.js +0 -83
  96. package/build/runtime-cjs/hooks/index.d.ts +0 -8
  97. package/build/runtime-cjs/hooks/index.js +0 -45
  98. package/build/runtime-cjs/hooks/useDeepCompareEffect.d.ts +0 -35
  99. package/build/runtime-cjs/hooks/useDeepCompareEffect.js +0 -76
  100. package/build/runtime-cjs/hooks/useDocumentHandle.d.ts +0 -36
  101. package/build/runtime-cjs/hooks/useDocumentHandle.js +0 -177
  102. package/build/runtime-cjs/hooks/useDocumentStore.d.ts +0 -11
  103. package/build/runtime-cjs/hooks/useDocumentStore.js +0 -76
  104. package/build/runtime-cjs/hooks/useDocumentSubscription.d.ts +0 -11
  105. package/build/runtime-cjs/hooks/useDocumentSubscription.js +0 -76
  106. package/build/runtime-cjs/hooks/useFragment.d.ts +0 -16
  107. package/build/runtime-cjs/hooks/useFragment.js +0 -102
  108. package/build/runtime-cjs/hooks/useFragmentHandle.d.ts +0 -8
  109. package/build/runtime-cjs/hooks/useFragmentHandle.js +0 -47
  110. package/build/runtime-cjs/hooks/useIsMounted.d.ts +0 -3
  111. package/build/runtime-cjs/hooks/useIsMounted.js +0 -38
  112. package/build/runtime-cjs/hooks/useMutation.d.ts +0 -14
  113. package/build/runtime-cjs/hooks/useMutation.js +0 -67
  114. package/build/runtime-cjs/hooks/useQuery.d.ts +0 -5
  115. package/build/runtime-cjs/hooks/useQuery.js +0 -32
  116. package/build/runtime-cjs/hooks/useQueryHandle.d.ts +0 -10
  117. package/build/runtime-cjs/hooks/useQueryHandle.js +0 -131
  118. package/build/runtime-cjs/hooks/useSubscription.d.ts +0 -4
  119. package/build/runtime-cjs/hooks/useSubscription.js +0 -32
  120. package/build/runtime-cjs/hooks/useSubscriptionHandle.d.ts +0 -25
  121. package/build/runtime-cjs/hooks/useSubscriptionHandle.js +0 -42
  122. package/build/runtime-cjs/index.d.ts +0 -14
  123. package/build/runtime-cjs/index.js +0 -88
  124. package/build/runtime-cjs/manifest.d.ts +0 -3
  125. package/build/runtime-cjs/manifest.js +0 -25
  126. package/build/runtime-cjs/package.json +0 -1
  127. package/build/runtime-cjs/routing/Router.d.ts +0 -62
  128. package/build/runtime-cjs/routing/Router.js +0 -540
  129. package/build/runtime-cjs/routing/cache.d.ts +0 -7
  130. package/build/runtime-cjs/routing/cache.js +0 -61
  131. package/build/runtime-cjs/routing/hooks.d.ts +0 -40
  132. package/build/runtime-cjs/routing/hooks.js +0 -93
  133. package/build/runtime-cjs/routing/index.d.ts +0 -3
  134. package/build/runtime-cjs/routing/index.js +0 -33
  135. package/build/runtime-esm/client.d.ts +0 -3
  136. package/build/runtime-esm/client.js +0 -5
  137. package/build/runtime-esm/clientPlugin.d.ts +0 -3
  138. package/build/runtime-esm/clientPlugin.js +0 -17
  139. package/build/runtime-esm/componentFields.d.ts +0 -9
  140. package/build/runtime-esm/componentFields.js +0 -59
  141. package/build/runtime-esm/hooks/index.d.ts +0 -8
  142. package/build/runtime-esm/hooks/index.js +0 -15
  143. package/build/runtime-esm/hooks/useDeepCompareEffect.d.ts +0 -35
  144. package/build/runtime-esm/hooks/useDeepCompareEffect.js +0 -41
  145. package/build/runtime-esm/hooks/useDocumentHandle.d.ts +0 -36
  146. package/build/runtime-esm/hooks/useDocumentHandle.js +0 -143
  147. package/build/runtime-esm/hooks/useDocumentStore.d.ts +0 -11
  148. package/build/runtime-esm/hooks/useDocumentStore.js +0 -42
  149. package/build/runtime-esm/hooks/useDocumentSubscription.d.ts +0 -11
  150. package/build/runtime-esm/hooks/useDocumentSubscription.js +0 -42
  151. package/build/runtime-esm/hooks/useFragment.d.ts +0 -16
  152. package/build/runtime-esm/hooks/useFragment.js +0 -67
  153. package/build/runtime-esm/hooks/useFragmentHandle.d.ts +0 -8
  154. package/build/runtime-esm/hooks/useFragmentHandle.js +0 -23
  155. package/build/runtime-esm/hooks/useIsMounted.d.ts +0 -3
  156. package/build/runtime-esm/hooks/useIsMounted.js +0 -14
  157. package/build/runtime-esm/hooks/useMutation.d.ts +0 -14
  158. package/build/runtime-esm/hooks/useMutation.js +0 -42
  159. package/build/runtime-esm/hooks/useQuery.d.ts +0 -5
  160. package/build/runtime-esm/hooks/useQuery.js +0 -8
  161. package/build/runtime-esm/hooks/useQueryHandle.d.ts +0 -10
  162. package/build/runtime-esm/hooks/useQueryHandle.js +0 -97
  163. package/build/runtime-esm/hooks/useSubscription.d.ts +0 -4
  164. package/build/runtime-esm/hooks/useSubscription.js +0 -8
  165. package/build/runtime-esm/hooks/useSubscriptionHandle.d.ts +0 -25
  166. package/build/runtime-esm/hooks/useSubscriptionHandle.js +0 -18
  167. package/build/runtime-esm/index.d.ts +0 -14
  168. package/build/runtime-esm/index.js +0 -48
  169. package/build/runtime-esm/manifest.d.ts +0 -3
  170. package/build/runtime-esm/manifest.js +0 -5
  171. package/build/runtime-esm/routing/Router.d.ts +0 -62
  172. package/build/runtime-esm/routing/Router.js +0 -499
  173. package/build/runtime-esm/routing/cache.d.ts +0 -7
  174. package/build/runtime-esm/routing/cache.js +0 -36
  175. package/build/runtime-esm/routing/hooks.d.ts +0 -40
  176. package/build/runtime-esm/routing/hooks.js +0 -53
  177. package/build/runtime-esm/routing/index.d.ts +0 -3
  178. package/build/runtime-esm/routing/index.js +0 -6
  179. package/build/server/index.d.ts +0 -1
  180. package/build/server-cjs/index.js +0 -28
  181. package/build/server-cjs/package.json +0 -1
  182. package/build/server-esm/index.js +0 -4
  183. package/build/server-esm/package.json +0 -1
  184. /package/{build/plugin-esm → server}/package.json +0 -0
  185. /package/{build/runtime-esm → vite}/package.json +0 -0
@@ -0,0 +1,77 @@
1
+ import type { GraphQLObject } from 'houdini/runtime'
2
+ import type { Cache } from 'houdini/runtime/cache'
3
+
4
+ import client from './client.js'
5
+ import manifest from './manifest.js'
6
+ import {
7
+ Router as RouterImpl,
8
+ type RouterCache,
9
+ type FormResult,
10
+ RouterContextProvider,
11
+ } from './routing/index.js'
12
+
13
+ export * from './hooks/index.js'
14
+ export {
15
+ router_cache,
16
+ useCache,
17
+ useSession,
18
+ useRoute,
19
+ notFound,
20
+ unauthorized,
21
+ forbidden,
22
+ httpError,
23
+ redirect,
24
+ isRoutingError,
25
+ isApiError,
26
+ RoutingError,
27
+ RedirectError,
28
+ } from './routing/index.js'
29
+ export type { GenericRoute } from './routing/index.js'
30
+ export * from './Link.js'
31
+ export { createMock } from './mock.js'
32
+
33
+ export function Router({
34
+ cache,
35
+ initialURL,
36
+ artifact_cache,
37
+ component_cache,
38
+ data_cache,
39
+ ssr_signals,
40
+ last_variables,
41
+ session,
42
+ formResult,
43
+ formToken,
44
+ assetPrefix,
45
+ injectToStream,
46
+ }: {
47
+ initialURL: string
48
+ initialVariables: GraphQLObject
49
+ cache: Cache
50
+ session?: App.Session
51
+ formResult?: FormResult | null
52
+ formToken?: string | null
53
+ assetPrefix: string
54
+ injectToStream?: (chunk: string) => void
55
+ } & RouterCache) {
56
+ return (
57
+ <RouterContextProvider
58
+ client={client()}
59
+ cache={cache}
60
+ artifact_cache={artifact_cache}
61
+ component_cache={component_cache}
62
+ data_cache={data_cache}
63
+ ssr_signals={ssr_signals}
64
+ last_variables={last_variables}
65
+ session={session}
66
+ formResult={formResult}
67
+ formToken={formToken}
68
+ >
69
+ <RouterImpl
70
+ initialURL={initialURL}
71
+ manifest={manifest}
72
+ assetPrefix={assetPrefix}
73
+ injectToStream={injectToStream}
74
+ />
75
+ </RouterContextProvider>
76
+ )
77
+ }
@@ -0,0 +1,22 @@
1
+ import { getAuthUrl } from 'houdini/runtime'
2
+
3
+ // loginURL builds the URL for Houdini's redirect-login entry point (`/login`). Point a link at it
4
+ // to start the flow: `redirectTo` is where the user lands afterward (passed as a query param on
5
+ // this initial request, then captured into a signed cookie server-side for the OAuth round-trip) —
6
+ // omit it to return the user to the page they came from. `params`
7
+ // are forwarded verbatim to the trusted integration (e.g. `{ provider: 'github' }`) — Houdini stays
8
+ // out of the provider business.
9
+ //
10
+ // This helper is only exported from `$houdini` when `auth.redirect` is configured in
11
+ // `src/server/+config`; without a trusted integration there is no flow to start.
12
+ export function loginURL(opts?: { redirectTo?: string; params?: Record<string, string> }): string {
13
+ const search = new URLSearchParams()
14
+ if (opts?.redirectTo) {
15
+ search.set('redirectTo', opts.redirectTo)
16
+ }
17
+ for (const [key, value] of Object.entries(opts?.params ?? {})) {
18
+ search.set(key, value)
19
+ }
20
+ const qs = search.toString()
21
+ return `${getAuthUrl()}/login${qs ? `?${qs}` : ''}`
22
+ }
@@ -0,0 +1,6 @@
1
+ import type { RouterManifest } from 'houdini/router/types'
2
+
3
+ // @ts-expect-error: this file will get replaced by the build system
4
+ const manifest: RouterManifest = {}
5
+
6
+ export default manifest
@@ -0,0 +1,9 @@
1
+ import type React from 'react'
2
+
3
+ export function createMock(_args: {
4
+ url: string
5
+ params: any
6
+ data: any
7
+ }): React.ComponentType<{}> {
8
+ throw new Error('createMock: no routes have been generated yet. Run `houdini generate` first.')
9
+ }
@@ -0,0 +1 @@
1
+ {"type":"module","main":"./index.tsx"}
@@ -0,0 +1,151 @@
1
+ import { unmarshalValue, type Unmarshaler } from 'houdini/runtime'
2
+
3
+ // A marshaler turns a rich runtime value (e.g. a Date) into the transport form that
4
+ // belongs in the URL (e.g. a timestamp). Keyed by param / search-param name.
5
+ type Marshaler = (value: any) => any
6
+ type Marshalers = Record<string, Marshaler>
7
+
8
+ // scalarMarshalers builds a name→marshal map for the route's params or search params by
9
+ // looking each declared GraphQL type up in the config's custom scalars. Names without a
10
+ // custom-scalar marshal function are omitted, so callers fall back to String(value).
11
+ // Kept pure (scalars are passed in) so it can be unit-tested without the runtime config.
12
+ export function scalarMarshalers(
13
+ defs: ReadonlyArray<{ name: string; type?: string }> | undefined,
14
+ scalars: Record<string, { marshal?: Marshaler } | undefined> | undefined
15
+ ): Marshalers {
16
+ const out: Marshalers = {}
17
+ for (const def of defs ?? []) {
18
+ const marshal = def.type ? scalars?.[def.type]?.marshal : undefined
19
+ if (marshal) {
20
+ out[def.name] = marshal
21
+ }
22
+ }
23
+ return out
24
+ }
25
+
26
+ // marshalValue applies the per-key marshaler (if any) and stringifies the result.
27
+ function marshalValue(value: unknown, marshal?: Marshaler): string {
28
+ return String(marshal ? marshal(value) : value)
29
+ }
30
+
31
+ // An unmarshaler is the inverse of a Marshaler: it turns the transport form a URL carries
32
+ // back into a rich runtime value (e.g. a timestamp string into a Date). The shared
33
+ // coercion core (houdini/runtime) owns the type and the per-leaf logic.
34
+ type Unmarshalers = Record<string, Unmarshaler>
35
+
36
+ // scalarUnmarshalers is the read-side mirror of scalarMarshalers: a name→unmarshal map for
37
+ // the declared params/search params whose GraphQL type is a custom scalar. Names without a
38
+ // custom-scalar unmarshal function are omitted (built-ins and UI-only keys are left as-is).
39
+ export function scalarUnmarshalers(
40
+ defs: ReadonlyArray<{ name: string; type?: string }> | undefined,
41
+ scalars: Record<string, { unmarshal?: Unmarshaler } | undefined> | undefined
42
+ ): Unmarshalers {
43
+ const out: Unmarshalers = {}
44
+ for (const def of defs ?? []) {
45
+ const unmarshal = def.type ? scalars?.[def.type]?.unmarshal : undefined
46
+ if (unmarshal) {
47
+ out[def.name] = unmarshal
48
+ }
49
+ }
50
+ return out
51
+ }
52
+
53
+ // unmarshalScalars turns the transport values a parsed query string carries back into rich
54
+ // runtime values for every key that has a custom-scalar unmarshaler. Keys without one
55
+ // (built-ins, UI-only keys) pass through untouched; List values are unmarshaled
56
+ // element-wise (via the shared coercion core). Used for both useRoute().search and the
57
+ // query variables the router feeds back into marshalInputs, so a custom-scalar search
58
+ // param round-trips correctly. When there's nothing to unmarshal the input object is
59
+ // returned as-is (no allocation).
60
+ export function unmarshalScalars(
61
+ values: Record<string, any>,
62
+ unmarshalers: Unmarshalers
63
+ ): Record<string, any> {
64
+ if (Object.keys(unmarshalers).length === 0) {
65
+ return values
66
+ }
67
+ const out: Record<string, any> = { ...values }
68
+ for (const [key, unmarshal] of Object.entries(unmarshalers)) {
69
+ if (!(key in out) || out[key] == null) {
70
+ continue
71
+ }
72
+ out[key] = unmarshalValue(out[key], unmarshal)
73
+ }
74
+ return out
75
+ }
76
+
77
+ // the per-route info buildHref needs to marshal: the declared param and search types.
78
+ export type RouteHrefInfo = {
79
+ params?: ReadonlyArray<{ name: string; type?: string }>
80
+ searchParams?: ReadonlyArray<{ name: string; type?: string }>
81
+ }
82
+
83
+ // buildHref assembles a full href for a route: it fills the path params and appends the
84
+ // search string, marshaling custom-scalar values in both so the URL always carries their
85
+ // transport form. This is the one place params + search become a URL, shared by <Link>
86
+ // and goto so they behave identically. `route` provides the type info (undefined for an
87
+ // external href, which is returned untouched).
88
+ export function buildHref(
89
+ to: string,
90
+ route: RouteHrefInfo | undefined,
91
+ scalars: Record<string, { marshal?: Marshaler } | undefined> | undefined,
92
+ params?: Record<string, unknown>,
93
+ search?: Record<string, unknown>
94
+ ): string {
95
+ let href =
96
+ params != null ? resolveHref(to, params, scalarMarshalers(route?.params, scalars)) : to
97
+ if (search != null) {
98
+ href += serializeSearch(search, scalarMarshalers(route?.searchParams, scalars))
99
+ }
100
+ return href
101
+ }
102
+
103
+ export function resolveHref(
104
+ href: string,
105
+ params: Record<string, unknown>,
106
+ marshalers?: Marshalers
107
+ ): string {
108
+ // renders a present param value through its marshaler, or undefined when absent
109
+ const render = (key: string): string | undefined => {
110
+ const val = params[key]
111
+ if (val === undefined || val === null) {
112
+ return undefined
113
+ }
114
+ return marshalValue(val, marshalers?.[key])
115
+ }
116
+ // optional [[param]] — strip the whole /[[param]] segment when the value is absent
117
+ href = href.replace(/\/\[\[([^\]]+)\]\]/g, (_, key: string) => {
118
+ const val = render(key)
119
+ return val !== undefined ? '/' + val : ''
120
+ })
121
+ // rest [...slug] — substitute [..slug] with the value (or empty string when absent)
122
+ href = href.replace(/\[\.\.\.([^\]]+)\]/g, (_, key: string) => render(key) ?? '')
123
+ // regular [param]
124
+ return href.replace(/\[([^\]]+)\]/g, (_, key: string) => render(key) ?? key)
125
+ }
126
+
127
+ // serializeSearch turns a search object into a query string (including the leading
128
+ // "?"), skipping null/undefined values and expanding arrays into repeated keys so
129
+ // they round-trip with List-typed query variables. A custom-scalar value is run
130
+ // through its marshaler so the URL holds the transport form. Returns "" when nothing
131
+ // is set.
132
+ export function serializeSearch(search: Record<string, unknown>, marshalers?: Marshalers): string {
133
+ const params = new URLSearchParams()
134
+ for (const [key, value] of Object.entries(search)) {
135
+ if (value === null || value === undefined) {
136
+ continue
137
+ }
138
+ const marshal = marshalers?.[key]
139
+ if (Array.isArray(value)) {
140
+ for (const entry of value) {
141
+ if (entry !== null && entry !== undefined) {
142
+ params.append(key, marshalValue(entry, marshal))
143
+ }
144
+ }
145
+ } else {
146
+ params.append(key, marshalValue(value, marshal))
147
+ }
148
+ }
149
+ const str = params.toString()
150
+ return str ? '?' + str : ''
151
+ }
@@ -0,0 +1,93 @@
1
+ // this file is part of houdini's generated runtime — do not edit
2
+ //
3
+ // It is the single source of per-route typing: the types here are derived from the
4
+ // generated manifest's shape and drive <Link>, goto(), and createMock so the rules for
5
+ // "which params/search does this route accept" live in exactly one place.
6
+
7
+ // @ts-ignore
8
+ import type rawManifest from './manifest.js'
9
+ // @ts-ignore
10
+ import type { _TSType } from './manifest.js'
11
+
12
+ type _Pages = (typeof rawManifest)['pages']
13
+
14
+ // ---- route params ----
15
+
16
+ type _Param = { readonly name: string; readonly type: string; readonly optional: boolean }
17
+ type _ParamObj<Ps extends readonly _Param[]> = {
18
+ [P in Ps[number] as P['optional'] extends true ? P['name'] : never]?: _TSType<P['type']>
19
+ } & {
20
+ [P in Ps[number] as P['optional'] extends true ? never : P['name']]: _TSType<P['type']>
21
+ }
22
+
23
+ // ---- search params ----
24
+
25
+ // Search params are derived from a query's nullable variables, so every key is
26
+ // optional. A `List`-wrapped variable accepts an array (serialized as repeated keys).
27
+ type _SearchParam = {
28
+ readonly name: string
29
+ readonly type: string
30
+ readonly wrappers: readonly string[]
31
+ }
32
+ type _SearchValue<P extends _SearchParam> = 'List' extends P['wrappers'][number]
33
+ ? _TSType<P['type']>[]
34
+ : _TSType<P['type']>
35
+ // The route's nullable query variables get precise types, but search isn't limited to
36
+ // them: extra keys are allowed so the query string can also carry UI-only state that no
37
+ // query reads (a selected tab, an open modal, a client-side sort). The query consumes
38
+ // only the declared keys; the rest just ride along in the URL.
39
+ type _SearchObj<Ps extends readonly _SearchParam[]> = {
40
+ [P in Ps[number] as P['name']]?: _SearchValue<P>
41
+ } & Record<string, unknown>
42
+
43
+ // ---- public route typing ----
44
+
45
+ // All known app route URL strings — useful as a constraint for custom link wrappers.
46
+ export type RouteHrefs = _Pages[keyof _Pages] extends { readonly url: infer U extends string }
47
+ ? U
48
+ : never
49
+
50
+ // hrefs that aren't app routes: external links, mailto/tel, fragments, relative paths.
51
+ export type ExternalHref =
52
+ | `http://${string}`
53
+ | `https://${string}`
54
+ | `mailto:${string}`
55
+ | `tel:${string}`
56
+ | `blob:${string}`
57
+ | `data:${string}`
58
+ | `//${string}`
59
+ | `#${string}`
60
+ | `./${string}`
61
+ | `../${string}`
62
+
63
+ type _PageForRoute<H extends string> = Extract<_Pages[keyof _Pages], { readonly url: H }>
64
+
65
+ // params is required (and typed) when the route has dynamic segments, and absent
66
+ // otherwise. Kept a separate intersection so TS evaluates `to` independently — route
67
+ // completions still include parameterized routes even before params is filled in.
68
+ export type ParamsForRoute<H extends string> = [_PageForRoute<H>] extends [never]
69
+ ? { params?: never }
70
+ : _PageForRoute<H> extends { readonly params: readonly [] }
71
+ ? { params?: never }
72
+ : _PageForRoute<H> extends { readonly params: infer Ps extends readonly _Param[] }
73
+ ? { params: _ParamObj<Ps> }
74
+ : { params?: never }
75
+
76
+ // search is always optional and always open (any route can carry UI-only query string
77
+ // state). When the route declares nullable query variables, those keys get precise
78
+ // types; otherwise search is just an open record of serializable values.
79
+ export type SearchForRoute<H extends string> = [_PageForRoute<H>] extends [never]
80
+ ? { search?: Record<string, unknown> }
81
+ : _PageForRoute<H> extends { readonly searchParams: infer Ps extends readonly _SearchParam[] }
82
+ ? { search?: _SearchObj<Ps> }
83
+ : { search?: Record<string, unknown> }
84
+
85
+ // a typed navigation target: a known route plus its (typed) params and search. Used by
86
+ // goto(); <Link> intersects the same pieces onto its anchor props.
87
+ export type NavTarget<H extends RouteHrefs> = { to: H } & ParamsForRoute<H> & SearchForRoute<H>
88
+
89
+ // goto() accepts either a ready-made url string (escape hatch) or a typed NavTarget.
90
+ export interface Goto {
91
+ (url: string): void
92
+ <H extends RouteHrefs>(target: NavTarget<H>): void
93
+ }