query-optimistic 0.3.4 → 0.4.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.
@@ -1,5 +1,6 @@
1
1
  import { I as IdGetter, C as CollectionDef, E as EntityDef, M as MutationDef, O as Optimistic } from '../types-BOq5W_Qm.mjs';
2
2
  export { A as AnyDef, a as OptimisticAction, b as OptimisticInstruction, c as OptimisticStatus, P as PaginatedOptions, Q as QueryOptions } from '../types-BOq5W_Qm.mjs';
3
+ import { QueryClient } from '@tanstack/react-query';
3
4
 
4
5
  /**
5
6
  * Define a collection query for fetching arrays of items
@@ -86,19 +87,32 @@ type RegisteredEntry = RegisteredCollection | RegisteredEntity | RegisteredPagin
86
87
  */
87
88
  declare class QueryRegistry {
88
89
  private entries;
90
+ private queryClient;
91
+ private collectionDefs;
92
+ /** Set the query client for direct cache access */
93
+ setQueryClient(client: QueryClient): void;
94
+ /** Register a collection definition for direct cache updates */
95
+ registerDef(def: CollectionDef<any, any>): void;
89
96
  /** Register an active query */
90
97
  register(entry: RegisteredEntry): void;
91
98
  /** Unregister a query when component unmounts */
92
99
  unregister(entry: RegisteredEntry): void;
93
100
  /** Get all registered entries for a query name */
94
101
  getByName(name: string): RegisteredEntry[];
102
+ /**
103
+ * Check if params partially match the given scope object.
104
+ * Returns true if all key-value pairs in scope exist in params.
105
+ */
106
+ private matchesScope;
95
107
  /** Apply an optimistic update to all queries with given name */
96
108
  applyUpdate<T>(name: string, action: 'prepend' | 'append' | 'update' | 'delete' | 'replace', payload: {
97
109
  data?: Partial<Optimistic<T>>;
98
110
  id?: string;
99
111
  where?: (item: T) => boolean;
100
112
  update?: (item: T) => T;
101
- }): (() => void)[];
113
+ }, scope?: Record<string, unknown>): (() => void)[];
114
+ /** Apply update directly to query cache (used when scope is provided) */
115
+ private applyDirectCacheUpdate;
102
116
  private applyCollectionUpdate;
103
117
  }
104
118
  /** Singleton registry instance */
@@ -115,11 +129,23 @@ interface OptimisticTransaction {
115
129
  sync?: boolean;
116
130
  rollback: () => void;
117
131
  }
132
+ /** Options for channel operations */
133
+ interface ChannelOptions {
134
+ /**
135
+ * Only apply updates to queries whose params partially match this object.
136
+ * All key-value pairs in scope must exist in the query's params.
137
+ * @example
138
+ * channel(ordersCollection, { scope: { chain: 'solana' } }).delete(id)
139
+ * // Only affects queries with params containing chain: 'solana'
140
+ */
141
+ scope?: Record<string, unknown>;
142
+ }
118
143
  /** Channel for a collection - provides typed optimistic mutation methods */
119
144
  declare class CollectionChannel<TEntity> {
120
145
  private readonly target;
146
+ private readonly options?;
121
147
  private readonly optimisticId;
122
- constructor(target: CollectionDef<TEntity, any>);
148
+ constructor(target: CollectionDef<TEntity, any>, options?: ChannelOptions | undefined);
123
149
  /**
124
150
  * Prepend an item to the collection
125
151
  * @returns Rollback function to undo the change
@@ -189,9 +215,14 @@ declare class EntityChannel<TEntity> {
189
215
  * @example
190
216
  * // Update an entity
191
217
  * channel(userEntity).update(user => ({ ...user, name: 'Jane' }));
218
+ *
219
+ * @example
220
+ * // Scoped update
221
+ * channel(ordersCollection, { scope: { chain: 'solana' } }).delete(id);
222
+ * // Only affects queries with params containing chain: 'solana'
192
223
  */
193
224
  interface Channel {
194
- <TEntity>(target: CollectionDef<TEntity, any>): CollectionChannel<TEntity>;
225
+ <TEntity>(target: CollectionDef<TEntity, any>, options?: ChannelOptions): CollectionChannel<TEntity>;
195
226
  <TEntity>(target: EntityDef<TEntity, any>): EntityChannel<TEntity>;
196
227
  }
197
228
  /**
@@ -205,7 +236,11 @@ interface Channel {
205
236
  * } catch (error) {
206
237
  * rollback(); // Undo the optimistic update
207
238
  * }
239
+ *
240
+ * @example
241
+ * // Scoped update - only affects queries with matching params
242
+ * channel(ordersCollection, { scope: { chain: 'solana', status: 'pending' } }).delete(id);
208
243
  */
209
244
  declare const channel: Channel;
210
245
 
211
- export { type Channel, CollectionChannel, CollectionDef, EntityChannel, EntityDef, IdGetter, MutationDef, Optimistic, type OptimisticTransaction, type RegisteredCollection, type RegisteredEntity, type RegisteredEntry, type RegisteredPaginatedCollection, channel, defineCollection, defineEntity, defineMutation, registry };
246
+ export { type Channel, type ChannelOptions, CollectionChannel, CollectionDef, EntityChannel, EntityDef, IdGetter, MutationDef, Optimistic, type OptimisticTransaction, type RegisteredCollection, type RegisteredEntity, type RegisteredEntry, type RegisteredPaginatedCollection, channel, defineCollection, defineEntity, defineMutation, registry };
@@ -1,5 +1,6 @@
1
1
  import { I as IdGetter, C as CollectionDef, E as EntityDef, M as MutationDef, O as Optimistic } from '../types-BOq5W_Qm.js';
2
2
  export { A as AnyDef, a as OptimisticAction, b as OptimisticInstruction, c as OptimisticStatus, P as PaginatedOptions, Q as QueryOptions } from '../types-BOq5W_Qm.js';
3
+ import { QueryClient } from '@tanstack/react-query';
3
4
 
4
5
  /**
5
6
  * Define a collection query for fetching arrays of items
@@ -86,19 +87,32 @@ type RegisteredEntry = RegisteredCollection | RegisteredEntity | RegisteredPagin
86
87
  */
87
88
  declare class QueryRegistry {
88
89
  private entries;
90
+ private queryClient;
91
+ private collectionDefs;
92
+ /** Set the query client for direct cache access */
93
+ setQueryClient(client: QueryClient): void;
94
+ /** Register a collection definition for direct cache updates */
95
+ registerDef(def: CollectionDef<any, any>): void;
89
96
  /** Register an active query */
90
97
  register(entry: RegisteredEntry): void;
91
98
  /** Unregister a query when component unmounts */
92
99
  unregister(entry: RegisteredEntry): void;
93
100
  /** Get all registered entries for a query name */
94
101
  getByName(name: string): RegisteredEntry[];
102
+ /**
103
+ * Check if params partially match the given scope object.
104
+ * Returns true if all key-value pairs in scope exist in params.
105
+ */
106
+ private matchesScope;
95
107
  /** Apply an optimistic update to all queries with given name */
96
108
  applyUpdate<T>(name: string, action: 'prepend' | 'append' | 'update' | 'delete' | 'replace', payload: {
97
109
  data?: Partial<Optimistic<T>>;
98
110
  id?: string;
99
111
  where?: (item: T) => boolean;
100
112
  update?: (item: T) => T;
101
- }): (() => void)[];
113
+ }, scope?: Record<string, unknown>): (() => void)[];
114
+ /** Apply update directly to query cache (used when scope is provided) */
115
+ private applyDirectCacheUpdate;
102
116
  private applyCollectionUpdate;
