@tanstack/db-sqlite-persistence-core 0.2.23 → 0.4.0

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 (58) hide show
  1. package/README.md +258 -7
  2. package/dist/cjs/broadcast-coordinator.cjs +1689 -0
  3. package/dist/cjs/broadcast-coordinator.cjs.map +1 -0
  4. package/dist/cjs/broadcast-coordinator.d.cts +116 -0
  5. package/dist/cjs/errors.cjs +73 -0
  6. package/dist/cjs/errors.cjs.map +1 -1
  7. package/dist/cjs/errors.d.cts +44 -0
  8. package/dist/cjs/index.cjs +19 -0
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/index.d.cts +2 -0
  11. package/dist/cjs/persisted.cjs +1782 -394
  12. package/dist/cjs/persisted.cjs.map +1 -1
  13. package/dist/cjs/persisted.d.cts +176 -11
  14. package/dist/cjs/remote-subset-owner.cjs +20 -0
  15. package/dist/cjs/remote-subset-owner.cjs.map +1 -0
  16. package/dist/cjs/remote-subset-owner.d.cts +7 -0
  17. package/dist/cjs/remote-subset-wire.cjs +681 -0
  18. package/dist/cjs/remote-subset-wire.cjs.map +1 -0
  19. package/dist/cjs/remote-subset-wire.d.cts +59 -0
  20. package/dist/cjs/sqlite-core-adapter.cjs +969 -364
  21. package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
  22. package/dist/cjs/sqlite-core-adapter.d.cts +45 -1
  23. package/dist/cjs/sqlite-value.cjs +31 -0
  24. package/dist/cjs/sqlite-value.cjs.map +1 -0
  25. package/dist/cjs/sqlite-value.d.cts +10 -0
  26. package/dist/esm/broadcast-coordinator.d.ts +116 -0
  27. package/dist/esm/broadcast-coordinator.js +1689 -0
  28. package/dist/esm/broadcast-coordinator.js.map +1 -0
  29. package/dist/esm/errors.d.ts +44 -0
  30. package/dist/esm/errors.js +74 -1
  31. package/dist/esm/errors.js.map +1 -1
  32. package/dist/esm/index.d.ts +2 -0
  33. package/dist/esm/index.js +21 -2
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/persisted.d.ts +176 -11
  36. package/dist/esm/persisted.js +1784 -396
  37. package/dist/esm/persisted.js.map +1 -1
  38. package/dist/esm/remote-subset-owner.d.ts +7 -0
  39. package/dist/esm/remote-subset-owner.js +20 -0
  40. package/dist/esm/remote-subset-owner.js.map +1 -0
  41. package/dist/esm/remote-subset-wire.d.ts +59 -0
  42. package/dist/esm/remote-subset-wire.js +681 -0
  43. package/dist/esm/remote-subset-wire.js.map +1 -0
  44. package/dist/esm/sqlite-core-adapter.d.ts +45 -1
  45. package/dist/esm/sqlite-core-adapter.js +955 -350
  46. package/dist/esm/sqlite-core-adapter.js.map +1 -1
  47. package/dist/esm/sqlite-value.d.ts +10 -0
  48. package/dist/esm/sqlite-value.js +31 -0
  49. package/dist/esm/sqlite-value.js.map +1 -0
  50. package/package.json +12 -2
  51. package/src/broadcast-coordinator.ts +2456 -0
  52. package/src/errors.ts +120 -0
  53. package/src/index.ts +2 -0
  54. package/src/persisted.ts +2617 -442
  55. package/src/remote-subset-owner.ts +36 -0
  56. package/src/remote-subset-wire.ts +946 -0
  57. package/src/sqlite-core-adapter.ts +1297 -410
  58. package/src/sqlite-value.ts +30 -0
