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.
- package/bin/houdini-react +173 -0
- package/package.json +63 -39
- package/postInstall.js +360 -0
- package/runtime/Link.tsx +50 -0
- package/runtime/client.ts +5 -0
- package/runtime/clientPlugin.ts +17 -0
- package/runtime/componentFields.ts +79 -0
- package/runtime/escape.ts +15 -0
- package/runtime/hooks/index.ts +17 -0
- package/runtime/hooks/recycleNodesInto.ts +60 -0
- package/runtime/hooks/useDeepCompareEffect.ts +93 -0
- package/runtime/hooks/useDocumentHandle.ts +243 -0
- package/runtime/hooks/useDocumentStore.ts +112 -0
- package/runtime/hooks/useDocumentSubscription.ts +65 -0
- package/runtime/hooks/useFragment.ts +269 -0
- package/runtime/hooks/useFragmentHandle.ts +274 -0
- package/runtime/hooks/useIsMounted.ts +14 -0
- package/runtime/hooks/useLogoutForm.tsx +60 -0
- package/runtime/hooks/useMutation.ts +80 -0
- package/runtime/hooks/useMutationForm.tsx +163 -0
- package/runtime/hooks/useQuery.ts +12 -0
- package/runtime/hooks/useQueryHandle.ts +194 -0
- package/runtime/hooks/useSubscription.ts +12 -0
- package/runtime/hooks/useSubscriptionHandle.ts +42 -0
- package/runtime/hydration.tsx +186 -0
- package/runtime/index.tsx +77 -0
- package/runtime/login.ts +22 -0
- package/runtime/manifest.ts +6 -0
- package/runtime/mock.ts +9 -0
- package/runtime/package.json +1 -0
- package/runtime/resolve-href.ts +151 -0
- package/runtime/routes.ts +93 -0
- package/runtime/routing/Router.tsx +1101 -0
- package/runtime/routing/cache.ts +57 -0
- package/runtime/routing/errors.tsx +145 -0
- package/runtime/routing/index.ts +17 -0
- package/runtime/testing.tsx +163 -0
- package/runtime/tsconfig.json +39 -0
- package/server/index.d.ts +1 -0
- package/server/index.js +4 -0
- package/server/react-streaming.d.js +0 -0
- package/vite/index.d.ts +3 -0
- package/vite/index.js +397 -0
- package/vite/strip-headers.d.ts +1 -0
- package/vite/strip-headers.js +35 -0
- package/vite/transform.d.ts +13 -0
- package/vite/transform.js +105 -0
- package/README.md +0 -36
- package/build/plugin/codegen/entries/documentWrappers.d.ts +0 -6
- package/build/plugin/codegen/entries/fallbacks.d.ts +0 -5
- package/build/plugin/codegen/entries/index.d.ts +0 -16
- package/build/plugin/codegen/entries/pages.d.ts +0 -2
- package/build/plugin/codegen/index.d.ts +0 -17
- package/build/plugin/codegen/manifest.d.ts +0 -5
- package/build/plugin/codegen/render.d.ts +0 -7
- package/build/plugin/codegen/router.d.ts +0 -7
- package/build/plugin/codegen/typeRoot.d.ts +0 -5
- package/build/plugin/config.d.ts +0 -4
- package/build/plugin/dedent.d.ts +0 -1
- package/build/plugin/extract.d.ts +0 -6
- package/build/plugin/index.d.ts +0 -5
- package/build/plugin/state.d.ts +0 -3
- package/build/plugin/transform.d.ts +0 -6
- package/build/plugin/vite.d.ts +0 -27
- package/build/plugin-cjs/index.js +0 -90119
- package/build/plugin-cjs/package.json +0 -1
- package/build/plugin-esm/index.js +0 -90115
- package/build/runtime/client.d.ts +0 -3
- package/build/runtime/clientPlugin.d.ts +0 -3
- package/build/runtime/componentFields.d.ts +0 -9
- package/build/runtime/hooks/index.d.ts +0 -8
- package/build/runtime/hooks/useDeepCompareEffect.d.ts +0 -35
- package/build/runtime/hooks/useDocumentHandle.d.ts +0 -36
- package/build/runtime/hooks/useDocumentStore.d.ts +0 -11
- package/build/runtime/hooks/useDocumentSubscription.d.ts +0 -11
- package/build/runtime/hooks/useFragment.d.ts +0 -16
- package/build/runtime/hooks/useFragmentHandle.d.ts +0 -8
- package/build/runtime/hooks/useIsMounted.d.ts +0 -3
- package/build/runtime/hooks/useMutation.d.ts +0 -14
- package/build/runtime/hooks/useQuery.d.ts +0 -5
- package/build/runtime/hooks/useQueryHandle.d.ts +0 -10
- package/build/runtime/hooks/useSubscription.d.ts +0 -4
- package/build/runtime/hooks/useSubscriptionHandle.d.ts +0 -25
- package/build/runtime/index.d.ts +0 -14
- package/build/runtime/manifest.d.ts +0 -3
- package/build/runtime/routing/Router.d.ts +0 -62
- package/build/runtime/routing/cache.d.ts +0 -7
- package/build/runtime/routing/hooks.d.ts +0 -40
- package/build/runtime/routing/index.d.ts +0 -3
- package/build/runtime-cjs/client.d.ts +0 -3
- package/build/runtime-cjs/client.js +0 -25
- package/build/runtime-cjs/clientPlugin.d.ts +0 -3
- package/build/runtime-cjs/clientPlugin.js +0 -37
- package/build/runtime-cjs/componentFields.d.ts +0 -9
- package/build/runtime-cjs/componentFields.js +0 -83
- package/build/runtime-cjs/hooks/index.d.ts +0 -8
- package/build/runtime-cjs/hooks/index.js +0 -45
- package/build/runtime-cjs/hooks/useDeepCompareEffect.d.ts +0 -35
- package/build/runtime-cjs/hooks/useDeepCompareEffect.js +0 -76
- package/build/runtime-cjs/hooks/useDocumentHandle.d.ts +0 -36
- package/build/runtime-cjs/hooks/useDocumentHandle.js +0 -177
- package/build/runtime-cjs/hooks/useDocumentStore.d.ts +0 -11
- package/build/runtime-cjs/hooks/useDocumentStore.js +0 -76
- package/build/runtime-cjs/hooks/useDocumentSubscription.d.ts +0 -11
- package/build/runtime-cjs/hooks/useDocumentSubscription.js +0 -76
- package/build/runtime-cjs/hooks/useFragment.d.ts +0 -16
- package/build/runtime-cjs/hooks/useFragment.js +0 -102
- package/build/runtime-cjs/hooks/useFragmentHandle.d.ts +0 -8
- package/build/runtime-cjs/hooks/useFragmentHandle.js +0 -47
- package/build/runtime-cjs/hooks/useIsMounted.d.ts +0 -3
- package/build/runtime-cjs/hooks/useIsMounted.js +0 -38
- package/build/runtime-cjs/hooks/useMutation.d.ts +0 -14
- package/build/runtime-cjs/hooks/useMutation.js +0 -67
- package/build/runtime-cjs/hooks/useQuery.d.ts +0 -5
- package/build/runtime-cjs/hooks/useQuery.js +0 -32
- package/build/runtime-cjs/hooks/useQueryHandle.d.ts +0 -10
- package/build/runtime-cjs/hooks/useQueryHandle.js +0 -131
- package/build/runtime-cjs/hooks/useSubscription.d.ts +0 -4
- package/build/runtime-cjs/hooks/useSubscription.js +0 -32
- package/build/runtime-cjs/hooks/useSubscriptionHandle.d.ts +0 -25
- package/build/runtime-cjs/hooks/useSubscriptionHandle.js +0 -42
- package/build/runtime-cjs/index.d.ts +0 -14
- package/build/runtime-cjs/index.js +0 -88
- package/build/runtime-cjs/manifest.d.ts +0 -3
- package/build/runtime-cjs/manifest.js +0 -25
- package/build/runtime-cjs/package.json +0 -1
- package/build/runtime-cjs/routing/Router.d.ts +0 -62
- package/build/runtime-cjs/routing/Router.js +0 -540
- package/build/runtime-cjs/routing/cache.d.ts +0 -7
- package/build/runtime-cjs/routing/cache.js +0 -61
- package/build/runtime-cjs/routing/hooks.d.ts +0 -40
- package/build/runtime-cjs/routing/hooks.js +0 -93
- package/build/runtime-cjs/routing/index.d.ts +0 -3
- package/build/runtime-cjs/routing/index.js +0 -33
- package/build/runtime-esm/client.d.ts +0 -3
- package/build/runtime-esm/client.js +0 -5
- package/build/runtime-esm/clientPlugin.d.ts +0 -3
- package/build/runtime-esm/clientPlugin.js +0 -17
- package/build/runtime-esm/componentFields.d.ts +0 -9
- package/build/runtime-esm/componentFields.js +0 -59
- package/build/runtime-esm/hooks/index.d.ts +0 -8
- package/build/runtime-esm/hooks/index.js +0 -15
- package/build/runtime-esm/hooks/useDeepCompareEffect.d.ts +0 -35
- package/build/runtime-esm/hooks/useDeepCompareEffect.js +0 -41
- package/build/runtime-esm/hooks/useDocumentHandle.d.ts +0 -36
- package/build/runtime-esm/hooks/useDocumentHandle.js +0 -143
- package/build/runtime-esm/hooks/useDocumentStore.d.ts +0 -11
- package/build/runtime-esm/hooks/useDocumentStore.js +0 -42
- package/build/runtime-esm/hooks/useDocumentSubscription.d.ts +0 -11
- package/build/runtime-esm/hooks/useDocumentSubscription.js +0 -42
- package/build/runtime-esm/hooks/useFragment.d.ts +0 -16
- package/build/runtime-esm/hooks/useFragment.js +0 -67
- package/build/runtime-esm/hooks/useFragmentHandle.d.ts +0 -8
- package/build/runtime-esm/hooks/useFragmentHandle.js +0 -23
- package/build/runtime-esm/hooks/useIsMounted.d.ts +0 -3
- package/build/runtime-esm/hooks/useIsMounted.js +0 -14
- package/build/runtime-esm/hooks/useMutation.d.ts +0 -14
- package/build/runtime-esm/hooks/useMutation.js +0 -42
- package/build/runtime-esm/hooks/useQuery.d.ts +0 -5
- package/build/runtime-esm/hooks/useQuery.js +0 -8
- package/build/runtime-esm/hooks/useQueryHandle.d.ts +0 -10
- package/build/runtime-esm/hooks/useQueryHandle.js +0 -97
- package/build/runtime-esm/hooks/useSubscription.d.ts +0 -4
- package/build/runtime-esm/hooks/useSubscription.js +0 -8
- package/build/runtime-esm/hooks/useSubscriptionHandle.d.ts +0 -25
- package/build/runtime-esm/hooks/useSubscriptionHandle.js +0 -18
- package/build/runtime-esm/index.d.ts +0 -14
- package/build/runtime-esm/index.js +0 -48
- package/build/runtime-esm/manifest.d.ts +0 -3
- package/build/runtime-esm/manifest.js +0 -5
- package/build/runtime-esm/routing/Router.d.ts +0 -62
- package/build/runtime-esm/routing/Router.js +0 -499
- package/build/runtime-esm/routing/cache.d.ts +0 -7
- package/build/runtime-esm/routing/cache.js +0 -36
- package/build/runtime-esm/routing/hooks.d.ts +0 -40
- package/build/runtime-esm/routing/hooks.js +0 -53
- package/build/runtime-esm/routing/index.d.ts +0 -3
- package/build/runtime-esm/routing/index.js +0 -6
- package/build/server/index.d.ts +0 -1
- package/build/server-cjs/index.js +0 -28
- package/build/server-cjs/package.json +0 -1
- package/build/server-esm/index.js +0 -4
- package/build/server-esm/package.json +0 -1
- /package/{build/plugin-esm → server}/package.json +0 -0
- /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
|
+
}
|
package/runtime/login.ts
ADDED
|
@@ -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
|
+
}
|
package/runtime/mock.ts
ADDED
|
@@ -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
|
+
}
|