@eleven-am/golem-react 0.1.1 → 0.2.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
@@ -10,13 +10,13 @@ Built on `@eleven-am/golem-client` and `@tanstack/react-query`.
10
10
  npm install @eleven-am/golem-react @eleven-am/golem-client @tanstack/react-query react gql.tada graphql graphql-ws
11
11
  ```
12
12
 
13
- Peer dependencies: `@eleven-am/golem-client` ^0.1.0, `@tanstack/react-query` ^5, `react` >=18, `gql.tada` ^1.8. The client in turn needs `graphql` ^16 and `graphql-ws` ^6 (and `@tanstack/query-core` ^5, which `@tanstack/react-query` brings).
13
+ Peer dependencies: `@eleven-am/golem-client` ^0.2.1, `@tanstack/react-query` ^5, `react` >=18, `gql.tada` ^1.8. The client in turn needs `graphql` ^16 and `graphql-ws` ^6 (and `@tanstack/query-core` ^5, which `@tanstack/react-query` brings).
14
14
 
15
15
  ## Setup
16
16
 
17
17
  The client lives on your application's `QueryClient`. It does not make a second one.
18
18
 
19
- The manifest is `zz_golem_client.gen.ts`, written by `golem generate` beside your GraphQL schema on the server (go/v0.8.2 or later). Copy or import it into your front end, and regenerate it whenever the schema changes. Never edit it.
19
+ The manifest is `zz_golem_client.gen.ts`, written by `golem generate` beside your GraphQL schema on the server (go/v0.8.3 or later). Copy or import it into your front end, and regenerate it whenever the schema changes. Never edit it.
20
20
 
21
21
  ```tsx
22
22
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
@@ -73,7 +73,7 @@ function Posts() {
73
73
  }
74
74
  ```
75
75
 
76
- It throws `GolemNotLiveError` (naming the reason and the root) for a document that cannot be live. Use `useGolemStaticQuery` for those.
76
+ Aggregates, group-by, relation group-by and custom roots that return model rows are live: any event on a model they come from refetches the document through the coalescer, and they are never patched. Put counts in their own query. It throws `GolemNotLiveError` (naming the reason and the root) for a document that reaches a model without events or a custom root whose result holds no model rows. Use `useGolemStaticQuery` for those. Relation group-by needs a go/v0.8.3 manifest to refetch on events of the models it groups by.
77
77
 
78
78
  ### useGolemStatus
79
79
 
@@ -84,6 +84,10 @@ const status = useGolemStatus(POSTS)
84
84
  return <small>{status?.state}</small>
85
85
  ```
86
86
 
87
+ ### useGolemConnection
88
+
89
+ The connection state of the whole client, as one of the `transport.status` values: `idle` before any live query has opened a connection, then `connecting`, `open`, `reconnecting` or one of the fatal states. It is global, unlike `useGolemStatus`.
90
+
87
91
  ### useGolemMutation
88
92
 
89
93
  `useMutation` over `client.mutate`. A committed mutation invalidates every cached query over the models it touches, so the live event or the refetch brings it up to date. The mutation result is never written into the cache. A document holds exactly one operation.
@@ -117,7 +121,7 @@ const pages = feed.data?.pages ?? []
117
121
 
118
122
  ### useGolemStaticQuery
119
123
 
120
- `useQuery` for a document that cannot be live: aggregates, group-by, counts and update-many roots. Fetched once, never patched.
124
+ `useQuery` for a document you want fetched once and never refreshed, or one that cannot be live (a model without events, a custom root with no model rows). Counts and aggregates are live through `useGolemQuery`.
121
125
 
122
126
  ```tsx
123
127
  const COUNT = `
@@ -127,6 +131,7 @@ const COUNT = `
127
131
  `
128
132
 
129
133
  const total = useGolemStaticQuery(COUNT)
