@tanstack/db 0.2.2 → 0.2.4

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/src/collection.ts CHANGED
@@ -395,13 +395,14 @@ export class CollectionImpl<
395
395
  const callbacks = [...this.onFirstReadyCallbacks]
396
396
  this.onFirstReadyCallbacks = []
397
397
  callbacks.forEach((callback) => callback())
398
-
399
- // to notify subscribers (like LiveQueryCollection) that the collection is ready
400
- if (this.changeListeners.size > 0) {
401
- this.emitEmptyReadyEvent()
402
- }
403
398
  }
404
399
  }
400
+
401
+ // Always notify dependents when markReady is called, after status is set
402
+ // This ensures live queries get notified when their dependencies become ready
403
+ if (this.changeListeners.size > 0) {
404
+ this.emitEmptyReadyEvent()
405
+ }
405
406
  }
406
407
 
407
408
  public id = ``
@@ -1270,6 +1271,13 @@ export class CollectionImpl<
1270
1271
  this.syncedData.clear()
1271
1272
  this.syncedMetadata.clear()
1272
1273
  this.syncedKeys.clear()
1274
+
1275
+ // 3) Clear currentVisibleState for truncated keys to ensure subsequent operations
1276
+ // are compared against the post-truncate state (undefined) rather than pre-truncate state
1277
+ // This ensures that re-inserted keys are emitted as INSERT events, not UPDATE events
1278
+ for (const key of changedKeys) {
1279
+ currentVisibleState.delete(key)
1280
+ }
1273
1281
  }
1274
1282
 
1275
1283
  for (const operation of transaction.operations) {
@@ -500,20 +500,20 @@ export type Ref<T = any> = {
500
500
  [K in keyof T]: IsNonExactOptional<T[K]> extends true
501
501
  ? IsNonExactNullable<T[K]> extends true
502
502
  ? // Both optional and nullable
503
- NonNullable<T[K]> extends Record<string, any>
503
+ IsPlainObject<NonNullable<T[K]>> extends true
504
504
  ? Ref<NonNullable<T[K]>> | undefined
505
505
  : RefLeaf<NonNullable<T[K]>> | undefined
506
506
  : // Optional only
507
- NonUndefined<T[K]> extends Record<string, any>
507
+ IsPlainObject<NonUndefined<T[K]>> extends true
508
508
  ? Ref<NonUndefined<T[K]>> | undefined
509
509
  : RefLeaf<NonUndefined<T[K]>> | undefined
510
510
  : IsNonExactNullable<T[K]> extends true
511
511
  ? // Nullable only
512
- NonNull<T[K]> extends Record<string, any>
512
+ IsPlainObject<NonNull<T[K]>> extends true
513
513
  ? Ref<NonNull<T[K]>> | null
514
514
  : RefLeaf<NonNull<T[K]>> | null
515
515
  : // Required
516
- T[K] extends Record<string, any>
516
+ IsPlainObject<T[K]> extends true
517
517
  ? Ref<T[K]>
518
518
  : RefLeaf<T[K]>
519
519
  } & RefLeaf<T>
@@ -825,3 +825,48 @@ export type WithResult<TContext extends Context, TResult> = Prettify<
825
825
  export type Prettify<T> = {
826
826
  [K in keyof T]: T[K]
827
827
  } & {}
828
+
829
+ /**
830
+ * IsPlainObject - Utility type to check if T is a plain object
831
+ */
832
+ type IsPlainObject<T> = T extends unknown
833
+ ? T extends object
834
+ ? T extends ReadonlyArray<any>
835
+ ? false
836
+ : T extends JsBuiltIns
837
+ ? false
838
+ : true
839
+ : false
840
+ : false
841
+
842
+ /**
843
+ * JsBuiltIns - List of JavaScript built-ins
844
+ */
845
+ type JsBuiltIns =
846
+ | ArrayBuffer
847
+ | ArrayBufferLike
848
+ | AsyncGenerator<any, any, any>
849
+ | BigInt64Array
850
+ | BigUint64Array
851
+ | DataView
852
+ | Date
853
+ | Error
854
+ | Float32Array
855
+ | Float64Array
856
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
857
+ | Function
858
+ | Generator<any, any, any>
859
+ | Int16Array
860
+ | Int32Array
861
+ | Int8Array
862
+ | Map<any, any>
863
+ | Promise<any>
864
+ | RegExp
865
+ | Set<any>
866
+ | string
867
+ | Uint16Array
868
+ | Uint32Array
869
+ | Uint8Array
870
+ | Uint8ClampedArray
871
+ | WeakMap<any, any>
872
+ | 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(