···11+---
22+'@urql/solid': patch
33+---
44+55+Use `@solid-primitives/utils` for `access` and `MaybeAccessor` utilities instead of custom implementations. This aligns the package with standard Solid ecosystem conventions.
+16
.changeset/tall-paths-roll.md
···11+---
22+'@urql/solid-start': minor
33+---
44+55+Initial release of `@urql/solid-start` - URQL integration built with SolidStart's native primitives.
66+77+Get started with:
88+99+- **`createQuery`** - GraphQL queries using SolidStart's `query()` and `createAsync()`
1010+- **`createMutation`** - GraphQL mutations using SolidStart's `action()` and `useAction()`
1111+- **`createSubscription`** - Real-time GraphQL subscriptions
1212+- **`Provider`** and **`useClient`** - Context-based client access
1313+- **Reactive variables** - All parameters accept signals/accessors for automatic re-execution
1414+- **Full SSR support** - Works seamlessly with SolidStart's server-side rendering
1515+- **TypeScript support** - Complete type safety with GraphQL types
1616+- **Uses `@solid-primitives/utils`** - Leverages standard Solid ecosystem utilities
+3-1
docs/README.md
···1717`urql` can be understood as a collection of connected parts and packages.
1818When we only need to install a single package for our framework of choice. We're then able to
1919declaratively send GraphQL requests to our API. All framework packages — like `urql` (for React),
2020-`@urql/preact`, `@urql/svelte`, and `@urql/vue` — wrap the [core package,
2020+`@urql/preact`, `@urql/svelte`, `@urql/solid`/`@urql/solid-start` and `@urql/vue` — wrap the [core package,
2121`@urql/core`](./basics/core.md), which we can imagine as the brain
2222of `urql` with most of its logic. As we progress with implementing `urql` into our application,
2323we're later able to extend it by adding ["addon packages", which we call
···3333- [**React/Preact**](./basics/react-preact.md) covers how to work with the bindings for React/Preact.
3434- [**Vue**](./basics/vue.md) covers how to work with the bindings for Vue 3.
3535- [**Svelte**](./basics/svelte.md) covers how to work with the bindings for Svelte.
3636+- [**Solid**](./basics/solid.md) covers how to work with the bindings for Solid.
3737+- [**SolidStart**](./basics/solid-start.md) covers how to work with the bindings for SolidStart.
3638- [**Core Package**](./basics/core.md) covers the shared "core APIs" and how we can use them directly
3739 in Node.js or imperatively.
3840
+835
docs/basics/solid-start.md
···11+---
22+title: SolidStart Bindings
33+order: 3
44+---
55+66+# SolidStart
77+88+This guide covers how to use `@urql/solid-start` with SolidStart applications. The `@urql/solid-start` package integrates urql with SolidStart's native data fetching primitives like `query()`, `action()`, `createAsync()`, and `useAction()`.
99+1010+> **Note:** This guide is for SolidStart applications with SSR. If you're building a client-side only SolidJS app, see the [Solid guide](./solid.md) instead. See the [comparison section](#solidjs-vs-solidstart) below for key differences between the packages.
1111+1212+## Getting started
1313+1414+### Installation
1515+1616+Installing `@urql/solid-start` requires both the package and its peer dependencies:
1717+1818+```sh
1919+yarn add @urql/solid-start @urql/solid @urql/core graphql
2020+# or
2121+npm install --save @urql/solid-start @urql/solid @urql/core graphql
2222+# or
2323+pnpm add @urql/solid-start @urql/solid @urql/core graphql
2424+```
2525+2626+The `@urql/solid-start` package depends on `@urql/solid` for shared utilities and re-exports some primitives that work identically on both client and server.
2727+2828+### Setting up the `Client`
2929+3030+The `@urql/solid-start` package exports a `Client` class from `@urql/core`. This central `Client` manages all of our GraphQL requests and results.
3131+3232+```js
3333+import { createClient, cacheExchange, fetchExchange } from '@urql/solid-start';
3434+3535+const client = createClient({
3636+ url: 'http://localhost:3000/graphql',
3737+ exchanges: [cacheExchange, fetchExchange],
3838+});
3939+```
4040+4141+At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client`.
4242+4343+For server-side requests, you'll often want to customize `fetchOptions` to include headers like cookies or authorization tokens:
4444+4545+```js
4646+import { getRequestEvent } from 'solid-js/web';
4747+4848+const client = createClient({
4949+ url: 'http://localhost:3000/graphql',
5050+ exchanges: [cacheExchange, fetchExchange],
5151+ fetchOptions: () => {
5252+ const event = getRequestEvent();
5353+ return {
5454+ headers: {
5555+ cookie: event?.request.headers.get('cookie') || '',
5656+ },
5757+ };
5858+ },
5959+});
6060+```
6161+6262+### Providing the `Client`
6363+6464+To make use of the `Client` in SolidStart we will provide it via Solid's Context API using the `Provider` export. The Provider also needs the `query` function from `@solidjs/router`:
6565+6666+```jsx
6767+// src/root.tsx or src/app.tsx
6868+import { Router, query } from '@solidjs/router';
6969+import { FileRoutes } from '@solidjs/start/router';
7070+import { Suspense } from 'solid-js';
7171+import { createClient, Provider, cacheExchange, fetchExchange } from '@urql/solid-start';
7272+7373+const client = createClient({
7474+ url: 'http://localhost:3000/graphql',
7575+ exchanges: [cacheExchange, fetchExchange],
7676+});
7777+7878+export default function App() {
7979+ return (
8080+ <Router
8181+ root={(props) => (
8282+ <Provider value={{ client, query }}>
8383+ <Suspense>{props.children}</Suspense>
8484+ </Provider>
8585+ )}
8686+ >
8787+ <FileRoutes />
8888+ </Router>
8989+ );
9090+}
9191+```
9292+9393+Now every route and component inside the `Provider` can use GraphQL queries that will be sent to our API. The `query` function is provided in the context so that `createQuery` can access it automatically without manual injection.
9494+9595+## Queries
9696+9797+The `@urql/solid-start` package offers a `createQuery` primitive that integrates with SolidStart's `query()` and `createAsync()` primitives for optimal server-side rendering and streaming.
9898+9999+### Run a first query
100100+101101+For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items.
102102+103103+```jsx
104104+// src/routes/todos.tsx
105105+import { Suspense, For, Show } from 'solid-js';
106106+import { createAsync } from '@solidjs/router';
107107+import { gql } from '@urql/core';
108108+import { createQuery } from '@urql/solid-start';
109109+110110+const TodosQuery = gql`
111111+ query {
112112+ todos {
113113+ id
114114+ title
115115+ }
116116+ }
117117+`;
118118+119119+export default function Todos() {
120120+ const queryTodos = createQuery(TodosQuery, 'todos-list');
121121+ const result = createAsync(() => queryTodos());
122122+123123+ return (
124124+ <Suspense fallback={<p>Loading...</p>}>
125125+ <Show when={result()?.data}>
126126+ <ul>
127127+ <For each={result()!.data.todos}>
128128+ {(todo) => <li>{todo.title}</li>}
129129+ </For>
130130+ </ul>
131131+ </Show>
132132+ </Suspense>
133133+ );
134134+}
135135+```
136136+137137+The `createQuery` primitive integrates with SolidStart's data fetching system:
138138+1. It wraps SolidStart's `query()` function to execute URQL queries with proper router context
139139+2. The `query` function is automatically retrieved from the URQL context (no manual injection needed)
140140+3. The second parameter is a cache key (string) for SolidStart's router
141141+4. The returned function is wrapped with `createAsync()` to get the reactive result
142142+5. `createQuery` must be called inside a component where it has access to the context
143143+144144+The query automatically executes on both the server (during SSR) and the client, with SolidStart handling serialization and hydration.
145145+146146+### Variables
147147+148148+Typically we'll also need to pass variables to our queries. Pass variables as an option in the fourth parameter:
149149+150150+```jsx
151151+// src/routes/todos/[page].tsx
152152+import { Suspense, For, Show } from 'solid-js';
153153+import { useParams, createAsync } from '@solidjs/router';
154154+import { gql } from '@urql/core';
155155+import { createQuery } from '@urql/solid-start';
156156+157157+const TodosListQuery = gql`
158158+ query ($from: Int!, $limit: Int!) {
159159+ todos(from: $from, limit: $limit) {
160160+ id
161161+ title
162162+ }
163163+ }
164164+`;
165165+166166+export default function TodosPage() {
167167+ const params = useParams();
168168+169169+ const queryTodos = createQuery(TodosListQuery, 'todos-paginated', {
170170+ variables: {
171171+ from: parseInt(params.page) * 10,
172172+ limit: 10,
173173+ },
174174+ });
175175+176176+ const result = createAsync(() => queryTodos());
177177+178178+ return (
179179+ <Suspense fallback={<p>Loading...</p>}>
180180+ <Show when={result()?.data}>
181181+ <ul>
182182+ <For each={result()!.data.todos}>
183183+ {(todo) => <li>{todo.title}</li>}
184184+ </For>
185185+ </ul>
186186+ </Show>
187187+ </Suspense>
188188+ );
189189+}
190190+```
191191+192192+For dynamic variables that change based on reactive values, you'll need to recreate the query function when dependencies change.
193193+194194+### Request Policies
195195+196196+The `requestPolicy` option determines how results are retrieved from the cache:
197197+198198+```jsx
199199+const queryTodos = createQuery(TodosQuery, 'todos-list', {
200200+ requestPolicy: 'cache-and-network',
201201+});
202202+const result = createAsync(() => queryTodos());
203203+```
204204+205205+Available policies:
206206+- `cache-first` (default): Prefer cached results, fall back to network
207207+- `cache-only`: Only use cached results, never send network requests
208208+- `network-only`: Always send a network request, ignore cache
209209+- `cache-and-network`: Return cached results immediately, then fetch from network
210210+211211+[Learn more about request policies on the "Document Caching" page.](./document-caching.md)
212212+213213+### Revalidation
214214+215215+There are two approaches to revalidating data in SolidStart with urql:
216216+217217+1. **urql's cache invalidation** - Invalidates specific queries or entities in urql's cache, causing automatic refetches
218218+2. **SolidStart's revalidation** - Uses SolidStart's router revalidation to reload route data
219219+220220+Both approaches work well, and you can choose based on your needs. urql's invalidation is more granular and works at the query level, while SolidStart's revalidation works at the route level.
221221+222222+#### Manual Revalidation with urql
223223+224224+You can manually revalidate queries using urql's cache invalidation with the `keyFor` helper. This invalidates specific queries in urql's cache and triggers automatic refetches:
225225+226226+```jsx
227227+// src/routes/todos.tsx
228228+import { Suspense, For, Show } from 'solid-js';
229229+import { createAsync } from '@solidjs/router';
230230+import { gql, keyFor } from '@urql/core';
231231+import { createQuery, useClient } from '@urql/solid-start';
232232+233233+const TodosQuery = gql`
234234+ query {
235235+ todos {
236236+ id
237237+ title
238238+ }
239239+ }
240240+`;
241241+242242+export default function Todos() {
243243+ const client = useClient();
244244+ const queryTodos = createQuery(TodosQuery, 'todos-list');
245245+ const result = createAsync(() => queryTodos());
246246+247247+ const handleRefresh = () => {
248248+ // Invalidate the todos query using keyFor
249249+ const key = keyFor(TodosQuery);
250250+ client.reexecuteOperation(client.createRequestOperation('query', {
251251+ key,
252252+ query: TodosQuery
253253+ }));
254254+ };
255255+256256+ return (
257257+ <div>
258258+ <button onClick={handleRefresh}>Refresh Todos</button>
259259+ <Suspense fallback={<p>Loading...</p>}>
260260+ <Show when={result()?.data}>
261261+ <ul>
262262+ <For each={result()!.data.todos}>
263263+ {(todo) => <li>{todo.title}</li>}
264264+ </For>
265265+ </ul>
266266+ </Show>
267267+ </Suspense>
268268+ </div>
269269+ );
270270+}
271271+```
272272+273273+#### Manual Revalidation with SolidStart
274274+275275+Alternatively, you can use SolidStart's built-in `revalidate` function to reload route data. This is useful when you want to refresh all queries on a specific route:
276276+277277+```jsx
278278+// src/routes/todos.tsx
279279+import { Suspense, For, Show } from 'solid-js';
280280+import { createAsync, revalidate } from '@solidjs/router';
281281+import { gql } from '@urql/core';
282282+import { createQuery } from '@urql/solid-start';
283283+284284+const TodosQuery = gql`
285285+ query {
286286+ todos {
287287+ id
288288+ title
289289+ }
290290+ }
291291+`;
292292+293293+export default function Todos() {
294294+ const queryTodos = createQuery(TodosQuery, 'todos-list');
295295+ const result = createAsync(() => queryTodos());
296296+297297+ const handleRefresh = async () => {
298298+ // Revalidate the current route - refetches all queries on this page
299299+ await revalidate();
300300+ };
301301+302302+ return (
303303+ <div>
304304+ <button onClick={handleRefresh}>Refresh Todos</button>
305305+ <Suspense fallback={<p>Loading...</p>}>
306306+ <Show when={result()?.data}>
307307+ <ul>
308308+ <For each={result()!.data.todos}>
309309+ {(todo) => <li>{todo.title}</li>}
310310+ </For>
311311+ </ul>
312312+ </Show>
313313+ </Suspense>
314314+ </div>
315315+ );
316316+}
317317+```
318318+319319+#### Revalidation After Mutations
320320+321321+A common pattern is to revalidate after a mutation succeeds. You can choose either approach:
322322+323323+**Using urql's cache invalidation:**
324324+325325+```jsx
326326+// src/routes/todos/new.tsx
327327+import { useNavigate } from '@solidjs/router';
328328+import { gql, keyFor } from '@urql/core';
329329+import { createMutation, useClient } from '@urql/solid-start';
330330+331331+const TodosQuery = gql`
332332+ query {
333333+ todos {
334334+ id
335335+ title
336336+ }
337337+ }
338338+`;
339339+340340+const CreateTodo = gql`
341341+ mutation ($title: String!) {
342342+ createTodo(title: $title) {
343343+ id
344344+ title
345345+ }
346346+ }
347347+`;
348348+349349+export default function NewTodo() {
350350+ const navigate = useNavigate();
351351+ const client = useClient();
352352+ const [state, createTodo] = createMutation(CreateTodo);
353353+354354+ const handleSubmit = async (e: Event) => {
355355+ e.preventDefault();
356356+ const formData = new FormData(e.target as HTMLFormElement);
357357+ const title = formData.get('title') as string;
358358+359359+ const result = await createTodo({ title });
360360+361361+ if (!result.error) {
362362+ // Invalidate todos query using keyFor
363363+ const key = keyFor(TodosQuery);
364364+ client.reexecuteOperation(client.createRequestOperation('query', {
365365+ key,
366366+ query: TodosQuery
367367+ }));
368368+ navigate('/todos');
369369+ }
370370+ };
371371+372372+ return (
373373+ <form onSubmit={handleSubmit}>
374374+ <input name="title" type="text" required />
375375+ <button type="submit" disabled={state.fetching}>
376376+ {state.fetching ? 'Creating...' : 'Create Todo'}
377377+ </button>
378378+ </form>
379379+ );
380380+}
381381+```
382382+383383+**Using SolidStart's revalidation:**
384384+385385+```jsx
386386+// src/routes/todos/new.tsx
387387+import { useNavigate } from '@solidjs/router';
388388+import { gql } from '@urql/core';
389389+import { createMutation } from '@urql/solid-start';
390390+import { revalidate } from '@solidjs/router';
391391+392392+const CreateTodo = gql`
393393+ mutation ($title: String!) {
394394+ createTodo(title: $title) {
395395+ id
396396+ title
397397+ }
398398+ }
399399+`;
400400+401401+export default function NewTodo() {
402402+ const navigate = useNavigate();
403403+ const [state, createTodo] = createMutation(CreateTodo);
404404+405405+ const handleSubmit = async (e: Event) => {
406406+ e.preventDefault();
407407+ const formData = new FormData(e.target as HTMLFormElement);
408408+ const title = formData.get('title') as string;
409409+410410+ const result = await createTodo({ title });
411411+412412+ if (!result.error) {
413413+ // Revalidate the /todos route to refetch all its queries
414414+ await revalidate('/todos');
415415+ navigate('/todos');
416416+ }
417417+ };
418418+419419+ return (
420420+ <form onSubmit={handleSubmit}>
421421+ <input name="title" type="text" required />
422422+ <button type="submit" disabled={state.fetching}>
423423+ {state.fetching ? 'Creating...' : 'Create Todo'}
424424+ </button>
425425+ </form>
426426+ );
427427+}
428428+```
429429+430430+#### Automatic Revalidation with Actions
431431+432432+When using SolidStart actions, you can configure automatic revalidation by returning the appropriate response:
433433+434434+```jsx
435435+import { action, revalidate } from '@solidjs/router';
436436+import { gql } from '@urql/core';
437437+438438+const createTodoAction = action(async (formData: FormData) => {
439439+ const title = formData.get('title') as string;
440440+441441+ // Perform mutation
442442+ const result = await client.mutation(CreateTodo, { title }).toPromise();
443443+444444+ if (!result.error) {
445445+ // Revalidate multiple routes if needed
446446+ await revalidate(['/todos', '/']);
447447+ }
448448+449449+ return result;
450450+});
451451+```
452452+453453+#### Choosing Between Approaches
454454+455455+**Use urql's `keyFor` and `reexecuteOperation` when:**
456456+- You need to refetch a specific query after a mutation
457457+- You want fine-grained control over which queries to refresh
458458+- You're working with multiple queries on the same route and only want to refetch one
459459+460460+**Use SolidStart's `revalidate` when:**
461461+- You want to refresh all data on a route
462462+- You're navigating to a different route and want to ensure fresh data
463463+- You want to leverage SolidStart's routing system for cache management
464464+465465+Both approaches are valid and can even be used together depending on your application's needs.
466466+467467+### Context Options
468468+469469+Context options can be passed to customize the query behavior:
470470+471471+```jsx
472472+const queryTodos = createQuery(TodosQuery, 'todos-list', {
473473+ context: {
474474+ requestPolicy: 'cache-and-network',
475475+ fetchOptions: {
476476+ headers: {
477477+ 'X-Custom-Header': 'value',
478478+ },
479479+ },
480480+ },
481481+});
482482+const result = createAsync(() => queryTodos());
483483+```
484484+485485+[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
486486+487487+## Mutations
488488+489489+The `@urql/solid-start` package offers a `createMutation` primitive that integrates with SolidStart's `action()` and `useAction()` primitives.
490490+491491+### Sending a mutation
492492+493493+Mutations in SolidStart are executed using actions. Here's an example of updating a todo item:
494494+495495+```jsx
496496+// src/routes/todos/[id]/edit.tsx
497497+import { gql } from '@urql/core';
498498+import { createMutation } from '@urql/solid-start';
499499+import { useParams, useNavigate } from '@solidjs/router';
500500+import { Show } from 'solid-js';
501501+502502+const UpdateTodo = gql`
503503+ mutation ($id: ID!, $title: String!) {
504504+ updateTodo(id: $id, title: $title) {
505505+ id
506506+ title
507507+ }
508508+ }
509509+`;
510510+511511+export default function EditTodo() {
512512+ const params = useParams();
513513+ const navigate = useNavigate();
514514+ const [state, updateTodo] = createMutation(UpdateTodo);
515515+516516+ const handleSubmit = async (e: Event) => {
517517+ e.preventDefault();
518518+ const formData = new FormData(e.target as HTMLFormElement);
519519+ const title = formData.get('title') as string;
520520+521521+ const result = await updateTodo({
522522+ id: params.id,
523523+ title,
524524+ });
525525+526526+ if (!result.error) {
527527+ navigate(`/todos/${params.id}`);
528528+ }
529529+ };
530530+531531+ return (
532532+ <form onSubmit={handleSubmit}>
533533+ <input name="title" type="text" required />
534534+ <button type="submit" disabled={state.fetching}>
535535+ {state.fetching ? 'Saving...' : 'Save'}
536536+ </button>
537537+ <Show when={state.error}>
538538+ <p style={{ color: 'red' }}>Error: {state.error.message}</p>
539539+ </Show>
540540+ </form>
541541+ );
542542+}
543543+```
544544+545545+The `createMutation` primitive returns a tuple:
546546+1. A reactive state object containing `fetching`, `error`, and `data`
547547+2. An execute function that triggers the mutation
548548+549549+You can optionally provide a custom `key` parameter to control how mutations are cached by SolidStart's router:
550550+551551+```jsx
552552+const [state, updateTodo] = createMutation(UpdateTodo, 'update-todo-mutation');
553553+```
554554+555555+### Progressive enhancement with actions
556556+557557+SolidStart actions work with and without JavaScript enabled. Here's how to set up a mutation that works progressively:
558558+559559+```jsx
560560+import { action, redirect } from '@solidjs/router';
561561+import { gql } from '@urql/core';
562562+import { createMutation } from '@urql/solid-start';
563563+564564+const CreateTodo = gql`
565565+ mutation ($title: String!) {
566566+ createTodo(title: $title) {
567567+ id
568568+ title
569569+ }
570570+ }
571571+`;
572572+573573+export default function NewTodo() {
574574+ const [state, createTodo] = createMutation(CreateTodo);
575575+576576+ const handleSubmit = async (formData: FormData) => {
577577+ const title = formData.get('title') as string;
578578+ const result = await createTodo({ title });
579579+580580+ if (!result.error) {
581581+ return redirect('/todos');
582582+ }
583583+ };
584584+585585+ return (
586586+ <form action={handleSubmit} method="post">
587587+ <input name="title" type="text" required />
588588+ <button type="submit" disabled={state.fetching}>
589589+ {state.fetching ? 'Creating...' : 'Create Todo'}
590590+ </button>
591591+ <Show when={state.error}>
592592+ <p style={{ color: 'red' }}>Error: {state.error.message}</p>
593593+ </Show>
594594+ </form>
595595+ );
596596+}
597597+```
598598+599599+### Using mutation results
600600+601601+The mutation state is reactive and updates automatically as the mutation progresses:
602602+603603+```jsx
604604+const [state, updateTodo] = createMutation(UpdateTodo);
605605+606606+createEffect(() => {
607607+ if (state.data) {
608608+ console.log('Mutation succeeded:', state.data);
609609+ }
610610+ if (state.error) {
611611+ console.error('Mutation failed:', state.error);
612612+ }
613613+ if (state.fetching) {
614614+ console.log('Mutation in progress...');
615615+ }
616616+});
617617+```
618618+619619+The execute function also returns a promise that resolves to the result:
620620+621621+```jsx
622622+const [state, updateTodo] = createMutation(UpdateTodo);
623623+624624+const handleUpdate = async () => {
625625+ const result = await updateTodo({ id: '1', title: 'Updated' });
626626+627627+ if (result.error) {
628628+ console.error('Oh no!', result.error);
629629+ } else {
630630+ console.log('Success!', result.data);
631631+ }
632632+};
633633+```
634634+635635+### Handling mutation errors
636636+637637+Mutation promises never reject. Instead, check the `error` field on the result:
638638+639639+```jsx
640640+const [state, updateTodo] = createMutation(UpdateTodo);
641641+642642+const handleUpdate = async () => {
643643+ const result = await updateTodo({ id: '1', title: 'Updated' });
644644+645645+ if (result.error) {
646646+ // CombinedError with network or GraphQL errors
647647+ console.error('Mutation failed:', result.error);
648648+649649+ // Check for specific error types
650650+ if (result.error.networkError) {
651651+ console.error('Network error:', result.error.networkError);
652652+ }
653653+ if (result.error.graphQLErrors.length > 0) {
654654+ console.error('GraphQL errors:', result.error.graphQLErrors);
655655+ }
656656+ }
657657+};
658658+```
659659+660660+[Read more about error handling on the "Errors" page.](./errors.md)
661661+662662+## Subscriptions
663663+664664+For GraphQL subscriptions, `@urql/solid-start` re-exports the `createSubscription` primitive from `@urql/solid`, as subscriptions work identically on both client and server:
665665+666666+```jsx
667667+import { gql } from '@urql/core';
668668+import { createSubscription } from '@urql/solid-start';
669669+import { createSignal, For } from 'solid-js';
670670+671671+const NewTodos = gql`
672672+ subscription {
673673+ newTodos {
674674+ id
675675+ title
676676+ }
677677+ }
678678+`;
679679+680680+export default function TodoSubscription() {
681681+ const [todos, setTodos] = createSignal([]);
682682+683683+ const handleSubscription = (previousData, newData) => {
684684+ setTodos(current => [...current, newData.newTodos]);
685685+ return newData;
686686+ };
687687+688688+ const [result] = createSubscription(
689689+ {
690690+ query: NewTodos,
691691+ },
692692+ handleSubscription
693693+ );
694694+695695+ return (
696696+ <div>
697697+ <h2>Live Updates</h2>
698698+ <ul>
699699+ <For each={todos()}>
700700+ {(todo) => <li>{todo.title}</li>}
701701+ </For>
702702+ </ul>
703703+ </div>
704704+ );
705705+}
706706+```
707707+708708+Note that GraphQL subscriptions typically require WebSocket support. You'll need to configure your client with a subscription exchange like `subscriptionExchange` from `@urql/core`.
709709+710710+## Server-Side Rendering
711711+712712+SolidStart automatically handles server-side rendering and hydration. The `createQuery` primitive works seamlessly on both server and client:
713713+714714+1. On the server, queries execute during SSR and their results are serialized
715715+2. On the client, SolidStart hydrates the data without refetching
716716+3. Subsequent navigations use the standard cache policies
717717+718718+### SSR Considerations
719719+720720+When using `createQuery` in SolidStart:
721721+722722+- Queries execute on the server during initial page load
723723+- Results are automatically streamed to the client
724724+- The client hydrates with the server data
725725+- No manual script injection or data serialization needed
726726+- SolidStart handles all the complexity automatically
727727+728728+### Handling cookies and authentication
729729+730730+For authenticated requests, forward cookies and headers from the server request:
731731+732732+```jsx
733733+import { getRequestEvent } from 'solid-js/web';
734734+import { createClient, cacheExchange, fetchExchange } from '@urql/solid-start';
735735+736736+const client = createClient({
737737+ url: 'http://localhost:3000/graphql',
738738+ exchanges: [cacheExchange, fetchExchange],
739739+ fetchOptions: () => {
740740+ const event = getRequestEvent();
741741+ const headers: Record<string, string> = {};
742742+743743+ // Forward cookies for authenticated requests
744744+ if (event) {
745745+ const cookie = event.request.headers.get('cookie');
746746+ if (cookie) {
747747+ headers.cookie = cookie;
748748+ }
749749+ }
750750+751751+ return { headers };
752752+ },
753753+});
754754+```
755755+756756+## SolidJS vs SolidStart
757757+758758+### When to Use Each Package
759759+760760+| Use Case | Package | Why |
761761+|----------|---------|-----|
762762+| Client-side SPA | `@urql/solid` | Optimized for client-only apps, uses SolidJS reactivity patterns |
763763+| SolidStart SSR App | `@urql/solid-start` | Integrates with SolidStart's routing, SSR, and action system |
764764+765765+### Key Differences
766766+767767+#### Queries
768768+769769+**@urql/solid** (Client-side):
770770+```tsx
771771+import { createQuery } from '@urql/solid';
772772+773773+const [result] = createQuery({ query: TodosQuery });
774774+// Returns: [Accessor<OperationResult>, Accessor<ReExecute>]
775775+```
776776+777777+**@urql/solid-start** (SSR):
778778+```tsx
779779+import { createQuery } from '@urql/solid-start';
780780+import { createAsync } from '@solidjs/router';
781781+782782+const queryTodos = createQuery(TodosQuery, 'todos');
783783+const todos = createAsync(() => queryTodos());
784784+// Returns: Accessor<OperationResult | undefined>
785785+// Works with SSR and SolidStart's caching
786786+```
787787+788788+#### Mutations
789789+790790+**@urql/solid** (Client-side):
791791+```tsx
792792+import { createMutation } from '@urql/solid';
793793+794794+const [result, executeMutation] = createMutation(AddTodoMutation);
795795+await executeMutation({ title: 'New Todo' });
796796+// Returns: [Accessor<OperationResult>, ExecuteMutation]
797797+```
798798+799799+**@urql/solid-start** (SSR with Actions):
800800+```tsx
801801+import { createMutation } from '@urql/solid-start';
802802+import { useAction, useSubmission } from '@solidjs/router';
803803+804804+const addTodoAction = createMutation(AddTodoMutation, 'add-todo');
805805+const addTodo = useAction(addTodoAction);
806806+const submission = useSubmission(addTodoAction);
807807+await addTodo({ title: 'New Todo' });
808808+// Integrates with SolidStart's action system for progressive enhancement
809809+```
810810+811811+### Why Different APIs?
812812+813813+- **SSR Support**: SolidStart queries run on the server and stream to the client
814814+- **Router Integration**: Automatic caching and invalidation with SolidStart's router
815815+- **Progressive Enhancement**: Actions work without JavaScript enabled
816816+- **Suspense**: Native support for SolidJS Suspense boundaries
817817+818818+### Migration
819819+820820+If you're moving from a SolidJS SPA to SolidStart:
821821+1. Change imports from `@urql/solid` to `@urql/solid-start`
822822+2. Wrap queries with `createAsync()`
823823+3. Update mutations to use the action pattern with `useAction()` and `useSubmission()`
824824+825825+For more details, see the [Solid bindings documentation](./solid.md).
826826+827827+## Reading on
828828+829829+This concludes the introduction for using `@urql/solid-start` with SolidStart. For more information:
830830+831831+- [Solid bindings documentation](./solid.md) - for client-only features
832832+- [How does the default "document cache" work?](./document-caching.md)
833833+- [How are errors handled and represented?](./errors.md)
834834+- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
835835+- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
+469
docs/basics/solid.md
···11+---
22+title: Solid Bindings
33+order: 3
44+---
55+66+# Solid
77+88+This guide covers how to install and setup `@urql/solid` and the `Client`, as well as query and mutate data with Solid. The `@urql/solid` package provides reactive primitives that integrate seamlessly with Solid's fine-grained reactivity system.
99+1010+> **Note:** This guide is for client-side SolidJS applications. If you're building a SolidStart application with SSR, see the [SolidStart guide](./solid-start.md) instead. The packages use different APIs optimized for their respective use cases.
1111+1212+## Getting started
1313+1414+### Installation
1515+1616+Installing `@urql/solid` is quick and you won't need any other packages to get started with at first. We'll install the package with our package manager of choice.
1717+1818+```sh
1919+yarn add @urql/solid graphql
2020+# or
2121+npm install --save @urql/solid graphql
2222+# or
2323+pnpm add @urql/solid graphql
2424+```
2525+2626+Most libraries related to GraphQL also need the `graphql` package to be installed as a peer dependency, so that they can adapt to your specific versioning requirements. That's why we'll need to install `graphql` alongside `@urql/solid`.
2727+2828+Both the `@urql/solid` and `graphql` packages follow [semantic versioning](https://semver.org) and all `@urql/solid` packages will define a range of compatible versions of `graphql`. Watch out for breaking changes in the future however, in which case your package manager may warn you about `graphql` being out of the defined peer dependency range.
2929+3030+### Setting up the `Client`
3131+3232+The `@urql/solid` package exports a `Client` class from `@urql/core`, which we can use to create the GraphQL client. This central `Client` manages all of our GraphQL requests and results.
3333+3434+```js
3535+import { createClient, cacheExchange, fetchExchange } from '@urql/solid';
3636+3737+const client = createClient({
3838+ url: 'http://localhost:3000/graphql',
3939+ exchanges: [cacheExchange, fetchExchange],
4040+});
4141+```
4242+4343+At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client` to get started.
4444+4545+Another common option is `fetchOptions`. This option allows us to customize the options that will be passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object, or a function returning an options object.
4646+4747+In the following example we'll add a token to each `fetch` request that our `Client` sends to our GraphQL API.
4848+4949+```js
5050+const client = createClient({
5151+ url: 'http://localhost:3000/graphql',
5252+ exchanges: [cacheExchange, fetchExchange],
5353+ fetchOptions: () => {
5454+ const token = getToken();
5555+ return {
5656+ headers: { authorization: token ? `Bearer ${token}` : '' },
5757+ };
5858+ },
5959+});
6060+```
6161+6262+### Providing the `Client`
6363+6464+To make use of the `Client` in Solid we will have to provide it via Solid's Context API. This may be done with the help of the `Provider` export.
6565+6666+```jsx
6767+import { render } from 'solid-js/web';
6868+import { createClient, Provider, cacheExchange, fetchExchange } from '@urql/solid';
6969+7070+const client = createClient({
7171+ url: 'http://localhost:3000/graphql',
7272+ exchanges: [cacheExchange, fetchExchange],
7373+});
7474+7575+const App = () => (
7676+ <Provider value={client}>
7777+ <YourRoutes />
7878+ </Provider>
7979+);
8080+8181+render(() => <App />, document.getElementById('root'));
8282+```
8383+8484+Now every component inside and under the `Provider` can use GraphQL queries that will be sent to our API.
8585+8686+## Queries
8787+8888+The `@urql/solid` package offers a `createQuery` primitive that integrates with Solid's fine-grained reactivity system.
8989+9090+### Run a first query
9191+9292+For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items. Let's dive right into it!
9393+9494+```jsx
9595+import { Suspense, For } from 'solid-js';
9696+import { gql } from '@urql/core';
9797+import { createQuery } from '@urql/solid';
9898+9999+const TodosQuery = gql`
100100+ query {
101101+ todos {
102102+ id
103103+ title
104104+ }
105105+ }
106106+`;
107107+108108+const Todos = () => {
109109+ const [result] = createQuery({
110110+ query: TodosQuery,
111111+ });
112112+113113+ return (
114114+ <Suspense fallback={<p>Loading...</p>}>
115115+ <ul>
116116+ <For each={result().data.todos}>
117117+ {(todo) => <li>{todo.title}</li>}
118118+ </For>
119119+ </ul>
120120+ </Suspense>
121121+ );
122122+};
123123+```
124124+125125+Here we have implemented our first GraphQL query to fetch todos. We see that `createQuery` accepts options and returns a tuple. In this case we've set the `query` option to our GraphQL query. The tuple we then get in return is an array where the first item is an accessor function that returns the result object.
126126+127127+The result object contains several properties. The `fetching` field indicates whether the query is loading data, `data` contains the actual `data` from the API's result, and `error` is set when either the request to the API has failed or when our API result contained some `GraphQLError`s, which we'll get into later on the ["Errors" page](./errors.md).
128128+129129+### Variables
130130+131131+Typically we'll also need to pass variables to our queries, for instance, if we are dealing with pagination. For this purpose `createQuery` also accepts a `variables` option, which can be reactive.
132132+133133+```jsx
134134+const TodosListQuery = gql`
135135+ query ($from: Int!, $limit: Int!) {
136136+ todos(from: $from, limit: $limit) {
137137+ id
138138+ title
139139+ }
140140+ }
141141+`;
142142+143143+const Todos = (props) => {
144144+ const [result] = createQuery({
145145+ query: TodosListQuery,
146146+ variables: () => ({ from: props.from, limit: props.limit }),
147147+ });
148148+149149+ // ...
150150+};
151151+```
152152+153153+The `variables` option can be passed as a static object or as an accessor function that returns the variables. When using an accessor, the query will automatically re-execute when the variables change.
154154+155155+```jsx
156156+import { Suspense, For, createSignal } from 'solid-js';
157157+import { gql } from '@urql/core';
158158+import { createQuery } from '@urql/solid';
159159+160160+const TodosListQuery = gql`
161161+ query ($from: Int!, $limit: Int!) {
162162+ todos(from: $from, limit: $limit) {
163163+ id
164164+ title
165165+ }
166166+ }
167167+`;
168168+169169+const Todos = () => {
170170+ const [from, setFrom] = createSignal(0);
171171+ const limit = 10;
172172+173173+ const [result] = createQuery({
174174+ query: TodosListQuery,
175175+ variables: () => ({ from: from(), limit }),
176176+ });
177177+178178+ return (
179179+ <div>
180180+ <Suspense fallback={<p>Loading...</p>}>
181181+ <ul>
182182+ <For each={result().data.todos}>
183183+ {(todo) => <li>{todo.title}</li>}
184184+ </For>
185185+ </ul>
186186+ </Suspense>
187187+ <button onClick={() => setFrom(f => f + 10)}>Next Page</button>
188188+ </div>
189189+ );
190190+};
191191+```
192192+193193+Whenever the variables change, `fetching` will switch to `true`, and a new request will be sent to our API, unless a result has already been cached previously.
194194+195195+### Pausing `createQuery`
196196+197197+In some cases we may want `createQuery` to execute a query when a pre-condition has been met, and not execute the query otherwise. For instance, we may be building a form and want a validation query to only take place when a field has been filled out.
198198+199199+The `createQuery` primitive accepts a `pause` option that temporarily stops the query from executing.
200200+201201+```jsx
202202+const Todos = (props) => {
203203+ const shouldPause = () => props.from == null || props.limit == null;
204204+205205+ const [result] = createQuery({
206206+ query: TodosListQuery,
207207+ variables: () => ({ from: props.from, limit: props.limit }),
208208+ pause: shouldPause,
209209+ });
210210+211211+ // ...
212212+};
213213+```
214214+215215+Now whenever the mandatory variables aren't supplied the query won't be executed. This also means that `result().data` won't change, which means we'll still have access to our old data even though the variables may have changed.
216216+217217+### Request Policies
218218+219219+The `createQuery` primitive accepts a `requestPolicy` option that determines how results are retrieved from our `Client`'s cache. By default, this is set to `cache-first`, which means that we prefer to get results from our cache, but are falling back to sending an API request.
220220+221221+Request policies aren't specific to `@urql/solid`, but are a common feature in urql's core. [You can learn more about how the cache behaves given the four different policies on the "Document Caching" page.](./document-caching.md)
222222+223223+```jsx
224224+const [result] = createQuery({
225225+ query: TodosListQuery,
226226+ variables: () => ({ from: props.from, limit: props.limit }),
227227+ requestPolicy: 'cache-and-network',
228228+});
229229+```
230230+231231+The `requestPolicy` can be passed as a static string or as an accessor function. When using `cache-and-network`, the query will be refreshed from our API even after our cache has given us a cached result.
232232+233233+### Context Options
234234+235235+The `requestPolicy` option is part of urql's context options. In fact, there are several more built-in context options. These options can be passed via the `context` parameter.
236236+237237+```jsx
238238+const [result] = createQuery({
239239+ query: TodosListQuery,
240240+ variables: () => ({ from: props.from, limit: props.limit }),
241241+ context: () => ({
242242+ requestPolicy: 'cache-and-network',
243243+ url: 'http://localhost:3000/graphql?debug=true',
244244+ }),
245245+});
246246+```
247247+248248+[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
249249+250250+### Reexecuting Queries
251251+252252+The `createQuery` primitive updates and executes queries automatically when reactive inputs change, but in some cases we may need to programmatically trigger a new query. This is the purpose of the second item in the tuple that `createQuery` returns.
253253+254254+```jsx
255255+const Todos = () => {
256256+ const [result, reexecuteQuery] = createQuery({
257257+ query: TodosListQuery,
258258+ variables: { from: 0, limit: 10 },
259259+ });
260260+261261+ const refresh = () => {
262262+ // Refetch the query and skip the cache
263263+ reexecuteQuery({ requestPolicy: 'network-only' });
264264+ };
265265+266266+ return (
267267+ <div>
268268+ <Suspense fallback={<p>Loading...</p>}>
269269+ <ul>
270270+ <For each={result().data.todos}>
271271+ {(todo) => <li>{todo.title}</li>}
272272+ </For>
273273+ </ul>
274274+ </Suspense>
275275+ <button onClick={refresh}>Refresh</button>
276276+ </div>
277277+ );
278278+};
279279+```
280280+281281+Calling `refresh` in the above example will execute the query again forcefully, and will skip the cache, since we're passing `requestPolicy: 'network-only'`.
282282+283283+## Mutations
284284+285285+The `@urql/solid` package offers a `createMutation` primitive for executing GraphQL mutations.
286286+287287+### Sending a mutation
288288+289289+Let's again pick up an example with an imaginary GraphQL API for todo items. We'll set up a mutation that updates a todo item's title.
290290+291291+```jsx
292292+import { gql } from '@urql/core';
293293+import { createMutation } from '@urql/solid';
294294+295295+const UpdateTodo = gql`
296296+ mutation ($id: ID!, $title: String!) {
297297+ updateTodo (id: $id, title: $title) {
298298+ id
299299+ title
300300+ }
301301+ }
302302+`;
303303+304304+const Todo = (props) => {
305305+ const [result, updateTodo] = createMutation(UpdateTodo);
306306+307307+ const handleSubmit = (newTitle) => {
308308+ updateTodo({ id: props.id, title: newTitle });
309309+ };
310310+311311+ return (
312312+ <div>
313313+ <Show when={result().fetching}>
314314+ <p>Updating...</p>
315315+ </Show>
316316+ <Show when={result().error}>
317317+ <p>Error: {result().error.message}</p>
318318+ </Show>
319319+ {/* Your form UI here */}
320320+ </div>
321321+ );
322322+};
323323+```
324324+325325+Similar to `createQuery`, `createMutation` returns a tuple. The first item is an accessor that returns the result object containing `fetching`, `error`, and `data` — identical to query results. The second item is the execute function that triggers the mutation.
326326+327327+Unlike `createQuery`, `createMutation` doesn't execute automatically. We must call the execute function with the mutation variables.
328328+329329+### Using the mutation result
330330+331331+The mutation result is available both through the reactive accessor and through the promise returned by the execute function.
332332+333333+```jsx
334334+const Todo = (props) => {
335335+ const [result, updateTodo] = createMutation(UpdateTodo);
336336+337337+ const handleSubmit = (newTitle) => {
338338+ const variables = { id: props.id, title: newTitle };
339339+340340+ updateTodo(variables).then((result) => {
341341+ // The result is almost identical to result() from the accessor
342342+ // It is an OperationResult.
343343+ if (!result.error) {
344344+ console.log('Todo updated!', result.data);
345345+ }
346346+ });
347347+ };
348348+349349+ return (
350350+ <div>
351351+ <Show when={result().fetching}>
352352+ <p>Updating...</p>
353353+ </Show>
354354+ {/* Your form UI here */}
355355+ </div>
356356+ );
357357+};
358358+```
359359+360360+The reactive accessor is useful when your UI needs to display progress on the mutation, and the returned promise is particularly useful for side effects that run after the mutation completes.
361361+362362+### Handling mutation errors
363363+364364+The promise returned by the execute function will never reject. Instead it will always return a promise that resolves to a result.
365365+366366+If you're checking for errors, you should use `result.error`, which will be set to a `CombinedError` when any kind of errors occurred while executing your mutation. [Read more about errors on our "Errors" page.](./errors.md)
367367+368368+```jsx
369369+const Todo = (props) => {
370370+ const [result, updateTodo] = createMutation(UpdateTodo);
371371+372372+ const handleSubmit = (newTitle) => {
373373+ const variables = { id: props.id, title: newTitle };
374374+375375+ updateTodo(variables).then((result) => {
376376+ if (result.error) {
377377+ console.error('Oh no!', result.error);
378378+ }
379379+ });
380380+ };
381381+382382+ // ...
383383+};
384384+```
385385+386386+## Subscriptions
387387+388388+The `@urql/solid` package offers a `createSubscription` primitive for handling GraphQL subscriptions with Solid's reactive system.
389389+390390+### Setting up a subscription
391391+392392+GraphQL subscriptions allow you to receive real-time updates from your GraphQL API. Here's an example of how to set up a subscription:
393393+394394+```jsx
395395+import { gql } from '@urql/core';
396396+import { createSubscription } from '@urql/solid';
397397+398398+const NewTodos = gql`
399399+ subscription {
400400+ newTodos {
401401+ id
402402+ title
403403+ }
404404+ }
405405+`;
406406+407407+const TodoSubscription = () => {
408408+ const [result] = createSubscription({
409409+ query: NewTodos,
410410+ });
411411+412412+ return (
413413+ <div>
414414+ <Show when={result().fetching}>
415415+ <p>Waiting for updates...</p>
416416+ </Show>
417417+ <Show when={result().error}>
418418+ <p>Error: {result().error.message}</p>
419419+ </Show>
420420+ <Show when={result().data}>
421421+ <p>New todo: {result().data.newTodos.title}</p>
422422+ </Show>
423423+ </div>
424424+ );
425425+};
426426+```
427427+428428+### Handling subscription data
429429+430430+Unlike queries and mutations, subscriptions can emit multiple results over time. You can use a `handler` function to accumulate or process subscription events:
431431+432432+```jsx
433433+import { createSignal } from 'solid-js';
434434+435435+const TodoSubscription = () => {
436436+ const [todos, setTodos] = createSignal([]);
437437+438438+ const handleSubscription = (previousData, newData) => {
439439+ setTodos(current => [...current, newData.newTodos]);
440440+ return newData;
441441+ };
442442+443443+ const [result] = createSubscription(
444444+ {
445445+ query: NewTodos,
446446+ },
447447+ handleSubscription
448448+ );
449449+450450+ return (
451451+ <ul>
452452+ <For each={todos()}>
453453+ {(todo) => <li>{todo.title}</li>}
454454+ </For>
455455+ </ul>
456456+ );
457457+};
458458+```
459459+460460+The handler function receives the previous data and the new data from the subscription, allowing you to accumulate results or transform them as needed.
461461+462462+## Reading on
463463+464464+This concludes the introduction for using `@urql/solid` with Solid. The rest of the documentation is mostly framework-agnostic and will apply to either `urql` in general, or the `@urql/core` package, which is the same between all framework bindings. Hence, next we may want to learn more about one of the following:
465465+466466+- [How does the default "document cache" work?](./document-caching.md)
467467+- [How are errors handled and represented?](./errors.md)
468468+- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
469469+- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
···11+# URQL with SolidStart
22+33+This example demonstrates how to use URQL with SolidStart.
44+55+## Features
66+77+- Basic query with `createQuery`
88+- Client setup with `Provider`
99+- SSR with automatic hydration
1010+- Route-level preloading
1111+- Suspense integration
1212+1313+## Getting Started
1414+1515+```bash
1616+pnpm install
1717+pnpm start
1818+```
1919+2020+Then open [http://localhost:3000](http://localhost:3000) in your browser.
2121+2222+## What's Inside
2323+2424+- `src/app.tsx` - Sets up the URQL client and router
2525+- `src/routes/index.tsx` - Demonstrates `createQuery` with preloading and Suspense
2626+2727+## Key Features Demonstrated
2828+2929+### Route Preloading
3030+3131+The example uses SolidStart's `preload` function to start fetching data before the route component renders:
3232+3333+```tsx
3434+export const route = {
3535+ preload: () => {
3636+ const pokemons = createQuery({ query: POKEMONS_QUERY });
3737+ return pokemons(); // Start fetching
3838+ },
3939+} satisfies RouteDefinition;
4040+```
4141+4242+### Server-Side Rendering
4343+4444+Queries automatically execute on the server during SSR and hydrate on the client without refetching.
4545+4646+## Learn More
4747+4848+- [SolidStart Documentation](https://start.solidjs.com/)
4949+- [URQL SolidStart Documentation](https://formidable.com/open-source/urql/docs/basics/solid-start/)
+3
examples/with-solid-start/app.config.ts
···11+import { defineConfig } from '@solidjs/start/config';
22+33+export default defineConfig({});
···11+# URQL with Solid
22+33+This example demonstrates how to use URQL with Solid.js.
44+55+## Features
66+77+- Basic query with `createQuery`
88+- Client setup with `Provider`
99+- Suspense integration
1010+1111+## Getting Started
1212+1313+```bash
1414+pnpm install
1515+pnpm start
1616+```
1717+1818+Then open [http://localhost:5173](http://localhost:5173) in your browser.
1919+2020+## What's Inside
2121+2222+- `src/App.jsx` - Sets up the URQL client and provider
2323+- `src/PokemonList.jsx` - Demonstrates `createQuery` with Suspense
2424+2525+## Learn More
2626+2727+- [Solid Documentation](https://www.solidjs.com/)
2828+- [URQL Solid Documentation](https://formidable.com/open-source/urql/docs/basics/solid/)
···11+/** @jsxImportSource solid-js */
22+import { render } from 'solid-js/web';
33+import App from './App';
44+55+render(() => <App />, document.getElementById('root'));
+6
examples/with-solid/vite.config.js
···11+import { defineConfig } from 'vite';
22+import solid from 'vite-plugin-solid';
33+44+export default defineConfig({
55+ plugins: [solid()],
66+});
+2-2
packages/site/src/screens/home/hero.js
···217217 <HeroTitle>urql</HeroTitle>
218218 <HeroBody>
219219 The highly customizable and versatile GraphQL client for React,
220220- Svelte, Vue, or plain JavaScript, with which you add on features like
221221- normalized caching as you grow.
220220+ Svelte, Vue, Solid or plain JavaScript, with which you add on features
221221+ like normalized caching as you grow.
222222 </HeroBody>
223223 <HeroButtonsWrapper>
224224 <HeroNPMWrapper>
···11+// @vitest-environment jsdom
22+33+import { expect, it, describe, vi } from 'vitest';
44+import { createQuery } from './createQuery';
55+import { renderHook } from '@solidjs/testing-library';
66+import { createClient } from '@urql/core';
77+import { createSignal } from 'solid-js';
88+import { makeSubject, pipe, toPromise } from 'wonka';
99+import { OperationResult, OperationResultSource } from '@urql/core';
1010+1111+const client = createClient({
1212+ url: '/graphql',
1313+ exchanges: [],
1414+});
1515+1616+vi.mock('./context', () => {
1717+ const useClient = () => {
1818+ return client!;
1919+ };
2020+2121+ const useQuery = () => {
2222+ // Return a mock query function that wraps with SolidStart's query primitive
2323+ return (fn: any, _key: string) => {
2424+ // Store the query function for later execution
2525+ const queryFn = fn;
2626+ // Return a function that executes the query
2727+ return () => queryFn();
2828+ };
2929+ };
3030+3131+ return { useClient, useQuery };
3232+});
3333+3434+vi.mock('@solidjs/router', () => {
3535+ return {
3636+ query: (fn: any) => fn,
3737+ createAsync: (fn: any) => {
3838+ const [data, setData] = createSignal<any>();
3939+ fn().then(setData);
4040+ return data;
4141+ },
4242+ };
4343+});
4444+4545+describe('createQuery', () => {
4646+ it('should execute a query', async () => {
4747+ const subject =
4848+ makeSubject<Pick<OperationResult<{ test: boolean }, any>, 'data'>>();
4949+ const executeQuery = vi
5050+ .spyOn(client, 'executeQuery')
5151+ .mockImplementation(() => {
5252+ const source = subject.source as OperationResultSource<OperationResult>;
5353+ // Return an object with toPromise method
5454+ return {
5555+ toPromise: () => pipe(source, toPromise),
5656+ } as any;
5757+ });
5858+5959+ const result = renderHook(() =>
6060+ createQuery<{ test: boolean }>('{ test }', 'test-query')
6161+ );
6262+6363+ // Trigger the query
6464+ subject.next({ data: { test: true } });
6565+6666+ await vi.waitFor(() => {
6767+ const data = result.result();
6868+ expect(data).toBeDefined();
6969+ });
7070+7171+ executeQuery.mockRestore();
7272+ });
7373+7474+ it('should respect pause option', () => {
7575+ const executeQuery = vi.spyOn(client, 'executeQuery');
7676+7777+ renderHook(() =>
7878+ createQuery('{ test }', 'test-query-pause', {
7979+ pause: true,
8080+ } as any)
8181+ );
8282+8383+ expect(executeQuery).not.toHaveBeenCalled();
8484+ executeQuery.mockRestore();
8585+ });
8686+8787+ it.skip('should re-execute when reactive variables change', async () => {
8888+ // This test is skipped because SolidStart's query() primitive doesn't
8989+ // automatically re-execute when variables change. This would require
9090+ // using createAsync with a reactive dependency or manually refetching.
9191+ // This is expected behavior for SolidStart.
9292+ });
9393+});
+105
packages/solid-start-urql/src/createQuery.ts
···11+import {
22+ type AnyVariables,
33+ type DocumentInput,
44+ type OperationContext,
55+ type RequestPolicy,
66+ createRequest,
77+ type Client,
88+} from '@urql/core';
99+import { useClient, useQuery } from './context';
1010+1111+/**
1212+ * Creates a cached query function using SolidStart's query primitive.
1313+ *
1414+ * @remarks
1515+ * This function creates a reusable query function that executes a GraphQL query.
1616+ * It uses SolidStart's query primitive for caching and deduplication.
1717+ * Call this at module level, then use the returned function with createAsync in your component.
1818+ *
1919+ * @example
2020+ * ```tsx
2121+ * import { createQuery } from '@urql/solid-start';
2222+ * import { createAsync } from '@solidjs/router';
2323+ * import { gql } from '@urql/core';
2424+ *
2525+ * const POKEMONS_QUERY = gql`
2626+ * query Pokemons {
2727+ * pokemons(limit: 10) {
2828+ * id
2929+ * name
3030+ * }
3131+ * }
3232+ * `;
3333+ *
3434+ * const queryPokemons = createQuery(POKEMONS_QUERY, 'list-pokemons');
3535+ *
3636+ * export default function PokemonList() {
3737+ * const client = useClient();
3838+ * const pokemons = createAsync(() => queryPokemons(client));
3939+ *
4040+ * return (
4141+ * <Show when={pokemons()?.data}>
4242+ * <For each={pokemons()!.data.pokemons}>
4343+ * {pokemon => <li>{pokemon.name}</li>}
4444+ * </For>
4545+ * </Show>
4646+ * );
4747+ * }
4848+ * ```
4949+ */
5050+export function createQuery<
5151+ Data = any,
5252+ Variables extends AnyVariables = AnyVariables,
5353+>(
5454+ queryDocument: DocumentInput<Data, Variables>,
5555+ key: string,
5656+ options?: {
5757+ variables?: Variables;
5858+ requestPolicy?: RequestPolicy;
5959+ context?: Partial<OperationContext>;
6060+ }
6161+) {
6262+ // Get the query function from context
6363+ const queryFn = useQuery();
6464+6565+ // Return the result of calling the query function
6666+ return queryFn(
6767+ async (
6868+ clientOrVariables?: Client | Variables,
6969+ variablesOrContext?: Variables | Partial<OperationContext>,
7070+ contextOverride?: Partial<OperationContext>
7171+ ) => {
7272+ // Determine if first arg is client or variables
7373+ let client: Client;
7474+ let variables: Variables | undefined;
7575+ let context: Partial<OperationContext> | undefined;
7676+7777+ if (
7878+ clientOrVariables &&
7979+ typeof (clientOrVariables as any).executeQuery === 'function'
8080+ ) {
8181+ // First arg is client
8282+ client = clientOrVariables as Client;
8383+ variables = variablesOrContext as Variables | undefined;
8484+ context = contextOverride;
8585+ } else {
8686+ // First arg is variables (or nothing), use useClient
8787+ client = useClient();
8888+ variables = clientOrVariables as Variables | undefined;
8989+ context = variablesOrContext as Partial<OperationContext> | undefined;
9090+ }
9191+9292+ const finalVariables =
9393+ variables !== undefined ? variables : options && options.variables;
9494+ const request = createRequest(queryDocument, finalVariables as Variables);
9595+ const finalContext: Partial<OperationContext> = {
9696+ requestPolicy: options && options.requestPolicy,
9797+ ...(options && options.context),
9898+ ...context,
9999+ };
100100+101101+ return await client.executeQuery(request, finalContext).toPromise();
102102+ },
103103+ key
104104+ );
105105+}
+26
packages/solid-start-urql/src/index.ts
···11+// Re-export everything from @urql/core
22+export * from '@urql/core';
33+44+// Context exports
55+export { type UseClient, type UseQuery, type UrqlContext } from './context';
66+export { useClient, useQuery, Provider } from './context';
77+88+// Query exports
99+export { createQuery } from './createQuery';
1010+1111+// Mutation exports
1212+export { type CreateMutationAction } from './createMutation';
1313+export { createMutation } from './createMutation';
1414+1515+// Subscription exports - re-exported from @urql/solid (no SolidStart-specific changes needed)
1616+export {
1717+ type CreateSubscriptionArgs,
1818+ type CreateSubscriptionState,
1919+ type CreateSubscriptionExecute,
2020+ type CreateSubscriptionResult,
2121+ type SubscriptionHandler,
2222+ createSubscription,
2323+} from '@urql/solid';
2424+2525+// Utility exports
2626+export { type MaybeAccessor } from './utils';
+6
packages/solid-start-urql/src/utils.ts
···11+// Re-export utility types and functions from @solid-primitives/utils
22+export {
33+ access,
44+ asAccessor,
55+ type MaybeAccessor,
66+} from '@solid-primitives/utils';
···11+# @urql/solid
22+33+A highly customizable and versatile GraphQL client for SolidJS.
44+55+> **Note:** This package is for client-side SolidJS applications. If you're building a SolidStart application with SSR, use [`@urql/solid-start`](../solid-start-urql) instead. See the [comparison section](../solid-start-urql#solidjs-vs-solidstart) in the SolidStart package for key differences.
66+77+## Installation
88+99+```bash
1010+npm install @urql/solid @urql/core graphql
1111+# or
1212+pnpm add @urql/solid @urql/core graphql
1313+# or
1414+yarn add @urql/solid @urql/core graphql
1515+```
1616+1717+## Documentation
1818+1919+Full documentation is available at [formidable.com/open-source/urql/docs/](https://formidable.com/open-source/urql/docs/).
2020+2121+## Quick Start
2222+2323+```tsx
2424+import { createClient, Provider, createQuery } from '@urql/solid';
2525+import { cacheExchange, fetchExchange } from '@urql/core';
2626+import { gql } from '@urql/core';
2727+2828+const client = createClient({
2929+ url: 'https://api.example.com/graphql',
3030+ exchanges: [cacheExchange, fetchExchange],
3131+});
3232+3333+const TodosQuery = gql`
3434+ query {
3535+ todos {
3636+ id
3737+ title
3838+ }
3939+ }
4040+`;
4141+4242+function App() {
4343+ return (
4444+ <Provider value={client}>
4545+ <TodoList />
4646+ </Provider>
4747+ );
4848+}
4949+5050+function TodoList() {
5151+ const [result] = createQuery({ query: TodosQuery });
5252+5353+ return (
5454+ <div>
5555+ {result().data?.todos.map(todo => (
5656+ <div>{todo.title}</div>
5757+ ))}
5858+ </div>
5959+ );
6060+}
6161+```
6262+6363+## When to Use @urql/solid vs @urql/solid-start
6464+6565+| Use Case | Package |
6666+|----------|---------|
6767+| Client-side SPA | `@urql/solid` |
6868+| SolidStart with SSR | `@urql/solid-start` |
6969+7070+For a detailed comparison of the APIs and when to use each package, see the [SolidJS vs SolidStart comparison](../solid-start-urql#solidjs-vs-solidstart) in the `@urql/solid-start` documentation.
7171+7272+## License
7373+7474+MIT