@tanstack/db 0.2.3 → 0.2.5

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.
Files changed (39) hide show
  1. package/dist/cjs/collection.cjs +23 -4
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +35 -41
  4. package/dist/cjs/local-only.cjs.map +1 -1
  5. package/dist/cjs/local-only.d.cts +17 -43
  6. package/dist/cjs/local-storage.cjs +3 -12
  7. package/dist/cjs/local-storage.cjs.map +1 -1
  8. package/dist/cjs/local-storage.d.cts +16 -39
  9. package/dist/cjs/query/builder/types.d.cts +12 -11
  10. package/dist/cjs/query/compiler/joins.cjs +4 -3
  11. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  12. package/dist/cjs/query/live/collection-subscriber.cjs +5 -1
  13. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  14. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  15. package/dist/cjs/types.d.cts +10 -31
  16. package/dist/esm/collection.d.ts +35 -41
  17. package/dist/esm/collection.js +23 -4
  18. package/dist/esm/collection.js.map +1 -1
  19. package/dist/esm/local-only.d.ts +17 -43
  20. package/dist/esm/local-only.js.map +1 -1
  21. package/dist/esm/local-storage.d.ts +16 -39
  22. package/dist/esm/local-storage.js +3 -12
  23. package/dist/esm/local-storage.js.map +1 -1
  24. package/dist/esm/query/builder/types.d.ts +12 -11
  25. package/dist/esm/query/compiler/joins.js +4 -3
  26. package/dist/esm/query/compiler/joins.js.map +1 -1
  27. package/dist/esm/query/live/collection-subscriber.js +5 -1
  28. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  29. package/dist/esm/query/live-query-collection.js.map +1 -1
  30. package/dist/esm/types.d.ts +10 -31
  31. package/package.json +2 -2
  32. package/src/collection.ts +148 -196
  33. package/src/local-only.ts +57 -77
  34. package/src/local-storage.ts +53 -85
  35. package/src/query/builder/types.ts +52 -16
  36. package/src/query/compiler/joins.ts +4 -3
  37. package/src/query/live/collection-subscriber.ts +5 -1
  38. package/src/query/live-query-collection.ts +1 -1
  39. package/src/types.ts +25 -55
