fetchium 0.1.0 → 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.
Files changed (139) hide show
  1. package/CHANGELOG.md +12 -5
  2. package/README.md +1 -1
  3. package/dist/cjs/development/QueryAdapter-DUo338ga.js +2 -0
  4. package/dist/cjs/development/QueryAdapter-DUo338ga.js.map +1 -0
  5. package/dist/cjs/development/QueryClient-m7BzCIe9.js +2 -0
  6. package/dist/cjs/development/QueryClient-m7BzCIe9.js.map +1 -0
  7. package/dist/cjs/development/index.js +1 -1
  8. package/dist/cjs/development/index.js.map +1 -1
  9. package/dist/cjs/development/mutation-wUhcGxKl.js +2 -0
  10. package/dist/cjs/development/mutation-wUhcGxKl.js.map +1 -0
  11. package/dist/cjs/development/react/index.js +1 -1
  12. package/dist/cjs/development/rest/index.js +2 -0
  13. package/dist/cjs/development/rest/index.js.map +1 -0
  14. package/dist/cjs/development/topic/index.js +2 -0
  15. package/dist/cjs/development/topic/index.js.map +1 -0
  16. package/dist/cjs/production/QueryAdapter-DUo338ga.js +2 -0
  17. package/dist/cjs/production/QueryAdapter-DUo338ga.js.map +1 -0
  18. package/dist/cjs/production/QueryClient-4T90peFN.js +2 -0
  19. package/dist/cjs/production/QueryClient-4T90peFN.js.map +1 -0
  20. package/dist/cjs/production/index.js +1 -1
  21. package/dist/cjs/production/index.js.map +1 -1
  22. package/dist/cjs/production/mutation-Dk0gznwX.js +2 -0
  23. package/dist/cjs/production/mutation-Dk0gznwX.js.map +1 -0
  24. package/dist/cjs/production/react/index.js +1 -1
  25. package/dist/cjs/production/rest/index.js +2 -0
  26. package/dist/cjs/production/rest/index.js.map +1 -0
  27. package/dist/cjs/production/topic/index.js +2 -0
  28. package/dist/cjs/production/topic/index.js.map +1 -0
  29. package/dist/esm/MutationResult.d.ts +0 -1
  30. package/dist/esm/MutationResult.d.ts.map +1 -1
  31. package/dist/esm/QueryAdapter.d.ts +49 -0
  32. package/dist/esm/QueryAdapter.d.ts.map +1 -0
  33. package/dist/esm/QueryClient.d.ts +26 -4
  34. package/dist/esm/QueryClient.d.ts.map +1 -1
  35. package/dist/esm/QueryResult.d.ts +11 -11
  36. package/dist/esm/QueryResult.d.ts.map +1 -1
  37. package/dist/esm/development/QueryAdapter-Bu5UJjE4.js +14 -0
  38. package/dist/esm/development/QueryAdapter-Bu5UJjE4.js.map +1 -0
  39. package/dist/esm/development/QueryClient-BajBmpnA.js +2572 -0
  40. package/dist/esm/development/QueryClient-BajBmpnA.js.map +1 -0
  41. package/dist/esm/development/index.js +29 -100
  42. package/dist/esm/development/index.js.map +1 -1
  43. package/dist/esm/development/mutation-DAOZE4Ok.js +58 -0
  44. package/dist/esm/development/mutation-DAOZE4Ok.js.map +1 -0
  45. package/dist/esm/development/react/index.js +1 -1
  46. package/dist/esm/development/rest/index.js +142 -0
  47. package/dist/esm/development/rest/index.js.map +1 -0
  48. package/dist/esm/development/{shared-Dq2yW78d.js → shared-DcuVH8Pf.js} +5 -5
  49. package/dist/esm/development/{shared-Dq2yW78d.js.map → shared-DcuVH8Pf.js.map} +1 -1
  50. package/dist/esm/development/stores/async.js +6 -6
  51. package/dist/esm/development/stores/sync.js +5 -5
  52. package/dist/esm/development/topic/index.js +86 -0
  53. package/dist/esm/development/topic/index.js.map +1 -0
  54. package/dist/esm/index.d.ts +5 -4
  55. package/dist/esm/index.d.ts.map +1 -1
  56. package/dist/esm/mutation.d.ts +6 -19
  57. package/dist/esm/mutation.d.ts.map +1 -1
  58. package/dist/esm/production/QueryAdapter-Bu5UJjE4.js +14 -0
  59. package/dist/esm/production/QueryAdapter-Bu5UJjE4.js.map +1 -0
  60. package/dist/esm/production/{QueryClient-BP0Z1rQV.js → QueryClient-KH0Ex_8m.js} +595 -591
  61. package/dist/esm/production/QueryClient-KH0Ex_8m.js.map +1 -0
  62. package/dist/esm/production/index.js +29 -100
  63. package/dist/esm/production/index.js.map +1 -1
  64. package/dist/esm/production/mutation-C7BOChR2.js +58 -0
  65. package/dist/esm/production/mutation-C7BOChR2.js.map +1 -0
  66. package/dist/esm/production/react/index.js +1 -1
  67. package/dist/esm/production/rest/index.js +142 -0
  68. package/dist/esm/production/rest/index.js.map +1 -0
  69. package/dist/esm/production/{shared-Dq2yW78d.js → shared-DcuVH8Pf.js} +5 -5
  70. package/dist/esm/production/{shared-Dq2yW78d.js.map → shared-DcuVH8Pf.js.map} +1 -1
  71. package/dist/esm/production/stores/async.js +6 -6
  72. package/dist/esm/production/stores/sync.js +5 -5
  73. package/dist/esm/production/topic/index.js +86 -0
  74. package/dist/esm/production/topic/index.js.map +1 -0
  75. package/dist/esm/query-types.d.ts +2 -4
  76. package/dist/esm/query-types.d.ts.map +1 -1
  77. package/dist/esm/query.d.ts +17 -39
  78. package/dist/esm/query.d.ts.map +1 -1
  79. package/dist/esm/rest/RESTMutation.d.ts +18 -0
  80. package/dist/esm/rest/RESTMutation.d.ts.map +1 -0
  81. package/dist/esm/rest/RESTQuery.d.ts +24 -0
  82. package/dist/esm/rest/RESTQuery.d.ts.map +1 -0
  83. package/dist/esm/rest/RESTQueryAdapter.d.ts +34 -0
  84. package/dist/esm/rest/RESTQueryAdapter.d.ts.map +1 -0
  85. package/dist/esm/rest/index.d.ts +5 -0
  86. package/dist/esm/rest/index.d.ts.map +1 -0
  87. package/dist/esm/stores/shared.d.ts.map +1 -1
  88. package/dist/esm/testing/MockClient.d.ts +64 -0
  89. package/dist/esm/testing/MockClient.d.ts.map +1 -0
  90. package/dist/esm/testing/auto-generate.d.ts +20 -0
  91. package/dist/esm/testing/auto-generate.d.ts.map +1 -0
  92. package/dist/esm/testing/entity-factory.d.ts +13 -0
  93. package/dist/esm/testing/entity-factory.d.ts.map +1 -0
  94. package/dist/esm/testing/index.d.ts +6 -0
  95. package/dist/esm/testing/index.d.ts.map +1 -0
  96. package/dist/esm/testing/types.d.ts +37 -0
  97. package/dist/esm/testing/types.d.ts.map +1 -0
  98. package/dist/esm/topic/TopicQuery.d.ts +10 -0
  99. package/dist/esm/topic/TopicQuery.d.ts.map +1 -0
  100. package/dist/esm/topic/TopicQueryAdapter.d.ts +43 -0
  101. package/dist/esm/topic/TopicQueryAdapter.d.ts.map +1 -0
  102. package/dist/esm/topic/index.d.ts +3 -0
  103. package/dist/esm/topic/index.d.ts.map +1 -0
  104. package/dist/esm/typeDefs.d.ts +1 -1
  105. package/dist/esm/types.d.ts +9 -4
  106. package/dist/esm/types.d.ts.map +1 -1
  107. package/package.json +51 -4
  108. package/plugin/.claude-plugin/plugin.json +10 -0
  109. package/plugin/agents/fetchium.md +168 -0
  110. package/plugin/docs/api/fetchium-react.md +135 -0
  111. package/plugin/docs/api/fetchium.md +674 -0
  112. package/plugin/docs/api/stores-async.md +219 -0
  113. package/plugin/docs/api/stores-sync.md +133 -0
  114. package/plugin/docs/core/entities.md +351 -0
  115. package/plugin/docs/core/queries.md +600 -0
  116. package/plugin/docs/core/streaming.md +550 -0
  117. package/plugin/docs/core/types.md +374 -0
  118. package/plugin/docs/data/caching.md +298 -0
  119. package/plugin/docs/data/live-data.md +435 -0
  120. package/plugin/docs/data/mutations.md +465 -0
  121. package/plugin/docs/guides/auth.md +318 -0
  122. package/plugin/docs/guides/error-handling.md +351 -0
  123. package/plugin/docs/guides/offline.md +270 -0
  124. package/plugin/docs/guides/testing.md +301 -0
  125. package/plugin/docs/quickstart.md +170 -0
  126. package/plugin/docs/reference/pagination.md +519 -0
  127. package/plugin/docs/reference/rest-queries.md +107 -0
  128. package/plugin/docs/reference/why-signalium.md +364 -0
  129. package/plugin/docs/setup/project-setup.md +319 -0
  130. package/plugin/install.mjs +88 -0
  131. package/plugin/skills/design/SKILL.md +140 -0
  132. package/plugin/skills/teach/SKILL.md +105 -0
  133. package/dist/cjs/development/QueryClient-CpmwggOn.js +0 -2
  134. package/dist/cjs/development/QueryClient-CpmwggOn.js.map +0 -1
  135. package/dist/cjs/production/QueryClient-qi3bR0eD.js +0 -2
  136. package/dist/cjs/production/QueryClient-qi3bR0eD.js.map +0 -1
  137. package/dist/esm/development/QueryClient-DRZtPKFD.js +0 -2568
  138. package/dist/esm/development/QueryClient-DRZtPKFD.js.map +0 -1
  139. package/dist/esm/production/QueryClient-BP0Z1rQV.js.map +0 -1
