@firsthandjs/data-apollo 0.5.0 → 0.6.1

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 CHANGED
@@ -1,34 +1,50 @@
1
1
  # @firsthandjs/data-apollo
2
2
 
3
- Apollo Client documents as loaders for [`@firsthandjs/data`](https://www.npmjs.com/package/@firsthandjs/data).
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.37 kB gzip. It has **no dependency on Apollo** and no peer dependency either —
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 { apolloLoader } from '@firsthandjs/data-apollo';
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 = apolloLoader(apollo, gql);
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((context) =>
23
- billing(InvoicesDocument, { month: month.value })(context),
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
- `apolloLoader(client, parse)` takes the parse function too — `gql` from
30
- `@apollo/client`, or `parse` from `graphql` — because Apollo wants a parsed
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 `apolloObservable` instead. One write in
43
- Apollo's cache then updates every view of that entity at the same moment,
44
- which is what a per-call-site resource cannot do:
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(...apolloObservable(apollo, gql)(UserDocument, { id: props.id }));
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 so an invalidation
59
- arriving mid-flight still finds it and a mutation declares its `@invalidates`,
60
- which an action hands straight to the store:
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, { signal, invalidates }) =>
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 loaders for `@firsthandjs/data`.
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 `apolloObservable` instead: one write in
16
- * Apollo's cache updates every view of that entity at once, which is what
17
- * a per-call-site resource cannot do. Also one source of truth.
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 LoadContext, type Variables } from '@firsthandjs/data';
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
- * A document as something that pushes, for `fromObservable`.
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
- * ```ts
82
- * const user = fromObservable(...apolloObservable(apollo, gql)(UserDocument, { id }));
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 declare function apolloObservable(client: WatchLike, parse: Parse): <T, V extends Variables>(document: GraphQLDocument<T, V>, ...rest: DocumentArguments<V>) => readonly [{
86
- readonly subscribe: (observer: {
87
- next?: (value: T) => void;
88
- error?: (error: unknown) => void;
89
- }) => {
90
- unsubscribe: () => void;
91
- };
92
- }, {
93
- readonly reload: () => Promise<unknown>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAE3B,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACpE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACvE;AAED;;;;;;GAMG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,IAInD,CAAC,EAAE,CAAC,SAAS,SAAS,EAAE,UAAU,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC,MACrF,iBAAiB,WAAW,KAAG,OAAO,CAAC,CAAC,CAAC,CAoBnD;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAC5C,SAAS,CAAC,QAAQ,EAAE;YAClB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE;gBAAE,IAAI,EAAE,OAAO,CAAA;aAAE,KAAK,IAAI,CAAC;YAC1C,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;SAClC,GAAG;YAAE,WAAW,EAAE,MAAM,IAAI,CAAA;SAAE,CAAC;QAChC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;KAC7B,CAAC;CACH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,IACtD,CAAC,EAAE,CAAC,SAAS,SAAS,EAC5B,UAAU,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAC/B,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC;mCAKH;QAAE,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE;qBAxBtE,MAAM,IAAI;;;;GAiChC"}
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 c}from"@firsthandjs/data";function k(n,a){return(e,...s)=>async({tags:o,force:r})=>{let t=s[0]??{},i=e.kind==="mutation"?e.invalidates:e.tags;o(...c(i,t));let u=a(e.source);return e.kind==="mutation"?(await n.mutate({mutation:u,variables:t})).data:(await n.query({query:u,variables:t,fetchPolicy:r?"network-only":"cache-first"})).data}}function m(n,a){return(e,...s)=>{let o=n.watchQuery({query:a(e.source),variables:s[0]??{}});return[{subscribe:r=>o.subscribe({next:t=>r.next?.(t.data),...r.error===void 0?{}:{error:r.error}})},{reload:()=>o.refetch()}]}}export{k as apolloLoader,m as apolloObservable};
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.5.0",
3
+ "version": "0.6.1",
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.5.0"
22
+ "@firsthandjs/data": "0.6.1"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=20.11.0"