@query-farm/vgi 0.8.0 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@query-farm/vgi",
3
- "version": "0.8.0",
3
+ "version": "0.9.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)",
@@ -14,6 +14,10 @@ import {
14
14
  VGI_CONST_KEY,
15
15
  VGI_CONST_TRUE,
16
16
  VGI_DOC_KEY,
17
+ VGI_DEFAULT_KEY,
18
+ VGI_CHOICES_KEY,
19
+ VGI_RANGE_KEY,
20
+ VGI_PATTERN_KEY,
17
21
  } from "../types.js";
18
22
 
19
23
  export interface ArgumentSpec {
@@ -25,6 +29,126 @@ export interface ArgumentSpec {
25
29
  isVarargs?: boolean;
26
30
  isConst?: boolean;
27
31
  doc?: string;
32
+ /**
33
+ * Discovery-facing constraint metadata, pre-encoded to their surfaced UTF-8
34
+ * forms (presence-only — `undefined` means the constraint is absent and the
35
+ * corresponding field-metadata key is omitted entirely). Mirrors Python's
36
+ * `ArgumentSpec.default_json` / `choices_json` / `range_notation` / `pattern`
37
+ * so `argumentSpecsToSchema`/`schemaToArgumentSpecs` round-trip symmetrically.
38
+ */
39
+ defaultJson?: string;
40
+ choicesJson?: string;
41
+ rangeNotation?: string;
42
+ pattern?: string;
43
+ }
44
+
45
+ /**
46
+ * Raw per-argument constraint declarations, as authored on a function's
47
+ * argument descriptor. Encoded into the pre-computed {@link ArgumentSpec}
48
+ * constraint fields via {@link constraintSpecFields}.
49
+ */
50
+ export interface ArgumentConstraints {
51
+ /** Closed set of allowed values (JSON-encoded into `vgi_choices`). */
52
+ choices?: readonly unknown[];
53
+ /** Inclusive lower bound (`>=`). */
54
+ ge?: number;
55
+ /** Inclusive upper bound (`<=`). */
56
+ le?: number;
57
+ /** Exclusive lower bound (`>`). */
58
+ gt?: number;
59
+ /** Exclusive upper bound (`<`). */
60
+ lt?: number;
61
+ /** Regex the value must match (surfaced as `vgi_pattern`, as-is). */
62
+ pattern?: string;
63
+ /** Default value (JSON-encoded into `vgi_default`); optional args only. */
64
+ default?: unknown;
65
+ }
66
+
67
+ /** `JSON.stringify`, falling back to the value's string form when unserializable. */
68
+ function safeJson(value: unknown): string {
69
+ try {
70
+ const encoded = JSON.stringify(value);
71
+ // JSON.stringify returns undefined for functions/symbols/undefined.
72
+ if (encoded === undefined) {
73
+ return JSON.stringify(String(value));
74
+ }
75
+ return encoded;
76
+ } catch {
77
+ return JSON.stringify(String(value));
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Build interval notation from an argument's numeric bounds.
83
+ *
84
+ * Inclusive bounds (`ge`/`le`) render as square brackets, exclusive bounds
85
+ * (`gt`/`lt`) as parentheses, and an open side as `-inf`/`+inf`. Returns
86
+ * `undefined` when the argument has no numeric bound at all.
87
+ *
88
+ * Examples: `ge=0,le=10` -> `"[0, 10]"`; `gt=0` -> `"(0, +inf)"`;
89
+ * `ge=1,lt=10` -> `"[1, 10)"`.
90
+ */
91
+ export function formatRange(
92
+ ge?: number,
93
+ le?: number,
94
+ gt?: number,
95
+ lt?: number,
96
+ ): string | undefined {
97
+ if (ge === undefined && le === undefined && gt === undefined && lt === undefined) {
98
+ return undefined;
99
+ }
100
+ let low: string;
101
+ if (gt !== undefined) {
102
+ low = `(${gt}`;
103
+ } else if (ge !== undefined) {
104
+ low = `[${ge}`;
105
+ } else {
106
+ low = "(-inf";
107
+ }
108
+ let high: string;
109
+ if (lt !== undefined) {
110
+ high = `${lt})`;
111
+ } else if (le !== undefined) {
112
+ high = `${le}]`;
113
+ } else {
114
+ high = "+inf)";
115
+ }
116
+ return `${low}, ${high}`;
117
+ }
118
+
119
+ /**
120
+ * Encode raw {@link ArgumentConstraints} into the pre-computed constraint
121
+ * fields of an {@link ArgumentSpec} (presence-only — absent constraints yield
122
+ * no field). Mirrors Python's `_constraint_kwargs`.
123
+ */
124
+ export function constraintSpecFields(
125
+ constraints: ArgumentConstraints | undefined,
126
+ ): Pick<ArgumentSpec, "defaultJson" | "choicesJson" | "rangeNotation" | "pattern"> {
127
+ const fields: Pick<
128
+ ArgumentSpec,
129
+ "defaultJson" | "choicesJson" | "rangeNotation" | "pattern"
130
+ > = {};
131
+ if (constraints === undefined) return fields;
132
+
133
+ if ("default" in constraints && constraints.default !== undefined) {
134
+ fields.defaultJson = safeJson(constraints.default);
135
+ }
136
+ if (constraints.choices !== undefined) {
137
+ fields.choicesJson = safeJson([...constraints.choices]);
138
+ }
139
+ const range = formatRange(
140
+ constraints.ge,
141
+ constraints.le,
142
+ constraints.gt,
143
+ constraints.lt,
144
+ );
145
+ if (range !== undefined) {
146
+ fields.rangeNotation = range;
147
+ }
148
+ if (constraints.pattern !== undefined) {
149
+ fields.pattern = constraints.pattern;
150
+ }
151
+ return fields;
28
152
  }
29
153
 
30
154
  function argumentSpecSortKey(spec: ArgumentSpec): [number, number | string] {
@@ -76,6 +200,20 @@ export function argumentSpecsToSchema(specs: ArgumentSpec[]): VgiSchema {
76
200
  metadata.set(VGI_DOC_KEY, spec.doc);
77
201
  }
78
202
 
203
+ // Per-argument constraint metadata (presence-only; already value-encoded).
204
+ if (spec.defaultJson !== undefined) {
205
+ metadata.set(VGI_DEFAULT_KEY, spec.defaultJson);
206
+ }
207
+ if (spec.choicesJson !== undefined) {
208
+ metadata.set(VGI_CHOICES_KEY, spec.choicesJson);
209
+ }
210
+ if (spec.rangeNotation !== undefined) {
211
+ metadata.set(VGI_RANGE_KEY, spec.rangeNotation);
212
+ }
213
+ if (spec.pattern !== undefined) {
214
+ metadata.set(VGI_PATTERN_KEY, spec.pattern);
215
+ }
216
+
79
217
  const field = field_(
80
218
  spec.name,
81
219
  spec.arrowType,
@@ -184,6 +322,12 @@ export function schemaToArgumentSpecs(schema: VgiSchema): ArgumentSpec[] {
184
322
  const isConst = metadata.get(VGI_CONST_KEY) === VGI_CONST_TRUE;
185
323
  const doc = metadata.get(VGI_DOC_KEY) ?? "";
186
324
 
325
+ // Per-argument constraint metadata (absent key -> undefined).
326
+ const defaultJson = metadata.get(VGI_DEFAULT_KEY);
327
+ const choicesJson = metadata.get(VGI_CHOICES_KEY);
328
+ const rangeNotation = metadata.get(VGI_RANGE_KEY);
329
+ const pattern = metadata.get(VGI_PATTERN_KEY);
330
+
187
331
  specs.push({
188
332
  name: field.name,
189
333
  position,
@@ -193,6 +337,10 @@ export function schemaToArgumentSpecs(schema: VgiSchema): ArgumentSpec[] {
193
337
  isVarargs,
194
338
  isConst,
195
339
  doc,
340
+ defaultJson,
341
+ choicesJson,
342
+ rangeNotation,
343
+ pattern,
196
344
  });
197
345
  }
198
346
 
@@ -37,7 +37,7 @@ import type {
37
37
  FunctionExample,
38
38
  } from "./types.js";
39
39
  import type { ArgumentSpec } from "../arguments/argument-spec.js";
40
- import { argumentSpecsToSchema } from "../arguments/argument-spec.js";
40
+ import { argumentSpecsToSchema, constraintSpecFields } from "../arguments/argument-spec.js";
41
41
  import { batchToScalarDict, batchToSecretDict, batchFromColumns, projectBatch, safeNumber } from "../util/arrow/index.js";
42
42
 
43
43
  // ============================================================================
@@ -71,6 +71,22 @@ export interface ScalarParameterDef {
71
71
  varargs?: boolean;
72
72
  /** Human-readable per-argument description (surfaced as `vgi_doc`). */
73
73
  doc?: string;
74
+ // Discovery-facing validation constraints (surfaced via
75
+ // `vgi_function_arguments()`). All optional; see `ArgumentConstraints`.
76
+ /** Closed set of allowed values (surfaced as `vgi_choices`). */
77
+ choices?: readonly unknown[];
78
+ /** Inclusive lower bound, `>=` (folded into `vgi_range`). */
79
+ ge?: number;
80
+ /** Inclusive upper bound, `<=` (folded into `vgi_range`). */
81
+ le?: number;
82
+ /** Exclusive lower bound, `>` (folded into `vgi_range`). */
83
+ gt?: number;
84
+ /** Exclusive upper bound, `<` (folded into `vgi_range`). */
85
+ lt?: number;
86
+ /** Regex the value must match (surfaced as `vgi_pattern`). */
87
+ pattern?: string;
88
+ /** Default value (surfaced as `vgi_default`, JSON-encoded). */
89
+ default?: unknown;
74
90
  }
75
91
 
76
92
  /**
@@ -183,6 +199,7 @@ export function defineScalarFunction<
183
199
  isConst: p.const,
184
200
  isVarargs: p.varargs,
185
201
  doc: p.doc,
202
+ ...constraintSpecFields(p),
186
203
  });
187
204
  }
188
205
  } else {
package/src/index.core.ts CHANGED
@@ -75,7 +75,7 @@ export type {
75
75
 
76
76
  // Arguments
77
77
  export { Arguments } from "./arguments/arguments.js";
78
- export { type ArgumentSpec, argumentSpecsToSchema, schemaToArgumentSpecs, macroArgumentsSchema, macroParameterDocsFromSchema } from "./arguments/argument-spec.js";
78
+ export { type ArgumentSpec, type ArgumentConstraints, argumentSpecsToSchema, schemaToArgumentSpecs, macroArgumentsSchema, macroParameterDocsFromSchema, formatRange, constraintSpecFields } from "./arguments/argument-spec.js";
79
79
 
80
80
  // Errors
81
81
  export {
package/src/types.ts CHANGED
@@ -62,6 +62,20 @@ export const VGI_CONST_TRUE = "true";
62
62
  // Per-argument description (UTF-8; presence-only). `vgi_doc_*` prefix reserved.
63
63
  export const VGI_DOC_KEY = "vgi_doc";
64
64
 
65
+ // Per-argument constraint metadata for agent discovery. All presence-only
66
+ // (omitted when absent) and value-encoded as UTF-8 so the surfaced column type
67
+ // stays uniform regardless of the arg's value type:
68
+ // vgi_default — JSON scalar (the arg's default value; optional args only)
69
+ // vgi_choices — JSON array (the closed set of allowed values)
70
+ // vgi_range — interval notation built from ge/le/gt/lt (e.g. "[0, 100]",
71
+ // "(0, +inf)", "[1, 10)"); a discovery surface, not raw bounds
72
+ // vgi_pattern — raw regex the value must match (open set)
73
+ // Kept byte-for-byte in sync with the C++ reader and the Python/other SDKs.
74
+ export const VGI_DEFAULT_KEY = "vgi_default";
75
+ export const VGI_CHOICES_KEY = "vgi_choices";
76
+ export const VGI_RANGE_KEY = "vgi_range";
77
+ export const VGI_PATTERN_KEY = "vgi_pattern";
78
+
65
79
  export interface TableCardinality {
66
80
  estimate: number | null;
67
81
  max: number | null;