@signalium/query 1.0.2 → 1.0.3

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @signalium/query
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - cfe249d: Export QueryClientContext
8
+
3
9
  ## 1.0.2
4
10
 
5
11
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,315 @@
1
+ # @signalium/query
2
+
3
+ A reactive query client built on [Signalium](https://signalium.dev) that provides powerful data fetching, caching, and entity management with automatic reactivity.
4
+
5
+ ## IMPORTANT NOTE: This package is still in development and the API is subject to change.
6
+
7
+ v1.0.0 was published prematurely and we are not treating it as the stable v1 release from a semver perspective. APIs are not expected to change dramatically, but breaking changes may occur, and v1.1.0 will be the first stable release.
8
+
9
+ ## Features
10
+
11
+ - **Entity-Based Caching**: Global entity map with automatic deduplication across queries
12
+ - **Signalium Reactivity**: Automatic reactive updates when entities change
13
+ - **REST Query API**: Type-safe REST queries with path and search parameter interpolation
14
+ - **Infinite Queries**: Built-in support for paginated data fetching
15
+ - **Stream Queries**: Real-time updates via subscriptions
16
+ - **Smart Refetching**: Configurable stale-time, refetch intervals, and network-aware fetching
17
+ - **Request Deduplication**: Automatic deduplication of in-flight requests
18
+ - **Offline Support**: Response caching with configurable garbage collection
19
+ - **TypeScript First**: Full type inference for queries and responses
20
+ - **Framework Agnostic**: Works with React, or use standalone
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @signalium/query signalium
26
+ ```
27
+
28
+ For React support:
29
+
30
+ ```bash
31
+ npm install @signalium/query signalium react
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### Basic Query
37
+
38
+ ```typescript
39
+ import { QueryClient, SyncQueryStore, MemoryPersistentStore, query, t } from '@signalium/query';
40
+
41
+ // Create a query client
42
+ const store = new SyncQueryStore(new MemoryPersistentStore());
43
+ const client = new QueryClient(store, {
44
+ fetch: globalThis.fetch,
45
+ });
46
+
47
+ // Define a query
48
+ const getUser = query(() => ({
49
+ path: '/users/[id]',
50
+ response: {
51
+ id: t.number,
52
+ name: t.string,
53
+ email: t.string,
54
+ },
55
+ }));
56
+
57
+ // Use the query with standard async/await syntax
58
+ const user = await getUser({ id: '123' });
59
+ console.log(user.name); // Fully typed!
60
+ ```
61
+
62
+ ### Entity Queries
63
+
64
+ Define entities to enable automatic caching and deduplication:
65
+
66
+ ```typescript
67
+ import { entity, t } from '@signalium/query';
68
+
69
+ // Define an entity
70
+ const User = entity('User', () => ({
71
+ id: t.number,
72
+ name: t.string,
73
+ email: t.string,
74
+ }));
75
+
76
+ // Use in a query
77
+ const getUser = query(() => ({
78
+ path: '/users/[id]',
79
+ response: User,
80
+ }));
81
+
82
+ // Multiple queries returning the same entity will share cached data
83
+ const user1 = await getUser({ id: '123' });
84
+ const user2 = await getUserFromTeam({ teamId: '456' }); // May return same User entity
85
+
86
+ // Both references will update reactively when the entity changes!
87
+ ```
88
+
89
+ ### Search Parameters
90
+
91
+ ```typescript
92
+ const listUsers = query(() => ({
93
+ path: '/users',
94
+ searchParams: {
95
+ page: t.number,
96
+ limit: t.number,
97
+ status: t.string.optional,
98
+ },
99
+ response: {
100
+ users: t.array(User),
101
+ total: t.number,
102
+ },
103
+ }));
104
+
105
+ // Use with search params
106
+ const result = await listUsers({
107
+ page: 1,
108
+ limit: 10,
109
+ status: 'active',
110
+ });
111
+ ```
112
+
113
+ ### Infinite Queries
114
+
115
+ ```typescript
116
+ import { infiniteQuery } from '@signalium/query';
117
+
118
+ const listPosts = infiniteQuery(() => ({
119
+ path: '/posts',
120
+ searchParams: {
121
+ cursor: t.string.optional,
122
+ limit: t.number,
123
+ },
124
+ response: {
125
+ posts: t.array(Post),
126
+ nextCursor: t.string.optional,
127
+ },
128
+ getNextPageParams: lastPage => ({ cursor: lastPage.nextCursor }),
129
+ }));
130
+
131
+ const postsResult = listPosts({ limit: 20 });
132
+ const pages = await postsResult;
133
+
134
+ // Load more pages
135
+ if (postsResult.hasNextPage) {
136
+ await postsResult.fetchNextPage();
137
+ }
138
+ ```
139
+
140
+ ### Stream Queries
141
+
142
+ For real-time updates:
143
+
144
+ ```typescript
145
+ import { streamQuery } from '@signalium/query';
146
+
147
+ const subscribeToUser = streamQuery(() => ({
148
+ path: '/users/[id]',
149
+ response: User, // Must be an entity
150
+ subscribeFn: (context, params, onUpdate) => {
151
+ const ws = new WebSocket(`wss://api.example.com/users/${params.id}`);
152
+
153
+ ws.onmessage = event => {
154
+ const update = JSON.parse(event.data);
155
+ onUpdate(update);
156
+ };
157
+
158
+ // Return unsubscribe function
159
+ return () => ws.close();
160
+ },
161
+ }));
162
+
163
+ // Subscribe to updates
164
+ const userStream = subscribeToUser({ id: '123' });
165
+ const user = await userStream;
166
+ // User will reactively update when new data arrives
167
+ ```
168
+
169
+ ## Caching Options
170
+
171
+ Configure caching behavior per query:
172
+
173
+ ```typescript
174
+ const getUser = query(() => ({
175
+ path: '/users/[id]',
176
+ response: User,
177
+ cache: {
178
+ staleTime: 5000, // Data is fresh for 5 seconds
179
+ gcTime: 300000, // Cache persists for 5 minutes after last use
180
+ refetchInterval: 10000, // Refetch every 10 seconds when in use
181
+ networkMode: 'online', // Only fetch when online
182
+ retry: 3, // Retry failed requests 3 times
183
+ refreshStaleOnReconnect: true, // Refetch stale data on reconnect
184
+ },
185
+ }));
186
+ ```
187
+
188
+ ## React Integration
189
+
190
+ ```typescript
191
+ import { QueryClientContext } from '@signalium/query';
192
+ import { reactive } from 'signalium/react';
193
+
194
+ // Provide the client
195
+ function App() {
196
+ return (
197
+ <QueryClientContext.Provider value={client}>
198
+ <UserProfile userId="123" />
199
+ </QueryClientContext.Provider>
200
+ );
201
+ }
202
+
203
+ // Use queries in components
204
+ const UserProfile = reactive(({ userId }) => {
205
+ const user = getUser({ id: userId });
206
+
207
+ // While the user is loading initially, show a loading state
208
+ if (!user.isReady) {
209
+ return <div>Loading...</div>;
210
+ }
211
+
212
+ // Once the user is loaded and the result is ready, `user.value` is guaranteed
213
+ // to be defined and have a loaded user value.
214
+ return (
215
+ <div>
216
+ <h1>{user.value.name}</h1>
217
+ <p>{user.value.email}</p>
218
+ </div>
219
+ );
220
+ });
221
+ ```
222
+
223
+ ## Store Types
224
+
225
+ ### Synchronous Store
226
+
227
+ For in-memory only or synchronous persistence:
228
+
229
+ ```typescript
230
+ import { SyncQueryStore, MemoryPersistentStore } from '@signalium/query';
231
+
232
+ const store = new SyncQueryStore(new MemoryPersistentStore());
233
+ ```
234
+
235
+ ### Asynchronous Store
236
+
237
+ For async persistence (IndexedDB, AsyncStorage, etc.):
238
+
239
+ ```typescript
240
+ import { AsyncQueryStore } from '@signalium/query/stores/async';
241
+
242
+ const store = new AsyncQueryStore({
243
+ async get(key) {
244
+ /* ... */
245
+ },
246
+ async set(key, value) {
247
+ /* ... */
248
+ },
249
+ async delete(key) {
250
+ /* ... */
251
+ },
252
+ async clear() {
253
+ /* ... */
254
+ },
255
+ });
256
+ ```
257
+
258
+ ## Type Definitions
259
+
260
+ The `t` object provides type-safe validators:
261
+
262
+ - **Primitives**: `t.string`, `t.number`, `t.boolean`, `t.null`, `t.undefined`
263
+ - **Collections**: `t.array(type)`, `t.record(type)`, `t.object({ ... })`
264
+ - **Unions**: `t.union(t.string, t.number, t.null)`
265
+ - **Entities**: `entity(() => ({ ... }))`
266
+
267
+ ## Network Management
268
+
269
+ Control network status and behavior:
270
+
271
+ ```typescript
272
+ import { NetworkManager } from '@signalium/query';
273
+
274
+ const networkManager = new NetworkManager();
275
+
276
+ // Manually control network status
277
+ networkManager.setOnline(false);
278
+
279
+ // Listen to browser events (default)
280
+ networkManager.listen();
281
+
282
+ // Use in QueryClient
283
+ const client = new QueryClient(store, {
284
+ fetch,
285
+ networkManager,
286
+ });
287
+ ```
288
+
289
+ ## API Reference
290
+
291
+ ### Core Exports
292
+
293
+ - `QueryClient` - Main query client class
294
+ - `QueryClientContext` - React context for client
295
+ - `query()` - Define a standard query
296
+ - `infiniteQuery()` - Define a paginated query
297
+ - `streamQuery()` - Define a streaming query
298
+ - `t` - Type definition helpers
299
+ - `entity()` - Define an entity type
300
+ - `SyncQueryStore` / `AsyncQueryStore` - Store implementations
301
+ - `NetworkManager` - Network status manager
302
+
303
+ ### Type Utilities
304
+
305
+ - `QueryResult<T>` - Result type for queries
306
+ - `QueryContext` - Context passed to fetch functions
307
+ - `QueryCacheOptions` - Cache configuration options
308
+
309
+ ## License
310
+
311
+ ISC
312
+
313
+ ## Contributing
314
+
315
+ See the main [Signalium repository](https://github.com/Signalium/signalium) for contributing guidelines.
package/dist/cjs/index.js CHANGED
@@ -14,10 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.NetworkManagerContext = exports.defaultNetworkManager = exports.NetworkManager = exports.streamQuery = exports.infiniteQuery = exports.query = exports.registerFormat = exports.entity = exports.t = exports.queueKeyFor = exports.updatedAtKeyFor = exports.refIdsKeyFor = exports.refCountKeyFor = exports.valueKeyFor = exports.AsyncQueryStore = exports.MemoryPersistentStore = exports.SyncQueryStore = exports.QueryResult = exports.QueryClient = void 0;
17
+ exports.NetworkManagerContext = exports.defaultNetworkManager = exports.NetworkManager = exports.streamQuery = exports.infiniteQuery = exports.query = exports.registerFormat = exports.entity = exports.t = exports.queueKeyFor = exports.updatedAtKeyFor = exports.refIdsKeyFor = exports.refCountKeyFor = exports.valueKeyFor = exports.AsyncQueryStore = exports.MemoryPersistentStore = exports.SyncQueryStore = exports.QueryResult = exports.QueryClientContext = exports.QueryClient = void 0;
18
18
  __exportStar(require("./types.js"), exports);
19
19
  var QueryClient_js_1 = require("./QueryClient.js");
20
20
  Object.defineProperty(exports, "QueryClient", { enumerable: true, get: function () { return QueryClient_js_1.QueryClient; } });
21
+ Object.defineProperty(exports, "QueryClientContext", { enumerable: true, get: function () { return QueryClient_js_1.QueryClientContext; } });
21
22
  Object.defineProperty(exports, "QueryResult", { enumerable: true, get: function () { return QueryClient_js_1.QueryResultImpl; } });
22
23
  var QueryStore_js_1 = require("./QueryStore.js");
23
24
  Object.defineProperty(exports, "SyncQueryStore", { enumerable: true, get: function () { return QueryStore_js_1.SyncQueryStore; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA2B;AAE3B,mDAA+E;AAAtE,6GAAA,WAAW,OAAA;AAAE,6GAAA,eAAe,OAAe;AAGpD,iDASyB;AARvB,+GAAA,cAAc,OAAA;AACd,sHAAA,qBAAqB,OAAA;AACrB,gHAAA,eAAe,OAAA;AACf,4GAAA,WAAW,OAAA;AACX,+GAAA,cAAc,OAAA;AACd,6GAAA,YAAY,OAAA;AACZ,gHAAA,eAAe,OAAA;AACf,4GAAA,WAAW,OAAA;AAEb,6CAA0D;AAAjD,gGAAA,CAAC,OAAA;AAAE,qGAAA,MAAM,OAAA;AAAE,6GAAA,cAAc,OAAA;AAElC,uCAA+D;AAAtD,iGAAA,KAAK,OAAA;AAAE,yGAAA,aAAa,OAAA;AAAE,uGAAA,WAAW,OAAA;AAC1C,yDAAmG;AAA1F,mHAAA,cAAc,OAAA;AAAE,0HAAA,qBAAqB,OAAA;AAAE,0HAAA,qBAAqB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA2B;AAE3B,mDAAmG;AAA1F,6GAAA,WAAW,OAAA;AAAE,oHAAA,kBAAkB,OAAA;AAAE,6GAAA,eAAe,OAAe;AAGxE,iDASyB;AARvB,+GAAA,cAAc,OAAA;AACd,sHAAA,qBAAqB,OAAA;AACrB,gHAAA,eAAe,OAAA;AACf,4GAAA,WAAW,OAAA;AACX,+GAAA,cAAc,OAAA;AACd,6GAAA,YAAY,OAAA;AACZ,gHAAA,eAAe,OAAA;AACf,4GAAA,WAAW,OAAA;AAEb,6CAA0D;AAAjD,gGAAA,CAAC,OAAA;AAAE,qGAAA,MAAM,OAAA;AAAE,6GAAA,cAAc,OAAA;AAElC,uCAA+D;AAAtD,iGAAA,KAAK,OAAA;AAAE,yGAAA,aAAa,OAAA;AAAE,uGAAA,WAAW,OAAA;AAC1C,yDAAmG;AAA1F,mHAAA,cAAc,OAAA;AAAE,0HAAA,qBAAqB,OAAA;AAAE,0HAAA,qBAAqB,OAAA"}
@@ -1,5 +1,5 @@
1
1
  export * from './types.js';
2
- export { QueryClient, QueryResultImpl as QueryResult } from './QueryClient.js';
2
+ export { QueryClient, QueryClientContext, QueryResultImpl as QueryResult } from './QueryClient.js';
3
3
  export type { QueryContext } from './QueryClient.js';
4
4
  export type { QueryStore, CachedQuery } from './QueryStore.js';
5
5
  export { SyncQueryStore, MemoryPersistentStore, AsyncQueryStore, valueKeyFor, refCountKeyFor, refIdsKeyFor, updatedAtKeyFor, queueKeyFor, } from './QueryStore.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,WAAW,EAAE,eAAe,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/E,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACtH,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACnG,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACtH,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './types.js';
2
- export { QueryClient, QueryResultImpl as QueryResult } from './QueryClient.js';
2
+ export { QueryClient, QueryClientContext, QueryResultImpl as QueryResult } from './QueryClient.js';
3
3
  export { SyncQueryStore, MemoryPersistentStore, AsyncQueryStore, valueKeyFor, refCountKeyFor, refIdsKeyFor, updatedAtKeyFor, queueKeyFor, } from './QueryStore.js';
4
4
  export { t, entity, registerFormat } from './typeDefs.js';
5
5
  export { query, infiniteQuery, streamQuery } from './query.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,WAAW,EAAE,eAAe,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/E,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE1D,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGnG,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE1D,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalium/query",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",