Skip to content

Commit 5b67271

Browse files
authored
Merge pull request #5 from seamapi/hooks
Add hooks and provider
2 parents fb598f9 + 0b8c62e commit 5b67271

8 files changed

+928
-1
lines changed

src/lib/SeamQueryProvider.tsx

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import type {
2+
SeamHttp,
3+
SeamHttpEndpoints,
4+
SeamHttpOptionsWithClientSessionToken,
5+
} from '@seamapi/http/connect'
6+
import {
7+
QueryClient,
8+
QueryClientContext,
9+
QueryClientProvider,
10+
} from '@tanstack/react-query'
11+
import {
12+
createContext,
13+
type JSX,
14+
type PropsWithChildren,
15+
useContext,
16+
useEffect,
17+
useMemo,
18+
} from 'react'
19+
20+
import { useSeamClient } from './use-seam-client.js'
21+
22+
export interface SeamQueryContext {
23+
client: SeamHttp | null
24+
endpointClient: SeamHttpEndpoints | null
25+
clientOptions?: SeamQueryProviderClientOptions | undefined
26+
publishableKey?: string | undefined
27+
userIdentifierKey?: string | undefined
28+
clientSessionToken?: string | undefined
29+
consoleSessionToken?: string | undefined
30+
workspaceId?: string | undefined
31+
queryKeyPrefix?: string | undefined
32+
}
33+
34+
export type SeamQueryProviderProps =
35+
| SeamQueryProviderPropsWithClient
36+
| SeamQueryProviderPropsWithPublishableKey
37+
| SeamQueryProviderPropsWithClientSessionToken
38+
| SeamQueryProviderPropsWithConsoleSessionToken
39+
40+
export interface SeamQueryProviderPropsWithClient
41+
extends SeamQueryProviderBaseProps {
42+
client: SeamHttp
43+
queryKeyPrefix: string
44+
}
45+
46+
export interface SeamQueryProviderPropsWithPublishableKey
47+
extends SeamQueryProviderBaseProps,
48+
SeamQueryProviderClientOptions {
49+
publishableKey: string
50+
userIdentifierKey?: string
51+
}
52+
53+
export interface SeamQueryProviderPropsWithClientSessionToken
54+
extends SeamQueryProviderBaseProps,
55+
SeamQueryProviderClientOptions {
56+
clientSessionToken: string
57+
}
58+
59+
export interface SeamQueryProviderPropsWithConsoleSessionToken
60+
extends SeamQueryProviderBaseProps,
61+
SeamQueryProviderClientOptions {
62+
consoleSessionToken: string
63+
workspaceId?: string | undefined
64+
}
65+
66+
interface SeamQueryProviderBaseProps extends PropsWithChildren {
67+
queryClient?: QueryClient | undefined
68+
onSessionUpdate?: (client: SeamHttp) => void
69+
}
70+
71+
type SeamClientOptions = SeamHttpOptionsWithClientSessionToken
72+
73+
export type SeamQueryProviderClientOptions = Pick<
74+
SeamClientOptions,
75+
'endpoint' | 'isUndocumentedApiEnabled'
76+
>
77+
78+
const defaultQueryClient = new QueryClient()
79+
80+
export function SeamQueryProvider({
81+
children,
82+
onSessionUpdate = () => {},
83+
queryClient,
84+
...props
85+
}: SeamQueryProviderProps): JSX.Element {
86+
const value = useMemo(() => {
87+
const context = createSeamQueryContextValue(props)
88+
if (
89+
context.client == null &&
90+
context.publishableKey == null &&
91+
context.clientSessionToken == null &&
92+
context.consoleSessionToken == null
93+
) {
94+
return defaultSeamQueryContextValue
95+
}
96+
return context
97+
}, [props])
98+
99+
if (
100+
value.client == null &&
101+
value.publishableKey == null &&
102+
value.clientSessionToken == null &&
103+
value.consoleSessionToken == null
104+
) {
105+
throw new Error(
106+
`Must provide either a Seam client, clientSessionToken, publishableKey or consoleSessionToken.`,
107+
)
108+
}
109+
110+
const { Provider } = seamContext
111+
const queryClientFromContext = useContext(QueryClientContext)
112+
113+
if (
114+
queryClientFromContext != null &&
115+
queryClient != null &&
116+
queryClientFromContext !== queryClient
117+
) {
118+
throw new Error(
119+
'The QueryClient passed into SeamQueryProvider is different from the one in the existing QueryClientContext. Omit the queryClient prop from SeamProvider or SeamQueryProvider to use the existing QueryClient provided by the QueryClientProvider.',
120+
)
121+
}
122+
123+
return (
124+
<QueryClientProvider
125+
client={queryClientFromContext ?? queryClient ?? defaultQueryClient}
126+
>
127+
<Provider value={value}>
128+
<Session onSessionUpdate={onSessionUpdate}>{children}</Session>
129+
</Provider>
130+
</QueryClientProvider>
131+
)
132+
}
133+
134+
function Session({
135+
onSessionUpdate,
136+
children,
137+
}: Required<Pick<SeamQueryProviderProps, 'onSessionUpdate'>> &
138+
PropsWithChildren): JSX.Element | null {
139+
const { client } = useSeamClient()
140+
useEffect(() => {
141+
if (client != null) onSessionUpdate(client)
142+
}, [onSessionUpdate, client])
143+
144+
return <>{children}</>
145+
}
146+
147+
const createDefaultSeamQueryContextValue = (): SeamQueryContext => {
148+
return { client: null, endpointClient: null }
149+
}
150+
151+
const createSeamQueryContextValue = (
152+
options: SeamQueryProviderProps,
153+
): SeamQueryContext => {
154+
if (isSeamQueryProviderPropsWithClient(options)) {
155+
if (options.queryKeyPrefix == null) {
156+
throw new InvalidSeamQueryProviderProps(
157+
'The client prop must be used with a queryKeyPrefix prop.',
158+
)
159+
}
160+
return {
161+
...options,
162+
endpointClient: null,
163+
}
164+
}
165+
166+
if (isSeamQueryProviderPropsWithClientSessionToken(options)) {
167+
const { clientSessionToken, ...clientOptions } = options
168+
return {
169+
clientSessionToken,
170+
clientOptions,
171+
client: null,
172+
endpointClient: null,
173+
}
174+
}
175+
176+
if (isSeamQueryProviderPropsWithPublishableKey(options)) {
177+
const { publishableKey, userIdentifierKey, ...clientOptions } = options
178+
return {
179+
publishableKey,
180+
userIdentifierKey,
181+
clientOptions,
182+
client: null,
183+
endpointClient: null,
184+
}
185+
}
186+
187+
if (isSeamQueryProviderPropsWithConsoleSessionToken(options)) {
188+
const { consoleSessionToken, workspaceId, ...clientOptions } = options
189+
return {
190+
consoleSessionToken,
191+
workspaceId,
192+
clientOptions,
193+
client: null,
194+
endpointClient: null,
195+
}
196+
}
197+
198+
return { client: null, endpointClient: null }
199+
}
200+
201+
const defaultSeamQueryContextValue = createDefaultSeamQueryContextValue()
202+
203+
export const seamContext = createContext<SeamQueryContext>(
204+
defaultSeamQueryContextValue,
205+
)
206+
207+
export function useSeamQueryContext(): SeamQueryContext {
208+
return useContext(seamContext)
209+
}
210+
211+
const isSeamQueryProviderPropsWithClient = (
212+
props: SeamQueryProviderProps,
213+
): props is SeamQueryProviderPropsWithClient => {
214+
if (!('client' in props)) return false
215+
216+
const { client, ...otherProps } = props
217+
if (client == null) return false
218+
219+
const otherNonNullProps = Object.values(otherProps).filter((v) => v != null)
220+
if (otherNonNullProps.length > 0) {
221+
throw new InvalidSeamQueryProviderProps(
222+
`The client prop cannot be used with ${otherNonNullProps.join(' or ')}.`,
223+
)
224+
}
225+
226+
return true
227+
}
228+
229+
const isSeamQueryProviderPropsWithPublishableKey = (
230+
props: SeamQueryProviderProps,
231+
): props is SeamQueryProviderPropsWithPublishableKey &
232+
SeamQueryProviderClientOptions => {
233+
if (!('publishableKey' in props)) return false
234+
235+
const { publishableKey } = props
236+
if (publishableKey == null) return false
237+
238+
if ('client' in props && props.client != null) {
239+
throw new InvalidSeamQueryProviderProps(
240+
'The client prop cannot be used with the publishableKey prop.',
241+
)
242+
}
243+
244+
if ('clientSessionToken' in props && props.clientSessionToken != null) {
245+
throw new InvalidSeamQueryProviderProps(
246+
'The clientSessionToken prop cannot be used with the publishableKey prop.',
247+
)
248+
}
249+
250+
if ('consoleSessionToken' in props && props.consoleSessionToken != null) {
251+
throw new InvalidSeamQueryProviderProps(
252+
'The consoleSessionToken prop cannot be used with the publishableKey prop.',
253+
)
254+
}
255+
256+
if ('workspaceId' in props && props.workspaceId != null) {
257+
throw new InvalidSeamQueryProviderProps(
258+
'The workspaceId prop cannot be used with the publishableKey prop.',
259+
)
260+
}
261+
262+
return true
263+
}
264+
265+
const isSeamQueryProviderPropsWithClientSessionToken = (
266+
props: SeamQueryProviderProps,
267+
): props is SeamQueryProviderPropsWithClientSessionToken &
268+
SeamQueryProviderClientOptions => {
269+
if (!('clientSessionToken' in props)) return false
270+
271+
const { clientSessionToken } = props
272+
if (clientSessionToken == null) return false
273+
274+
if ('client' in props && props.client != null) {
275+
throw new InvalidSeamQueryProviderProps(
276+
'The client prop cannot be used with the clientSessionToken prop.',
277+
)
278+
}
279+
280+
if ('publishableKey' in props && props.publishableKey != null) {
281+
throw new InvalidSeamQueryProviderProps(
282+
'The publishableKey prop cannot be used with the clientSessionToken prop.',
283+
)
284+
}
285+
286+
if ('userIdentifierKey' in props && props.userIdentifierKey != null) {
287+
throw new InvalidSeamQueryProviderProps(
288+
'The userIdentifierKey prop cannot be used with the clientSessionToken prop.',
289+
)
290+
}
291+
292+
if ('consoleSessionToken' in props && props.consoleSessionToken != null) {
293+
throw new InvalidSeamQueryProviderProps(
294+
'The consoleSessionToken prop cannot be used with the clientSessionToken prop.',
295+
)
296+
}
297+
298+
if ('workspaceId' in props && props.workspaceId != null) {
299+
throw new InvalidSeamQueryProviderProps(
300+
'The workspaceId prop cannot be used with the clientSessionToken prop.',
301+
)
302+
}
303+
304+
return true
305+
}
306+
307+
const isSeamQueryProviderPropsWithConsoleSessionToken = (
308+
props: SeamQueryProviderProps,
309+
): props is SeamQueryProviderPropsWithConsoleSessionToken &
310+
SeamQueryProviderClientOptions => {
311+
if (!('consoleSessionToken' in props)) return false
312+
313+
const { consoleSessionToken } = props
314+
if (consoleSessionToken == null) return false
315+
316+
if ('client' in props && props.client != null) {
317+
throw new InvalidSeamQueryProviderProps(
318+
'The client prop cannot be used with the publishableKey prop.',
319+
)
320+
}
321+
322+
if ('clientSessionToken' in props && props.clientSessionToken != null) {
323+
throw new InvalidSeamQueryProviderProps(
324+
'The clientSessionToken prop cannot be used with the publishableKey prop.',
325+
)
326+
}
327+
328+
if ('publishableKey' in props && props.publishableKey != null) {
329+
throw new InvalidSeamQueryProviderProps(
330+
'The publishableKey prop cannot be used with the consoleSessionToken prop.',
331+
)
332+
}
333+
334+
if ('userIdentifierKey' in props && props.userIdentifierKey != null) {
335+
throw new InvalidSeamQueryProviderProps(
336+
'The userIdentifierKey prop cannot be used with the consoleSessionToken prop.',
337+
)
338+
}
339+
340+
return true
341+
}
342+
343+
class InvalidSeamQueryProviderProps extends Error {
344+
constructor(message: string) {
345+
super(`SeamQueryProvider received invalid props: ${message}`)
346+
this.name = this.constructor.name
347+
}
348+
}

src/lib/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
export default null
1+
export * from './SeamQueryProvider.js'
2+
export * from './use-seam-client.js'
3+
export * from './use-seam-infinite-query.js'
4+
export * from './use-seam-mutation.js'
5+
export * from './use-seam-mutation-without-workspace.js'
6+
export * from './use-seam-query.js'
7+
export * from './use-seam-query-without-workspace.js'

0 commit comments

Comments
 (0)