fetchium 0.1.1 → 0.2.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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/cjs/development/QueryAdapter-DUo338ga.js +2 -0
- package/dist/cjs/development/QueryAdapter-DUo338ga.js.map +1 -0
- package/dist/cjs/development/QueryClient-m7BzCIe9.js +2 -0
- package/dist/cjs/development/QueryClient-m7BzCIe9.js.map +1 -0
- package/dist/cjs/development/index.js +1 -1
- package/dist/cjs/development/mutation-wUhcGxKl.js +2 -0
- package/dist/cjs/development/mutation-wUhcGxKl.js.map +1 -0
- package/dist/cjs/development/react/index.js +1 -1
- package/dist/cjs/development/rest/index.js +1 -1
- package/dist/cjs/development/rest/index.js.map +1 -1
- package/dist/cjs/development/topic/index.js +1 -1
- package/dist/cjs/development/topic/index.js.map +1 -1
- package/dist/cjs/production/QueryAdapter-DUo338ga.js +2 -0
- package/dist/cjs/production/QueryAdapter-DUo338ga.js.map +1 -0
- package/dist/cjs/production/QueryClient-4T90peFN.js +2 -0
- package/dist/cjs/production/QueryClient-4T90peFN.js.map +1 -0
- package/dist/cjs/production/index.js +1 -1
- package/dist/cjs/production/mutation-Dk0gznwX.js +2 -0
- package/dist/cjs/production/mutation-Dk0gznwX.js.map +1 -0
- package/dist/cjs/production/react/index.js +1 -1
- package/dist/cjs/production/rest/index.js +1 -1
- package/dist/cjs/production/rest/index.js.map +1 -1
- package/dist/cjs/production/topic/index.js +1 -1
- package/dist/cjs/production/topic/index.js.map +1 -1
- package/dist/esm/{QueryController.d.ts → QueryAdapter.d.ts} +7 -7
- package/dist/esm/QueryAdapter.d.ts.map +1 -0
- package/dist/esm/QueryClient.d.ts +6 -6
- package/dist/esm/QueryClient.d.ts.map +1 -1
- package/dist/esm/QueryResult.d.ts +2 -2
- package/dist/esm/QueryResult.d.ts.map +1 -1
- package/dist/esm/development/{QueryController-Ch_ncxiI.js → QueryAdapter-Bu5UJjE4.js} +2 -2
- package/dist/esm/development/QueryAdapter-Bu5UJjE4.js.map +1 -0
- package/dist/esm/development/{QueryClient-Dtde3pss.js → QueryClient-BajBmpnA.js} +532 -532
- package/dist/esm/development/QueryClient-BajBmpnA.js.map +1 -0
- package/dist/esm/development/index.js +14 -14
- package/dist/esm/development/{mutation-UZshUQAf.js → mutation-DAOZE4Ok.js} +13 -13
- package/dist/esm/development/mutation-DAOZE4Ok.js.map +1 -0
- package/dist/esm/development/react/index.js +1 -1
- package/dist/esm/development/rest/index.js +26 -26
- package/dist/esm/development/rest/index.js.map +1 -1
- package/dist/esm/development/topic/index.js +11 -11
- package/dist/esm/development/topic/index.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/mutation.d.ts +3 -3
- package/dist/esm/mutation.d.ts.map +1 -1
- package/dist/esm/production/{QueryController-Ch_ncxiI.js → QueryAdapter-Bu5UJjE4.js} +2 -2
- package/dist/esm/production/QueryAdapter-Bu5UJjE4.js.map +1 -0
- package/dist/esm/production/{QueryClient-YqnBxFy1.js → QueryClient-KH0Ex_8m.js} +708 -708
- package/dist/esm/production/QueryClient-KH0Ex_8m.js.map +1 -0
- package/dist/esm/production/index.js +14 -14
- package/dist/esm/production/{mutation-pgFl1uIY.js → mutation-C7BOChR2.js} +13 -13
- package/dist/esm/production/mutation-C7BOChR2.js.map +1 -0
- package/dist/esm/production/react/index.js +1 -1
- package/dist/esm/production/rest/index.js +26 -26
- package/dist/esm/production/rest/index.js.map +1 -1
- package/dist/esm/production/topic/index.js +11 -11
- package/dist/esm/production/topic/index.js.map +1 -1
- package/dist/esm/query.d.ts +6 -6
- package/dist/esm/query.d.ts.map +1 -1
- package/dist/esm/rest/RESTMutation.d.ts +2 -2
- package/dist/esm/rest/RESTMutation.d.ts.map +1 -1
- package/dist/esm/rest/RESTQuery.d.ts +2 -2
- package/dist/esm/rest/RESTQuery.d.ts.map +1 -1
- package/dist/esm/rest/{RESTQueryController.d.ts → RESTQueryAdapter.d.ts} +6 -6
- package/dist/esm/rest/RESTQueryAdapter.d.ts.map +1 -0
- package/dist/esm/rest/index.d.ts +2 -2
- package/dist/esm/rest/index.d.ts.map +1 -1
- package/dist/esm/topic/TopicQuery.d.ts +2 -2
- package/dist/esm/topic/TopicQuery.d.ts.map +1 -1
- package/dist/esm/topic/{TopicQueryController.d.ts → TopicQueryAdapter.d.ts} +3 -3
- package/dist/esm/topic/TopicQueryAdapter.d.ts.map +1 -0
- package/dist/esm/topic/index.d.ts +1 -1
- package/dist/esm/topic/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/plugin/docs/api/fetchium.md +40 -40
- package/plugin/docs/core/queries.md +15 -15
- package/plugin/docs/core/streaming.md +18 -18
- package/plugin/docs/data/mutations.md +5 -5
- package/plugin/docs/quickstart.md +1 -1
- package/plugin/docs/setup/project-setup.md +19 -19
- package/dist/cjs/development/QueryClient-CLi3ONNM.js +0 -2
- package/dist/cjs/development/QueryClient-CLi3ONNM.js.map +0 -1
- package/dist/cjs/development/QueryController-BQA49OYU.js +0 -2
- package/dist/cjs/development/QueryController-BQA49OYU.js.map +0 -1
- package/dist/cjs/development/mutation-CikIl_6k.js +0 -2
- package/dist/cjs/development/mutation-CikIl_6k.js.map +0 -1
- package/dist/cjs/production/QueryClient-N0MJmuHW.js +0 -2
- package/dist/cjs/production/QueryClient-N0MJmuHW.js.map +0 -1
- package/dist/cjs/production/QueryController-BQA49OYU.js +0 -2
- package/dist/cjs/production/QueryController-BQA49OYU.js.map +0 -1
- package/dist/cjs/production/mutation-P_Yb4LI9.js +0 -2
- package/dist/cjs/production/mutation-P_Yb4LI9.js.map +0 -1
- package/dist/esm/QueryController.d.ts.map +0 -1
- package/dist/esm/development/QueryClient-Dtde3pss.js.map +0 -1
- package/dist/esm/development/QueryController-Ch_ncxiI.js.map +0 -1
- package/dist/esm/development/mutation-UZshUQAf.js.map +0 -1
- package/dist/esm/production/QueryClient-YqnBxFy1.js.map +0 -1
- package/dist/esm/production/QueryController-Ch_ncxiI.js.map +0 -1
- package/dist/esm/production/mutation-pgFl1uIY.js.map +0 -1
- package/dist/esm/rest/RESTQueryController.d.ts.map +0 -1
- package/dist/esm/topic/TopicQueryController.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/topic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/topic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ API reference for the main `fetchium` package — a data-fetching and query laye
|
|
|
10
10
|
```ts
|
|
11
11
|
import {
|
|
12
12
|
Query,
|
|
13
|
-
|
|
13
|
+
QueryAdapter,
|
|
14
14
|
fetchQuery,
|
|
15
15
|
queryKeyForClass,
|
|
16
16
|
Mutation,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from 'fetchium';
|
|
31
31
|
|
|
32
32
|
// REST adapter (JSON REST APIs)
|
|
33
|
-
import { RESTQuery, RESTMutation,
|
|
33
|
+
import { RESTQuery, RESTMutation, RESTQueryAdapter } from 'fetchium/rest';
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
---
|
|
@@ -49,10 +49,10 @@ Base class for all query definitions. Extend this to define custom data-fetching
|
|
|
49
49
|
|
|
50
50
|
#### Static properties
|
|
51
51
|
|
|
52
|
-
| Property
|
|
53
|
-
|
|
|
54
|
-
| `cache`
|
|
55
|
-
| `
|
|
52
|
+
| Property | Type | Description |
|
|
53
|
+
| --------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `cache` | `QueryCacheOptions \| undefined` | Class-level persistent cache settings (maxCount, cacheTime). |
|
|
55
|
+
| `adapter` | `typeof QueryAdapter` | **(required)** The adapter class responsible for sending requests. Set automatically on `RESTQuery`. Custom query types must set this to their own adapter class. |
|
|
56
56
|
|
|
57
57
|
#### Instance properties
|
|
58
58
|
|
|
@@ -79,16 +79,16 @@ Convenience base class for REST/JSON queries. Handles URL construction, search p
|
|
|
79
79
|
|
|
80
80
|
#### Instance properties
|
|
81
81
|
|
|
82
|
-
| Property | Type | Default | Description
|
|
83
|
-
| ---------------- | ------------------------------------------------- | ------- |
|
|
84
|
-
| `method` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH'` | `'GET'` | HTTP method.
|
|
85
|
-
| `path` | `string \| undefined` | — | URL path. Use template literal interpolation with `this.params` references.
|
|
86
|
-
| `searchParams` | `Record<string, unknown> \| undefined` | — | Query string parameters.
|
|
87
|
-
| `body` | `Record<string, unknown> \| undefined` | — | Request body (JSON-serialized).
|
|
88
|
-
| `headers` | `HeadersInit \| undefined` | — | Custom HTTP headers.
|
|
89
|
-
| `requestOptions` | `QueryRequestOptions \| undefined` | — | Additional fetch options (credentials, mode, baseUrl, etc.).
|
|
90
|
-
| `fetchNext` | `FetchNextConfig \| undefined` | — | Static pagination config. Values can be FieldRefs (e.g. `this.result.nextCursor`).
|
|
91
|
-
| `response` | `Response \| undefined` | — | The raw HTTP `Response` from the last fetch. Set by `
|
|
82
|
+
| Property | Type | Default | Description |
|
|
83
|
+
| ---------------- | ------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `method` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH'` | `'GET'` | HTTP method. |
|
|
85
|
+
| `path` | `string \| undefined` | — | URL path. Use template literal interpolation with `this.params` references. |
|
|
86
|
+
| `searchParams` | `Record<string, unknown> \| undefined` | — | Query string parameters. |
|
|
87
|
+
| `body` | `Record<string, unknown> \| undefined` | — | Request body (JSON-serialized). |
|
|
88
|
+
| `headers` | `HeadersInit \| undefined` | — | Custom HTTP headers. |
|
|
89
|
+
| `requestOptions` | `QueryRequestOptions \| undefined` | — | Additional fetch options (credentials, mode, baseUrl, etc.). |
|
|
90
|
+
| `fetchNext` | `FetchNextConfig \| undefined` | — | Static pagination config. Values can be FieldRefs (e.g. `this.result.nextCursor`). |
|
|
91
|
+
| `response` | `Response \| undefined` | — | The raw HTTP `Response` from the last fetch. Set by `RESTQueryAdapter` after each request completes. Available in `getConfig()`. |
|
|
92
92
|
|
|
93
93
|
#### `getIdentityKey()` default
|
|
94
94
|
|
|
@@ -161,9 +161,9 @@ Base class for mutation definitions.
|
|
|
161
161
|
|
|
162
162
|
#### Static properties
|
|
163
163
|
|
|
164
|
-
| Property
|
|
165
|
-
|
|
|
166
|
-
| `
|
|
164
|
+
| Property | Type | Description |
|
|
165
|
+
| --------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| `adapter` | `typeof QueryAdapter` | **(required)** The adapter class that handles sending this mutation. Set automatically on `RESTMutation`. Custom mutation types must set this to their own adapter class. |
|
|
167
167
|
|
|
168
168
|
#### Instance properties
|
|
169
169
|
|
|
@@ -227,7 +227,7 @@ new QueryClient(config: QueryClientConfig)
|
|
|
227
227
|
| Field | Type | Default | Description |
|
|
228
228
|
| -------------------- | ---------------------------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
|
229
229
|
| `store` | `QueryStore` | — | **(required)** Persistent storage backend. |
|
|
230
|
-
| `
|
|
230
|
+
| `adapters` | `QueryAdapter[]` | `[]` | Transport adapters (e.g. `new RESTQueryAdapter({ fetch, baseUrl })`). |
|
|
231
231
|
| `log` | `LogContext \| undefined` | `console` | Logger with `error`, `warn`, `info`, `debug` methods. |
|
|
232
232
|
| `evictionMultiplier` | `number \| undefined` | `1` | Scales all GC times for testing. Set to `0.001` to make timers fire in milliseconds. |
|
|
233
233
|
| `networkManager` | `NetworkManager` | `new NetworkManager()` | Tracks network connectivity. |
|
|
@@ -244,35 +244,35 @@ new QueryClient(config: QueryClientConfig)
|
|
|
244
244
|
|
|
245
245
|
---
|
|
246
246
|
|
|
247
|
-
### `
|
|
247
|
+
### `QueryAdapter` (abstract)
|
|
248
248
|
|
|
249
|
-
Base class for transport adapters.
|
|
249
|
+
Base class for transport adapters. An adapter handles sending queries and mutations for all query/mutation classes that declare it via `static adapter`. Register adapters with `QueryClient` at construction time.
|
|
250
250
|
|
|
251
251
|
#### Methods
|
|
252
252
|
|
|
253
|
-
| Method | Signature | Description
|
|
254
|
-
| ----------------------- | -------------------------------------------------------- |
|
|
255
|
-
| `register` | `(queryClient:
|
|
256
|
-
| `send` | `(ctx: Query, signal: AbortSignal): Promise<unknown>` | **(abstract)** Send a query and return the raw response data.
|
|
257
|
-
| `sendNext` | `(ctx: Query, signal: AbortSignal): Promise<unknown>` | Optional. Send the next-page request for a paginated query.
|
|
258
|
-
| `hasNext` | `(ctx: Query): boolean` | Optional. Return `true` if more pages are available for the current result.
|
|
259
|
-
| `sendMutation` | `(ctx: Mutation, signal: AbortSignal): Promise<unknown>` | Optional. Send a mutation and return the raw response data.
|
|
260
|
-
| `onNetworkStatusChange` | `(isOnline: boolean): void` | Optional. Called when the network comes online or goes offline.
|
|
261
|
-
| `destroy` | `(): void` | Optional. Called when the `QueryClient` is destroyed. Clean up connections or timers.
|
|
253
|
+
| Method | Signature | Description |
|
|
254
|
+
| ----------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
255
|
+
| `register` | `(queryClient: IQueryClientForAdapter): void` | Called once when the adapter is registered with a `QueryClient`. Override to do setup (e.g. open a WebSocket connection). |
|
|
256
|
+
| `send` | `(ctx: Query, signal: AbortSignal): Promise<unknown>` | **(abstract)** Send a query and return the raw response data. |
|
|
257
|
+
| `sendNext` | `(ctx: Query, signal: AbortSignal): Promise<unknown>` | Optional. Send the next-page request for a paginated query. |
|
|
258
|
+
| `hasNext` | `(ctx: Query): boolean` | Optional. Return `true` if more pages are available for the current result. |
|
|
259
|
+
| `sendMutation` | `(ctx: Mutation, signal: AbortSignal): Promise<unknown>` | Optional. Send a mutation and return the raw response data. |
|
|
260
|
+
| `onNetworkStatusChange` | `(isOnline: boolean): void` | Optional. Called when the network comes online or goes offline. |
|
|
261
|
+
| `destroy` | `(): void` | Optional. Called when the `QueryClient` is destroyed. Clean up connections or timers. |
|
|
262
262
|
|
|
263
263
|
#### Protected properties
|
|
264
264
|
|
|
265
|
-
| Property | Type
|
|
266
|
-
| ------------- |
|
|
267
|
-
| `queryClient` | `
|
|
265
|
+
| Property | Type | Description |
|
|
266
|
+
| ------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
267
|
+
| `queryClient` | `IQueryClientForAdapter \| undefined` | Set by `register()`. Use to access the shared query context via `this.queryClient.getContext()`. |
|
|
268
268
|
|
|
269
269
|
#### Example
|
|
270
270
|
|
|
271
271
|
```ts
|
|
272
|
-
import {
|
|
272
|
+
import { QueryAdapter } from 'fetchium';
|
|
273
273
|
import type { Query } from 'fetchium';
|
|
274
274
|
|
|
275
|
-
class
|
|
275
|
+
class GraphQLAdapter extends QueryAdapter {
|
|
276
276
|
async send(ctx: Query, signal: AbortSignal): Promise<unknown> {
|
|
277
277
|
const q = ctx as GraphQLQuery;
|
|
278
278
|
const response = await fetch('/graphql', {
|
|
@@ -289,22 +289,22 @@ class GraphQLController extends QueryController {
|
|
|
289
289
|
|
|
290
290
|
new QueryClient({
|
|
291
291
|
store,
|
|
292
|
-
|
|
292
|
+
adapters: [new GraphQLAdapter()],
|
|
293
293
|
});
|
|
294
294
|
```
|
|
295
295
|
|
|
296
296
|
---
|
|
297
297
|
|
|
298
|
-
### `
|
|
298
|
+
### `RESTQueryAdapter` extends `QueryAdapter`
|
|
299
299
|
|
|
300
|
-
Transport
|
|
300
|
+
Transport adapter for `RESTQuery` and `RESTMutation`. Handles URL construction, JSON serialization, search params, pagination, and `baseUrl` resolution.
|
|
301
301
|
|
|
302
302
|
Import from `fetchium/rest`.
|
|
303
303
|
|
|
304
304
|
#### Constructor
|
|
305
305
|
|
|
306
306
|
```ts
|
|
307
|
-
new
|
|
307
|
+
new RESTQueryAdapter(options?: RESTQueryAdapterOptions)
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
| Option | Type | Description |
|
|
@@ -451,19 +451,19 @@ For a more in depth guide to query configuration, see the [REST Queries referenc
|
|
|
451
451
|
|
|
452
452
|
## Custom Queries
|
|
453
453
|
|
|
454
|
-
`RESTQuery` is an adapter for JSON REST APIs. But queries as a concept are protocol-agnostic. When your use case doesn't fit REST --- GraphQL, gRPC, WebSockets, local databases, or any other data source --- you build a **`
|
|
454
|
+
`RESTQuery` is an adapter for JSON REST APIs. But queries as a concept are protocol-agnostic. When your use case doesn't fit REST --- GraphQL, gRPC, WebSockets, local databases, or any other data source --- you build a **`QueryAdapter`** that handles the transport, and a **`Query`** subclass that stays purely declarative.
|
|
455
455
|
|
|
456
|
-
The split follows the same logic as the rest of Fetchium: the _definition_ (params, result, identity) lives on the `Query` class; the _transport_ (how to actually fetch data) lives on the
|
|
456
|
+
The split follows the same logic as the rest of Fetchium: the _definition_ (params, result, identity) lives on the `Query` class; the _transport_ (how to actually fetch data) lives on the adapter.
|
|
457
457
|
|
|
458
|
-
### Defining
|
|
458
|
+
### Defining an adapter
|
|
459
459
|
|
|
460
|
-
A `
|
|
460
|
+
A `QueryAdapter` handles sending requests on behalf of queries that declare it. Extend `QueryAdapter` and implement `send(ctx, signal)`:
|
|
461
461
|
|
|
462
462
|
```ts
|
|
463
|
-
import {
|
|
463
|
+
import { QueryAdapter } from 'fetchium';
|
|
464
464
|
import type { Query } from 'fetchium';
|
|
465
465
|
|
|
466
|
-
class
|
|
466
|
+
class DBQueryAdapter extends QueryAdapter {
|
|
467
467
|
async send(ctx: Query, signal: AbortSignal): Promise<unknown> {
|
|
468
468
|
const q = ctx as DBQuery;
|
|
469
469
|
const db = await openDatabase();
|
|
@@ -478,24 +478,24 @@ Inside `send()`:
|
|
|
478
478
|
- **`signal`** --- an `AbortSignal` for cancellation, passed automatically by the query lifecycle
|
|
479
479
|
- **`this.queryClient`** --- the registered `QueryClient`; call `this.queryClient.getContext()` to access `log` and any other context properties you passed at setup
|
|
480
480
|
|
|
481
|
-
Register the
|
|
481
|
+
Register the adapter when creating the `QueryClient`:
|
|
482
482
|
|
|
483
483
|
```ts
|
|
484
484
|
new QueryClient({
|
|
485
485
|
store,
|
|
486
|
-
|
|
486
|
+
adapters: [new DBQueryAdapter()],
|
|
487
487
|
});
|
|
488
488
|
```
|
|
489
489
|
|
|
490
490
|
### Defining the query class
|
|
491
491
|
|
|
492
|
-
The query class is purely declarative. It declares `static
|
|
492
|
+
The query class is purely declarative. It declares `static adapter` to point at the adapter, defines `params`, `result`, and `getIdentityKey()`, and can include any additional fields your adapter reads:
|
|
493
493
|
|
|
494
494
|
```ts
|
|
495
495
|
import { Query, t } from 'fetchium';
|
|
496
496
|
|
|
497
497
|
abstract class DBQuery extends Query {
|
|
498
|
-
static override
|
|
498
|
+
static override adapter = DBQueryAdapter;
|
|
499
499
|
|
|
500
500
|
abstract collection: string;
|
|
501
501
|
abstract id: unknown;
|
|
@@ -520,10 +520,10 @@ class GetUser extends DBQuery {
|
|
|
520
520
|
Here is a more complete example --- a GraphQL adapter:
|
|
521
521
|
|
|
522
522
|
```ts
|
|
523
|
-
import {
|
|
523
|
+
import { QueryAdapter, Query, t } from 'fetchium';
|
|
524
524
|
|
|
525
|
-
//
|
|
526
|
-
class
|
|
525
|
+
// Adapter: owns the transport
|
|
526
|
+
class GraphQLAdapter extends QueryAdapter {
|
|
527
527
|
async send(ctx: Query, signal: AbortSignal): Promise<unknown> {
|
|
528
528
|
const q = ctx as GraphQLQuery;
|
|
529
529
|
const { log } = this.queryClient!.getContext();
|
|
@@ -551,7 +551,7 @@ class GraphQLController extends QueryController {
|
|
|
551
551
|
|
|
552
552
|
// Base query class: purely declarative
|
|
553
553
|
abstract class GraphQLQuery extends Query {
|
|
554
|
-
static override
|
|
554
|
+
static override adapter = GraphQLAdapter;
|
|
555
555
|
|
|
556
556
|
abstract query: string;
|
|
557
557
|
abstract variables?: Record<string, unknown>;
|
|
@@ -575,7 +575,7 @@ class GetUser extends GraphQLQuery {
|
|
|
575
575
|
}
|
|
576
576
|
```
|
|
577
577
|
|
|
578
|
-
Custom queries participate in all the same systems as `RESTQuery` --- caching, entity normalization, live data, refetching, and pagination (via `sendNext()` and `hasNext()` on the
|
|
578
|
+
Custom queries participate in all the same systems as `RESTQuery` --- caching, entity normalization, live data, refetching, and pagination (via `sendNext()` and `hasNext()` on the adapter). The `Query` base class provides the full reactive lifecycle; your adapter only needs to implement the transport.
|
|
579
579
|
|
|
580
580
|
{% callout title="The identity key" type="note" %}
|
|
581
581
|
`getIdentityKey()` returns a value that uniquely identifies this query's _definition_. Two query instances with the same identity key and the same params share the same cache entry and are deduplicated. For `RESTQuery`, the default is `${method}:${path}`. For custom adapters, choose a key that captures all the inputs that make a query unique.
|
|
@@ -85,7 +85,7 @@ class GetPrices extends RESTQuery {
|
|
|
85
85
|
The `subscribe` function receives an `onEvent` callback that accepts `MutationEvent` objects and returns a cleanup function. Fetchium calls `subscribe` when the query activates (a component reads it) and calls the cleanup function when the query deactivates (all observers disconnect).
|
|
86
86
|
|
|
87
87
|
{% callout %}
|
|
88
|
-
The `subscribe` config is a low-level building block. For polling, use the built-in `poll()` helper. For topic-based streaming (WebSocket message buses, SSE, pub/sub), use [TopicQuery](#topic-queries) --- which provides a declarative,
|
|
88
|
+
The `subscribe` config is a low-level building block. For polling, use the built-in `poll()` helper. For topic-based streaming (WebSocket message buses, SSE, pub/sub), use [TopicQuery](#topic-queries) --- which provides a declarative, adapter-based approach.
|
|
89
89
|
{% /callout %}
|
|
90
90
|
|
|
91
91
|
### Polling
|
|
@@ -129,7 +129,7 @@ Both mechanisms feed into the same entity event system, so you can mix and match
|
|
|
129
129
|
|
|
130
130
|
## Topic Queries
|
|
131
131
|
|
|
132
|
-
For applications with a centralized message bus --- a single WebSocket connection, an SSE endpoint, a pub/sub system --- `TopicQuery` provides a declarative adapter. Instead of manually wiring `subscribe` callbacks per query, you define _topics_ and let
|
|
132
|
+
For applications with a centralized message bus --- a single WebSocket connection, an SSE endpoint, a pub/sub system --- `TopicQuery` provides a declarative adapter. Instead of manually wiring `subscribe` callbacks per query, you define _topics_ and let an adapter manage the connection lifecycle.
|
|
133
133
|
|
|
134
134
|
### Defining a topic query
|
|
135
135
|
|
|
@@ -164,14 +164,14 @@ class GetBalances extends MyTopicQuery {
|
|
|
164
164
|
|
|
165
165
|
The identity key for a topic query is `topic:${topic}` --- two queries with the same topic and params share the same cache entry and are deduplicated.
|
|
166
166
|
|
|
167
|
-
### Implementing
|
|
167
|
+
### Implementing an adapter
|
|
168
168
|
|
|
169
|
-
The `
|
|
169
|
+
The `TopicQueryAdapter` is the bridge between your message bus and Fetchium. Extend it and implement two abstract methods:
|
|
170
170
|
|
|
171
171
|
```tsx
|
|
172
|
-
import {
|
|
172
|
+
import { TopicQueryAdapter } from 'fetchium/topic';
|
|
173
173
|
|
|
174
|
-
class
|
|
174
|
+
class MyStreamAdapter extends TopicQueryAdapter {
|
|
175
175
|
private ws: WebSocket;
|
|
176
176
|
|
|
177
177
|
constructor(url: string) {
|
|
@@ -205,7 +205,7 @@ class MyStreamController extends TopicQueryController {
|
|
|
205
205
|
}
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
The
|
|
208
|
+
The adapter has several protected helper methods:
|
|
209
209
|
|
|
210
210
|
| Method | Description |
|
|
211
211
|
| --------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
@@ -215,33 +215,33 @@ The controller has several protected helper methods:
|
|
|
215
215
|
| `clearTopic(topic)` | Clear buffered state for a topic. Call this in `unsubscribe` to reset for the next subscription cycle. |
|
|
216
216
|
| `clearAll()` | Clear all buffered topic state. Useful when resetting the connection. |
|
|
217
217
|
|
|
218
|
-
### Registering the
|
|
218
|
+
### Registering the adapter
|
|
219
219
|
|
|
220
|
-
Pass the
|
|
220
|
+
Pass the adapter to `QueryClient` in the `adapters` array, the same way you register a `RESTQueryAdapter`:
|
|
221
221
|
|
|
222
222
|
```tsx
|
|
223
223
|
import { QueryClient } from 'fetchium';
|
|
224
|
-
import {
|
|
224
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
225
225
|
|
|
226
226
|
const queryClient = new QueryClient({
|
|
227
|
-
|
|
228
|
-
new
|
|
229
|
-
new
|
|
227
|
+
adapters: [
|
|
228
|
+
new RESTQueryAdapter({ baseUrl: '/api' }),
|
|
229
|
+
new MyStreamAdapter('ws://api.example.com/stream'),
|
|
230
230
|
],
|
|
231
231
|
});
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
Then make your topic query classes reference the
|
|
234
|
+
Then make your topic query classes reference the adapter:
|
|
235
235
|
|
|
236
236
|
```tsx
|
|
237
237
|
abstract class MyTopicQuery extends TopicQuery {
|
|
238
|
-
static override
|
|
238
|
+
static override adapter = MyStreamAdapter;
|
|
239
239
|
}
|
|
240
240
|
```
|
|
241
241
|
|
|
242
242
|
### Pre-fulfillment
|
|
243
243
|
|
|
244
|
-
A powerful feature of the
|
|
244
|
+
A powerful feature of the adapter is that `fulfillTopic` can be called _before_ the query activates. If your message bus proactively sends data for topics it knows the page will need, the adapter can buffer that data:
|
|
245
245
|
|
|
246
246
|
```tsx
|
|
247
247
|
// Data arrives from the stream before any component subscribes
|
|
@@ -257,8 +257,8 @@ This enables smart pre-fetching strategies where the server pushes data ahead of
|
|
|
257
257
|
|
|
258
258
|
The full lifecycle of a topic query:
|
|
259
259
|
|
|
260
|
-
1. **Component reads the query** --- Fetchium calls `send()` on the
|
|
261
|
-
2. **
|
|
260
|
+
1. **Component reads the query** --- Fetchium calls `send()` on the adapter, which creates a deferred promise and calls your `subscribe(topic)` implementation.
|
|
261
|
+
2. **Adapter subscribes** --- Your implementation connects to the message bus for this topic (e.g., sends a subscribe message over WebSocket).
|
|
262
262
|
3. **Initial data arrives** --- Your `onmessage` handler calls `fulfillTopic(topic, data)`, resolving the deferred promise. The component renders with the data.
|
|
263
263
|
4. **Ongoing updates** --- Your handler calls `sendMutationEvent(event)` for each update. Live arrays and live values react automatically.
|
|
264
264
|
5. **Component unmounts** --- Fetchium calls your `unsubscribe(topic)` implementation. Your code disconnects from the message bus for this topic.
|
|
@@ -324,15 +324,15 @@ If a mutation with optimistic updates fails, the rollback restores the entity to
|
|
|
324
324
|
|
|
325
325
|
## Custom Mutations
|
|
326
326
|
|
|
327
|
-
`RESTMutation` is an adapter for JSON REST APIs. But mutations as a concept are protocol-agnostic. When your use case doesn't fit REST --- GraphQL, file uploads, WebSocket messages, RPC calls --- you build a **`
|
|
327
|
+
`RESTMutation` is an adapter for JSON REST APIs. But mutations as a concept are protocol-agnostic. When your use case doesn't fit REST --- GraphQL, file uploads, WebSocket messages, RPC calls --- you build a **`QueryAdapter`** that handles the transport and a **`Mutation`** subclass that stays purely declarative.
|
|
328
328
|
|
|
329
|
-
The same
|
|
329
|
+
The same adapter that handles queries can also handle mutations by implementing `sendMutation(ctx, signal)`. This means custom query and mutation transports for the same protocol live in one place:
|
|
330
330
|
|
|
331
331
|
```ts
|
|
332
|
-
import {
|
|
332
|
+
import { QueryAdapter, Mutation, t } from 'fetchium';
|
|
333
333
|
import type { Query } from 'fetchium';
|
|
334
334
|
|
|
335
|
-
class
|
|
335
|
+
class MyAdapter extends QueryAdapter {
|
|
336
336
|
async send(ctx: Query, signal: AbortSignal): Promise<unknown> {
|
|
337
337
|
// ... query transport
|
|
338
338
|
}
|
|
@@ -362,7 +362,7 @@ The mutation class is purely declarative:
|
|
|
362
362
|
import { Mutation, t } from 'fetchium';
|
|
363
363
|
|
|
364
364
|
class UploadAvatar extends Mutation {
|
|
365
|
-
static override
|
|
365
|
+
static override adapter = MyAdapter;
|
|
366
366
|
|
|
367
367
|
params = { userId: t.id, file: t.any };
|
|
368
368
|
result = { url: t.string };
|
|
@@ -79,7 +79,7 @@ function App() {
|
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
This is the minimal setup. The store defaults to an in-memory cache and `
|
|
82
|
+
This is the minimal setup. The store defaults to an in-memory cache and `RESTQueryAdapter` is auto-instantiated on first use with `globalThis.fetch`. When you need a `baseUrl`, auth headers, or persistent storage, pass explicit options --- see [Project Setup](/setup/project-setup).
|
|
83
83
|
|
|
84
84
|
{% callout title="Want to go deeper?" type="note" %}
|
|
85
85
|
For a complete guide to configuring `baseUrl`, auth headers, persistent stores, and project structure, see [Project Setup](/setup/project-setup).
|
|
@@ -26,12 +26,12 @@ The `QueryClient` constructor takes a single config object. The only required fi
|
|
|
26
26
|
```tsx
|
|
27
27
|
import { QueryClient } from 'fetchium';
|
|
28
28
|
import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
29
|
-
import {
|
|
29
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
30
30
|
|
|
31
31
|
const client = new QueryClient({
|
|
32
32
|
store: new SyncQueryStore(new MemoryPersistentStore()),
|
|
33
|
-
|
|
34
|
-
new
|
|
33
|
+
adapters: [
|
|
34
|
+
new RESTQueryAdapter({
|
|
35
35
|
fetch: globalThis.fetch,
|
|
36
36
|
baseUrl: 'https://api.example.com',
|
|
37
37
|
}),
|
|
@@ -43,29 +43,29 @@ The store is responsible for _persistent_ caching --- saving query results and e
|
|
|
43
43
|
|
|
44
44
|
### QueryClientConfig options
|
|
45
45
|
|
|
46
|
-
| Option
|
|
47
|
-
|
|
|
48
|
-
| `store`
|
|
49
|
-
| `
|
|
50
|
-
| `log`
|
|
46
|
+
| Option | Type | Default | Description |
|
|
47
|
+
| ---------- | ---------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| `store` | `QueryStore` | `SyncQueryStore` (in-memory) | Persistent storage backend for query results and entity data. Defaults to an in-memory store — data is lost on page refresh. |
|
|
49
|
+
| `adapters` | `QueryAdapter[]` | `[]` | Transport adapters. Register a `RESTQueryAdapter` to configure `fetch`, `baseUrl`, and headers for REST queries. |
|
|
50
|
+
| `log` | `object` | `console` | A logger with `warn` and `error` methods. Fetchium uses `log.warn` for non-fatal parse failures. |
|
|
51
51
|
|
|
52
52
|
### Auto-instantiation
|
|
53
53
|
|
|
54
|
-
Both the store and
|
|
54
|
+
Both the store and adapters have sensible defaults, so the minimal `QueryClient` requires no configuration at all:
|
|
55
55
|
|
|
56
56
|
```tsx
|
|
57
|
-
// Fully minimal — in-memory store,
|
|
57
|
+
// Fully minimal — in-memory store, RESTQueryAdapter auto-instantiated on first use
|
|
58
58
|
const client = new QueryClient();
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
- `store` defaults to `SyncQueryStore(MemoryPersistentStore)` — data lives in memory and is lost on page refresh
|
|
62
|
-
-
|
|
62
|
+
- Adapters are auto-instantiated from their base class the first time a query of that type runs. `RESTQueryAdapter` has a no-arg constructor that defaults to `globalThis.fetch`
|
|
63
63
|
|
|
64
64
|
Once you need a `baseUrl`, auth headers, persistent storage, or a custom fetch wrapper, pass explicit options.
|
|
65
65
|
|
|
66
|
-
### The
|
|
66
|
+
### The RESTQueryAdapter
|
|
67
67
|
|
|
68
|
-
`
|
|
68
|
+
`RESTQueryAdapter` is the transport layer for all REST queries and mutations. It accepts:
|
|
69
69
|
|
|
70
70
|
| Option | Type | Default | Description |
|
|
71
71
|
| --------- | ---------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -85,13 +85,13 @@ The `QueryClient` is made available to your component tree through Signalium's `
|
|
|
85
85
|
```tsx
|
|
86
86
|
import { QueryClient, QueryClientContext } from 'fetchium';
|
|
87
87
|
import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
88
|
-
import {
|
|
88
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
89
89
|
import { ContextProvider } from 'signalium/react';
|
|
90
90
|
|
|
91
91
|
const client = new QueryClient({
|
|
92
92
|
store: new SyncQueryStore(new MemoryPersistentStore()),
|
|
93
|
-
|
|
94
|
-
new
|
|
93
|
+
adapters: [
|
|
94
|
+
new RESTQueryAdapter({
|
|
95
95
|
fetch: globalThis.fetch,
|
|
96
96
|
baseUrl: 'https://api.example.com',
|
|
97
97
|
}),
|
|
@@ -248,12 +248,12 @@ A single file creates and exports the `QueryClient`. This is the place to config
|
|
|
248
248
|
// src/api/client.ts
|
|
249
249
|
import { QueryClient } from 'fetchium';
|
|
250
250
|
import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
251
|
-
import {
|
|
251
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
252
252
|
|
|
253
253
|
export const queryClient = new QueryClient({
|
|
254
254
|
store: new SyncQueryStore(new MemoryPersistentStore()),
|
|
255
|
-
|
|
256
|
-
new
|
|
255
|
+
adapters: [
|
|
256
|
+
new RESTQueryAdapter({
|
|
257
257
|
fetch: globalThis.fetch,
|
|
258
258
|
baseUrl: import.meta.env.VITE_API_URL ?? 'https://api.example.com',
|
|
259
259
|
}),
|