@routier/core 0.4.0 → 0.6.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 +1 -1
- package/dist/index.cjs +88 -438
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +94 -443
- package/dist/index.js.map +1 -1
- package/dist/plugins/TelemetryDbPlugin.d.ts +43 -0
- package/dist/plugins/index.cjs +82 -1
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +88 -3
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/resultShape.d.ts +35 -0
- package/dist/transfer/ChunkEncoder.d.ts +60 -0
- package/dist/transfer/decoder.d.ts +29 -0
- package/dist/transfer/fillers.d.ts +36 -0
- package/dist/transfer/index.cjs +873 -0
- package/dist/transfer/index.cjs.map +1 -0
- package/dist/transfer/index.d.ts +47 -0
- package/dist/transfer/index.js +872 -0
- package/dist/transfer/index.js.map +1 -0
- package/dist/transfer/plan.d.ts +94 -0
- package/dist/transfer/types.d.ts +138 -0
- package/package.json +9 -9
- package/dist/capabilities/Capability.d.ts +0 -11
- package/dist/capabilities/PerformanceCapability.d.ts +0 -13
- package/dist/capabilities/TracingCapability.d.ts +0 -11
- package/dist/capabilities/index.cjs +0 -820
- package/dist/capabilities/index.cjs.map +0 -1
- package/dist/capabilities/index.d.ts +0 -4
- package/dist/capabilities/index.js +0 -808
- package/dist/capabilities/index.js.map +0 -1
- package/dist/capabilities/performance/PerformanceTracker.d.ts +0 -11
- package/dist/capabilities/tracing/CallTraceManager.d.ts +0 -12
- package/dist/capabilities/types.d.ts +0 -17
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
// The require scope
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
|
|
4
|
+
// webpack/runtime/define_property_getters
|
|
5
|
+
(() => {
|
|
6
|
+
__webpack_require__.d = (exports, definition) => {
|
|
7
|
+
for(var key in definition) {
|
|
8
|
+
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
9
|
+
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
})();
|
|
14
|
+
// webpack/runtime/has_own_property
|
|
15
|
+
(() => {
|
|
16
|
+
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
17
|
+
})();
|
|
18
|
+
var __webpack_exports__ = {};
|
|
19
|
+
|
|
20
|
+
// EXPORTS
|
|
21
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
22
|
+
Ai: () => (/* reexport */ isTransferCodecSupported),
|
|
23
|
+
WZ: () => (/* reexport */ isTransferJsonError),
|
|
24
|
+
sg: () => (/* reexport */ transferEncodingFor),
|
|
25
|
+
E5: () => (/* reexport */ assertTransferVersion),
|
|
26
|
+
YA: () => (/* reexport */ nullByteLength),
|
|
27
|
+
iT: () => (/* reexport */ /* inlined export .CHUNK_ROWS */ (4096)),
|
|
28
|
+
wN: () => (/* reexport */ clearDecoderCache),
|
|
29
|
+
FG: () => (/* reexport */ /* inlined export .TRANSFER_VERSION */ (1)),
|
|
30
|
+
rq: () => (/* reexport */ assertColumnLayout),
|
|
31
|
+
yl: () => (/* reexport */ rawStorageTransferTypes),
|
|
32
|
+
fY: () => (/* reexport */ buildTransferPlan),
|
|
33
|
+
WQ: () => (/* reexport */ decodeChunk),
|
|
34
|
+
Im: () => (/* reexport */ ChunkEncoder),
|
|
35
|
+
ze: () => (/* reexport */ parsedValueTransferTypes)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
;// CONCATENATED MODULE: ./src/transfer/types.ts
|
|
39
|
+
/**
|
|
40
|
+
* The wire format for query results crossing an in-process worker boundary.
|
|
41
|
+
*
|
|
42
|
+
* `postMessage` with no transfer list structured-clones everything, and a clone of a large row
|
|
43
|
+
* array is paid twice — once serialising in the worker, once deserialising on the main thread,
|
|
44
|
+
* where it blocks. Columnar typed arrays are transferred instead: the buffer changes owner and
|
|
45
|
+
* nothing is copied.
|
|
46
|
+
*
|
|
47
|
+
* Nothing here knows about SQL, schemas, or workers. A column's values go in and
|
|
48
|
+
* `{ payload, transferables }` comes out; the caller owns the transport. A transport with no
|
|
49
|
+
* transfer list may ignore the array — the payload then clones correctly on its own.
|
|
50
|
+
*/ /**
|
|
51
|
+
* The COMPLETE layout: chunk size, bitmap semantics, JSON joining, and the framing above.
|
|
52
|
+
*
|
|
53
|
+
* Both sides reject a version they do not know rather than guessing. The worker ships as its own
|
|
54
|
+
* bundle, so the two halves can be built from different sources.
|
|
55
|
+
*/ const TRANSFER_VERSION = 1;
|
|
56
|
+
/**
|
|
57
|
+
* Rows per chunk.
|
|
58
|
+
*
|
|
59
|
+
* Chunking is what lets the main thread decode chunk *k* while the worker fills *k+1*, so the
|
|
60
|
+
* first rows arrive in about 2ms at any result size instead of after the whole clone. Measured
|
|
61
|
+
* best at 4,096: 8,192 is within noise, 25,000 is measurably worse.
|
|
62
|
+
*/ const CHUNK_ROWS = 4096;
|
|
63
|
+
/** Bytes of null bitmap for `rowCount` rows: one bit per row, LSB-first. */ const nullByteLength = (rowCount)=>rowCount + 7 >> 3;
|
|
64
|
+
/**
|
|
65
|
+
* Rejects a version this build cannot read.
|
|
66
|
+
*
|
|
67
|
+
* Never attempt to decode an unknown version. Every field's meaning is version-scoped, so a
|
|
68
|
+
* layout that merely looks close would be read wrongly and silently.
|
|
69
|
+
*/ const assertTransferVersion = (version)=>{
|
|
70
|
+
if (version !== TRANSFER_VERSION) {
|
|
71
|
+
throw new Error(`transfer codec version ${version} is not supported`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Rejects a plan that does not describe the result the engine is about to produce.
|
|
76
|
+
*
|
|
77
|
+
* OPTIONAL, for an engine that can report the fields of a result before yielding rows. Encoding
|
|
78
|
+
* against a wrong layout would put each field's values under another field's name and report
|
|
79
|
+
* nothing, so a caller that CAN check should. An engine whose records are heterogeneous has
|
|
80
|
+
* nothing to check against and skips it — the plan decides the result shape there.
|
|
81
|
+
*
|
|
82
|
+
* Checked once per result, never per row.
|
|
83
|
+
*/ const assertColumnLayout = (plan, names)=>{
|
|
84
|
+
const planned = plan.columns.map((column)=>column.name);
|
|
85
|
+
const matches = planned.length === names.length && planned.every((name, index)=>name === names[index]);
|
|
86
|
+
if (matches === false) {
|
|
87
|
+
throw new Error(`The transfer plan does not match the result columns. ` + `Plan: [${planned.join(', ')}]. Result: [${names.join(', ')}].`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
;// CONCATENATED MODULE: ./src/transfer/fillers.ts
|
|
92
|
+
|
|
93
|
+
const NULL_BYTES = nullByteLength(/* inlined export .CHUNK_ROWS */ (4096));
|
|
94
|
+
/**
|
|
95
|
+
* Epoch milliseconds for any shape an engine returns a date in, or `null` for anything else.
|
|
96
|
+
*
|
|
97
|
+
* An invalid `Date` and an unparseable string both answer `null` rather than writing `NaN`: a
|
|
98
|
+
* date that cannot be represented is a value this encoding has no answer for, and the column
|
|
99
|
+
* falls back so the raw value survives.
|
|
100
|
+
*/ const toEpoch = (value)=>{
|
|
101
|
+
if (value instanceof Date) {
|
|
102
|
+
const epoch = value.getTime();
|
|
103
|
+
return Number.isNaN(epoch) ? null : epoch;
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === 'number') {
|
|
106
|
+
return Number.isFinite(value) ? value : null;
|
|
107
|
+
}
|
|
108
|
+
if (typeof value !== 'string') {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const parsed = Date.parse(value);
|
|
112
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
113
|
+
};
|
|
114
|
+
class TypedFiller {
|
|
115
|
+
data;
|
|
116
|
+
nulls;
|
|
117
|
+
constructor(){
|
|
118
|
+
this.data = this.allocate();
|
|
119
|
+
this.nulls = new Uint8Array(NULL_BYTES);
|
|
120
|
+
}
|
|
121
|
+
set(index, value) {
|
|
122
|
+
if (value == null) {
|
|
123
|
+
this.nulls[index >> 3] |= 1 << (index & 7);
|
|
124
|
+
this.data[index] = 0;
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
return this.write(index, value);
|
|
128
|
+
}
|
|
129
|
+
drain(count) {
|
|
130
|
+
const values = new Array(count);
|
|
131
|
+
for(let i = 0; i < count; i++){
|
|
132
|
+
const isNull = (this.nulls[i >> 3] & 1 << (i & 7)) !== 0;
|
|
133
|
+
values[i] = isNull ? null : this.raw(i);
|
|
134
|
+
}
|
|
135
|
+
return values;
|
|
136
|
+
}
|
|
137
|
+
emit(rowCount, transferables) {
|
|
138
|
+
// `subarray` would not do: transfer moves the whole underlying buffer, so a short final
|
|
139
|
+
// chunk has to be copied to its exact size. A full chunk is already exact.
|
|
140
|
+
const data = rowCount === /* inlined export .CHUNK_ROWS */ (4096) ? this.data : this.data.slice(0, rowCount);
|
|
141
|
+
const nulls = rowCount === /* inlined export .CHUNK_ROWS */ (4096) ? this.nulls : this.nulls.slice(0, nullByteLength(rowCount));
|
|
142
|
+
transferables.push(data.buffer, nulls.buffer);
|
|
143
|
+
return {
|
|
144
|
+
encoding: this.encoding,
|
|
145
|
+
data,
|
|
146
|
+
nulls
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
reset() {
|
|
150
|
+
this.data = this.allocate();
|
|
151
|
+
this.nulls = new Uint8Array(NULL_BYTES);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Numbers, including `NaN` and the infinities — those are legitimate JS numbers and pass through.
|
|
156
|
+
*
|
|
157
|
+
* No `BigInt64Array`. Routier only ever writes JS numbers, so `Float64Array` round-trips
|
|
158
|
+
* everything it stored; a `bigint` or a wide integer type can only come from something else, and
|
|
159
|
+
* that column falls back rather than losing precision silently.
|
|
160
|
+
*/ class Float64Filler extends TypedFiller {
|
|
161
|
+
encoding = 'float64';
|
|
162
|
+
allocate() {
|
|
163
|
+
return new Float64Array(/* inlined export .CHUNK_ROWS */ (4096));
|
|
164
|
+
}
|
|
165
|
+
write(index, value) {
|
|
166
|
+
if (typeof value !== 'number') {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
this.data[index] = value;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
raw(index) {
|
|
173
|
+
return this.data[index];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Epoch milliseconds, whatever shape the engine returned the date in.
|
|
178
|
+
*
|
|
179
|
+
* All three shapes are accepted because engines genuinely differ: one that stores a date as text
|
|
180
|
+
* returns a string, one that parses before returning gives a `Date`, and one that stores a
|
|
181
|
+
* timestamp gives a number. Requiring text would silently drop the whole encoding for every
|
|
182
|
+
* engine of the other two kinds — they would fall back to `clone` on every row.
|
|
183
|
+
*
|
|
184
|
+
* The final entity shape needs a `Date` either way, so converting here means the wire carries
|
|
185
|
+
* eight bytes and the main thread builds the `Date` from them.
|
|
186
|
+
*/ class DateFiller extends TypedFiller {
|
|
187
|
+
encoding = 'date-f64';
|
|
188
|
+
allocate() {
|
|
189
|
+
return new Float64Array(/* inlined export .CHUNK_ROWS */ (4096));
|
|
190
|
+
}
|
|
191
|
+
write(index, value) {
|
|
192
|
+
const epoch = toEpoch(value);
|
|
193
|
+
if (epoch == null) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
this.data[index] = epoch;
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* A `Date`, not the epoch number.
|
|
201
|
+
*
|
|
202
|
+
* A fallback column is decoded raw, and the rows already written can no longer produce their
|
|
203
|
+
* ISO text. A `Date` is what the decoder would have emitted for them, and it is also what
|
|
204
|
+
* the existing date deserializer passes through untouched — an epoch number would reach the
|
|
205
|
+
* entity as a number.
|
|
206
|
+
*/ raw(index) {
|
|
207
|
+
return new Date(this.data[index]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 0/1 bytes.
|
|
212
|
+
*
|
|
213
|
+
* Both shapes are accepted: an engine with no boolean type returns 0 or 1, one with a boolean
|
|
214
|
+
* type returns a boolean. Requiring either would drop the encoding entirely for engines of the
|
|
215
|
+
* other kind. Both decode to `true`/`false`, so the result is identical.
|
|
216
|
+
*/ class BooleanFiller extends TypedFiller {
|
|
217
|
+
encoding = 'boolean-byte';
|
|
218
|
+
allocate() {
|
|
219
|
+
return new Uint8Array(/* inlined export .CHUNK_ROWS */ (4096));
|
|
220
|
+
}
|
|
221
|
+
write(index, value) {
|
|
222
|
+
if (value === 0 || value === 1) {
|
|
223
|
+
this.data[index] = value;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (typeof value !== 'boolean') {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
this.data[index] = value ? 1 : 0;
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
raw(index) {
|
|
233
|
+
return this.data[index];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* JSON text, joined into one document per chunk so the main thread parses once per column
|
|
238
|
+
* instead of once per row.
|
|
239
|
+
*
|
|
240
|
+
* The join is valid because each element is a complete JSON document and `null` is valid JSON.
|
|
241
|
+
* The text is NOT validated here; that would be the second parse this exists to avoid. Text that
|
|
242
|
+
* is not JSON poisons the chunk's document and the decoder reports it (see `decodeChunk`).
|
|
243
|
+
*/ class JsonFiller {
|
|
244
|
+
toText;
|
|
245
|
+
fromText;
|
|
246
|
+
encoding = 'json';
|
|
247
|
+
texts = [];
|
|
248
|
+
/**
|
|
249
|
+
* Which rows were null, so a fallback to `clone` can carry `null` rather than the text
|
|
250
|
+
* `'null'` the document holds. Sparse — only null rows are written.
|
|
251
|
+
*/ nulls = [];
|
|
252
|
+
/**
|
|
253
|
+
* @param toText The JSON text for one non-null value, or `null` to send the column to
|
|
254
|
+
* `clone`. This is the whole difference between an engine that returns text and one that
|
|
255
|
+
* returns a live object.
|
|
256
|
+
* @param fromText Recovers the value the engine originally gave, for a fallback to `clone`.
|
|
257
|
+
* The stored text is all that is kept, so a filler whose input was NOT text has to reverse
|
|
258
|
+
* its own conversion — otherwise the rows written before the fallback would change type,
|
|
259
|
+
* coming back as text while every row after it comes back as an object.
|
|
260
|
+
*/ constructor(toText, fromText){
|
|
261
|
+
this.toText = toText;
|
|
262
|
+
this.fromText = fromText;
|
|
263
|
+
}
|
|
264
|
+
set(index, value) {
|
|
265
|
+
if (value == null) {
|
|
266
|
+
this.texts[index] = 'null';
|
|
267
|
+
this.nulls[index] = true;
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
const text = this.toText(value);
|
|
271
|
+
if (text == null) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
this.texts[index] = text;
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
drain(count) {
|
|
278
|
+
const values = new Array(count);
|
|
279
|
+
for(let i = 0; i < count; i++){
|
|
280
|
+
values[i] = this.nulls[i] === true ? null : this.fromText(this.texts[i]);
|
|
281
|
+
}
|
|
282
|
+
return values;
|
|
283
|
+
}
|
|
284
|
+
emit(rowCount) {
|
|
285
|
+
// Joined with a single ',' and nothing else, which is the whole of the format.
|
|
286
|
+
const doc = `[${this.texts.slice(0, rowCount).join(',')}]`;
|
|
287
|
+
return {
|
|
288
|
+
encoding: this.encoding,
|
|
289
|
+
doc
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
reset() {
|
|
293
|
+
this.texts = [];
|
|
294
|
+
this.nulls = [];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Raw values in a plain array, structured-cloned as they are.
|
|
299
|
+
*
|
|
300
|
+
* Strings live here and are never encoded. `TextEncoder` loses to clone by a wide margin —
|
|
301
|
+
* 14.0ms against 8.3ms for 4,000 rows of 2KB text — because cloning a V8 string is a native
|
|
302
|
+
* memcpy.
|
|
303
|
+
*/ class CloneFiller {
|
|
304
|
+
encoding = 'clone';
|
|
305
|
+
data;
|
|
306
|
+
constructor(seed = []){
|
|
307
|
+
this.data = seed;
|
|
308
|
+
}
|
|
309
|
+
set(index, value) {
|
|
310
|
+
this.data[index] = value;
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
drain(count) {
|
|
314
|
+
return this.data.slice(0, count);
|
|
315
|
+
}
|
|
316
|
+
emit(rowCount) {
|
|
317
|
+
return {
|
|
318
|
+
encoding: this.encoding,
|
|
319
|
+
data: this.data.slice(0, rowCount)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
reset() {
|
|
323
|
+
this.data = [];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/** A value that is already JSON text is passed straight through; anything else is not text. */ const asJsonText = (value)=>typeof value === 'string' ? value : null;
|
|
327
|
+
/**
|
|
328
|
+
* A live value becomes text here.
|
|
329
|
+
*
|
|
330
|
+
* `JSON.stringify` throws on a circular structure and on a `bigint`, and returns `undefined` for
|
|
331
|
+
* a value that is not representable at all — a function, or a lone `undefined`. All three send the
|
|
332
|
+
* column to `clone`, which is the same answer every other filler gives a value it cannot encode.
|
|
333
|
+
*/ const asStringifiedJson = (value)=>{
|
|
334
|
+
try {
|
|
335
|
+
return JSON.stringify(value) ?? null;
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Back to a value, for a `json-stringify` column that fell back.
|
|
342
|
+
*
|
|
343
|
+
* The text came from `JSON.stringify` on this same value, so it parses. A `catch` returning the
|
|
344
|
+
* text is there only so a fallback — already the unhappy path — cannot throw.
|
|
345
|
+
*/ const parseJsonText = (text)=>{
|
|
346
|
+
try {
|
|
347
|
+
return JSON.parse(text);
|
|
348
|
+
} catch {
|
|
349
|
+
return text;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const FILLERS = {
|
|
353
|
+
'float64': ()=>new Float64Filler(),
|
|
354
|
+
'date-f64': ()=>new DateFiller(),
|
|
355
|
+
'boolean-byte': ()=>new BooleanFiller(),
|
|
356
|
+
// The engine gave text, so the text IS the raw value a fallback should carry.
|
|
357
|
+
'json': ()=>new JsonFiller(asJsonText, (text)=>text),
|
|
358
|
+
// The engine gave a live object, so a fallback has to parse the text back into one.
|
|
359
|
+
'json-stringify': ()=>new JsonFiller(asStringifiedJson, parseJsonText),
|
|
360
|
+
'clone': ()=>new CloneFiller()
|
|
361
|
+
};
|
|
362
|
+
const createFiller = (encoding)=>{
|
|
363
|
+
const create = FILLERS[encoding];
|
|
364
|
+
if (create == null) {
|
|
365
|
+
throw new Error(`transfer encoding '${encoding}' is not supported`);
|
|
366
|
+
}
|
|
367
|
+
return create();
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
;// CONCATENATED MODULE: ./src/transfer/ChunkEncoder.ts
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
/** Probe for names the prototype chain answers for — `__proto__`, `toString`, `constructor`. */ const EMPTY_OBJECT = {};
|
|
374
|
+
/**
|
|
375
|
+
* Fills one chunk at a time from row values, and emits it with the buffers its transport can hand
|
|
376
|
+
* over.
|
|
377
|
+
*
|
|
378
|
+
* One encoder per result, not per chunk: a column that falls back to `clone` stays there for
|
|
379
|
+
* every later chunk, and a forward-only cursor cannot rewind to re-encode what it already yielded.
|
|
380
|
+
*
|
|
381
|
+
* Usage is a loop — `appendRow` or `appendRecord` until `isFull`, `take`, repeat, then `take` once
|
|
382
|
+
* more for the short final chunk. A zero-row result takes exactly one chunk with `rowCount: 0`.
|
|
383
|
+
*/ class ChunkEncoder {
|
|
384
|
+
columns;
|
|
385
|
+
fillers;
|
|
386
|
+
inheritedNames;
|
|
387
|
+
rows = 0;
|
|
388
|
+
constructor(plan){
|
|
389
|
+
assertTransferVersion(plan.version);
|
|
390
|
+
if (plan.columns.length === 0) {
|
|
391
|
+
throw new Error('A transfer plan needs at least one column; a result with no columns has no rows to encode.');
|
|
392
|
+
}
|
|
393
|
+
const names = new Set();
|
|
394
|
+
for (const column of plan.columns){
|
|
395
|
+
if (names.has(column.name)) {
|
|
396
|
+
// One entry per column name in the emitted chunk, so two columns sharing a name
|
|
397
|
+
// would silently keep only the second. The caller aliases them instead.
|
|
398
|
+
throw new Error(`A transfer plan names the column '${column.name}' more than once.`);
|
|
399
|
+
}
|
|
400
|
+
names.add(column.name);
|
|
401
|
+
}
|
|
402
|
+
this.columns = plan.columns;
|
|
403
|
+
this.fillers = plan.columns.map((column)=>createFiller(column.encoding));
|
|
404
|
+
this.inheritedNames = plan.columns.some((column)=>column.name in EMPTY_OBJECT);
|
|
405
|
+
}
|
|
406
|
+
/** Rows in the chunk being filled. */ get rowCount() {
|
|
407
|
+
return this.rows;
|
|
408
|
+
}
|
|
409
|
+
get isFull() {
|
|
410
|
+
return this.rows === /* inlined export .CHUNK_ROWS */ (4096);
|
|
411
|
+
}
|
|
412
|
+
/** The column names, in the order a row's values must arrive in. */ get columnNames() {
|
|
413
|
+
return this.columns.map((column)=>column.name);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Adds one row, its values in plan column order.
|
|
417
|
+
*
|
|
418
|
+
* A value that does not belong in its column's encoding sends that column to `clone` for the
|
|
419
|
+
* rest of the result, carrying the rows already written with it. Nothing is coerced into a
|
|
420
|
+
* typed array.
|
|
421
|
+
*/ appendRow(values) {
|
|
422
|
+
if (this.isFull) {
|
|
423
|
+
throw new Error(`The chunk is full at ${/* inlined export .CHUNK_ROWS */ (4096)} rows; take it before appending another.`);
|
|
424
|
+
}
|
|
425
|
+
if (values.length !== this.fillers.length) {
|
|
426
|
+
throw new Error(`A row carried ${values.length} values for ${this.fillers.length} planned columns.`);
|
|
427
|
+
}
|
|
428
|
+
const index = this.rows;
|
|
429
|
+
for(let i = 0; i < this.fillers.length; i++){
|
|
430
|
+
if (this.fillers[i].set(index, values[i]) === false) {
|
|
431
|
+
this.fallBack(i, index, values[i]);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
this.rows = index + 1;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Adds one row from a NAME-KEYED record, reading each planned column out of it.
|
|
438
|
+
*
|
|
439
|
+
* For an engine that yields records rather than positional tuples — a document store, a
|
|
440
|
+
* key-value store, a driver that returns row objects. Projecting to an array is the caller's
|
|
441
|
+
* alternative, and getting that order wrong is silent corruption rather than an error, so the
|
|
442
|
+
* mapping belongs here once instead of in every plugin.
|
|
443
|
+
*
|
|
444
|
+
* A column the record does not carry is `null`, not an error. Records are legitimately
|
|
445
|
+
* heterogeneous outside a fixed-schema table, and the plan is what decides the result shape.
|
|
446
|
+
*/ appendRecord(record) {
|
|
447
|
+
const values = new Array(this.columns.length);
|
|
448
|
+
for(let i = 0; i < this.columns.length; i++){
|
|
449
|
+
values[i] = this.readField(record, this.columns[i].name);
|
|
450
|
+
}
|
|
451
|
+
this.appendRow(values);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* A plain read, unless a planned name is one the prototype chain answers for.
|
|
455
|
+
*
|
|
456
|
+
* `record['__proto__']` on an object literal returns `Object.prototype` rather than
|
|
457
|
+
* `undefined`, and `toString` returns a function — either would be encoded as a value. The
|
|
458
|
+
* own-property test that avoids it costs a call per field, so it is only taken when a name in
|
|
459
|
+
* this plan actually needs it.
|
|
460
|
+
*/ readField(record, name) {
|
|
461
|
+
if (this.inheritedNames) {
|
|
462
|
+
return Object.prototype.hasOwnProperty.call(record, name) ? record[name] : null;
|
|
463
|
+
}
|
|
464
|
+
const value = record[name];
|
|
465
|
+
// `null`, not `undefined`. An absent field and one holding `undefined` mean the same thing
|
|
466
|
+
// here, and a `clone` column would otherwise carry `undefined` all the way to the entity —
|
|
467
|
+
// a decoded row says `null` for an absent value in every other encoding.
|
|
468
|
+
return value === undefined ? null : value;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Emits the filled chunk and readies the encoder for the next one.
|
|
472
|
+
*
|
|
473
|
+
* Transfer DETACHES the emitted buffers, so the fillers allocate fresh arrays here rather
|
|
474
|
+
* than reusing them. Reading a chunk's typed arrays after this is a use-after-transfer.
|
|
475
|
+
*/ take() {
|
|
476
|
+
const transferables = [];
|
|
477
|
+
// `Object.create(null)`, because a column named `__proto__` assigned onto a plain object
|
|
478
|
+
// reaches `Object.prototype`'s setter and never becomes an own property — the column then
|
|
479
|
+
// survives same-realm through the getter and vanishes the moment the chunk is cloned.
|
|
480
|
+
const columns = Object.create(null);
|
|
481
|
+
for(let i = 0; i < this.columns.length; i++){
|
|
482
|
+
columns[this.columns[i].name] = this.fillers[i].emit(this.rows, transferables);
|
|
483
|
+
}
|
|
484
|
+
const payload = {
|
|
485
|
+
version: /* inlined export .TRANSFER_VERSION */ (1),
|
|
486
|
+
rowCount: this.rows,
|
|
487
|
+
columns
|
|
488
|
+
};
|
|
489
|
+
this.rows = 0;
|
|
490
|
+
for (const filler of this.fillers){
|
|
491
|
+
filler.reset();
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
payload,
|
|
495
|
+
transferables
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
fallBack(column, index, value) {
|
|
499
|
+
const clone = new CloneFiller(this.fillers[column].drain(index));
|
|
500
|
+
clone.set(index, value);
|
|
501
|
+
this.fillers[column] = clone;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
;// CONCATENATED MODULE: ./src/transfer/decoder.ts
|
|
506
|
+
|
|
507
|
+
const isTransferJsonError = (error)=>typeof error?.transferJsonColumn === 'string';
|
|
508
|
+
const jsonError = (column, cause)=>Object.assign(new Error(`The transferred JSON document for column '${column}' did not parse, so this column holds ` + `text that is not JSON. Retry the request without a transfer plan. Cause: ` + `${cause?.message ?? String(cause)}`), {
|
|
509
|
+
transferJsonColumn: column
|
|
510
|
+
});
|
|
511
|
+
const effectiveColumns = (plan, chunk)=>plan.columns.map((column)=>{
|
|
512
|
+
// An own-property test, not a truthiness one: a name like `__proto__` or `toString`
|
|
513
|
+
// resolves to something inherited on a plain object, which would pass a null check and
|
|
514
|
+
// then be decoded as a column.
|
|
515
|
+
if (Object.prototype.hasOwnProperty.call(chunk.columns, column.name) === false) {
|
|
516
|
+
throw new Error(`The transferred chunk has no column '${column.name}', which the plan lists.`);
|
|
517
|
+
}
|
|
518
|
+
const encoded = chunk.columns[column.name];
|
|
519
|
+
return {
|
|
520
|
+
name: column.name,
|
|
521
|
+
encoding: encoded.encoding
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
/**
|
|
525
|
+
* The serialized layout IS the key.
|
|
526
|
+
*
|
|
527
|
+
* Content-keyed, never by collection name: a migration changes the columns under one name, one
|
|
528
|
+
* worker serves every database on the page, and joins and projections produce many shapes per
|
|
529
|
+
* collection. Names are quoted so a name containing the separator cannot collide with a
|
|
530
|
+
* different layout — a collision would hand a result the wrong decoder, silently.
|
|
531
|
+
*/ const cacheKey = (columns)=>'v1|' + columns.map((column)=>`${JSON.stringify(column.name)}:${column.encoding}`).join('|');
|
|
532
|
+
/** Retains a compiled function per entry, so it is bounded. */ const CACHE_CAPACITY = 64;
|
|
533
|
+
const cache = new Map();
|
|
534
|
+
const cached = (key)=>{
|
|
535
|
+
const decoder = cache.get(key);
|
|
536
|
+
if (decoder == null) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
// Re-inserted so the eviction below drops the least recently USED entry, not the oldest.
|
|
540
|
+
cache.delete(key);
|
|
541
|
+
cache.set(key, decoder);
|
|
542
|
+
return decoder;
|
|
543
|
+
};
|
|
544
|
+
const remember = (key, decoder)=>{
|
|
545
|
+
cache.set(key, decoder);
|
|
546
|
+
if (cache.size > CACHE_CAPACITY) {
|
|
547
|
+
const oldest = cache.keys().next();
|
|
548
|
+
if (oldest.done === false) {
|
|
549
|
+
cache.delete(oldest.value);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
/** Emptied between tests. Not part of the decoding contract. */ const clearDecoderCache = ()=>cache.clear();
|
|
554
|
+
const rowValue = (column, index)=>{
|
|
555
|
+
const nulled = `(u${index}[b] & m) !== 0`;
|
|
556
|
+
switch(column.encoding){
|
|
557
|
+
case 'float64':
|
|
558
|
+
return `${nulled} ? null : d${index}[i]`;
|
|
559
|
+
case 'date-f64':
|
|
560
|
+
return `${nulled} ? null : new Date(d${index}[i])`;
|
|
561
|
+
case 'boolean-byte':
|
|
562
|
+
return `${nulled} ? null : d${index}[i] !== 0`;
|
|
563
|
+
case 'json':
|
|
564
|
+
return `j${index}[i]`;
|
|
565
|
+
case 'clone':
|
|
566
|
+
return `d${index}[i]`;
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
/**
|
|
570
|
+
* The key to write in the emitted object literal.
|
|
571
|
+
*
|
|
572
|
+
* A quoted `"__proto__"` in an object literal sets the row's prototype instead of defining a
|
|
573
|
+
* property (Annex B.3.1), so that one name needs a computed key. Every other name keeps the
|
|
574
|
+
* constant form: computed keys throughout measured 9% slower over a 4,096-row chunk, and decode
|
|
575
|
+
* speed is what this whole module is for.
|
|
576
|
+
*/ const literalKey = (name)=>name === '__proto__' ? `[${JSON.stringify(name)}]` : JSON.stringify(name);
|
|
577
|
+
const isTyped = (encoding)=>encoding === 'float64' || encoding === 'date-f64' || encoding === 'boolean-byte';
|
|
578
|
+
const decoderSource = (columns)=>{
|
|
579
|
+
const lines = [
|
|
580
|
+
'"use strict";',
|
|
581
|
+
'var n = chunk.rowCount;',
|
|
582
|
+
'var rows = new Array(n);'
|
|
583
|
+
];
|
|
584
|
+
columns.forEach((column, index)=>{
|
|
585
|
+
if (column.encoding === 'json') {
|
|
586
|
+
lines.push(`var j${index} = json[${index}];`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
lines.push(`var c${index} = chunk.columns[${JSON.stringify(column.name)}];`);
|
|
590
|
+
lines.push(`var d${index} = c${index}.data;`);
|
|
591
|
+
if (isTyped(column.encoding)) {
|
|
592
|
+
lines.push(`var u${index} = c${index}.nulls;`);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
lines.push('for (var i = 0; i < n; i++) {');
|
|
596
|
+
lines.push('var b = i >> 3, m = 1 << (i & 7);');
|
|
597
|
+
// One object literal per row, properties in plan order, so all rows share a hidden class.
|
|
598
|
+
lines.push('rows[i] = {');
|
|
599
|
+
columns.forEach((column, index)=>{
|
|
600
|
+
lines.push(`${literalKey(column.name)}: ${rowValue(column, index)},`);
|
|
601
|
+
});
|
|
602
|
+
lines.push('};');
|
|
603
|
+
lines.push('}');
|
|
604
|
+
lines.push('return rows;');
|
|
605
|
+
return lines.join('\n');
|
|
606
|
+
};
|
|
607
|
+
/**
|
|
608
|
+
* Whether this environment allows generated functions at all.
|
|
609
|
+
*
|
|
610
|
+
* `new Function` needs `unsafe-eval`, which a Content-Security-Policy can withhold. A caller
|
|
611
|
+
* asks once at startup and stops sending plans if the answer is no, which falls back to the
|
|
612
|
+
* transport's ordinary clone — a known-good path. There is deliberately no reflective decoder to
|
|
613
|
+
* fall back to: one was measured and never beat the clone it would replace.
|
|
614
|
+
*/ let generationSupported = null;
|
|
615
|
+
const isTransferCodecSupported = ()=>{
|
|
616
|
+
if (generationSupported == null) {
|
|
617
|
+
try {
|
|
618
|
+
new Function('return 1')();
|
|
619
|
+
generationSupported = true;
|
|
620
|
+
} catch {
|
|
621
|
+
generationSupported = false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return generationSupported;
|
|
625
|
+
};
|
|
626
|
+
const buildDecoder = (columns)=>new Function('chunk', 'json', decoderSource(columns));
|
|
627
|
+
const decoderFor = (columns)=>{
|
|
628
|
+
const key = cacheKey(columns);
|
|
629
|
+
const hit = cached(key);
|
|
630
|
+
if (hit != null) {
|
|
631
|
+
return hit;
|
|
632
|
+
}
|
|
633
|
+
const decoder = buildDecoder(columns);
|
|
634
|
+
remember(key, decoder);
|
|
635
|
+
return decoder;
|
|
636
|
+
};
|
|
637
|
+
/**
|
|
638
|
+
* Parsed outside the generated function, so a failure can name its column.
|
|
639
|
+
*
|
|
640
|
+
* One parse per JSON column per chunk replaces one per row — worth about 16% of the codec's
|
|
641
|
+
* total win, and it stacks with chunking.
|
|
642
|
+
*/ const parseJsonColumns = (columns, chunk)=>{
|
|
643
|
+
const parsed = new Array(columns.length);
|
|
644
|
+
columns.forEach((column, index)=>{
|
|
645
|
+
if (column.encoding !== 'json') {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const encoded = chunk.columns[column.name];
|
|
649
|
+
const doc = encoded.encoding === 'json' ? encoded.doc : '[]';
|
|
650
|
+
try {
|
|
651
|
+
parsed[index] = JSON.parse(doc);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
throw jsonError(column.name, error);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
return parsed;
|
|
657
|
+
};
|
|
658
|
+
/**
|
|
659
|
+
* Decodes one chunk into final-shape row objects — real booleans, `Date` objects, parsed JSON.
|
|
660
|
+
*
|
|
661
|
+
* Not the raw storage shape. The entity needs the final shape either way, and decoding to raw and
|
|
662
|
+
* re-shaping afterwards measured slower (156ms against 140ms at 100,000 rows). An absent or null
|
|
663
|
+
* value becomes JavaScript `null`, never `undefined` and never an absent property.
|
|
664
|
+
*
|
|
665
|
+
* A column that fell back to `clone` in the worker comes back RAW, and the caller still owes it
|
|
666
|
+
* whatever shaping that column would otherwise have had.
|
|
667
|
+
*/ const decodeChunk = (plan, chunk)=>{
|
|
668
|
+
assertTransferVersion(plan.version);
|
|
669
|
+
assertTransferVersion(chunk.version);
|
|
670
|
+
const columns = effectiveColumns(plan, chunk);
|
|
671
|
+
return decoderFor(columns)(chunk, parseJsonColumns(columns, chunk));
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
;// CONCATENATED MODULE: ./src/schema/types.ts
|
|
675
|
+
var types_SchemaTypes = /*#__PURE__*/ function(SchemaTypes) {
|
|
676
|
+
SchemaTypes["Array"] = "Array";
|
|
677
|
+
SchemaTypes["Boolean"] = "Boolean";
|
|
678
|
+
SchemaTypes["Date"] = "Date";
|
|
679
|
+
SchemaTypes["Number"] = "Number";
|
|
680
|
+
SchemaTypes["Object"] = "Object";
|
|
681
|
+
SchemaTypes["String"] = "String";
|
|
682
|
+
SchemaTypes["Definition"] = "Definition";
|
|
683
|
+
SchemaTypes["Function"] = "Function";
|
|
684
|
+
SchemaTypes["Computed"] = "Computed";
|
|
685
|
+
/**
|
|
686
|
+
* Content in, reference out. The only type whose write shape differs from its stored
|
|
687
|
+
* shape, and a leaf on purpose — see `SchemaFile`.
|
|
688
|
+
*/ SchemaTypes["File"] = "File";
|
|
689
|
+
/**
|
|
690
|
+
* A fixed-length list of numbers, carrying its dimension count — see `SchemaVector`.
|
|
691
|
+
*
|
|
692
|
+
* Value-shaped exactly like `s.array(s.number())`, which is why every array codegen
|
|
693
|
+
* handler accepts it. It is a distinct type only so a backend can recognise it and store
|
|
694
|
+
* it natively; nothing else needs to tell the two apart.
|
|
695
|
+
*/ SchemaTypes["Vector"] = "Vector";
|
|
696
|
+
return SchemaTypes;
|
|
697
|
+
}({});
|
|
698
|
+
var types_HashType = /*#__PURE__*/ (/* unused pure expression or super */ null && (function(HashType) {
|
|
699
|
+
HashType["Ids"] = "Ids";
|
|
700
|
+
HashType["Object"] = "Object";
|
|
701
|
+
return HashType;
|
|
702
|
+
}({})));
|
|
703
|
+
|
|
704
|
+
;// CONCATENATED MODULE: ./src/transfer/plan.ts
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* For an engine that returns a column as the raw text or number it stored.
|
|
709
|
+
*
|
|
710
|
+
* SQLite is the case this was measured against — a date is TEXT holding ISO-8601 and a boolean is
|
|
711
|
+
* INTEGER holding 0 or 1 — but the mapping is about the STORED shape, not about SQL. Any engine
|
|
712
|
+
* that keeps those encodings uses it; one that parses values before returning them (PGlite) needs
|
|
713
|
+
* its own.
|
|
714
|
+
*
|
|
715
|
+
* `String` is deliberately absent, so strings cross in a plain array. Cloning a V8 string is a
|
|
716
|
+
* native memcpy and encoding one measured slower: 14.0ms against 8.3ms for 4,000 rows of 2KB text.
|
|
717
|
+
*/ const rawStorageTransferTypes = {
|
|
718
|
+
[types_SchemaTypes.Number]: 'float64',
|
|
719
|
+
[types_SchemaTypes.Boolean]: 'boolean-byte',
|
|
720
|
+
[types_SchemaTypes.Date]: 'date-f64',
|
|
721
|
+
[types_SchemaTypes.Object]: 'json',
|
|
722
|
+
[types_SchemaTypes.Array]: 'json',
|
|
723
|
+
[types_SchemaTypes.Vector]: 'json'
|
|
724
|
+
};
|
|
725
|
+
/**
|
|
726
|
+
* For an engine that returns values already parsed — a document store, a key-value store holding
|
|
727
|
+
* decoded records, or a driver with type parsers registered.
|
|
728
|
+
*
|
|
729
|
+
* Differs from {@link rawStorageTransferTypes} in ONE place: a nested structure arrives as a live
|
|
730
|
+
* object, so it is stringified on the way out rather than passed through as text. Dates and
|
|
731
|
+
* booleans need no separate entry, because those fillers accept either shape.
|
|
732
|
+
*
|
|
733
|
+
* Whether `json-stringify` beats `clone` for a given payload is unmeasured — see the encoding's
|
|
734
|
+
* own note. An engine unsure of that should map its nested types to `clone` and keep today's
|
|
735
|
+
* behaviour.
|
|
736
|
+
*/ const parsedValueTransferTypes = {
|
|
737
|
+
[types_SchemaTypes.Number]: 'float64',
|
|
738
|
+
[types_SchemaTypes.Boolean]: 'boolean-byte',
|
|
739
|
+
[types_SchemaTypes.Date]: 'date-f64',
|
|
740
|
+
[types_SchemaTypes.Object]: 'json-stringify',
|
|
741
|
+
[types_SchemaTypes.Array]: 'json-stringify',
|
|
742
|
+
[types_SchemaTypes.Vector]: 'json-stringify'
|
|
743
|
+
};
|
|
744
|
+
/**
|
|
745
|
+
* True when this layer must not touch the column, whatever its declared type says.
|
|
746
|
+
*
|
|
747
|
+
* A property that serializes, deserializes or transforms itself owns its storage shape, and this
|
|
748
|
+
* has no way to know what that shape is. Handing an already-parsed value to a property carrying
|
|
749
|
+
* `.deserialize(x => JSON.parse(String(x)))` throws, from a schema that was working.
|
|
750
|
+
*/ const ownsItsShape = (property)=>property.valueSerializer != null || property.valueDeserializer != null || property.transform != null || property.functionBody != null;
|
|
751
|
+
/**
|
|
752
|
+
* The encoding for one column.
|
|
753
|
+
*
|
|
754
|
+
* A schema type alone does not prove what the engine will return — a migration or an external
|
|
755
|
+
* writer can put anything in a column — so this is a starting point the encoder still validates
|
|
756
|
+
* per value.
|
|
757
|
+
*/ const transferEncodingFor = (column, strategy)=>{
|
|
758
|
+
const property = column.property;
|
|
759
|
+
// Checked BEFORE the resolver, and not overridable by it. A property carrying its own
|
|
760
|
+
// serializer owns its storage shape; pre-shaping it throws from a schema that was working,
|
|
761
|
+
// and that is true on every engine.
|
|
762
|
+
if (property == null || ownsItsShape(property)) {
|
|
763
|
+
return 'clone';
|
|
764
|
+
}
|
|
765
|
+
const { resolve, types } = normalize(strategy);
|
|
766
|
+
return resolve?.(column) ?? types?.[property.type] ?? 'clone';
|
|
767
|
+
};
|
|
768
|
+
const normalize = (strategy)=>{
|
|
769
|
+
if (typeof strategy === 'function') {
|
|
770
|
+
return {
|
|
771
|
+
resolve: strategy
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
if ('resolve' in strategy || 'types' in strategy) {
|
|
775
|
+
return strategy;
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
types: strategy
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Builds the plan for an ordered result column list, or `undefined` when there is nothing to plan.
|
|
783
|
+
*
|
|
784
|
+
* `undefined` is not a failure — it means this result takes the ordinary clone path. Two shapes
|
|
785
|
+
* get it:
|
|
786
|
+
*
|
|
787
|
+
* - **No columns.** There is no row to decode.
|
|
788
|
+
* - **A repeated column name.** One chunk carries one entry per name, and a row object holds one
|
|
789
|
+
* value per key, so a result naming a column twice cannot round-trip through a plan. That is a
|
|
790
|
+
* legal result that works without the codec, so it keeps working rather than becoming an error.
|
|
791
|
+
*/ const buildTransferPlan = (columns, strategy)=>{
|
|
792
|
+
if (columns.length === 0) {
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
795
|
+
const names = new Set(columns.map((column)=>column.name));
|
|
796
|
+
if (names.size !== columns.length) {
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
version: /* inlined export .TRANSFER_VERSION */ (1),
|
|
801
|
+
columns: columns.map((column)=>({
|
|
802
|
+
name: column.name,
|
|
803
|
+
encoding: transferEncodingFor(column, strategy)
|
|
804
|
+
}))
|
|
805
|
+
};
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
;// CONCATENATED MODULE: ./src/transfer/index.ts
|
|
809
|
+
/**
|
|
810
|
+
* The worker-boundary transfer codec.
|
|
811
|
+
*
|
|
812
|
+
* Not SQL-specific. Any plugin whose engine runs in a worker crosses this boundary for the same
|
|
813
|
+
* reason — `FileSystemFileHandle.createSyncAccessHandle` is undefined on the main thread, so OPFS
|
|
814
|
+
* persistence is only reachable from a worker — and pays the same structured clone for its records.
|
|
815
|
+
* A document store, a key-value store, or anything else compiled to WASM over OPFS is the same
|
|
816
|
+
* problem: many records, each a set of named values, crossing one `postMessage`.
|
|
817
|
+
*
|
|
818
|
+
* What an engine has to supply is small and deliberately so:
|
|
819
|
+
*
|
|
820
|
+
* - **An ordered list of the fields a result carries**, and the schema property behind each one
|
|
821
|
+
* where there is one. Positional (`appendRow`) or name-keyed (`appendRecord`), whichever the
|
|
822
|
+
* engine yields.
|
|
823
|
+
* - **A `TransferEncodingStrategy`** — which encoding each field takes. A table by schema type
|
|
824
|
+
* covers the common case; a resolver covers an engine whose answer is not a function of the type.
|
|
825
|
+
*
|
|
826
|
+
* Every encoding names a VALUE shape rather than an engine, and the fillers accept every shape a
|
|
827
|
+
* value plausibly arrives in: a date as a `Date`, an epoch number, or text; a boolean as a boolean
|
|
828
|
+
* or as 0/1; a nested structure as text (`json`) or as a live object (`json-stringify`). An engine
|
|
829
|
+
* chooses; nothing here assumes.
|
|
830
|
+
*
|
|
831
|
+
* Three layers, and the first two are here:
|
|
832
|
+
*
|
|
833
|
+
* - **codec** (`types`, `ChunkEncoder`, `decoder`) — column values in, `{ payload, transferables }`
|
|
834
|
+
* out. Knows nothing about schemas or workers.
|
|
835
|
+
* - **plan building** (`plan`) — result columns become a `TransferPlan`. Needs `SchemaTypes` and a
|
|
836
|
+
* property's serializers, which are data-model facts, so it belongs here too. What varies by
|
|
837
|
+
* engine is only which values that engine hands back, and that is a `TransferTypeMapping` the
|
|
838
|
+
* caller passes in.
|
|
839
|
+
* - **wiring** — the worker protocol and the transport. Belongs to each plugin.
|
|
840
|
+
*
|
|
841
|
+
* What is NOT here is anything that knows a STORAGE LAYOUT. `entityResultColumns` lives in
|
|
842
|
+
* `@routier/sql-plugin-core` because "one JSON column per nested subtree, named for its root" is a
|
|
843
|
+
* fact about flat tables, not about the data model.
|
|
844
|
+
*
|
|
845
|
+
* Deliberately NOT in `core/src/plugins/wire/`: that module's contract is plain JSON for crossing
|
|
846
|
+
* a trust boundary over HTTP, and a transferable only moves in-process.
|
|
847
|
+
*
|
|
848
|
+
* The encoder is import-light on purpose — a worker file ships as its own bundle, so everything it
|
|
849
|
+
* pulls from core is bundled into it. `plan` adds one runtime import, the `SchemaTypes` enum, whose
|
|
850
|
+
* own module is type-only imports throughout.
|
|
851
|
+
*/
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
var __webpack_exports__CHUNK_ROWS = __webpack_exports__.iT;
|
|
857
|
+
var __webpack_exports__ChunkEncoder = __webpack_exports__.Im;
|
|
858
|
+
var __webpack_exports__TRANSFER_VERSION = __webpack_exports__.FG;
|
|
859
|
+
var __webpack_exports__assertColumnLayout = __webpack_exports__.rq;
|
|
860
|
+
var __webpack_exports__assertTransferVersion = __webpack_exports__.E5;
|
|
861
|
+
var __webpack_exports__buildTransferPlan = __webpack_exports__.fY;
|
|
862
|
+
var __webpack_exports__clearDecoderCache = __webpack_exports__.wN;
|
|
863
|
+
var __webpack_exports__decodeChunk = __webpack_exports__.WQ;
|
|
864
|
+
var __webpack_exports__isTransferCodecSupported = __webpack_exports__.Ai;
|
|
865
|
+
var __webpack_exports__isTransferJsonError = __webpack_exports__.WZ;
|
|
866
|
+
var __webpack_exports__nullByteLength = __webpack_exports__.YA;
|
|
867
|
+
var __webpack_exports__parsedValueTransferTypes = __webpack_exports__.ze;
|
|
868
|
+
var __webpack_exports__rawStorageTransferTypes = __webpack_exports__.yl;
|
|
869
|
+
var __webpack_exports__transferEncodingFor = __webpack_exports__.sg;
|
|
870
|
+
export { __webpack_exports__CHUNK_ROWS as CHUNK_ROWS, __webpack_exports__ChunkEncoder as ChunkEncoder, __webpack_exports__TRANSFER_VERSION as TRANSFER_VERSION, __webpack_exports__assertColumnLayout as assertColumnLayout, __webpack_exports__assertTransferVersion as assertTransferVersion, __webpack_exports__buildTransferPlan as buildTransferPlan, __webpack_exports__clearDecoderCache as clearDecoderCache, __webpack_exports__decodeChunk as decodeChunk, __webpack_exports__isTransferCodecSupported as isTransferCodecSupported, __webpack_exports__isTransferJsonError as isTransferJsonError, __webpack_exports__nullByteLength as nullByteLength, __webpack_exports__parsedValueTransferTypes as parsedValueTransferTypes, __webpack_exports__rawStorageTransferTypes as rawStorageTransferTypes, __webpack_exports__transferEncodingFor as transferEncodingFor };
|
|
871
|
+
|
|
872
|
+
//# sourceMappingURL=index.js.map
|