Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(svelte): v2 — The return of the functional APIs! (#2370)

* BREAKING CHANGE: urql-svelte v2

* Update packages/svelte-urql/src/subscriptionStore.test.ts

Co-authored-by: Jovi De Croock <decroockjovi@gmail.com>

* chore(urql-svelte): drop placeholderData

* chore(urql-svelte): add requestPolicy input

* chore(urql-svelte): update code comments

* chore(urql-svelte): fix typos

* refactor(urql-svelte): use args interface

similar to react bindings
1. args interface
2. documentNode argument is named "query" for all kinds
3. reduces duplicate code (SSOT)

https://github.com/FormidableLabs/urql/blob/f86763cc565f0c36b948ce9017da0c7c2c302d58/packages/react-urql/src/hooks/useSubscription.ts#L18

* chore: drop unrelated storage update

* fix:(urql-svelte): allow pausing subscription

* chore: rename isPaused to pause

* fix: remove outdated placeholder comments

* Update packages/svelte-urql/src/common.ts

Co-authored-by: Phil Pluckthun <phil@kitten.sh>

* chore: drop common exports
https://github.com/FormidableLabs/urql/pull/2370#discussion_r837941373

* fix: unusbscribe wonka with svelte

https://github.com/FormidableLabs/urql/pull/2370#pullrequestreview-943896034

* chore: clarify wording

https://github.com/FormidableLabs/urql/pull/2370#discussion_r851768603

* feat: restore context helpers

* chore: rename methods to indicate context

* fix: correct typo

* fix: remove typo

* feat: add context to `baseResult.operation`

* refactor: move operation into common

* Refactor: align optimise and align with other bindings

* Add missing subscription handler to mutationStore

* Add changeset

* Remove redundant update from mutationStore

* Update API docs

* Add outdated notice to svelte basics docs

Co-authored-by: Jovi De Croock <decroockjovi@gmail.com>
Co-authored-by: Phil Pluckthun <phil@kitten.sh>

authored by

Jonathan Stanley
Jovi De Croock
Phil Pluckthun
and committed by
GitHub
2114429b 232310fa

+513 -852
+11
.changeset/wise-parents-yawn.md
··· 1 + --- 2 + '@urql/svelte': major 3 + --- 4 + 5 + Reimplement Svelte with functional-only API. 6 + We've gotten plenty of feedback and issues from the Svelte community about our prior 7 + Svelte bindings. These bindings favoured a Store singleton to read and write to, 8 + and a separate signal to start an operation. 9 + Svelte usually however calls for a lot more flexibility, so we're returning the 10 + API to a functional-only API again that serves to only create stores, which is more 11 + similar to the original implementation.
+56 -77
docs/api/svelte.md
··· 5 5 6 6 # Svelte API 7 7 8 - ## operationStore 8 + ## queryStore 9 9 10 - Accepts three arguments as inputs, where only the first one — `query` — is required. 10 + The `queryStore` factory accepts properties as inputs and returns a Svelte pausable, readable store 11 + of results, with type `OperationResultStore & Pausable`. 11 12 12 - | Argument | Type | Description | 13 - | ----------- | ------------------------ | ---------------------------------------------------------------------------------- | 14 - | `query` | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. | 15 - | `variables` | `?object` | The variables to be used with the GraphQL request. | 16 - | `context` | `?object` | Holds the contextual information for the query. | 13 + | Argument | Type | Description | 14 + | --------------- | -------------------------- | -------------------------------------------------------------------------------------------------------- | 15 + | `client` | `Client` | The [`Client`](./core.md#Client) to use for the operation. | 16 + | `query` | `string \| DocumentNode \` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. | 17 + | `variables` | `?object` | The variables to be used with the GraphQL request. | 18 + | `requestPolicy` | `?RequestPolicy` | An optional [request policy](./core.md#requestpolicy) that should be used specifying the cache strategy. | 19 + | `pause` | `?boolean` | A boolean flag instructing [execution to be paused](../basics/vue.md#pausing-usequery). | 20 + | `context` | `?object` | Holds the contextual information for the query. | 17 21 18 - This is a [Svelte Writable Store](https://svelte.dev/docs#writable) that is used by other utilities 19 - listed in these docs to read [`Operation` inputs](./core.md#operation) from and write 20 - [`OperationResult` outputs](./core.md#operationresult) to. 22 + This store is pausable, which means that the result has methods on it to `pause()` or `resume()` 23 + the subscription of the operation. 21 24 22 - The store has several properties on its value. The **writable properties** of it are inputs that are 23 - used by either [`query`](#query), [`mutation`](#mutation), or [`subscription`](#subscription) to 24 - create an [`Operation`](./core.md#operation) to execute. These are `query`, `variables`, and 25 - `context`; the same properties that the `operationStore` accepts as arguments on creation. 25 + [Read more about how to use the `queryStore` API on the "Queries" page.](../basics/svelte.md#queries) 26 26 27 - Additionally the `context` may have a `pause: boolean` property that instructs the `query` and 28 - `subscription` operations to pause execution and freeze the result. 27 + ## mutationStore 29 28 30 - Furthermore the store exposes some **readonly properties** which represent the operation's progress 31 - and [result](./core.md#operationresult). 29 + The `mutationStore` factory accepts properties as inputs and returns a Svelte readable store of a result. 32 30 33 - | Prop | Type | Description | 34 - | ------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | 35 - | `data` | `?any` | Data returned by the specified query | 36 - | `error` | `?CombinedError` | A [`CombinedError`](./core.md#combinederror) instances that wraps network or `GraphQLError`s (if any) | 37 - | `extensions` | `?Record<string, any>` | Extensions that the GraphQL server may have returned. | 38 - | `stale` | `boolean` | A flag that may be set to `true` by exchanges to indicate that the `data` is incomplete or out-of-date, and that the result will be updated soon. | 39 - | `fetching` | `boolean` | A flag that indicates whether the operation is currently in progress, which means that the `data` and `error` is out-of-date for the given inputs. | 31 + | Argument | Type | Description | 32 + | ----------- | -------------------------- | ---------------------------------------------------------------------------------- | 33 + | `client` | `Client` | The [`Client`](./core.md#Client) to use for the operation. | 34 + | `query` | `string \| DocumentNode \` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. | 35 + | `variables` | `?object` | The variables to be used with the GraphQL request. | 36 + | `context` | `?object` | Holds the contextual information for the query. | 40 37 41 - All of the writable properties are updatable either via the common Svelte Writable's `set` or 42 - `update` methods or directly. The `operationStore` exposes setters for the writable properties which 43 - will automatically update the store and notify reactive subscribers. 44 - 45 - In development, trying to update the _readonly_ properties directly or via the `set` or `update` 46 - method will result in a `TypeError` being thrown. 47 - 48 - An additional non-standard method on the store is `reexecute`, which does _almost_ the same as 49 - assigning a new context to the operation. It is syntactic sugar to ensure that an operation may be 50 - reexecuted at any point in time: 38 + [Read more about how to use the `mutation` API on the "Mutations" 39 + page.](../basics/svelte.md#mutations) 51 40 52 - ```js 53 - operationStore(...).reexecute(); 54 - operationStore(...).reexecute({ requestPolicy: 'network-only' }); 55 - ``` 41 + ## subscriptionStore 56 42 57 - [Read more about `writable` stores on the Svelte API docs.](https://svelte.dev/docs#writable) 58 - 59 - ## query 60 - 61 - The `query` utility function only accepts an `operationStore` as its only argument. Per 62 - `operationStore` it should only be called once per component as it lives alongside the component and 63 - hooks into its `onDestroy` lifecycle method. This means that we must avoid passing a reactive 64 - variable to it, and instead must pass the raw `operationStore`. 65 - 66 - This function will return the `operationStore` itself that has been passed. 67 - 68 - [Read more about how to use the `query` API on the "Queries" page.](../basics/svelte.md#queries) 69 - 70 - ## subscription 71 - 72 - The `subscription` utility function accepts an `operationStore` as its first argument, like the 73 - [`query` function](#query). It should also per `operationStore` be called once per component. 43 + The `subscriptionStore` utility function accepts the same inputs as `queryStore` does as its first 44 + argument, [see above](#querystore). 74 45 75 46 The function also optionally accepts a second argument, a `handler` function. This function has the 76 47 following type signature: ··· 83 54 incoming from a subscription event, and may be used to "reduce" the data over time, altering the 84 55 value of `result.data`. 85 56 86 - `subscription` itself will return the `operationStore` that has been passed when called. 87 - 88 57 [Read more about how to use the `subscription` API on the "Subscriptions" 89 58 page.](../advanced/subscriptions.md#svelte) 90 59 91 - ## mutation 60 + ## OperationResultStore 92 61 93 - The `mutation` utility function either accepts an `operationStore` as its only argument or an object 94 - containing `query`, `variables`, and `context` properties. When it receives the latter it will 95 - create an `operationStore` automatically. 62 + A Svelte Readble store of an [`OperationResult`](./core.md#operationresult). 63 + This store will be updated as the incoming data changes. 96 64 97 - The function will return an `executeMutation` callback, which can be used to trigger the mutation. 98 - This callback optionally accepts a `variables` argument and a `context` argument of type 99 - [`Partial<OperationContext>`](./core.md#operationcontext). If these arguments are passed, they will 100 - automatically update the `operationStore` before starting the mutation. 65 + | Prop | Type | Description | 66 + | ------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | 67 + | `data` | `?any` | Data returned by the specified query | 68 + | `error` | `?CombinedError` | A [`CombinedError`](./core.md#combinederror) instances that wraps network or `GraphQLError`s (if any) | 69 + | `extensions` | `?Record<string, any>` | Extensions that the GraphQL server may have returned. | 70 + | `stale` | `boolean` | A flag that may be set to `true` by exchanges to indicate that the `data` is incomplete or out-of-date, and that the result will be updated soon. | 71 + | `fetching` | `boolean` | A flag that indicates whether the operation is currently in progress, which means that the `data` and `error` is out-of-date for the given inputs. | 72 + 73 + ## Pausable 101 74 102 - The `executeMutation` callback will return a promise which resolves to the `operationStore` once the 103 - mutation has been completed. 75 + The `queryStore` and `subscriptionStore`'s stores are pausable. This means they inherit the 76 + following properties from the `Pausable` store. 104 77 105 - [Read more about how to use the `mutation` API on the "Mutations" 106 - page.](../basics/svelte.md#mutations) 78 + | Prop | Type | Description | 79 + | ----------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | 80 + | `isPaused$` | `Readable<boolean>` | A Svelte readable store indicating whether the operation is currently paused. Essentially, this is equivalent to `!fetching` | 81 + | `pause()` | `pause(): void` | This method pauses the ongoing operation. | 82 + | `resume()` | `resume(): void` | This method resumes the previously paused operation. | 107 83 108 84 ## Context API 109 85 110 - In Svelte the [`Client`](./core.md#client) is passed around using [Svelte's Context 111 - API](https://svelte.dev/tutorial/context-api). `@urql/svelte` wraps around Svelte's 112 - [`setContext`](https://svelte.dev/docs#setContext) and 113 - [`getContext`](https://svelte.dev/docs#getContext) functions and exposes: 86 + In `urql`'s Svelte bindings, the [`Client`](./core.md#client) is passed into the factories for 87 + stores above manually. This is to cater to greater flexibility. However, for convenience's sake, 88 + instead of keeping a `Client` singleton, we may also use [Svelte's Context 89 + API](https://svelte.dev/tutorial/context-api). 114 90 115 - - `setClient` 116 - - `getClient` 117 - - `initClient` (a shortcut for `createClient` + `setClient`) 91 + `@urql/svelte` provides wrapper functions around Svelte's [`setContext`](https://svelte.dev/docs#setContext) and 92 + [`getContext`](https://svelte.dev/docs#getContext) functions: 93 + 94 + - `setContextClient` 95 + - `getContextClient` 96 + - `initContextClient` (a shortcut for `createClient` + `setContextClient`)
+3
docs/basics/svelte.md
··· 5 5 6 6 # Svelte 7 7 8 + > **NOTE:** These API docs are for the v1 version of `@urql/svelte` rather than v2. 9 + > [In the meantime, please check out our API docs for our Svelte bindings instead!](../api/svelte.md) 10 + 8 11 ## Getting started 9 12 10 13 This "Getting Started" guide covers how to install and set up `urql` and provide a `Client` for
+40
packages/svelte-urql/src/common.ts
··· 1 + import type { Readable, Writable } from 'svelte/store'; 2 + import type { OperationResult } from '@urql/core'; 3 + import { Source, make } from 'wonka'; 4 + 5 + export interface OperationResultState<Data = any, Variables = object> 6 + extends OperationResult<Data, Variables> { 7 + fetching: boolean; 8 + } 9 + 10 + /** A Readable containing an `OperationResult` with a fetching flag. */ 11 + export type OperationResultStore<Data = any, Variables = object> = Readable< 12 + OperationResultState<Data, Variables> 13 + >; 14 + 15 + export const fromStore = <T>(store$: Readable<T>): Source<T> => 16 + make(observer => store$.subscribe(observer.next)); 17 + 18 + export const initialResult = { 19 + fetching: false, 20 + stale: false, 21 + error: undefined, 22 + data: undefined, 23 + extensions: undefined, 24 + }; 25 + 26 + export interface Pausable { 27 + isPaused$: Writable<boolean>; 28 + pause(): void; 29 + resume(): void; 30 + } 31 + 32 + export const createPausable = (isPaused$: Writable<boolean>): Pausable => ({ 33 + isPaused$, 34 + pause() { 35 + isPaused$.set(true); 36 + }, 37 + resume() { 38 + isPaused$.set(false); 39 + }, 40 + });
+18 -5
packages/svelte-urql/src/context.ts
··· 1 1 import { setContext, getContext } from 'svelte'; 2 2 import { Client, ClientOptions } from '@urql/core'; 3 - import { _contextKey } from './internal'; 3 + 4 + const _contextKey = '$$_urql'; 5 + 6 + /** Retrieves a Client from Svelte's context */ 7 + export const getContextClient = (): Client => { 8 + const client = getContext(_contextKey); 9 + if (process.env.NODE_ENV !== 'production' && !client) { 10 + throw new Error( 11 + 'No urql Client was found in Svelte context. Did you forget to call setContextClient?' 12 + ); 13 + } 4 14 5 - export const getClient = (): Client => getContext(_contextKey); 15 + return client as Client; 16 + }; 6 17 7 - export const setClient = (client: Client): void => { 18 + /** Sets a Client on Svelte's context */ 19 + export const setContextClient = (client: Client): void => { 8 20 setContext(_contextKey, client); 9 21 }; 10 22 11 - export const initClient = (args: ClientOptions): Client => { 23 + /** Creates Client and adds it to Svelte's context */ 24 + export const initContextClient = (args: ClientOptions): Client => { 12 25 const client = new Client(args); 13 - setClient(client); 26 + setContextClient(client); 14 27 return client; 15 28 };
+3 -2
packages/svelte-urql/src/index.ts
··· 1 1 export * from '@urql/core'; 2 - export * from './operationStore'; 2 + export * from './queryStore'; 3 + export * from './mutationStore'; 4 + export * from './subscriptionStore'; 3 5 export * from './context'; 4 - export * from './operations';
-6
packages/svelte-urql/src/internal.ts
··· 1 - export const _contextKey = '$$_urql'; 2 - export const _storeUpdate = new WeakSet<object>(); 3 - export const _markStoreUpdate = 4 - process.env.NODE_ENV !== 'production' 5 - ? (value: object) => _storeUpdate.add(value) 6 - : () => undefined;
+30
packages/svelte-urql/src/mutationStore.test.ts
··· 1 + import { createClient } from '@urql/core'; 2 + import { get } from 'svelte/store'; 3 + import { mutationStore } from './mutationStore'; 4 + 5 + describe('mutationStore', () => { 6 + const client = createClient({ url: 'https://example.com' }); 7 + const variables = {}; 8 + const context = {}; 9 + const query = 10 + 'mutation ($input: Example!) { doExample(input: $input) { id } }'; 11 + const store = mutationStore({ 12 + client, 13 + query, 14 + variables, 15 + context, 16 + }); 17 + 18 + it('creates a svelte store', () => { 19 + const subscriber = jest.fn(); 20 + store.subscribe(subscriber); 21 + expect(subscriber).toHaveBeenCalledTimes(1); 22 + }); 23 + 24 + it('fills the store with correct values', () => { 25 + expect(get(store).operation.kind).toBe('mutation'); 26 + expect(get(store).operation.context.url).toBe('https://example.com'); 27 + expect(get(store).operation.query.loc?.source.body).toBe(query); 28 + expect(get(store).operation.variables).toBe(variables); 29 + }); 30 + });
+74
packages/svelte-urql/src/mutationStore.ts
··· 1 + import type { DocumentNode } from 'graphql'; 2 + import { 3 + Client, 4 + OperationContext, 5 + TypedDocumentNode, 6 + createRequest, 7 + } from '@urql/core'; 8 + import { pipe, map, scan, subscribe } from 'wonka'; 9 + import { derived, writable } from 'svelte/store'; 10 + 11 + import { 12 + OperationResultState, 13 + OperationResultStore, 14 + initialResult, 15 + } from './common'; 16 + 17 + export type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R; 18 + 19 + export interface MutationArgs<Data = any, Variables = object> { 20 + client: Client; 21 + query: string | DocumentNode | TypedDocumentNode<Data, Variables>; 22 + variables: Variables; 23 + context?: Partial<OperationContext>; 24 + } 25 + 26 + export function mutationStore<Data = any, Result = Data, Variables = object>( 27 + args: MutationArgs<Data, Variables>, 28 + handler?: SubscriptionHandler<Data, Result> 29 + ): OperationResultStore<Result, Variables> { 30 + const request = createRequest(args.query, args.variables); 31 + const operation = args.client.createRequestOperation( 32 + 'mutation', 33 + request, 34 + args.context 35 + ); 36 + const initialState: OperationResultState<Result, Variables> = { 37 + ...initialResult, 38 + operation, 39 + fetching: true, 40 + }; 41 + const result$ = writable(initialState); 42 + 43 + const subscription = pipe( 44 + pipe( 45 + args.client.executeRequestOperation(operation), 46 + map(({ stale, data, error, extensions, operation }) => ({ 47 + fetching: false, 48 + stale: !!stale, 49 + data, 50 + error, 51 + operation, 52 + extensions, 53 + })) 54 + ), 55 + scan((result: OperationResultState<Result, Variables>, partial: any) => { 56 + // If a handler has been passed, it's used to merge new data in 57 + const data = 58 + partial.data !== undefined 59 + ? typeof handler === 'function' 60 + ? handler(result.data, partial.data) 61 + : partial.data 62 + : result.data; 63 + return { ...result, ...partial, data }; 64 + }, initialState), 65 + subscribe(result => { 66 + result$.set(result); 67 + }) 68 + ); 69 + 70 + return derived(result$, (result, set) => { 71 + set(result); 72 + return subscription.unsubscribe; 73 + }); 74 + }
-121
packages/svelte-urql/src/operationStore.test.ts
··· 1 - import { operationStore } from './operationStore'; 2 - import { _markStoreUpdate } from './internal'; 3 - 4 - it('instantiates an operation container acting as a store', () => { 5 - const variables = {}; 6 - const context = {}; 7 - const store = operationStore('{ test }', variables, context); 8 - 9 - expect(store.query).toBe('{ test }'); 10 - expect(store.variables).toBe(variables); 11 - expect(store.context).toBe(context); 12 - 13 - const subscriber = jest.fn(); 14 - 15 - store.subscribe(subscriber); 16 - 17 - expect(subscriber).toHaveBeenCalledWith(store); 18 - expect(subscriber).toHaveBeenCalledTimes(1); 19 - 20 - store.set({ query: '{ test2 }' }); 21 - expect(subscriber).toHaveBeenCalledWith(store); 22 - expect(subscriber).toHaveBeenCalledTimes(2); 23 - expect(store.query).toBe('{ test2 }'); 24 - }); 25 - 26 - it('adds getters and setters for known values', () => { 27 - const variables = {}; 28 - const context = {}; 29 - const store = operationStore('{ test }', variables, context); 30 - 31 - const update = { 32 - query: '{ update }', 33 - variables: undefined, 34 - context: { requestPolicy: 'cache-and-network' }, 35 - stale: true, 36 - fetching: true, 37 - data: { update: true }, 38 - error: undefined, 39 - extensions: undefined, 40 - }; 41 - 42 - _markStoreUpdate(update); 43 - store.set(update as any); 44 - 45 - expect(store.query).toBe(update.query); 46 - expect(store.variables).toEqual({}); 47 - expect(store.context).toBe(update.context); 48 - expect(store.stale).toBe(update.stale); 49 - expect(store.fetching).toBe(update.fetching); 50 - expect(store.data).toBe(update.data); 51 - expect(store.error).toBe(update.error); 52 - expect(store.extensions).toBe(update.extensions); 53 - 54 - const subscriber = jest.fn(); 55 - store.subscribe(subscriber); 56 - expect(subscriber).toHaveBeenCalledTimes(1); 57 - 58 - const state = subscriber.mock.calls[0][0]; 59 - expect(state.stale).toBe(true); 60 - expect(state.query).toBe('{ update }'); 61 - 62 - store.query = '{ imperative }'; 63 - expect(subscriber).toHaveBeenCalledTimes(2); 64 - expect(store.query).toBe('{ imperative }'); 65 - }); 66 - 67 - it('adds stale when not present in update', () => { 68 - const variables = {}; 69 - const context = {}; 70 - const store = operationStore('{ test }', variables, context); 71 - 72 - const update = { 73 - query: '{ update }', 74 - variables: undefined, 75 - context: { requestPolicy: 'cache-and-network' }, 76 - fetching: true, 77 - data: { update: true }, 78 - error: undefined, 79 - extensions: undefined, 80 - }; 81 - 82 - _markStoreUpdate(update); 83 - store.set(update as any); 84 - 85 - expect(store.query).toBe(update.query); 86 - expect(store.variables).toEqual({}); 87 - expect(store.context).toBe(update.context); 88 - expect(store.stale).toBe(false); 89 - expect(store.fetching).toBe(update.fetching); 90 - expect(store.data).toBe(update.data); 91 - expect(store.error).toBe(update.error); 92 - expect(store.extensions).toBe(update.extensions); 93 - 94 - const subscriber = jest.fn(); 95 - store.subscribe(subscriber); 96 - expect(subscriber).toHaveBeenCalledTimes(1); 97 - 98 - const state = subscriber.mock.calls[0][0]; 99 - expect(state.stale).toBe(false); 100 - expect(state.query).toBe('{ update }'); 101 - 102 - store.query = '{ imperative }'; 103 - expect(subscriber).toHaveBeenCalledTimes(2); 104 - expect(store.query).toBe('{ imperative }'); 105 - }); 106 - 107 - it('throws when illegal values are set', () => { 108 - const store = operationStore('{ test }'); 109 - 110 - expect(() => { 111 - (store as any).variables = {}; 112 - }).not.toThrow(); 113 - 114 - expect(() => { 115 - (store as any).data = {}; 116 - }).toThrow(); 117 - 118 - expect(() => { 119 - (store as any).set({ error: null }); 120 - }).toThrow(); 121 - });
-177
packages/svelte-urql/src/operationStore.ts
··· 1 - import { Readable, writable } from 'svelte/store'; 2 - import { DocumentNode } from 'graphql'; 3 - import { TypedDocumentNode } from '@graphql-typed-document-node/core'; 4 - import { 5 - OperationContext, 6 - CombinedError, 7 - createRequest, 8 - stringifyVariables, 9 - } from '@urql/core'; 10 - 11 - import { _storeUpdate } from './internal'; 12 - 13 - type Updater<T> = (value: T) => T; 14 - 15 - /** 16 - * This Svelte store wraps both a `GraphQLRequest` and an `OperationResult`. 17 - * It can be used to update the query and read the subsequent result back. 18 - */ 19 - export interface OperationStore<Data = any, Vars = any, Result = Data> 20 - extends Readable<OperationStore<Data, Vars, Result>> { 21 - // Input properties 22 - query: DocumentNode | TypedDocumentNode<Data, Vars> | string; 23 - variables: Vars | null; 24 - context: Partial<OperationContext & { pause: boolean }> | undefined; 25 - // Output properties 26 - readonly stale: boolean; 27 - readonly fetching: boolean; 28 - readonly data: Result | undefined; 29 - readonly error: CombinedError | undefined; 30 - readonly extensions: Record<string, any> | undefined; 31 - // Writable properties 32 - set(value: Partial<OperationStore<Data, Vars, Result>>): void; 33 - update(updater: Updater<Partial<OperationStore<Data, Vars, Result>>>): void; 34 - // Imperative methods 35 - reexecute(context?: Partial<OperationContext> | undefined): void; 36 - } 37 - 38 - export function operationStore<Data = any, Vars = object, Result = Data>( 39 - query: string | DocumentNode | TypedDocumentNode<Data, Vars>, 40 - variables?: Vars | null, 41 - context?: Partial<OperationContext & { pause: boolean }> 42 - ): OperationStore<Data, Vars, Result> { 43 - const internal = { 44 - query, 45 - variables: variables || null, 46 - context, 47 - }; 48 - 49 - const state = { 50 - stale: false, 51 - fetching: false, 52 - data: undefined, 53 - error: undefined, 54 - extensions: undefined, 55 - } as OperationStore<Data, Vars, Result>; 56 - 57 - const svelteStore = writable(state); 58 - let _internalUpdate = false; 59 - 60 - state.set = function set(value?: Partial<typeof state>) { 61 - if (!value || value === state) return; 62 - 63 - _internalUpdate = true; 64 - if (process.env.NODE_ENV !== 'production') { 65 - if (!_storeUpdate.has(value!)) { 66 - for (const key in value) { 67 - if (!(key in internal)) { 68 - throw new TypeError( 69 - 'It is not allowed to update result properties on an OperationStore.' 70 - ); 71 - } 72 - } 73 - } 74 - } 75 - 76 - let hasUpdate = false; 77 - 78 - if ('query' in value! || 'variables' in value!) { 79 - const prev = createRequest( 80 - internal.query, 81 - internal.variables || ({} as Vars) 82 - ); 83 - const next = createRequest( 84 - value.query || internal.query, 85 - value.variables || internal.variables 86 - ); 87 - if (prev.key !== next.key) { 88 - hasUpdate = true; 89 - internal.query = value.query || internal.query; 90 - internal.variables = value.variables || internal.variables || null; 91 - } 92 - } 93 - 94 - if ('context' in value!) { 95 - const prevKey = stringifyVariables(internal.context); 96 - const nextKey = stringifyVariables(value.context); 97 - if (prevKey !== nextKey) { 98 - hasUpdate = true; 99 - internal.context = value.context; 100 - } 101 - } 102 - 103 - for (const key in value) { 104 - if (key === 'query' || key === 'variables' || key === 'context') { 105 - continue; 106 - } else if (key === 'fetching') { 107 - (state as any)[key] = !!value[key]; 108 - } else if (key in state) { 109 - state[key] = value[key]; 110 - } 111 - 112 - hasUpdate = true; 113 - } 114 - 115 - (state as any).stale = !!value!.stale; 116 - 117 - _internalUpdate = false; 118 - if (hasUpdate) svelteStore.set(state); 119 - }; 120 - 121 - state.update = function update(fn: Updater<typeof state>): void { 122 - state.set(fn(state)); 123 - }; 124 - 125 - state.subscribe = function subscribe(run, invalidate) { 126 - return svelteStore.subscribe(run, invalidate); 127 - }; 128 - 129 - state.reexecute = function (context) { 130 - internal.context = { ...(context || internal.context) }; 131 - svelteStore.set(state); 132 - }; 133 - 134 - Object.keys(internal).forEach(prop => { 135 - Object.defineProperty(state, prop, { 136 - configurable: false, 137 - get: () => internal[prop], 138 - set(value) { 139 - internal[prop] = value; 140 - if (!_internalUpdate) svelteStore.set(state); 141 - }, 142 - }); 143 - }); 144 - 145 - if (process.env.NODE_ENV !== 'production') { 146 - const result = { ...state }; 147 - 148 - Object.keys(state).forEach(prop => { 149 - Object.defineProperty(result, prop, { 150 - configurable: false, 151 - get() { 152 - return state[prop]; 153 - }, 154 - set() { 155 - throw new TypeError( 156 - 'It is not allowed to update result properties on an OperationStore.' 157 - ); 158 - }, 159 - }); 160 - }); 161 - 162 - Object.keys(internal).forEach(prop => { 163 - Object.defineProperty(result, prop, { 164 - configurable: false, 165 - get: () => internal[prop], 166 - set(value) { 167 - internal[prop] = value; 168 - if (!_internalUpdate) svelteStore.set(state); 169 - }, 170 - }); 171 - }); 172 - 173 - return result; 174 - } 175 - 176 - return state; 177 - }
-266
packages/svelte-urql/src/operations.test.ts
··· 1 - import { makeSubject } from 'wonka'; 2 - import { createClient } from '@urql/core'; 3 - import { operationStore } from './operationStore'; 4 - import { query, subscription, mutation } from './operations'; 5 - 6 - const client = createClient({ url: '/graphql', exchanges: [] }); 7 - 8 - jest.mock('./context', () => ({ getClient: () => client })); 9 - jest.mock('svelte', () => ({ onDestroy: () => undefined })); 10 - 11 - beforeEach(() => { 12 - jest.resetAllMocks(); 13 - }); 14 - 15 - describe('query', () => { 16 - it('subscribes to a query and updates data', () => { 17 - const subscriber = jest.fn(); 18 - const subject = makeSubject<any>(); 19 - const executeQuery = jest 20 - .spyOn(client, 'executeQuery') 21 - .mockImplementation(() => subject.source); 22 - 23 - const store = operationStore('{ test }'); 24 - store.subscribe(subscriber); 25 - 26 - query(store); 27 - 28 - expect(executeQuery).toHaveBeenCalledWith( 29 - { 30 - key: expect.any(Number), 31 - query: expect.any(Object), 32 - variables: {}, 33 - context: undefined, 34 - }, 35 - undefined 36 - ); 37 - 38 - expect(subscriber).toHaveBeenCalledTimes(2); 39 - expect(store.fetching).toBe(true); 40 - 41 - subject.next({ data: { test: true } }); 42 - expect(subscriber).toHaveBeenCalledTimes(3); 43 - expect(store.data).toEqual({ test: true }); 44 - 45 - subject.complete(); 46 - expect(subscriber).toHaveBeenCalledTimes(4); 47 - expect(store.fetching).toBe(false); 48 - }); 49 - 50 - it('pauses the query when requested to do so', () => { 51 - const subscriber = jest.fn(); 52 - const subject = makeSubject<any>(); 53 - const executeQuery = jest 54 - .spyOn(client, 'executeQuery') 55 - .mockImplementation(() => subject.source); 56 - 57 - const store = operationStore('{ test }', undefined, { pause: true }); 58 - store.subscribe(subscriber); 59 - 60 - query(store); 61 - 62 - expect(executeQuery).not.toHaveBeenCalled(); 63 - expect(subscriber).toHaveBeenCalledTimes(2); 64 - expect(store.fetching).toBe(false); 65 - expect(store.stale).toBe(false); 66 - 67 - store.set({ context: { pause: false } }); 68 - expect(executeQuery).toHaveBeenCalled(); 69 - expect(subscriber).toHaveBeenCalledTimes(4); 70 - expect(store.fetching).toBe(true); 71 - }); 72 - 73 - it('updates the executed query when inputs change', () => { 74 - const subscriber = jest.fn(); 75 - const subject = makeSubject<any>(); 76 - const executeQuery = jest 77 - .spyOn(client, 'executeQuery') 78 - .mockImplementation(() => subject.source); 79 - 80 - const store = operationStore('{ test }'); 81 - store.subscribe(subscriber); 82 - 83 - query(store); 84 - 85 - expect(executeQuery).toHaveBeenCalledWith( 86 - { 87 - key: expect.any(Number), 88 - query: expect.any(Object), 89 - variables: {}, 90 - context: undefined, 91 - }, 92 - undefined 93 - ); 94 - 95 - subject.next({ data: { test: true } }); 96 - expect(subscriber).toHaveBeenCalledTimes(3); 97 - expect(store.data).toEqual({ test: true }); 98 - 99 - store.variables = { test: true }; 100 - expect(executeQuery).toHaveBeenCalledTimes(2); 101 - expect(executeQuery).toHaveBeenCalledWith( 102 - { 103 - key: expect.any(Number), 104 - query: expect.any(Object), 105 - variables: { test: true }, 106 - context: undefined, 107 - }, 108 - undefined 109 - ); 110 - 111 - expect(subscriber).toHaveBeenCalledTimes(5); 112 - expect(store.fetching).toBe(true); 113 - expect(store.data).toEqual({ test: true }); 114 - 115 - store.context = { requestPolicy: 'cache-and-network' }; 116 - expect(executeQuery).toHaveBeenCalledTimes(3); 117 - expect(executeQuery).toHaveBeenCalledWith( 118 - { 119 - key: expect.any(Number), 120 - query: expect.any(Object), 121 - variables: { test: true }, 122 - context: { requestPolicy: 'cache-and-network' }, 123 - }, 124 - { 125 - requestPolicy: 'cache-and-network', 126 - } 127 - ); 128 - }); 129 - }); 130 - 131 - describe('subscription', () => { 132 - it('subscribes to a subscription and updates data', () => { 133 - const subscriber = jest.fn(); 134 - const subject = makeSubject<any>(); 135 - const executeQuery = jest 136 - .spyOn(client, 'executeSubscription') 137 - .mockImplementation(() => subject.source); 138 - 139 - const store = operationStore('subscription { test }'); 140 - store.subscribe(subscriber); 141 - 142 - subscription(store); 143 - 144 - expect(executeQuery).toHaveBeenCalledWith( 145 - { 146 - key: expect.any(Number), 147 - query: expect.any(Object), 148 - variables: {}, 149 - }, 150 - undefined 151 - ); 152 - 153 - expect(subscriber).toHaveBeenCalledTimes(2); 154 - expect(store.fetching).toBe(true); 155 - 156 - subject.next({ data: { test: true } }); 157 - expect(subscriber).toHaveBeenCalledTimes(3); 158 - expect(store.data).toEqual({ test: true }); 159 - 160 - subject.complete(); 161 - expect(subscriber).toHaveBeenCalledTimes(4); 162 - expect(store.fetching).toBe(false); 163 - }); 164 - 165 - it('updates the executed subscription when inputs change', () => { 166 - const subscriber = jest.fn(); 167 - const subject = makeSubject<any>(); 168 - const executeSubscription = jest 169 - .spyOn(client, 'executeSubscription') 170 - .mockImplementation(() => subject.source); 171 - 172 - const store = operationStore('{ test }'); 173 - store.subscribe(subscriber); 174 - 175 - subscription(store); 176 - 177 - expect(executeSubscription).toHaveBeenCalledWith( 178 - { 179 - key: expect.any(Number), 180 - query: expect.any(Object), 181 - variables: {}, 182 - }, 183 - undefined 184 - ); 185 - 186 - subject.next({ data: { test: true } }); 187 - expect(subscriber).toHaveBeenCalledTimes(3); 188 - expect(store.data).toEqual({ test: true }); 189 - 190 - store.variables = { test: true }; 191 - expect(executeSubscription).toHaveBeenCalledTimes(2); 192 - expect(executeSubscription).toHaveBeenCalledWith( 193 - { 194 - key: expect.any(Number), 195 - query: expect.any(Object), 196 - variables: { test: true }, 197 - }, 198 - undefined 199 - ); 200 - 201 - expect(subscriber).toHaveBeenCalledTimes(5); 202 - expect(store.fetching).toBe(true); 203 - expect(store.data).toEqual({ test: true }); 204 - }); 205 - 206 - it('supports a custom scanning handler', () => { 207 - const subscriber = jest.fn(); 208 - const subject = makeSubject<any>(); 209 - const executeSubscription = jest 210 - .spyOn(client, 'executeSubscription') 211 - .mockImplementation(() => subject.source); 212 - 213 - const store = operationStore('subscription { counter }'); 214 - store.subscribe(subscriber); 215 - 216 - subscription(store, (prev, current) => ({ 217 - counter: (prev ? prev.counter : 0) + current.counter, 218 - })); 219 - 220 - expect(executeSubscription).toHaveBeenCalledWith( 221 - { 222 - key: expect.any(Number), 223 - query: expect.any(Object), 224 - variables: {}, 225 - }, 226 - undefined 227 - ); 228 - 229 - subject.next({ data: { counter: 1 } }); 230 - expect(subscriber).toHaveBeenCalledTimes(3); 231 - expect(store.data).toEqual({ counter: 1 }); 232 - 233 - subject.next({ data: { counter: 2 } }); 234 - expect(subscriber).toHaveBeenCalledTimes(4); 235 - expect(store.data).toEqual({ counter: 3 }); 236 - }); 237 - }); 238 - 239 - describe('mutation', () => { 240 - it('provides an execute method that resolves a promise', async () => { 241 - const subscriber = jest.fn(); 242 - const subject = makeSubject<any>(); 243 - const clientMutation = jest 244 - .spyOn(client, 'executeMutation') 245 - .mockImplementation((): any => subject.source); 246 - 247 - const store = operationStore('mutation { test }', { test: false }); 248 - store.subscribe(subscriber); 249 - 250 - const start = mutation(store); 251 - expect(subscriber).toHaveBeenCalledTimes(1); 252 - expect(clientMutation).not.toHaveBeenCalled(); 253 - 254 - const result$ = start({ test: true }); 255 - expect(subscriber).toHaveBeenCalledTimes(2); 256 - expect(store.fetching).toBe(true); 257 - expect(store.variables).toEqual({ test: true }); 258 - 259 - subject.next({ data: { test: true } }); 260 - expect(await result$).toEqual(store); 261 - 262 - expect(subscriber).toHaveBeenCalledTimes(3); 263 - expect(store.fetching).toBe(false); 264 - expect(store.data).toEqual({ test: true }); 265 - }); 266 - });
-198
packages/svelte-urql/src/operations.ts
··· 1 - import { onDestroy } from 'svelte'; 2 - 3 - import { 4 - createRequest, 5 - OperationContext, 6 - OperationResult, 7 - GraphQLRequest, 8 - TypedDocumentNode, 9 - } from '@urql/core'; 10 - 11 - import { 12 - Source, 13 - pipe, 14 - map, 15 - make, 16 - scan, 17 - concat, 18 - fromValue, 19 - switchMap, 20 - subscribe, 21 - toPromise, 22 - take, 23 - } from 'wonka'; 24 - 25 - import { OperationStore, operationStore } from './operationStore'; 26 - import { getClient } from './context'; 27 - import { _markStoreUpdate } from './internal'; 28 - import { DocumentNode } from 'graphql'; 29 - 30 - interface SourceRequest<Data = any, Variables = object> 31 - extends GraphQLRequest<Data, Variables> { 32 - context?: Partial<OperationContext> & { 33 - pause?: boolean; 34 - }; 35 - } 36 - 37 - const baseState = { 38 - fetching: false, 39 - stale: false, 40 - error: undefined, 41 - data: undefined, 42 - extensions: undefined, 43 - }; 44 - 45 - function toSource<Data, Variables, Result>( 46 - store: OperationStore<Data, Variables, Result> 47 - ) { 48 - return make<SourceRequest<Data, Variables>>(observer => { 49 - let _key: number | void; 50 - let _context: object | void = {}; 51 - 52 - return store.subscribe(state => { 53 - const request = createRequest<Data, Variables>( 54 - state.query, 55 - state.variables! 56 - ) as SourceRequest<Data, Variables>; 57 - if ( 58 - (request.context = state.context) !== _context || 59 - request.key !== _key 60 - ) { 61 - _key = request.key; 62 - _context = state.context; 63 - observer.next(request); 64 - } 65 - }); 66 - }); 67 - } 68 - 69 - export function query<Data = any, Variables = object>( 70 - store: OperationStore<Data, Variables> 71 - ): OperationStore<Data, Variables> { 72 - const client = getClient(); 73 - const subscription = pipe( 74 - toSource(store), 75 - switchMap(request => { 76 - if (request.context && request.context.pause) { 77 - return fromValue({ fetching: false, stale: false }); 78 - } 79 - 80 - return concat([ 81 - fromValue({ fetching: true, stale: false }), 82 - pipe( 83 - client.executeQuery<Data, Variables>(request, request.context!), 84 - map(result => ({ 85 - fetching: false, 86 - ...result, 87 - stale: !!result.stale, 88 - })) 89 - ), 90 - fromValue({ fetching: false, stale: false }), 91 - ]); 92 - }), 93 - scan( 94 - (result: Partial<OperationResult<Data, Variables>>, partial) => ({ 95 - ...result, 96 - ...partial, 97 - }), 98 - baseState 99 - ), 100 - subscribe(update => { 101 - _markStoreUpdate(update); 102 - store.set(update as OperationStore); 103 - }) 104 - ); 105 - 106 - onDestroy(subscription.unsubscribe); 107 - return store; 108 - } 109 - 110 - export type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R; 111 - 112 - export function subscription<Data = any, Result = Data, Variables = object>( 113 - store: OperationStore<Data, Variables, any>, 114 - handler?: SubscriptionHandler<Data, Result> 115 - ): OperationStore<Data, Variables, Result> { 116 - const client = getClient(); 117 - const subscription = pipe( 118 - toSource(store), 119 - switchMap( 120 - (request): Source<Partial<OperationStore>> => { 121 - if (request.context && request.context.pause) { 122 - return fromValue({ fetching: false }); 123 - } 124 - 125 - return concat<Partial<OperationStore>>([ 126 - fromValue({ fetching: true }), 127 - client.executeSubscription(request, store.context), 128 - fromValue({ fetching: false }), 129 - ]); 130 - } 131 - ), 132 - scan( 133 - (result: Partial<OperationResult<Result, Variables>>, partial: any) => { 134 - const data = 135 - partial.data !== undefined 136 - ? typeof handler === 'function' 137 - ? handler(result.data, partial.data) 138 - : partial.data 139 - : result.data; 140 - return { ...result, ...partial, data, stale: false }; 141 - }, 142 - baseState 143 - ), 144 - subscribe(update => { 145 - _markStoreUpdate(update); 146 - store.set(update); 147 - }) 148 - ); 149 - 150 - onDestroy(subscription.unsubscribe); 151 - return store; 152 - } 153 - 154 - export type ExecuteMutation<Data = any, Variables = object> = ( 155 - variables?: Variables, 156 - context?: Partial<OperationContext> 157 - ) => Promise<OperationStore<Data, Variables>>; 158 - 159 - interface GraphQLRequestInput<Data = any, Variables = object> { 160 - key?: number; 161 - query: DocumentNode | TypedDocumentNode<Data, Variables> | string; 162 - variables?: Variables; 163 - } 164 - 165 - export function mutation<Data = any, Variables = object>( 166 - input: GraphQLRequestInput<Data, Variables> | OperationStore<Data, Variables> 167 - ): ExecuteMutation<Data, Variables> { 168 - const client = getClient(); 169 - 170 - const store = 171 - typeof (input as any).subscribe !== 'function' 172 - ? operationStore<Data, Variables>(input.query, input.variables) 173 - : (input as OperationStore<Data, Variables>); 174 - 175 - return (vars, context) => { 176 - const update = { 177 - fetching: true, 178 - variables: vars || store.variables, 179 - context: context || store.context, 180 - }; 181 - 182 - _markStoreUpdate(update); 183 - store.set(update); 184 - return pipe( 185 - client.executeMutation( 186 - createRequest(store.query, store.variables || ({} as Variables)), 187 - store.context 188 - ), 189 - take(1), 190 - toPromise 191 - ).then(result => { 192 - const update = { fetching: false, ...result }; 193 - _markStoreUpdate(update); 194 - store.set(update); 195 - return store; 196 - }); 197 - }; 198 - }
+34
packages/svelte-urql/src/queryStore.test.ts
··· 1 + import { createClient } from '@urql/core'; 2 + import { queryStore } from './queryStore'; 3 + import { get } from 'svelte/store'; 4 + 5 + describe('queryStore', () => { 6 + const client = createClient({ url: 'https://example.com' }); 7 + const variables = {}; 8 + const context = {}; 9 + const query = '{ test }'; 10 + const store = queryStore({ client, query, variables, context }); 11 + 12 + it('creates a svelte store', () => { 13 + const subscriber = jest.fn(); 14 + store.subscribe(subscriber); 15 + expect(subscriber).toHaveBeenCalledTimes(1); 16 + }); 17 + 18 + it('fills the store with correct values', () => { 19 + expect(get(store).operation.kind).toBe('query'); 20 + expect(get(store).operation.context.url).toBe('https://example.com'); 21 + expect(get(store).operation.query.loc?.source.body).toBe(query); 22 + expect(get(store).operation.variables).toBe(variables); 23 + }); 24 + 25 + it('adds pause handles', () => { 26 + expect(get(store.isPaused$)).toBe(false); 27 + 28 + store.pause(); 29 + expect(get(store.isPaused$)).toBe(true); 30 + 31 + store.resume(); 32 + expect(get(store.isPaused$)).toBe(false); 33 + }); 34 + });
+106
packages/svelte-urql/src/queryStore.ts
··· 1 + import type { DocumentNode } from 'graphql'; 2 + import { 3 + Client, 4 + OperationContext, 5 + TypedDocumentNode, 6 + RequestPolicy, 7 + createRequest, 8 + } from '@urql/core'; 9 + import { 10 + Source, 11 + pipe, 12 + map, 13 + fromValue, 14 + switchMap, 15 + subscribe, 16 + concat, 17 + scan, 18 + never, 19 + } from 'wonka'; 20 + import { derived, writable } from 'svelte/store'; 21 + 22 + import { 23 + OperationResultState, 24 + OperationResultStore, 25 + Pausable, 26 + initialResult, 27 + createPausable, 28 + fromStore, 29 + } from './common'; 30 + 31 + export interface QueryArgs<Data = any, Variables = object> { 32 + client: Client; 33 + query: string | DocumentNode | TypedDocumentNode<Data, Variables>; 34 + variables: Variables; 35 + context?: Partial<OperationContext>; 36 + requestPolicy?: RequestPolicy; 37 + pause?: boolean; 38 + } 39 + 40 + export function queryStore<Data = any, Variables = object>( 41 + args: QueryArgs<Data, Variables> 42 + ): OperationResultStore<Data, Variables> & Pausable { 43 + const request = createRequest(args.query, args.variables); 44 + 45 + const context: Partial<OperationContext> = { 46 + requestPolicy: args.requestPolicy, 47 + ...args.context, 48 + }; 49 + 50 + const operation = args.client.createRequestOperation( 51 + 'query', 52 + request, 53 + context 54 + ); 55 + const initialState: OperationResultState<Data, Variables> = { 56 + ...initialResult, 57 + operation, 58 + }; 59 + const result$ = writable(initialState); 60 + const isPaused$ = writable(!!args.pause); 61 + 62 + const subscription = pipe( 63 + fromStore(isPaused$), 64 + switchMap( 65 + (isPaused): Source<Partial<OperationResultState<Data, Variables>>> => { 66 + if (isPaused) { 67 + return never as any; 68 + } 69 + 70 + return concat<Partial<OperationResultState<Data, Variables>>>([ 71 + fromValue({ fetching: true, stale: false }), 72 + pipe( 73 + args.client.executeRequestOperation(operation), 74 + map(({ stale, data, error, extensions, operation }) => ({ 75 + fetching: false, 76 + stale: !!stale, 77 + data, 78 + error, 79 + operation, 80 + extensions, 81 + })) 82 + ), 83 + fromValue({ fetching: false }), 84 + ]); 85 + } 86 + ), 87 + scan( 88 + (result: OperationResultState<Data, Variables>, partial) => ({ 89 + ...result, 90 + ...partial, 91 + }), 92 + initialState 93 + ), 94 + subscribe(result => { 95 + result$.set(result); 96 + }) 97 + ); 98 + 99 + return { 100 + ...derived(result$, (result, set) => { 101 + set(result); 102 + return subscription.unsubscribe; 103 + }), 104 + ...createPausable(isPaused$), 105 + }; 106 + }
+39
packages/svelte-urql/src/subscriptionStore.test.ts
··· 1 + import { createClient } from '@urql/core'; 2 + import { get } from 'svelte/store'; 3 + import { subscriptionStore } from './subscriptionStore'; 4 + 5 + describe('subscriptionStore', () => { 6 + const client = createClient({ url: 'https://example.com' }); 7 + const variables = {}; 8 + const context = {}; 9 + const query = `subscription ($input: ExampleInput) { exampleSubscribe(input: $input) { data } }`; 10 + const store = subscriptionStore({ 11 + client, 12 + query, 13 + variables, 14 + context, 15 + }); 16 + 17 + it('creates a svelte store', () => { 18 + const subscriber = jest.fn(); 19 + store.subscribe(subscriber); 20 + expect(subscriber).toHaveBeenCalledTimes(1); 21 + }); 22 + 23 + it('fills the store with correct values', () => { 24 + expect(get(store).operation.kind).toBe('subscription'); 25 + expect(get(store).operation.context.url).toBe('https://example.com'); 26 + expect(get(store).operation.query.loc?.source.body).toBe(query); 27 + expect(get(store).operation.variables).toBe(variables); 28 + }); 29 + 30 + it('adds pause handles', () => { 31 + expect(get(store.isPaused$)).toBe(false); 32 + 33 + store.pause(); 34 + expect(get(store.isPaused$)).toBe(true); 35 + 36 + store.resume(); 37 + expect(get(store.isPaused$)).toBe(false); 38 + }); 39 + });
+99
packages/svelte-urql/src/subscriptionStore.ts
··· 1 + import type { DocumentNode } from 'graphql'; 2 + import { 3 + Client, 4 + OperationContext, 5 + TypedDocumentNode, 6 + createRequest, 7 + } from '@urql/core'; 8 + import { 9 + Source, 10 + pipe, 11 + map, 12 + fromValue, 13 + switchMap, 14 + subscribe, 15 + concat, 16 + scan, 17 + never, 18 + } from 'wonka'; 19 + import { derived, writable } from 'svelte/store'; 20 + 21 + import { 22 + OperationResultState, 23 + OperationResultStore, 24 + Pausable, 25 + initialResult, 26 + createPausable, 27 + fromStore, 28 + } from './common'; 29 + 30 + export interface SubscriptionArgs<Data = any, Variables = object> { 31 + client: Client; 32 + query: string | DocumentNode | TypedDocumentNode<Data, Variables>; 33 + variables: Variables; 34 + context?: Partial<OperationContext>; 35 + pause?: boolean; 36 + } 37 + 38 + export function subscriptionStore<Data, Variables extends object = {}>( 39 + args: SubscriptionArgs<Data, Variables> 40 + ): OperationResultStore<Data, Variables> & Pausable { 41 + const request = createRequest(args.query, args.variables); 42 + 43 + const operation = args.client.createRequestOperation( 44 + 'subscription', 45 + request, 46 + args.context 47 + ); 48 + const initialState: OperationResultState<Data, Variables> = { 49 + ...initialResult, 50 + operation, 51 + }; 52 + const result$ = writable(initialState); 53 + const isPaused$ = writable(!!args.pause); 54 + 55 + const subscription = pipe( 56 + fromStore(isPaused$), 57 + switchMap( 58 + (isPaused): Source<Partial<OperationResultState<Data, Variables>>> => { 59 + if (isPaused) { 60 + return never as any; 61 + } 62 + 63 + return concat<Partial<OperationResultState<Data, Variables>>>([ 64 + fromValue({ fetching: true, stale: false }), 65 + pipe( 66 + args.client.executeRequestOperation(operation), 67 + map(({ stale, data, error, extensions, operation }) => ({ 68 + fetching: true, 69 + stale: !!stale, 70 + data, 71 + error, 72 + operation, 73 + extensions, 74 + })) 75 + ), 76 + fromValue({ fetching: false }), 77 + ]); 78 + } 79 + ), 80 + scan( 81 + (result: OperationResultState<Data, Variables>, partial) => ({ 82 + ...result, 83 + ...partial, 84 + }), 85 + initialState 86 + ), 87 + subscribe(result => { 88 + result$.set(result); 89 + }) 90 + ); 91 + 92 + return { 93 + ...derived(result$, (result, set) => { 94 + set(result); 95 + return subscription.unsubscribe; 96 + }), 97 + ...createPausable(isPaused$), 98 + }; 99 + }