@tanstack/query-db-collection 0.1.0 → 0.1.2

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.
@@ -31,9 +31,74 @@ class GetKeyRequiredError extends QueryCollectionError {
31
31
  this.name = `GetKeyRequiredError`;
32
32
  }
33
33
  }
34
+ class SyncNotInitializedError extends QueryCollectionError {
35
+ constructor() {
36
+ super(
37
+ `Collection must be in 'ready' state for manual sync operations. Sync not initialized yet.`
38
+ );
39
+ this.name = `SyncNotInitializedError`;
40
+ }
41
+ }
42
+ class InvalidItemStructureError extends QueryCollectionError {
43
+ constructor(message) {
44
+ super(`Invalid item structure: ${message}`);
45
+ this.name = `InvalidItemStructureError`;
46
+ }
47
+ }
48
+ class ItemNotFoundError extends QueryCollectionError {
49
+ constructor(key) {
50
+ super(`Item with key '${key}' does not exist.`);
51
+ this.name = `ItemNotFoundError`;
52
+ }
53
+ }
54
+ class DuplicateKeyInBatchError extends QueryCollectionError {
55
+ constructor(key) {
56
+ super(`Duplicate key '${key}' found within batch operations`);
57
+ this.name = `DuplicateKeyInBatchError`;
58
+ }
59
+ }
60
+ class UpdateOperationItemNotFoundError extends QueryCollectionError {
61
+ constructor(key) {
62
+ super(`Update operation: Item with key '${key}' does not exist`);
63
+ this.name = `UpdateOperationItemNotFoundError`;
64
+ }
65
+ }
66
+ class DeleteOperationItemNotFoundError extends QueryCollectionError {
67
+ constructor(key) {
68
+ super(`Delete operation: Item with key '${key}' does not exist`);
69
+ this.name = `DeleteOperationItemNotFoundError`;
70
+ }
71
+ }
72
+ class InvalidSyncOperationError extends QueryCollectionError {
73
+ constructor(message) {
74
+ super(`Invalid sync operation: ${message}`);
75
+ this.name = `InvalidSyncOperationError`;
76
+ }
77
+ }
78
+ class UnknownOperationTypeError extends QueryCollectionError {
79
+ constructor(type) {
80
+ super(`Unknown operation type: ${type}`);
81
+ this.name = `UnknownOperationTypeError`;
82
+ }
83
+ }
84
+ class MissingKeyFieldError extends QueryCollectionError {
85
+ constructor(operation, message) {
86
+ super(`${operation} item must contain the key field: ${message}`);
87
+ this.name = `MissingKeyFieldError`;
88
+ }
89
+ }
90
+ exports.DeleteOperationItemNotFoundError = DeleteOperationItemNotFoundError;
91
+ exports.DuplicateKeyInBatchError = DuplicateKeyInBatchError;
34
92
  exports.GetKeyRequiredError = GetKeyRequiredError;
93
+ exports.InvalidItemStructureError = InvalidItemStructureError;
94
+ exports.InvalidSyncOperationError = InvalidSyncOperationError;
95
+ exports.ItemNotFoundError = ItemNotFoundError;
96
+ exports.MissingKeyFieldError = MissingKeyFieldError;
35
97
  exports.QueryClientRequiredError = QueryClientRequiredError;
36
98
  exports.QueryCollectionError = QueryCollectionError;
37
99
  exports.QueryFnRequiredError = QueryFnRequiredError;
38
100
  exports.QueryKeyRequiredError = QueryKeyRequiredError;
