@tanstack/db 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/SortedMap.cjs +38 -11
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/SortedMap.d.cts +10 -0
- package/dist/cjs/collection.cjs +467 -95
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +80 -4
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/optimistic-action.cjs +21 -0
- package/dist/cjs/optimistic-action.cjs.map +1 -0
- package/dist/cjs/optimistic-action.d.cts +39 -0
- package/dist/cjs/query/compiled-query.cjs +21 -11
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/query-builder.cjs +2 -2
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +3 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +44 -0
- package/dist/esm/SortedMap.d.ts +10 -0
- package/dist/esm/SortedMap.js +38 -11
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection.d.ts +80 -4
- package/dist/esm/collection.js +467 -95
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/optimistic-action.d.ts +39 -0
- package/dist/esm/optimistic-action.js +21 -0
- package/dist/esm/optimistic-action.js.map +1 -0
- package/dist/esm/query/compiled-query.js +21 -11
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/query-builder.js +2 -2
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/transactions.js +3 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +44 -0
- package/package.json +1 -1
- package/src/SortedMap.ts +46 -13
- package/src/collection.ts +624 -119
- package/src/index.ts +1 -0
- package/src/optimistic-action.ts +65 -0
- package/src/query/compiled-query.ts +36 -14
- package/src/query/query-builder.ts +2 -2
- package/src/transactions.ts +6 -1
- package/src/types.ts +47 -0
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./errors"
|
|
|
7
7
|
export * from "./utils"
|
|
8
8
|
export * from "./proxy"
|
|
9
9
|
export * from "./query/index.js"
|
|
10
|
+
export * from "./optimistic-action"
|
|
10
11
|
|
|
11
12
|
// Re-export some stuff explicitly to ensure the type & value is exported
|
|
12
13
|
export type { Collection } from "./collection"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createTransaction } from "./transactions"
|
|
2
|
+
import type { CreateOptimisticActionsOptions, Transaction } from "./types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates an optimistic action function that applies local optimistic updates immediately
|
|
6
|
+
* before executing the actual mutation on the server.
|
|
7
|
+
*
|
|
8
|
+
* This pattern allows for responsive UI updates while the actual mutation is in progress.
|
|
9
|
+
* The optimistic update is applied via the `onMutate` callback, and the server mutation
|
|
10
|
+
* is executed via the `mutationFn`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const addTodo = createOptimisticAction<string>({
|
|
15
|
+
* onMutate: (text) => {
|
|
16
|
+
* // Instantly applies local optimistic state
|
|
17
|
+
* todoCollection.insert({
|
|
18
|
+
* id: uuid(),
|
|
19
|
+
* text,
|
|
20
|
+
* completed: false
|
|
21
|
+
* })
|
|
22
|
+
* },
|
|
23
|
+
* mutationFn: async (text, params) => {
|
|
24
|
+
* // Persist the todo to your backend
|
|
25
|
+
* const response = await fetch('/api/todos', {
|
|
26
|
+
* method: 'POST',
|
|
27
|
+
* body: JSON.stringify({ text, completed: false }),
|
|
28
|
+
* })
|
|
29
|
+
* return response.json()
|
|
30
|
+
* }
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Usage
|
|
34
|
+
* const transaction = addTodo('New Todo Item')
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @template TVariables - The type of variables that will be passed to the action function
|
|
38
|
+
* @param options - Configuration options for the optimistic action
|
|
39
|
+
* @returns A function that accepts variables of type TVariables and returns a Transaction
|
|
40
|
+
*/
|
|
41
|
+
export function createOptimisticAction<TVariables = unknown>(
|
|
42
|
+
options: CreateOptimisticActionsOptions<TVariables>
|
|
43
|
+
) {
|
|
44
|
+
const { mutationFn, onMutate, ...config } = options
|
|
45
|
+
|
|
46
|
+
return (variables: TVariables): Transaction => {
|
|
47
|
+
// Create transaction with the original config
|
|
48
|
+
const transaction = createTransaction({
|
|
49
|
+
...config,
|
|
50
|
+
// Wire the mutationFn to use the provided variables
|
|
51
|
+
mutationFn: async (params) => {
|
|
52
|
+
return await mutationFn(variables, params)
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Execute the transaction. The mutationFn is called once mutate()
|
|
57
|
+
// is finished.
|
|
58
|
+
transaction.mutate(() => {
|
|
59
|
+
// Call onMutate with variables to apply optimistic updates
|
|
60
|
+
onMutate(variables)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return transaction
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { D2, MultiSet, output } from "@electric-sql/d2mini"
|
|
2
2
|
import { createCollection } from "../collection.js"
|
|
3
3
|
import { compileQueryPipeline } from "./pipeline-compiler.js"
|
|
4
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
4
5
|
import type { Collection } from "../collection.js"
|
|
5
6
|
import type { ChangeMessage, ResolveType, SyncConfig } from "../types.js"
|
|
6
7
|
import type {
|
|
@@ -114,10 +115,36 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
114
115
|
|
|
115
116
|
this.graph = graph
|
|
116
117
|
this.inputs = inputs
|
|
118
|
+
|
|
119
|
+
const compare = query.orderBy
|
|
120
|
+
? (
|
|
121
|
+
val1: ResolveType<
|
|
122
|
+
TResults,
|
|
123
|
+
StandardSchemaV1,
|
|
124
|
+
Record<string, unknown>
|
|
125
|
+
>,
|
|
126
|
+
val2: ResolveType<TResults, StandardSchemaV1, Record<string, unknown>>
|
|
127
|
+
): number => {
|
|
128
|
+
// The query builder always adds an _orderByIndex property if the results are ordered
|
|
129
|
+
const x = val1 as TResults & { _orderByIndex: number }
|
|
130
|
+
const y = val2 as TResults & { _orderByIndex: number }
|
|
131
|
+
if (x._orderByIndex < y._orderByIndex) {
|
|
132
|
+
return -1
|
|
133
|
+
} else if (x._orderByIndex > y._orderByIndex) {
|
|
134
|
+
return 1
|
|
135
|
+
} else {
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
: undefined
|
|
140
|
+
|
|
117
141
|
this.resultCollection = createCollection<TResults>({
|
|
118
142
|
getKey: (val: unknown) => {
|
|
119
143
|
return (val as any)._key
|
|
120
144
|
},
|
|
145
|
+
gcTime: 0,
|
|
146
|
+
startSync: true,
|
|
147
|
+
compare,
|
|
121
148
|
sync: {
|
|
122
149
|
sync: sync as unknown as (params: {
|
|
123
150
|
collection: Collection<
|
|
@@ -178,26 +205,21 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
178
205
|
throw new Error(`Query is stopped`)
|
|
179
206
|
}
|
|
180
207
|
|
|
181
|
-
// Send initial state
|
|
182
|
-
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
183
|
-
this.sendChangesToInput(
|
|
184
|
-
key,
|
|
185
|
-
collection.currentStateAsChanges(),
|
|
186
|
-
collection.config.getKey
|
|
187
|
-
)
|
|
188
|
-
})
|
|
189
|
-
this.runGraph()
|
|
190
|
-
|
|
191
208
|
// Subscribe to changes
|
|
192
209
|
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
193
|
-
const unsubscribe = collection.subscribeChanges(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
210
|
+
const unsubscribe = collection.subscribeChanges(
|
|
211
|
+
(changes) => {
|
|
212
|
+
this.sendChangesToInput(key, changes, collection.config.getKey)
|
|
213
|
+
this.runGraph()
|
|
214
|
+
},
|
|
215
|
+
{ includeInitialState: true }
|
|
216
|
+
)
|
|
197
217
|
|
|
198
218
|
this.unsubscribeCallbacks.push(unsubscribe)
|
|
199
219
|
})
|
|
200
220
|
|
|
221
|
+
this.runGraph()
|
|
222
|
+
|
|
201
223
|
this.state = `running`
|
|
202
224
|
return () => {
|
|
203
225
|
this.stop()
|
|
@@ -268,7 +268,7 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
268
268
|
// Ensure we have an orderByIndex in the select if we have an orderBy
|
|
269
269
|
// This is required if select is called after orderBy
|
|
270
270
|
if (this._query.orderBy) {
|
|
271
|
-
validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `
|
|
271
|
+
validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `fractional` } })
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
const newBuilder = new BaseQueryBuilder<TContext>(
|
|
@@ -735,7 +735,7 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
735
735
|
// This is required if select is called before orderBy
|
|
736
736
|
newBuilder.query.select = [
|
|
737
737
|
...(newBuilder.query.select ?? []),
|
|
738
|
-
{ _orderByIndex: { ORDER_INDEX: `
|
|
738
|
+
{ _orderByIndex: { ORDER_INDEX: `fractional` } },
|
|
739
739
|
]
|
|
740
740
|
|
|
741
741
|
return newBuilder as QueryBuilder<TContext>
|
package/src/transactions.ts
CHANGED
|
@@ -156,7 +156,12 @@ export class Transaction<T extends object = Record<string, unknown>> {
|
|
|
156
156
|
for (const mutation of this.mutations) {
|
|
157
157
|
if (!hasCalled.has(mutation.collection.id)) {
|
|
158
158
|
mutation.collection.onTransactionStateChange()
|
|
159
|
-
|
|
159
|
+
|
|
160
|
+
// Only call commitPendingTransactions if there are pending sync transactions
|
|
161
|
+
if (mutation.collection.pendingSyncedTransactions.length > 0) {
|
|
162
|
+
mutation.collection.commitPendingTransactions()
|
|
163
|
+
}
|
|
164
|
+
|
|
160
165
|
hasCalled.add(mutation.collection.id)
|
|
161
166
|
}
|
|
162
167
|
}
|
package/src/types.ts
CHANGED
|
@@ -111,6 +111,17 @@ export interface TransactionConfig<T extends object = Record<string, unknown>> {
|
|
|
111
111
|
metadata?: Record<string, unknown>
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Options for the createOptimisticAction helper
|
|
116
|
+
*/
|
|
117
|
+
export interface CreateOptimisticActionsOptions<TVars = unknown>
|
|
118
|
+
extends Omit<TransactionConfig, `mutationFn`> {
|
|
119
|
+
/** Function to apply optimistic updates locally before the mutation completes */
|
|
120
|
+
onMutate: (vars: TVars) => void
|
|
121
|
+
/** Function to execute the mutation on the server */
|
|
122
|
+
mutationFn: (vars: TVars, params: MutationFnParams) => Promise<any>
|
|
123
|
+
}
|
|
124
|
+
|
|
114
125
|
export type { Transaction }
|
|
115
126
|
|
|
116
127
|
type Value<TExtensions = never> =
|
|
@@ -216,6 +227,21 @@ export type DeleteMutationFn<T extends object = Record<string, unknown>> = (
|
|
|
216
227
|
params: DeleteMutationFnParams<T>
|
|
217
228
|
) => Promise<any>
|
|
218
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Collection status values for lifecycle management
|
|
232
|
+
*/
|
|
233
|
+
export type CollectionStatus =
|
|
234
|
+
/** Collection is created but sync hasn't started yet (when startSync config is false) */
|
|
235
|
+
| `idle`
|
|
236
|
+
/** Sync has started but hasn't received the first commit yet */
|
|
237
|
+
| `loading`
|
|
238
|
+
/** Collection has received at least one commit and is ready for use */
|
|
239
|
+
| `ready`
|
|
240
|
+
/** An error occurred during sync initialization */
|
|
241
|
+
| `error`
|
|
242
|
+
/** Collection has been cleaned up and resources freed */
|
|
243
|
+
| `cleaned-up`
|
|
244
|
+
|
|
219
245
|
export interface CollectionConfig<
|
|
220
246
|
T extends object = Record<string, unknown>,
|
|
221
247
|
TKey extends string | number = string | number,
|
|
@@ -236,6 +262,27 @@ export interface CollectionConfig<
|
|
|
236
262
|
* getKey: (item) => item.uuid
|
|
237
263
|
*/
|
|
238
264
|
getKey: (item: T) => TKey
|
|
265
|
+
/**
|
|
266
|
+
* Time in milliseconds after which the collection will be garbage collected
|
|
267
|
+
* when it has no active subscribers. Defaults to 5 minutes (300000ms).
|
|
268
|
+
*/
|
|
269
|
+
gcTime?: number
|
|
270
|
+
/**
|
|
271
|
+
* Whether to start syncing immediately when the collection is created.
|
|
272
|
+
* Defaults to false for lazy loading. Set to true to immediately sync.
|
|
273
|
+
*/
|
|
274
|
+
startSync?: boolean
|
|
275
|
+
/**
|
|
276
|
+
* Optional function to compare two items.
|
|
277
|
+
* This is used to order the items in the collection.
|
|
278
|
+
* @param x The first item to compare
|
|
279
|
+
* @param y The second item to compare
|
|
280
|
+
* @returns A number indicating the order of the items
|
|
281
|
+
* @example
|
|
282
|
+
* // For a collection with a 'createdAt' field
|
|
283
|
+
* compare: (x, y) => x.createdAt.getTime() - y.createdAt.getTime()
|
|
284
|
+
*/
|
|
285
|
+
compare?: (x: T, y: T) => number
|
|
239
286
|
/**
|
|
240
287
|
* Optional asynchronous handler function called before an insert operation
|
|
241
288
|
* @param params Object containing transaction and mutation information
|