@query-farm/vgi 0.33.0 → 0.35.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/dist/client/client.d.ts +1 -1
- package/dist/client/errors.d.ts +2 -1
- package/dist/client-entry.js +46 -2
- package/dist/client-entry.js.map +6 -6
- package/dist/index.js +46 -2
- package/dist/index.js.map +6 -6
- package/dist/serve-entry.js +45 -1
- package/dist/serve-entry.js.map +4 -4
- package/dist/worker-cf-entry.js +45 -1
- package/dist/worker-cf-entry.js.map +4 -4
- package/package.json +3 -3
- package/src/arguments/arguments.ts +9 -0
- package/src/arrow/impl-arrowjs/canonical.ts +50 -0
- package/src/arrow/impl-flechette/canonical.ts +21 -4
- package/src/client/client.ts +5 -1
- package/src/client/errors.ts +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@query-farm/vgi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "TypeScript framework for extending DuckDB with user-defined functions via the Vector Gateway Interface (VGI) over Arrow IPC.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Query Farm LLC (https://query.farm)",
|
|
@@ -91,12 +91,12 @@
|
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"@query-farm/apache-arrow": "^21.1.1",
|
|
94
|
-
"@query-farm/vgi-rpc": ">=0.25.
|
|
94
|
+
"@query-farm/vgi-rpc": ">=0.25.4 <0.26.0"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@duckdb/node-api": "^1.5.5-r.4",
|
|
98
98
|
"@query-farm/apache-arrow": "^21.1.1",
|
|
99
|
-
"@query-farm/vgi-rpc": "^0.25.
|
|
99
|
+
"@query-farm/vgi-rpc": "^0.25.4",
|
|
100
100
|
"@types/bun": "latest",
|
|
101
101
|
"typescript": "^7.0.2"
|
|
102
102
|
},
|
|
@@ -53,11 +53,20 @@ export class Arguments {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Unwrap an Arrow Scalar to a plain JS value.
|
|
56
|
+
*
|
|
57
|
+
* A `Date` is returned as-is. Arguments are decoded to their RICH values
|
|
58
|
+
* (deserializeArguments runs every value through the codec), and a JS `Date`
|
|
59
|
+
* is the rich value of date32/date64 — `valueOf()` would turn it into epoch
|
|
60
|
+
* MILLISECONDS, while a bare number in a date32 column means DAYS. So a DATE
|
|
61
|
+
* argument read through `get()` (every typed `params.args.x`, every scalar
|
|
62
|
+
* const) and written back out as a DATE came back 86,400,000x too far out:
|
|
63
|
+
* '2024-01-15' as 480510-12-09 on arrow-js, and an invalid Date on flechette.
|
|
56
64
|
*/
|
|
57
65
|
function unwrapScalar(val: any): any {
|
|
58
66
|
if (val === null || val === undefined) return null;
|
|
59
67
|
// If it's already a primitive, return it
|
|
60
68
|
if (typeof val !== "object") return val;
|
|
69
|
+
if (val instanceof Date) return val;
|
|
61
70
|
// Arrow Scalar objects have a valueOf() method
|
|
62
71
|
if (typeof val.valueOf === "function") {
|
|
63
72
|
let v: any;
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import {
|
|
28
28
|
Struct,
|
|
29
29
|
List,
|
|
30
|
+
FixedSizeList,
|
|
30
31
|
DataType,
|
|
31
32
|
DateUnit,
|
|
32
33
|
makeData,
|
|
@@ -54,6 +55,7 @@ export function writeCanonicalColumn(
|
|
|
54
55
|
|
|
55
56
|
function buildColumnData(values: unknown[], type: DataType): any {
|
|
56
57
|
if (DataType.isList(type)) return buildListData(values, type as List);
|
|
58
|
+
if (DataType.isFixedSizeList(type)) return buildFixedSizeListData(values, type as FixedSizeList);
|
|
57
59
|
if (DataType.isMap(type)) return buildMapData(values, type as Map_);
|
|
58
60
|
if (DataType.isStruct(type)) return buildStructData(values, type as Struct);
|
|
59
61
|
if (DataType.isDecimal(type)) return buildDecimalData(values, type);
|
|
@@ -200,6 +202,48 @@ function buildListData(values: unknown[], listType: List): any {
|
|
|
200
202
|
});
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
/**
|
|
206
|
+
* FixedSizeList (DuckDB's ARRAY) is built by hand, like List, so its items go
|
|
207
|
+
* through buildColumnData. Handing it to vectorFromArray fed arrow-js's own
|
|
208
|
+
* child builders CANONICAL values they read in other units: a date32
|
|
209
|
+
* day-number as epoch-ms (DATE[2], STRUCT(d DATE)[1], MAP(DATE, DATE)[1] and
|
|
210
|
+
* DATE[][1] all came back as 1970-01-01), and a bigint timestamp or unscaled
|
|
211
|
+
* decimal threw.
|
|
212
|
+
*/
|
|
213
|
+
function buildFixedSizeListData(values: unknown[], type: FixedSizeList): any {
|
|
214
|
+
const childField = type.children[0];
|
|
215
|
+
const listSize = type.listSize;
|
|
216
|
+
const numRows = values.length;
|
|
217
|
+
const allItems: unknown[] = [];
|
|
218
|
+
const nullBitmap = new Uint8Array(Math.ceil(numRows / 8));
|
|
219
|
+
let nullCount = 0;
|
|
220
|
+
for (let i = 0; i < numRows; i++) {
|
|
221
|
+
const row = values[i];
|
|
222
|
+
if (row == null) {
|
|
223
|
+
// A null row still occupies listSize child slots.
|
|
224
|
+
nullCount++;
|
|
225
|
+
for (let j = 0; j < listSize; j++) allItems.push(null);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
nullBitmap[i >> 3] |= 1 << (i & 7);
|
|
229
|
+
const items = Array.isArray(row) ? row : Array.from(row as Iterable<unknown>);
|
|
230
|
+
if (items.length !== listSize) {
|
|
231
|
+
throw new TypeError(
|
|
232
|
+
`fixed_size_list[${listSize}]: row ${i} has ${items.length} items`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
for (const item of items) allItems.push(item);
|
|
236
|
+
}
|
|
237
|
+
const childData = allItems.length > 0
|
|
238
|
+
? buildColumnData(allItems, childField.type)
|
|
239
|
+
: emptyDataForType(childField.type);
|
|
240
|
+
return makeData({
|
|
241
|
+
type, length: numRows, nullCount,
|
|
242
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
243
|
+
child: childData,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
203
247
|
function buildMapData(values: unknown[], mapType: Map_): any {
|
|
204
248
|
const numRows = values.length;
|
|
205
249
|
const entriesField = mapType.children[0];
|
|
@@ -285,6 +329,12 @@ function emptyDataForType(type: DataType): any {
|
|
|
285
329
|
valueOffsets: new Int32Array([0]),
|
|
286
330
|
});
|
|
287
331
|
}
|
|
332
|
+
if (DataType.isFixedSizeList(type)) {
|
|
333
|
+
return makeData({
|
|
334
|
+
type, length: 0, nullCount: 0,
|
|
335
|
+
child: emptyDataForType((type as FixedSizeList).children[0].type),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
288
338
|
if (DataType.isStruct(type)) {
|
|
289
339
|
const children = (type as any).children.map((cf: Field) => emptyDataForType(cf.type));
|
|
290
340
|
return makeData({ type, length: 0, nullCount: 0, children });
|
|
@@ -118,12 +118,29 @@ export function prepareForFlechette(type: VgiDataType, values: unknown[]): unkno
|
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
if (tid === 17) {
|
|
121
|
+
// The keys and values need the same preparation as a column of their
|
|
122
|
+
// type: a date32 key/value is a canonical day-number, which flechette's
|
|
123
|
+
// builder reads as epoch-ms, so MAP {DATE: DATE} came back as
|
|
124
|
+
// {1970-01-01=1970-01-01} unless it is turned into a JS Date first.
|
|
125
|
+
const entries = (type as any).children?.[0]?.type?.children;
|
|
126
|
+
const keyType = entries?.[0]?.type as VgiDataType | undefined;
|
|
127
|
+
const valueType = entries?.[1]?.type as VgiDataType | undefined;
|
|
128
|
+
const prepKey = keyType !== undefined && needsPrepare(keyType);
|
|
129
|
+
const prepValue = valueType !== undefined && needsPrepare(valueType);
|
|
121
130
|
return values.map((v) => {
|
|
122
131
|
if (v == null) return null;
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
126
|
-
|
|
132
|
+
let pairs: Array<[unknown, unknown]>;
|
|
133
|
+
if (v instanceof Map) pairs = Array.from(v.entries());
|
|
134
|
+
else if (Array.isArray(v)) pairs = v as Array<[unknown, unknown]>;
|
|
135
|
+
else if (typeof v === "object") pairs = Object.entries(v as any);
|
|
136
|
+
else return v;
|
|
137
|
+
if (prepKey || prepValue) {
|
|
138
|
+
pairs = pairs.map(([k, x]) => [
|
|
139
|
+
prepKey ? prepareForFlechette(keyType!, [k])[0] : k,
|
|
140
|
+
prepValue ? prepareForFlechette(valueType!, [x])[0] : x,
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
return new Map(pairs);
|
|
127
144
|
});
|
|
128
145
|
}
|
|
129
146
|
if (tid === 13) {
|
package/src/client/client.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
// Works with any RpcClient (subprocess or HTTP transport).
|
|
4
4
|
|
|
5
5
|
import { type VgiSchema, schema as schema_, type VgiField, field, type VgiBatch, type VgiDataType, utf8, binary, list } from "../arrow/index.js";
|
|
6
|
-
import
|
|
6
|
+
// `import type`, not `import { type … }`: the latter keeps the statement and
|
|
7
|
+
// emits `import {} from "@query-farm/vgi-rpc"`, a side-effect import of the
|
|
8
|
+
// package root — which re-exports the whole framework, server included, into
|
|
9
|
+
// every browser bundle. See the note in ./errors.
|
|
10
|
+
import type { RpcClient, StreamSession } from "@query-farm/vgi-rpc";
|
|
7
11
|
import {
|
|
8
12
|
serializeBindRequest,
|
|
9
13
|
deserializeBindResponse,
|
package/src/client/errors.ts
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
// VgiClient error class + RpcClient wrapper that translates RpcError into
|
|
3
3
|
// VgiClientError, preserving the worker's remote traceback.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// RpcError comes from the `/connect` subpath, not the package root, and the
|
|
6
|
+
// distinction is worth ~160 kB in every browser bundle. The root re-exports the
|
|
7
|
+
// whole framework — protocol, dispatch, access log, the server — and a bundler
|
|
8
|
+
// cannot drop it, so importing one error class from there shipped `RpcServer`
|
|
9
|
+
// to browsers to satisfy an `instanceof`. `RpcClient` is a pure `import type`
|
|
10
|
+
// for the same reason: `import { type X }` leaves the statement behind, and a
|
|
11
|
+
// bare `import {} from "@query-farm/vgi-rpc"` is a side-effect import of that
|
|
12
|
+
// same graph.
|
|
13
|
+
import { RpcError } from "@query-farm/vgi-rpc/connect";
|
|
14
|
+
import type { RpcClient } from "@query-farm/vgi-rpc";
|
|
6
15
|
|
|
7
16
|
/** Error thrown by VgiClient when an RPC call fails or returns unexpected data. */
|
|
8
17
|
export class VgiClientError extends Error {
|