@tanstack/db 0.5.8 → 0.5.10

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/db",
3
3
  "description": "A reactive client store for building super fast apps on sync",
4
- "version": "0.5.8",
4
+ "version": "0.5.10",
5
5
  "dependencies": {
6
6
  "@standard-schema/spec": "^1.0.0",
7
7
  "@tanstack/pacer-lite": "^0.1.0",
@@ -10,7 +10,7 @@
10
10
  "devDependencies": {
11
11
  "@vitest/coverage-istanbul": "^3.2.4",
12
12
  "arktype": "^2.1.27",
13
- "superjson": "^2.2.5",
13
+ "superjson": "^2.2.6",
14
14
  "temporal-polyfill": "^0.3.0"
15
15
  },
16
16
  "exports": {
@@ -127,32 +127,85 @@ export interface Collection<
127
127
  *
128
128
  */
129
129
 
130
- // Overload for when schema is provided
130
+ // Overload for when schema is provided and utils is required (not optional)
131
+ // We can't infer the Utils type from the CollectionConfig because it will always be optional
132
+ // So we omit it from that type and instead infer it from the extension `& { utils: TUtils }`
133
+ // such that we have the real, non-optional Utils type
131
134
  export function createCollection<
132
135
  T extends StandardSchemaV1,
133
- TKey extends string | number = string | number,
134
- TUtils extends UtilsRecord = UtilsRecord,
136
+ TKey extends string | number,
137
+ TUtils extends UtilsRecord,
135
138
  >(
136
- options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
139
+ options: Omit<
140
+ CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils>,
141
+ `utils`
142
+ > & {
137
143
  schema: T
138
- utils?: TUtils
144
+ utils: TUtils // Required utils
139
145
  } & NonSingleResult
140
146
  ): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &
141
147
  NonSingleResult
142
148
 
149
+ // Overload for when schema is provided and utils is optional
150
+ // In this case we can simply infer the Utils type from the CollectionConfig type
151
+ export function createCollection<
152
+ T extends StandardSchemaV1,
153
+ TKey extends string | number,
154
+ TUtils extends UtilsRecord,
155
+ >(
156
+ options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
157
+ schema: T
158
+ } & NonSingleResult
159
+ ): Collection<
160
+ InferSchemaOutput<T>,
161
+ TKey,
162
+ Exclude<TUtils, undefined>,
163
+ T,
164
+ InferSchemaInput<T>
165
+ > &
166
+ NonSingleResult
167
+
168
+ // Overload for when schema is provided, singleResult is true, and utils is required
169
+ export function createCollection<
170
+ T extends StandardSchemaV1,
171
+ TKey extends string | number,
172
+ TUtils extends UtilsRecord,
173
+ >(
174
+ options: Omit<
175
+ CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils>,
176
+ `utils`
177
+ > & {
178
+ schema: T
179
+ utils: TUtils // Required utils
180
+ } & SingleResult
181
+ ): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &
182
+ SingleResult
183
+
143
184
  // Overload for when schema is provided and singleResult is true
144
185
  export function createCollection<
145
186
  T extends StandardSchemaV1,
146
- TKey extends string | number = string | number,
147
- TUtils extends UtilsRecord = UtilsRecord,
187
+ TKey extends string | number,
188
+ TUtils extends UtilsRecord,
148
189
  >(
149
190
  options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
150
191
  schema: T
151
- utils?: TUtils
152
192
  } & SingleResult
153
193
  ): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &
154
194
  SingleResult
155
195
 
196
+ // Overload for when no schema is provided and utils is required
197
+ // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
198
+ export function createCollection<
199
+ T extends object,
200
+ TKey extends string | number,
201
+ TUtils extends UtilsRecord,
202
+ >(
203
+ options: Omit<CollectionConfig<T, TKey, never, TUtils>, `utils`> & {
204
+ schema?: never // prohibit schema if an explicit type is provided
205
+ utils: TUtils // Required utils
206
+ } & NonSingleResult
207
+ ): Collection<T, TKey, TUtils, never, T> & NonSingleResult
208
+
156
209
  // Overload for when no schema is provided
157
210
  // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
158
211
  export function createCollection<