101
+ exports.SyncNotInitializedError = SyncNotInitializedError;
102
+ exports.UnknownOperationTypeError = UnknownOperationTypeError;
103
+ exports.UpdateOperationItemNotFoundError = UpdateOperationItemNotFoundError;
39
104
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","sources":["../../src/errors.ts"],"sourcesContent":["import { TanStackDBError } from \"@tanstack/db\"\n\n// Query Collection Errors\nexport class QueryCollectionError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryCollectionError`\n }\n}\n\nexport class QueryKeyRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] queryKey must be provided.`)\n this.name = `QueryKeyRequiredError`\n }\n}\n\nexport class QueryFnRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] queryFn must be provided.`)\n this.name = `QueryFnRequiredError`\n }\n}\n\nexport class QueryClientRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] queryClient must be provided.`)\n this.name = `QueryClientRequiredError`\n }\n}\n\nexport class GetKeyRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] getKey must be provided.`)\n this.name = `GetKeyRequiredError`\n }\n}\n"],"names":["TanStackDBError"],"mappings":";;;AAGO,MAAM,6BAA6BA,GAAAA,gBAAgB;AAAA,EACxD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,8BAA8B,qBAAqB;AAAA,EAC9D,cAAc;AACZ,UAAM,8CAA8C;AACpD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,6BAA6B,qBAAqB;AAAA,EAC7D,cAAc;AACZ,UAAM,6CAA6C;AACnD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,iCAAiC,qBAAqB;AAAA,EACjE,cAAc;AACZ,UAAM,iDAAiD;AACvD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,4BAA4B,qBAAqB;AAAA,EAC5D,cAAc;AACZ,UAAM,4CAA4C;AAClD,SAAK,OAAO;AAAA,EACd;AACF;;;;;;"}
1
+ {"version":3,"file":"errors.cjs","sources":["../../src/errors.ts"],"sourcesContent":["import { TanStackDBError } from \"@tanstack/db\"\n\n// Query Collection Errors\nexport class QueryCollectionError extends TanStackDBError {\n constructor(message: string) {\n super(message)\n this.name = `QueryCollectionError`\n }\n}\n\nexport class QueryKeyRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] queryKey must be provided.`)\n this.name = `QueryKeyRequiredError`\n }\n}\n\nexport class QueryFnRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] queryFn must be provided.`)\n this.name = `QueryFnRequiredError`\n }\n}\n\nexport class QueryClientRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] queryClient must be provided.`)\n this.name = `QueryClientRequiredError`\n }\n}\n\nexport class GetKeyRequiredError extends QueryCollectionError {\n constructor() {\n super(`[QueryCollection] getKey must be provided.`)\n this.name = `GetKeyRequiredError`\n }\n}\n\nexport class SyncNotInitializedError extends QueryCollectionError {\n constructor() {\n super(\n `Collection must be in 'ready' state for manual sync operations. Sync not initialized yet.`\n )\n this.name = `SyncNotInitializedError`\n }\n}\n\nexport class InvalidItemStructureError extends QueryCollectionError {\n constructor(message: string) {\n super(`Invalid item structure: ${message}`)\n this.name = `InvalidItemStructureError`\n }\n}\n\nexport class ItemNotFoundError extends QueryCollectionError {\n constructor(key: string | number) {\n super(`Item with key '${key}' does not exist.`)\n this.name = `ItemNotFoundError`\n }\n}\n\nexport class DuplicateKeyInBatchError extends QueryCollectionError {\n constructor(key: string | number) {\n super(`Duplicate key '${key}' found within batch operations`)\n this.name = `DuplicateKeyInBatchError`\n }\n}\n\nexport class UpdateOperationItemNotFoundError extends QueryCollectionError {\n constructor(key: string | number) {\n super(`Update operation: Item with key '${key}' does not exist`)\n this.name = `UpdateOperationItemNotFoundError`\n }\n}\n\nexport class DeleteOperationItemNotFoundError extends QueryCollectionError {\n constructor(key: string | number) {\n super(`Delete operation: Item with key '${key}' does not exist`)\n this.name = `DeleteOperationItemNotFoundError`\n }\n}\n\nexport class InvalidSyncOperationError extends QueryCollectionError {\n constructor(message: string) {\n super(`Invalid sync operation: ${message}`)\n this.name = `InvalidSyncOperationError`\n }\n}\n\nexport class UnknownOperationTypeError extends QueryCollectionError {\n constructor(type: string) {\n super(`Unknown operation type: ${type}`)\n this.name = `UnknownOperationTypeError`\n }\n}\n\nexport class MissingKeyFieldError extends QueryCollectionError {\n constructor(operation: string, message: string) {\n super(`${operation} item must contain the key field: ${message}`)\n this.name = `MissingKeyFieldError`\n }\n}\n"],"names":["TanStackDBError"],"mappings":";;;AAGO,MAAM,6BAA6BA,GAAAA,gBAAgB;AAAA,EACxD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,8BAA8B,qBAAqB;AAAA,EAC9D,cAAc;AACZ,UAAM,8CAA8C;AACpD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,6BAA6B,qBAAqB;AAAA,EAC7D,cAAc;AACZ,UAAM,6CAA6C;AACnD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,iCAAiC,qBAAqB;AAAA,EACjE,cAAc;AACZ,UAAM,iDAAiD;AACvD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,4BAA4B,qBAAqB;AAAA,EAC5D,cAAc;AACZ,UAAM,4CAA4C;AAClD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,gCAAgC,qBAAqB;AAAA,EAChE,cAAc;AACZ;AAAA,MACE;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,kCAAkC,qBAAqB;AAAA,EAClE,YAAY,SAAiB;AAC3B,UAAM,2BAA2B,OAAO,EAAE;AAC1C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,0BAA0B,qBAAqB;AAAA,EAC1D,YAAY,KAAsB;AAChC,UAAM,kBAAkB,GAAG,mBAAmB;AAC9C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,iCAAiC,qBAAqB;AAAA,EACjE,YAAY,KAAsB;AAChC,UAAM,kBAAkB,GAAG,iCAAiC;AAC5D,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,yCAAyC,qBAAqB;AAAA,EACzE,YAAY,KAAsB;AAChC,UAAM,oCAAoC,GAAG,kBAAkB;AAC/D,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,yCAAyC,qBAAqB;AAAA,EACzE,YAAY,KAAsB;AAChC,UAAM,oCAAoC,GAAG,kBAAkB;AAC/D,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,kCAAkC,qBAAqB;AAAA,EAClE,YAAY,SAAiB;AAC3B,UAAM,2BAA2B,OAAO,EAAE;AAC1C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,kCAAkC,qBAAqB;AAAA,EAClE,YAAY,MAAc;AACxB,UAAM,2BAA2B,IAAI,EAAE;AACvC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,6BAA6B,qBAAqB;AAAA,EAC7D,YAAY,WAAmB,SAAiB;AAC9C,UAAM,GAAG,SAAS,qCAAqC,OAAO,EAAE;AAChE,SAAK,OAAO;AAAA,EACd;AACF;;;;;;;;;;;;;;;"}
@@ -14,3 +14,30 @@ export declare class QueryClientRequiredError extends QueryCollectionError {
14
14
  export declare class GetKeyRequiredError extends QueryCollectionError {
15
15
  constructor();
16
16
  }
17
+ export declare class SyncNotInitializedError extends QueryCollectionError {
18
+ constructor();
19
+ }
20
+ export declare class InvalidItemStructureError extends QueryCollectionError {
21
+ constructor(message: string);
22
+ }
23
+ export declare class ItemNotFoundError extends QueryCollectionError {
24
+ constructor(key: string | number);
25
+ }
26
+ export declare class DuplicateKeyInBatchError extends QueryCollectionError {
27
+ constructor(key: string | number);
28
+ }
29
+ export declare class UpdateOperationItemNotFoundError extends QueryCollectionError {
30
+ constructor(key: string | number);
31
+ }
32
+ export declare class DeleteOperationItemNotFoundError extends QueryCollectionError {
33
+ constructor(key: string | number);
34
+ }
35
+ export declare class InvalidSyncOperationError extends QueryCollectionError {
36
+ constructor(message: string);
37
+ }
38
+ export declare class UnknownOperationTypeError extends QueryCollectionError {
39
+ constructor(type: string);
40
+ }
41
+ export declare class MissingKeyFieldError extends QueryCollectionError {
42
+ constructor(operation: string, message: string);
43
+ }
@@ -3,9 +3,18 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const query = require("./query.cjs");
4
4
  const errors = require("./errors.cjs");
5
5
  exports.queryCollectionOptions = query.queryCollectionOptions;
6
+ exports.DeleteOperationItemNotFoundError = errors.DeleteOperationItemNotFoundError;
7
+ exports.DuplicateKeyInBatchError = errors.DuplicateKeyInBatchError;
6
8
  exports.GetKeyRequiredError = errors.GetKeyRequiredError;
9
+ exports.InvalidItemStructureError = errors.InvalidItemStructureError;
10
+ exports.InvalidSyncOperationError = errors.InvalidSyncOperationError;
11
+ exports.ItemNotFoundError = errors.ItemNotFoundError;
12
+ exports.MissingKeyFieldError = errors.MissingKeyFieldError;
7
13
  exports.QueryClientRequiredError = errors.QueryClientRequiredError;
8
14
  exports.QueryCollectionError = errors.QueryCollectionError;
9
15
  exports.QueryFnRequiredError = errors.QueryFnRequiredError;
10
16
  exports.QueryKeyRequiredError = errors.QueryKeyRequiredError;
17
+ exports.SyncNotInitializedError = errors.SyncNotInitializedError;
18
+ exports.UnknownOperationTypeError = errors.UnknownOperationTypeError;
19
+ exports.UpdateOperationItemNotFoundError = errors.UpdateOperationItemNotFoundError;
11
20
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
@@ -1,2 +1,2 @@
1
- export { queryCollectionOptions, type QueryCollectionConfig, type QueryCollectionUtils, } from './query.cjs';
1
+ export { queryCollectionOptions, type QueryCollectionConfig, type QueryCollectionUtils, type SyncOperation, } from './query.cjs';
2
2
  export * from './errors.cjs';
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const errors = require("./errors.cjs");
4
+ function normalizeOperations(ops, ctx) {
5
+ const operations = Array.isArray(ops) ? ops : [ops];
6
+ const normalized = [];
7
+ for (const op of operations) {
8
+ if (op.type === `delete`) {
9
+ const keys = Array.isArray(op.key) ? op.key : [op.key];
10
+ for (const key of keys) {
11
+ normalized.push({ type: `delete`, key });
12
+ }
13
+ } else {
14
+ const items = Array.isArray(op.data) ? op.data : [op.data];
15
+ for (const item of items) {
16
+ let key;
17
+ if (op.type === `update`) {
18
+ key = ctx.getKey(item);
19
+ } else {
20
+ const resolved = ctx.collection.validateData(
21
+ item,
22
+ op.type === `upsert` ? `insert` : op.type
23
+ );
24
+ key = ctx.getKey(resolved);
25
+ }
26
+ normalized.push({ type: op.type, key, data: item });
27
+ }
28
+ }
29
+ }
30
+ return normalized;
31
+ }
32
+ function validateOperations(operations, ctx) {
33
+ const seenKeys = /* @__PURE__ */ new Set();
34
+ for (const op of operations) {
35
+ if (seenKeys.has(op.key)) {
36
+ throw new errors.DuplicateKeyInBatchError(op.key);
37
+ }
38
+ seenKeys.add(op.key);
39
+ if (op.type === `update`) {
40
+ if (!ctx.collection.has(op.key)) {
41
+ throw new errors.UpdateOperationItemNotFoundError(op.key);
42
+ }
43
+ } else if (op.type === `delete`) {
44
+ if (!ctx.collection.has(op.key)) {
45
+ throw new errors.DeleteOperationItemNotFoundError(op.key);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ function performWriteOperations(operations, ctx) {
51
+ const normalized = normalizeOperations(operations, ctx);
52
+ validateOperations(normalized, ctx);
53
+ ctx.begin();
54
+ for (const op of normalized) {
55
+ switch (op.type) {
56
+ case `insert`: {
57
+ const resolved = ctx.collection.validateData(op.data, `insert`);
58
+ ctx.write({
59
+ type: `insert`,
60
+ value: resolved
61
+ });
62
+ break;
63
+ }
64
+ case `update`: {
65
+ const currentItem = ctx.collection.get(op.key);
66
+ const updatedItem = {
67
+ ...currentItem,
68
+ ...op.data
69
+ };
70
+ const resolved = ctx.collection.validateData(
71
+ updatedItem,
72
+ `update`,
73
+ op.key
74
+ );
75
+ ctx.write({
76
+ type: `update`,
77
+ value: resolved
78
+ });
79
+ break;
80
+ }
81
+ case `delete`: {
82
+ const currentItem = ctx.collection.get(op.key);
83
+ ctx.write({
84
+ type: `delete`,
85
+ value: currentItem
86
+ });
87
+ break;
88
+ }
89
+ case `upsert`: {
90
+ const resolved = ctx.collection.validateData(
91
+ op.data,
92
+ ctx.collection.has(op.key) ? `update` : `insert`,
93
+ op.key
94
+ );
95
+ if (ctx.collection.has(op.key)) {
96
+ ctx.write({
97
+ type: `update`,
98
+ value: resolved
99
+ });
100
+ } else {
101
+ ctx.write({
102
+ type: `insert`,
103
+ value: resolved
104
+ });
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ ctx.commit();
111
+ const updatedData = ctx.collection.toArray;
112
+ ctx.queryClient.setQueryData(ctx.queryKey, updatedData);
113
+ }
114
+ function createWriteUtils(getContext) {
115
+ function ensureContext() {
116
+ const context = getContext();
117
+ if (!context) {
118
+ throw new errors.SyncNotInitializedError();
119
+ }
120
+ return context;
121
+ }
122
+ return {
123
+ writeInsert(data) {
124
+ const ctx = ensureContext();
125
+ performWriteOperations({ type: `insert`, data }, ctx);
126
+ },
127
+ writeUpdate(data) {
128
+ const ctx = ensureContext();
129
+ performWriteOperations({ type: `update`, data }, ctx);
130
+ },
131
+ writeDelete(key) {
132
+ const ctx = ensureContext();
133
+ performWriteOperations({ type: `delete`, key }, ctx);
134
+ },
135
+ writeUpsert(data) {
136
+ const ctx = ensureContext();
137
+ performWriteOperations({ type: `upsert`, data }, ctx);
138
+ },
139
+ writeBatch(operations) {
140
+ const ctx = ensureContext();
141
+ performWriteOperations(operations, ctx);
142
+ }
143
+ };
144
+ }
145
+ exports.createWriteUtils = createWriteUtils;
146
+ exports.performWriteOperations = performWriteOperations;
147
+ //# sourceMappingURL=manual-sync.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-sync.cjs","sources":["../../src/manual-sync.ts"],"sourcesContent":["import {\n DeleteOperationItemNotFoundError,\n DuplicateKeyInBatchError,\n SyncNotInitializedError,\n UpdateOperationItemNotFoundError,\n} from \"./errors\"\nimport type { QueryClient } from \"@tanstack/query-core\"\nimport type { ChangeMessage, Collection } from \"@tanstack/db\"\n\n// Types for sync operations\nexport type SyncOperation<\n TRow extends object,\n TKey extends string | number = string | number,\n TInsertInput extends object = TRow,\n> =\n | { type: `insert`; data: TInsertInput | Array<TInsertInput> }\n | { type: `update`; data: Partial<TRow> | Array<Partial<TRow>> }\n | { type: `delete`; key: TKey | Array<TKey> }\n | { type: `upsert`; data: Partial<TRow> | Array<Partial<TRow>> }\n\nexport interface SyncContext<\n TRow extends object,\n TKey extends string | number = string | number,\n> {\n collection: Collection<TRow>\n queryClient: QueryClient\n queryKey: Array<unknown>\n getKey: (item: TRow) => TKey\n begin: () => void\n write: (message: Omit<ChangeMessage<TRow>, `key`>) => void\n commit: () => void\n}\n\ninterface NormalizedOperation<\n TRow extends object,\n TKey extends string | number = string | number,\n> {\n type: `insert` | `update` | `delete` | `upsert`\n key: TKey\n data?: TRow | Partial<TRow>\n}\n\n// Normalize operations into a consistent format\nfunction normalizeOperations<\n TRow extends object,\n TKey extends string | number = string | number,\n TInsertInput extends object = TRow,\n>(\n ops:\n | SyncOperation<TRow, TKey, TInsertInput>\n | Array<SyncOperation<TRow, TKey, TInsertInput>>,\n ctx: SyncContext<TRow, TKey>\n): Array<NormalizedOperation<TRow, TKey>> {\n const operations = Array.isArray(ops) ? ops : [ops]\n const normalized: Array<NormalizedOperation<TRow, TKey>> = []\n\n for (const op of operations) {\n if (op.type === `delete`) {\n const keys = Array.isArray(op.key) ? op.key : [op.key]\n for (const key of keys) {\n normalized.push({ type: `delete`, key })\n }\n } else {\n const items = Array.isArray(op.data) ? op.data : [op.data]\n for (const item of items) {\n let key: TKey\n if (op.type === `update`) {\n // For updates, we need to get the key from the partial data\n key = ctx.getKey(item as TRow)\n } else {\n // For insert/upsert, validate and resolve the full item first\n const resolved = ctx.collection.validateData(\n item,\n op.type === `upsert` ? `insert` : op.type\n )\n key = ctx.getKey(resolved)\n }\n normalized.push({ type: op.type, key, data: item })\n }\n }\n }\n\n return normalized\n}\n\n// Validate operations before executing\nfunction validateOperations<\n TRow extends object,\n TKey extends string | number = string | number,\n>(\n operations: Array<NormalizedOperation<TRow, TKey>>,\n ctx: SyncContext<TRow, TKey>\n): void {\n const seenKeys = new Set<TKey>()\n\n for (const op of operations) {\n // Check for duplicate keys within the batch\n if (seenKeys.has(op.key)) {\n throw new DuplicateKeyInBatchError(op.key)\n }\n seenKeys.add(op.key)\n\n // Validate operation-specific requirements\n if (op.type === `update`) {\n if (!ctx.collection.has(op.key)) {\n throw new UpdateOperationItemNotFoundError(op.key)\n }\n } else if (op.type === `delete`) {\n if (!ctx.collection.has(op.key)) {\n throw new DeleteOperationItemNotFoundError(op.key)\n }\n }\n }\n}\n\n// Execute a batch of operations\nexport function performWriteOperations<\n TRow extends object,\n TKey extends string | number = string | number,\n TInsertInput extends object = TRow,\n>(\n operations:\n | SyncOperation<TRow, TKey, TInsertInput>\n | Array<SyncOperation<TRow, TKey, TInsertInput>>,\n ctx: SyncContext<TRow, TKey>\n): void {\n const normalized = normalizeOperations(operations, ctx)\n validateOperations(normalized, ctx)\n\n ctx.begin()\n\n for (const op of normalized) {\n switch (op.type) {\n case `insert`: {\n const resolved = ctx.collection.validateData(op.data, `insert`)\n ctx.write({\n type: `insert`,\n value: resolved,\n })\n break\n }\n case `update`: {\n const currentItem = ctx.collection.get(op.key)!\n const updatedItem = {\n ...currentItem,\n ...op.data,\n }\n const resolved = ctx.collection.validateData(\n updatedItem,\n `update`,\n op.key\n )\n ctx.write({\n type: `update`,\n value: resolved,\n })\n break\n }\n case `delete`: {\n const currentItem = ctx.collection.get(op.key)!\n ctx.write({\n type: `delete`,\n value: currentItem,\n })\n break\n }\n case `upsert`: {\n const resolved = ctx.collection.validateData(\n op.data,\n ctx.collection.has(op.key) ? `update` : `insert`,\n op.key\n )\n if (ctx.collection.has(op.key)) {\n ctx.write({\n type: `update`,\n value: resolved,\n })\n } else {\n ctx.write({\n type: `insert`,\n value: resolved,\n })\n }\n break\n }\n }\n }\n\n ctx.commit()\n\n // Update query cache after successful commit\n const updatedData = ctx.collection.toArray\n ctx.queryClient.setQueryData(ctx.queryKey, updatedData)\n}\n\n// Factory function to create write utils\nexport function createWriteUtils<\n TRow extends object,\n TKey extends string | number = string | number,\n TInsertInput extends object = TRow,\n>(getContext: () => SyncContext<TRow, TKey> | null) {\n function ensureContext(): SyncContext<TRow, TKey> {\n const context = getContext()\n if (!context) {\n throw new SyncNotInitializedError()\n }\n return context\n }\n\n return {\n writeInsert(data: TInsertInput | Array<TInsertInput>) {\n const ctx = ensureContext()\n performWriteOperations({ type: `insert`, data }, ctx)\n },\n\n writeUpdate(data: Partial<TRow> | Array<Partial<TRow>>) {\n const ctx = ensureContext()\n performWriteOperations({ type: `update`, data }, ctx)\n },\n\n writeDelete(key: TKey | Array<TKey>) {\n const ctx = ensureContext()\n performWriteOperations({ type: `delete`, key }, ctx)\n },\n\n writeUpsert(data: Partial<TRow> | Array<Partial<TRow>>) {\n const ctx = ensureContext()\n performWriteOperations({ type: `upsert`, data }, ctx)\n },\n\n writeBatch(operations: Array<SyncOperation<TRow, TKey, TInsertInput>>) {\n const ctx = ensureContext()\n performWriteOperations(operations, ctx)\n },\n }\n}\n"],"names":["DuplicateKeyInBatchError","UpdateOperationItemNotFoundError","DeleteOperationItemNotFoundError","SyncNotInitializedError"],"mappings":";;;AA2CA,SAAS,oBAKP,KAGA,KACwC;AACxC,QAAM,aAAa,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;AAClD,QAAM,aAAqD,CAAA;AAE3D,aAAW,MAAM,YAAY;AAC3B,QAAI,GAAG,SAAS,UAAU;AACxB,YAAM,OAAO,MAAM,QAAQ,GAAG,GAAG,IAAI,GAAG,MAAM,CAAC,GAAG,GAAG;AACrD,iBAAW,OAAO,MAAM;AACtB,mBAAW,KAAK,EAAE,MAAM,UAAU,KAAK;AAAA,MACzC;AAAA,IACF,OAAO;AACL,YAAM,QAAQ,MAAM,QAAQ,GAAG,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI;AACzD,iBAAW,QAAQ,OAAO;AACxB,YAAI;AACJ,YAAI,GAAG,SAAS,UAAU;AAExB,gBAAM,IAAI,OAAO,IAAY;AAAA,QAC/B,OAAO;AAEL,gBAAM,WAAW,IAAI,WAAW;AAAA,YAC9B;AAAA,YACA,GAAG,SAAS,WAAW,WAAW,GAAG;AAAA,UAAA;AAEvC,gBAAM,IAAI,OAAO,QAAQ;AAAA,QAC3B;AACA,mBAAW,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,MAAM;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,mBAIP,YACA,KACM;AACN,QAAM,+BAAe,IAAA;AAErB,aAAW,MAAM,YAAY;AAE3B,QAAI,SAAS,IAAI,GAAG,GAAG,GAAG;AACxB,YAAM,IAAIA,OAAAA,yBAAyB,GAAG,GAAG;AAAA,IAC3C;AACA,aAAS,IAAI,GAAG,GAAG;AAGnB,QAAI,GAAG,SAAS,UAAU;AACxB,UAAI,CAAC,IAAI,WAAW,IAAI,GAAG,GAAG,GAAG;AAC/B,cAAM,IAAIC,OAAAA,iCAAiC,GAAG,GAAG;AAAA,MACnD;AAAA,IACF,WAAW,GAAG,SAAS,UAAU;AAC/B,UAAI,CAAC,IAAI,WAAW,IAAI,GAAG,GAAG,GAAG;AAC/B,cAAM,IAAIC,OAAAA,iCAAiC,GAAG,GAAG;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,uBAKd,YAGA,KACM;AACN,QAAM,aAAa,oBAAoB,YAAY,GAAG;AACtD,qBAAmB,YAAY,GAAG;AAElC,MAAI,MAAA;AAEJ,aAAW,MAAM,YAAY;AAC3B,YAAQ,GAAG,MAAA;AAAA,MACT,KAAK,UAAU;AACb,cAAM,WAAW,IAAI,WAAW,aAAa,GAAG,MAAM,QAAQ;AAC9D,YAAI,MAAM;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AACD;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM,cAAc,IAAI,WAAW,IAAI,GAAG,GAAG;AAC7C,cAAM,cAAc;AAAA,UAClB,GAAG;AAAA,UACH,GAAG,GAAG;AAAA,QAAA;AAER,cAAM,WAAW,IAAI,WAAW;AAAA,UAC9B;AAAA,UACA;AAAA,UACA,GAAG;AAAA,QAAA;AAEL,YAAI,MAAM;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AACD;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM,cAAc,IAAI,WAAW,IAAI,GAAG,GAAG;AAC7C,YAAI,MAAM;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AACD;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM,WAAW,IAAI,WAAW;AAAA,UAC9B,GAAG;AAAA,UACH,IAAI,WAAW,IAAI,GAAG,GAAG,IAAI,WAAW;AAAA,UACxC,GAAG;AAAA,QAAA;AAEL,YAAI,IAAI,WAAW,IAAI,GAAG,GAAG,GAAG;AAC9B,cAAI,MAAM;AAAA,YACR,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,OAAO;AACL,cAAI,MAAM;AAAA,YACR,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH;AACA;AAAA,MACF;AAAA,IAAA;AAAA,EAEJ;AAEA,MAAI,OAAA;AAGJ,QAAM,cAAc,IAAI,WAAW;AACnC,MAAI,YAAY,aAAa,IAAI,UAAU,WAAW;AACxD;AAGO,SAAS,iBAId,YAAkD;AAClD,WAAS,gBAAyC;AAChD,UAAM,UAAU,WAAA;AAChB,QAAI,CAAC,SAAS;AACZ,YAAM,IAAIC,OAAAA,wBAAA;AAAA,IACZ;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,YAAY,MAA0C;AACpD,YAAM,MAAM,cAAA;AACZ,6BAAuB,EAAE,MAAM,UAAU,KAAA,GAAQ,GAAG;AAAA,IACtD;AAAA,IAEA,YAAY,MAA4C;AACtD,YAAM,MAAM,cAAA;AACZ,6BAAuB,EAAE,MAAM,UAAU,KAAA,GAAQ,GAAG;AAAA,IACtD;AAAA,IAEA,YAAY,KAAyB;AACnC,YAAM,MAAM,cAAA;AACZ,6BAAuB,EAAE,MAAM,UAAU,IAAA,GAAO,GAAG;AAAA,IACrD;AAAA,IAEA,YAAY,MAA4C;AACtD,YAAM,MAAM,cAAA;AACZ,6BAAuB,EAAE,MAAM,UAAU,KAAA,GAAQ,GAAG;AAAA,IACtD;AAAA,IAEA,WAAW,YAA4D;AACrE,YAAM,MAAM,cAAA;AACZ,6BAAuB,YAAY,GAAG;AAAA,IACxC;AAAA,EAAA;AAEJ;;;"}
@@ -0,0 +1,32 @@
1
+ import { QueryClient } from '@tanstack/query-core';
2
+ import { ChangeMessage, Collection } from '@tanstack/db';
3
+ export type SyncOperation<TRow extends object, TKey extends string | number = string | number, TInsertInput extends object = TRow> = {
4
+ type: `insert`;
5
+ data: TInsertInput | Array<TInsertInput>;
6
+ } | {
7
+ type: `update`;
8
+ data: Partial<TRow> | Array<Partial<TRow>>;
9
+ } | {
10
+ type: `delete`;
11
+ key: TKey | Array<TKey>;
12
+ } | {
13
+ type: `upsert`;
14
+ data: Partial<TRow> | Array<Partial<TRow>>;
15
+ };
16
+ export interface SyncContext<TRow extends object, TKey extends string | number = string | number> {
17
+ collection: Collection<TRow>;
18
+ queryClient: QueryClient;
19
+ queryKey: Array<unknown>;
20
+ getKey: (item: TRow) => TKey;
21
+ begin: () => void;
22
+ write: (message: Omit<ChangeMessage<TRow>, `key`>) => void;
23
+ commit: () => void;
24
+ }
25
+ export declare function performWriteOperations<TRow extends object, TKey extends string | number = string | number, TInsertInput extends object = TRow>(operations: SyncOperation<TRow, TKey, TInsertInput> | Array<SyncOperation<TRow, TKey, TInsertInput>>, ctx: SyncContext<TRow, TKey>): void;
26
+ export declare function createWriteUtils<TRow extends object, TKey extends string | number = string | number, TInsertInput extends object = TRow>(getContext: () => SyncContext<TRow, TKey> | null): {
27
+ writeInsert(data: TInsertInput | Array<TInsertInput>): void;
28
+ writeUpdate(data: Partial<TRow> | Array<Partial<TRow>>): void;
29
+ writeDelete(key: TKey | Array<TKey>): void;
30
+ writeUpsert(data: Partial<TRow> | Array<Partial<TRow>>): void;
31
+ writeBatch(operations: Array<SyncOperation<TRow, TKey, TInsertInput>>): void;
32
+ };
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const queryCore = require("@tanstack/query-core");
4
4
  const errors = require("./errors.cjs");
5
+ const manualSync = require("./manual-sync.cjs");
5
6
  function queryCollectionOptions(config) {
6
7
  const {
7
8
  queryKey,
@@ -110,6 +111,23 @@ function queryCollectionOptions(config) {
110
111
  queryKey
111
112
  });
112
113
  };
114
+ let writeContext = null;
115
+ const enhancedInternalSync = (params) => {
116
+ const { begin, write, commit, collection } = params;
117
+ writeContext = {
118
+ collection,
119
+ queryClient,
120
+ queryKey,
121
+ getKey,
122
+ begin,
123
+ write,
124
+ commit
125
+ };
126
+ return internalSync(params);
127
+ };
128
+ const writeUtils = manualSync.createWriteUtils(
129
+ () => writeContext
130
+ );
113
131
  const wrappedOnInsert = onInsert ? async (params) => {
114
132
  const handlerResult = await onInsert(params) ?? {};
115
133
  const shouldRefetch = handlerResult.refetch !== false;
@@ -137,12 +155,13 @@ function queryCollectionOptions(config) {
137
155
  return {
138
156
  ...baseCollectionConfig,
139
157
  getKey,
140
- sync: { sync: internalSync },
158
+ sync: { sync: enhancedInternalSync },
141
159
  onInsert: wrappedOnInsert,
142
160
  onUpdate: wrappedOnUpdate,
143
161
  onDelete: wrappedOnDelete,
144
162
  utils: {
145
- refetch
163
+ refetch,
164
+ ...writeUtils
146
165
  }
147
166
  };
148
167
  }
@@ -1 +1 @@
1
- {"version":3,"file":"query.cjs","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver } from \"@tanstack/query-core\"\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from \"./errors\"\nimport type {\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n} from \"@tanstack/query-core\"\nimport type {\n CollectionConfig,\n DeleteMutationFn,\n DeleteMutationFnParams,\n InsertMutationFn,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFn,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"@tanstack/db\"\n\nexport interface QueryCollectionConfig<\n TItem extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n> {\n queryKey: TQueryKey\n queryFn: (context: QueryFunctionContext<TQueryKey>) => Promise<Array<TItem>>\n queryClient: QueryClient\n\n // Query-specific options\n enabled?: boolean\n refetchInterval?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`staleTime`]\n\n // Standard Collection configuration properties\n id?: string\n getKey: CollectionConfig<TItem>[`getKey`]\n schema?: CollectionConfig<TItem>[`schema`]\n sync?: CollectionConfig<TItem>[`sync`]\n startSync?: CollectionConfig<TItem>[`startSync`]\n\n // Direct persistence handlers\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection insert handler\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * // Automatically refetches query after insert\n * }\n *\n * @example\n * // Insert handler with refetch control\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Insert handler with multiple items\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * await api.createTodos(items)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Insert handler with error handling\n * onInsert: async ({ transaction }) => {\n * try {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * } catch (error) {\n * console.error('Insert failed:', error)\n * throw error // Transaction will rollback optimistic changes\n * }\n * }\n */\n onInsert?: InsertMutationFn<TItem>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection update handler\n * onUpdate: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n * // Automatically refetches query after update\n * }\n *\n * @example\n * // Update handler with multiple items\n * onUpdate: async ({ transaction }) => {\n * const updates = transaction.mutations.map(m => ({\n * id: m.key,\n * changes: m.changes\n * }))\n * await api.updateTodos(updates)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Update handler with manual refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Manually trigger refetch\n * await collection.utils.refetch()\n *\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Update handler with related collection refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Refetch related collections when this item changes\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * tagsCollection.utils.refetch() // Refetch tags\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onUpdate?: UpdateMutationFn<TItem>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection delete handler\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * // Automatically refetches query after delete\n * }\n *\n * @example\n * // Delete handler with refetch control\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Delete handler with multiple items\n * onDelete: async ({ transaction }) => {\n * const keysToDelete = transaction.mutations.map(m => m.key)\n * await api.deleteTodos(keysToDelete)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Delete handler with related collection refetch\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n *\n * // Refetch related collections when this item is deleted\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * projectsCollection.utils.refetch() // Refetch projects\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onDelete?: DeleteMutationFn<TItem>\n // TODO type returning { refetch: boolean }\n}\n\n/**\n * Type for the refetch utility function\n */\nexport type RefetchFn = () => Promise<void>\n\n/**\n * Query collection utilities type\n */\nexport interface QueryCollectionUtils extends UtilsRecord {\n refetch: RefetchFn\n}\n\n/**\n * Creates query collection options for use with a standard Collection\n *\n * @param config - Configuration options for the Query collection\n * @returns Collection options with utilities\n */\nexport function queryCollectionOptions<\n TItem extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n>(\n config: QueryCollectionConfig<TItem, TError, TQueryKey>\n): CollectionConfig<TItem> & { utils: QueryCollectionUtils } {\n const {\n queryKey,\n queryFn,\n queryClient,\n enabled,\n refetchInterval,\n retry,\n retryDelay,\n staleTime,\n getKey,\n onInsert,\n onUpdate,\n onDelete,\n ...baseCollectionConfig\n } = config\n\n // Validate required parameters\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryKey) {\n throw new QueryKeyRequiredError()\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryFn) {\n throw new QueryFnRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryClient) {\n throw new QueryClientRequiredError()\n }\n\n if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n const internalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n const observerOptions: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n > = {\n queryKey: queryKey,\n queryFn: queryFn,\n enabled: enabled,\n refetchInterval: refetchInterval,\n retry: retry,\n retryDelay: retryDelay,\n staleTime: staleTime,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n }\n\n const localObserver = new QueryObserver<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >(queryClient, observerOptions)\n\n const actualUnsubscribeFn = localObserver.subscribe((result) => {\n if (result.isSuccess) {\n const newItemsArray = result.data\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n console.error(\n `[QueryCollection] queryFn did not return an array of objects. Skipping update.`,\n newItemsArray\n )\n return\n }\n\n const currentSyncedItems = new Map(collection.syncedData)\n const newItemsMap = new Map<string | number, TItem>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n // Helper function for shallow equality check of objects\n const shallowEqual = (\n obj1: Record<string, any>,\n obj2: Record<string, any>\n ): boolean => {\n // Get all keys from both objects\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n\n // If number of keys is different, objects are not equal\n if (keys1.length !== keys2.length) return false\n\n // Check if all keys in obj1 have the same values in obj2\n return keys1.every((key) => {\n // Skip comparing functions and complex objects deeply\n if (typeof obj1[key] === `function`) return true\n if (typeof obj1[key] === `object` && obj1[key] !== null) {\n // For nested objects, just compare references\n // A more robust solution might do recursive shallow comparison\n // or let users provide a custom equality function\n return obj1[key] === obj2[key]\n }\n return obj1[key] === obj2[key]\n })\n }\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n write({ type: `delete`, value: oldItem })\n } else if (\n !shallowEqual(\n oldItem as Record<string, any>,\n newItem as Record<string, any>\n )\n ) {\n // Only update if there are actual differences in the properties\n write({ type: `update`, value: newItem })\n }\n })\n\n newItemsMap.forEach((newItem, key) => {\n if (!currentSyncedItems.has(key)) {\n write({ type: `insert`, value: newItem })\n }\n })\n\n commit()\n\n // Mark collection as ready after first successful query result\n markReady()\n } else if (result.isError) {\n console.error(\n `[QueryCollection] Error observing query ${String(queryKey)}:`,\n result.error\n )\n\n // Mark collection as ready even on error to avoid blocking apps\n markReady()\n }\n })\n\n return async () => {\n actualUnsubscribeFn()\n await queryClient.cancelQueries({ queryKey })\n queryClient.removeQueries({ queryKey })\n }\n }\n\n /**\n * Refetch the query data\n * @returns Promise that resolves when the refetch is complete\n */\n const refetch: RefetchFn = async (): Promise<void> => {\n return queryClient.refetchQueries({\n queryKey: queryKey,\n })\n }\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<TItem>) => {\n const handlerResult = (await onInsert(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = onUpdate\n ? async (params: UpdateMutationFnParams<TItem>) => {\n const handlerResult = (await onUpdate(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = onDelete\n ? async (params: DeleteMutationFnParams<TItem>) => {\n const handlerResult = (await onDelete(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n return {\n ...baseCollectionConfig,\n getKey,\n sync: { sync: internalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n refetch,\n },\n }\n}\n"],"names":["QueryKeyRequiredError","QueryFnRequiredError","QueryClientRequiredError","GetKeyRequiredError","QueryObserver"],"mappings":"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"}
1
+ {"version":3,"file":"query.cjs","sources":["../../src/query.ts"],"sourcesContent":["import { QueryObserver } from \"@tanstack/query-core\"\nimport {\n GetKeyRequiredError,\n QueryClientRequiredError,\n QueryFnRequiredError,\n QueryKeyRequiredError,\n} from \"./errors\"\nimport { createWriteUtils } from \"./manual-sync\"\nimport type { SyncOperation } from \"./manual-sync\"\nimport type {\n QueryClient,\n QueryFunctionContext,\n QueryKey,\n QueryObserverOptions,\n} from \"@tanstack/query-core\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n DeleteMutationFn,\n DeleteMutationFnParams,\n InsertMutationFn,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFn,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"@tanstack/db\"\n\n// Re-export for external use\nexport type { SyncOperation } from \"./manual-sync\"\n\nexport interface QueryCollectionConfig<\n TItem extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n> {\n queryKey: TQueryKey\n queryFn: (context: QueryFunctionContext<TQueryKey>) => Promise<Array<TItem>>\n queryClient: QueryClient\n\n // Query-specific options\n enabled?: boolean\n refetchInterval?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`refetchInterval`]\n retry?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`retry`]\n retryDelay?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`retryDelay`]\n staleTime?: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >[`staleTime`]\n\n // Standard Collection configuration properties\n id?: string\n getKey: CollectionConfig<TItem>[`getKey`]\n schema?: CollectionConfig<TItem>[`schema`]\n sync?: CollectionConfig<TItem>[`sync`]\n startSync?: CollectionConfig<TItem>[`startSync`]\n\n // Direct persistence handlers\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection insert handler\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * // Automatically refetches query after insert\n * }\n *\n * @example\n * // Insert handler with refetch control\n * onInsert: async ({ transaction }) => {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Insert handler with multiple items\n * onInsert: async ({ transaction }) => {\n * const items = transaction.mutations.map(m => m.modified)\n * await api.createTodos(items)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Insert handler with error handling\n * onInsert: async ({ transaction }) => {\n * try {\n * const newItem = transaction.mutations[0].modified\n * await api.createTodo(newItem)\n * } catch (error) {\n * console.error('Insert failed:', error)\n * throw error // Transaction will rollback optimistic changes\n * }\n * }\n */\n onInsert?: InsertMutationFn<TItem>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection update handler\n * onUpdate: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n * // Automatically refetches query after update\n * }\n *\n * @example\n * // Update handler with multiple items\n * onUpdate: async ({ transaction }) => {\n * const updates = transaction.mutations.map(m => ({\n * id: m.key,\n * changes: m.changes\n * }))\n * await api.updateTodos(updates)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Update handler with manual refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Manually trigger refetch\n * await collection.utils.refetch()\n *\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Update handler with related collection refetch\n * onUpdate: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.updateTodo(mutation.original.id, mutation.changes)\n *\n * // Refetch related collections when this item changes\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * tagsCollection.utils.refetch() // Refetch tags\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onUpdate?: UpdateMutationFn<TItem>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to void or { refetch?: boolean } to control refetching\n * @example\n * // Basic query collection delete handler\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * // Automatically refetches query after delete\n * }\n *\n * @example\n * // Delete handler with refetch control\n * onDelete: async ({ transaction }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n * return { refetch: false } // Skip automatic refetch\n * }\n *\n * @example\n * // Delete handler with multiple items\n * onDelete: async ({ transaction }) => {\n * const keysToDelete = transaction.mutations.map(m => m.key)\n * await api.deleteTodos(keysToDelete)\n * // Will refetch query to get updated data\n * }\n *\n * @example\n * // Delete handler with related collection refetch\n * onDelete: async ({ transaction, collection }) => {\n * const mutation = transaction.mutations[0]\n * await api.deleteTodo(mutation.original.id)\n *\n * // Refetch related collections when this item is deleted\n * await Promise.all([\n * collection.utils.refetch(), // Refetch this collection\n * usersCollection.utils.refetch(), // Refetch users\n * projectsCollection.utils.refetch() // Refetch projects\n * ])\n *\n * return { refetch: false } // Skip automatic refetch since we handled it manually\n * }\n */\n onDelete?: DeleteMutationFn<TItem>\n // TODO type returning { refetch: boolean }\n}\n\n/**\n * Type for the refetch utility function\n */\nexport type RefetchFn = () => Promise<void>\n\n/**\n * Query collection utilities type\n */\n/**\n * Write operation types for batch operations\n */\nexport interface QueryCollectionUtils<\n TItem extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TInsertInput extends object = TItem,\n> extends UtilsRecord {\n refetch: RefetchFn\n writeInsert: (data: TInsertInput | Array<TInsertInput>) => void\n writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void\n writeDelete: (keys: TKey | Array<TKey>) => void\n writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void\n writeBatch: (\n operations: Array<SyncOperation<TItem, TKey, TInsertInput>>\n ) => void\n}\n\n/**\n * Creates query collection options for use with a standard Collection\n *\n * @param config - Configuration options for the Query collection\n * @returns Collection options with utilities\n */\nexport function queryCollectionOptions<\n TItem extends object,\n TError = unknown,\n TQueryKey extends QueryKey = QueryKey,\n TKey extends string | number = string | number,\n TInsertInput extends object = TItem,\n>(\n config: QueryCollectionConfig<TItem, TError, TQueryKey>\n): CollectionConfig<TItem> & {\n utils: QueryCollectionUtils<TItem, TKey, TInsertInput>\n} {\n const {\n queryKey,\n queryFn,\n queryClient,\n enabled,\n refetchInterval,\n retry,\n retryDelay,\n staleTime,\n getKey,\n onInsert,\n onUpdate,\n onDelete,\n ...baseCollectionConfig\n } = config\n\n // Validate required parameters\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryKey) {\n throw new QueryKeyRequiredError()\n }\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryFn) {\n throw new QueryFnRequiredError()\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!queryClient) {\n throw new QueryClientRequiredError()\n }\n\n if (!getKey) {\n throw new GetKeyRequiredError()\n }\n\n const internalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, markReady, collection } = params\n\n const observerOptions: QueryObserverOptions<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n > = {\n queryKey: queryKey,\n queryFn: queryFn,\n enabled: enabled,\n refetchInterval: refetchInterval,\n retry: retry,\n retryDelay: retryDelay,\n staleTime: staleTime,\n structuralSharing: true,\n notifyOnChangeProps: `all`,\n }\n\n const localObserver = new QueryObserver<\n Array<TItem>,\n TError,\n Array<TItem>,\n Array<TItem>,\n TQueryKey\n >(queryClient, observerOptions)\n\n const actualUnsubscribeFn = localObserver.subscribe((result) => {\n if (result.isSuccess) {\n const newItemsArray = result.data\n\n if (\n !Array.isArray(newItemsArray) ||\n newItemsArray.some((item) => typeof item !== `object`)\n ) {\n console.error(\n `[QueryCollection] queryFn did not return an array of objects. Skipping update.`,\n newItemsArray\n )\n return\n }\n\n const currentSyncedItems = new Map(collection.syncedData)\n const newItemsMap = new Map<string | number, TItem>()\n newItemsArray.forEach((item) => {\n const key = getKey(item)\n newItemsMap.set(key, item)\n })\n\n begin()\n\n // Helper function for shallow equality check of objects\n const shallowEqual = (\n obj1: Record<string, any>,\n obj2: Record<string, any>\n ): boolean => {\n // Get all keys from both objects\n const keys1 = Object.keys(obj1)\n const keys2 = Object.keys(obj2)\n\n // If number of keys is different, objects are not equal\n if (keys1.length !== keys2.length) return false\n\n // Check if all keys in obj1 have the same values in obj2\n return keys1.every((key) => {\n // Skip comparing functions and complex objects deeply\n if (typeof obj1[key] === `function`) return true\n if (typeof obj1[key] === `object` && obj1[key] !== null) {\n // For nested objects, just compare references\n // A more robust solution might do recursive shallow comparison\n // or let users provide a custom equality function\n return obj1[key] === obj2[key]\n }\n return obj1[key] === obj2[key]\n })\n }\n\n currentSyncedItems.forEach((oldItem, key) => {\n const newItem = newItemsMap.get(key)\n if (!newItem) {\n write({ type: `delete`, value: oldItem })\n } else if (\n !shallowEqual(\n oldItem as Record<string, any>,\n newItem as Record<string, any>\n )\n ) {\n // Only update if there are actual differences in the properties\n write({ type: `update`, value: newItem })\n }\n })\n\n newItemsMap.forEach((newItem, key) => {\n if (!currentSyncedItems.has(key)) {\n write({ type: `insert`, value: newItem })\n }\n })\n\n commit()\n\n // Mark collection as ready after first successful query result\n markReady()\n } else if (result.isError) {\n console.error(\n `[QueryCollection] Error observing query ${String(queryKey)}:`,\n result.error\n )\n\n // Mark collection as ready even on error to avoid blocking apps\n markReady()\n }\n })\n\n return async () => {\n actualUnsubscribeFn()\n await queryClient.cancelQueries({ queryKey })\n queryClient.removeQueries({ queryKey })\n }\n }\n\n /**\n * Refetch the query data\n * @returns Promise that resolves when the refetch is complete\n */\n const refetch: RefetchFn = async (): Promise<void> => {\n return queryClient.refetchQueries({\n queryKey: queryKey,\n })\n }\n\n // Create write context for manual write operations\n let writeContext: {\n collection: any\n queryClient: QueryClient\n queryKey: Array<unknown>\n getKey: (item: TItem) => TKey\n begin: () => void\n write: (message: Omit<ChangeMessage<TItem>, `key`>) => void\n commit: () => void\n } | null = null\n\n // Enhanced internalSync that captures write functions for manual use\n const enhancedInternalSync: SyncConfig<TItem>[`sync`] = (params) => {\n const { begin, write, commit, collection } = params\n\n // Store references for manual write operations\n writeContext = {\n collection,\n queryClient,\n queryKey: queryKey as unknown as Array<unknown>,\n getKey: getKey as (item: TItem) => TKey,\n begin,\n write,\n commit,\n }\n\n // Call the original internalSync logic\n return internalSync(params)\n }\n\n // Create write utils using the manual-sync module\n const writeUtils = createWriteUtils<TItem, TKey, TInsertInput>(\n () => writeContext\n )\n\n // Create wrapper handlers for direct persistence operations that handle refetching\n const wrappedOnInsert = onInsert\n ? async (params: InsertMutationFnParams<TItem>) => {\n const handlerResult = (await onInsert(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnUpdate = onUpdate\n ? async (params: UpdateMutationFnParams<TItem>) => {\n const handlerResult = (await onUpdate(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n const wrappedOnDelete = onDelete\n ? async (params: DeleteMutationFnParams<TItem>) => {\n const handlerResult = (await onDelete(params)) ?? {}\n const shouldRefetch =\n (handlerResult as { refetch?: boolean }).refetch !== false\n\n if (shouldRefetch) {\n await refetch()\n }\n\n return handlerResult\n }\n : undefined\n\n return {\n ...baseCollectionConfig,\n getKey,\n sync: { sync: enhancedInternalSync },\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n refetch,\n ...writeUtils,\n },\n }\n}\n"],"names":["QueryKeyRequiredError","QueryFnRequiredError","QueryClientRequiredError","GetKeyRequiredError","QueryObserver","createWriteUtils"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
+ import { SyncOperation } from './manual-sync.cjs';
1
2
  import { QueryClient, QueryFunctionContext, QueryKey, QueryObserverOptions } from '@tanstack/query-core';
2
3
  import { CollectionConfig, DeleteMutationFn, InsertMutationFn, UpdateMutationFn, UtilsRecord } from '@tanstack/db';
4
+ export type { SyncOperation } from './manual-sync.cjs';
3
5
  export interface QueryCollectionConfig<TItem extends object, TError = unknown, TQueryKey extends QueryKey = QueryKey> {
4
6
  queryKey: TQueryKey;
5
7
  queryFn: (context: QueryFunctionContext<TQueryKey>) => Promise<Array<TItem>>;
@@ -160,8 +162,16 @@ export type RefetchFn = () => Promise<void>;
160
162
  /**
161
163
  * Query collection utilities type
162
164
  */
163
- export interface QueryCollectionUtils extends UtilsRecord {
165
+ /**
166
+ * Write operation types for batch operations
167
+ */
168
+ export interface QueryCollectionUtils<TItem extends object = Record<string, unknown>, TKey extends string | number = string | number, TInsertInput extends object = TItem> extends UtilsRecord {
164
169
  refetch: RefetchFn;
170
+ writeInsert: (data: TInsertInput | Array<TInsertInput>) => void;
171
+ writeUpdate: (updates: Partial<TItem> | Array<Partial<TItem>>) => void;
172
+ writeDelete: (keys: TKey | Array<TKey>) => void;
173
+ writeUpsert: (data: Partial<TItem> | Array<Partial<TItem>>) => void;
174
+ writeBatch: (operations: Array<SyncOperation<TItem, TKey, TInsertInput>>) => void;
165
175
  }
166
176
  /**
167
177
  * Creates query collection options for use with a standard Collection
@@ -169,6 +179,6 @@ export interface QueryCollectionUtils extends UtilsRecord {
169
179
  * @param config - Configuration options for the Query collection
170
180
  * @returns Collection options with utilities
171
181
  */
172
- export declare function queryCollectionOptions<TItem extends object, TError = unknown, TQueryKey extends QueryKey = QueryKey>(config: QueryCollectionConfig<TItem, TError, TQueryKey>): CollectionConfig<TItem> & {
173
- utils: QueryCollectionUtils;
182
+ export declare function queryCollectionOptions<TItem extends object, TError = unknown, TQueryKey extends QueryKey = QueryKey, TKey extends string | number = string | number, TInsertInput extends object = TItem>(config: QueryCollectionConfig<TItem, TError, TQueryKey>): CollectionConfig<TItem> & {
183
+ utils: QueryCollectionUtils<TItem, TKey, TInsertInput>;
174
184
  };
@@ -14,3 +14,30 @@ export declare class QueryClientRequiredError extends QueryCollectionError {
14
14
  export declare class GetKeyRequiredError extends QueryCollectionError {
15
15
  constructor();
16
16
  }
17
+ export declare class SyncNotInitializedError extends QueryCollectionError {
18
+ constructor();
19
+ }
20
+ export declare class InvalidItemStructureError extends QueryCollectionError {
21
+ constructor(message: string);
22
+ }
23
+ export declare class ItemNotFoundError extends QueryCollectionError {
24
+ constructor(key: string | number);
25
+ }
26
+ export declare class DuplicateKeyInBatchError extends QueryCollectionError {
27
+ constructor(key: string | number);
28
+ }
29
+ export declare class UpdateOperationItemNotFoundError extends QueryCollectionError {
30
+ constructor(key: string | number);
31
+ }
32
+ export declare class DeleteOperationItemNotFoundError extends QueryCollectionError {
33
+ constructor(key: string | number);
34
+ }
35
+ export declare class InvalidSyncOperationError extends QueryCollectionError {
36
+ constructor(message: string);
37
+ }
38
+ export declare class UnknownOperationTypeError extends QueryCollectionError {
39
+ constructor(type: string);
40
+ }
41
+ export declare class MissingKeyFieldError extends QueryCollectionError {
42
+ constructor(operation: string, message: string);
43
+ }
@@ -29,11 +29,76 @@ class GetKeyRequiredError extends QueryCollectionError {
29
29
  this.name = `GetKeyRequiredError`;
30
30
  }
31
31
  }
32
+ class SyncNotInitializedError extends QueryCollectionError {
33
+ constructor() {
34
+ super(
35
+ `Collection must be in 'ready' state for manual sync operations. Sync not initialized yet.`
36
+ );
37
+ this.name = `SyncNotInitializedError`;
38
+ }
39
+ }
40
+ class InvalidItemStructureError extends QueryCollectionError {
41
+ constructor(message) {
42
+ super(`Invalid item structure: ${message}`);
43
+ this.name = `InvalidItemStructureError`;
44
+ }
45
+ }
46
+ class ItemNotFoundError extends QueryCollectionError {
47
+ constructor(key) {
48
+ super(`Item with key '${key}' does not exist.`);
49
+ this.name = `ItemNotFoundError`;
50
+ }
51
+ }
52
+ class DuplicateKeyInBatchError extends QueryCollectionError {
53
+ constructor(key) {
54
+ super(`Duplicate key '${key}' found within batch operations`);
55
+ this.name = `DuplicateKeyInBatchError`;
56
+ }
57
+ }
58
+ class UpdateOperationItemNotFoundError extends QueryCollectionError {
59
+ constructor(key) {
60
+ super(`Update operation: Item with key '${key}' does not exist`);
61
+ this.name = `UpdateOperationItemNotFoundError`;
62
+ }
63
+ }
64
+ class DeleteOperationItemNotFoundError extends QueryCollectionError {
65
+ constructor(key) {
66
+ super(`Delete operation: Item with key '${key}' does not exist`);
67
+ this.name = `DeleteOperationItemNotFoundError`;
68
+ }
69
+ }
70
+ class InvalidSyncOperationError extends QueryCollectionError {
71
+ constructor(message) {
72
+ super(`Invalid sync operation: ${message}`);
73
+ this.name = `InvalidSyncOperationError`;
74
+ }
75
+ }
76
+ class UnknownOperationTypeError extends QueryCollectionError {
77
+ constructor(type) {
78
+ super(`Unknown operation type: ${type}`);
79
+ this.name = `UnknownOperationTypeError`;
80
+ }
81
+ }
82
+ class MissingKeyFieldError extends QueryCollectionError {
83
+ constructor(operation, message) {
84
+ super(`${operation} item must contain the key field: ${message}`);
85
+ this.name = `MissingKeyFieldError`;
86
+ }
87
+ }
32
88
  export {
89
+ DeleteOperationItemNotFoundError,
90
+ DuplicateKeyInBatchError,
33
91
  GetKeyRequiredError,
92
+ InvalidItemStructureError,
93
+ InvalidSyncOperationError,
94
+ ItemNotFoundError,
95
+ MissingKeyFieldError,
34
96
  QueryClientRequiredError,
35
97
  QueryCollectionError,
36
98
  QueryFnRequiredError,
37
- QueryKeyRequiredError
99
+ QueryKeyRequiredError,
100
+ SyncNotInitializedError,
101
+ UnknownOperationTypeError,
102
+ UpdateOperationItemNotFoundError
38
103
  };
39
104
  //# sourceMappingURL=errors.js.map