package/src/local-only.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type {
2
+ BaseCollectionConfig,
2
3
  CollectionConfig,
3
4
  DeleteMutationFnParams,
5
+ InferSchemaOutput,
4
6
  InsertMutationFnParams,
5
7
  OperationType,
6
- ResolveType,
7
8
  SyncConfig,
8
9
  UpdateMutationFnParams,
9
10
  UtilsRecord,
@@ -12,76 +13,23 @@ import type { StandardSchemaV1 } from "@standard-schema/spec"
12
13
 
13
14
  /**
14
15
  * Configuration interface for Local-only collection options
15
- * @template TExplicit - The explicit type of items in the collection (highest priority)
16
- * @template TSchema - The schema type for validation and type inference (second priority)
17
- * @template TFallback - The fallback type if no explicit or schema type is provided
18
- * @template TKey - The type of the key returned by getKey
19
- *
20
- * @remarks
21
- * Type resolution follows a priority order:
22
- * 1. If you provide an explicit type via generic parameter, it will be used
23
- * 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
24
- * 3. If neither explicit type nor schema is provided, the fallback type will be used
25
- *
26
- * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
16
+ * @template T - The type of items in the collection
17
+ * @template TSchema - The schema type for validation
18
+ * @template TKey - The type of the key returned by `getKey`
27
19
  */
28
20
  export interface LocalOnlyCollectionConfig<
29
- TExplicit = unknown,
21
+ T extends object = object,
30
22
  TSchema extends StandardSchemaV1 = never,
31
- TFallback extends Record<string, unknown> = Record<string, unknown>,
32
23
  TKey extends string | number = string | number,
33
- > {
34
- /**
35
- * Standard Collection configuration properties
36
- */
37
- id?: string
38
- schema?: TSchema
39
- getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => TKey
40
-
24
+ > extends Omit<
25
+ BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,
26
+ `gcTime` | `startSync`
27
+ > {
41
28
  /**
42
29
  * Optional initial data to populate the collection with on creation
43
30
  * This data will be applied during the initial sync process
44
31
  */
45
- initialData?: Array<ResolveType<TExplicit, TSchema, TFallback>>
46
-
47
- /**
48
- * Optional asynchronous handler function called after an insert operation
49
- * @param params Object containing transaction and collection information
50
- * @returns Promise resolving to any value
51
- */
52
- onInsert?: (
53
- params: InsertMutationFnParams<
54
- ResolveType<TExplicit, TSchema, TFallback>,
55
- TKey,
56
- LocalOnlyCollectionUtils
57
- >
58
- ) => Promise<any>
59
-
60
- /**
61
- * Optional asynchronous handler function called after an update operation
62
- * @param params Object containing transaction and collection information
63
- * @returns Promise resolving to any value
64
- */
65
- onUpdate?: (
66
- params: UpdateMutationFnParams<
67
- ResolveType<TExplicit, TSchema, TFallback>,
68
- TKey,
69
- LocalOnlyCollectionUtils
70
- >
71
- ) => Promise<any>
72
-
73
- /**
74
- * Optional asynchronous handler function called after a delete operation
75
- * @param params Object containing transaction and collection information
76
- * @returns Promise resolving to any value
77
- */
78
- onDelete?: (
79
- params: DeleteMutationFnParams<
80
- ResolveType<TExplicit, TSchema, TFallback>,
81
- TKey,
82
- LocalOnlyCollectionUtils
83
- >
84
- ) => Promise<any>
32
+ initialData?: Array<T>
85
33
  }
86
34
 
87
35
  /**
@@ -96,9 +44,7 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {}
96
44
  * that immediately "syncs" all optimistic changes to the collection, making them permanent.
97
45
  * Perfect for local-only data that doesn't need persistence or external synchronization.
98
46
  *
99
- * @template TExplicit - The explicit type of items in the collection (highest priority)
100
- * @template TSchema - The schema type for validation and type inference (second priority)
101
- * @template TFallback - The fallback type if no explicit or schema type is provided
47
+ * @template T - The schema type if a schema is provided, otherwise the type of items in the collection
102
48
  * @template TKey - The type of the key returned by getKey
103
49
  * @param config - Configuration options for the Local-only collection
104
50
  * @returns Collection options with utilities (currently empty but follows the pattern)
@@ -135,29 +81,55 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {}
135
81
  * })
136
82
  * )
137
83
  */
84
+
85
+ // Overload for when schema is provided
138
86
  export function localOnlyCollectionOptions<
139
- TExplicit = unknown,
140
- TSchema extends StandardSchemaV1 = never,
141
- TFallback extends Record<string, unknown> = Record<string, unknown>,
87
+ T extends StandardSchemaV1,
142
88
  TKey extends string | number = string | number,
143
89
  >(
144
- config: LocalOnlyCollectionConfig<TExplicit, TSchema, TFallback, TKey>
145
- ): CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>, TKey> & {
90
+ config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
91
+ schema: T
92
+ }
93
+ ): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
146
94
  utils: LocalOnlyCollectionUtils
147
- } {
148
- type ResolvedType = ResolveType<TExplicit, TSchema, TFallback>
95
+ schema: T
96
+ }
97
+
98
+ // Overload for when no schema is provided
99
+ // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
100
+ export function localOnlyCollectionOptions<
101
+ T extends object,
102
+ TKey extends string | number = string | number,
103
+ >(
104
+ config: LocalOnlyCollectionConfig<T, never, TKey> & {
105
+ schema?: never // prohibit schema
106
+ }
107
+ ): CollectionConfig<T, TKey> & {
108
+ utils: LocalOnlyCollectionUtils
109
+ schema?: never // no schema in the result
110
+ }
149
111
 
