@query-farm/vgi 0.38.0 → 0.40.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 +31 -0
- package/dist/client/client.d.ts +31 -2
- package/dist/client/types.d.ts +56 -1
- package/dist/client-entry.js +99 -8
- package/dist/client-entry.js.map +4 -4
- package/dist/functions/copy-to.d.ts +1 -1
- package/dist/index.core.d.ts +3 -3
- package/dist/index.js +128 -10
- package/dist/index.js.map +7 -7
- package/dist/rpc-server.d.ts +23 -1
- package/dist/serve-entry.d.ts +13 -1
- package/dist/serve-entry.js +31 -5
- package/dist/serve-entry.js.map +5 -5
- package/dist/worker-cf-entry.d.ts +1 -1
- package/dist/worker-cf-entry.js +13 -3
- package/dist/worker-cf-entry.js.map +6 -6
- package/package.json +3 -3
- package/src/client/client.ts +148 -0
- package/src/client/types.ts +58 -1
- package/src/codec/asd.ts +12 -8
- package/src/functions/copy-to.ts +1 -1
- package/src/index.core.ts +4 -0
- package/src/rpc-server.ts +55 -1
- package/src/serve-entry.ts +19 -1
- package/src/worker-cf-entry.ts +1 -0
package/README.md
CHANGED
|
@@ -615,6 +615,37 @@ never return `null` for one: both read as a definitive answer. Return `null` onl
|
|
|
615
615
|
when the store answered and the credential is unknown; throw `GrantRefusedError` to
|
|
616
616
|
decline a grant.
|
|
617
617
|
|
|
618
|
+
### Sealed grants: minted credentials that log their owner in
|
|
619
|
+
|
|
620
|
+
`issue_grant` mints a credential meant for unattended automation to present later
|
|
621
|
+
as an ordinary bearer. Configure **grant keys** and the worker both mints and
|
|
622
|
+
accepts them, with no storage and no code:
|
|
623
|
+
|
|
624
|
+
```console
|
|
625
|
+
$ VGI_RPC_GRANT_KEYS="$(openssl rand -base64 32)" bun run scripts/serve.ts
|
|
626
|
+
# or: bun run scripts/serve.ts --grant-key "$(openssl rand -base64 32)"
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
- `VGI_RPC_GRANT_KEYS`: comma-separated standard base64, exactly 32 bytes each. The
|
|
630
|
+
first mints; every key verifies. Rotate by adding the new key first and removing
|
|
631
|
+
the old one once its grants have expired. `--grant-key KEY` (repeatable) on
|
|
632
|
+
`serveVgiWorker`, or the `grantKeys` option (`GrantKeys.parse([...])`, required on
|
|
633
|
+
Cloudflare Workers, where there is no process environment) work too.
|
|
634
|
+
- Optional `VGI_RPC_GRANT_AUDIENCE` (bind grants to this deployment) and
|
|
635
|
+
`VGI_RPC_GRANT_MAX_TTL_SECONDS` (default 7 days).
|
|
636
|
+
- With keys set, HTTP hosts `issue_grant` (minted by the framework unless you supply
|
|
637
|
+
`mintGrant`) and the worker's HTTP authentication accepts `vgig1.` grants after
|
|
638
|
+
your own `authenticate`, as the grant's owner (domain `grant`). A caller
|
|
639
|
+
authenticated by a grant has no `auth_time`, so it cannot mint another grant.
|
|
640
|
+
- A worker that supplies `resolveToken` also has bearers your `authenticate` did not
|
|
641
|
+
accept resolved through it (domain `token`); an `AuthUnavailableError` from the hook
|
|
642
|
+
is a 503 with `Retry-After`, never a 401.
|
|
643
|
+
- Your `authenticate` must throw (not return anonymous) for a bearer it does not
|
|
644
|
+
recognise, or the chain ends before grants are tried.
|
|
645
|
+
- No key set, nothing changes. A malformed key stops the worker at startup.
|
|
646
|
+
- Sealed grants are not individually revocable: keep the lifetime short; removing a
|
|
647
|
+
key revokes every grant it minted.
|
|
648
|
+
|
|
618
649
|
## Runtimes & entry points
|
|
619
650
|
|
|
620
651
|
The package ships a backend-agnostic Arrow facade and selects an implementation at
|
package/dist/client/client.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type VgiBatch } from "../arrow/index.js";
|
|
2
2
|
import type { RpcClient } from "@query-farm/vgi-rpc";
|
|
3
|
-
import { type SchemaInfo, type TableInfo, type ViewInfo, type FunctionInfo, type MacroInfo, type MacroType, type CatalogAttachResult, type SchemaContentsInfo, type AttachOpaqueData, type TransactionOpaqueData, type CatalogInfo, type ScanFunctionResult } from "../catalog/interface.js";
|
|
3
|
+
import { type SchemaInfo, type TableInfo, type ViewInfo, type FunctionInfo, type MacroInfo, type MacroType, type CatalogAttachResult, type SchemaContentsInfo, type IndexInfo, type AttachOpaqueData, type TransactionOpaqueData, type CatalogInfo, type ScanFunctionResult } from "../catalog/interface.js";
|
|
4
4
|
import { VgiClientError } from "./errors.js";
|
|
5
5
|
export { VgiClientError };
|
|
6
|
-
import type { VgiClientOptions, TableFunctionOptions, ScalarFunctionOptions, TableInOutFunctionOptions, OnCreateConflict, CatalogFunctionType, CatalogMacroType, CatalogAttachOptions, AttachOptionValue } from "./types.js";
|
|
6
|
+
import type { VgiClientOptions, TableFunctionOptions, ScalarFunctionOptions, TableInOutFunctionOptions, OnCreateConflict, CatalogFunctionType, CatalogMacroType, CatalogAttachOptions, AttachOptionValue, CatalogSnapshot, LoadCatalogOptions } from "./types.js";
|
|
7
7
|
/**
|
|
8
8
|
* High-level client for calling VGI worker functions and catalog API.
|
|
9
9
|
*
|
|
@@ -103,6 +103,9 @@ export declare class VgiClient {
|
|
|
103
103
|
* current the worker answers `not_modified` with no schemas. `etag` null
|
|
104
104
|
* means the worker does not revalidate. Each schema's wire `path` is checked
|
|
105
105
|
* against its decoded `SchemaInfo.path`.
|
|
106
|
+
*
|
|
107
|
+
* This is the raw RPC. To enumerate a catalog, use `loadCatalog`, which
|
|
108
|
+
* checks the capability, revalidates and falls back to the per-schema RPCs.
|
|
106
109
|
*/
|
|
107
110
|
catalogContents(attachOpaqueData: AttachOpaqueData, ifNoneMatch?: string | null): Promise<{
|
|
108
111
|
catalog_version: number;
|
|
@@ -110,6 +113,30 @@ export declare class VgiClient {
|
|
|
110
113
|
not_modified: boolean;
|
|
111
114
|
schemas: SchemaContentsInfo[];
|
|
112
115
|
}>;
|
|
116
|
+
/**
|
|
117
|
+
* Enumerate the whole catalog: every schema with all of its tables, views,
|
|
118
|
+
* scalar/aggregate/table functions, scalar/table macros and indexes.
|
|
119
|
+
*
|
|
120
|
+
* Honors the attach result's `supports_catalog_contents` the way the DuckDB
|
|
121
|
+
* extension does:
|
|
122
|
+
*
|
|
123
|
+
* - advertised (and `useCatalogContents` not false, no transaction): one
|
|
124
|
+
* `catalog_contents` call. With `previous.etag` it is conditional
|
|
125
|
+
* (`if_none_match`); `not_modified` returns `previous`'s contents.
|
|
126
|
+
* - not advertised: `catalog_schemas` + per-schema
|
|
127
|
+
* `catalog_schema_contents_*` calls -- `catalog_contents` is never sent.
|
|
128
|
+
* Kinds whose `estimated_object_count` is exactly 0 are skipped.
|
|
129
|
+
* - advertised but the call fails (or answers malformed, e.g. a
|
|
130
|
+
* `SchemaContents.path` that disagrees with its `SchemaInfo.path`): falls
|
|
131
|
+
* back to the per-schema calls and reports the failure in
|
|
132
|
+
* `fallback_error`.
|
|
133
|
+
*
|
|
134
|
+
* A full answer older than `previous` (a lagging replica) is retried once,
|
|
135
|
+
* then the per-schema path is used.
|
|
136
|
+
*/
|
|
137
|
+
loadCatalog(attach: Pick<CatalogAttachResult, "attach_opaque_data" | "supports_catalog_contents">, opts?: LoadCatalogOptions): Promise<CatalogSnapshot>;
|
|
138
|
+
/** `loadCatalog` without `catalog_contents`: catalog_schemas + per-schema calls. */
|
|
139
|
+
private _loadCatalogPerSchema;
|
|
113
140
|
/** Detach a previously-attached catalog. */
|
|
114
141
|
catalogDetach(attachOpaqueData: AttachOpaqueData): Promise<void>;
|
|
115
142
|
/**
|
|
@@ -153,6 +180,8 @@ export declare class VgiClient {
|
|
|
153
180
|
schemaContentsViews(attachOpaqueData: AttachOpaqueData, path: string[] | string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo[]>;
|
|
154
181
|
/** List functions in a schema, filtered by type. */
|
|
155
182
|
schemaContentsFunctions(attachOpaqueData: AttachOpaqueData, path: string[] | string, type: CatalogFunctionType, transactionOpaqueData?: TransactionOpaqueData): Promise<FunctionInfo[]>;
|
|
183
|
+
/** List indexes in a schema. */
|
|
184
|
+
schemaContentsIndexes(attachOpaqueData: AttachOpaqueData, path: string[] | string, transactionOpaqueData?: TransactionOpaqueData): Promise<IndexInfo[]>;
|
|
156
185
|
/** Get a table by name, or null if not found. */
|
|
157
186
|
tableGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[] | string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo | null>;
|
|
158
187
|
/** Create a new table. */
|
package/dist/client/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { VgiBatch } from "../arrow/index.js";
|
|
|
2
2
|
import type { Arguments } from "../arguments/arguments.js";
|
|
3
3
|
import type { AttachOptionValue } from "../catalog/attach-options.js";
|
|
4
4
|
import type { BindResponse } from "../protocol/types.js";
|
|
5
|
+
import type { SchemaContentsInfo, TransactionOpaqueData } from "../catalog/interface.js";
|
|
5
6
|
import { OrderByDirection, OrderByNullOrder } from "../protocol/types.js";
|
|
6
7
|
export type { AttachOptionValue };
|
|
7
8
|
export { OrderByDirection, OrderByNullOrder };
|
|
@@ -46,7 +47,7 @@ export interface CatalogAttachOptions {
|
|
|
46
47
|
/** Conflict resolution strategy for create operations. */
|
|
47
48
|
export type OnCreateConflict = "error" | "ignore" | "replace";
|
|
48
49
|
/** DuckDB catalog function type filter (sent as uppercase wire values). */
|
|
49
|
-
export type CatalogFunctionType = "SCALAR_FUNCTION" | "TABLE_FUNCTION";
|
|
50
|
+
export type CatalogFunctionType = "SCALAR_FUNCTION" | "AGGREGATE_FUNCTION" | "TABLE_FUNCTION";
|
|
50
51
|
/** Macro type filter for schema contents listing (uppercase wire values). */
|
|
51
52
|
export type CatalogMacroType = "SCALAR_MACRO" | "TABLE_MACRO";
|
|
52
53
|
/** Options for constructing a VgiClient. */
|
|
@@ -136,3 +137,57 @@ export interface TableInOutFunctionOptions {
|
|
|
136
137
|
*/
|
|
137
138
|
hasFinalize?: boolean;
|
|
138
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* A whole-catalog snapshot returned by `VgiClient.loadCatalog`: every schema
|
|
142
|
+
* with all of its tables, views, functions, macros and indexes.
|
|
143
|
+
*
|
|
144
|
+
* Hold on to it and pass it back as `LoadCatalogOptions.previous` to
|
|
145
|
+
* revalidate: when it carries an `etag` the client sends that as
|
|
146
|
+
* `if_none_match`, and a `not_modified` answer returns this snapshot's
|
|
147
|
+
* contents unchanged.
|
|
148
|
+
*/
|
|
149
|
+
export interface CatalogSnapshot {
|
|
150
|
+
/**
|
|
151
|
+
* Version the snapshot was taken at, as reported by `catalog_contents`.
|
|
152
|
+
* `null` when it was assembled from the per-schema RPCs (which carry no
|
|
153
|
+
* version); 0 means the worker does not track versions.
|
|
154
|
+
*/
|
|
155
|
+
catalog_version: number | null;
|
|
156
|
+
/** Validator to revalidate with; null = the worker does not revalidate. */
|
|
157
|
+
etag: string | null;
|
|
158
|
+
/**
|
|
159
|
+
* How the contents were obtained: one `catalog_contents` call, or
|
|
160
|
+
* `catalog_schemas` + per-schema `catalog_schema_contents_*` calls.
|
|
161
|
+
*/
|
|
162
|
+
source: "catalog_contents" | "per_schema";
|
|
163
|
+
/** True when this load was a `not_modified` revalidation of `previous`. */
|
|
164
|
+
not_modified: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Set when the catalog advertised `catalog_contents` but the call failed
|
|
167
|
+
* (or returned a malformed answer) and the client fell back to the
|
|
168
|
+
* per-schema RPCs: the failure's message.
|
|
169
|
+
*/
|
|
170
|
+
fallback_error: string | null;
|
|
171
|
+
/** One entry per schema, parents before children. */
|
|
172
|
+
schemas: SchemaContentsInfo[];
|
|
173
|
+
}
|
|
174
|
+
/** Options for `VgiClient.loadCatalog`. */
|
|
175
|
+
export interface LoadCatalogOptions {
|
|
176
|
+
/**
|
|
177
|
+
* A snapshot from an earlier load of the same attach. Its `etag` (if any)
|
|
178
|
+
* is sent as `if_none_match`; a `not_modified` answer keeps its contents.
|
|
179
|
+
*/
|
|
180
|
+
previous?: CatalogSnapshot | null;
|
|
181
|
+
/**
|
|
182
|
+
* Set false to never call `catalog_contents` (the client-side equivalent of
|
|
183
|
+
* DuckDB's `SET vgi_catalog_contents = false`). Default true: it is used
|
|
184
|
+
* whenever the attach result advertised `supports_catalog_contents`.
|
|
185
|
+
*/
|
|
186
|
+
useCatalogContents?: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Enumerate inside this transaction. `catalog_contents` returns only the
|
|
189
|
+
* committed catalog (it takes no transaction), so a transaction-scoped load
|
|
190
|
+
* always uses the per-schema RPCs.
|
|
191
|
+
*/
|
|
192
|
+
transactionOpaqueData?: TransactionOpaqueData;
|
|
193
|
+
}
|
package/dist/client-entry.js
CHANGED
|
@@ -2388,21 +2388,23 @@ function normalizeValue(raw, type) {
|
|
|
2388
2388
|
return out;
|
|
2389
2389
|
}
|
|
2390
2390
|
if (isMap(type)) {
|
|
2391
|
+
const entries = type.children[0].type;
|
|
2392
|
+
const valueType = entries.children[1].type;
|
|
2391
2393
|
const out = {};
|
|
2394
|
+
const put = (k, v) => {
|
|
2395
|
+
out[String(k)] = normalizeValue(v, valueType);
|
|
2396
|
+
};
|
|
2392
2397
|
if (raw[Symbol.iterator]) {
|
|
2393
2398
|
for (const entry of raw) {
|
|
2394
2399
|
if (Array.isArray(entry)) {
|
|
2395
|
-
|
|
2400
|
+
put(entry[0], entry[1]);
|
|
2396
2401
|
} else if (entry && typeof entry === "object") {
|
|
2397
|
-
|
|
2398
|
-
const v = entry.value ?? entry[1];
|
|
2399
|
-
out[String(k)] = v == null ? "" : String(v);
|
|
2402
|
+
put(entry.key ?? entry[0], entry.value ?? entry[1]);
|
|
2400
2403
|
}
|
|
2401
2404
|
}
|
|
2402
2405
|
} else if (typeof raw === "object") {
|
|
2403
|
-
for (const [k, v] of Object.entries(raw))
|
|
2404
|
-
|
|
2405
|
-
}
|
|
2406
|
+
for (const [k, v] of Object.entries(raw))
|
|
2407
|
+
put(k, v);
|
|
2406
2408
|
}
|
|
2407
2409
|
return out;
|
|
2408
2410
|
}
|
|
@@ -3973,6 +3975,84 @@ class VgiClient {
|
|
|
3973
3975
|
schemas
|
|
3974
3976
|
};
|
|
3975
3977
|
}
|
|
3978
|
+
async loadCatalog(attach, opts) {
|
|
3979
|
+
const attachOpaqueData = attach.attach_opaque_data;
|
|
3980
|
+
const previous = opts?.previous ?? null;
|
|
3981
|
+
const useContents = attach.supports_catalog_contents === true && opts?.useCatalogContents !== false && opts?.transactionOpaqueData == null;
|
|
3982
|
+
if (!useContents) {
|
|
3983
|
+
return this._loadCatalogPerSchema(attachOpaqueData, opts?.transactionOpaqueData, null);
|
|
3984
|
+
}
|
|
3985
|
+
let fallbackError = null;
|
|
3986
|
+
const ifNoneMatch = previous?.etag ?? null;
|
|
3987
|
+
const knownVersion = previous?.catalog_version ?? 0;
|
|
3988
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
3989
|
+
let response;
|
|
3990
|
+
try {
|
|
3991
|
+
response = await this.catalogContents(attachOpaqueData, ifNoneMatch);
|
|
3992
|
+
} catch (err) {
|
|
3993
|
+
fallbackError = err instanceof Error ? err.message : String(err);
|
|
3994
|
+
break;
|
|
3995
|
+
}
|
|
3996
|
+
if (response.not_modified) {
|
|
3997
|
+
if (previous == null || ifNoneMatch == null) {
|
|
3998
|
+
fallbackError = "catalog_contents answered not_modified to an unconditional request";
|
|
3999
|
+
break;
|
|
4000
|
+
}
|
|
4001
|
+
return {
|
|
4002
|
+
...previous,
|
|
4003
|
+
catalog_version: response.catalog_version !== 0 ? response.catalog_version : previous.catalog_version,
|
|
4004
|
+
etag: response.etag ?? ifNoneMatch,
|
|
4005
|
+
not_modified: true,
|
|
4006
|
+
fallback_error: null
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
if (response.catalog_version !== 0 && knownVersion !== 0 && response.catalog_version < knownVersion) {
|
|
4010
|
+
fallbackError = `catalog_contents snapshot version ${response.catalog_version} is older than the known version ${knownVersion}`;
|
|
4011
|
+
continue;
|
|
4012
|
+
}
|
|
4013
|
+
return {
|
|
4014
|
+
catalog_version: response.catalog_version,
|
|
4015
|
+
etag: response.etag,
|
|
4016
|
+
source: "catalog_contents",
|
|
4017
|
+
not_modified: false,
|
|
4018
|
+
fallback_error: null,
|
|
4019
|
+
schemas: response.schemas
|
|
4020
|
+
};
|
|
4021
|
+
}
|
|
4022
|
+
return this._loadCatalogPerSchema(attachOpaqueData, undefined, fallbackError);
|
|
4023
|
+
}
|
|
4024
|
+
async _loadCatalogPerSchema(attachOpaqueData, transactionOpaqueData, fallbackError) {
|
|
4025
|
+
const schemas = await this.schemas(attachOpaqueData, transactionOpaqueData);
|
|
4026
|
+
const out = [];
|
|
4027
|
+
for (const schema of schemas) {
|
|
4028
|
+
const counts = schema.estimated_object_count ?? {};
|
|
4029
|
+
const path = schema.path;
|
|
4030
|
+
const has = (kind) => {
|
|
4031
|
+
const n = counts[kind];
|
|
4032
|
+
return n == null || Number(n) !== 0;
|
|
4033
|
+
};
|
|
4034
|
+
const t = transactionOpaqueData;
|
|
4035
|
+
out.push({
|
|
4036
|
+
schema,
|
|
4037
|
+
tables: has("table") ? await this.schemaContentsTables(attachOpaqueData, path, t) : [],
|
|
4038
|
+
views: has("view") ? await this.schemaContentsViews(attachOpaqueData, path, t) : [],
|
|
4039
|
+
scalar_functions: has("scalar_function") ? await this.schemaContentsFunctions(attachOpaqueData, path, "SCALAR_FUNCTION", t) : [],
|
|
4040
|
+
aggregate_functions: has("aggregate_function") ? await this.schemaContentsFunctions(attachOpaqueData, path, "AGGREGATE_FUNCTION", t) : [],
|
|
4041
|
+
table_functions: has("table_function") ? await this.schemaContentsFunctions(attachOpaqueData, path, "TABLE_FUNCTION", t) : [],
|
|
4042
|
+
scalar_macros: has("macro") ? await this.schemaContentsMacros(attachOpaqueData, path, "SCALAR_MACRO", t) : [],
|
|
4043
|
+
table_macros: has("macro") ? await this.schemaContentsMacros(attachOpaqueData, path, "TABLE_MACRO", t) : [],
|
|
4044
|
+
indexes: has("index") ? await this.schemaContentsIndexes(attachOpaqueData, path, t) : []
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
4047
|
+
return {
|
|
4048
|
+
catalog_version: null,
|
|
4049
|
+
etag: null,
|
|
4050
|
+
source: "per_schema",
|
|
4051
|
+
not_modified: false,
|
|
4052
|
+
fallback_error: fallbackError,
|
|
4053
|
+
schemas: out
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
3976
4056
|
async catalogDetach(attachOpaqueData) {
|
|
3977
4057
|
await this.rpc.call("catalog_detach", { attach_opaque_data: attachOpaqueData });
|
|
3978
4058
|
}
|
|
@@ -4111,6 +4191,17 @@ class VgiClient {
|
|
|
4111
4191
|
const inner = unwrapResult(result);
|
|
4112
4192
|
return deserializeInfoList(inner.items, decodeFunctionInfo);
|
|
4113
4193
|
}
|
|
4194
|
+
async schemaContentsIndexes(attachOpaqueData, path, transactionOpaqueData) {
|
|
4195
|
+
const result = await this.rpc.call("catalog_schema_contents_indexes", {
|
|
4196
|
+
attach_opaque_data: attachOpaqueData,
|
|
4197
|
+
path: normalizeSchemaPath(path),
|
|
4198
|
+
transaction_opaque_data: transactionOpaqueData ?? null
|
|
4199
|
+
});
|
|
4200
|
+
if (!result)
|
|
4201
|
+
return [];
|
|
4202
|
+
const inner = unwrapResult(result);
|
|
4203
|
+
return deserializeInfoList(inner.items, decodeIndexInfo);
|
|
4204
|
+
}
|
|
4114
4205
|
async tableGet(attachOpaqueData, schemaPath, name, transactionOpaqueData) {
|
|
4115
4206
|
const result = await this.rpc.call("catalog_table_get", {
|
|
4116
4207
|
attach_opaque_data: attachOpaqueData,
|
|
@@ -4549,4 +4640,4 @@ export {
|
|
|
4549
4640
|
typeSignature
|
|
4550
4641
|
};
|
|
4551
4642
|
|
|
4552
|
-
//# debugId=
|
|
4643
|
+
//# debugId=A700197CD6EACFA764756E2164756E21
|