@tanstack/db-sqlite-persistence-core 0.2.21 → 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.
- package/README.md +258 -7
- package/dist/cjs/broadcast-coordinator.cjs +1689 -0
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -0
- package/dist/cjs/broadcast-coordinator.d.cts +116 -0
- package/dist/cjs/errors.cjs +73 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +44 -0
- package/dist/cjs/index.cjs +19 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/persisted.cjs +1840 -399
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +177 -11
- package/dist/cjs/remote-subset-owner.cjs +20 -0
- package/dist/cjs/remote-subset-owner.cjs.map +1 -0
- package/dist/cjs/remote-subset-owner.d.cts +7 -0
- package/dist/cjs/remote-subset-wire.cjs +681 -0
- package/dist/cjs/remote-subset-wire.cjs.map +1 -0
- package/dist/cjs/remote-subset-wire.d.cts +59 -0
- package/dist/cjs/sqlite-core-adapter.cjs +969 -364
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +45 -1
- package/dist/cjs/sqlite-value.cjs +31 -0
- package/dist/cjs/sqlite-value.cjs.map +1 -0
- package/dist/cjs/sqlite-value.d.cts +10 -0
- package/dist/esm/broadcast-coordinator.d.ts +116 -0
- package/dist/esm/broadcast-coordinator.js +1689 -0
- package/dist/esm/broadcast-coordinator.js.map +1 -0
- package/dist/esm/errors.d.ts +44 -0
- package/dist/esm/errors.js +74 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +21 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/persisted.d.ts +177 -11
- package/dist/esm/persisted.js +1842 -401
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-owner.d.ts +7 -0
- package/dist/esm/remote-subset-owner.js +20 -0
- package/dist/esm/remote-subset-owner.js.map +1 -0
- package/dist/esm/remote-subset-wire.d.ts +59 -0
- package/dist/esm/remote-subset-wire.js +681 -0
- package/dist/esm/remote-subset-wire.js.map +1 -0
- package/dist/esm/sqlite-core-adapter.d.ts +45 -1
- package/dist/esm/sqlite-core-adapter.js +955 -350
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/dist/esm/sqlite-value.d.ts +10 -0
- package/dist/esm/sqlite-value.js +31 -0
- package/dist/esm/sqlite-value.js.map +1 -0
- package/package.json +12 -2
- package/src/broadcast-coordinator.ts +2456 -0
- package/src/errors.ts +120 -0
- package/src/index.ts +2 -0
- package/src/persisted.ts +2690 -444
- package/src/remote-subset-owner.ts +36 -0
- package/src/remote-subset-wire.ts +946 -0
- package/src/sqlite-core-adapter.ts +1297 -410
- 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
|
+
}
|