@query-farm/vgi 0.9.0 → 0.10.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/arguments/argument-spec.d.ts +7 -0
- package/dist/index.js +63 -2
- package/dist/index.js.map +4 -4
- package/dist/worker-cf-entry.js +70 -2
- package/dist/worker-cf-entry.js.map +4 -4
- package/package.json +1 -1
- package/src/arguments/argument-spec.ts +68 -0
- package/src/functions/scalar.ts +31 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@query-farm/vgi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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)",
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
VGI_RANGE_KEY,
|
|
20
20
|
VGI_PATTERN_KEY,
|
|
21
21
|
} from "../types.js";
|
|
22
|
+
import { ArgumentValidationError } from "../errors.js";
|
|
22
23
|
|
|
23
24
|
export interface ArgumentSpec {
|
|
24
25
|
name: string;
|
|
@@ -151,6 +152,73 @@ export function constraintSpecFields(
|
|
|
151
152
|
return fields;
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
/** Coerce a numeric const value (number or int64 `bigint`) to `number`. */
|
|
156
|
+
function toNumber(value: unknown): number | undefined {
|
|
157
|
+
if (typeof value === "number") return value;
|
|
158
|
+
if (typeof value === "bigint") return Number(value);
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Equality for a choice comparison, coercing numeric/bigint operands. */
|
|
163
|
+
function choiceEquals(choice: unknown, value: unknown): boolean {
|
|
164
|
+
const cn = toNumber(choice);
|
|
165
|
+
const vn = toNumber(value);
|
|
166
|
+
if (cn !== undefined && vn !== undefined) return cn === vn;
|
|
167
|
+
return choice === value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Enforce a bind-time const value against its declared {@link ArgumentConstraints},
|
|
172
|
+
* throwing {@link ArgumentValidationError} on violation. Mirrors the Python SDK's
|
|
173
|
+
* `Arg._validate`: numeric range (`ge`/`le`/`gt`/`lt`), closed choice set, and
|
|
174
|
+
* regex `pattern`. A `null`/`undefined` value skips its value constraints.
|
|
175
|
+
*/
|
|
176
|
+
export function validateConstConstraints(
|
|
177
|
+
name: string,
|
|
178
|
+
constraints: ArgumentConstraints,
|
|
179
|
+
value: unknown,
|
|
180
|
+
): void {
|
|
181
|
+
if (value === null || value === undefined) return;
|
|
182
|
+
|
|
183
|
+
const num = toNumber(value);
|
|
184
|
+
if (num !== undefined) {
|
|
185
|
+
if (constraints.ge !== undefined && num < constraints.ge) {
|
|
186
|
+
throw new ArgumentValidationError(`argument '${name}' must be >= ${constraints.ge}`);
|
|
187
|
+
}
|
|
188
|
+
if (constraints.le !== undefined && num > constraints.le) {
|
|
189
|
+
throw new ArgumentValidationError(`argument '${name}' must be <= ${constraints.le}`);
|
|
190
|
+
}
|
|
191
|
+
if (constraints.gt !== undefined && num <= constraints.gt) {
|
|
192
|
+
throw new ArgumentValidationError(`argument '${name}' must be > ${constraints.gt}`);
|
|
193
|
+
}
|
|
194
|
+
if (constraints.lt !== undefined && num >= constraints.lt) {
|
|
195
|
+
throw new ArgumentValidationError(`argument '${name}' must be < ${constraints.lt}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (constraints.choices !== undefined && constraints.choices.length > 0) {
|
|
200
|
+
if (!constraints.choices.some((c) => choiceEquals(c, value))) {
|
|
201
|
+
throw new ArgumentValidationError(
|
|
202
|
+
`argument '${name}' must be one of ${JSON.stringify([...constraints.choices])}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (constraints.pattern !== undefined && typeof value === "string") {
|
|
208
|
+
let re: RegExp | undefined;
|
|
209
|
+
try {
|
|
210
|
+
re = new RegExp(constraints.pattern);
|
|
211
|
+
} catch {
|
|
212
|
+
re = undefined; // an invalid declared regex must not reject the caller
|
|
213
|
+
}
|
|
214
|
+
if (re !== undefined && !re.test(value)) {
|
|
215
|
+
throw new ArgumentValidationError(
|
|
216
|
+
`argument '${name}' must match pattern ${JSON.stringify(constraints.pattern)}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
154
222
|
function argumentSpecSortKey(spec: ArgumentSpec): [number, number | string] {
|
|
155
223
|
if (typeof spec.position === "number") {
|
|
156
224
|
return [0, spec.position];
|
package/src/functions/scalar.ts
CHANGED
|
@@ -37,7 +37,12 @@ import type {
|
|
|
37
37
|
FunctionExample,
|
|
38
38
|
} from "./types.js";
|
|
39
39
|
import type { ArgumentSpec } from "../arguments/argument-spec.js";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
argumentSpecsToSchema,
|
|
42
|
+
constraintSpecFields,
|
|
43
|
+
validateConstConstraints,
|
|
44
|
+
type ArgumentConstraints,
|
|
45
|
+
} from "../arguments/argument-spec.js";
|
|
41
46
|
import { batchToScalarDict, batchToSecretDict, batchFromColumns, projectBatch, safeNumber } from "../util/arrow/index.js";
|
|
42
47
|
|
|
43
48
|
// ============================================================================
|
|
@@ -232,6 +237,26 @@ export function defineScalarFunction<
|
|
|
232
237
|
}
|
|
233
238
|
}
|
|
234
239
|
|
|
240
|
+
// Raw const-arg constraints for bind-time enforcement, keyed by arg name.
|
|
241
|
+
// Only the ordered `parameters` API carries constraints (the legacy
|
|
242
|
+
// params/constParams path does not), so only its const params appear here.
|
|
243
|
+
const constConstraints = new Map<string, ArgumentConstraints>();
|
|
244
|
+
if (config.parameters) {
|
|
245
|
+
for (const p of config.parameters) {
|
|
246
|
+
if (
|
|
247
|
+
p.const &&
|
|
248
|
+
(p.choices !== undefined ||
|
|
249
|
+
p.ge !== undefined ||
|
|
250
|
+
p.le !== undefined ||
|
|
251
|
+
p.gt !== undefined ||
|
|
252
|
+
p.lt !== undefined ||
|
|
253
|
+
p.pattern !== undefined)
|
|
254
|
+
) {
|
|
255
|
+
constConstraints.set(p.name, p);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
235
260
|
const meta: FunctionMeta = {
|
|
236
261
|
name: config.name,
|
|
237
262
|
description: config.description,
|
|
@@ -270,7 +295,11 @@ export function defineScalarFunction<
|
|
|
270
295
|
let constIdx = 0;
|
|
271
296
|
for (const spec of specs) {
|
|
272
297
|
if (spec.isConst) {
|
|
273
|
-
|
|
298
|
+
const value = request.arguments.get(constIdx, undefined);
|
|
299
|
+
constArgs[spec.name] = value;
|
|
300
|
+
// Enforce declared const-arg constraints at bind (choices/range/pattern).
|
|
301
|
+
const constraints = constConstraints.get(spec.name);
|
|
302
|
+
if (constraints) validateConstConstraints(spec.name, constraints, value);
|
|
274
303
|
constIdx++;
|
|
275
304
|
}
|
|
276
305
|
}
|