@@ -162,10 +215,22 @@ export function createCollection<
162
215
  >(
163
216
  options: CollectionConfig<T, TKey, never, TUtils> & {
164
217
  schema?: never // prohibit schema if an explicit type is provided
165
- utils?: TUtils
166
218
  } & NonSingleResult
167
219
  ): Collection<T, TKey, TUtils, never, T> & NonSingleResult
168
220
 
221
+ // Overload for when no schema is provided, singleResult is true, and utils is required
222
+ // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
223
+ export function createCollection<
224
+ T extends object,
225
+ TKey extends string | number = string | number,
226
+ TUtils extends UtilsRecord = UtilsRecord,
227
+ >(
228
+ options: Omit<CollectionConfig<T, TKey, never, TUtils>, `utils`> & {
229
+ schema?: never // prohibit schema if an explicit type is provided
230
+ utils: TUtils // Required utils
231
+ } & SingleResult
232
+ ): Collection<T, TKey, TUtils, never, T> & SingleResult
233
+
169
234
  // Overload for when no schema is provided and singleResult is true
170
235
  // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
171
236
  export function createCollection<
@@ -175,15 +240,13 @@ export function createCollection<
175
240
  >(
176
241
  options: CollectionConfig<T, TKey, never, TUtils> & {
177
242
  schema?: never // prohibit schema if an explicit type is provided
178
- utils?: TUtils
179
243
  } & SingleResult
180
244
  ): Collection<T, TKey, TUtils, never, T> & SingleResult
181
245
 
182
246
  // Implementation