@@ -0,0 +1,946 @@
1
+ import { IR } from '@tanstack/db'
2
+ import type { LoadSubsetOptions } from '@tanstack/db'
3
+
4
+ export type RemoteSubsetWirePrimitive =
5
+ | undefined
6
+ | null
7
+ | boolean
8
+ | string
9
+ | number
10
+ | bigint
11
+
12
+ export type RemoteSubsetWireTypedArray =
13
+ | Int8Array
14
+ | Uint8Array
15
+ | Uint8ClampedArray
16
+ | Int16Array
17
+ | Uint16Array
18
+ | Int32Array
19
+ | Uint32Array
20
+ | Float32Array
21
+ | Float64Array
22
+ | BigInt64Array
23
+ | BigUint64Array
24
+
25
+ export type RemoteSubsetWireRecord = {
26
+ [key: string]: RemoteSubsetWireValue
27
+ }
28
+
29
+ /** Values whose complete semantics survive a coordinator transport boundary. */
30
+ export type RemoteSubsetWireValue =
31
+ | RemoteSubsetWirePrimitive
32
+ | Date
33
+ | RegExp
34
+ | ArrayBuffer
35
+ | DataView
36
+ | RemoteSubsetWireTypedArray
37
+ | Array<RemoteSubsetWireValue>
38
+ | Map<RemoteSubsetWireValue, RemoteSubsetWireValue>
39
+ | Set<RemoteSubsetWireValue>
40
+ | RemoteSubsetWireRecord
41
+
42
+ export type RemoteSubsetWireExpression =
43
+ | { type: `ref`; path: Array<string>; sourceAlias?: string }
44
+ | { type: `val`; value: RemoteSubsetWireValue }
45
+ | {
46
+ type: `func`
47
+ name: string
48
+ args: Array<RemoteSubsetWireExpression>
49
+ }
50
+
51
+ export type RemoteSubsetWireCompareOptions = {
52
+ direction: `asc` | `desc`
53
+ nulls: `first` | `last`
54
+ } & (
55
+ | { stringSort?: `lexical` }
56
+ | {
57
+ stringSort?: `locale`
58
+ locale?: string
59
+ localeOptions?: RemoteSubsetWireRecord
60
+ }
61
+ )
62
+
63
+ export type RemoteSubsetWireOrderByClause = {
64
+ expression: RemoteSubsetWireExpression
65
+ compareOptions: RemoteSubsetWireCompareOptions
66
+ }
67
+
68
+ export type RemoteSubsetWireCursor = {
69
+ whereFrom: RemoteSubsetWireExpression
70
+ whereCurrent: RemoteSubsetWireExpression
71
+ lastKey?: string | number
72
+ }
73
+
74
+ /** The complete data portion transported by a remote-subset request. */
75
+ export type TransportedLoadSubsetOptions = {
76
+ where?: RemoteSubsetWireExpression
77
+ orderBy?: Array<RemoteSubsetWireOrderByClause>
78
+ limit?: number
79
+ cursor?: RemoteSubsetWireCursor
80
+ offset?: number
81
+ }
82
+
83
+ export class RemoteSubsetWireValueError extends TypeError {
84
+ override readonly name = `RemoteSubsetWireValueError`
85
+
86
+ constructor(
87
+ readonly path: string,
88
+ reason: string,
89
+ ) {
90
+ super(`Unsupported remote subset wire value at ${path}: ${reason}`)
91
+ }
92
+ }
93
+
94
+ type ProjectionState = {
95
+ expressions: WeakMap<object, RemoteSubsetWireExpression>
96
+ expressionArrays: WeakMap<object, Array<RemoteSubsetWireExpression>>
97
+ stringArrays: WeakMap<object, Array<string>>
98
+ orderByArrays: WeakMap<object, Array<RemoteSubsetWireOrderByClause>>
99
+ orderByClauses: WeakMap<object, RemoteSubsetWireOrderByClause>
100
+ compareOptions: WeakMap<object, RemoteSubsetWireCompareOptions>
101
+ cursors: WeakMap<object, RemoteSubsetWireCursor>
102
+ wireValues: WeakMap<object, RemoteSubsetWireValue>
103
+ }
104
+
105
+ type DataProperty = {
106
+ present: boolean
107
+ value?: unknown
108
+ }
109
+
110
+ type TypedArrayConstructor = {
111
+ readonly prototype: RemoteSubsetWireTypedArray
112
+ new (
113
+ buffer: ArrayBuffer,
114
+ byteOffset?: number,
115
+ length?: number,
116
+ ): RemoteSubsetWireTypedArray
117
+ }
118
+
119
+ const typedArrayConstructors: ReadonlyArray<TypedArrayConstructor> = [
120
+ Int8Array,
121
+ Uint8Array,
122
+ Uint8ClampedArray,
123
+ Int16Array,
124
+ Uint16Array,
125
+ Int32Array,
126
+ Uint32Array,
127
+ Float32Array,
128
+ Float64Array,
129
+ BigInt64Array,
130
+ BigUint64Array,
131
+ ]
132
+
133
+ const NativeDate = Date
134
+ const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/
135
+
136
+ /**
137
+ * Projects one live subset request into the exact coordinator wire domain.
138
+ * Validation happens before callers choose a local-leader or transported path.
139
+ */
140
+ export function toTransportedLoadSubsetOptions(
141
+ options: LoadSubsetOptions,
142
+ ): TransportedLoadSubsetOptions {
143
+ assertPlainContainer(options, `options`)
144
+ assertAllowedProperties(options, `options`, [
145
+ `where`,
146
+ `orderBy`,
147
+ `limit`,
148
+ `cursor`,
149
+ `offset`,
150
+ `signal`,
151
+ `subscription`,
152
+ ])
153
+
154
+ const state: ProjectionState = {
155
+ expressions: new WeakMap(),
156
+ expressionArrays: new WeakMap(),
157
+ stringArrays: new WeakMap(),
158
+ orderByArrays: new WeakMap(),
159
+ orderByClauses: new WeakMap(),
160
+ compareOptions: new WeakMap(),
161
+ cursors: new WeakMap(),
162
+ wireValues: new WeakMap(),
163
+ }
164
+ const projected: TransportedLoadSubsetOptions = {}
165
+ const where = readDataProperty(options, `where`, `options.where`)
166
+ const orderBy = readDataProperty(options, `orderBy`, `options.orderBy`)
167
+ const limit = readDataProperty(options, `limit`, `options.limit`)
168
+ const cursor = readDataProperty(options, `cursor`, `options.cursor`)
169
+ const offset = readDataProperty(options, `offset`, `options.offset`)
170
+
171
+ if (where.present && where.value !== undefined) {
172
+ projected.where = projectExpression(where.value, `options.where`, state)
173
+ }
174
+ if (orderBy.present && orderBy.value !== undefined) {
175
+ projected.orderBy = projectOrderBy(orderBy.value, `options.orderBy`, state)
176
+ }
177
+ if (limit.present && limit.value !== undefined) {
178
+ projected.limit = projectNumber(limit.value, `options.limit`)
179
+ }
180
+ if (cursor.present && cursor.value !== undefined) {
181
+ projected.cursor = projectCursor(cursor.value, `options.cursor`, state)
182
+ }
183
+ if (offset.present && offset.value !== undefined) {
184
+ projected.offset = projectNumber(offset.value, `options.offset`)
185
+ }
186
+
187
+ return projected
188
+ }
189
+
190
+ /** Adds live lifecycle references only to an already validated local request. */
191
+ export function toProcessLocalLoadSubsetOptions(
192
+ options: LoadSubsetOptions,
193
+ transported = toTransportedLoadSubsetOptions(options),
194
+ ): TransportedLoadSubsetOptions &
195
+ Pick<LoadSubsetOptions, `signal` | `subscription`> {
196
+ const projected = {
197
+ ...transported,
198
+ } as TransportedLoadSubsetOptions &
199
+ Pick<LoadSubsetOptions, `signal` | `subscription`>
200
+ const signal = readDataProperty(options, `signal`, `options.signal`)
201
+ const subscription = readDataProperty(
202
+ options,
203
+ `subscription`,
204
+ `options.subscription`,
205
+ )
206
+
207
+ if (signal.present) {
208
+ projected.signal = signal.value as LoadSubsetOptions[`signal`]
209
+ }
210
+ if (subscription.present) {
211
+ projected.subscription =
212
+ subscription.value as LoadSubsetOptions[`subscription`]
213
+ }
214
+
215
+ return projected
216
+ }
217
+
218
+ function projectExpression(
219
+ value: unknown,
220
+ path: string,
221
+ state: ProjectionState,
222
+ ): RemoteSubsetWireExpression {
223
+ const object = requireObject(value, path)
224
+ const existing = state.expressions.get(object)
225
+ if (existing) return existing
226
+
227
+ const type = readDataProperty(object, `type`, `${path}.type`)
228
+ if (!type.present || typeof type.value !== `string`) {
229
+ throw new RemoteSubsetWireValueError(`${path}.type`, describe(type.value))
230
+ }
231
+
232
+ switch (type.value) {
233
+ case `ref`: {
234
+ assertExpressionPrototype(object, path, IR.PropRef.prototype)
235
+ assertAllowedProperties(object, path, [`type`, `path`, `sourceAlias`])
236
+ const projected: Extract<RemoteSubsetWireExpression, { type: `ref` }> = {
237
+ type: `ref`,
238
+ path: [],
239
+ }
240
+ state.expressions.set(object, projected)
241
+ const sourcePath = readRequiredDataProperty(
242
+ object,
243
+ `path`,
244
+ `${path}.path`,
245
+ )
246
+ projected.path = projectStringArray(sourcePath, `${path}.path`, state)
247
+ const sourceAlias = readDataProperty(
248
+ object,
249
+ `sourceAlias`,
250
+ `${path}.sourceAlias`,
251
+ )
252
+ if (sourceAlias.present) {
253
+ if (
254
+ typeof sourceAlias.value !== `string` ||
255
+ projected.path[0] !== sourceAlias.value
256
+ ) {
257
+ throw new RemoteSubsetWireValueError(
258
+ `${path}.sourceAlias`,
259
+ `source alias must match the first path segment`,
260
+ )
261
+ }
262
+ projected.sourceAlias = sourceAlias.value
263
+ }
264
+ return projected
265
+ }
266
+ case `val`: {
267
+ assertExpressionPrototype(object, path, IR.Value.prototype)
268
+ assertAllowedProperties(object, path, [`type`, `value`])
269
+ const projected: Extract<RemoteSubsetWireExpression, { type: `val` }> = {
270
+ type: `val`,
271
+ value: undefined,
272
+ }
273
+ state.expressions.set(object, projected)
274
+ const sourceValue = readRequiredDataProperty(
275
+ object,
276
+ `value`,
277
+ `${path}.value`,
278
+ )
279
+ projected.value = projectWireValue(sourceValue, `${path}.value`, state)
280
+ return projected
281
+ }
282
+ case `func`: {
283
+ assertExpressionPrototype(object, path, IR.Func.prototype)
284
+ assertAllowedProperties(object, path, [`type`, `name`, `args`])
285
+ const projected = {
286
+ type: `func`,
287
+ name: ``,
288
+ args: [] as Array<RemoteSubsetWireExpression>,
289
+ } satisfies RemoteSubsetWireExpression
290
+ state.expressions.set(object, projected)
291
+ const name = readRequiredDataProperty(object, `name`, `${path}.name`)
292
+ if (typeof name !== `string`) {
293
+ throw new RemoteSubsetWireValueError(`${path}.name`, describe(name))
294
+ }
295
+ projected.name = name
296
+ const args = readRequiredDataProperty(object, `args`, `${path}.args`)
297
+ projected.args = projectExpressionArray(args, `${path}.args`, state)
298
+ return projected
299
+ }
300
+ default:
301
+ throw new RemoteSubsetWireValueError(`${path}.type`, String(type.value))
302
+ }
303
+ }
304
+
305
+ function projectExpressionArray(
306
+ value: unknown,
307
+ path: string,
308
+ state: ProjectionState,
309
+ ): Array<RemoteSubsetWireExpression> {
310
+ const array = requireArray(value, path)
311
+ const existing = state.expressionArrays.get(array)
312
+ if (existing) return existing
313
+ assertArrayShape(array, path)
314
+ const projected = new Array<RemoteSubsetWireExpression>(array.length)
315
+ state.expressionArrays.set(array, projected)
316
+ for (let index = 0; index < array.length; index++) {
317
+ if (!(index in array)) {
318
+ throw new RemoteSubsetWireValueError(
319
+ `${path}[${index}]`,
320
+ `missing expression`,
321
+ )
322
+ }
323
+ projected[index] = projectExpression(
324
+ array[index],
325
+ `${path}[${index}]`,
326
+ state,
327
+ )
328
+ }
329
+ return projected
330
+ }
331
+
332
+ function projectStringArray(
333
+ value: unknown,
334
+ path: string,
335
+ state: ProjectionState,
336
+ ): Array<string> {
337
+ const array = requireArray(value, path)
338
+ const existing = state.stringArrays.get(array)
339
+ if (existing) return existing
340
+ assertArrayShape(array, path)
341
+ const projected = new Array<string>(array.length)
342
+ state.stringArrays.set(array, projected)
343
+ for (let index = 0; index < array.length; index++) {
344
+ const entry = array[index]
345
+ if (!(index in array) || typeof entry !== `string`) {
346
+ throw new RemoteSubsetWireValueError(`${path}[${index}]`, describe(entry))
347
+ }
348
+ projected[index] = entry
349
+ }
350
+ return projected
351
+ }
352
+
353
+ function projectOrderBy(
354
+ value: unknown,
355
+ path: string,
356
+ state: ProjectionState,
357
+ ): Array<RemoteSubsetWireOrderByClause> {
358
+ const array = requireArray(value, path)
359
+ const existing = state.orderByArrays.get(array)
360
+ if (existing) return existing
361
+ assertArrayShape(array, path)
362
+ const projected = new Array<RemoteSubsetWireOrderByClause>(array.length)
363
+ state.orderByArrays.set(array, projected)
364
+
365
+ for (let index = 0; index < array.length; index++) {
366
+ const clausePath = `${path}[${index}]`
367
+ const clause = requirePlainRecord(array[index], clausePath)
368
+ const existingClause = state.orderByClauses.get(clause)
369
+ if (existingClause) {
370
+ projected[index] = existingClause
371
+ continue
372
+ }
373
+ assertAllowedProperties(clause, clausePath, [
374
+ `expression`,
375
+ `compareOptions`,
376
+ ])
377
+ const projectedClause = {
378
+ expression: undefined as unknown as RemoteSubsetWireExpression,
379
+ compareOptions: undefined as unknown as RemoteSubsetWireCompareOptions,
380
+ }
381
+ projected[index] = projectedClause
382
+ state.orderByClauses.set(clause, projectedClause)
383
+ projectedClause.expression = projectExpression(
384
+ readRequiredDataProperty(
385
+ clause,
386
+ `expression`,
387
+ `${clausePath}.expression`,
388
+ ),
389
+ `${clausePath}.expression`,
390
+ state,
391
+ )
392
+ projectedClause.compareOptions = projectCompareOptions(
393
+ readRequiredDataProperty(
394
+ clause,
395
+ `compareOptions`,
396
+ `${clausePath}.compareOptions`,
397
+ ),
398
+ `${clausePath}.compareOptions`,
399
+ state,
400
+ )
401
+ }
402
+ return projected
403
+ }
404
+
405
+ function projectCompareOptions(
406
+ value: unknown,
407
+ path: string,
408
+ state: ProjectionState,
409
+ ): RemoteSubsetWireCompareOptions {
410
+ const source = requirePlainRecord(value, path)
411
+ const existing = state.compareOptions.get(source)
412
+ if (existing) return existing
413
+ assertAllowedProperties(source, path, [
414
+ `direction`,
415
+ `nulls`,
416
+ `stringSort`,
417
+ `locale`,
418
+ `localeOptions`,
419
+ ])
420
+ const projected: {
421
+ direction?: `asc` | `desc`
422
+ nulls?: `first` | `last`
423
+ stringSort?: `lexical` | `locale`
424
+ locale?: string
425
+ localeOptions?: RemoteSubsetWireRecord
426
+ } = {}
427
+
428
+ const direction = readRequiredDataProperty(
429
+ source,
430
+ `direction`,
431
+ `${path}.direction`,
432
+ )
433
+ if (direction !== `asc` && direction !== `desc`) {
434
+ throw new RemoteSubsetWireValueError(
435
+ `${path}.direction`,
436
+ describe(direction),
437
+ )
438
+ }
439
+ projected.direction = direction
440
+
441
+ const nulls = readRequiredDataProperty(source, `nulls`, `${path}.nulls`)
442
+ if (nulls !== `first` && nulls !== `last`) {
443
+ throw new RemoteSubsetWireValueError(`${path}.nulls`, describe(nulls))
444
+ }
445
+ projected.nulls = nulls
446
+
447
+ const stringSort = readDataProperty(
448
+ source,
449
+ `stringSort`,
450
+ `${path}.stringSort`,
451
+ )
452
+ if (stringSort.present && stringSort.value !== undefined) {
453
+ if (stringSort.value !== `lexical` && stringSort.value !== `locale`) {
454
+ throw new RemoteSubsetWireValueError(
455
+ `${path}.stringSort`,
456
+ describe(stringSort.value),
457
+ )
458
+ }
459
+ projected.stringSort = stringSort.value
460
+ }
461
+ if (projected.stringSort !== `lexical`) {
462
+ const locale = readDataProperty(source, `locale`, `${path}.locale`)
463
+ if (locale.present && locale.value !== undefined) {
464
+ if (typeof locale.value !== `string`) {
465
+ throw new RemoteSubsetWireValueError(
466
+ `${path}.locale`,
467
+ describe(locale.value),
468
+ )
469
+ }
470
+ projected.locale = locale.value
471
+ }
472
+ const localeOptions = readDataProperty(
473
+ source,
474
+ `localeOptions`,
475
+ `${path}.localeOptions`,
476
+ )
477
+ if (localeOptions.present && localeOptions.value !== undefined) {
478
+ projected.localeOptions = projectWireRecord(
479
+ localeOptions.value,
480
+ `${path}.localeOptions`,
481
+ state,
482
+ )
483
+ }
484
+ }
485
+ const result = projected as RemoteSubsetWireCompareOptions
486
+ state.compareOptions.set(source, result)
487
+ return result
488
+ }
489
+
490
+ function projectCursor(
491
+ value: unknown,
492
+ path: string,
493
+ state: ProjectionState,
494
+ ): RemoteSubsetWireCursor {
495
+ const source = requirePlainRecord(value, path)
496
+ const existing = state.cursors.get(source)
497
+ if (existing) return existing
498
+ assertAllowedProperties(source, path, [
499
+ `whereFrom`,
500
+ `whereCurrent`,
501
+ `lastKey`,
502
+ ])
503
+ const projected: RemoteSubsetWireCursor = {
504
+ whereFrom: undefined as unknown as RemoteSubsetWireExpression,
505
+ whereCurrent: undefined as unknown as RemoteSubsetWireExpression,
506
+ }
507
+ state.cursors.set(source, projected)
508
+ projected.whereFrom = projectExpression(
509
+ readRequiredDataProperty(source, `whereFrom`, `${path}.whereFrom`),
510
+ `${path}.whereFrom`,
511
+ state,
512
+ )
513
+ projected.whereCurrent = projectExpression(
514
+ readRequiredDataProperty(source, `whereCurrent`, `${path}.whereCurrent`),
515
+ `${path}.whereCurrent`,
516
+ state,
517
+ )
518
+ const lastKey = readDataProperty(source, `lastKey`, `${path}.lastKey`)
519
+ if (lastKey.present && lastKey.value !== undefined) {
520
+ if (
521
+ typeof lastKey.value !== `string` &&
522
+ typeof lastKey.value !== `number`
523
+ ) {
524
+ throw new RemoteSubsetWireValueError(
525
+ `${path}.lastKey`,
526
+ describe(lastKey.value),
527
+ )
528
+ }
529
+ projected.lastKey = lastKey.value
530
+ }
531
+ return projected
532
+ }
533
+
534
+ function projectWireValue(
535
+ value: unknown,
536
+ path: string,
537
+ state: ProjectionState,
538
+ ): RemoteSubsetWireValue {
539
+ if (
540
+ value === undefined ||
541
+ value === null ||
542
+ typeof value === `boolean` ||
543
+ typeof value === `string` ||
544
+ typeof value === `number` ||
545
+ typeof value === `bigint`
546
+ ) {
547
+ return value
548
+ }
549
+ if (typeof value !== `object`) {
550
+ throw new RemoteSubsetWireValueError(path, describe(value))
551
+ }
552
+
553
+ const existing = state.wireValues.get(value)
554
+ if (existing) return existing
555
+
556
+ if (
557
+ typeof SharedArrayBuffer !== `undefined` &&
558
+ value instanceof SharedArrayBuffer
559
+ ) {
560
+ throw new RemoteSubsetWireValueError(path, `SharedArrayBuffer`)
561
+ }
562
+ if (value instanceof NativeDate) {
563
+ assertExactPrototype(value, NativeDate.prototype, path)
564
+ assertNoOwnProperties(value, path)
565
+ const projected = new NativeDate(value.getTime())
566
+ state.wireValues.set(value, projected)
567
+ return projected
568
+ }
569
+ if (value instanceof RegExp) {
570
+ assertExactPrototype(value, RegExp.prototype, path)
571
+ assertRegExpShape(value, path)
572
+ const projected = new RegExp(value.source, value.flags)
573
+ state.wireValues.set(value, projected)
574
+ return projected
575
+ }
576
+ if (value instanceof ArrayBuffer) {
577
+ assertExactPrototype(value, ArrayBuffer.prototype, path)
578
+ assertNoOwnProperties(value, path)
579
+ assertFixedArrayBuffer(value, path)
580
+ let projected: ArrayBuffer
581
+ try {
582
+ projected = value.slice(0)
583
+ } catch {
584
+ throw new RemoteSubsetWireValueError(path, `detached ArrayBuffer`)
585
+ }
586
+ state.wireValues.set(value, projected)
587
+ return projected
588
+ }
589
+ if (value instanceof DataView) {
590
+ assertExactPrototype(value, DataView.prototype, path)
591
+ assertNoOwnProperties(value, path)
592
+ let byteOffset: number
593
+ let byteLength: number
594
+ let buffer: ArrayBufferLike
595
+ try {
596
+ byteOffset = value.byteOffset
597
+ byteLength = value.byteLength
598
+ buffer = value.buffer
599
+ } catch {
600
+ throw new RemoteSubsetWireValueError(path, `detached DataView`)
601
+ }
602
+ const projectedBuffer = projectWireValue(buffer, path, state)
603
+ if (!(projectedBuffer instanceof ArrayBuffer)) {
604
+ throw new RemoteSubsetWireValueError(path, `shared DataView buffer`)
605
+ }
606
+ const projected = new DataView(projectedBuffer, byteOffset, byteLength)
607
+ state.wireValues.set(value, projected)
608
+ return projected
609
+ }
610
+
611
+ const typedArrayConstructor = getTypedArrayConstructor(value)
612
+ if (typedArrayConstructor) {
613
+ const typedArray = value as RemoteSubsetWireTypedArray
614
+ assertTypedArrayShape(typedArray, path)
615
+ let byteOffset: number
616
+ let length: number
617
+ let buffer: ArrayBufferLike
618
+ try {
619
+ byteOffset = typedArray.byteOffset
620
+ length = typedArray.length
621
+ buffer = typedArray.buffer
622
+ } catch {
623
+ throw new RemoteSubsetWireValueError(path, `detached typed array`)
624
+ }
625
+ if (!(buffer instanceof ArrayBuffer)) {
626
+ throw new RemoteSubsetWireValueError(path, `shared typed-array buffer`)
627
+ }
628
+ const projectedBuffer = projectWireValue(buffer, path, state)
629
+ if (!(projectedBuffer instanceof ArrayBuffer)) {
630
+ throw new RemoteSubsetWireValueError(path, `shared typed-array buffer`)
631
+ }
632
+ let projected: RemoteSubsetWireTypedArray
633
+ try {
634
+ projected = new typedArrayConstructor(projectedBuffer, byteOffset, length)
635
+ } catch {
636
+ throw new RemoteSubsetWireValueError(path, `detached typed array`)
637
+ }
638
+ state.wireValues.set(value, projected)
639
+ return projected
640
+ }
641
+ if (ArrayBuffer.isView(value)) {
642
+ throw new RemoteSubsetWireValueError(path, describe(value))
643
+ }
644
+ if (Array.isArray(value)) return projectWireArray(value, path, state)
645
+ if (value instanceof Map) return projectWireMap(value, path, state)
646
+ if (value instanceof Set) return projectWireSet(value, path, state)
647
+ return projectWireRecord(value, path, state)
648
+ }
649
+
650
+ function projectWireArray(
651
+ value: Array<unknown>,
652
+ path: string,
653
+ state: ProjectionState,
654
+ ): Array<RemoteSubsetWireValue> {
655
+ assertArrayShape(value, path)
656
+ const projected = new Array<RemoteSubsetWireValue>(value.length)
657
+ state.wireValues.set(value, projected)
658
+ for (let index = 0; index < value.length; index++) {
659
+ if (index in value) {
660
+ projected[index] = projectWireValue(
661
+ value[index],
662
+ `${path}[${index}]`,
663
+ state,
664
+ )
665
+ }
666
+ }
667
+ return projected
668
+ }
669
+
670
+ function projectWireMap(
671
+ value: Map<unknown, unknown>,
672
+ path: string,
673
+ state: ProjectionState,
674
+ ): Map<RemoteSubsetWireValue, RemoteSubsetWireValue> {
675
+ assertExactPrototype(value, Map.prototype, path)
676
+ assertNoOwnProperties(value, path)
677
+ const projected = new Map<RemoteSubsetWireValue, RemoteSubsetWireValue>()
678
+ state.wireValues.set(value, projected)
679
+ let index = 0
680
+ 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),
684
+ )
685
+ index++
686
+ }
687
+ return projected
688
+ }
689
+
690
+ function projectWireSet(
691
+ value: Set<unknown>,
692
+ path: string,
693
+ state: ProjectionState,
694
+ ): Set<RemoteSubsetWireValue> {
695
+ assertExactPrototype(value, Set.prototype, path)
696
+ assertNoOwnProperties(value, path)
697
+ const projected = new Set<RemoteSubsetWireValue>()
698
+ state.wireValues.set(value, projected)
699
+ let index = 0
700
+ for (const entry of value) {
701
+ projected.add(projectWireValue(entry, `${path}.values[${index}]`, state))
702
+ index++
703
+ }
704
+ return projected
705
+ }
706
+
707
+ function projectWireRecord(
708
+ value: unknown,
709
+ path: string,
710
+ state: ProjectionState,
711
+ ): RemoteSubsetWireRecord {
712
+ const source = requirePlainRecord(value, path)
713
+ const existing = state.wireValues.get(source)
714
+ if (existing) return existing as RemoteSubsetWireRecord
715
+ const projected: RemoteSubsetWireRecord = {}
716
+ state.wireValues.set(source, projected)
717
+
718
+ for (const key of ownKeys(source, path)) {
719
+ const childPath = propertyPath(path, key)
720
+ if (typeof key === `symbol`) {
721
+ throw new RemoteSubsetWireValueError(childPath, `symbol property key`)
722
+ }
723
+ const descriptor = Object.getOwnPropertyDescriptor(source, key)
724
+ if (!descriptor || !descriptor.enumerable || !(`value` in descriptor)) {
725
+ throw new RemoteSubsetWireValueError(childPath, `non-data property`)
726
+ }
727
+ Object.defineProperty(projected, key, {
728
+ value: projectWireValue(descriptor.value, childPath, state),
729
+ enumerable: true,
730
+ writable: true,
731
+ configurable: true,
732
+ })
733
+ }
734
+ return projected
735
+ }
736
+
737
+ function projectNumber(value: unknown, path: string): number {
738
+ if (typeof value !== `number` || !Number.isSafeInteger(value) || value < 0) {
739
+ throw new RemoteSubsetWireValueError(path, `non-negative safe integer`)
740
+ }
741
+ return value
742
+ }
743
+
744
+ function readRequiredDataProperty(
745
+ value: object,
746
+ key: string,
747
+ path: string,
748
+ ): unknown {
749
+ const property = readDataProperty(value, key, path)
750
+ if (!property.present) {
751
+ throw new RemoteSubsetWireValueError(path, `missing property`)
752
+ }
753
+ return property.value
754
+ }
755
+
756
+ function readDataProperty(
757
+ value: object,
758
+ key: string,
759
+ path: string,
760
+ ): DataProperty {
761
+ let descriptor: PropertyDescriptor | undefined
762
+ try {
763
+ descriptor = Object.getOwnPropertyDescriptor(value, key)
764
+ } catch {
765
+ throw new RemoteSubsetWireValueError(path, `unreadable property`)
766
+ }
767
+ if (!descriptor) return { present: false }
768
+ if (!descriptor.enumerable || !(`value` in descriptor)) {
769
+ throw new RemoteSubsetWireValueError(path, `non-data property`)
770
+ }
771
+ return { present: true, value: descriptor.value }
772
+ }
773
+
774
+ function assertAllowedProperties(
775
+ value: object,
776
+ path: string,
777
+ allowed: ReadonlyArray<string>,
778
+ ): void {
779
+ const allowedSet = new Set(allowed)
780
+ for (const key of ownKeys(value, path)) {
781
+ if (typeof key === `symbol` || !allowedSet.has(key)) {
782
+ throw new RemoteSubsetWireValueError(propertyPath(path, key), `property`)
783
+ }
784
+ readDataProperty(value, key, propertyPath(path, key))
785
+ }
786
+ }
787
+
788
+ function assertPlainContainer(value: object, path: string): void {
789
+ assertExactPrototype(value, Object.prototype, path)
790
+ }
791
+
792
+ function assertExpressionPrototype(
793
+ value: object,
794
+ path: string,
795
+ expressionPrototype: object,
796
+ ): void {
797
+ let prototype: object | null
798
+ try {
799
+ prototype = Object.getPrototypeOf(value)
800
+ } catch {
801
+ throw new RemoteSubsetWireValueError(path, `unreadable prototype`)
802
+ }
803
+ if (prototype !== Object.prototype && prototype !== expressionPrototype) {
804
+ throw new RemoteSubsetWireValueError(path, describe(value))
805
+ }
806
+ }
807
+
808
+ function assertExactPrototype(
809
+ value: object,
810
+ expected: object,
811
+ path: string,
812
+ ): void {
813
+ let prototype: object | null
814
+ try {
815
+ prototype = Object.getPrototypeOf(value)
816
+ } catch {
817
+ throw new RemoteSubsetWireValueError(path, `unreadable prototype`)
818
+ }
819
+ if (prototype !== expected) {
820
+ throw new RemoteSubsetWireValueError(path, describe(value))
821
+ }
822
+ }
823
+
824
+ function assertNoOwnProperties(value: object, path: string): void {
825
+ const keys = ownKeys(value, path)
826
+ if (keys.length > 0) {
827
+ throw new RemoteSubsetWireValueError(
828
+ propertyPath(path, keys[0]!),
829
+ `property`,
830
+ )
831
+ }
832
+ }
833
+
834
+ function assertFixedArrayBuffer(value: ArrayBuffer, path: string): void {
835
+ if ((value as ArrayBuffer & { readonly resizable?: boolean }).resizable) {
836
+ throw new RemoteSubsetWireValueError(path, `resizable ArrayBuffer`)
837
+ }
838
+ }
839
+
840
+ function assertRegExpShape(value: RegExp, path: string): void {
841
+ const keys = ownKeys(value, path)
842
+ for (const key of keys) {
843
+ if (key !== `lastIndex`) {
844
+ throw new RemoteSubsetWireValueError(propertyPath(path, key), `property`)
845
+ }
846
+ }
847
+ const descriptor = Object.getOwnPropertyDescriptor(value, `lastIndex`)
848
+ if (!descriptor || !(`value` in descriptor) || descriptor.value !== 0) {
849
+ throw new RemoteSubsetWireValueError(
850
+ `${path}.lastIndex`,
851
+ `RegExp lastIndex must be 0`,
852
+ )
853
+ }
854
+ }
855
+
856
+ function assertTypedArrayShape(
857
+ value: RemoteSubsetWireTypedArray,
858
+ path: string,
859
+ ): void {
860
+ const keys = ownKeys(value, path)
861
+ for (const key of keys) {
862
+ if (typeof key !== `string` || !isCanonicalArrayIndex(key, value.length)) {
863
+ throw new RemoteSubsetWireValueError(propertyPath(path, key), `property`)
864
+ }
865
+ }
866
+ }
867
+
868
+ function assertArrayShape(value: Array<unknown>, path: string): void {
869
+ assertExactPrototype(value, Array.prototype, path)
870
+ const keys = ownKeys(value, path)
871
+ for (const key of keys) {
872
+ if (
873
+ key !== `length` &&
874
+ (typeof key !== `string` || !isCanonicalArrayIndex(key, value.length))
875
+ ) {
876
+ throw new RemoteSubsetWireValueError(propertyPath(path, key), `property`)
877
+ }
878
+ if (key !== `length`) {
879
+ readDataProperty(value, key, propertyPath(path, key))
880
+ }
881
+ }
882
+ }
883
+
884
+ function isCanonicalArrayIndex(key: string, length: number): boolean {
885
+ if (!/^(0|[1-9][0-9]*)$/.test(key)) return false
886
+ const index = Number(key)
887
+ return Number.isSafeInteger(index) && index >= 0 && index < length
888
+ }
889
+
890
+ function getTypedArrayConstructor(
891
+ value: object,
892
+ ): TypedArrayConstructor | undefined {
893
+ let prototype: object | null
894
+ try {
895
+ prototype = Object.getPrototypeOf(value)
896
+ } catch {
897
+ return undefined
898
+ }
899
+ return typedArrayConstructors.find(
900
+ (constructor) => prototype === constructor.prototype,
901
+ )
902
+ }
903
+
904
+ function requireArray(value: unknown, path: string): Array<unknown> {
905
+ if (!Array.isArray(value)) {
906
+ throw new RemoteSubsetWireValueError(path, describe(value))
907
+ }
908
+ return value
909
+ }
910
+
911
+ function requireObject(value: unknown, path: string): object {
912
+ if (value === null || typeof value !== `object`) {
913
+ throw new RemoteSubsetWireValueError(path, describe(value))
914
+ }
915
+ return value
916
+ }
917
+
918
+ function requirePlainRecord(
919
+ value: unknown,
920
+ path: string,
921
+ ): Record<string, unknown> {
922
+ const object = requireObject(value, path)
923
+ assertExactPrototype(object, Object.prototype, path)
924
+ return object as Record<string, unknown>
925
+ }
926
+
927
+ function ownKeys(value: object, path: string): Array<string | symbol> {
928
+ try {
929
+ return Reflect.ownKeys(value)
930
+ } catch {
931
+ throw new RemoteSubsetWireValueError(path, `unreadable properties`)
932
+ }
933
+ }
934
+
935
+ function propertyPath(path: string, key: string | symbol): string {
936
+ if (typeof key === `symbol`) return `${path}[${String(key)}]`
937
+ return identifierPattern.test(key)
938
+ ? `${path}.${key}`
939
+ : `${path}[${JSON.stringify(key)}]`
940
+ }
941
+
942
+ function describe(value: unknown): string {
943
+ if (value === null) return `null`
944
+ if (typeof value !== `object`) return typeof value
945
+ return `object`
946
+ }