112
+ export function localOnlyCollectionOptions(
113
+ config: LocalOnlyCollectionConfig<any, any, string | number>
114
+ ): CollectionConfig<any, string | number, any> & {
115
+ utils: LocalOnlyCollectionUtils
116
+ schema?: StandardSchemaV1
117
+ } {
150
118
  const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config
151
119
 
152
120
  // Create the sync configuration with transaction confirmation capability
153
- const syncResult = createLocalOnlySync<ResolvedType, TKey>(initialData)
121
+ const syncResult = createLocalOnlySync(initialData)
154
122
 
155
123
  /**
156
124
  * Create wrapper handlers that call user handlers first, then confirm transactions
157
125
  * Wraps the user's onInsert handler to also confirm the transaction immediately
158
126
  */
159
127
  const wrappedOnInsert = async (
160
- params: InsertMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>
128
+ params: InsertMutationFnParams<
129
+ any,
130
+ string | number,
131
+ LocalOnlyCollectionUtils
132
+ >
161
133
  ) => {
162
134
  // Call user handler first if provided
163
135
  let handlerResult
@@ -175,7 +147,11 @@ export function localOnlyCollectionOptions<
175
147
  * Wrapper for onUpdate handler that also confirms the transaction immediately
176
148
  */
177
149
  const wrappedOnUpdate = async (
178
- params: UpdateMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>
150
+ params: UpdateMutationFnParams<
151
+ any,
152
+ string | number,
153
+ LocalOnlyCollectionUtils
154
+ >
179
155
  ) => {
180
156
  // Call user handler first if provided
181
157
  let handlerResult
@@ -193,7 +169,11 @@ export function localOnlyCollectionOptions<
193
169
  * Wrapper for onDelete handler that also confirms the transaction immediately
194
170
  */
195
171
  const wrappedOnDelete = async (
196
- params: DeleteMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>
172
+ params: DeleteMutationFnParams<
173
+ any,
174
+ string | number,
175
+ LocalOnlyCollectionUtils
176
+ >
197
177
  ) => {
198
178
  // Call user handler first if provided
199
179
  let handlerResult
@@ -7,10 +7,11 @@ import {
7
7
  StorageKeyRequiredError,
8
8
  } from "./errors"
9
9
  import type {
10
+ BaseCollectionConfig,
10
11
  CollectionConfig,
11
12
  DeleteMutationFnParams,
13
+ InferSchemaOutput,
12
14
  InsertMutationFnParams,
13
- ResolveType,
14
15
  SyncConfig,
15
16
  UpdateMutationFnParams,
16
17
  UtilsRecord,
@@ -46,23 +47,15 @@ interface StoredItem<T> {
46
47
 
47
48
  /**
48
49
  * Configuration interface for localStorage collection options
49
- * @template TExplicit - The explicit type of items in the collection (highest priority)
50
- * @template TSchema - The schema type for validation and type inference (second priority)
51
- * @template TFallback - The fallback type if no explicit or schema type is provided
52
- *
53
- * @remarks
54
- * Type resolution follows a priority order:
55
- * 1. If you provide an explicit type via generic parameter, it will be used
56
- * 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
57
- * 3. If neither explicit type nor schema is provided, the fallback type will be used
58
- *
59
- * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
50
+ * @template T - The type of items in the collection
51
+ * @template TSchema - The schema type for validation
52
+ * @template TKey - The type of the key returned by `getKey`
60
53
  */
61
54
  export interface LocalStorageCollectionConfig<
62
- TExplicit = unknown,
55
+ T extends object = object,
63
56
  TSchema extends StandardSchemaV1 = never,
64
- TFallback extends object = Record<string, unknown>,
65
- > {
57
+ TKey extends string | number = string | number,
58
+ > extends BaseCollectionConfig<T, TKey, TSchema> {
66
59
  /**
67
60
  * The key to use for storing the collection data in localStorage/sessionStorage
68
61
  */
@@ -79,41 +72,6 @@ export interface LocalStorageCollectionConfig<
79
72
  * Can be any object that implements addEventListener/removeEventListener for storage events
80
73
  */
81
74
  storageEventApi?: StorageEventApi
82
-
83
- /**
84
- * Collection identifier (defaults to "local-collection:{storageKey}" if not provided)
85
- */
86
- id?: string
87
- schema?: TSchema
88
- getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`getKey`]
89
- sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`sync`]
90
-
91
- /**
92
- * Optional asynchronous handler function called before an insert operation
93
- * @param params Object containing transaction and collection information
94
- * @returns Promise resolving to any value
95
- */
96
- onInsert?: (
97
- params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>
98
- ) => Promise<any>
99
-
100
- /**
101
- * Optional asynchronous handler function called before an update operation
102
- * @param params Object containing transaction and collection information
103
- * @returns Promise resolving to any value
104
- */
105
- onUpdate?: (
106
- params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>
107
- ) => Promise<any>
108
-
109
- /**
110
- * Optional asynchronous handler function called before a delete operation
111
- * @param params Object containing transaction and collection information
112
- * @returns Promise resolving to any value
113
- */
114
- onDelete?: (
115
- params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>
116
- ) => Promise<any>
117
75
  }
118
76
 
119
77
  /**
@@ -202,18 +160,43 @@ function generateUuid(): string {
202
160
  * })
203
161
  * )
204
162
  */
163
+
164
+ // Overload for when schema is provided
205
165
  export function localStorageCollectionOptions<
206
- TExplicit = unknown,
207
- TSchema extends StandardSchemaV1 = never,
208
- TFallback extends object = Record<string, unknown>,
166
+ T extends StandardSchemaV1,
167
+ TKey extends string | number = string | number,
209
168
  >(
210
- config: LocalStorageCollectionConfig<TExplicit, TSchema, TFallback>
211
- ): Omit<CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>, `id`> & {
169
+ config: LocalStorageCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
170
+ schema: T
171
+ }
172
+ ): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
212
173
  id: string
213
174
  utils: LocalStorageCollectionUtils
214
- } {
215
- type ResolvedType = ResolveType<TExplicit, TSchema, TFallback>
175
+ schema: T
176
+ }
216
177
 
178
+ // Overload for when no schema is provided
179
+ // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
180
+ export function localStorageCollectionOptions<
181
+ T extends object,
182
+ TKey extends string | number = string | number,
183
+ >(
184
+ config: LocalStorageCollectionConfig<T, never, TKey> & {
185
+ schema?: never // prohibit schema
186
+ }
187
+ ): CollectionConfig<T, TKey> & {
188
+ id: string
189
+ utils: LocalStorageCollectionUtils
190
+ schema?: never // no schema in the result
191
+ }
192
+
193
+ export function localStorageCollectionOptions(
194
+ config: LocalStorageCollectionConfig<any, any, string | number>
195
+ ): Omit<CollectionConfig<any, string | number, any>, `id`> & {
196
+ id: string
197
+ utils: LocalStorageCollectionUtils
198
+ schema?: StandardSchemaV1
199
+ } {
217
200
  // Validate required parameters
218
201
  if (!config.storageKey) {
219
202
  throw new StorageKeyRequiredError()
@@ -237,10 +220,10 @@ export function localStorageCollectionOptions<
237
220
  }
238
221
 
239
222
  // Track the last known state to detect changes
240
- const lastKnownData = new Map<string | number, StoredItem<ResolvedType>>()
223
+ const lastKnownData = new Map<string | number, StoredItem<any>>()
241
224
 
242
225
  // Create the sync configuration
243
- const sync = createLocalStorageSync<ResolvedType>(
226
+ const sync = createLocalStorageSync<any>(
244
227
  config.storageKey,
245
228
  storage,
246
229
  storageEventApi,
@@ -263,11 +246,11 @@ export function localStorageCollectionOptions<
263
246
  * @param dataMap - Map of items with version tracking to save to storage
264
247
  */
265
248
  const saveToStorage = (
266
- dataMap: Map<string | number, StoredItem<ResolvedType>>
249
+ dataMap: Map<string | number, StoredItem<any>>
267
250
  ): void => {
268
251
  try {
269
252
  // Convert Map to object format for storage
270
- const objectData: Record<string, StoredItem<ResolvedType>> = {}
253
+ const objectData: Record<string, StoredItem<any>> = {}
271
254
  dataMap.forEach((storedItem, key) => {
272
255
  objectData[String(key)] = storedItem
273
256
  })
@@ -302,9 +285,7 @@ export function localStorageCollectionOptions<
302
285
  * Create wrapper handlers for direct persistence operations that perform actual storage operations
303
286
  * Wraps the user's onInsert handler to also save changes to localStorage
304
287
  */
305
- const wrappedOnInsert = async (
306
- params: InsertMutationFnParams<ResolvedType>
307
- ) => {
288
+ const wrappedOnInsert = async (params: InsertMutationFnParams<any>) => {
308
289
  // Validate that all values in the transaction can be JSON serialized
309
290
  params.transaction.mutations.forEach((mutation) => {
310
291
  validateJsonSerializable(mutation.modified, `insert`)
@@ -318,15 +299,12 @@ export function localStorageCollectionOptions<
318
299
 
319
300
  // Always persist to storage
320
301
  // Load current data from storage
321
- const currentData = loadFromStorage<ResolvedType>(
322
- config.storageKey,
323
- storage
324
- )
302
+ const currentData = loadFromStorage<any>(config.storageKey, storage)
325
303
 
326
304
  // Add new items with version keys
327
305
  params.transaction.mutations.forEach((mutation) => {
328
306
  const key = config.getKey(mutation.modified)
329
- const storedItem: StoredItem<ResolvedType> = {
307
+ const storedItem: StoredItem<any> = {
330
308
  versionKey: generateUuid(),
331
309
  data: mutation.modified,
332
310
  }
@@ -342,9 +320,7 @@ export function localStorageCollectionOptions<
342
320
  return handlerResult
343
321
  }
344
322
 
345
- const wrappedOnUpdate = async (
346
- params: UpdateMutationFnParams<ResolvedType>
347
- ) => {
323
+ const wrappedOnUpdate = async (params: UpdateMutationFnParams<any>) => {
348
324
  // Validate that all values in the transaction can be JSON serialized
349
325
  params.transaction.mutations.forEach((mutation) => {
350
326
  validateJsonSerializable(mutation.modified, `update`)
@@ -358,15 +334,12 @@ export function localStorageCollectionOptions<
358
334
 
359
335
  // Always persist to storage
360
336
  // Load current data from storage
361
- const currentData = loadFromStorage<ResolvedType>(
362
- config.storageKey,
363
- storage
364
- )
337
+ const currentData = loadFromStorage<any>(config.storageKey, storage)
365
338
 
366
339
  // Update items with new version keys
367
340
  params.transaction.mutations.forEach((mutation) => {
368
341
  const key = config.getKey(mutation.modified)
369
- const storedItem: StoredItem<ResolvedType> = {
342
+ const storedItem: StoredItem<any> = {
370
343
  versionKey: generateUuid(),
371
344
  data: mutation.modified,
372
345
  }
@@ -382,9 +355,7 @@ export function localStorageCollectionOptions<
382
355
  return handlerResult
383
356
  }
384
357
 
385
- const wrappedOnDelete = async (
386
- params: DeleteMutationFnParams<ResolvedType>
387
- ) => {
358
+ const wrappedOnDelete = async (params: DeleteMutationFnParams<any>) => {
388
359
  // Call the user handler BEFORE persisting changes (if provided)
389
360
  let handlerResult: any = {}
390
361
  if (config.onDelete) {
@@ -393,15 +364,12 @@ export function localStorageCollectionOptions<
393
364
 
394
365
  // Always persist to storage
395
366
  // Load current data from storage
396
- const currentData = loadFromStorage<ResolvedType>(
397
- config.storageKey,
398
- storage
399
- )
367
+ const currentData = loadFromStorage<any>(config.storageKey, storage)
400
368
 
401
369
  // Remove items
402
370
  params.transaction.mutations.forEach((mutation) => {
403
371
  // For delete operations, mutation.original contains the full object
404
- const key = config.getKey(mutation.original as ResolvedType)
372
+ const key = config.getKey(mutation.original)
405
373
  currentData.delete(key)
406
374
  })
407
375
 
@@ -8,7 +8,6 @@ import type {
8
8
  Value,
9
9
  } from "../ir.js"
10
10
  import type { QueryBuilder } from "./index.js"
11
- import type { ResolveType } from "../../types.js"
12
11
 
13
12
  /**
14
13
  * Context - The central state container for query builder operations
@@ -77,19 +76,11 @@ export type Source = {
77
76
  /**
78
77
  * InferCollectionType - Extracts the TypeScript type from a CollectionImpl
79
78
  *
80
- * This helper ensures we get the same type that would be used when creating
81
- * the collection itself. It uses the internal `ResolveType` logic to maintain
82
- * consistency between collection creation and query type inference.
83
- *
84
- * The complex generic parameters extract:
85
- * - U: The base document type
86
- * - TSchema: The schema definition
87
- * - The resolved type combines these with any transforms
79
+ * This helper ensures we get the same type that was used when creating the collection itself.
80
+ * This can be an explicit type passed by the user or the schema output type.
88
81
  */
89
82
  export type InferCollectionType<T> =
90
- T extends CollectionImpl<infer U, any, any, infer TSchema, any>
91
- ? ResolveType<U, TSchema, U>
92
- : never
83
+ T extends CollectionImpl<infer TOutput, any, any, any, any> ? TOutput : never
93
84
 
94
85
  /**
95
86
  * SchemaFromSource - Converts a Source definition into a ContextSchema
@@ -500,20 +491,20 @@ export type Ref<T = any> = {
500
491
  [K in keyof T]: IsNonExactOptional<T[K]> extends true
501
492
  ? IsNonExactNullable<T[K]> extends true
502
493
  ? // Both optional and nullable
503
- NonNullable<T[K]> extends Record<string, any>
494
+ IsPlainObject<NonNullable<T[K]>> extends true
504
495
  ? Ref<NonNullable<T[K]>> | undefined
505
496
  : RefLeaf<NonNullable<T[K]>> | undefined
506
497
  : // Optional only
507
- NonUndefined<T[K]> extends Record<string, any>
498
+ IsPlainObject<NonUndefined<T[K]>> extends true
508
499
  ? Ref<NonUndefined<T[K]>> | undefined
509
500
  : RefLeaf<NonUndefined<T[K]>> | undefined
510
501
  : IsNonExactNullable<T[K]> extends true
511
502
  ? // Nullable only
512
- NonNull<T[K]> extends Record<string, any>
503
+ IsPlainObject<NonNull<T[K]>> extends true
513
504
  ? Ref<NonNull<T[K]>> | null
514
505
  : RefLeaf<NonNull<T[K]>> | null
515
506
  : // Required
516
- T[K] extends Record<string, any>
507
+ IsPlainObject<T[K]> extends true
517
508
  ? Ref<T[K]>
518
509
  : RefLeaf<T[K]>
519
510
  } & RefLeaf<T>
@@ -825,3 +816,48 @@ export type WithResult<TContext extends Context, TResult> = Prettify<
825
816
  export type Prettify<T> = {
826
817
  [K in keyof T]: T[K]
827
818
  } & {}
819
+
820
+ /**
821
+ * IsPlainObject - Utility type to check if T is a plain object
822
+ */
823
+ type IsPlainObject<T> = T extends unknown
824
+ ? T extends object
825
+ ? T extends ReadonlyArray<any>
826
+ ? false
827
+ : T extends JsBuiltIns
828
+ ? false
829
+ : true
830
+ : false
831
+ : false
832
+
833
+ /**
834
+ * JsBuiltIns - List of JavaScript built-ins
835
+ */
836
+ type JsBuiltIns =
837
+ | ArrayBuffer
838
+ | ArrayBufferLike
839
+ | AsyncGenerator<any, any, any>
840
+ | BigInt64Array
841
+ | BigUint64Array
842
+ | DataView
843
+ | Date
844
+ | Error
845
+ | Float32Array
846
+ | Float64Array
847
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
848
+ | Function
849
+ | Generator<any, any, any>
850
+ | Int16Array
851
+ | Int32Array
852
+ | Int8Array
853
+ | Map<any, any>
854
+ | Promise<any>
855
+ | RegExp
856
+ | Set<any>
857
+ | string
858
+ | Uint16Array
859
+ | Uint32Array
860
+ | Uint8Array
861
+ | Uint8ClampedArray
862
+ | WeakMap<any, any>
863
+ | WeakSet<any>
@@ -243,7 +243,7 @@ function processJoin(
243
243
  const activePipelineWithLoading: IStreamBuilder<
244
244
  [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]
245
245
  > = activePipeline.pipe(
246
- tap(([joinKey, _]) => {
246
+ tap((data) => {
247
247
  if (deoptimized) {
248
248
  return
249
249
  }
@@ -270,10 +270,11 @@ function processJoin(
270
270
 
271
271
  const { loadKeys, loadInitialState } = collectionCallbacks
272
272
 
273
- if (index && index.supports(`eq`)) {
273
+ if (index && index.supports(`in`)) {
274
274
  // Use the index to fetch the PKs of the rows in the lazy collection
275
275
  // that match this row from the active collection based on the value of the joinKey
276
- const matchingKeys = index.lookup(`eq`, joinKey)
276
+ const joinKeys = data.getInner().map(([[joinKey]]) => joinKey)
277
+ const matchingKeys = index.lookup(`in`, joinKeys)
277
278
  // Inform the lazy collection that those keys need to be loaded
278
279
  loadKeys(matchingKeys)
279
280
  } else {
@@ -138,6 +138,7 @@ export class CollectionSubscriber<
138
138
  keys: Iterable<string | number>,
139
139
  filterFn: (item: object) => boolean
140
140
  ) {
141
+ const changes: Array<ChangeMessage<any, string | number>> = []
141
142
  for (const key of keys) {
142
143
  // Only load the key once
143
144
  if (this.sentKeys.has(key)) continue
@@ -145,9 +146,12 @@ export class CollectionSubscriber<
145
146
  const value = this.collection.get(key)
146
147
  if (value !== undefined && filterFn(value)) {
147
148
  this.sentKeys.add(key)
148
- this.sendChangesToPipeline([{ type: `insert`, key, value }])
149
+ changes.push({ type: `insert`, key, value })
149
150
  }
150
151
  }
152
+ if (changes.length > 0) {
153
+ this.sendChangesToPipeline(changes)
154
+ }
151
155
  }
152
156
 
153
157
  private subscribeToAllChanges(
@@ -130,7 +130,7 @@ export function createLiveQueryCollection<
130
130
 
131
131
  /**
132
132
  * Bridge function that handles the type compatibility between query2's TResult
133
- * and core collection's ResolveType without exposing ugly type assertions to users
133
+ * and core collection's output type without exposing ugly type assertions to users
134
134
  */
135
135
  function bridgeToCreateCollection<
136
136
  TResult extends object,