@firsthandjs/data-apollo 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -21
- package/dist/index.d.ts +69 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,34 +1,50 @@
|
|
|
1
1
|
# @firsthandjs/data-apollo
|
|
2
2
|
|
|
3
|
-
Apollo Client
|
|
3
|
+
Apollo Client as a client for [`@firsthandjs/data`](https://www.npmjs.com/package/@firsthandjs/data).
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
npm install @firsthandjs/data-apollo
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
0.
|
|
9
|
+
0.61 kB gzip. It has **no dependency on Apollo** and no peer dependency either —
|
|
10
10
|
the three methods it uses are declared structurally. That also keeps your copy
|
|
11
11
|
of `graphql` the only copy, which is a category of afternoon worth avoiding.
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
14
|
import { gql } from '@apollo/client';
|
|
15
|
-
import {
|
|
16
|
-
import { useResource } from '@firsthandjs/data';
|
|
15
|
+
import { createApolloClient } from '@firsthandjs/data-apollo';
|
|
16
|
+
import { useAction, useResource } from '@firsthandjs/data';
|
|
17
17
|
import InvoicesDocument from './invoices.gql';
|
|
18
18
|
|
|
19
|
-
const billing =
|
|
19
|
+
export const billing = createApolloClient(apollo, gql, {
|
|
20
|
+
// Read per request and untracked: the token may change, and a resource must
|
|
21
|
+
// not depend on it.
|
|
22
|
+
headers: () => ({ authorization: `Bearer ${session.token.peek()}` }),
|
|
23
|
+
});
|
|
20
24
|
|
|
21
25
|
function Invoices() {
|
|
22
|
-
const invoices = useResource((
|
|
23
|
-
billing(InvoicesDocument, { month: month.value })(
|
|
26
|
+
const invoices = useResource(({ request }) =>
|
|
27
|
+
billing.query(InvoicesDocument, { month: month.value })(request),
|
|
24
28
|
);
|
|
25
29
|
return <List items={invoices.data.value?.invoices ?? []} />;
|
|
26
30
|
}
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
`
|
|
30
|
-
`@apollo/client`, or `parse` from `graphql` — because Apollo wants a
|
|
31
|
-
`DocumentNode` and that parser should be yours, not ours.
|
|
33
|
+
`createApolloClient(client, parse, options?)` takes the parse function too —
|
|
34
|
+
`gql` from `@apollo/client`, or `parse` from `graphql` — because Apollo wants a
|
|
35
|
+
parsed `DocumentNode` and that parser should be yours, not ours.
|
|
36
|
+
|
|
37
|
+
## The client
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
billing.query(Document, variables); // a loader; declares @tag
|
|
41
|
+
billing.mutate(Document, variables); // a loader; declares @invalidates
|
|
42
|
+
billing.watch(Document, variables); // what `fromObservable` takes
|
|
43
|
+
billing.with({ options: { errorPolicy: 'all' } }); // a variation
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
An operation with required variables cannot be called without them: the
|
|
47
|
+
generated types travel with the document.
|
|
32
48
|
|
|
33
49
|
## The decision this package leaves to you: the cache
|
|
34
50
|
|
|
@@ -39,12 +55,12 @@ together, and only two:
|
|
|
39
55
|
- **Apollo as transport.** The resources are your state. `force` is passed on
|
|
40
56
|
as `fetchPolicy: 'network-only'`, so an invalidation reaches past the cache;
|
|
41
57
|
set `no-cache` on the client if you want it out of the way entirely.
|
|
42
|
-
- **Apollo as the store.** Use `
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
- **Apollo as the store.** Use `watch` instead. One write in Apollo's cache
|
|
59
|
+
then updates every view of that entity at the same moment, which is what a
|
|
60
|
+
per-call-site resource cannot do:
|
|
45
61
|
|
|
46
62
|
```tsx
|
|
47
|
-
const user = fromObservable(...
|
|
63
|
+
const user = fromObservable(...billing.watch(UserDocument, { id: props.id }));
|
|
48
64
|
```
|
|
49
65
|
|
|
50
66
|
What is not on the list is both at once, with the same data living in two
|
|
@@ -55,14 +71,12 @@ places under two invalidation rules. See
|
|
|
55
71
|
|
|
56
72
|
`@firsthandjs/data/vite` reads `@tag` and `@invalidates` at build time and
|
|
57
73
|
strips them, so what reaches Apollo is a plain GraphQL document. A query
|
|
58
|
-
declares its `@tag` directives before the request goes out
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
declares its `@tag` directives before the request goes out; a mutation declares
|
|
75
|
+
its `@invalidates` — and inside an action the request's tags _are_ the store's
|
|
76
|
+
invalidation, so nothing at the call site wires them:
|
|
61
77
|
|
|
62
78
|
```tsx
|
|
63
|
-
const pay = useAction((id: string, {
|
|
64
|
-
billing(PayDocument, { id })({ signal, force: true, tags: invalidates }),
|
|
65
|
-
);
|
|
79
|
+
const pay = useAction((id: string, { request }) => billing.mutate(PayDocument, { id })(request));
|
|
66
80
|
```
|
|
67
81
|
|
|
68
|
-
MIT licensed. See the [data guide](https://github.com/firsthandjs/firsthand/blob/main/docs/guide/09-data.md).
|
|
82
|
+
MIT licensed. See the [data guide](https://github.com/firsthandjs/firsthand/blob/main/docs/guide/09-data.md#apollo).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Apollo Client, as
|
|
2
|
+
* Apollo Client, as a client for `@firsthandjs/data`.
|
|
3
3
|
*
|
|
4
4
|
* It takes a client you built and touches three of its methods. Your links,
|
|
5
5
|
* your authentication, your uploads stay where they are — and so does the
|
|
@@ -12,17 +12,32 @@
|
|
|
12
12
|
* - **Apollo as transport.** `fetchPolicy: 'no-cache'`, or the `force` this
|
|
13
13
|
* package passes on, and the resources are your state. One source of
|
|
14
14
|
* truth.
|
|
15
|
-
* - **Apollo as the store.** Use `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* - **Apollo as the store.** Use `watch` instead: one write in Apollo's
|
|
16
|
+
* cache updates every view of that entity at once, which is what a
|
|
17
|
+
* per-call-site resource cannot do. Also one source of truth.
|
|
18
18
|
*
|
|
19
19
|
* What is not on the list is both at once (ADR-0022).
|
|
20
20
|
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { gql } from '@apollo/client';
|
|
23
|
+
* import { createApolloClient } from '@firsthandjs/data-apollo';
|
|
24
|
+
*
|
|
25
|
+
* export const billing = createApolloClient(apollo, gql, {
|
|
26
|
+
* // Read per request and untracked: the token may change, and a resource
|
|
27
|
+
* // must not depend on it.
|
|
28
|
+
* headers: () => ({ authorization: `Bearer ${token.value}` }),
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* const invoices = useResource(({ request }) =>
|
|
32
|
+
* billing.query(InvoicesDocument, { month: month.value })(request),
|
|
33
|
+
* );
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
21
36
|
* There is no dependency on Apollo here, and no peer dependency either: the
|
|
22
37
|
* shapes below are declared structurally, so this package has no opinion about
|
|
23
38
|
* which version you run, and nothing to follow when that version changes.
|
|
24
39
|
*/
|
|
25
|
-
import { type DocumentArguments, type GraphQLDocument, type
|
|
40
|
+
import { type BridgeOptions, type CacheClient, type CacheOptions, type DocumentArguments, type GraphQLDocument, type Loader, type ObservableLike, type Variables } from '@firsthandjs/data';
|
|
26
41
|
/** The part of an Apollo client this package uses. Nothing else. */
|
|
27
42
|
export interface ApolloLike {
|
|
28
43
|
query(options: Record<string, unknown>): Promise<{
|
|
@@ -32,31 +47,6 @@ export interface ApolloLike {
|
|
|
32
47
|
data?: unknown;
|
|
33
48
|
}>;
|
|
34
49
|
}
|
|
35
|
-
/**
|
|
36
|
-
* How to turn a document's source into whatever Apollo wants.
|
|
37
|
-
*
|
|
38
|
-
* Apollo takes a parsed `DocumentNode`, which means `gql` from `@apollo/client`
|
|
39
|
-
* or `parse` from `graphql` — your copy of it, not ours, since two copies of
|
|
40
|
-
* `graphql` in one application is its own kind of afternoon.
|
|
41
|
-
*/
|
|
42
|
-
export type Parse = (source: string) => unknown;
|
|
43
|
-
/**
|
|
44
|
-
* Binds an Apollo client so a `.gql` document can be a resource's loader.
|
|
45
|
-
*
|
|
46
|
-
* ```ts
|
|
47
|
-
* import { gql } from '@apollo/client';
|
|
48
|
-
* import { apolloLoader } from '@firsthandjs/data-apollo';
|
|
49
|
-
*
|
|
50
|
-
* const billing = apolloLoader(apollo, gql);
|
|
51
|
-
*
|
|
52
|
-
* const invoices = useResource((context) => billing(InvoicesDocument, { month: month.value })(context));
|
|
53
|
-
* ```
|
|
54
|
-
*
|
|
55
|
-
* The tags come from the document's directives, declared before the request
|
|
56
|
-
* goes out. `force` becomes `fetchPolicy: 'network-only'`, which is what makes
|
|
57
|
-
* an invalidation reach past Apollo's cache.
|
|
58
|
-
*/
|
|
59
|
-
export declare function apolloLoader(client: ApolloLike, parse: Parse): <T, V extends Variables>(document: GraphQLDocument<T, V>, ...rest: DocumentArguments<V>) => ({ tags, force }: LoadContext) => Promise<T>;
|
|
60
50
|
/** A watched query: what Apollo pushes when its cache changes. */
|
|
61
51
|
export interface WatchLike {
|
|
62
52
|
watchQuery(options: Record<string, unknown>): {
|
|
@@ -72,24 +62,55 @@ export interface WatchLike {
|
|
|
72
62
|
};
|
|
73
63
|
}
|
|
74
64
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* This is the shape to reach for when the same entity is shown in many places
|
|
78
|
-
* and must stay consistent: Apollo's cache is then the one source of truth,
|
|
79
|
-
* and every view of it updates from the same write at the same moment.
|
|
65
|
+
* How to turn a document's source into whatever Apollo wants.
|
|
80
66
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
67
|
+
* Apollo takes a parsed `DocumentNode`, which means `gql` from `@apollo/client`
|
|
68
|
+
* or `parse` from `graphql` — your copy of it, not ours, since two copies of
|
|
69
|
+
* `graphql` in one application is its own kind of afternoon.
|
|
84
70
|
*/
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
export type Parse = (source: string) => unknown;
|
|
72
|
+
export interface ApolloClientOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Headers for every request, sent through Apollo's per-operation context. A
|
|
75
|
+
* function is called **per request and untracked**, which is what lets a
|
|
76
|
+
* token change without making every resource depend on it.
|
|
77
|
+
*/
|
|
78
|
+
readonly headers?: Record<string, string> | (() => Record<string, string>);
|
|
79
|
+
/**
|
|
80
|
+
* A cache in front of Apollo: `false` (the default, because Apollo has one
|
|
81
|
+
* of its own and two caches over the same data disagree), options for a
|
|
82
|
+
* cache of this client's own, or a `CacheClient` shared with the rest of the
|
|
83
|
+
* application. Queries only.
|
|
84
|
+
*/
|
|
85
|
+
readonly cache?: false | CacheOptions | CacheClient;
|
|
86
|
+
/** Merged into the options of every query and mutation. */
|
|
87
|
+
readonly options?: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
export interface ApolloClient {
|
|
90
|
+
/**
|
|
91
|
+
* A query, as a loader. Declares the document's `@tag` directives before the
|
|
92
|
+
* request goes out; `force` becomes `fetchPolicy: 'network-only'`, which is
|
|
93
|
+
* what makes an invalidation reach past Apollo's cache.
|
|
94
|
+
*/
|
|
95
|
+
query<T, V extends Variables>(document: GraphQLDocument<T, V>, ...rest: DocumentArguments<V>): Loader<T>;
|
|
96
|
+
/**
|
|
97
|
+
* A mutation, as a loader. Declares the document's `@invalidates` directives
|
|
98
|
+
* into the request — which inside an action is the store's `invalidates`, so
|
|
99
|
+
* the document's own declaration reaches the store with nothing to wire.
|
|
100
|
+
*/
|
|
101
|
+
mutate<T, V extends Variables>(document: GraphQLDocument<T, V>, ...rest: DocumentArguments<V>): Loader<T>;
|
|
102
|
+
/**
|
|
103
|
+
* A watched query, for `fromObservable`: Apollo's cache as the one source of
|
|
104
|
+
* truth, so every view of an entity updates from the same write.
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* const user = fromObservable(...billing.watch(UserDocument, { id }));
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
watch<T, V extends Variables>(document: GraphQLDocument<T, V>, ...rest: DocumentArguments<V>): readonly [ObservableLike<T>, BridgeOptions];
|
|
111
|
+
/** A copy with some options replaced. The cache is shared unless replaced. */
|
|
112
|
+
with(options: ApolloClientOptions): ApolloClient;
|
|
113
|
+
readonly cache: CacheClient | undefined;
|
|
114
|
+
}
|
|
115
|
+
export declare function createApolloClient(client: ApolloLike & WatchLike, parse: Parse, options?: ApolloClientOptions): ApolloClient;
|
|
95
116
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveTags as
|
|
1
|
+
import{createCacheClient as y,resolveTags as T}from"@firsthandjs/data";import{untrack as V}from"@firsthandjs/core";function b(s,i,t={}){let c=t.cache===void 0||t.cache===!1?void 0:"read"in t.cache?t.cache:y(t.cache),u=()=>{let e=typeof t.headers=="function"?V(t.headers):t.headers;return e===void 0?{}:{context:{headers:e}}},d=async(e,n,o,r)=>{let a=i(n.source);return e==="mutation"?(await s.mutate({...t.options,...u(),mutation:a,variables:o})).data:(await s.query({...t.options,...u(),query:a,variables:o,fetchPolicy:r.force?"network-only":"cache-first"})).data},m=(e,n,o)=>async r=>{let a=o[0]??{};r.tags?.(...T(e==="mutation"?n.invalidates:n.tags,a));let l=n;if(c===void 0||e==="mutation")return await d(e,l,a,r);let p=`${n.operation}(${JSON.stringify(a)})`;return await c.read(p,h=>d(e,l,a,h))(r)};return{cache:c,query:(e,...n)=>m("query",e,n),mutate:(e,...n)=>m("mutation",e,n),watch:(e,...n)=>{let o=s.watchQuery({...t.options,...u(),query:i(e.source),variables:n[0]??{}});return[{subscribe:r=>o.subscribe({next:a=>r.next?.(a.data),...r.error===void 0?{}:{error:r.error}})},{reload:()=>o.refetch()}]},with:e=>b(s,i,{...t,...e,cache:e.cache??c??!1})}}export{b as createApolloClient};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firsthandjs/data-apollo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Loaders for Apollo Client, for @firsthandjs/data. Takes your client; never configures it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@firsthandjs/data": "0.
|
|
22
|
+
"@firsthandjs/data": "0.6.0"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=20.11.0"
|