134
+ const liveTotal = useGolemQuery(COUNT)
130
135
  ```
131
136
 
132
137
  ## Live status
@@ -194,17 +199,11 @@ The client adds `_version` and `__typename` to every selection of a model with e
194
199
  - `golem.transport.status`: `idle`, `connecting`, `open`, `reconnecting`, `unauthenticated`, `forbidden`, `invalid`, `protocol-mismatch`, `contract-mismatch` or `deadline-required`.
195
200
  - `golem.transport.onStatus(listener)`: calls you on every change and returns an unsubscribe function.
196
201
 
197
- Wrap them in `useSyncExternalStore`:
202
+ `useGolemConnection()` wraps both and returns the transport status. It re-renders on every change.
198
203
 
199
204
  ```tsx
200
- import { useSyncExternalStore } from 'react'
201
-
202
- function useConnection() {
203
- return useSyncExternalStore(
204
- (notify) => golem.transport.onStatus(notify),
205
- () => golem.transport.status,
206
- )
207
- }
205
+ const connection = useGolemConnection()
206
+ return connection === 'open' ? null : <small>{connection}</small>
208
207
  ```
209
208
 
210
209
  #### Why there is a manifest
@@ -221,6 +220,18 @@ GraphQL does not say which subscription root carries a model's events, what its
221
220
 
222
221
  For a walkthrough from a model to a live component, see [Using golem from React](https://github.com/eleven-am/golem/blob/main/docs/golem-go/REACT.md).
223
222
 
223
+ ## Mutations on page unload
224
+
225
+ Browsers cancel ordinary fetches while a page unloads. Pass `keepalive: true` to `useGolemMutation` and that mutation's requests are sent with `fetch(..., { keepalive: true })`.
226
+
227
+ ```tsx
228
+ const endSession = useGolemMutation(END_SESSION, { keepalive: true })
229
+
230
+ addEventListener('pagehide', () => endSession.mutate({ id: sessionId }))
231
+ ```
232
+
233
+ The response may never be processed, so treat the mutation as possibly committed; the next page load refetches the authoritative state. Browsers cap keepalive request bodies at 64 KiB in total, so a larger body throws `GolemKeepaliveTooLargeError` before anything is sent.
234
+
224
235
  ## Errors
225
236
 
226
237
  All server errors are `GolemError` (`code`, `path`, `extensions`, `retryable`) or a subclass:
@@ -234,7 +245,7 @@ Client-side failures: `GolemConfigurationError` (bad setup), `GolemDocumentError
234
245
 
235
246
  ## Requirements
236
247
 
237
- - golem **go/v0.8.2 or later** on the server, with `golem.Subscriptions` on every model a live query reaches.
248
+ - golem **go/v0.8.3 or later** on the server, with `golem.Subscriptions` on every model a live query reaches.
238
249
  - `react` >=18, `@tanstack/react-query` 5.
