@tanstack/db-sqlite-persistence-core 0.4.4 → 0.4.7

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.
@@ -87,6 +87,7 @@ export class RemoteSubsetWireValueError extends TypeError {
87
87
  }
88
88
 
89
89
  type ProjectionState = {
90
+ validateOnly: boolean
90
91
  expressions: WeakMap<object, RemoteSubsetWireExpression>
91
92
  expressionArrays: WeakMap<object, Array<RemoteSubsetWireExpression>>
92
93
  stringArrays: WeakMap<object, Array<string>>
@@ -134,6 +135,20 @@ const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/
134
135
  */
135
136
  export function toTransportedLoadSubsetOptions(
136
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,
137
152
  ): TransportedLoadSubsetOptions {
138
153
  assertPlainContainer(options, `options`)
139
154
  assertAllowedProperties(options, `options`, [
@@ -147,6 +162,7 @@ export function toTransportedLoadSubsetOptions(
147
162
  ])
148
163
 
149
164
  const state: ProjectionState = {
165
+ validateOnly,
150
166
  expressions: new WeakMap(),
151
167
  expressionArrays: new WeakMap(),
152
168
  stringArrays: new WeakMap(),
@@ -156,7 +172,9 @@ export function toTransportedLoadSubsetOptions(
156
172
  cursors: new WeakMap(),
157
173
  wireValues: new WeakMap(),
158
174
  }
159
- const projected: TransportedLoadSubsetOptions = {}
175
+ const projected: TransportedLoadSubsetOptions = validateOnly
176
+ ? (options as TransportedLoadSubsetOptions)
177
+ : {}
160
178
  const where = readDataProperty(options, `where`, `options.where`)
161
179
  const orderBy = readDataProperty(options, `orderBy`, `options.orderBy`)
162
180
  const limit = readDataProperty(options, `limit`, `options.limit`)
@@ -164,19 +182,24 @@ export function toTransportedLoadSubsetOptions(
164
182
  const offset = readDataProperty(options, `offset`, `options.offset`)
165
183
 
166
184
  if (where.present && where.value !== undefined) {
167
- projected.where = projectExpression(where.value, `options.where`, state)
185
+ const result = projectExpression(where.value, `options.where`, state)
186
+ if (!state.validateOnly) projected.where = result
168
187
  }
169
188
  if (orderBy.present && orderBy.value !== undefined) {
170
- projected.orderBy = projectOrderBy(orderBy.value, `options.orderBy`, state)
189
+ const result = projectOrderBy(orderBy.value, `options.orderBy`, state)
190
+ if (!state.validateOnly) projected.orderBy = result
171
191
  }
172
192
  if (limit.present && limit.value !== undefined) {
173
- projected.limit = projectNumber(limit.value, `options.limit`)
193
+ const result = projectNumber(limit.value, `options.limit`)
194
+ if (!state.validateOnly) projected.limit = result
174
195
  }
175
196
  if (cursor.present && cursor.value !== undefined) {
176
- projected.cursor = projectCursor(cursor.value, `options.cursor`, state)
197
+ const result = projectCursor(cursor.value, `options.cursor`, state)
198
+ if (!state.validateOnly) projected.cursor = result
177
199
  }
178
200
  if (offset.present && offset.value !== undefined) {
179
- projected.offset = projectNumber(offset.value, `options.offset`)
201
+ const result = projectNumber(offset.value, `options.offset`)
202
+ if (!state.validateOnly) projected.offset = result
180
203
  }
181
204
 
182
205
  return projected
@@ -228,17 +251,18 @@ function projectExpression(
228
251
  case `ref`: {
229
252
  assertExpressionPrototype(object, path, IR.PropRef.prototype)
230
253
  assertAllowedProperties(object, path, [`type`, `path`, `sourceAlias`])
231
- const projected: Extract<RemoteSubsetWireExpression, { type: `ref` }> = {
232
- type: `ref`,
233
- path: [],
234
- }
254
+ const projected: Extract<RemoteSubsetWireExpression, { type: `ref` }> =
255
+ state.validateOnly
256
+ ? (object as Extract<RemoteSubsetWireExpression, { type: `ref` }>)
257
+ : { type: `ref`, path: [] }
235
258
  state.expressions.set(object, projected)
236
259
  const sourcePath = readRequiredDataProperty(
237
260
  object,
238
261
  `path`,
239
262
  `${path}.path`,
240
263
  )
241
- projected.path = projectStringArray(sourcePath, `${path}.path`, state)
264
+ const result = projectStringArray(sourcePath, `${path}.path`, state)
265
+ if (!state.validateOnly) projected.path = result
242
266
  const sourceAlias = readDataProperty(
243
267
  object,
244
268
  `sourceAlias`,
@@ -247,49 +271,50 @@ function projectExpression(
247
271
  if (sourceAlias.present) {
248
272
  if (
249
273
  typeof sourceAlias.value !== `string` ||
250
- projected.path[0] !== sourceAlias.value
274
+ result[0] !== sourceAlias.value
251
275
  ) {
252
276
  throw new RemoteSubsetWireValueError(
253
277
  `${path}.sourceAlias`,
254
278
  `source alias must match the first path segment`,
255
279
  )
256
280
  }
257
- projected.sourceAlias = sourceAlias.value
281
+ if (!state.validateOnly) projected.sourceAlias = sourceAlias.value
258
282
  }
259
283
  return projected
260
284
  }
261
285
  case `val`: {
262
286
  assertExpressionPrototype(object, path, IR.Value.prototype)
263
287
  assertAllowedProperties(object, path, [`type`, `value`])
264
- const projected: Extract<RemoteSubsetWireExpression, { type: `val` }> = {
265
- type: `val`,
266
- value: undefined,
267
- }
288
+ const projected: Extract<RemoteSubsetWireExpression, { type: `val` }> =
289
+ state.validateOnly
290
+ ? (object as Extract<RemoteSubsetWireExpression, { type: `val` }>)
291
+ : { type: `val`, value: undefined }
268
292
  state.expressions.set(object, projected)
269
293
  const sourceValue = readRequiredDataProperty(
270
294
  object,
271
295
  `value`,
272
296
  `${path}.value`,
273
297
  )
274
- projected.value = projectWireValue(sourceValue, `${path}.value`, state)
298
+ const result = projectWireValue(sourceValue, `${path}.value`, state)
299
+ if (!state.validateOnly) projected.value = result
275
300
  return projected
276
301
  }
277
302
  case `func`: {
278
303
  assertExpressionPrototype(object, path, IR.Func.prototype)
279
304
  assertAllowedProperties(object, path, [`type`, `name`, `args`])
280
- const projected = {
281
- type: `func`,
282
- name: ``,
283
- args: [] as Array<RemoteSubsetWireExpression>,
284
- } satisfies RemoteSubsetWireExpression
305
+ const projected: Extract<RemoteSubsetWireExpression, { type: `func` }> =
306
+ state.validateOnly
307
+ ? (object as Extract<RemoteSubsetWireExpression, { type: `func` }>)
308
+ : { type: `func`, name: ``, args: [] }
285
309
  state.expressions.set(object, projected)
286
310
  const name = readRequiredDataProperty(object, `name`, `${path}.name`)
287
311
  if (typeof name !== `string`) {
288
312
  throw new RemoteSubsetWireValueError(`${path}.name`, describe(name))
289
313
  }
290
- projected.name = name
314
+ if (!state.validateOnly) projected.name = name
291
315
  const args = readRequiredDataProperty(object, `args`, `${path}.args`)
292
- projected.args = projectExpressionArray(args, `${path}.args`, state)
316
+ const result = projectExpressionArray(args, `${path}.args`, state)
317
+ if (!state.validateOnly) projected.args = result
293
318
  return projected
294
319
  }
295
320
  default:
@@ -306,7 +331,9 @@ function projectExpressionArray(
306
331
  const existing = state.expressionArrays.get(array)
307
332
  if (existing) return existing
308
333
  assertArrayShape(array, path)
309
- const projected = new Array<RemoteSubsetWireExpression>(array.length)
334
+ const projected = state.validateOnly
335
+ ? (array as Array<RemoteSubsetWireExpression>)
336
+ : new Array<RemoteSubsetWireExpression>(array.length)
310
337
  state.expressionArrays.set(array, projected)
311
338
  for (let index = 0; index < array.length; index++) {
312
339
  if (!(index in array)) {
@@ -315,11 +342,8 @@ function projectExpressionArray(
315
342
  `missing expression`,
316
343
  )
317
344
  }
318
- projected[index] = projectExpression(
319
- array[index],
320
- `${path}[${index}]`,
321
- state,
322
- )
345
+ const result = projectExpression(array[index], `${path}[${index}]`, state)
346
+ if (!state.validateOnly) projected[index] = result
323
347
  }
324
348
  return projected
325
349
  }
@@ -333,14 +357,16 @@ function projectStringArray(
333
357
  const existing = state.stringArrays.get(array)
334
358
  if (existing) return existing
335
359
  assertArrayShape(array, path)
336
- const projected = new Array<string>(array.length)
360
+ const projected = state.validateOnly
361
+ ? (array as Array<string>)
362
+ : new Array<string>(array.length)
337
363
  state.stringArrays.set(array, projected)
338
364
  for (let index = 0; index < array.length; index++) {
339
365
  const entry = array[index]
340
366
  if (!(index in array) || typeof entry !== `string`) {
341
367
  throw new RemoteSubsetWireValueError(`${path}[${index}]`, describe(entry))
342
368
  }
343
- projected[index] = entry
369
+ if (!state.validateOnly) projected[index] = entry
344
370
  }
345
371
  return projected
346
372
  }
@@ -354,7 +380,9 @@ function projectOrderBy(
354
380
  const existing = state.orderByArrays.get(array)
355
381
  if (existing) return existing
356
382
  assertArrayShape(array, path)
357
- const projected = new Array<RemoteSubsetWireOrderByClause>(array.length)
383
+ const projected = state.validateOnly
384
+ ? (array as Array<RemoteSubsetWireOrderByClause>)
385
+ : new Array<RemoteSubsetWireOrderByClause>(array.length)
358
386
  state.orderByArrays.set(array, projected)
359
387
 
360
388
  for (let index = 0; index < array.length; index++) {
@@ -362,20 +390,23 @@ function projectOrderBy(
362
390
  const clause = requirePlainRecord(array[index], clausePath)
363
391
  const existingClause = state.orderByClauses.get(clause)
364
392
  if (existingClause) {
365
- projected[index] = existingClause
393
+ if (!state.validateOnly) projected[index] = existingClause
366
394
  continue
367
395
  }
368
396
  assertAllowedProperties(clause, clausePath, [
369
397
  `expression`,
370
398
  `compareOptions`,
371
399
  ])
372
- const projectedClause = {
373
- expression: undefined as unknown as RemoteSubsetWireExpression,
374
- compareOptions: undefined as unknown as RemoteSubsetWireCompareOptions,
375
- }
376
- 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
377
408
  state.orderByClauses.set(clause, projectedClause)
378
- projectedClause.expression = projectExpression(
409
+ const expression = projectExpression(
379
410
  readRequiredDataProperty(
380
411
  clause,
381
412
  `expression`,
@@ -384,7 +415,7 @@ function projectOrderBy(
384
415
  `${clausePath}.expression`,
385
416
  state,
386
417
  )
387
- projectedClause.compareOptions = projectCompareOptions(
418
+ const compareOptions = projectCompareOptions(
388
419
  readRequiredDataProperty(
389
420
  clause,
390
421
  `compareOptions`,
@@ -393,6 +424,10 @@ function projectOrderBy(
393
424
  `${clausePath}.compareOptions`,
394
425
  state,
395
426
  )
427
+ if (!state.validateOnly) {
428
+ projectedClause.expression = expression
429
+ projectedClause.compareOptions = compareOptions
430
+ }
396
431
  }
397
432
  return projected
398
433
  }
@@ -418,7 +453,7 @@ function projectCompareOptions(
418
453
  stringSort?: `lexical` | `locale`
419
454
  locale?: string
420
455
  localeOptions?: RemoteSubsetWireRecord
421
- } = {}
456
+ } = state.validateOnly ? source : {}
422
457
 
423
458
  const direction = readRequiredDataProperty(
424
459
  source,
@@ -431,13 +466,13 @@ function projectCompareOptions(
431
466
  describe(direction),
432
467
  )
433
468
  }
434
- projected.direction = direction
469
+ if (!state.validateOnly) projected.direction = direction
435
470
 
436
471
  const nulls = readRequiredDataProperty(source, `nulls`, `${path}.nulls`)
437
472
  if (nulls !== `first` && nulls !== `last`) {
438
473
  throw new RemoteSubsetWireValueError(`${path}.nulls`, describe(nulls))
439
474
  }
440
- projected.nulls = nulls
475
+ if (!state.validateOnly) projected.nulls = nulls
441
476
 
442
477
  const stringSort = readDataProperty(
443
478
  source,
@@ -451,7 +486,7 @@ function projectCompareOptions(
451
486
  describe(stringSort.value),
452
487
  )
453
488
  }
454
- projected.stringSort = stringSort.value
489
+ if (!state.validateOnly) projected.stringSort = stringSort.value
455
490
  }
456
491
  if (projected.stringSort !== `lexical`) {
457
492
  const locale = readDataProperty(source, `locale`, `${path}.locale`)
@@ -462,7 +497,7 @@ function projectCompareOptions(
462
497
  describe(locale.value),
463
498
  )
464
499
  }
465
- projected.locale = locale.value
500
+ if (!state.validateOnly) projected.locale = locale.value
466
501
  }
467
502
  const localeOptions = readDataProperty(
468
503
  source,
@@ -470,11 +505,12 @@ function projectCompareOptions(
470
505
  `${path}.localeOptions`,
471
506
  )
472
507
  if (localeOptions.present && localeOptions.value !== undefined) {
473
- projected.localeOptions = projectWireRecord(
508
+ const result = projectWireRecord(
474
509
  localeOptions.value,
475
510
  `${path}.localeOptions`,
476
511
  state,
477
512
  )
513
+ if (!state.validateOnly) projected.localeOptions = result
478
514
  }
479
515
  }
480
516
  const result = projected as RemoteSubsetWireCompareOptions
@@ -495,21 +531,27 @@ function projectCursor(
495
531
  `whereCurrent`,
496
532
  `lastKey`,
497
533
  ])
498
- const projected: RemoteSubsetWireCursor = {
499
- whereFrom: undefined as unknown as RemoteSubsetWireExpression,
500
- whereCurrent: undefined as unknown as RemoteSubsetWireExpression,
501
- }
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
+ }
502
540
  state.cursors.set(source, projected)
503
- projected.whereFrom = projectExpression(
541
+ const whereFrom = projectExpression(
504
542
  readRequiredDataProperty(source, `whereFrom`, `${path}.whereFrom`),
505
543
  `${path}.whereFrom`,
506
544
  state,
507
545
  )
508
- projected.whereCurrent = projectExpression(
546
+ const whereCurrent = projectExpression(
509
547
  readRequiredDataProperty(source, `whereCurrent`, `${path}.whereCurrent`),
510
548
  `${path}.whereCurrent`,
511
549
  state,
512
550
  )
551
+ if (!state.validateOnly) {
552
+ projected.whereFrom = whereFrom
553
+ projected.whereCurrent = whereCurrent
554
+ }
513
555
  const lastKey = readDataProperty(source, `lastKey`, `${path}.lastKey`)
514
556
  if (lastKey.present && lastKey.value !== undefined) {
515
557
  if (
@@ -521,7 +563,7 @@ function projectCursor(
521
563
  describe(lastKey.value),
522
564
  )
523
565
  }
524
- projected.lastKey = lastKey.value
566
+ if (!state.validateOnly) projected.lastKey = lastKey.value
525
567
  }
526
568
  return projected
527
569
  }
@@ -557,14 +599,16 @@ function projectWireValue(
557
599
  if (value instanceof NativeDate) {
558
600
  assertExactPrototype(value, NativeDate.prototype, path)
559
601
  assertNoOwnProperties(value, path)
560
- const projected = new NativeDate(value.getTime())
602
+ const time = value.getTime()
603
+ const projected = state.validateOnly ? value : new NativeDate(time)
561
604
  state.wireValues.set(value, projected)
562
605
  return projected
563
606
  }
564
607
  if (value instanceof RegExp) {
565
608
  assertExactPrototype(value, RegExp.prototype, path)
566
609
  assertRegExpShape(value, path)
567
- const projected = new RegExp(value.source, value.flags)
610
+ const { source, flags } = value
611
+ const projected = state.validateOnly ? value : new RegExp(source, flags)
568
612
  state.wireValues.set(value, projected)
569
613
  return projected
570
614
  }
@@ -574,7 +618,13 @@ function projectWireValue(
574
618
  assertFixedArrayBuffer(value, path)
575
619
  let projected: ArrayBuffer
576
620
  try {
577
- 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
+ }
578
628
  } catch {
579
629
  throw new RemoteSubsetWireValueError(path, `detached ArrayBuffer`)
580
630
  }
@@ -598,7 +648,9 @@ function projectWireValue(
598
648
  if (!(projectedBuffer instanceof ArrayBuffer)) {
599
649
  throw new RemoteSubsetWireValueError(path, `shared DataView buffer`)
600
650
  }
601
- const projected = new DataView(projectedBuffer, byteOffset, byteLength)
651
+ const projected = state.validateOnly
652
+ ? value
653
+ : new DataView(projectedBuffer, byteOffset, byteLength)
602
654
  state.wireValues.set(value, projected)
603
655
  return projected
604
656
  }
@@ -626,7 +678,9 @@ function projectWireValue(
626
678
  }
627
679
  let projected: RemoteSubsetWireTypedArray
628
680
  try {
629
- projected = new typedArrayConstructor(projectedBuffer, byteOffset, length)
681
+ projected = state.validateOnly
682
+ ? typedArray
683
+ : new typedArrayConstructor(projectedBuffer, byteOffset, length)
630
684
  } catch {
631
685
  throw new RemoteSubsetWireValueError(path, `detached typed array`)
632
686
  }
@@ -648,15 +702,14 @@ function projectWireArray(
648
702
  state: ProjectionState,
649
703
  ): Array<RemoteSubsetWireValue> {
650
704
  assertArrayShape(value, path)
651
- const projected = new Array<RemoteSubsetWireValue>(value.length)
705
+ const projected = state.validateOnly
706
+ ? (value as Array<RemoteSubsetWireValue>)
707
+ : new Array<RemoteSubsetWireValue>(value.length)
652
708
  state.wireValues.set(value, projected)
653
709
  for (let index = 0; index < value.length; index++) {
654
710
  if (index in value) {
655
- projected[index] = projectWireValue(
656
- value[index],
657
- `${path}[${index}]`,
658
- state,
659
- )
711
+ const result = projectWireValue(value[index], `${path}[${index}]`, state)
712
+ if (!state.validateOnly) projected[index] = result
660
713
  }
661
714
  }
662
715
  return projected
@@ -669,14 +722,23 @@ function projectWireMap(
669
722
  ): Map<RemoteSubsetWireValue, RemoteSubsetWireValue> {
670
723
  assertExactPrototype(value, Map.prototype, path)
671
724
  assertNoOwnProperties(value, path)
672
- const projected = new Map<RemoteSubsetWireValue, RemoteSubsetWireValue>()
725
+ const projected = state.validateOnly
726
+ ? (value as Map<RemoteSubsetWireValue, RemoteSubsetWireValue>)
727
+ : new Map<RemoteSubsetWireValue, RemoteSubsetWireValue>()
673
728
  state.wireValues.set(value, projected)
674
729
  let index = 0
675
730
  for (const [key, entry] of value) {
676
- projected.set(
677
- projectWireValue(key, `${path}.entries[${index}].key`, state),
678
- projectWireValue(entry, `${path}.entries[${index}].value`, state),
731
+ const projectedKey = projectWireValue(
732
+ key,
733
+ `${path}.entries[${index}].key`,
734
+ state,
679
735
  )
736
+ const projectedValue = projectWireValue(
737
+ entry,
738
+ `${path}.entries[${index}].value`,
739
+ state,
740
+ )
741
+ if (!state.validateOnly) projected.set(projectedKey, projectedValue)
680
742
  index++
681
743
  }
682
744
  return projected
@@ -689,11 +751,14 @@ function projectWireSet(
689
751
  ): Set<RemoteSubsetWireValue> {
690
752
  assertExactPrototype(value, Set.prototype, path)
691
753
  assertNoOwnProperties(value, path)
692
- const projected = new Set<RemoteSubsetWireValue>()
754
+ const projected = state.validateOnly
755
+ ? (value as Set<RemoteSubsetWireValue>)
756
+ : new Set<RemoteSubsetWireValue>()
693
757
  state.wireValues.set(value, projected)
694
758
  let index = 0
695
759
  for (const entry of value) {
696
- projected.add(projectWireValue(entry, `${path}.values[${index}]`, state))
760
+ const result = projectWireValue(entry, `${path}.values[${index}]`, state)
761
+ if (!state.validateOnly) projected.add(result)
697
762
  index++
698
763
  }
699
764
  return projected
@@ -707,7 +772,9 @@ function projectWireRecord(
707
772
  const source = requirePlainRecord(value, path)
708
773
  const existing = state.wireValues.get(source)
709
774
  if (existing) return existing as RemoteSubsetWireRecord
710
- const projected: RemoteSubsetWireRecord = {}
775
+ const projected: RemoteSubsetWireRecord = state.validateOnly
776
+ ? (source as RemoteSubsetWireRecord)
777
+ : {}
711
778
  state.wireValues.set(source, projected)
712
779
 
713
780
  for (const key of ownKeys(source, path)) {
@@ -719,12 +786,15 @@ function projectWireRecord(
719
786
  if (!descriptor || !descriptor.enumerable || !(`value` in descriptor)) {
720
787
  throw new RemoteSubsetWireValueError(childPath, `non-data property`)
721
788
  }
722
- Object.defineProperty(projected, key, {
723
- value: projectWireValue(descriptor.value, childPath, state),
724
- enumerable: true,
725
- writable: true,
726
- configurable: true,
727
- })
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
+ }
728
798
  }
729
799
  return projected
730
800
  }