houdini-react 2.0.0-next.8 → 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 -90125
- package/build/plugin-cjs/package.json +0 -1
- package/build/plugin-esm/index.js +0 -90121
- 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,57 @@
|
|
|
1
|
+
// a suspense cache is an object that maintains a key-value store of
|
|
2
|
+
// objects. If a value is missing when get() is called, a promise
|
|
3
|
+
// is thrown that resolves when a value is passed to set()
|
|
4
|
+
import { LRUCache } from 'houdini/runtime'
|
|
5
|
+
|
|
6
|
+
export function suspense_cache<T>(initialData?: Record<string, T>): SuspenseCache<T> {
|
|
7
|
+
const cache = new SuspenseCache<T>()
|
|
8
|
+
|
|
9
|
+
for (const [key, value] of Object.entries(initialData ?? {})) {
|
|
10
|
+
cache.set(key, value)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return cache
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SuspenseCache<_Data> extends LRUCache<_Data> {
|
|
17
|
+
// if get is called before set, we need to invoke a callback.
|
|
18
|
+
// that means we need a place to put our callbacks
|
|
19
|
+
#callbacks: Map<string, { resolve: () => void; reject: () => void }[]> = new Map()
|
|
20
|
+
|
|
21
|
+
get(key: string): _Data {
|
|
22
|
+
// if there is a value, use that
|
|
23
|
+
if (super.has(key)) {
|
|
24
|
+
return super.get(key)!
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// we don't have a value, so we need to throw a promise
|
|
28
|
+
// that resolves when a value is passed to set()
|
|
29
|
+
throw new Promise<void>((resolve, reject) => {
|
|
30
|
+
this.#subscribe(key, resolve, reject)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override clear() {
|
|
35
|
+
super.clear()
|
|
36
|
+
this.#callbacks.clear()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set(key: string, value: _Data) {
|
|
40
|
+
// perform the set like normal
|
|
41
|
+
super.set(key, value)
|
|
42
|
+
|
|
43
|
+
// if there are subscribers, resolve them
|
|
44
|
+
if (this.#callbacks.has(key)) {
|
|
45
|
+
// resolve all of the callbacks
|
|
46
|
+
this.#callbacks.get(key)?.forEach(({ resolve }) => {
|
|
47
|
+
resolve()
|
|
48
|
+
})
|
|
49
|
+
// delete the key
|
|
50
|
+
this.#callbacks.delete(key)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#subscribe(key: string, resolve: () => void, reject: () => void) {
|
|
55
|
+
this.#callbacks.set(key, [...(this.#callbacks.get(key) || []), { resolve, reject }])
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { GraphQLError } from 'houdini/runtime'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export class GraphQLErrors extends Error {
|
|
5
|
+
graphqlErrors: GraphQLError[]
|
|
6
|
+
|
|
7
|
+
constructor(errors: GraphQLError[]) {
|
|
8
|
+
super(errors.map((e) => e.message).join('\n'))
|
|
9
|
+
this.name = 'GraphQLErrors'
|
|
10
|
+
this.graphqlErrors = errors
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let _currentSegment: string | undefined
|
|
15
|
+
|
|
16
|
+
export function setCurrentSegment(id: string | undefined): void {
|
|
17
|
+
_currentSegment = id
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCurrentSegment(): string | undefined {
|
|
21
|
+
return _currentSegment
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class RoutingError extends Error {
|
|
25
|
+
status: number
|
|
26
|
+
segment: string | undefined
|
|
27
|
+
|
|
28
|
+
constructor(status: number) {
|
|
29
|
+
super(`Routing error: ${status}`)
|
|
30
|
+
this.name = 'RoutingError'
|
|
31
|
+
this.status = status
|
|
32
|
+
this.segment = getCurrentSegment()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class RedirectError extends Error {
|
|
37
|
+
status: number
|
|
38
|
+
location: string
|
|
39
|
+
|
|
40
|
+
constructor(status: number, location: string) {
|
|
41
|
+
super(`Redirect: ${status} ${location}`)
|
|
42
|
+
this.name = 'RedirectError'
|
|
43
|
+
this.status = status
|
|
44
|
+
this.location = location
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isRoutingError(error: unknown): error is RoutingError {
|
|
49
|
+
return error instanceof RoutingError
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isApiError(error: unknown): error is GraphQLErrors {
|
|
53
|
+
return error instanceof GraphQLErrors
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function notFound(): never {
|
|
57
|
+
throw new RoutingError(404)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function unauthorized(): never {
|
|
61
|
+
throw new RoutingError(401)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function forbidden(): never {
|
|
65
|
+
throw new RoutingError(403)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function httpError(status: number): never {
|
|
69
|
+
throw new RoutingError(status)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function redirect(status: 300 | 301 | 302 | 303 | 307 | 308, location: string): never {
|
|
73
|
+
throw new RedirectError(status, location)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Mutable ref passed from the server renderer so that a synchronous RoutingError
|
|
77
|
+
// or redirect() can propagate the correct HTTP status/location before streaming.
|
|
78
|
+
export const StatusContext = React.createContext<{ status: number; location?: string } | null>(null)
|
|
79
|
+
|
|
80
|
+
type HoudiniErrorBoundaryProps = {
|
|
81
|
+
errorView: React.ComponentType<{
|
|
82
|
+
errors: Array<Error | GraphQLError>
|
|
83
|
+
children: React.ReactNode
|
|
84
|
+
}>
|
|
85
|
+
children: React.ReactNode
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type HoudiniErrorBoundaryState = {
|
|
89
|
+
hasError: boolean
|
|
90
|
+
errors: Array<Error | GraphQLError>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class HoudiniErrorBoundary extends React.Component<
|
|
94
|
+
HoudiniErrorBoundaryProps,
|
|
95
|
+
HoudiniErrorBoundaryState
|
|
96
|
+
> {
|
|
97
|
+
static contextType = StatusContext
|
|
98
|
+
declare context: React.ContextType<typeof StatusContext>
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
props: HoudiniErrorBoundaryProps,
|
|
102
|
+
context: React.ContextType<typeof StatusContext>
|
|
103
|
+
) {
|
|
104
|
+
super(props, context)
|
|
105
|
+
// Second-pass SSR: statusRef is pre-set to an error status by on_render after the first
|
|
106
|
+
// render threw. Start in error state immediately so children never render (and never throw).
|
|
107
|
+
if (typeof window === 'undefined' && context && context.status >= 400) {
|
|
108
|
+
this.state = {
|
|
109
|
+
hasError: true,
|
|
110
|
+
errors: [new RoutingError(context.status)],
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
this.state = { hasError: false, errors: [] }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static getDerivedStateFromError(error: unknown): HoudiniErrorBoundaryState {
|
|
118
|
+
if (error instanceof GraphQLErrors) {
|
|
119
|
+
return { hasError: true, errors: error.graphqlErrors }
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
hasError: true,
|
|
123
|
+
errors: [error instanceof Error ? error : new Error(String(error))],
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
componentDidCatch(error: Error): void {
|
|
128
|
+
if (this.context) {
|
|
129
|
+
if (error instanceof RoutingError) {
|
|
130
|
+
this.context.status = error.status
|
|
131
|
+
} else if (error instanceof RedirectError) {
|
|
132
|
+
this.context.status = error.status
|
|
133
|
+
this.context.location = error.location
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
render() {
|
|
139
|
+
if (this.state.hasError) {
|
|
140
|
+
const ErrorView = this.props.errorView
|
|
141
|
+
return <ErrorView errors={this.state.errors}>{this.props.children}</ErrorView>
|
|
142
|
+
}
|
|
143
|
+
return this.props.children
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './Router.js'
|
|
2
|
+
export { type SuspenseCache, suspense_cache } from './cache.js'
|
|
3
|
+
export {
|
|
4
|
+
HoudiniErrorBoundary,
|
|
5
|
+
GraphQLErrors,
|
|
6
|
+
RoutingError,
|
|
7
|
+
RedirectError,
|
|
8
|
+
notFound,
|
|
9
|
+
unauthorized,
|
|
10
|
+
forbidden,
|
|
11
|
+
httpError,
|
|
12
|
+
redirect,
|
|
13
|
+
isRoutingError,
|
|
14
|
+
isApiError,
|
|
15
|
+
StatusContext,
|
|
16
|
+
setCurrentSegment,
|
|
17
|
+
} from './errors.js'
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// @refresh reset
|
|
2
|
+
import { getCurrentConfig } from '$houdini/runtime/config'
|
|
3
|
+
import { Cache } from 'houdini/runtime/cache'
|
|
4
|
+
import { DataSource } from 'houdini/runtime/types'
|
|
5
|
+
import { HoudiniClient } from '$houdini/runtime/client'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
|
|
8
|
+
import { scalarMarshalers, serializeSearch } from './resolve-href.js'
|
|
9
|
+
import { Router as RouterImpl, RouterContextProvider, router_cache } from './routing/index.js'
|
|
10
|
+
import manifest from './manifest.js'
|
|
11
|
+
|
|
12
|
+
type MockValue =
|
|
13
|
+
| Record<string, unknown>
|
|
14
|
+
| ((vars: any) => Record<string, unknown>)
|
|
15
|
+
| AsyncIterable<Record<string, unknown>>
|
|
16
|
+
| ((vars: any) => AsyncIterable<Record<string, unknown>>)
|
|
17
|
+
|
|
18
|
+
// buildMockPath turns a route pattern, its params, and an optional search object into
|
|
19
|
+
// the concrete path a test wants to render. The typed createMock wrapper calls this so
|
|
20
|
+
// that param substitution (and its missing-param error) and search serialization both
|
|
21
|
+
// happen before the path reaches _createMock.
|
|
22
|
+
export function buildMockPath(
|
|
23
|
+
pattern: string,
|
|
24
|
+
params: Record<string, string>,
|
|
25
|
+
search?: Record<string, unknown>
|
|
26
|
+
): string {
|
|
27
|
+
if (!search) {
|
|
28
|
+
return buildURL(pattern, params)
|
|
29
|
+
}
|
|
30
|
+
// marshal custom-scalar search values the same way <Link> does, so tests exercise
|
|
31
|
+
// the real serialization path
|
|
32
|
+
const m = manifest as any
|
|
33
|
+
const page = m.pages[m.pagesByUrl[pattern]] as
|
|
34
|
+
| { searchParams?: ReadonlyArray<{ name: string; type: string }> }
|
|
35
|
+
| undefined
|
|
36
|
+
const marshalers = scalarMarshalers(page?.searchParams, getCurrentConfig()?.scalars)
|
|
37
|
+
return buildURL(pattern, params) + serializeSearch(search, marshalers)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function _createMock({
|
|
41
|
+
path,
|
|
42
|
+
data: mocks,
|
|
43
|
+
}: {
|
|
44
|
+
path: string
|
|
45
|
+
data: Record<string, MockValue>
|
|
46
|
+
}): React.ComponentType<{}> {
|
|
47
|
+
// the route patterns are anchored to the path, so strip any search string
|
|
48
|
+
// before matching
|
|
49
|
+
const pathname = path.split('?')[0]
|
|
50
|
+
|
|
51
|
+
// Validate required mocks up-front at the call site so the error points
|
|
52
|
+
// directly to the createMock() call rather than surfacing during rendering.
|
|
53
|
+
const pages = manifest.pages as Record<
|
|
54
|
+
string,
|
|
55
|
+
{ pattern: RegExp; documents: Record<string, unknown> }
|
|
56
|
+
>
|
|
57
|
+
const page = Object.values(pages).find((p) => p.pattern.test(pathname))
|
|
58
|
+
if (page) {
|
|
59
|
+
const missing = Object.keys(page.documents).filter((name) => !(name in mocks))
|
|
60
|
+
if (missing.length > 0) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`createMock: missing mock data for ${missing.map((n) => `"${n}"`).join(', ')} on route "${pathname}". Add ${missing.length === 1 ? 'it' : 'them'} to the data object passed to createMock.`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cache = new Cache(getCurrentConfig())
|
|
68
|
+
|
|
69
|
+
const mockPlugin = () => ({
|
|
70
|
+
network(ctx: any, { resolve }: any) {
|
|
71
|
+
const mock = mocks[ctx.artifact.name]
|
|
72
|
+
if (mock === undefined) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`createMock: "${ctx.artifact.name}" fired but was not in data. Add it to the data object passed to createMock.`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (ctx.artifact.kind === 'HoudiniSubscription') {
|
|
79
|
+
const iterable =
|
|
80
|
+
typeof mock === 'function'
|
|
81
|
+
? (mock as (v: any) => AsyncIterable<Record<string, unknown>>)(
|
|
82
|
+
ctx.variables ?? {}
|
|
83
|
+
)
|
|
84
|
+
: (mock as AsyncIterable<Record<string, unknown>>)
|
|
85
|
+
const iterator = iterable[Symbol.asyncIterator]()
|
|
86
|
+
ctx.abortController.signal.addEventListener(
|
|
87
|
+
'abort',
|
|
88
|
+
() => {
|
|
89
|
+
iterator.return?.()
|
|
90
|
+
},
|
|
91
|
+
{ once: true }
|
|
92
|
+
)
|
|
93
|
+
;(async () => {
|
|
94
|
+
for await (const data of { [Symbol.asyncIterator]: () => iterator }) {
|
|
95
|
+
if (ctx.abortController.signal.aborted) break
|
|
96
|
+
resolve(ctx, {
|
|
97
|
+
data,
|
|
98
|
+
errors: null,
|
|
99
|
+
fetching: false,
|
|
100
|
+
variables: null,
|
|
101
|
+
source: DataSource.Network,
|
|
102
|
+
partial: false,
|
|
103
|
+
stale: false,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
})()
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const data = typeof mock === 'function' ? mock(ctx.variables ?? {}) : mock
|
|
111
|
+
resolve(ctx, {
|
|
112
|
+
data,
|
|
113
|
+
errors: null,
|
|
114
|
+
fetching: false,
|
|
115
|
+
variables: null,
|
|
116
|
+
source: DataSource.Network,
|
|
117
|
+
partial: false,
|
|
118
|
+
stale: false,
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// Pass the same fresh cache to the client so the cache policy plugin doesn't
|
|
124
|
+
// reach into the global cacheRef singleton and return stale data from previous tests.
|
|
125
|
+
// the mockPlugin intercepts the network, so no url is needed (and the client no longer takes one)
|
|
126
|
+
const mockClient = new HoudiniClient({
|
|
127
|
+
plugins: [mockPlugin as any],
|
|
128
|
+
cache,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return function MockApp() {
|
|
132
|
+
const cachesRef = React.useRef<ReturnType<typeof router_cache> | null>(null)
|
|
133
|
+
if (cachesRef.current === null) {
|
|
134
|
+
cachesRef.current = router_cache()
|
|
135
|
+
}
|
|
136
|
+
const caches = cachesRef.current
|
|
137
|
+
return (
|
|
138
|
+
<RouterContextProvider
|
|
139
|
+
client={mockClient}
|
|
140
|
+
cache={cache}
|
|
141
|
+
artifact_cache={caches.artifact_cache}
|
|
142
|
+
component_cache={caches.component_cache}
|
|
143
|
+
data_cache={caches.data_cache}
|
|
144
|
+
ssr_signals={caches.ssr_signals}
|
|
145
|
+
last_variables={caches.last_variables}
|
|
146
|
+
>
|
|
147
|
+
<React.Suspense fallback={null}>
|
|
148
|
+
<RouterImpl manifest={manifest} initialURL={path} assetPrefix="" />
|
|
149
|
+
</React.Suspense>
|
|
150
|
+
</RouterContextProvider>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildURL(pattern: string, params: Record<string, string>): string {
|
|
156
|
+
return pattern.replace(/\[([^\]]+)\]/g, (_, key) => {
|
|
157
|
+
const value = params[key]
|
|
158
|
+
if (value === undefined) {
|
|
159
|
+
throw new Error(`createMock: missing param "${key}" for pattern "${pattern}"`)
|
|
160
|
+
}
|
|
161
|
+
return String(value)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".",
|
|
4
|
+
"paths": {
|
|
5
|
+
"$houdini": ["."],
|
|
6
|
+
"$houdini/*": ["./*"],
|
|
7
|
+
"~": ["../src"],
|
|
8
|
+
"~/*": ["../src/*"]
|
|
9
|
+
},
|
|
10
|
+
"rootDirs": ["..", "./types"],
|
|
11
|
+
"target": "ESNext",
|
|
12
|
+
"useDefineForClassFields": true,
|
|
13
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
14
|
+
"allowJs": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"esModuleInterop": false,
|
|
17
|
+
"allowSyntheticDefaultImports": true,
|
|
18
|
+
"strict": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"module": "ESNext",
|
|
21
|
+
"moduleResolution": "Bundler",
|
|
22
|
+
"allowImportingTsExtensions": true,
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
"isolatedModules": true,
|
|
25
|
+
"noEmit": true,
|
|
26
|
+
"jsx": "react-jsx"
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"ambient.d.ts",
|
|
30
|
+
"./types/**/$types.d.ts",
|
|
31
|
+
"../vite.config.ts",
|
|
32
|
+
"../src/**/*.js",
|
|
33
|
+
"../src/**/*.ts",
|
|
34
|
+
"../src/**/*.jsx",
|
|
35
|
+
"../src/**/*.tsx",
|
|
36
|
+
"../src/+app.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": ["../node_modules/**", "./[!ambient.d.ts]**"]
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderToStream } from 'react-streaming/server';
|
package/server/index.js
ADDED
|
File without changes
|
package/vite/index.d.ts
ADDED