103
117
  }
104
118
  /** Singleton registry instance */
@@ -115,11 +129,23 @@ interface OptimisticTransaction {
115
129
  sync?: boolean;
116
130
  rollback: () => void;
117
131
  }
132
+ /** Options for channel operations */
133
+ interface ChannelOptions {
134
+ /**
135
+ * Only apply updates to queries whose params partially match this object.
136
+ * All key-value pairs in scope must exist in the query's params.
137
+ * @example
138
+ * channel(ordersCollection, { scope: { chain: 'solana' } }).delete(id)
139
+ * // Only affects queries with params containing chain: 'solana'
140
+ */
141
+ scope?: Record<string, unknown>;
142
+ }
118
143
  /** Channel for a collection - provides typed optimistic mutation methods */
119
144
  declare class CollectionChannel<TEntity> {
120
145
  private readonly target;
146
+ private readonly options?;
121
147
  private readonly optimisticId;
122
- constructor(target: CollectionDef<TEntity, any>);
148
+ constructor(target: CollectionDef<TEntity, any>, options?: ChannelOptions | undefined);
123
149
  /**
124
150
  * Prepend an item to the collection
125
151
  * @returns Rollback function to undo the change
@@ -189,9 +215,14 @@ declare class EntityChannel<TEntity> {
189
215
  * @example
190
216
  * // Update an entity
191
217
  * channel(userEntity).update(user => ({ ...user, name: 'Jane' }));
218
+ *
219
+ * @example
220
+ * // Scoped update
221
+ * channel(ordersCollection, { scope: { chain: 'solana' } }).delete(id);
222
+ * // Only affects queries with params containing chain: 'solana'
192
223
  */
193
224
  interface Channel {
194
- <TEntity>(target: CollectionDef<TEntity, any>): CollectionChannel<TEntity>;
225
+ <TEntity>(target: CollectionDef<TEntity, any>, options?: ChannelOptions): CollectionChannel<TEntity>;
195
226
  <TEntity>(target: EntityDef<TEntity, any>): EntityChannel<TEntity>;
196
227
  }
197
228
  /**
@@ -205,7 +236,11 @@ interface Channel {
205
236
  * } catch (error) {
206
237
  * rollback(); // Undo the optimistic update
207
238
  * }
239
+ *
240
+ * @example
241
+ * // Scoped update - only affects queries with matching params
242
+ * channel(ordersCollection, { scope: { chain: 'solana', status: 'pending' } }).delete(id);
208
243
  */
209
244
  declare const channel: Channel;
210
245
 
211
- export { type Channel, CollectionChannel, CollectionDef, EntityChannel, EntityDef, IdGetter, MutationDef, Optimistic, type OptimisticTransaction, type RegisteredCollection, type RegisteredEntity, type RegisteredEntry, type RegisteredPaginatedCollection, channel, defineCollection, defineEntity, defineMutation, registry };
246
+ export { type Channel, type ChannelOptions, CollectionChannel, CollectionDef, EntityChannel, EntityDef, IdGetter, MutationDef, Optimistic, type OptimisticTransaction, type RegisteredCollection, type RegisteredEntity, type RegisteredEntry, type RegisteredPaginatedCollection, channel, defineCollection, defineEntity, defineMutation, registry };
@@ -30,6 +30,16 @@ function defineMutation(config) {
30
30
  var QueryRegistry = class {
31
31
  constructor() {
32
32
  this.entries = /* @__PURE__ */ new Map();
33
+ this.queryClient = null;
34
+ this.collectionDefs = /* @__PURE__ */ new Map();
35
+ }
36
+ /** Set the query client for direct cache access */
37
+ setQueryClient(client) {
38
+ this.queryClient = client;
39
+ }
40
+ /** Register a collection definition for direct cache updates */
41
+ registerDef(def) {
42
+ this.collectionDefs.set(def.name, def);
33
43
  }
34
44
  /** Register an active query */
35
45
  register(entry) {
@@ -37,6 +47,9 @@ var QueryRegistry = class {
37
47
  this.entries.set(entry.name, /* @__PURE__ */ new Set());
38
48
  }
39
49
  this.entries.get(entry.name).add(entry);
50
+ if (entry.kind === "collection" || entry.kind === "paginated") {
51
+ this.collectionDefs.set(entry.name, entry.def);
52
+ }
40
53
  }
41
54
  /** Unregister a query when component unmounts */
42
55
  unregister(entry) {
@@ -52,8 +65,20 @@ var QueryRegistry = class {
52
65
  getByName(name) {
53
66
  return Array.from(this.entries.get(name) ?? []);
54
67
  }
68
+ /**
69
+ * Check if params partially match the given scope object.
70
+ * Returns true if all key-value pairs in scope exist in params.
71
+ */
72
+ matchesScope(params, scope) {
73
+ if (!scope) return true;
74
+ if (!params) return false;
75
+ return Object.entries(scope).every(([key, value]) => params[key] === value);
76
+ }
55
77
  /** Apply an optimistic update to all queries with given name */
56
- applyUpdate(name, action, payload) {
78
+ applyUpdate(name, action, payload, scope) {
79
+ if (scope && this.queryClient) {
80
+ return this.applyDirectCacheUpdate(name, action, payload, scope);
81
+ }
57
82
  const entries = this.getByName(name);
58
83
  const rollbacks = [];
59
84
  const seenKeys = /* @__PURE__ */ new Set();
@@ -61,7 +86,8 @@ var QueryRegistry = class {
61
86
  const key = JSON.stringify(entry.queryKey);
62
87
  if (seenKeys.has(key)) return false;
63
88
  seenKeys.add(key);
64
- return true;
89
+ const params = entry.queryKey[1];
90
+ return this.matchesScope(params, scope);
65
91
  });
66
92
  for (const entry of uniqueEntries) {
67
93
  if (entry.kind === "collection") {
@@ -98,6 +124,45 @@ var QueryRegistry = class {
98
124
  }
99
125
  return rollbacks;
100
126
  }
127
+ /** Apply update directly to query cache (used when scope is provided) */
128
+ applyDirectCacheUpdate(name, action, payload, scope) {
129
+ if (!this.queryClient) return [];
130
+ const def = this.collectionDefs.get(name);
131
+ if (!def) return [];
132
+ const rollbacks = [];
133
+ const queries = this.queryClient.getQueriesData({
134
+ queryKey: [name]
135
+ });
136
+ for (const [queryKey, data] of queries) {
137
+ if (!data) continue;
138
+ const params = queryKey[1];
139
+ if (!this.matchesScope(params, scope)) continue;
140
+ const isPaginated = data && typeof data === "object" && "pages" in data;
141
+ if (isPaginated) {
142
+ const paginatedData = data;
143
+ const previous = paginatedData;
144
+ rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
145
+ this.queryClient.setQueryData(queryKey, (prev) => {
146
+ if (!prev) return prev;
147
+ return {
148
+ ...prev,
149
+ pages: prev.pages.map(
150
+ (page, i) => i === 0 ? this.applyCollectionUpdate(page, action, payload, def.id) : page
151
+ )
152
+ };
153
+ });
154
+ } else {
155
+ const arrayData = data;
156
+ const previous = arrayData;
157
+ rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
158
+ this.queryClient.setQueryData(queryKey, (prev) => {
159
+ if (!prev) return prev;
160
+ return this.applyCollectionUpdate(prev, action, payload, def.id);
161
+ });
162
+ }
163
+ }
164
+ return rollbacks;
165
+ }
101
166
  applyCollectionUpdate(items, action, payload, getId) {
102
167
  switch (action) {
103
168
  case "prepend":
@@ -133,8 +198,9 @@ var QueryRegistry = class {
133
198
  };
134
199
  var registry = new QueryRegistry();
135
200
  var CollectionChannel = class {
136
- constructor(target) {
201
+ constructor(target, options) {
137
202
  this.target = target;
203
+ this.options = options;
138
204
  this.optimisticId = nanoid.nanoid();
139
205
  }
140
206
  /**
@@ -148,7 +214,7 @@ var CollectionChannel = class {
148
214
  };
149
215
  const rollbacks = registry.applyUpdate(this.target.name, "prepend", {
150
216
  data: optimisticData
151
- });
217
+ }, this.options?.scope);
152
218
  return () => rollbacks.forEach((rb) => rb());
153
219
  }
154
220
  /**
@@ -162,7 +228,7 @@ var CollectionChannel = class {
162
228
  };
163
229
  const rollbacks = registry.applyUpdate(this.target.name, "append", {
164
230
  data: optimisticData
165
- });
231
+ }, this.options?.scope);
166
232
  return () => rollbacks.forEach((rb) => rb());
167
233
  }
168
234
  /**
@@ -173,7 +239,7 @@ var CollectionChannel = class {
173
239
  const rollbacks = registry.applyUpdate(this.target.name, "update", {
174
240
  id,
175
241
  update: updateFn
176
- });
242
+ }, this.options?.scope);
177
243
  return () => rollbacks.forEach((rb) => rb());
178
244
  }
179
245
  /**
@@ -184,7 +250,7 @@ var CollectionChannel = class {
184
250
  const rollbacks = registry.applyUpdate(this.target.name, "update", {
185
251
  where,
186
252
  update: updateFn
187
- });
253
+ }, this.options?.scope);
188
254
  return () => rollbacks.forEach((rb) => rb());
189
255
  }
190
256
  /**
@@ -194,7 +260,7 @@ var CollectionChannel = class {
194
260
  delete(id) {
195
261
  const rollbacks = registry.applyUpdate(this.target.name, "delete", {
196
262
  id
197
- });
263
+ }, this.options?.scope);
198
264
  return () => rollbacks.forEach((rb) => rb());
199
265
  }
200
266
  /**
@@ -204,7 +270,7 @@ var CollectionChannel = class {
204
270
  deleteWhere(where) {
205
271
  const rollbacks = registry.applyUpdate(this.target.name, "delete", {
206
272
  where
207
- });
273
+ }, this.options?.scope);
208
274
  return () => rollbacks.forEach((rb) => rb());
209
275
  }
210
276
  };
@@ -233,9 +299,9 @@ var EntityChannel = class {
233
299
  return () => rollbacks.forEach((rb) => rb());
234
300
  }
235
301
  };
236
- var channel = ((target) => {
302
+ var channel = ((target, options) => {
237
303
  if (target._type === "collection") {
238
- return new CollectionChannel(target);
304
+ return new CollectionChannel(target, options);
239
305
  } else {
240
306
  return new EntityChannel(target);
241
307
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/define.ts","../../src/core/registry.ts","../../src/core/channel.ts"],"names":["nanoid"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n CollectionDef,\n EntityDef,\n MutationDef,\n IdGetter,\n} from './types';\n\n/**\n * Define a collection query for fetching arrays of items\n *\n * @example\n * const postsQuery = defineCollection({\n * name: 'posts',\n * id: (post) => post._id,\n * fetch: ({ page }) => api.get(`/posts?page=${page}`).json()\n * })\n */\nexport function defineCollection<TData, TParams = void>(config: {\n name: string;\n id: IdGetter<TData>;\n fetch: (params: TParams) => Promise<TData[]>;\n}): CollectionDef<TData, TParams> {\n return {\n _type: 'collection',\n name: config.name,\n id: config.id,\n fetch: config.fetch,\n };\n}\n\n/**\n * Define an entity for fetching single items\n *\n * @example\n * const userEntity = defineEntity({\n * name: 'user',\n * fetch: (userId) => api.get(`/users/${userId}`).json()\n * })\n */\nexport function defineEntity<TData, TParams = void>(config: {\n name: string;\n fetch: (params: TParams) => Promise<TData>;\n}): EntityDef<TData, TParams> {\n return {\n _type: 'entity',\n name: config.name,\n fetch: config.fetch,\n };\n}\n\n/**\n * Define a mutation for writing data\n *\n * @example\n * const createPost = defineMutation({\n * name: 'createPost',\n * mutate: (data) => api.post('/posts', { json: data }).json()\n * })\n */\nexport function defineMutation<TParams, TResponse = void>(config: {\n name?: string;\n mutate: (params: TParams) => Promise<TResponse>;\n}): MutationDef<TParams, TResponse> {\n return {\n _type: 'mutation',\n name: config.name,\n mutate: config.mutate,\n };\n}\n","import type { CollectionDef, EntityDef, Optimistic } from './types';\n\n/** Registered collection entry */\nexport interface RegisteredCollection<T = any> {\n kind: 'collection';\n name: string;\n queryKey: readonly unknown[];\n def: CollectionDef<T, any>;\n getData: () => T[] | undefined;\n setData: (updater: (prev: T[] | undefined) => T[] | undefined) => void;\n}\n\n/** Registered entity entry */\nexport interface RegisteredEntity<T = any> {\n kind: 'entity';\n name: string;\n queryKey: readonly unknown[];\n def: EntityDef<T, any>;\n getData: () => T | undefined;\n setData: (updater: (prev: T | undefined) => T | undefined) => void;\n}\n\n/** Registered paginated collection entry */\nexport interface RegisteredPaginatedCollection<T = any> {\n kind: 'paginated';\n name: string;\n queryKey: readonly unknown[];\n def: CollectionDef<T, any>;\n getData: () => { pages: T[][]; pageParams: unknown[] } | undefined;\n setData: (\n updater: (\n prev: { pages: T[][]; pageParams: unknown[] } | undefined\n ) => { pages: T[][]; pageParams: unknown[] } | undefined\n ) => void;\n}\n\nexport type RegisteredEntry =\n | RegisteredCollection\n | RegisteredEntity\n | RegisteredPaginatedCollection;\n\n/**\n * Internal registry for tracking active queries\n * Used by optimistic updates to broadcast changes\n */\nclass QueryRegistry {\n private entries = new Map<string, Set<RegisteredEntry>>();\n\n /** Register an active query */\n register(entry: RegisteredEntry): void {\n if (!this.entries.has(entry.name)) {\n this.entries.set(entry.name, new Set());\n }\n this.entries.get(entry.name)!.add(entry);\n }\n\n /** Unregister a query when component unmounts */\n unregister(entry: RegisteredEntry): void {\n const set = this.entries.get(entry.name);\n if (set) {\n set.delete(entry);\n if (set.size === 0) {\n this.entries.delete(entry.name);\n }\n }\n }\n\n /** Get all registered entries for a query name */\n getByName(name: string): RegisteredEntry[] {\n return Array.from(this.entries.get(name) ?? []);\n }\n\n /** Apply an optimistic update to all queries with given name */\n applyUpdate<T>(\n name: string,\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace',\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n }\n ): (() => void)[] {\n const entries = this.getByName(name);\n const rollbacks: (() => void)[] = [];\n\n // Deduplicate by queryKey to avoid updating the same cache entry multiple times\n const seenKeys = new Set<string>();\n const uniqueEntries = entries.filter((entry) => {\n const key = JSON.stringify(entry.queryKey);\n if (seenKeys.has(key)) return false;\n seenKeys.add(key);\n return true;\n });\n\n for (const entry of uniqueEntries) {\n if (entry.kind === 'collection') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n entry.setData((prev) => {\n if (!prev) return prev;\n return this.applyCollectionUpdate(prev, action, payload, entry.def.id);\n });\n } else if (entry.kind === 'paginated') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n entry.setData((prev) => {\n if (!prev) return prev;\n return {\n ...prev,\n pages: prev.pages.map((page, i) =>\n i === 0\n ? this.applyCollectionUpdate(page, action, payload, entry.def.id)\n : page\n ),\n };\n });\n } else if (entry.kind === 'entity') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n if (action === 'update' && payload.update) {\n entry.setData((prev) => (prev ? payload.update!(prev as T) : prev));\n } else if (action === 'replace' && payload.data) {\n entry.setData(() => payload.data as T);\n }\n }\n }\n\n return rollbacks;\n }\n\n private applyCollectionUpdate<T>(\n items: T[],\n action: string,\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n },\n getId: (item: T) => string\n ): T[] {\n switch (action) {\n case 'prepend':\n return payload.data ? [payload.data as T, ...items] : items;\n\n case 'append':\n return payload.data ? [...items, payload.data as T] : items;\n\n case 'update':\n return items.map((item) => {\n const matches = payload.id\n ? getId(item) === payload.id\n : payload.where?.(item);\n if (matches && payload.update) {\n return payload.update(item);\n }\n if (matches && payload.data) {\n return { ...item, ...payload.data };\n }\n return item;\n });\n\n case 'delete':\n return items.filter((item) => {\n if (payload.id) return getId(item) !== payload.id;\n if (payload.where) return !payload.where(item);\n return true;\n });\n\n case 'replace':\n return items.map((item) => {\n const matches = payload.id\n ? getId(item) === payload.id\n : payload.where?.(item);\n return matches && payload.data ? (payload.data as T) : item;\n });\n\n default:\n return items;\n }\n }\n}\n\n/** Singleton registry instance */\nexport const registry = new QueryRegistry();\n","import { nanoid } from 'nanoid';\nimport type { CollectionDef, EntityDef, Optimistic } from './types';\nimport { registry } from './registry';\n\n/** Transaction returned from channel methods */\nexport interface OptimisticTransaction {\n target: CollectionDef<any, any> | EntityDef<any, any>;\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace';\n data?: any;\n id?: string;\n where?: (item: any) => boolean;\n update?: (item: any) => any;\n sync?: boolean;\n rollback: () => void;\n}\n\n/** Channel for a collection - provides typed optimistic mutation methods */\nexport class CollectionChannel<TEntity> {\n private readonly optimisticId = nanoid();\n\n constructor(private readonly target: CollectionDef<TEntity, any>) {}\n\n /**\n * Prepend an item to the collection\n * @returns Rollback function to undo the change\n */\n prepend(data: TEntity, options?: { sync?: boolean }): () => void {\n const optimisticData = {\n ...data,\n _optimistic: { id: this.optimisticId, status: 'pending' as const },\n };\n\n const rollbacks = registry.applyUpdate(this.target.name, 'prepend', {\n data: optimisticData,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Append an item to the collection\n * @returns Rollback function to undo the change\n */\n append(data: TEntity, options?: { sync?: boolean }): () => void {\n const optimisticData = {\n ...data,\n _optimistic: { id: this.optimisticId, status: 'pending' as const },\n };\n\n const rollbacks = registry.applyUpdate(this.target.name, 'append', {\n data: optimisticData,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Update an item in the collection by ID\n * @returns Rollback function to undo the change\n */\n update(\n id: string,\n updateFn: (item: TEntity) => TEntity,\n options?: { sync?: boolean }\n ): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'update', {\n id,\n update: updateFn,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Update items matching a predicate\n * @returns Rollback function to undo the change\n */\n updateWhere(\n where: (item: TEntity) => boolean,\n updateFn: (item: TEntity) => TEntity\n ): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'update', {\n where,\n update: updateFn,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Delete an item from the collection by ID\n * @returns Rollback function to undo the change\n */\n delete(id: string): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'delete', {\n id,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Delete items matching a predicate\n * @returns Rollback function to undo the change\n */\n deleteWhere(where: (item: TEntity) => boolean): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'delete', {\n where,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n}\n\n/** Channel for an entity - provides typed optimistic mutation methods */\nexport class EntityChannel<TEntity> {\n constructor(private readonly target: EntityDef<TEntity, any>) {}\n\n /**\n * Update the entity\n * @returns Rollback function to undo the change\n */\n update(updateFn: (item: TEntity) => TEntity, options?: { sync?: boolean }): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'update', {\n update: updateFn,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Replace the entity with new data\n * @returns Rollback function to undo the change\n */\n replace(data: TEntity, options?: { sync?: boolean }): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'replace', {\n data: data as any,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n}\n\n/**\n * Channel function for optimistic mutations.\n * Call with a collection or entity to get typed mutation methods.\n *\n * @example\n * // Standalone usage\n * const rollback = channel(usersCollection).prepend({ id: '1', name: 'John' });\n * // Later, to undo:\n * rollback();\n *\n * @example\n * // Update an entity\n * channel(userEntity).update(user => ({ ...user, name: 'Jane' }));\n */\nexport interface Channel {\n <TEntity>(target: CollectionDef<TEntity, any>): CollectionChannel<TEntity>;\n <TEntity>(target: EntityDef<TEntity, any>): EntityChannel<TEntity>;\n}\n\n/**\n * Create a channel for optimistic mutations.\n * Use this to apply immediate UI updates that can be rolled back.\n *\n * @example\n * const rollback = channel(usersCollection).prepend(newUser);\n * try {\n * await api.createUser(newUser);\n * } catch (error) {\n * rollback(); // Undo the optimistic update\n * }\n */\nexport const channel: Channel = (<TEntity>(\n target: CollectionDef<TEntity, any> | EntityDef<TEntity, any>\n): CollectionChannel<TEntity> | EntityChannel<TEntity> => {\n if (target._type === 'collection') {\n return new CollectionChannel(target);\n } else {\n return new EntityChannel(target);\n }\n}) as Channel;\n"]}
1
+ {"version":3,"sources":["../../src/core/define.ts","../../src/core/registry.ts","../../src/core/channel.ts"],"names":["nanoid"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n CollectionDef,\n EntityDef,\n MutationDef,\n IdGetter,\n} from './types';\n\n/**\n * Define a collection query for fetching arrays of items\n *\n * @example\n * const postsQuery = defineCollection({\n * name: 'posts',\n * id: (post) => post._id,\n * fetch: ({ page }) => api.get(`/posts?page=${page}`).json()\n * })\n */\nexport function defineCollection<TData, TParams = void>(config: {\n name: string;\n id: IdGetter<TData>;\n fetch: (params: TParams) => Promise<TData[]>;\n}): CollectionDef<TData, TParams> {\n return {\n _type: 'collection',\n name: config.name,\n id: config.id,\n fetch: config.fetch,\n };\n}\n\n/**\n * Define an entity for fetching single items\n *\n * @example\n * const userEntity = defineEntity({\n * name: 'user',\n * fetch: (userId) => api.get(`/users/${userId}`).json()\n * })\n */\nexport function defineEntity<TData, TParams = void>(config: {\n name: string;\n fetch: (params: TParams) => Promise<TData>;\n}): EntityDef<TData, TParams> {\n return {\n _type: 'entity',\n name: config.name,\n fetch: config.fetch,\n };\n}\n\n/**\n * Define a mutation for writing data\n *\n * @example\n * const createPost = defineMutation({\n * name: 'createPost',\n * mutate: (data) => api.post('/posts', { json: data }).json()\n * })\n */\nexport function defineMutation<TParams, TResponse = void>(config: {\n name?: string;\n mutate: (params: TParams) => Promise<TResponse>;\n}): MutationDef<TParams, TResponse> {\n return {\n _type: 'mutation',\n name: config.name,\n mutate: config.mutate,\n };\n}\n","import type { QueryClient } from '@tanstack/react-query';\nimport type { CollectionDef, EntityDef, Optimistic } from './types';\n\n/** Registered collection entry */\nexport interface RegisteredCollection<T = any> {\n kind: 'collection';\n name: string;\n queryKey: readonly unknown[];\n def: CollectionDef<T, any>;\n getData: () => T[] | undefined;\n setData: (updater: (prev: T[] | undefined) => T[] | undefined) => void;\n}\n\n/** Registered entity entry */\nexport interface RegisteredEntity<T = any> {\n kind: 'entity';\n name: string;\n queryKey: readonly unknown[];\n def: EntityDef<T, any>;\n getData: () => T | undefined;\n setData: (updater: (prev: T | undefined) => T | undefined) => void;\n}\n\n/** Registered paginated collection entry */\nexport interface RegisteredPaginatedCollection<T = any> {\n kind: 'paginated';\n name: string;\n queryKey: readonly unknown[];\n def: CollectionDef<T, any>;\n getData: () => { pages: T[][]; pageParams: unknown[] } | undefined;\n setData: (\n updater: (\n prev: { pages: T[][]; pageParams: unknown[] } | undefined\n ) => { pages: T[][]; pageParams: unknown[] } | undefined\n ) => void;\n}\n\nexport type RegisteredEntry =\n | RegisteredCollection\n | RegisteredEntity\n | RegisteredPaginatedCollection;\n\n/**\n * Internal registry for tracking active queries\n * Used by optimistic updates to broadcast changes\n */\nclass QueryRegistry {\n private entries = new Map<string, Set<RegisteredEntry>>();\n private queryClient: QueryClient | null = null;\n private collectionDefs = new Map<string, CollectionDef<any, any>>();\n\n /** Set the query client for direct cache access */\n setQueryClient(client: QueryClient): void {\n this.queryClient = client;\n }\n\n /** Register a collection definition for direct cache updates */\n registerDef(def: CollectionDef<any, any>): void {\n this.collectionDefs.set(def.name, def);\n }\n\n /** Register an active query */\n register(entry: RegisteredEntry): void {\n if (!this.entries.has(entry.name)) {\n this.entries.set(entry.name, new Set());\n }\n this.entries.get(entry.name)!.add(entry);\n\n // Also store the def for direct cache access\n if (entry.kind === 'collection' || entry.kind === 'paginated') {\n this.collectionDefs.set(entry.name, entry.def);\n }\n }\n\n /** Unregister a query when component unmounts */\n unregister(entry: RegisteredEntry): void {\n const set = this.entries.get(entry.name);\n if (set) {\n set.delete(entry);\n if (set.size === 0) {\n this.entries.delete(entry.name);\n }\n }\n }\n\n /** Get all registered entries for a query name */\n getByName(name: string): RegisteredEntry[] {\n return Array.from(this.entries.get(name) ?? []);\n }\n\n /**\n * Check if params partially match the given scope object.\n * Returns true if all key-value pairs in scope exist in params.\n */\n private matchesScope(params: Record<string, unknown> | undefined, scope?: Record<string, unknown>): boolean {\n if (!scope) return true;\n if (!params) return false;\n\n // Check if all scope keys exist in params with same value\n return Object.entries(scope).every(([key, value]) => params[key] === value);\n }\n\n /** Apply an optimistic update to all queries with given name */\n applyUpdate<T>(\n name: string,\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace',\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n },\n scope?: Record<string, unknown>\n ): (() => void)[] {\n // When scope is provided and we have a queryClient, update cache directly\n if (scope && this.queryClient) {\n return this.applyDirectCacheUpdate(name, action, payload, scope);\n }\n\n // Otherwise, use registry-based updates\n const entries = this.getByName(name);\n const rollbacks: (() => void)[] = [];\n\n // Deduplicate by queryKey to avoid updating the same cache entry multiple times\n const seenKeys = new Set<string>();\n const uniqueEntries = entries.filter((entry) => {\n const key = JSON.stringify(entry.queryKey);\n if (seenKeys.has(key)) return false;\n seenKeys.add(key);\n // Also filter by scope if provided\n const params = entry.queryKey[1] as Record<string, unknown> | undefined;\n return this.matchesScope(params, scope);\n });\n\n for (const entry of uniqueEntries) {\n if (entry.kind === 'collection') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n entry.setData((prev) => {\n if (!prev) return prev;\n return this.applyCollectionUpdate(prev, action, payload, entry.def.id);\n });\n } else if (entry.kind === 'paginated') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n entry.setData((prev) => {\n if (!prev) return prev;\n return {\n ...prev,\n pages: prev.pages.map((page, i) =>\n i === 0\n ? this.applyCollectionUpdate(page, action, payload, entry.def.id)\n : page\n ),\n };\n });\n } else if (entry.kind === 'entity') {\n const previous = entry.getData();\n const rollback = () => entry.setData(() => previous);\n rollbacks.push(rollback);\n\n if (action === 'update' && payload.update) {\n entry.setData((prev) => (prev ? payload.update!(prev as T) : prev));\n } else if (action === 'replace' && payload.data) {\n entry.setData(() => payload.data as T);\n }\n }\n }\n\n return rollbacks;\n }\n\n /** Apply update directly to query cache (used when scope is provided) */\n private applyDirectCacheUpdate<T>(\n name: string,\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace',\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n },\n scope: Record<string, unknown>\n ): (() => void)[] {\n if (!this.queryClient) return [];\n\n const def = this.collectionDefs.get(name);\n if (!def) return [];\n\n const rollbacks: (() => void)[] = [];\n\n // Get all queries with this name from the cache\n const queries = this.queryClient.getQueriesData<T[] | { pages: T[][]; pageParams: unknown[] }>({\n queryKey: [name],\n });\n\n for (const [queryKey, data] of queries) {\n if (!data) continue;\n\n // Extract params from queryKey [name, params]\n const params = queryKey[1] as Record<string, unknown> | undefined;\n if (!this.matchesScope(params, scope)) continue;\n\n // Check if this is a paginated query\n const isPaginated = data && typeof data === 'object' && 'pages' in data;\n\n if (isPaginated) {\n const paginatedData = data as { pages: T[][]; pageParams: unknown[] };\n const previous = paginatedData;\n rollbacks.push(() => this.queryClient!.setQueryData(queryKey, previous));\n\n this.queryClient.setQueryData<{ pages: T[][]; pageParams: unknown[] }>(queryKey, (prev) => {\n if (!prev) return prev;\n return {\n ...prev,\n pages: prev.pages.map((page, i) =>\n i === 0\n ? this.applyCollectionUpdate(page, action, payload, def.id)\n : page\n ),\n };\n });\n } else {\n const arrayData = data as T[];\n const previous = arrayData;\n rollbacks.push(() => this.queryClient!.setQueryData(queryKey, previous));\n\n this.queryClient.setQueryData<T[]>(queryKey, (prev) => {\n if (!prev) return prev;\n return this.applyCollectionUpdate(prev, action, payload, def.id);\n });\n }\n }\n\n return rollbacks;\n }\n\n private applyCollectionUpdate<T>(\n items: T[],\n action: string,\n payload: {\n data?: Partial<Optimistic<T>>;\n id?: string;\n where?: (item: T) => boolean;\n update?: (item: T) => T;\n },\n getId: (item: T) => string\n ): T[] {\n switch (action) {\n case 'prepend':\n return payload.data ? [payload.data as T, ...items] : items;\n\n case 'append':\n return payload.data ? [...items, payload.data as T] : items;\n\n case 'update':\n return items.map((item) => {\n const matches = payload.id\n ? getId(item) === payload.id\n : payload.where?.(item);\n if (matches && payload.update) {\n return payload.update(item);\n }\n if (matches && payload.data) {\n return { ...item, ...payload.data };\n }\n return item;\n });\n\n case 'delete':\n return items.filter((item) => {\n if (payload.id) return getId(item) !== payload.id;\n if (payload.where) return !payload.where(item);\n return true;\n });\n\n case 'replace':\n return items.map((item) => {\n const matches = payload.id\n ? getId(item) === payload.id\n : payload.where?.(item);\n return matches && payload.data ? (payload.data as T) : item;\n });\n\n default:\n return items;\n }\n }\n}\n\n/** Singleton registry instance */\nexport const registry = new QueryRegistry();\n","import { nanoid } from 'nanoid';\nimport type { CollectionDef, EntityDef, Optimistic } from './types';\nimport { registry } from './registry';\n\n/** Transaction returned from channel methods */\nexport interface OptimisticTransaction {\n target: CollectionDef<any, any> | EntityDef<any, any>;\n action: 'prepend' | 'append' | 'update' | 'delete' | 'replace';\n data?: any;\n id?: string;\n where?: (item: any) => boolean;\n update?: (item: any) => any;\n sync?: boolean;\n rollback: () => void;\n}\n\n/** Options for channel operations */\nexport interface ChannelOptions {\n /**\n * Only apply updates to queries whose params partially match this object.\n * All key-value pairs in scope must exist in the query's params.\n * @example\n * channel(ordersCollection, { scope: { chain: 'solana' } }).delete(id)\n * // Only affects queries with params containing chain: 'solana'\n */\n scope?: Record<string, unknown>;\n}\n\n/** Channel for a collection - provides typed optimistic mutation methods */\nexport class CollectionChannel<TEntity> {\n private readonly optimisticId = nanoid();\n\n constructor(\n private readonly target: CollectionDef<TEntity, any>,\n private readonly options?: ChannelOptions\n ) {}\n\n /**\n * Prepend an item to the collection\n * @returns Rollback function to undo the change\n */\n prepend(data: TEntity, options?: { sync?: boolean }): () => void {\n const optimisticData = {\n ...data,\n _optimistic: { id: this.optimisticId, status: 'pending' as const },\n };\n\n const rollbacks = registry.applyUpdate(this.target.name, 'prepend', {\n data: optimisticData,\n }, this.options?.scope);\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Append an item to the collection\n * @returns Rollback function to undo the change\n */\n append(data: TEntity, options?: { sync?: boolean }): () => void {\n const optimisticData = {\n ...data,\n _optimistic: { id: this.optimisticId, status: 'pending' as const },\n };\n\n const rollbacks = registry.applyUpdate(this.target.name, 'append', {\n data: optimisticData,\n }, this.options?.scope);\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Update an item in the collection by ID\n * @returns Rollback function to undo the change\n */\n update(\n id: string,\n updateFn: (item: TEntity) => TEntity,\n options?: { sync?: boolean }\n ): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'update', {\n id,\n update: updateFn,\n }, this.options?.scope);\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Update items matching a predicate\n * @returns Rollback function to undo the change\n */\n updateWhere(\n where: (item: TEntity) => boolean,\n updateFn: (item: TEntity) => TEntity\n ): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'update', {\n where,\n update: updateFn,\n }, this.options?.scope);\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Delete an item from the collection by ID\n * @returns Rollback function to undo the change\n */\n delete(id: string): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'delete', {\n id,\n }, this.options?.scope);\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Delete items matching a predicate\n * @returns Rollback function to undo the change\n */\n deleteWhere(where: (item: TEntity) => boolean): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'delete', {\n where,\n }, this.options?.scope);\n\n return () => rollbacks.forEach((rb) => rb());\n }\n}\n\n/** Channel for an entity - provides typed optimistic mutation methods */\nexport class EntityChannel<TEntity> {\n constructor(private readonly target: EntityDef<TEntity, any>) {}\n\n /**\n * Update the entity\n * @returns Rollback function to undo the change\n */\n update(updateFn: (item: TEntity) => TEntity, options?: { sync?: boolean }): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'update', {\n update: updateFn,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n\n /**\n * Replace the entity with new data\n * @returns Rollback function to undo the change\n */\n replace(data: TEntity, options?: { sync?: boolean }): () => void {\n const rollbacks = registry.applyUpdate(this.target.name, 'replace', {\n data: data as any,\n });\n\n return () => rollbacks.forEach((rb) => rb());\n }\n}\n\n/**\n * Channel function for optimistic mutations.\n * Call with a collection or entity to get typed mutation methods.\n *\n * @example\n * // Standalone usage\n * const rollback = channel(usersCollection).prepend({ id: '1', name: 'John' });\n * // Later, to undo:\n * rollback();\n *\n * @example\n * // Update an entity\n * channel(userEntity).update(user => ({ ...user, name: 'Jane' }));\n *\n * @example\n * // Scoped update\n * channel(ordersCollection, { scope: { chain: 'solana' } }).delete(id);\n * // Only affects queries with params containing chain: 'solana'\n */\nexport interface Channel {\n <TEntity>(target: CollectionDef<TEntity, any>, options?: ChannelOptions): CollectionChannel<TEntity>;\n <TEntity>(target: EntityDef<TEntity, any>): EntityChannel<TEntity>;\n}\n\n/**\n * Create a channel for optimistic mutations.\n * Use this to apply immediate UI updates that can be rolled back.\n *\n * @example\n * const rollback = channel(usersCollection).prepend(newUser);\n * try {\n * await api.createUser(newUser);\n * } catch (error) {\n * rollback(); // Undo the optimistic update\n * }\n *\n * @example\n * // Scoped update - only affects queries with matching params\n * channel(ordersCollection, { scope: { chain: 'solana', status: 'pending' } }).delete(id);\n */\nexport const channel: Channel = (<TEntity>(\n target: CollectionDef<TEntity, any> | EntityDef<TEntity, any>,\n options?: ChannelOptions\n): CollectionChannel<TEntity> | EntityChannel<TEntity> => {\n if (target._type === 'collection') {\n return new CollectionChannel(target, options);\n } else {\n return new EntityChannel(target);\n }\n}) as Channel;\n"]}
@@ -28,6 +28,16 @@ function defineMutation(config) {
28
28
  var QueryRegistry = class {
29
29
  constructor() {
30
30
  this.entries = /* @__PURE__ */ new Map();
31
+ this.queryClient = null;
32
+ this.collectionDefs = /* @__PURE__ */ new Map();
33
+ }
34
+ /** Set the query client for direct cache access */
35
+ setQueryClient(client) {
36
+ this.queryClient = client;
37
+ }
38
+ /** Register a collection definition for direct cache updates */
39
+ registerDef(def) {
40
+ this.collectionDefs.set(def.name, def);
31
41
  }
32
42
  /** Register an active query */
33
43
  register(entry) {
@@ -35,6 +45,9 @@ var QueryRegistry = class {
35
45
  this.entries.set(entry.name, /* @__PURE__ */ new Set());
36
46
  }
37
47
  this.entries.get(entry.name).add(entry);
48
+ if (entry.kind === "collection" || entry.kind === "paginated") {
49
+ this.collectionDefs.set(entry.name, entry.def);
50
+ }
38
51
  }
39
52
  /** Unregister a query when component unmounts */
40
53
  unregister(entry) {
@@ -50,8 +63,20 @@ var QueryRegistry = class {
50
63
  getByName(name) {
51
64
  return Array.from(this.entries.get(name) ?? []);
52
65
  }
66
+ /**
67
+ * Check if params partially match the given scope object.
68
+ * Returns true if all key-value pairs in scope exist in params.
69
+ */
70
+ matchesScope(params, scope) {
71
+ if (!scope) return true;
72
+ if (!params) return false;
73
+ return Object.entries(scope).every(([key, value]) => params[key] === value);
74
+ }
53
75
  /** Apply an optimistic update to all queries with given name */
54
- applyUpdate(name, action, payload) {
76
+ applyUpdate(name, action, payload, scope) {
77
+ if (scope && this.queryClient) {
78
+ return this.applyDirectCacheUpdate(name, action, payload, scope);
79
+ }
55
80
  const entries = this.getByName(name);
56
81
  const rollbacks = [];
57
82
  const seenKeys = /* @__PURE__ */ new Set();
@@ -59,7 +84,8 @@ var QueryRegistry = class {
59
84
  const key = JSON.stringify(entry.queryKey);
60
85
  if (seenKeys.has(key)) return false;
61
86
  seenKeys.add(key);
62
- return true;
87
+ const params = entry.queryKey[1];
88
+ return this.matchesScope(params, scope);
63
89
  });
64
90
  for (const entry of uniqueEntries) {
65
91
  if (entry.kind === "collection") {
@@ -96,6 +122,45 @@ var QueryRegistry = class {
96
122
  }
97
123
  return rollbacks;
98
124
  }
125
+ /** Apply update directly to query cache (used when scope is provided) */
126
+ applyDirectCacheUpdate(name, action, payload, scope) {
127
+ if (!this.queryClient) return [];
128
+ const def = this.collectionDefs.get(name);
129
+ if (!def) return [];
130
+ const rollbacks = [];
131
+ const queries = this.queryClient.getQueriesData({
132
+ queryKey: [name]
133
+ });
134
+ for (const [queryKey, data] of queries) {
135
+ if (!data) continue;
136
+ const params = queryKey[1];
137
+ if (!this.matchesScope(params, scope)) continue;
138
+ const isPaginated = data && typeof data === "object" && "pages" in data;
139
+ if (isPaginated) {
140
+ const paginatedData = data;
141
+ const previous = paginatedData;
142
+ rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
143
+ this.queryClient.setQueryData(queryKey, (prev) => {
144
+ if (!prev) return prev;
145
+ return {
146
+ ...prev,
147
+ pages: prev.pages.map(
148
+ (page, i) => i === 0 ? this.applyCollectionUpdate(page, action, payload, def.id) : page
149
+ )
150
+ };
151
+ });
152
+ } else {
153
+ const arrayData = data;
154
+ const previous = arrayData;
155
+ rollbacks.push(() => this.queryClient.setQueryData(queryKey, previous));
156
+ this.queryClient.setQueryData(queryKey, (prev) => {
157
+ if (!prev) return prev;
158
+ return this.applyCollectionUpdate(prev, action, payload, def.id);
159
+ });
160
+ }
161
+ }
162
+ return rollbacks;
163
+ }
99
164
  applyCollectionUpdate(items, action, payload, getId) {
100
165
  switch (action) {
101
166
  case "prepend":
@@ -131,8 +196,9 @@ var QueryRegistry = class {
131
196
  };
132
197
  var registry = new QueryRegistry();
133
198
  var CollectionChannel = class {
134
- constructor(target) {
199
+ constructor(target, options) {
135
200
  this.target = target;
201
+ this.options = options;
136
202
  this.optimisticId = nanoid();
137
203
  }
138
204
  /**
@@ -146,7 +212,7 @@ var CollectionChannel = class {
146
212
  };
147
213
  const rollbacks = registry.applyUpdate(this.target.name, "prepend", {
148
214
  data: optimisticData
149
- });
215
+ }, this.options?.scope);
150
216
  return () => rollbacks.forEach((rb) => rb());
151
217
  }
152
218
  /**
@@ -160,7 +226,7 @@ var CollectionChannel = class {
160
226
  };
161
227
  const rollbacks = registry.applyUpdate(this.target.name, "append", {
162
228
  data: optimisticData
163
- });
229
+ }, this.options?.scope);
164
230
  return () => rollbacks.forEach((rb) => rb());
165
231
  }
166
232
  /**
@@ -171,7 +237,7 @@ var CollectionChannel = class {
171
237
  const rollbacks = registry.applyUpdate(this.target.name, "update", {
172
238
  id,
173
239
  update: updateFn
174
- });
240
+ }, this.options?.scope);
175
241
  return () => rollbacks.forEach((rb) => rb());
176
242
  }
177
243
  /**
@@ -182,7 +248,7 @@ var CollectionChannel = class {
182
248
  const rollbacks = registry.applyUpdate(this.target.name, "update", {
183
249
  where,
184
250
  update: updateFn
185
- });
251
+ }, this.options?.scope);
186
252
  return () => rollbacks.forEach((rb) => rb());
187
253
  }
188
254
  /**
@@ -192,7 +258,7 @@ var CollectionChannel = class {
192
258
  delete(id) {
193
259
  const rollbacks = registry.applyUpdate(this.target.name, "delete", {
194
260
  id
195
- });
261
+ }, this.options?.scope);
196
262
  return () => rollbacks.forEach((rb) => rb());
197
263
  }
198
264
  /**
@@ -202,7 +268,7 @@ var CollectionChannel = class {
202
268
  deleteWhere(where) {
203
269
  const rollbacks = registry.applyUpdate(this.target.name, "delete", {
204
270
  where
205
- });
271
+ }, this.options?.scope);
206
272
  return () => rollbacks.forEach((rb) => rb());
207
273
  }
208
274
  };
@@ -231,9 +297,9 @@ var EntityChannel = class {
231
297
  return () => rollbacks.forEach((rb) => rb());
232
298
  }
233
299
  };
234
- var channel = ((target) => {
300
+ var channel = ((target, options) => {
235
301
  if (target._type === "collection") {
236
- return new CollectionChannel(target);
302
+ return new CollectionChannel(target, options);
237
303
  } else {
238
304
  return new EntityChannel(target);
239
305
  }