183
247
  export function createCollection(
184
- options: CollectionConfig<any, string | number, any> & {
248
+ options: CollectionConfig<any, string | number, any, UtilsRecord> & {
185
249
  schema?: StandardSchemaV1
186
- utils?: UtilsRecord
187
250
  }
188
251
  ): Collection<any, string | number, UtilsRecord, any, any> {
189
252
  const collection = new CollectionImpl<any, string | number, any, any, any>(
@@ -163,17 +163,19 @@ export class CollectionMutationsManager<
163
163
 
164
164
  const items = Array.isArray(data) ? data : [data]
165
165
  const mutations: Array<PendingMutation<TOutput>> = []
166
+ const keysInCurrentBatch = new Set<TKey>()
166
167
 
167
168
  // Create mutations for each item
168
169
  items.forEach((item) => {
169
170
  // Validate the data against the schema if one exists
170
171
  const validatedData = this.validateData(item, `insert`)
171
172
 
172
- // Check if an item with this ID already exists in the collection
173
+ // Check if an item with this ID already exists in the collection or in the current batch
173
174
  const key = this.config.getKey(validatedData)
174
- if (this.state.has(key)) {
175
+ if (this.state.has(key) || keysInCurrentBatch.has(key)) {
175
176
  throw new DuplicateKeyError(key)
176
177
  }
178
+ keysInCurrentBatch.add(key)
177
179
  const globalKey = this.generateGlobalKey(key, item)
178
180
 
179
181
  const mutation: PendingMutation<TOutput, `insert`> = {
@@ -73,9 +73,9 @@ export interface IndexInterface<
73
73
  /**
74
74
  * Base abstract class that all index types extend
75
75
  */
76
- export abstract class BaseIndex<TKey extends string | number = string | number>
77
- implements IndexInterface<TKey>
78
- {
76
+ export abstract class BaseIndex<
77
+ TKey extends string | number = string | number,
78
+ > implements IndexInterface<TKey> {
79
79
  public readonly id: number
80
80
  public readonly name?: string
81
81
  public readonly expression: BasicExpression
@@ -3,9 +3,9 @@ import type { OrderByDirection } from "../query/ir"
3
3
  import type { IndexInterface, IndexOperation, IndexStats } from "./base-index"
4
4
  import type { RangeQueryOptions } from "./btree-index"
5
5
 
6
- export class ReverseIndex<TKey extends string | number>
7
- implements IndexInterface<TKey>
8
- {
6
+ export class ReverseIndex<
7
+ TKey extends string | number,
8
+ > implements IndexInterface<TKey> {
9
9
  private originalIndex: IndexInterface<TKey>
10
10
 
11
11
  constructor(index: IndexInterface<TKey>) {
package/src/local-only.ts CHANGED
@@ -24,9 +24,9 @@ export interface LocalOnlyCollectionConfig<
24
24
  TSchema extends StandardSchemaV1 = never,
25
25
  TKey extends string | number = string | number,
26
26
  > extends Omit<
27
- BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,
28
- `gcTime` | `startSync`
29
- > {
27
+ BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,
28
+ `gcTime` | `startSync`
29
+ > {
30
30
  /**
31
31
  * Optional initial data to populate the collection with on creation
32
32
  * This data will be applied during the initial sync process
package/src/proxy.ts CHANGED
@@ -994,21 +994,31 @@ export function createChangeProxy<
994
994
  return true
995
995
  },
996
996
 
997
- defineProperty(_ptarget, prop, descriptor) {
998
- // const result = Reflect.defineProperty(
999
- // changeTracker.copy_,
1000
- // prop,
1001
- // descriptor
1002
- // )
1003
- // if (result) {
1004
- if (`value` in descriptor) {
997
+ defineProperty(ptarget, prop, descriptor) {
998
+ // Forward the defineProperty to the target to maintain Proxy invariants
999
+ // This allows Object.seal() and Object.freeze() to work on the proxy
1000
+ const result = Reflect.defineProperty(ptarget, prop, descriptor)
1001
+ if (result && `value` in descriptor) {
1005
1002
  changeTracker.copy_[prop as keyof T] = deepClone(descriptor.value)
1006
1003
  changeTracker.assigned_[prop.toString()] = true
1007
1004
  markChanged(changeTracker)
1008
1005
  }
1009
- // }
1010
- // return result
1011
- return true
1006
+ return result
1007
+ },
1008
+
1009
+ getOwnPropertyDescriptor(ptarget, prop) {
1010
+ // Forward to target to maintain Proxy invariants for seal/freeze
1011
+ return Reflect.getOwnPropertyDescriptor(ptarget, prop)
1012
+ },
1013
+
1014
+ preventExtensions(ptarget) {
1015
+ // Forward to target to allow Object.seal() and Object.preventExtensions()
1016
+ return Reflect.preventExtensions(ptarget)
1017
+ },
1018
+
1019
+ isExtensible(ptarget) {
1020
+ // Forward to target to maintain consistency
1021
+ return Reflect.isExtensible(ptarget)
1012
1022
  },
1013
1023
 
1014
1024
  deleteProperty(dobj, prop) {
@@ -1020,33 +1030,36 @@ export function createChangeProxy<
1020
1030
  const hadPropertyInOriginal =
1021
1031
  stringProp in changeTracker.originalObject
1022
1032
 
1023
- // Delete the property from the copy
1024
- // Use type assertion to tell TypeScript this is allowed
1025
- delete (changeTracker.copy_ as Record<string | symbol, unknown>)[prop]
1033
+ // Forward the delete to the target using Reflect
1034
+ // This respects Object.seal/preventExtensions constraints
1035
+ const result = Reflect.deleteProperty(dobj, prop)
1026
1036
 
1027
- // If the property didn't exist in the original object, removing it
1028
- // should revert to the original state
1029
- if (!hadPropertyInOriginal) {
1030
- delete changeTracker.copy_[stringProp]
1031
- delete changeTracker.assigned_[stringProp]
1037
+ if (result) {
1038
+ // If the property didn't exist in the original object, removing it
1039
+ // should revert to the original state
1040
+ if (!hadPropertyInOriginal) {
1041
+ delete changeTracker.assigned_[stringProp]
1032
1042
 
1033
- // If this is the last change and we're not a nested object,
1034
- // mark the object as unmodified
1035
- if (
1036
- Object.keys(changeTracker.assigned_).length === 0 &&
1037
- Object.getOwnPropertySymbols(changeTracker.assigned_).length === 0
1038
- ) {
1039
- changeTracker.modified = false
1043
+ // If this is the last change and we're not a nested object,
1044
+ // mark the object as unmodified
1045
+ if (
1046
+ Object.keys(changeTracker.assigned_).length === 0 &&
1047
+ Object.getOwnPropertySymbols(changeTracker.assigned_).length ===
1048
+ 0
1049
+ ) {
1050
+ changeTracker.modified = false
1051
+ } else {
1052
+ // We still have changes, keep as modified
1053
+ changeTracker.modified = true
1054
+ }
1040
1055
  } else {
1041
- // We still have changes, keep as modified
1042
- changeTracker.modified = true
1056
+ // Mark this property as deleted
1057
+ changeTracker.assigned_[stringProp] = false
1058
+ markChanged(changeTracker)
1043
1059
  }
1044
- } else {
1045
- // Mark this property as deleted
1046
- changeTracker.assigned_[stringProp] = false
1047
- changeTracker.copy_[stringProp as keyof T] = undefined as T[keyof T]
1048
- markChanged(changeTracker)
1049
1060
  }
1061
+
1062
+ return result
1050
1063
  }
1051
1064
 
1052
1065
  return true
@@ -1060,7 +1073,9 @@ export function createChangeProxy<
1060
1073
  }
1061
1074
 
1062
1075
  // Create a proxy for the target object
1063
- const proxy = createObjectProxy(target)
1076
+ // Use the unfrozen copy_ as the proxy target to avoid Proxy invariant violations
1077
+ // when the original target is frozen (e.g., from Immer)
1078
+ const proxy = createObjectProxy(changeTracker.copy_ as unknown as T)
1064
1079
 
1065
1080
  // Return the proxy and a function to get the changes
1066
1081
  return {
@@ -371,11 +371,11 @@ export type RefsForContext<TContext extends Context> = {
371
371
  > extends true
372
372
  ? IsNonExactNullable<TContext[`schema`][K]> extends true
373
373
  ? // T is both non-exact optional and non-exact nullable (e.g., string | null | undefined)
374
- // Extract the non-undefined and non-null part and place undefined outside
375
- Ref<NonNullable<TContext[`schema`][K]>> | undefined
374
+ // Extract the non-undefined and non-null part and place undefined outside
375
+ Ref<NonNullable<TContext[`schema`][K]>> | undefined
376
376
  : // T is optional (T | undefined) but not exactly undefined, and not nullable
377
- // Extract the non-undefined part and place undefined outside
378
- Ref<NonUndefined<TContext[`schema`][K]>> | undefined
377
+ // Extract the non-undefined part and place undefined outside
378
+ Ref<NonUndefined<TContext[`schema`][K]>> | undefined
379
379
  : IsNonExactNullable<TContext[`schema`][K]> extends true
380
380
  ? // T is nullable (T | null) but not exactly null, and not optional
381
381
  // Extract the non-null part and place null outside
@@ -612,7 +612,7 @@ export type ApplyJoinOptionalityToMergedSchema<
612
612
  // Apply optionality to new schema based on join type
613
613
  [K in keyof TNewSchema]: TJoinType extends `left` | `full`
614
614
  ? // New table becomes optional for left and full joins
615
- TNewSchema[K] | undefined
615
+ TNewSchema[K] | undefined
616
616
  : // New table is required for inner and right joins
617
617
  TNewSchema[K]
618
618
  }
package/src/types.ts CHANGED
@@ -13,9 +13,9 @@ export interface CollectionLike<
13
13
  T extends object = Record<string, unknown>,
14
14
  TKey extends string | number = string | number,
15
15
  > extends Pick<
16
- Collection<T, TKey>,
17
- `get` | `has` | `entries` | `indexes` | `id` | `compareOptions`
18
- > {}
16
+ Collection<T, TKey>,
17
+ `get` | `has` | `entries` | `indexes` | `id` | `compareOptions`
18
+ > {}
19
19
 
20
20
  /**
21
21
  * StringSortOpts - Options for string sorting behavior
@@ -727,8 +727,10 @@ export interface SubscribeChangesOptions {
727
727
  whereExpression?: BasicExpression<boolean>
728
728
  }
729
729
 
730
- export interface SubscribeChangesSnapshotOptions
731
- extends Omit<SubscribeChangesOptions, `includeInitialState`> {
730
+ export interface SubscribeChangesSnapshotOptions extends Omit<
731
+ SubscribeChangesOptions,
732
+ `includeInitialState`
733
+ > {
732
734
  orderBy?: OrderBy
733
735
  limit?: number
734
736
  }