@tanstack/db-sqlite-persistence-core 0.4.3 → 0.4.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.
@@ -2,12 +2,7 @@ import { IR } from '@tanstack/db'
2
2
  import type { LoadSubsetOptions } from '@tanstack/db'
3
3
 
4
4
  export type RemoteSubsetWirePrimitive =
5
- | undefined
6
- | null
7
- | boolean
8
- | string
9
- | number
10
- | bigint
5
+ undefined | null | boolean | string | number | bigint
11
6
 
12
7
  export type RemoteSubsetWireTypedArray =
13
8
  | Int8Array
@@ -92,6 +87,7 @@ export class RemoteSubsetWireValueError extends TypeError {
92
87
  }
93
88
 
94
89
  type ProjectionState = {
90
+ validateOnly: boolean
95
91
  expressions: WeakMap<object, RemoteSubsetWireExpression>
96
92
  expressionArrays: WeakMap<object, Array<RemoteSubsetWireExpression>>
97
93
  stringArrays: WeakMap<object, Array<string>>
@@ -139,6 +135,20 @@ const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/
139
135
  */
140
136
  export function toTransportedLoadSubsetOptions(
141
137
  options: LoadSubsetOptions,
138
+ ): TransportedLoadSubsetOptions {
139
+ return visitLoadSubsetOptions(options, false)
140
+ }
141
+
142
+ /** Checks wire admission without constructing a detached request snapshot. */
143
+ export function validateRemoteSubsetOptions(options: LoadSubsetOptions): void {
144
+ visitLoadSubsetOptions(options, true)
145
+ }
146
+
147
+ // Both modes run the same admission checks. Validation retains input nodes only
148
+ // as cycle markers; it never writes to them or returns them to a caller.
149
+ function visitLoadSubsetOptions(
150
+ options: LoadSubsetOptions,
151
+ validateOnly: boolean,
142
152
  ): TransportedLoadSubsetOptions {
143
153
  assertPlainContainer(options, `options`)
144
154
  assertAllowedProperties(options, `options`, [
@@ -152,6 +162,7 @@ export function toTransportedLoadSubsetOptions(
152
162
  ])
153
163
 
154
164
  const state: ProjectionState = {
165
+ validateOnly,
155
166
  expressions: new WeakMap(),
156
167
  expressionArrays: new WeakMap(),
157
168
  stringArrays: new WeakMap(),
@@ -161,7 +172,9 @@ export function toTransportedLoadSubsetOptions(
161
172
  cursors: new WeakMap(),
162
173
  wireValues: new WeakMap(),
163
174
  }
164
- const projected: TransportedLoadSubsetOptions = {}
175
+ const projected: TransportedLoadSubsetOptions = validateOnly
176
+ ? (options as TransportedLoadSubsetOptions)
177
+ : {}
165
178
  const where = readDataProperty(options, `where`, `options.where`)
166
179
  const orderBy = readDataProperty(options, `orderBy`, `options.orderBy`)
167
180
  const limit = readDataProperty(options, `limit`, `options.limit`)
@@ -169,19 +182,24 @@ export function toTransportedLoadSubsetOptions(
169
182
  const offset = readDataProperty(options, `offset`, `options.offset`)
170
183
 
171
184
  if (where.present && where.value !== undefined) {
172
- projected.where = projectExpression(where.value, `options.where`, state)
185
+ const result = projectExpression(where.value, `options.where`, state)
186
+ if (!state.validateOnly) projected.where = result
173
187
  }
174
188
  if (orderBy.present && orderBy.value !== undefined) {
175
- projected.orderBy = projectOrderBy(orderBy.value, `options.orderBy`, state)
189
+ const result = projectOrderBy(orderBy.value, `options.orderBy`, state)
190
+ if (!state.validateOnly) projected.orderBy = result
176
191
  }
177
192
  if (limit.present && limit.value !== undefined) {
178
- projected.limit = projectNumber(limit.value, `options.limit`)
193
+ const result = projectNumber(limit.value, `options.limit`)
194
+ if (!state.validateOnly) projected.limit = result
179
195
  }
180
196
  if (cursor.present && cursor.value !== undefined) {
181
- projected.cursor = projectCursor(cursor.value, `options.cursor`, state)
197
+ const result = projectCursor(cursor.value, `options.cursor`, state)
198
+ if (!state.validateOnly) projected.cursor = result
182
199
  }
183
200
  if (offset.present && offset.value !== undefined) {
184
- projected.offset = projectNumber(offset.value, `options.offset`)
201
+ const result = projectNumber(offset.value, `options.offset`)
202
+ if (!state.validateOnly) projected.offset = result
185
203
  }
186
204
 
187
205
  return projected
@@ -233,17 +251,18 @@ function projectExpression(
233
251
  case `ref`: {
234
252
  assertExpressionPrototype(object, path, IR.PropRef.prototype)
235
253
  assertAllowedProperties(object, path, [`type`, `path`, `sourceAlias`])
236
- const projected: Extract<RemoteSubsetWireExpression, { type: `ref` }> = {
237
- type: `ref`,
238
- path: [],
239
- }
254
+ const projected: Extract<RemoteSubsetWireExpression, { type: `ref` }> =
255
+ state.validateOnly
256
+ ? (object as Extract<RemoteSubsetWireExpression, { type: `ref` }>)
257
+ : { type: `ref`, path: [] }
240
258
  state.expressions.set(object, projected)
241
259
  const sourcePath = readRequiredDataProperty(
242
260
  object,
243
261
  `path`,
244
262
  `${path}.path`,
245
263
  )
246
- projected.path = projectStringArray(sourcePath, `${path}.path`, state)
264
+ const result = projectStringArray(sourcePath, `${path}.path`, state)
265
+ if (!state.validateOnly) projected.path = result
247
266
  const sourceAlias = readDataProperty(
248
267
  object,
249
268
  `sourceAlias`,
@@ -252,49 +271,50 @@ function projectExpression(
252
271
  if (sourceAlias.present) {
253
272
  if (
254
273
  typeof sourceAlias.value !== `string` ||
255
- projected.path[0] !== sourceAlias.value
274
+ result[0] !== sourceAlias.value
256
275
  ) {
257
276
  throw new RemoteSubsetWireValueError(
258
277
  `${path}.sourceAlias`,
259
278
  `source alias must match the first path segment`,
260
279
  )
261
280
  }
262
- projected.sourceAlias = sourceAlias.value
281
+ if (!state.validateOnly) projected.sourceAlias = sourceAlias.value
263
282
  }
264
283
  return projected
265
284
  }
266
285
  case `val`: {
267
286
  assertExpressionPrototype(object, path, IR.Value.prototype)
268
287
  assertAllowedProperties(object, path, [`type`, `value`])
269
- const projected: Extract<RemoteSubsetWireExpression, { type: `val` }> = {
270
- type: `val`,
271
- value: undefined,
272
- }
288
+ const projected: Extract<RemoteSubsetWireExpression, { type: `val` }> =
289
+ state.validateOnly
290
+ ? (object as Extract<RemoteSubsetWireExpression, { type: `val` }>)
291
+ : { type: `val`, value: undefined }
273
292
  state.expressions.set(object, projected)
274
293
  const sourceValue = readRequiredDataProperty(
275
294
  object,
276
295
  `value`,
277
296
  `${path}.value`,
278
297
  )
279
- projected.value = projectWireValue(sourceValue, `${path}.value`, state)
298
+ const result = projectWireValue(sourceValue, `${path}.value`, state)
299
+ if (!state.validateOnly) projected.value = result
280
300
  return projected
281
301
  }
282
302
  case `func`: {
283
303
  assertExpressionPrototype(object, path, IR.Func.prototype)
284
304
  assertAllowedProperties(object, path, [`type`, `name`, `args`])
285
- const projected = {
286
- type: `func`,
287
- name: ``,
288
- args: [] as Array<RemoteSubsetWireExpression>,
289
- } satisfies RemoteSubsetWireExpression
305
+ const projected: Extract<RemoteSubsetWireExpression, { type: `func` }> =
306
+ state.validateOnly
307
+ ? (object as Extract<RemoteSubsetWireExpression, { type: `func` }>)
308
+ : { type: `func`, name: ``, args: [] }
290
309
  state.expressions.set(object, projected)
291
310
  const name = readRequiredDataProperty(object, `name`, `${path}.name`)
292
311
  if (typeof name !== `string`) {
293
312
  throw new RemoteSubsetWireValueError(`${path}.name`, describe(name))
294
313
  }
295
- projected.name = name
314
+ if (!state.validateOnly) projected.name = name
296
315
  const args = readRequiredDataProperty(object, `args`, `${path}.args`)
297
- projected.args = projectExpressionArray(args, `${path}.args`, state)
316
+ const result = projectExpressionArray(args, `${path}.args`, state)
317
+ if (!state.validateOnly) projected.args = result
298
318
  return projected
299
319
  }
300
320
  default:
@@ -311,7 +331,9 @@ function projectExpressionArray(
311
331
  const existing = state.expressionArrays.get(array)
312
332
  if (existing) return existing
313
333
  assertArrayShape(array, path)
314
- const projected = new Array<RemoteSubsetWireExpression>(array.length)
334
+ const projected = state.validateOnly
335
+ ? (array as Array<RemoteSubsetWireExpression>)
336
+ : new Array<RemoteSubsetWireExpression>(array.length)
315
337
  state.expressionArrays.set(array, projected)
316
338
  for (let index = 0; index < array.length; index++) {
317
339
  if (!(index in array)) {
@@ -320,11 +342,8 @@ function projectExpressionArray(
320
342
  `missing expression`,
321
343
  )
322
344
  }
323
- projected[index] = projectExpression(
324
- array[index],
325
- `${path}[${index}]`,
326
- state,
327
- )
345
+ const result = projectExpression(array[index], `${path}[${index}]`, state)
346
+ if (!state.validateOnly) projected[index] = result
328
347
  }
329
348
  return projected
330
349
  }
@@ -338,14 +357,16 @@ function projectStringArray(
338
357
  const existing = state.stringArrays.get(array)
339
358
  if (existing) return existing
340
359
  assertArrayShape(array, path)
341
- const projected = new Array<string>(array.length)
360
+ const projected = state.validateOnly
361
+ ? (array as Array<string>)
362
+ : new Array<string>(array.length)
342
363
  state.stringArrays.set(array, projected)
343
364
  for (let index = 0; index < array.length; index++) {
344
365
  const entry = array[index]
345
366
  if (!(index in array) || typeof entry !== `string`) {
346
367
  throw new RemoteSubsetWireValueError(`${path}[${index}]`, describe(entry))
347
368
  }
348
- projected[index] = entry
369
+ if (!state.validateOnly) projected[index] = entry
349
370
  }
350
371
  return projected
351
372
  }
@@ -359,7 +380,9 @@ function projectOrderBy(
359
380
  const existing = state.orderByArrays.get(array)
360
381
  if (existing) return existing
361
382
  assertArrayShape(array, path)
362
- const projected = new Array<RemoteSubsetWireOrderByClause>(array.length)
383
+ const projected = state.validateOnly
384
+ ? (array as Array<RemoteSubsetWireOrderByClause>)
385
+ : new Array<RemoteSubsetWireOrderByClause>(array.length)
363
386
  state.orderByArrays.set(array, projected)
364
387
 
365
388
  for (let index = 0; index < array.length; index++) {
@@ -367,20 +390,23 @@ function projectOrderBy(
367
390
  const clause = requirePlainRecord(array[index], clausePath)
368
391
  const existingClause = state.orderByClauses.get(clause)
369
392
  if (existingClause) {
370
- projected[index] = existingClause
393
+ if (!state.validateOnly) projected[index] = existingClause
371
394
  continue
372
395
  }
373
396
  assertAllowedProperties(clause, clausePath, [
374
397
  `expression`,
375
398
  `compareOptions`,
376
399
  ])
377
- const projectedClause = {
378
- expression: undefined as unknown as RemoteSubsetWireExpression,
379
- compareOptions: undefined as unknown as RemoteSubsetWireCompareOptions,
380
- }
381
- projected[index] = projectedClause
400
+ const projectedClause = state.validateOnly
401
+ ? (clause as RemoteSubsetWireOrderByClause)
402
+ : {
403
+ expression: undefined as unknown as RemoteSubsetWireExpression,
404
+ compareOptions:
405
+ undefined as unknown as RemoteSubsetWireCompareOptions,
406
+ }
407
+ if (!state.validateOnly) projected[index] = projectedClause
382
408
  state.orderByClauses.set(clause, projectedClause)
383
- projectedClause.expression = projectExpression(
409
+ const expression = projectExpression(
384
410
  readRequiredDataProperty(
385
411
  clause,
386
412
  `expression`,
@@ -389,7 +415,7 @@ function projectOrderBy(
389
415
  `${clausePath}.expression`,
390
416
  state,
391
417
  )
392
- projectedClause.compareOptions = projectCompareOptions(
418
+ const compareOptions = projectCompareOptions(
393
419
  readRequiredDataProperty(
394
420
  clause,
395
421
  `compareOptions`,
@@ -398,6 +424,10 @@ function projectOrderBy(
398
424
  `${clausePath}.compareOptions`,
399
425
  state,
400
426
  )
427
+ if (!state.validateOnly) {
428
+ projectedClause.expression = expression
429
+ projectedClause.compareOptions = compareOptions
430
+ }
401
431
  }
402
432
  return projected
403
433
  }
@@ -423,7 +453,7 @@ function projectCompareOptions(
423
453
  stringSort?: `lexical` | `locale`
424
454
  locale?: string
425
455
  localeOptions?: RemoteSubsetWireRecord
426
- } = {}
456
+ } = state.validateOnly ? source : {}
427
457
 
428
458
  const direction = readRequiredDataProperty(
429
459
  source,
@@ -436,13 +466,13 @@ function projectCompareOptions(
436
466
  describe(direction),
437
467
  )
438
468
  }
439
- projected.direction = direction
469
+ if (!state.validateOnly) projected.direction = direction
440
470
 
441
471
  const nulls = readRequiredDataProperty(source, `nulls`, `${path}.nulls`)
442
472
  if (nulls !== `first` && nulls !== `last`) {
443
473
  throw new RemoteSubsetWireValueError(`${path}.nulls`, describe(nulls))
444
474
  }
445
- projected.nulls = nulls
475
+ if (!state.validateOnly) projected.nulls = nulls
446
476
 
447
477
  const stringSort = readDataProperty(
448
478
  source,
@@ -456,7 +486,7 @@ function projectCompareOptions(
456
486
  describe(stringSort.value),
457
487
  )
458
488
  }
459
- projected.stringSort = stringSort.value
489
+ if (!state.validateOnly) projected.stringSort = stringSort.value
460
490
  }
461
491
  if (projected.stringSort !== `lexical`) {
462
492
  const locale = readDataProperty(source, `locale`, `${path}.locale`)
@@ -467,7 +497,7 @@ function projectCompareOptions(
467
497
  describe(locale.value),
468
498
  )
469
499
  }
470
- projected.locale = locale.value
500
+ if (!state.validateOnly) projected.locale = locale.value
471
501
  }
472
502
  const localeOptions = readDataProperty(
473
503
  source,
@@ -475,11 +505,12 @@ function projectCompareOptions(
475
505
  `${path}.localeOptions`,
476
506
  )
477
507
  if (localeOptions.present && localeOptions.value !== undefined) {
478
- projected.localeOptions = projectWireRecord(
508
+ const result = projectWireRecord(
479
509
  localeOptions.value,
480
510
  `${path}.localeOptions`,
481
511
  state,
482
512
  )
513
+ if (!state.validateOnly) projected.localeOptions = result
483
514
  }
484
515
  }
485
516
  const result = projected as RemoteSubsetWireCompareOptions
@@ -500,21 +531,27 @@ function projectCursor(
500
531
  `whereCurrent`,
501
532
  `lastKey`,
502
533
  ])
503
- const projected: RemoteSubsetWireCursor = {
504
- whereFrom: undefined as unknown as RemoteSubsetWireExpression,
505
- whereCurrent: undefined as unknown as RemoteSubsetWireExpression,
506
- }
534
+ const projected: RemoteSubsetWireCursor = state.validateOnly
535
+ ? (source as RemoteSubsetWireCursor)
536
+ : {
537
+ whereFrom: undefined as unknown as RemoteSubsetWireExpression,
538
+ whereCurrent: undefined as unknown as RemoteSubsetWireExpression,
539
+ }
507
540
  state.cursors.set(source, projected)
508
- projected.whereFrom = projectExpression(
541
+ const whereFrom = projectExpression(
509
542
  readRequiredDataProperty(source, `whereFrom`, `${path}.whereFrom`),
510
543
  `${path}.whereFrom`,
511
544
  state,
512
545
  )
513
- projected.whereCurrent = projectExpression(
546
+ const whereCurrent = projectExpression(
514
547
  readRequiredDataProperty(source, `whereCurrent`, `${path}.whereCurrent`),
515
548
  `${path}.whereCurrent`,
516
549
  state,
517
550
  )
551
+ if (!state.validateOnly) {
552
+ projected.whereFrom = whereFrom
553
+ projected.whereCurrent = whereCurrent
554
+ }
518
555
  const lastKey = readDataProperty(source, `lastKey`, `${path}.lastKey`)
519
556
  if (lastKey.present && lastKey.value !== undefined) {
520
557
  if (
@@ -526,7 +563,7 @@ function projectCursor(
526
563
  describe(lastKey.value),
527
564
  )
528
565
  }
529
- projected.lastKey = lastKey.value
566
+ if (!state.validateOnly) projected.lastKey = lastKey.value
530
567
  }
531
568
  return projected
532
569
  }
@@ -562,14 +599,16 @@ function projectWireValue(
562
599
  if (value instanceof NativeDate) {
563
600
  assertExactPrototype(value, NativeDate.prototype, path)
564
601
  assertNoOwnProperties(value, path)
565
- const projected = new NativeDate(value.getTime())
602
+ const time = value.getTime()
603
+ const projected = state.validateOnly ? value : new NativeDate(time)
566
604
  state.wireValues.set(value, projected)
567
605
  return projected
568
606
  }
569
607
  if (value instanceof RegExp) {
570
608
  assertExactPrototype(value, RegExp.prototype, path)
571
609
  assertRegExpShape(value, path)
572
- const projected = new RegExp(value.source, value.flags)
610
+ const { source, flags } = value
611
+ const projected = state.validateOnly ? value : new RegExp(source, flags)
573
612
  state.wireValues.set(value, projected)
574
613
  return projected
575
614
  }
@@ -579,7 +618,13 @@ function projectWireValue(
579
618
  assertFixedArrayBuffer(value, path)
580
619
  let projected: ArrayBuffer
581
620
  try {
582
- projected = value.slice(0)
621
+ if (state.validateOnly) {
622
+ // A zero-length view checks detachment without copying the backing bytes.
623
+ new Uint8Array(value, 0, 0)
624
+ projected = value
625
+ } else {
626
+ projected = value.slice(0)
627
+ }
583
628
  } catch {
584
629
  throw new RemoteSubsetWireValueError(path, `detached ArrayBuffer`)
585
630
  }
@@ -603,7 +648,9 @@ function projectWireValue(
603
648
  if (!(projectedBuffer instanceof ArrayBuffer)) {
604
649
  throw new RemoteSubsetWireValueError(path, `shared DataView buffer`)
605
650
  }
606
- const projected = new DataView(projectedBuffer, byteOffset, byteLength)
651
+ const projected = state.validateOnly
652
+ ? value
653
+ : new DataView(projectedBuffer, byteOffset, byteLength)
607
654
  state.wireValues.set(value, projected)
608
655
  return projected
609
656
  }
@@ -631,7 +678,9 @@ function projectWireValue(
631
678
  }
632
679
  let projected: RemoteSubsetWireTypedArray
633
680
  try {
634
- projected = new typedArrayConstructor(projectedBuffer, byteOffset, length)
681
+ projected = state.validateOnly
682
+ ? typedArray
683
+ : new typedArrayConstructor(projectedBuffer, byteOffset, length)
635
684
  } catch {
636
685
  throw new RemoteSubsetWireValueError(path, `detached typed array`)
637
686
  }
@@ -653,15 +702,14 @@ function projectWireArray(
653
702
  state: ProjectionState,
654
703
  ): Array<RemoteSubsetWireValue> {
655
704
  assertArrayShape(value, path)
656
- const projected = new Array<RemoteSubsetWireValue>(value.length)
705
+ const projected = state.validateOnly
706
+ ? (value as Array<RemoteSubsetWireValue>)
707
+ : new Array<RemoteSubsetWireValue>(value.length)
657
708
  state.wireValues.set(value, projected)
658
709
  for (let index = 0; index < value.length; index++) {
659
710
  if (index in value) {
660
- projected[index] = projectWireValue(
661
- value[index],
662
- `${path}[${index}]`,
663
- state,
664
- )
711
+ const result = projectWireValue(value[index], `${path}[${index}]`, state)
712
+ if (!state.validateOnly) projected[index] = result
665
713
  }
666
714
  }
667
715
  return projected
@@ -674,14 +722,23 @@ function projectWireMap(
674
722
  ): Map<RemoteSubsetWireValue, RemoteSubsetWireValue> {
675
723
  assertExactPrototype(value, Map.prototype, path)
676
724
  assertNoOwnProperties(value, path)
677
- const projected = new Map<RemoteSubsetWireValue, RemoteSubsetWireValue>()
725
+ const projected = state.validateOnly
726
+ ? (value as Map<RemoteSubsetWireValue, RemoteSubsetWireValue>)
727
+ : new Map<RemoteSubsetWireValue, RemoteSubsetWireValue>()
678
728
  state.wireValues.set(value, projected)
679
729
  let index = 0
680
730
  for (const [key, entry] of value) {
681
- projected.set(
682
- projectWireValue(key, `${path}.entries[${index}].key`, state),
683
- projectWireValue(entry, `${path}.entries[${index}].value`, state),
731
+ const projectedKey = projectWireValue(
732
+ key,
733
+ `${path}.entries[${index}].key`,
734
+ state,
684
735
  )
736
+ const projectedValue = projectWireValue(
737
+ entry,
738
+ `${path}.entries[${index}].value`,
739
+ state,
740
+ )
741
+ if (!state.validateOnly) projected.set(projectedKey, projectedValue)
685
742
  index++
686
743
  }
687
744
  return projected
@@ -694,11 +751,14 @@ function projectWireSet(
694
751
  ): Set<RemoteSubsetWireValue> {
695
752
  assertExactPrototype(value, Set.prototype, path)
696
753
  assertNoOwnProperties(value, path)
697
- const projected = new Set<RemoteSubsetWireValue>()
754
+ const projected = state.validateOnly
755
+ ? (value as Set<RemoteSubsetWireValue>)
756
+ : new Set<RemoteSubsetWireValue>()
698
757
  state.wireValues.set(value, projected)
699
758
  let index = 0
700
759
  for (const entry of value) {
701
- projected.add(projectWireValue(entry, `${path}.values[${index}]`, state))
760
+ const result = projectWireValue(entry, `${path}.values[${index}]`, state)
761
+ if (!state.validateOnly) projected.add(result)
702
762
  index++
703
763
  }
704
764
  return projected
@@ -712,7 +772,9 @@ function projectWireRecord(
712
772
  const source = requirePlainRecord(value, path)
713
773
  const existing = state.wireValues.get(source)
714
774
  if (existing) return existing as RemoteSubsetWireRecord
715
- const projected: RemoteSubsetWireRecord = {}
775
+ const projected: RemoteSubsetWireRecord = state.validateOnly
776
+ ? (source as RemoteSubsetWireRecord)
777
+ : {}
716
778
  state.wireValues.set(source, projected)
717
779
 
718
780
  for (const key of ownKeys(source, path)) {
@@ -724,12 +786,15 @@ function projectWireRecord(
724
786
  if (!descriptor || !descriptor.enumerable || !(`value` in descriptor)) {
725
787
  throw new RemoteSubsetWireValueError(childPath, `non-data property`)
726
788
  }
727
- Object.defineProperty(projected, key, {
728
- value: projectWireValue(descriptor.value, childPath, state),
729
- enumerable: true,
730
- writable: true,
731
- configurable: true,
732
- })
789
+ const result = projectWireValue(descriptor.value, childPath, state)
790
+ if (!state.validateOnly) {
791
+ Object.defineProperty(projected, key, {
792
+ value: result,
793
+ enumerable: true,
794
+ writable: true,
795
+ configurable: true,
796
+ })
797
+ }
733
798
  }
734
799
  return projected
735
800
  }