@tanstack/db 0.0.10 → 0.0.12
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/collection.cjs +9 -49
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +29 -30
- package/dist/cjs/index.cjs +0 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.cjs +55 -62
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +0 -4
- package/dist/cjs/query/group-by.cjs +3 -3
- package/dist/cjs/query/group-by.cjs.map +1 -1
- package/dist/cjs/query/group-by.d.cts +1 -1
- package/dist/cjs/query/joins.cjs +16 -16
- package/dist/cjs/query/joins.cjs.map +1 -1
- package/dist/cjs/query/joins.d.cts +1 -1
- package/dist/cjs/query/order-by.cjs +6 -6
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.cjs +5 -5
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +1 -1
- package/dist/cjs/query/select.cjs +2 -2
- package/dist/cjs/query/select.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +5 -12
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/transactions.d.cts +1 -1
- package/dist/cjs/types.d.cts +40 -11
- package/dist/esm/collection.d.ts +29 -30
- package/dist/esm/collection.js +10 -50
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.js +1 -2
- package/dist/esm/query/compiled-query.d.ts +0 -4
- package/dist/esm/query/compiled-query.js +55 -62
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/group-by.d.ts +1 -1
- package/dist/esm/query/group-by.js +1 -1
- package/dist/esm/query/group-by.js.map +1 -1
- package/dist/esm/query/joins.d.ts +1 -1
- package/dist/esm/query/joins.js +1 -1
- package/dist/esm/query/joins.js.map +1 -1
- package/dist/esm/query/order-by.js +1 -1
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/pipeline-compiler.d.ts +1 -1
- package/dist/esm/query/pipeline-compiler.js +1 -1
- package/dist/esm/query/pipeline-compiler.js.map +1 -1
- package/dist/esm/query/select.js +1 -1
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/transactions.d.ts +1 -1
- package/dist/esm/transactions.js +5 -12
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +40 -11
- package/package.json +2 -2
- package/src/collection.ts +66 -121
- package/src/query/compiled-query.ts +85 -71
- package/src/query/group-by.ts +1 -1
- package/src/query/joins.ts +2 -2
- package/src/query/order-by.ts +1 -1
- package/src/query/pipeline-compiler.ts +2 -2
- package/src/query/select.ts +1 -1
- package/src/transactions.ts +8 -20
- package/src/types.ts +78 -9
package/src/collection.ts
CHANGED
|
@@ -11,23 +11,16 @@ import type {
|
|
|
11
11
|
OperationConfig,
|
|
12
12
|
OptimisticChangeMessage,
|
|
13
13
|
PendingMutation,
|
|
14
|
+
ResolveType,
|
|
14
15
|
StandardSchema,
|
|
15
16
|
Transaction as TransactionType,
|
|
16
17
|
UtilsRecord,
|
|
17
18
|
} from "./types"
|
|
19
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
18
20
|
|
|
19
21
|
// Store collections in memory
|
|
20
22
|
export const collectionsStore = new Map<string, CollectionImpl<any, any>>()
|
|
21
23
|
|
|
22
|
-
// Map to track loading collections
|
|
23
|
-
const loadingCollectionResolvers = new Map<
|
|
24
|
-
string,
|
|
25
|
-
{
|
|
26
|
-
promise: Promise<CollectionImpl<any, any>>
|
|
27
|
-
resolve: (value: CollectionImpl<any, any>) => void
|
|
28
|
-
}
|
|
29
|
-
>()
|
|
30
|
-
|
|
31
24
|
interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
32
25
|
committed: boolean
|
|
33
26
|
operations: Array<OptimisticChangeMessage<T>>
|
|
@@ -36,6 +29,7 @@ interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
|
36
29
|
/**
|
|
37
30
|
* Enhanced Collection interface that includes both data type T and utilities TUtils
|
|
38
31
|
* @template T - The type of items in the collection
|
|
32
|
+
* @template TKey - The type of the key for the collection
|
|
39
33
|
* @template TUtils - The utilities record type
|
|
40
34
|
*/
|
|
41
35
|
export interface Collection<
|
|
@@ -49,20 +43,53 @@ export interface Collection<
|
|
|
49
43
|
/**
|
|
50
44
|
* Creates a new Collection instance with the given configuration
|
|
51
45
|
*
|
|
52
|
-
* @template
|
|
46
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
53
47
|
* @template TKey - The type of the key for the collection
|
|
54
48
|
* @template TUtils - The utilities record type
|
|
49
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
50
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
55
51
|
* @param options - Collection options with optional utilities
|
|
56
52
|
* @returns A new Collection with utilities exposed both at top level and under .utils
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Using explicit type
|
|
56
|
+
* const todos = createCollection<Todo>({
|
|
57
|
+
* getKey: (todo) => todo.id,
|
|
58
|
+
* sync: { sync: () => {} }
|
|
59
|
+
* })
|
|
60
|
+
*
|
|
61
|
+
* // Using schema for type inference (preferred as it also gives you client side validation)
|
|
62
|
+
* const todoSchema = z.object({
|
|
63
|
+
* id: z.string(),
|
|
64
|
+
* title: z.string(),
|
|
65
|
+
* completed: z.boolean()
|
|
66
|
+
* })
|
|
67
|
+
*
|
|
68
|
+
* const todos = createCollection({
|
|
69
|
+
* schema: todoSchema,
|
|
70
|
+
* getKey: (todo) => todo.id,
|
|
71
|
+
* sync: { sync: () => {} }
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* // Note: You must provide either an explicit type or a schema, but not both
|
|
57
75
|
*/
|
|
58
76
|
export function createCollection<
|
|
59
|
-
|
|
77
|
+
TExplicit = unknown,
|
|
60
78
|
TKey extends string | number = string | number,
|
|
61
79
|
TUtils extends UtilsRecord = {},
|
|
80
|
+
TSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
81
|
+
TFallback extends object = Record<string, unknown>,
|
|
62
82
|
>(
|
|
63
|
-
options: CollectionConfig<
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
options: CollectionConfig<
|
|
84
|
+
ResolveType<TExplicit, TSchema, TFallback>,
|
|
85
|
+
TKey,
|
|
86
|
+
TSchema
|
|
87
|
+
> & { utils?: TUtils }
|
|
88
|
+
): Collection<ResolveType<TExplicit, TSchema, TFallback>, TKey, TUtils> {
|
|
89
|
+
const collection = new CollectionImpl<
|
|
90
|
+
ResolveType<TExplicit, TSchema, TFallback>,
|
|
91
|
+
TKey
|
|
92
|
+
>(options)
|
|
66
93
|
|
|
67
94
|
// Copy utils to both top level and .utils namespace
|
|
68
95
|
if (options.utils) {
|
|
@@ -71,98 +98,11 @@ export function createCollection<
|
|
|
71
98
|
collection.utils = {} as TUtils
|
|
72
99
|
}
|
|
73
100
|
|
|
74
|
-
return collection as Collection<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)
|
|
80
|
-
* If the collection has already loaded, it resolves immediately
|
|
81
|
-
*
|
|
82
|
-
* This function is useful in route loaders or similar pre-rendering scenarios where you want
|
|
83
|
-
* to ensure data is available before a route transition completes. It uses the same shared collection
|
|
84
|
-
* instance that will be used by useCollection, ensuring data consistency.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* ```typescript
|
|
88
|
-
* // In a route loader
|
|
89
|
-
* async function loader({ params }) {
|
|
90
|
-
* await preloadCollection({
|
|
91
|
-
* id: `users-${params.userId}`,
|
|
92
|
-
* sync: { ... },
|
|
93
|
-
* });
|
|
94
|
-
*
|
|
95
|
-
* return null;
|
|
96
|
-
* }
|
|
97
|
-
* ```
|
|
98
|
-
*
|
|
99
|
-
* @template T - The type of items in the collection
|
|
100
|
-
* @param config - Configuration for the collection, including id and sync
|
|
101
|
-
* @returns Promise that resolves when the initial sync is finished
|
|
102
|
-
*/
|
|
103
|
-
export function preloadCollection<
|
|
104
|
-
T extends object = Record<string, unknown>,
|
|
105
|
-
TKey extends string | number = string | number,
|
|
106
|
-
>(config: CollectionConfig<T, TKey>): Promise<CollectionImpl<T, TKey>> {
|
|
107
|
-
if (!config.id) {
|
|
108
|
-
throw new Error(`The id property is required for preloadCollection`)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// If the collection is already fully loaded, return a resolved promise
|
|
112
|
-
if (
|
|
113
|
-
collectionsStore.has(config.id) &&
|
|
114
|
-
!loadingCollectionResolvers.has(config.id)
|
|
115
|
-
) {
|
|
116
|
-
return Promise.resolve(
|
|
117
|
-
collectionsStore.get(config.id)! as CollectionImpl<T, TKey>
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// If the collection is in the process of loading, return its promise
|
|
122
|
-
if (loadingCollectionResolvers.has(config.id)) {
|
|
123
|
-
return loadingCollectionResolvers.get(config.id)!.promise
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create a new collection instance if it doesn't exist
|
|
127
|
-
if (!collectionsStore.has(config.id)) {
|
|
128
|
-
collectionsStore.set(
|
|
129
|
-
config.id,
|
|
130
|
-
createCollection<T, TKey>({
|
|
131
|
-
id: config.id,
|
|
132
|
-
getKey: config.getKey,
|
|
133
|
-
sync: config.sync,
|
|
134
|
-
schema: config.schema,
|
|
135
|
-
})
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const collection = collectionsStore.get(config.id)! as CollectionImpl<T, TKey>
|
|
140
|
-
|
|
141
|
-
// Create a promise that will resolve after the first commit
|
|
142
|
-
let resolveFirstCommit: (value: CollectionImpl<T, TKey>) => void
|
|
143
|
-
const firstCommitPromise = new Promise<CollectionImpl<T, TKey>>((resolve) => {
|
|
144
|
-
resolveFirstCommit = resolve
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
// Store the loading promise first
|
|
148
|
-
loadingCollectionResolvers.set(config.id, {
|
|
149
|
-
promise: firstCommitPromise,
|
|
150
|
-
resolve: resolveFirstCommit!,
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// Register a one-time listener for the first commit
|
|
154
|
-
collection.onFirstCommit(() => {
|
|
155
|
-
if (!config.id) {
|
|
156
|
-
throw new Error(`The id property is required for preloadCollection`)
|
|
157
|
-
}
|
|
158
|
-
if (loadingCollectionResolvers.has(config.id)) {
|
|
159
|
-
const resolver = loadingCollectionResolvers.get(config.id)!
|
|
160
|
-
loadingCollectionResolvers.delete(config.id)
|
|
161
|
-
resolver.resolve(collection)
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
return firstCommitPromise
|
|
101
|
+
return collection as Collection<
|
|
102
|
+
ResolveType<TExplicit, TSchema, TFallback>,
|
|
103
|
+
TKey,
|
|
104
|
+
TUtils
|
|
105
|
+
>
|
|
166
106
|
}
|
|
167
107
|
|
|
168
108
|
/**
|
|
@@ -184,8 +124,8 @@ export class SchemaValidationError extends Error {
|
|
|
184
124
|
message?: string
|
|
185
125
|
) {
|
|
186
126
|
const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues
|
|
187
|
-
.map((issue) => issue.message)
|
|
188
|
-
.join(
|
|
127
|
+
.map((issue) => `\n- ${issue.message} - path: ${issue.path}`)
|
|
128
|
+
.join(``)}`
|
|
189
129
|
|
|
190
130
|
super(message || defaultMessage)
|
|
191
131
|
this.name = `SchemaValidationError`
|
|
@@ -221,7 +161,7 @@ export class CollectionImpl<
|
|
|
221
161
|
|
|
222
162
|
private pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []
|
|
223
163
|
private syncedKeys = new Set<TKey>()
|
|
224
|
-
public config: CollectionConfig<T, TKey>
|
|
164
|
+
public config: CollectionConfig<T, TKey, any>
|
|
225
165
|
private hasReceivedFirstCommit = false
|
|
226
166
|
|
|
227
167
|
// Array to store one-time commit listeners
|
|
@@ -244,7 +184,7 @@ export class CollectionImpl<
|
|
|
244
184
|
* @param config - Configuration object for the collection
|
|
245
185
|
* @throws Error if sync config is missing
|
|
246
186
|
*/
|
|
247
|
-
constructor(config: CollectionConfig<T, TKey>) {
|
|
187
|
+
constructor(config: CollectionConfig<T, TKey, any>) {
|
|
248
188
|
// eslint-disable-next-line
|
|
249
189
|
if (!config) {
|
|
250
190
|
throw new Error(`Collection requires a config`)
|
|
@@ -803,7 +743,7 @@ export class CollectionImpl<
|
|
|
803
743
|
}
|
|
804
744
|
|
|
805
745
|
const items = Array.isArray(data) ? data : [data]
|
|
806
|
-
const mutations: Array<PendingMutation<T
|
|
746
|
+
const mutations: Array<PendingMutation<T, `insert`>> = []
|
|
807
747
|
|
|
808
748
|
// Create mutations for each item
|
|
809
749
|
items.forEach((item) => {
|
|
@@ -817,7 +757,7 @@ export class CollectionImpl<
|
|
|
817
757
|
}
|
|
818
758
|
const globalKey = this.generateGlobalKey(key, item)
|
|
819
759
|
|
|
820
|
-
const mutation: PendingMutation<T
|
|
760
|
+
const mutation: PendingMutation<T, `insert`> = {
|
|
821
761
|
mutationId: crypto.randomUUID(),
|
|
822
762
|
original: {},
|
|
823
763
|
modified: validatedData,
|
|
@@ -987,7 +927,7 @@ export class CollectionImpl<
|
|
|
987
927
|
}
|
|
988
928
|
|
|
989
929
|
// Create mutations for each object that has changes
|
|
990
|
-
const mutations: Array<PendingMutation<T
|
|
930
|
+
const mutations: Array<PendingMutation<T, `update`>> = keysArray
|
|
991
931
|
.map((key, index) => {
|
|
992
932
|
const itemChanges = changesArray[index] // User-provided changes for this specific item
|
|
993
933
|
|
|
@@ -1025,9 +965,9 @@ export class CollectionImpl<
|
|
|
1025
965
|
|
|
1026
966
|
return {
|
|
1027
967
|
mutationId: crypto.randomUUID(),
|
|
1028
|
-
original: originalItem
|
|
1029
|
-
modified: modifiedItem
|
|
1030
|
-
changes: validatedUpdatePayload as
|
|
968
|
+
original: originalItem,
|
|
969
|
+
modified: modifiedItem,
|
|
970
|
+
changes: validatedUpdatePayload as Partial<T>,
|
|
1031
971
|
globalKey,
|
|
1032
972
|
key,
|
|
1033
973
|
metadata: config.metadata as unknown,
|
|
@@ -1041,7 +981,7 @@ export class CollectionImpl<
|
|
|
1041
981
|
collection: this,
|
|
1042
982
|
}
|
|
1043
983
|
})
|
|
1044
|
-
.filter(Boolean) as Array<PendingMutation<T
|
|
984
|
+
.filter(Boolean) as Array<PendingMutation<T, `update`>>
|
|
1045
985
|
|
|
1046
986
|
// If no changes were made, return an empty transaction early
|
|
1047
987
|
if (mutations.length === 0) {
|
|
@@ -1117,15 +1057,20 @@ export class CollectionImpl<
|
|
|
1117
1057
|
}
|
|
1118
1058
|
|
|
1119
1059
|
const keysArray = Array.isArray(keys) ? keys : [keys]
|
|
1120
|
-
const mutations: Array<PendingMutation<T
|
|
1060
|
+
const mutations: Array<PendingMutation<T, `delete`>> = []
|
|
1121
1061
|
|
|
1122
1062
|
for (const key of keysArray) {
|
|
1063
|
+
if (!this.has(key)) {
|
|
1064
|
+
throw new Error(
|
|
1065
|
+
`Collection.delete was called with key '${key}' but there is no item in the collection with this key`
|
|
1066
|
+
)
|
|
1067
|
+
}
|
|
1123
1068
|
const globalKey = this.generateGlobalKey(key, this.get(key)!)
|
|
1124
|
-
const mutation: PendingMutation<T
|
|
1069
|
+
const mutation: PendingMutation<T, `delete`> = {
|
|
1125
1070
|
mutationId: crypto.randomUUID(),
|
|
1126
|
-
original: this.get(key)
|
|
1071
|
+
original: this.get(key)!,
|
|
1127
1072
|
modified: this.get(key)!,
|
|
1128
|
-
changes: this.get(key)
|
|
1073
|
+
changes: this.get(key)!,
|
|
1129
1074
|
globalKey,
|
|
1130
1075
|
key,
|
|
1131
1076
|
metadata: config?.metadata as unknown,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { D2,
|
|
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
4
|
import type { Collection } from "../collection.js"
|
|
5
|
-
import type { ChangeMessage, SyncConfig } from "../types.js"
|
|
5
|
+
import type { ChangeMessage, ResolveType, SyncConfig } from "../types.js"
|
|
6
6
|
import type {
|
|
7
7
|
IStreamBuilder,
|
|
8
8
|
MultiSetArray,
|
|
9
9
|
RootStreamBuilder,
|
|
10
|
-
} from "@electric-sql/
|
|
10
|
+
} from "@electric-sql/d2mini"
|
|
11
11
|
import type { QueryBuilder, ResultsFromContext } from "./query-builder.js"
|
|
12
12
|
import type { Context, Schema } from "./types.js"
|
|
13
13
|
|
|
@@ -25,7 +25,6 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
25
25
|
private inputCollections: Record<string, Collection<any>>
|
|
26
26
|
private resultCollection: Collection<TResults>
|
|
27
27
|
public state: `compiled` | `running` | `stopped` = `compiled`
|
|
28
|
-
private version = 0
|
|
29
28
|
private unsubscribeCallbacks: Array<() => void> = []
|
|
30
29
|
|
|
31
30
|
constructor(queryBuilder: QueryBuilder<Context<Schema>>) {
|
|
@@ -38,58 +37,76 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
38
37
|
|
|
39
38
|
this.inputCollections = collections
|
|
40
39
|
|
|
41
|
-
const graph = new D2(
|
|
40
|
+
const graph = new D2()
|
|
42
41
|
const inputs = Object.fromEntries(
|
|
43
42
|
Object.entries(collections).map(([key]) => [key, graph.newInput<any>()])
|
|
44
43
|
)
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
// Use TResults directly to ensure type compatibility
|
|
46
|
+
const sync: SyncConfig<TResults>[`sync`] = ({
|
|
47
|
+
begin,
|
|
48
|
+
write,
|
|
49
|
+
commit,
|
|
50
|
+
collection,
|
|
51
|
+
}) => {
|
|
47
52
|
compileQueryPipeline<IStreamBuilder<[unknown, TResults]>>(
|
|
48
53
|
query,
|
|
49
54
|
inputs
|
|
50
55
|
).pipe(
|
|
51
|
-
output((
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
56
|
+
output((data) => {
|
|
57
|
+
begin()
|
|
58
|
+
data
|
|
59
|
+
.getInner()
|
|
60
|
+
.reduce((acc, [[key, value], multiplicity]) => {
|
|
61
|
+
const changes = acc.get(key) || {
|
|
62
|
+
deletes: 0,
|
|
63
|
+
inserts: 0,
|
|
64
|
+
value,
|
|
65
|
+
}
|
|
66
|
+
if (multiplicity < 0) {
|
|
67
|
+
changes.deletes += Math.abs(multiplicity)
|
|
68
|
+
} else if (multiplicity > 0) {
|
|
69
|
+
changes.inserts += multiplicity
|
|
70
|
+
changes.value = value
|
|
71
|
+
}
|
|
72
|
+
acc.set(key, changes)
|
|
73
|
+
return acc
|
|
74
|
+
}, new Map<unknown, { deletes: number; inserts: number; value: TResults }>())
|
|
75
|
+
.forEach((changes, rawKey) => {
|
|
76
|
+
const { deletes, inserts, value } = changes
|
|
77
|
+
const valueWithKey = { ...value, _key: rawKey }
|
|
78
|
+
|
|
79
|
+
// Simple singular insert.
|
|
80
|
+
if (inserts && deletes === 0) {
|
|
81
|
+
write({
|
|
82
|
+
value: valueWithKey,
|
|
83
|
+
type: `insert`,
|
|
84
|
+
})
|
|
85
|
+
} else if (
|
|
86
|
+
// Insert & update(s) (updates are a delete & insert)
|
|
87
|
+
inserts > deletes ||
|
|
88
|
+
// Just update(s) but the item is already in the collection (so
|
|
89
|
+
// was inserted previously).
|
|
90
|
+
(inserts === deletes &&
|
|
91
|
+
collection.has(valueWithKey._key as string | number))
|
|
92
|
+
) {
|
|
93
|
+
write({
|
|
94
|
+
value: valueWithKey,
|
|
95
|
+
type: `update`,
|
|
96
|
+
})
|
|
97
|
+
// Only delete is left as an option
|
|
98
|
+
} else if (deletes > 0) {
|
|
99
|
+
write({
|
|
100
|
+
value: valueWithKey,
|
|
101
|
+
type: `delete`,
|
|
102
|
+
})
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`This should never happen ${JSON.stringify(changes)}`
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
commit()
|
|
93
110
|
})
|
|
94
111
|
)
|
|
95
112
|
graph.finalize()
|
|
@@ -98,14 +115,30 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
98
115
|
this.graph = graph
|
|
99
116
|
this.inputs = inputs
|
|
100
117
|
this.resultCollection = createCollection<TResults>({
|
|
101
|
-
id: crypto.randomUUID(), // TODO: remove when we don't require any more
|
|
102
118
|
getKey: (val: unknown) => {
|
|
103
119
|
return (val as any)._key
|
|
104
120
|
},
|
|
105
121
|
sync: {
|
|
106
|
-
sync
|
|
122
|
+
sync: sync as unknown as (params: {
|
|
123
|
+
collection: Collection<
|
|
124
|
+
ResolveType<TResults, never, Record<string, unknown>>,
|
|
125
|
+
string | number,
|
|
126
|
+
{}
|
|
127
|
+
>
|
|
128
|
+
begin: () => void
|
|
129
|
+
write: (
|
|
130
|
+
message: Omit<
|
|
131
|
+
ChangeMessage<
|
|
132
|
+
ResolveType<TResults, never, Record<string, unknown>>,
|
|
133
|
+
string | number
|
|
134
|
+
>,
|
|
135
|
+
`key`
|
|
136
|
+
>
|
|
137
|
+
) => void
|
|
138
|
+
commit: () => void
|
|
139
|
+
}) => void,
|
|
107
140
|
},
|
|
108
|
-
})
|
|
141
|
+
}) as unknown as Collection<TResults, string | number, {}>
|
|
109
142
|
}
|
|
110
143
|
|
|
111
144
|
get results() {
|
|
@@ -131,22 +164,7 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
131
164
|
multiSetArray.push([[key, change.value], -1])
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
|
-
input.sendData(
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private sendFrontierToInput(inputKey: string) {
|
|
138
|
-
const input = this.inputs[inputKey]!
|
|
139
|
-
input.sendFrontier(this.version)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private sendFrontierToAllInputs() {
|
|
143
|
-
Object.entries(this.inputs).forEach(([key]) => {
|
|
144
|
-
this.sendFrontierToInput(key)
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private incrementVersion() {
|
|
149
|
-
this.version++
|
|
167
|
+
input.sendData(new MultiSet(multiSetArray))
|
|
150
168
|
}
|
|
151
169
|
|
|
152
170
|
private runGraph() {
|
|
@@ -168,16 +186,12 @@ export class CompiledQuery<TResults extends object = Record<string, unknown>> {
|
|
|
168
186
|
collection.config.getKey
|
|
169
187
|
)
|
|
170
188
|
})
|
|
171
|
-
this.incrementVersion()
|
|
172
|
-
this.sendFrontierToAllInputs()
|
|
173
189
|
this.runGraph()
|
|
174
190
|
|
|
175
191
|
// Subscribe to changes
|
|
176
192
|
Object.entries(this.inputCollections).forEach(([key, collection]) => {
|
|
177
193
|
const unsubscribe = collection.subscribeChanges((changes) => {
|
|
178
194
|
this.sendChangesToInput(key, changes, collection.config.getKey)
|
|
179
|
-
this.incrementVersion()
|
|
180
|
-
this.sendFrontierToAllInputs()
|
|
181
195
|
this.runGraph()
|
|
182
196
|
})
|
|
183
197
|
|
package/src/query/group-by.ts
CHANGED
package/src/query/joins.ts
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
filter,
|
|
4
4
|
join as joinOperator,
|
|
5
5
|
map,
|
|
6
|
-
} from "@electric-sql/
|
|
6
|
+
} from "@electric-sql/d2mini"
|
|
7
7
|
import { evaluateConditionOnNamespacedRow } from "./evaluators.js"
|
|
8
8
|
import { extractJoinKey } from "./extractors.js"
|
|
9
9
|
import type { Query } from "./index.js"
|
|
10
|
-
import type { IStreamBuilder, JoinType } from "@electric-sql/
|
|
10
|
+
import type { IStreamBuilder, JoinType } from "@electric-sql/d2mini"
|
|
11
11
|
import type {
|
|
12
12
|
KeyedStream,
|
|
13
13
|
NamespacedAndKeyedStream,
|
package/src/query/order-by.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
orderBy,
|
|
4
4
|
orderByWithFractionalIndex,
|
|
5
5
|
orderByWithIndex,
|
|
6
|
-
} from "@electric-sql/
|
|
6
|
+
} from "@electric-sql/d2mini"
|
|
7
7
|
import { evaluateOperandOnNamespacedRow } from "./extractors"
|
|
8
8
|
import { isOrderIndexFunctionCall } from "./utils"
|
|
9
9
|
import type { ConditionOperand, Query } from "./schema"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { filter, map } from "@electric-sql/
|
|
1
|
+
import { filter, map } from "@electric-sql/d2mini"
|
|
2
2
|
import { evaluateWhereOnNamespacedRow } from "./evaluators.js"
|
|
3
3
|
import { processJoinClause } from "./joins.js"
|
|
4
4
|
import { processGroupBy } from "./group-by.js"
|
|
5
5
|
import { processOrderBy } from "./order-by.js"
|
|
6
6
|
import { processSelect } from "./select.js"
|
|
7
7
|
import type { Query } from "./schema.js"
|
|
8
|
-
import type { IStreamBuilder } from "@electric-sql/
|
|
8
|
+
import type { IStreamBuilder } from "@electric-sql/d2mini"
|
|
9
9
|
import type {
|
|
10
10
|
InputRow,
|
|
11
11
|
KeyedStream,
|
package/src/query/select.ts
CHANGED
package/src/transactions.ts
CHANGED
|
@@ -8,36 +8,24 @@ import type {
|
|
|
8
8
|
TransactionWithMutations,
|
|
9
9
|
} from "./types"
|
|
10
10
|
|
|
11
|
-
function generateUUID() {
|
|
12
|
-
// Check if crypto.randomUUID is available (modern browsers and Node.js 15+)
|
|
13
|
-
if (
|
|
14
|
-
typeof crypto !== `undefined` &&
|
|
15
|
-
typeof crypto.randomUUID === `function`
|
|
16
|
-
) {
|
|
17
|
-
return crypto.randomUUID()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Fallback implementation for older environments
|
|
21
|
-
return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {
|
|
22
|
-
const r = (Math.random() * 16) | 0
|
|
23
|
-
const v = c === `x` ? r : (r & 0x3) | 0x8
|
|
24
|
-
return v.toString(16)
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
11
|
const transactions: Array<Transaction<any>> = []
|
|
29
12
|
let transactionStack: Array<Transaction<any>> = []
|
|
30
13
|
|
|
31
|
-
export function createTransaction
|
|
14
|
+
export function createTransaction<
|
|
15
|
+
TData extends object = Record<string, unknown>,
|
|
16
|
+
>(config: TransactionConfig<TData>): Transaction<TData> {
|
|
32
17
|
if (typeof config.mutationFn === `undefined`) {
|
|
33
18
|
throw `mutationFn is required when creating a transaction`
|
|
34
19
|
}
|
|
35
20
|
|
|
36
21
|
let transactionId = config.id
|
|
37
22
|
if (!transactionId) {
|
|
38
|
-
transactionId =
|
|
23
|
+
transactionId = crypto.randomUUID()
|
|
39
24
|
}
|
|
40
|
-
const newTransaction = new Transaction({
|
|
25
|
+
const newTransaction = new Transaction<TData>({
|
|
26
|
+
...config,
|
|
27
|
+
id: transactionId,
|
|
28
|
+
})
|
|
41
29
|
|
|
42
30
|
transactions.push(newTransaction)
|
|
43
31
|
|