@@ -0,0 +1,319 @@
1
+ ---
2
+ title: Project Setup
3
+ ---
4
+
5
+ The [Quick Start](/quickstart) gets you from zero to a running query in five steps. This page goes deeper --- it explains _what_ each piece of the setup does, _why_ it exists, and how to configure it for a real production application.
6
+
7
+ If you are coming from libraries like TanStack Query or SWR, you may be used to creating a client object and passing it through React context. Fetchium follows a similar pattern, but with a few key differences: the `QueryClient` is _not_ a React-specific concept (it works in any JavaScript environment), it delegates persistence to a _pluggable store_, and it uses Signalium's context system rather than React's built-in `createContext`.
8
+
9
+ ---
10
+
11
+ ## The QueryClient
12
+
13
+ The `QueryClient` is the central coordinator of a Fetchium application. It manages:
14
+
15
+ - **Query instances** --- deduplicating in-flight requests, caching results, scheduling refetches
16
+ - **The entity store** --- normalizing, deduplicating, and storing entities by typename + id
17
+ - **Garbage collection** --- evicting unused queries and entities from memory
18
+ - **Network awareness** --- pausing and resuming queries based on connectivity
19
+
20
+ Every Fetchium operation --- `fetchQuery`, `useQuery`, `getMutation` --- looks up the `QueryClient` from a Signalium context. If no client is found, these calls will throw.
21
+
22
+ ### Creating a QueryClient
23
+
24
+ The `QueryClient` constructor takes a single config object. The only required field is `store`:
25
+
26
+ ```tsx
27
+ import { QueryClient } from 'fetchium';
28
+ import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
29
+ import { RESTQueryAdapter } from 'fetchium/rest';
30
+
31
+ const client = new QueryClient({
32
+ store: new SyncQueryStore(new MemoryPersistentStore()),
33
+ adapters: [
34
+ new RESTQueryAdapter({
35
+ fetch: globalThis.fetch,
36
+ baseUrl: 'https://api.example.com',
37
+ }),
38
+ ],
39
+ });
40
+ ```
41
+
42
+ The store is responsible for _persistent_ caching --- saving query results and entity data so they survive page refreshes, app restarts, or being evicted from the in-memory cache. `MemoryPersistentStore` keeps everything in memory (data is lost on refresh), which is perfect for development and tests. For production persistence, implement the `SyncPersistentStore` interface with a durable backend like `localStorage`, or use the `AsyncQueryStore` with IndexedDB. See the [Offline & Persistence](/guides/offline) guide for details.
43
+
44
+ ### QueryClientConfig options
45
+
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
+
52
+ ### Auto-instantiation
53
+
54
+ Both the store and adapters have sensible defaults, so the minimal `QueryClient` requires no configuration at all:
55
+
56
+ ```tsx
57
+ // Fully minimal — in-memory store, RESTQueryAdapter auto-instantiated on first use
58
+ const client = new QueryClient();
59
+ ```
60
+
61
+ - `store` defaults to `SyncQueryStore(MemoryPersistentStore)` — data lives in memory and is lost on page refresh
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
+
64
+ Once you need a `baseUrl`, auth headers, persistent storage, or a custom fetch wrapper, pass explicit options.
65
+
66
+ ### The RESTQueryAdapter
67
+
68
+ `RESTQueryAdapter` is the transport layer for all REST queries and mutations. It accepts:
69
+
70
+ | Option | Type | Default | Description |
71
+ | --------- | ---------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
+ | `fetch` | `Function` | `globalThis.fetch` | The fetch function used for all network requests. Pass a custom wrapper for auth headers, logging, or testing. |
73
+ | `baseUrl` | `string` | `''` | Prepended to every query path. Set this to your API root (`https://api.example.com`) so your query paths can be relative (`/users/42` instead of the full URL). |
74
+
75
+ `fetch` is the _single point of control_ for how Fetchium makes network requests. Every REST query and mutation flows through this function, which means you can add authentication, logging, retry logic, or any other cross-cutting concern in one place. We cover this in depth in the [Auth & Headers](/guides/auth) guide.
76
+
77
+ {% callout title="Why pass fetch explicitly?" type="note" %}
78
+ You might wonder why Fetchium asks you to pass `fetch` instead of just using the global. The reason is _testability_ and _universality_. By accepting `fetch` as a parameter, Fetchium works identically in browsers, Node.js, Deno, and test environments. In tests, you pass a mock fetch. On the server, you pass Node's fetch. In the browser, you pass a wrapper that adds auth headers. The interface is always the same.
79
+ {% /callout %}
80
+
81
+ ### Providing the client to your app
82
+
83
+ The `QueryClient` is made available to your component tree through Signalium's `ContextProvider`. This is analogous to React's `Context.Provider`, but works with Signalium's reactive context system:
84
+
85
+ ```tsx
86
+ import { QueryClient, QueryClientContext } from 'fetchium';
87
+ import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
88
+ import { RESTQueryAdapter } from 'fetchium/rest';
89
+ import { ContextProvider } from 'signalium/react';
90
+
91
+ const client = new QueryClient({
92
+ store: new SyncQueryStore(new MemoryPersistentStore()),
93
+ adapters: [
94
+ new RESTQueryAdapter({
95
+ fetch: globalThis.fetch,
96
+ baseUrl: 'https://api.example.com',
97
+ }),
98
+ ],
99
+ });
100
+
101
+ function App() {
102
+ return (
103
+ <ContextProvider value={client} context={QueryClientContext}>
104
+ <YourApp />
105
+ </ContextProvider>
106
+ );
107
+ }
108
+ ```
109
+
110
+ Any component or reactive function inside this provider can now call `useQuery`, `fetchQuery`, or `getMutation`, and they will automatically find the client.
111
+
112
+ {% callout title="If you are using Signalium mode" type="note" %}
113
+ When using `component()` from `signalium/react`, the `ContextProvider` is the standard way to provide contexts. If you are using `useQuery` with plain React hooks, you still need the `ContextProvider` --- the hook reads from the Signalium context internally.
114
+ {% /callout %}
115
+
116
+ ---
117
+
118
+ ## The Babel Transform
119
+
120
+ Signalium includes a Babel transform that enables two features:
121
+
122
+ 1. **Async reactivity** --- rewriting `async` functions used with `reactive()` into generator-based coroutines, so Signalium can track dependencies across `await` boundaries
123
+ 2. **Callback tracking** --- wrapping callback arguments for reactive tracking inside event handlers
124
+
125
+ The transform is _optional_ if you only use `useQuery` with plain React hooks. It becomes _necessary_ when you use Signalium's `reactive()` with `async` functions, which is one of the most powerful patterns for composing queries.
126
+
127
+ ### Vite + React
128
+
129
+ ```js
130
+ import { defineConfig } from 'vite';
131
+ import react from '@vitejs/plugin-react';
132
+ import { signaliumPreset } from 'signalium/transform';
133
+
134
+ export default defineConfig({
135
+ plugins: [
136
+ react({
137
+ babel: {
138
+ presets: [signaliumPreset()],
139
+ },
140
+ }),
141
+ ],
142
+ });
143
+ ```
144
+
145
+ ### Next.js
146
+
147
+ ```js
148
+ // next.config.js
149
+ const withSignalium = require('signalium/transform/next');
150
+
151
+ module.exports = withSignalium({
152
+ // your Next.js config
153
+ });
154
+ ```
155
+
156
+ ### Generic Babel config
157
+
158
+ ```js
159
+ // babel.config.js
160
+ import { signaliumPreset } from 'signalium/transform';
161
+
162
+ module.exports = {
163
+ presets: [
164
+ '@babel/preset-env',
165
+ '@babel/preset-react',
166
+ '@babel/preset-typescript',
167
+ signaliumPreset(),
168
+ ],
169
+ };
170
+ ```
171
+
172
+ The transform only affects code that uses Signalium APIs (`reactive`, `relay`, `task`). Standard async/await outside of reactive contexts is untouched. For a deeper look at what the transform does and when you need it, see [Why Signalium?](/reference/why-signalium).
173
+
174
+ ---
175
+
176
+ ## Project Structure
177
+
178
+ Fetchium does not enforce a particular file layout, but after working with it across projects, a pattern has emerged that works well.
179
+
180
+ ### Recommended layout
181
+
182
+ ```
183
+ src/
184
+ api/
185
+ client.ts # QueryClient creation and configuration
186
+ entities/ # Entity class definitions
187
+ User.ts
188
+ Post.ts
189
+ Comment.ts
190
+ queries/ # Query class definitions
191
+ GetUser.ts
192
+ GetPosts.ts
193
+ GetFeed.ts
194
+ mutations/ # Mutation class definitions
195
+ CreatePost.ts
196
+ UpdateUser.ts
197
+ reactive/ # Shared reactive functions (Signalium mode)
198
+ useUserProfile.ts
199
+ useFeed.ts
200
+ components/
201
+ ...
202
+ ```
203
+
204
+ The key principle is _separation of data definitions from UI_. Your entity and query classes are plain TypeScript --- they have no dependency on React, no JSX, no component logic. This makes them testable, reusable across frameworks, and easy to reason about independently.
205
+
206
+ ### Entity files
207
+
208
+ Each entity gets its own file. Entities tend to be referenced across many queries and mutations, so isolating them prevents circular imports:
209
+
210
+ ```ts
211
+ // src/api/entities/User.ts
212
+ import { Entity, t } from 'fetchium';
213
+
214
+ export class User extends Entity {
215
+ __typename = t.typename('User');
216
+ id = t.id;
217
+
218
+ name = t.string;
219
+ email = t.string;
220
+ avatar = t.optional(t.string);
221
+ }
222
+ ```
223
+
224
+ ### Query files
225
+
226
+ Queries import from entities and define the API surface:
227
+
228
+ ```ts
229
+ // src/api/queries/GetUser.ts
230
+ import { t } from 'fetchium';
231
+ import { RESTQuery } from 'fetchium/rest';
232
+ import { User } from '../entities/User';
233
+
234
+ export class GetUser extends RESTQuery {
235
+ params = { id: t.number };
236
+
237
+ path = `/users/${this.params.id}`;
238
+
239
+ result = { user: t.entity(User) };
240
+ }
241
+ ```
242
+
243
+ ### The client file
244
+
245
+ A single file creates and exports the `QueryClient`. This is the place to configure `baseUrl`, auth wrappers, and logging:
246
+
247
+ ```ts
248
+ // src/api/client.ts
249
+ import { QueryClient } from 'fetchium';
250
+ import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
251
+ import { RESTQueryAdapter } from 'fetchium/rest';
252
+
253
+ export const queryClient = new QueryClient({
254
+ store: new SyncQueryStore(new MemoryPersistentStore()),
255
+ adapters: [
256
+ new RESTQueryAdapter({
257
+ fetch: globalThis.fetch,
258
+ baseUrl: import.meta.env.VITE_API_URL ?? 'https://api.example.com',
259
+ }),
260
+ ],
261
+ });
262
+ ```
263
+
264
+ ### Shared reactive functions (Signalium mode)
265
+
266
+ If you are using Signalium, shared `reactive()` functions that compose multiple queries live in their own directory. These form a _reactive data layer_ between your raw API queries and your components:
267
+
268
+ ```ts
269
+ // src/api/reactive/useUserProfile.ts
270
+ import { reactive } from 'signalium';
271
+ import { fetchQuery } from 'fetchium';
272
+ import { GetUser } from '../queries/GetUser';
273
+ import { GetUserPosts } from '../queries/GetUserPosts';
274
+
275
+ export const getUserProfile = reactive((userId: number) => {
276
+ const user = fetchQuery(GetUser, { id: userId });
277
+ const posts = fetchQuery(GetUserPosts, { userId });
278
+ return { user, posts };
279
+ });
280
+ ```
281
+
282
+ This is not required --- you can call `fetchQuery` directly in components --- but extracting these functions keeps your components focused on rendering, and the reactive functions are automatically memoized and shared across consumers.
283
+
284
+ ---
285
+
286
+ ## Development vs Production
287
+
288
+ Fetchium uses a compile-time constant `IS_DEV` to enable additional runtime checks in development:
289
+
290
+ - **Parse validation warnings** --- detailed logs when response data doesn't match your type definitions
291
+ - **Entity write protection** --- throws when you accidentally try to mutate an entity property directly (entities are read-only)
292
+ - **Reference validation** --- catches common mistakes like using conditional logic in class field definitions
293
+
294
+ In production builds, all `IS_DEV` code paths are tree-shaken, so there is zero runtime cost.
295
+
296
+ Fetchium's `package.json` uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) to serve different bundles:
297
+
298
+ - The `development` condition serves the development build (with `IS_DEV = true`)
299
+ - The `production` condition (and the default) serves the production build (with `IS_DEV = false`)
300
+
301
+ Most bundlers (Vite, webpack, esbuild) resolve the `development` condition automatically when running in development mode. If your dev server is not picking up development warnings, check that your bundler's resolve conditions include `'development'`.
302
+
303
+ ---
304
+
305
+ ## Next Steps
306
+
307
+ With your project set up, you are ready to start defining your API surface:
308
+
309
+ {% quick-links %}
310
+
311
+ {% quick-link title="Queries" icon="presets" href="/core/queries" description="Learn how to define queries, use them in components, and understand the template system" /%}
312
+
313
+ {% quick-link title="Auth & Headers" icon="installation" href="/guides/auth" description="Add authentication tokens and custom headers to your requests" /%}
314
+
315
+ {% quick-link title="Error Handling" icon="theming" href="/guides/error-handling" description="Handle failures gracefully with retries, error states, and global interceptors" /%}
316
+
317
+ {% quick-link title="Testing" icon="plugins" href="/guides/testing" description="Set up a test QueryClient and mock your API layer" /%}
318
+
319
+ {% /quick-links %}
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Sets up Fetchium agent files for Claude Code and/or Cursor by creating
5
+ * symlinks into node_modules/fetchium/plugin/. Files stay in sync
6
+ * automatically when the package is updated.
7
+ *
8
+ * Usage:
9
+ * npx fetchium-agents # set up both .claude/ and .cursor/
10
+ * npx fetchium-agents --cursor # Cursor only
11
+ * npx fetchium-agents --claude # Claude Code only
12
+ */
13
+
14
+ import { existsSync, mkdirSync, symlinkSync, lstatSync, unlinkSync, rmSync } from 'node:fs';
15
+ import { dirname, join, relative, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const pluginRoot = __dirname;
20
+ const projectRoot = resolve(pluginRoot, '../../..');
21
+
22
+ const args = process.argv.slice(2);
23
+ const forceCursor = args.includes('--cursor');
24
+ const forceClaude = args.includes('--claude');
25
+ const installBoth = !forceCursor && !forceClaude;
26
+
27
+ function symlink(target, linkPath) {
28
+ const linkDir = dirname(linkPath);
29
+ mkdirSync(linkDir, { recursive: true });
30
+
31
+ try {
32
+ const stat = lstatSync(linkPath);
33
+ if (stat.isSymbolicLink() || stat.isFile()) {
34
+ unlinkSync(linkPath);
35
+ } else if (stat.isDirectory()) {
36
+ rmSync(linkPath, { recursive: true });
37
+ }
38
+ } catch {
39
+ // doesn't exist, fine
40
+ }
41
+
42
+ const relTarget = relative(linkDir, target);
43
+ symlinkSync(relTarget, linkPath);
44
+ }
45
+
46
+ const installed = [];
47
+
48
+ // --- Claude Code ---
49
+ if (forceClaude || installBoth) {
50
+ symlink(join(pluginRoot, 'agents/fetchium.md'), join(projectRoot, '.claude/agents/fetchium.md'));
51
+ installed.push(' .claude/agents/fetchium.md');
52
+
53
+ symlink(join(pluginRoot, 'skills/design'), join(projectRoot, '.claude/skills/fetchium-design'));
54
+ installed.push(' .claude/skills/fetchium-design/');
55
+
56
+ symlink(join(pluginRoot, 'skills/teach'), join(projectRoot, '.claude/skills/fetchium-teach'));
57
+ installed.push(' .claude/skills/fetchium-teach/');
58
+ }
59
+
60
+ // --- Cursor ---
61
+ if (forceCursor || installBoth) {
62
+ const cursorSkillsIsSymlink =
63
+ existsSync(join(projectRoot, '.cursor/skills')) && lstatSync(join(projectRoot, '.cursor/skills')).isSymbolicLink();
64
+
65
+ symlink(join(pluginRoot, 'agents/fetchium.md'), join(projectRoot, '.cursor/rules/fetchium.md'));
66
+ installed.push(' .cursor/rules/fetchium.md');
67
+
68
+ if (cursorSkillsIsSymlink) {
69
+ installed.push(' .cursor/skills/ is already symlinked — Claude Code skills are shared');
70
+ } else {
71
+ symlink(join(pluginRoot, 'skills/design'), join(projectRoot, '.cursor/skills/fetchium-design'));
72
+ installed.push(' .cursor/skills/fetchium-design/');
73
+
74
+ symlink(join(pluginRoot, 'skills/teach'), join(projectRoot, '.cursor/skills/fetchium-teach'));
75
+ installed.push(' .cursor/skills/fetchium-teach/');
76
+ }
77
+ }
78
+
79
+ if (installed.length > 0) {
80
+ console.log('\nFetchium agent files symlinked (all point to node_modules/fetchium/plugin/):\n');
81
+ for (const line of installed) {
82
+ console.log(line);
83
+ }
84
+ console.log('\nFiles stay in sync automatically when you update the fetchium package.');
85
+ console.log('The plugin is also available for direct use: claude --plugin-dir node_modules/fetchium/plugin\n');
86
+ } else {
87
+ console.log('Nothing to do. Run with --cursor or --claude to target a specific tool.\n');
88
+ }
@@ -0,0 +1,140 @@
1
+ ---
2
+ description: Analyze an existing data model and design Fetchium entities, queries, and mutations. Use when the user wants to map an API, schema, or data model onto Fetchium primitives, plan a migration, or design a new data layer.
3
+ ---
4
+
5
+ # Fetchium Design
6
+
7
+ Help the user analyze an existing data model and map it onto Fetchium's primitives: Entities, Queries, and Mutations. Produce a structured design plan.
8
+
9
+ ## Mode
10
+
11
+ If `$ARGUMENTS` contains "hooks" or "signalium", use that mode for component examples in the design plan. Otherwise, auto-detect by checking the codebase for `signalium` or `signalium/react` imports. Default to React + Hooks if neither is specified nor detected.
12
+
13
+ ## Process
14
+
15
+ ### 1. Gather the existing data model
16
+
17
+ Ask the user to provide one or more of:
18
+
19
+ - REST API endpoints (OpenAPI spec, route definitions, or example curl commands)
20
+ - GraphQL schema or queries
21
+ - TypeScript interfaces or types for API responses
22
+ - Database schema (SQL DDL or ORM models)
23
+ - Example JSON payloads from their API
24
+
25
+ If the user hasn't provided anything yet, ask them to share their data model before proceeding.
26
+
27
+ ### 2. Identify Entities
28
+
29
+ Entities are objects with **identity** — they have a unique typename and ID, and the same object may appear across multiple queries.
30
+
31
+ Look for:
32
+
33
+ - Objects with an `id` field (or similar: `_id`, `uuid`, `pk`)
34
+ - Objects referenced by multiple endpoints or nested in other objects
35
+ - Objects that can be created, updated, or deleted independently
36
+
37
+ For each entity, note:
38
+
39
+ - The typename (use the domain name: `User`, `Post`, `Comment`, etc.)
40
+ - The ID field and type (`string` or `number`)
41
+ - All scalar fields and their types
42
+ - Relationships to other entities (these become `t.entity(OtherEntity)` references)
43
+ - Any union/polymorphic types (need `t.typename(...)` discriminator)
44
+
45
+ **Not everything is an entity.** Plain response wrappers, pagination metadata, and one-off objects without identity should be `t.object({ ... })`, not Entity classes.
46
+
47
+ ### 3. Identify Queries
48
+
49
+ Map read operations to Query classes:
50
+
51
+ - Each GET endpoint or GraphQL query becomes a `RESTQuery` (or custom `Query`) class
52
+ - Identify params: path parameters, query string parameters, headers
53
+ - Identify the result shape: which fields come back, which are entities vs plain objects
54
+ - Note pagination patterns (offset/cursor) — these may need `sendNext()`/`hasNext()`
55
+ - Note polling/subscription needs — these use `config.subscribe`
56
+
57
+ ### 4. Identify Mutations
58
+
59
+ Map write operations to Mutation classes:
60
+
61
+ - Each POST/PUT/PATCH/DELETE endpoint becomes a `RESTMutation` (or custom `Mutation`)
62
+ - Identify params and body shape
63
+ - **Critically:** identify the side effects:
64
+ - **Creates**: which entity type is created? Does the server return the new entity?
65
+ - **Updates**: which entity fields change? Can effects be declared statically from params, or do they need `getEffects()` (server response dependent)?
66
+ - **Deletes**: which entity is removed?
67
+ - Note if optimistic updates are appropriate (simple, predictable changes)
68
+ - Note if `invalidates` is needed (complex server-side logic, bulk operations)
69
+
70
+ ### 5. Flag issues
71
+
72
+ Watch for these common problems:
73
+
74
+ - **Missing IDs**: Objects that should be entities but lack an `id` field
75
+ - **Undiscriminated unions**: Polymorphic arrays/objects without a `type`/`__typename` discriminator field
76
+ - **Circular references**: Entity A references Entity B which references Entity A — Fetchium handles this, but note it
77
+ - **Denormalized data**: The same entity returned with different field subsets across endpoints — design entities with `t.optional()` for fields not always present
78
+ - **Missing typename in API**: The API may not return a `type` field — note where the client needs to inject one
79
+
80
+ ### 6. Produce the design plan
81
+
82
+ Output a structured plan with these sections:
83
+
84
+ **Entities** — list each Entity class with its fields in `t.*` DSL format:
85
+
86
+ ```ts
87
+ class User extends Entity {
88
+ __typename = t.typename('User');
89
+ id = t.id;
90
+ name = t.string;
91
+ email = t.string;
92
+ posts = t.array(t.entity(Post));
93
+ }
94
+ ```
95
+
96
+ **Queries** — list each Query class with params, path, and result:
97
+
98
+ ```ts
99
+ class GetUser extends RESTQuery {
100
+ params = { id: t.id };
101
+ path = `/users/${this.params.id}`;
102
+ result = { user: t.entity(User) };
103
+ }
104
+ ```
105
+
106
+ **Mutations** — list each Mutation class with params, method, body, and effects:
107
+
108
+ ```ts
109
+ class UpdateUser extends RESTMutation {
110
+ params = { id: t.id, name: t.string };
111
+ path = `/users/${this.params.id}`;
112
+ method = 'PUT';
113
+ body = { name: this.params.name };
114
+ result = User;
115
+ effects = {
116
+ updates: [[User, { id: this.params.id, name: this.params.name }]],
117
+ };
118
+ }
119
+ ```
120
+
121
+ **Gaps and decisions** — list anything that needs user input:
122
+
123
+ - Ambiguous entity boundaries
124
+ - Missing discriminators that need to be added
125
+ - Fields that might need `t.optional()` vs required
126
+ - Polling/subscription decisions
127
+ - Live array constraints for collections
128
+
129
+ ## Reference
130
+
131
+ For detailed guidance on any topic, read the relevant files from `node_modules/fetchium/plugin/docs/`:
132
+
133
+ - Entity definitions and proxy behavior: `node_modules/fetchium/plugin/docs/core/entities.md`
134
+ - Query definitions and class rules: `node_modules/fetchium/plugin/docs/core/queries.md`
135
+ - Type DSL reference: `node_modules/fetchium/plugin/docs/core/types.md`
136
+ - Mutation effects and optimistic updates: `node_modules/fetchium/plugin/docs/data/mutations.md`
137
+ - Live arrays and live values: `node_modules/fetchium/plugin/docs/data/live-data.md`
138
+ - REST query configuration: `node_modules/fetchium/plugin/docs/reference/rest-queries.md`
139
+ - Pagination patterns: `node_modules/fetchium/plugin/docs/reference/pagination.md`
140
+ - Streaming and subscriptions: `node_modules/fetchium/plugin/docs/reference/streaming.md`
@@ -0,0 +1,105 @@
1
+ ---
2
+ description: Teach Fetchium concepts with thorough explanations, canonical examples, and documentation links. Use when the user wants to learn about Fetchium, understand how something works, or get a conceptual explanation.
3
+ ---
4
+
5
+ # Fetchium Teach
6
+
7
+ Enter teaching mode. Provide thorough, accurate explanations of Fetchium concepts using the full documentation. Prioritize depth and clarity over brevity.
8
+
9
+ ## Mode
10
+
11
+ If `$ARGUMENTS` contains "hooks" or "signalium", use that mode for all examples. Otherwise, auto-detect by checking the codebase for `signalium` or `signalium/react` imports. If Signalium is detected, show Signalium-style examples; otherwise default to React + Hooks examples.
12
+
13
+ ## Instructions
14
+
15
+ 1. **Read the docs first.** Before answering any question, read the relevant documentation files from `node_modules/fetchium/plugin/docs/`. The full documentation is available there as markdown files. Always ground your answers in the actual docs.
16
+
17
+ 2. **Use canonical examples.** Use the examples from the documentation, not invented ones. The docs contain carefully crafted examples that demonstrate correct patterns and avoid common pitfalls.
18
+
19
+ 3. **Link to the docs site.** For every concept you explain, include a link to the relevant page on the Fetchium docs site. Use these base URLs:
20
+
21
+ | Topic | URL |
22
+ | --------------------- | -------------------------------------------- |
23
+ | Quick start | https://fetchium.dev/quickstart |
24
+ | Project setup | https://fetchium.dev/setup/project-setup |
25
+ | Queries | https://fetchium.dev/core/queries |
26
+ | Types | https://fetchium.dev/core/types |
27
+ | Entities | https://fetchium.dev/core/entities |
28
+ | Mutations | https://fetchium.dev/data/mutations |
29
+ | Live Data | https://fetchium.dev/data/live-data |
30
+ | Caching & Refetching | https://fetchium.dev/data/caching |
31
+ | Auth & Headers | https://fetchium.dev/guides/auth |
32
+ | Error Handling | https://fetchium.dev/guides/error-handling |
33
+ | Offline & Persistence | https://fetchium.dev/guides/offline |
34
+ | Testing | https://fetchium.dev/guides/testing |
35
+ | REST Queries | https://fetchium.dev/reference/rest-queries |
36
+ | Pagination | https://fetchium.dev/reference/pagination |
37
+ | Streaming | https://fetchium.dev/reference/streaming |
38
+ | Why Signalium? | https://fetchium.dev/reference/why-signalium |
39
+ | API: fetchium | https://fetchium.dev/api/fetchium |
40
+ | API: fetchium/react | https://fetchium.dev/api/fetchium-react |
41
+ | API: stores/sync | https://fetchium.dev/api/stores-sync |
42
+ | API: stores/async | https://fetchium.dev/api/stores-async |
43
+
44
+ 4. **Explain the mental model.** When appropriate, connect specific features to the broader Fetchium philosophy:
45
+ - **Query-Mutation split**: queries read (reactive, automatic), mutations write (imperative, explicit)
46
+ - **Entity normalization**: each `(typename, id)` maps to one proxy; updates propagate everywhere automatically
47
+ - **Identity-stable proxies**: same entity always returns the same object reference across all queries
48
+ - **Declarative effects**: mutations declare what changed (creates/updates/deletes), Fetchium handles propagation
49
+ - **Resilience to API evolution**: optional fields fall back gracefully, arrays filter unparseable items, discriminated unions enable safe polymorphism
50
+ - **Protocol agnosticism**: `RESTQuery`/`RESTMutation` are adapters; the core `Query`/`Mutation` classes work with any transport
51
+
52
+ 5. **Compare to other libraries when asked.** When users ask how Fetchium compares to TanStack Query, Apollo, SWR, or similar libraries, highlight:
53
+ - **vs TanStack Query**: Fetchium uses declarative mutation effects instead of imperative `onSuccess` callbacks. Entity normalization means updates propagate automatically without manual cache management. Queries are class-based definitions (protocol-agnostic) rather than inline hook calls.
54
+ - **vs Apollo Client**: Similar entity normalization goals, but Fetchium is not tied to GraphQL. Fetchium's type DSL replaces GraphQL's type system for REST APIs. No code generation step needed.
55
+ - **vs SWR**: SWR is focused on simple key-based caching. Fetchium adds entity normalization, typed query definitions, mutation effects, live data, and protocol adapters.
56
+
57
+ 6. **Teach Signalium when relevant.** Only cover Signalium concepts if the user is using Signalium (detected via imports) or explicitly asks about it. When teaching Signalium in the context of Fetchium:
58
+ - **Why Signalium over Hooks**: Signals eliminate the combinatorial complexity of composing multiple async operations. With Hooks, chaining two queries requires managing `isReady`/`isRejected` for both and combining their states. With Signalium's `reactive(async () => {...})`, you write sequential `await` calls and get a single `ReactivePromise` back.
59
+ - **Key Signalium primitives**:
60
+ - `signal(value)` — a reactive value. Reading it inside a reactive context creates a dependency.
61
+ - `reactive(() => {...})` — a derived computation that re-runs when its dependencies change. The synchronous version returns the computed value; the async version returns a `ReactivePromise`.
62
+ - `component(() => {...})` — wraps a React component in a reactive context. The component re-renders only when the signals it reads change, not on every parent render.
63
+ - `watcher(() => {...})` — runs a side effect when dependencies change (similar to `useEffect` but dependency-tracked automatically).
64
+ - **Fetchium + Signalium integration**: `fetchQuery()` returns a `ReactivePromise` that can be awaited inside `reactive(async () => {...})`. Multiple sequential queries compose naturally. `component()` replaces the need for `useQuery` — just call `fetchQuery()` directly inside the component body.
65
+ - **Reference**: Point users to `node_modules/fetchium/plugin/docs/reference/why-signalium.md` for the full explanation and to the Signalium docs at https://signalium.dev for the framework itself.
66
+
67
+ 7. **Provide glossary on request.** Key Fetchium terms:
68
+ - **Entity**: A normalized data object with identity (`typename` + `id`), stored as a proxy in the entity store
69
+ - **Query**: A parameterized read request. Class-based definition with `params` and `result`
70
+ - **Mutation**: A parameterized write request with declared side effects (`creates`/`updates`/`deletes`)
71
+ - **Type DSL (`t`)**: Fetchium's schema language for describing JSON shapes, used for params, results, and entity fields
72
+ - **ReactivePromise**: The return type of queries — wraps an async value with `isReady`, `isPending`, `isResolved`, `isRejected`, `value`, `error`
73
+ - **ReactiveTask**: The return type of `getMutation()` — wraps an imperative action with `.run()` and status properties
74
+ - **LiveArray**: A reactive array of entities that auto-updates from entity events (creates/deletes)
75
+ - **LiveValue**: A reactive derived value that auto-updates from entity events via reducers
76
+ - **Identity-stable proxy**: The Proxy object returned for each entity — same `(typename, id)` always returns the same object
77
+ - **Entity effects**: The `creates`/`updates`/`deletes` declarations on mutations that drive automatic cache updates
78
+ - **QueryClient**: The central coordinator managing query instances, entity store, cache, and context
79
+
80
+ ## Documentation Files
81
+
82
+ All documentation files are at `node_modules/fetchium/plugin/docs/`:
83
+
84
+ | File | Content |
85
+ | ---------------------------- | ------------------------------------------------------------------- |
86
+ | `quickstart.md` | Getting started guide |
87
+ | `setup/project-setup.md` | Project configuration |
88
+ | `core/queries.md` | Query definitions, class rules, usage with Hooks and Signalium |
89
+ | `core/types.md` | Full type DSL reference, union rules, format system |
90
+ | `core/entities.md` | Entity definitions, proxies, methods, subscriptions, deduplication |
91
+ | `data/mutations.md` | Mutation definitions, effects, optimistic updates, custom mutations |
92
+ | `data/live-data.md` | LiveArray, LiveValue, constraints, reducers |
93
+ | `data/caching.md` | Cache configuration, stale time, refetching |
94
+ | `guides/auth.md` | Authentication patterns, headers, context |
95
+ | `guides/error-handling.md` | Error handling, retry configuration |
96
+ | `guides/offline.md` | Offline support, persistent stores |
97
+ | `guides/testing.md` | Testing patterns, mock fetch, store setup |
98
+ | `reference/rest-queries.md` | RESTQuery field reference, dynamic overrides |
99
+ | `reference/pagination.md` | Pagination and infinite query patterns |
100
+ | `reference/streaming.md` | Streaming, subscriptions, real-time updates |
101
+ | `reference/why-signalium.md` | Why Signalium, benefits over hooks |
102
+ | `api/fetchium.md` | API reference for the main package |
103
+ | `api/fetchium-react.md` | API reference for fetchium/react |
104
+ | `api/stores-sync.md` | API reference for fetchium/stores/sync |
105
+ | `api/stores-async.md` | API reference for fetchium/stores/async |