query-optimistic 0.1.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.
@@ -0,0 +1,211 @@
1
+ import { I as IdGetter, C as CollectionDef, E as EntityDef, M as MutationDef, a as Optimistic } from '../types-BRxQA1mR.js';
2
+ export { A as AnyDef, b as OptimisticAction, c as OptimisticInstruction, O as OptimisticStatus, P as PaginatedOptions, Q as QueryOptions } from '../types-BRxQA1mR.js';
3
+
4
+ /**
5
+ * Define a collection query for fetching arrays of items
6
+ *
7
+ * @example
8
+ * const postsQuery = defineCollection({
9
+ * name: 'posts',
10
+ * id: (post) => post._id,
11
+ * fetch: ({ page }) => api.get(`/posts?page=${page}`).json()
12
+ * })
13
+ */
14
+ declare function defineCollection<TData, TParams = void>(config: {
15
+ name: string;
16
+ id: IdGetter<TData>;
17
+ fetch: (params: TParams) => Promise<TData[]>;
18
+ }): CollectionDef<TData, TParams>;
19
+ /**
20
+ * Define an entity for fetching single items
21
+ *
22
+ * @example
23
+ * const userEntity = defineEntity({
24
+ * name: 'user',
25
+ * fetch: (userId) => api.get(`/users/${userId}`).json()
26
+ * })
27
+ */
28
+ declare function defineEntity<TData, TParams = void>(config: {
29
+ name: string;
30
+ fetch: (params: TParams) => Promise<TData>;
31
+ }): EntityDef<TData, TParams>;
32
+ /**
33
+ * Define a mutation for writing data
34
+ *
35
+ * @example
36
+ * const createPost = defineMutation({
37
+ * name: 'createPost',
38
+ * mutate: (data) => api.post('/posts', { json: data }).json()
39
+ * })
40
+ */
41
+ declare function defineMutation<TParams, TResponse = void>(config: {
42
+ name?: string;
43
+ mutate: (params: TParams) => Promise<TResponse>;
44
+ }): MutationDef<TParams, TResponse>;
45
+
46
+ /** Registered collection entry */
47
+ interface RegisteredCollection<T = any> {
48
+ kind: 'collection';
49
+ name: string;
50
+ queryKey: readonly unknown[];
51
+ def: CollectionDef<T, any>;
52
+ getData: () => T[] | undefined;
53
+ setData: (updater: (prev: T[] | undefined) => T[] | undefined) => void;
54
+ }
55
+ /** Registered entity entry */
56
+ interface RegisteredEntity<T = any> {
57
+ kind: 'entity';
58
+ name: string;
59
+ queryKey: readonly unknown[];
60
+ def: EntityDef<T, any>;
61
+ getData: () => T | undefined;
62
+ setData: (updater: (prev: T | undefined) => T | undefined) => void;
63
+ }
64
+ /** Registered paginated collection entry */
65
+ interface RegisteredPaginatedCollection<T = any> {
66
+ kind: 'paginated';
67
+ name: string;
68
+ queryKey: readonly unknown[];
69
+ def: CollectionDef<T, any>;
70
+ getData: () => {
71
+ pages: T[][];
72
+ pageParams: unknown[];
73
+ } | undefined;
74
+ setData: (updater: (prev: {
75
+ pages: T[][];
76
+ pageParams: unknown[];
77
+ } | undefined) => {
78
+ pages: T[][];
79
+ pageParams: unknown[];
80
+ } | undefined) => void;
81
+ }
82
+ type RegisteredEntry = RegisteredCollection | RegisteredEntity | RegisteredPaginatedCollection;
83
+ /**
84
+ * Internal registry for tracking active queries
85
+ * Used by optimistic updates to broadcast changes
86
+ */
87
+ declare class QueryRegistry {
88
+ private entries;
89
+ /** Register an active query */
90
+ register(entry: RegisteredEntry): void;
91
+ /** Unregister a query when component unmounts */
92
+ unregister(entry: RegisteredEntry): void;
93
+ /** Get all registered entries for a query name */
94
+ getByName(name: string): RegisteredEntry[];
95
+ /** Apply an optimistic update to all queries with given name */
96
+ applyUpdate<T>(name: string, action: 'prepend' | 'append' | 'update' | 'delete' | 'replace', payload: {
97
+ data?: Partial<Optimistic<T>>;
98
+ id?: string;
99
+ where?: (item: T) => boolean;
100
+ update?: (item: T) => T;
101
+ }): (() => void)[];
102
+ private applyCollectionUpdate;
103
+ }
104
+ /** Singleton registry instance */
105
+ declare const registry: QueryRegistry;
106
+
107
+ /** Transaction returned from channel methods */
108
+ interface OptimisticTransaction {
109
+ target: CollectionDef<any, any> | EntityDef<any, any>;
110
+ action: 'prepend' | 'append' | 'update' | 'delete' | 'replace';
111
+ data?: any;
112
+ id?: string;
113
+ where?: (item: any) => boolean;
114
+ update?: (item: any) => any;
115
+ sync?: boolean;
116
+ rollback: () => void;
117
+ }
118
+ /** Channel for a collection - provides typed optimistic mutation methods */
119
+ declare class CollectionChannel<TEntity> {
120
+ private readonly target;
121
+ private readonly optimisticId;
122
+ constructor(target: CollectionDef<TEntity, any>);
123
+ /**
124
+ * Prepend an item to the collection
125
+ * @returns Rollback function to undo the change
126
+ */
127
+ prepend(data: TEntity, options?: {
128
+ sync?: boolean;
129
+ }): () => void;
130
+ /**
131
+ * Append an item to the collection
132
+ * @returns Rollback function to undo the change
133
+ */
134
+ append(data: TEntity, options?: {
135
+ sync?: boolean;
136
+ }): () => void;
137
+ /**
138
+ * Update an item in the collection by ID
139
+ * @returns Rollback function to undo the change
140
+ */
141
+ update(id: string, updateFn: (item: TEntity) => TEntity, options?: {
142
+ sync?: boolean;
143
+ }): () => void;
144
+ /**
145
+ * Update items matching a predicate
146
+ * @returns Rollback function to undo the change
147
+ */
148
+ updateWhere(where: (item: TEntity) => boolean, updateFn: (item: TEntity) => TEntity): () => void;
149
+ /**
150
+ * Delete an item from the collection by ID
151
+ * @returns Rollback function to undo the change
152
+ */
153
+ delete(id: string): () => void;
154
+ /**
155
+ * Delete items matching a predicate
156
+ * @returns Rollback function to undo the change
157
+ */
158
+ deleteWhere(where: (item: TEntity) => boolean): () => void;
159
+ }
160
+ /** Channel for an entity - provides typed optimistic mutation methods */
161
+ declare class EntityChannel<TEntity> {
162
+ private readonly target;
163
+ constructor(target: EntityDef<TEntity, any>);
164
+ /**
165
+ * Update the entity
166
+ * @returns Rollback function to undo the change
167
+ */
168
+ update(updateFn: (item: TEntity) => TEntity, options?: {
169
+ sync?: boolean;
170
+ }): () => void;
171
+ /**
172
+ * Replace the entity with new data
173
+ * @returns Rollback function to undo the change
174
+ */
175
+ replace(data: TEntity, options?: {
176
+ sync?: boolean;
177
+ }): () => void;
178
+ }
179
+ /**
180
+ * Channel function for optimistic mutations.
181
+ * Call with a collection or entity to get typed mutation methods.
182
+ *
183
+ * @example
184
+ * // Standalone usage
185
+ * const rollback = channel(usersCollection).prepend({ id: '1', name: 'John' });
186
+ * // Later, to undo:
187
+ * rollback();
188
+ *
189
+ * @example
190
+ * // Update an entity
191
+ * channel(userEntity).update(user => ({ ...user, name: 'Jane' }));
192
+ */
193
+ interface Channel {
194
+ <TEntity>(target: CollectionDef<TEntity, any>): CollectionChannel<TEntity>;
195
+ <TEntity>(target: EntityDef<TEntity, any>): EntityChannel<TEntity>;
196
+ }
197
+ /**
198
+ * Create a channel for optimistic mutations.
199
+ * Use this to apply immediate UI updates that can be rolled back.
200
+ *
201
+ * @example
202
+ * const rollback = channel(usersCollection).prepend(newUser);
203
+ * try {
204
+ * await api.createUser(newUser);
205
+ * } catch (error) {
206
+ * rollback(); // Undo the optimistic update
207
+ * }
208
+ */
209
+ declare const channel: Channel;
210
+
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 };
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ var nanoid = require('nanoid');
4
+
5
+ // src/core/define.ts
6
+ function defineCollection(config) {
7
+ return {
8
+ _type: "collection",
9
+ name: config.name,
10
+ id: config.id,
11
+ fetch: config.fetch
12
+ };
13
+ }
14
+ function defineEntity(config) {
15
+ return {
16
+ _type: "entity",
17
+ name: config.name,
18
+ fetch: config.fetch
19
+ };
20
+ }
21
+ function defineMutation(config) {
22
+ return {
23
+ _type: "mutation",
24
+ name: config.name,
25
+ mutate: config.mutate
26
+ };
27
+ }
28
+
29
+ // src/core/registry.ts
30
+ var QueryRegistry = class {
31
+ constructor() {
32
+ this.entries = /* @__PURE__ */ new Map();
33
+ }
34
+ /** Register an active query */
35
+ register(entry) {
36
+ if (!this.entries.has(entry.name)) {
37
+ this.entries.set(entry.name, /* @__PURE__ */ new Set());
38
+ }
39
+ this.entries.get(entry.name).add(entry);
40
+ }
41
+ /** Unregister a query when component unmounts */
42
+ unregister(entry) {
43
+ const set = this.entries.get(entry.name);
44
+ if (set) {
45
+ set.delete(entry);
46
+ if (set.size === 0) {
47
+ this.entries.delete(entry.name);
48
+ }
49
+ }
50
+ }
51
+ /** Get all registered entries for a query name */
52
+ getByName(name) {
53
+ return Array.from(this.entries.get(name) ?? []);
54
+ }
55
+ /** Apply an optimistic update to all queries with given name */
56
+ applyUpdate(name, action, payload) {
57
+ const entries = this.getByName(name);
58
+ const rollbacks = [];
59
+ for (const entry of entries) {
60
+ if (entry.kind === "collection") {
61
+ const previous = entry.getData();
62
+ const rollback = () => entry.setData(() => previous);
63
+ rollbacks.push(rollback);
64
+ entry.setData((prev) => {
65
+ if (!prev) return prev;
66
+ return this.applyCollectionUpdate(prev, action, payload, entry.def.id);
67
+ });
68
+ } else if (entry.kind === "paginated") {
69
+ const previous = entry.getData();
70
+ const rollback = () => entry.setData(() => previous);
71
+ rollbacks.push(rollback);
72
+ entry.setData((prev) => {
73
+ if (!prev) return prev;
74
+ return {
75
+ ...prev,
76
+ pages: prev.pages.map(
77
+ (page, i) => i === 0 ? this.applyCollectionUpdate(page, action, payload, entry.def.id) : page
78
+ )
79
+ };
80
+ });
81
+ } else if (entry.kind === "entity") {
82
+ const previous = entry.getData();
83
+ const rollback = () => entry.setData(() => previous);
84
+ rollbacks.push(rollback);
85
+ if (action === "update" && payload.update) {
86
+ entry.setData((prev) => prev ? payload.update(prev) : prev);
87
+ } else if (action === "replace" && payload.data) {
88
+ entry.setData(() => payload.data);
89
+ }
90
+ }
91
+ }
92
+ return rollbacks;
93
+ }
94
+ applyCollectionUpdate(items, action, payload, getId) {
95
+ switch (action) {
96
+ case "prepend":
97
+ return payload.data ? [payload.data, ...items] : items;
98
+ case "append":
99
+ return payload.data ? [...items, payload.data] : items;
100
+ case "update":
101
+ return items.map((item) => {
102
+ const matches = payload.id ? getId(item) === payload.id : payload.where?.(item);
103
+ if (matches && payload.update) {
104
+ return payload.update(item);
105
+ }
106
+ if (matches && payload.data) {
107
+ return { ...item, ...payload.data };
108
+ }
109
+ return item;
110
+ });
111
+ case "delete":
112
+ return items.filter((item) => {
113
+ if (payload.id) return getId(item) !== payload.id;
114
+ if (payload.where) return !payload.where(item);
115
+ return true;
116
+ });
117
+ case "replace":
118
+ return items.map((item) => {
119
+ const matches = payload.id ? getId(item) === payload.id : payload.where?.(item);
120
+ return matches && payload.data ? payload.data : item;
121
+ });
122
+ default:
123
+ return items;
124
+ }
125
+ }
126
+ };
127
+ var registry = new QueryRegistry();
128
+ var CollectionChannel = class {
129
+ constructor(target) {
130
+ this.target = target;
131
+ this.optimisticId = nanoid.nanoid();
132
+ }
133
+ /**
134
+ * Prepend an item to the collection
135
+ * @returns Rollback function to undo the change
136
+ */
137
+ prepend(data, options) {
138
+ const optimisticData = {
139
+ ...data,
140
+ _optimistic: { id: this.optimisticId, status: "pending" }
141
+ };
142
+ const rollbacks = registry.applyUpdate(this.target.name, "prepend", {
143
+ data: optimisticData
144
+ });
145
+ return () => rollbacks.forEach((rb) => rb());
146
+ }
147
+ /**
148
+ * Append an item to the collection
149
+ * @returns Rollback function to undo the change
150
+ */
151
+ append(data, options) {
152
+ const optimisticData = {
153
+ ...data,
154
+ _optimistic: { id: this.optimisticId, status: "pending" }
155
+ };
156
+ const rollbacks = registry.applyUpdate(this.target.name, "append", {
157
+ data: optimisticData
158
+ });
159
+ return () => rollbacks.forEach((rb) => rb());
160
+ }
161
+ /**
162
+ * Update an item in the collection by ID
163
+ * @returns Rollback function to undo the change
164
+ */
165
+ update(id, updateFn, options) {
166
+ const rollbacks = registry.applyUpdate(this.target.name, "update", {
167
+ id,
168
+ update: updateFn
169
+ });
170
+ return () => rollbacks.forEach((rb) => rb());
171
+ }
172
+ /**
173
+ * Update items matching a predicate
174
+ * @returns Rollback function to undo the change
175
+ */
176
+ updateWhere(where, updateFn) {
177
+ const rollbacks = registry.applyUpdate(this.target.name, "update", {
178
+ where,
179
+ update: updateFn
180
+ });
181
+ return () => rollbacks.forEach((rb) => rb());
182
+ }
183
+ /**
184
+ * Delete an item from the collection by ID
185
+ * @returns Rollback function to undo the change
186
+ */
187
+ delete(id) {
188
+ const rollbacks = registry.applyUpdate(this.target.name, "delete", {
189
+ id
190
+ });
191
+ return () => rollbacks.forEach((rb) => rb());
192
+ }
193
+ /**
194
+ * Delete items matching a predicate
195
+ * @returns Rollback function to undo the change
196
+ */
197
+ deleteWhere(where) {
198
+ const rollbacks = registry.applyUpdate(this.target.name, "delete", {
199
+ where
200
+ });
201
+ return () => rollbacks.forEach((rb) => rb());
202
+ }
203
+ };
204
+ var EntityChannel = class {
205
+ constructor(target) {
206
+ this.target = target;
207
+ }
208
+ /**
209
+ * Update the entity
210
+ * @returns Rollback function to undo the change
211
+ */
212
+ update(updateFn, options) {
213
+ const rollbacks = registry.applyUpdate(this.target.name, "update", {
214
+ update: updateFn
215
+ });
216
+ return () => rollbacks.forEach((rb) => rb());
217
+ }
218
+ /**
219
+ * Replace the entity with new data
220
+ * @returns Rollback function to undo the change
221
+ */
222
+ replace(data, options) {
223
+ const rollbacks = registry.applyUpdate(this.target.name, "replace", {
224
+ data
225
+ });
226
+ return () => rollbacks.forEach((rb) => rb());
227
+ }
228
+ };
229
+ var channel = ((target) => {
230
+ if (target._type === "collection") {
231
+ return new CollectionChannel(target);
232
+ } else {
233
+ return new EntityChannel(target);
234
+ }
235
+ });
236
+
237
+ exports.CollectionChannel = CollectionChannel;
238
+ exports.EntityChannel = EntityChannel;
239
+ exports.channel = channel;
240
+ exports.defineCollection = defineCollection;
241
+ exports.defineEntity = defineEntity;
242
+ exports.defineMutation = defineMutation;
243
+ exports.registry = registry;
244
+ //# sourceMappingURL=index.js.map
245
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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 for (const entry of entries) {\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"]}