239
250
  - A socket deadline for every principal, and a manifest regenerated after every schema change. See [The socket deadline](https://github.com/eleven-am/golem/blob/main/docs/golem-go/GUIDE.md#the-socket-deadline) and [The client manifest](https://github.com/eleven-am/golem/blob/main/docs/golem-go/GUIDE.md#the-client-manifest).
240
251
 
package/dist/hooks.d.ts CHANGED
@@ -3,7 +3,9 @@ import type { Cursor } from '@eleven-am/golem-client';
3
3
  import type { GolemDocument, ResultFor, VariablesFor } from './types.js';
4
4
  export type GolemQueryOptions<TData> = Omit<UseQueryOptions<TData, Error, TData, readonly unknown[]>, 'queryKey' | 'queryFn' | 'meta'>;
5
5
  export type GolemInfiniteQueryOptions<TPage> = Omit<UseInfiniteQueryOptions<TPage, Error, InfiniteData<TPage, Cursor | null>, readonly unknown[], Cursor | null>, 'queryKey' | 'queryFn' | 'meta' | 'initialPageParam' | 'getNextPageParam'>;
6
- export type GolemMutationOptions<TData, TVariables> = Omit<UseMutationOptions<TData, Error, TVariables>, 'mutationFn'>;
6
+ export type GolemMutationOptions<TData, TVariables> = Omit<UseMutationOptions<TData, Error, TVariables>, 'mutationFn'> & {
7
+ readonly keepalive?: boolean;
8
+ };
7
9
  export declare function useGolemQuery<TDocument extends GolemDocument>(document: TDocument, variables?: VariablesFor<TDocument>, options?: GolemQueryOptions<ResultFor<TDocument>>): UseQueryResult<ResultFor<TDocument>, Error>;
8
10
  export declare function useGolemStaticQuery<TDocument extends GolemDocument>(document: TDocument, variables?: VariablesFor<TDocument>, options?: GolemQueryOptions<ResultFor<TDocument>>): UseQueryResult<ResultFor<TDocument>, Error>;
9
11
  export declare function useGolemInfiniteQuery<TDocument extends GolemDocument>(document: TDocument, variables?: VariablesFor<TDocument>, options?: GolemInfiniteQueryOptions<ResultFor<TDocument>>): UseInfiniteQueryResult<InfiniteData<ResultFor<TDocument>, Cursor | null>, Error>;
package/dist/hooks.js CHANGED
@@ -21,8 +21,9 @@ export function useGolemInfiniteQuery(document, variables, options) {
21
21
  }
22
22
  export function useGolemMutation(document, options) {
23
23
  const client = useGolemClient();
24
+ const { keepalive, ...mutation } = options ?? {};
24
25
  return useMutation({
25
- ...options,
26
- mutationFn: (variables) => client.mutate(document, variables),
26
+ ...mutation,
27
+ mutationFn: (variables) => client.mutate(document, variables, keepalive === undefined ? undefined : { keepalive }),
27
28
  });
28
29
  }
package/dist/status.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { type QueryKey } from '@tanstack/react-query';
2
- import type { GolemClient, LiveStatus } from '@eleven-am/golem-client';
2
+ import type { GolemClient, LiveStatus, SessionStatus } from '@eleven-am/golem-client';
3
3
  import type { GolemDocument } from './types.js';
4
4
  export declare function useLiveStatus(client: GolemClient, queryKey: QueryKey): LiveStatus | undefined;
5
5
  export interface GolemStatusOptions {
6
6
  readonly infinite?: boolean;
7
7
  }
8
8
  export declare function useGolemStatus(document: GolemDocument, variables?: Readonly<Record<string, unknown>>, options?: GolemStatusOptions): LiveStatus | undefined;
9
+ export declare function useGolemConnection(): SessionStatus;
package/dist/status.js CHANGED
@@ -16,3 +16,9 @@ export function useGolemStatus(document, variables = {}, options = {}) {
16
16
  const queryKey = useMemo(() => (options.infinite === true ? client.infiniteQueryOptions(document, variables) : client.queryOptions(document, variables)).queryKey, [client, document, variablesHash, options.infinite]);
17
17
  return useLiveStatus(client, queryKey);
18
18
  }
19
+ export function useGolemConnection() {
20
+ const client = useGolemClient();
21
+ const subscribe = useCallback((listener) => client.transport.onStatus(listener), [client]);
22
+ const snapshot = useCallback(() => client.transport.status, [client]);
23
+ return useSyncExternalStore(subscribe, snapshot, snapshot);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleven-am/golem-react",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "React bindings for the golem live GraphQL client",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "test": "tsc --noEmit -p tsconfig.spec.json && vitest run"
21
21
  },
22
22
  "peerDependencies": {
23
- "@eleven-am/golem-client": "^0.1.1",
23
+ "@eleven-am/golem-client": "^0.2.1",
24
24
  "@tanstack/react-query": "^5.0.0",
25
25
  "gql.tada": "^1.8.0",
26
26
  "react": ">=18"