@query-farm/vgi 0.37.1 → 0.39.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 +74 -0
- package/dist/catalog/composite.d.ts +23 -1
- package/dist/catalog/contents-digest.d.ts +25 -0
- package/dist/catalog/interface.d.ts +107 -2
- package/dist/catalog/read-only.d.ts +45 -1
- package/dist/client/client.d.ts +49 -2
- package/dist/client/types.d.ts +56 -1
- package/dist/client-entry.js +239 -11
- package/dist/client-entry.js.map +9 -8
- package/dist/codec/asd.d.ts +8 -0
- package/dist/functions/copy-to.d.ts +1 -1
- package/dist/generated/vgi-client.d.ts +19 -0
- package/dist/generated/vgi-protocol-schemas.d.ts +3 -0
- package/dist/generated/vgi-protocol-types.d.ts +26 -0
- package/dist/http/fetch.d.ts +2 -1
- package/dist/index.core.d.ts +6 -3
- package/dist/index.js +672 -43
- package/dist/index.js.map +21 -17
- package/dist/protocol/handlers/catalog/contents.d.ts +27 -0
- package/dist/rpc-server.d.ts +97 -0
- package/dist/serve-entry.d.ts +2 -1
- package/dist/serve-entry.js +405 -25
- package/dist/serve-entry.js.map +18 -14
- package/dist/worker-cf-entry.d.ts +3 -0
- package/dist/worker-cf-entry.js +528 -31
- package/dist/worker-cf-entry.js.map +20 -16
- package/dist/worker.d.ts +9 -1
- package/package.json +3 -3
- package/src/catalog/composite.ts +81 -0
- package/src/catalog/contents-digest.ts +78 -0
- package/src/catalog/interface.ts +167 -5
- package/src/catalog/read-only.ts +68 -6
- package/src/client/client.ts +201 -0
- package/src/client/types.ts +58 -1
- package/src/codec/asd.ts +108 -9
- package/src/functions/copy-to.ts +1 -1
- package/src/generated/vgi-client.ts +22 -1
- package/src/generated/vgi-protocol-schemas.ts +30 -1
- package/src/generated/vgi-protocol-types.ts +126 -0
- package/src/http/fetch.ts +10 -2
- package/src/index.core.ts +23 -2
- package/src/protocol/dispatch.ts +1 -1
- package/src/protocol/handlers/catalog/admin.ts +35 -26
- package/src/protocol/handlers/catalog/contents.ts +162 -0
- package/src/protocol/handlers/catalog/macro.ts +2 -1
- package/src/protocol/handlers/catalog/shared.ts +12 -1
- package/src/rpc-server.ts +273 -0
- package/src/serve-entry.ts +7 -1
- package/src/worker-cf-entry.ts +8 -0
- package/src/worker.ts +24 -6
package/README.md
CHANGED
|
@@ -479,6 +479,7 @@ A worker serves the same functions over any of:
|
|
|
479
479
|
- **AF_UNIX** — a long-lived warm worker (`--unix <path>`), reused across calls.
|
|
480
480
|
- **HTTP** — stateless; all state round-trips in a self-contained token, so requests
|
|
481
481
|
can be load-balanced across hosts.
|
|
482
|
+
- **TCP** (`--tcp [HOST:]PORT`) and caller-provided byte streams (`worker.serveStream`).
|
|
482
483
|
|
|
483
484
|
Transport handling lives in [`@query-farm/vgi-rpc`](https://www.npmjs.com/package/@query-farm/vgi-rpc).
|
|
484
485
|
|
|
@@ -541,6 +542,79 @@ its own required `name`/`doc`/`version`, so only the Cloudflare entry passes it
|
|
|
541
542
|
explicitly. Without it there is no landing page and no `/vgi-client.js`; see
|
|
542
543
|
[MIGRATION.md](MIGRATION.md).
|
|
543
544
|
|
|
545
|
+
### Hosting additional protocols
|
|
546
|
+
|
|
547
|
+
A worker can serve other [vgi-rpc](https://www.npmjs.com/package/@query-farm/vgi-rpc)
|
|
548
|
+
protocols beside `vgi.v2`, on the same listener, through the `hostedProtocols` hook:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
import { Protocol, str } from "@query-farm/vgi-rpc";
|
|
552
|
+
|
|
553
|
+
const reports = new Protocol("acme.Reports.v1").unary("render", {
|
|
554
|
+
params: { id: str },
|
|
555
|
+
result: { result: str },
|
|
556
|
+
handler: ({ id }) => ({ result: `report ${id}` }),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
new Worker({ functions, hostedProtocols: () => [reports] }).run();
|
|
560
|
+
// …or serveVgiWorker({ …, hostedProtocols: () => [reports] }),
|
|
561
|
+
// …or createVgiFetch({ …, hostedProtocols: () => [reports] }) on Cloudflare Workers.
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
- The hook is called **once**, when the server is built; it may read configuration,
|
|
565
|
+
but its answer is fixed for the life of the process.
|
|
566
|
+
- Its protocols are hosted on **every** transport (stdio, AF_UNIX, TCP, the Iroh raw
|
|
567
|
+
upstream, `serveStream`, HTTP), after `vgi.v2`, in the order returned. Every
|
|
568
|
+
transport builds its server through one function, `buildRpcServer`.
|
|
569
|
+
- Each needs a distinct name, which may not be `vgi.v2` or start with the reserved
|
|
570
|
+
`vgi_rpc.`. Violations are a startup error naming `hostedProtocols()`.
|
|
571
|
+
- Requests route on their `vgi_rpc.protocol` key, so hosting more protocols never
|
|
572
|
+
changes how DuckDB's `vgi.v2` calls are dispatched.
|
|
573
|
+
- The protocol is the unit of optionality: an optional capability is its own protocol.
|
|
574
|
+
|
|
575
|
+
### Token introspection and grants (`vgi_rpc.Identity.v1`)
|
|
576
|
+
|
|
577
|
+
A worker opts into `vgi_rpc.Identity.v1` by supplying `resolveToken` and/or
|
|
578
|
+
`mintGrant` (accepted by `Worker`, `serveVgiWorker`, `createVgiWorkerFetch` and
|
|
579
|
+
`createVgiFetch`). It is hosted on **HTTP only** — the transport that authenticates
|
|
580
|
+
callers — and only the methods whose hooks exist are hosted; with neither, the
|
|
581
|
+
protocol is absent.
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
import { AuthUnavailableError } from "@query-farm/vgi";
|
|
585
|
+
|
|
586
|
+
serveVgiWorker({
|
|
587
|
+
// …
|
|
588
|
+
resolveToken: async (token) => {
|
|
589
|
+
let row;
|
|
590
|
+
try {
|
|
591
|
+
row = await apiKeys.lookup(token);
|
|
592
|
+
} catch (err) {
|
|
593
|
+
// "I could not find out" -- an outage, not a refusal.
|
|
594
|
+
throw new AuthUnavailableError("key store unreachable", 5);
|
|
595
|
+
}
|
|
596
|
+
return row ? { principal: row.principal, tokenName: row.label } : null; // null = unknown
|
|
597
|
+
},
|
|
598
|
+
introspectPrincipals: ["edge-proxy"], // or VGI_INTROSPECT_PRINCIPALS=edge-proxy
|
|
599
|
+
});
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Enabling introspection without an allowlist refuses to start.** Authenticating
|
|
603
|
+
and introspecting are different capabilities; "any authenticated caller" would let
|
|
604
|
+
any user resolve any other user's credential to its owner. Set
|
|
605
|
+
`introspectPrincipals` or `VGI_INTROSPECT_PRINCIPALS` (comma-separated). `mintGrant`
|
|
606
|
+
alone needs no allowlist.
|
|
607
|
+
|
|
608
|
+
**Transient failures:** throw `AuthUnavailableError` (re-exported here from
|
|
609
|
+
`@query-farm/vgi-rpc`) from `resolveToken` or `mintGrant` when the answer is not
|
|
610
|
+
knowable — a store or sidecar outage, a timeout, a 5xx. The framework translates it
|
|
611
|
+
to `identity_unavailable` (code `UNAVAILABLE`) carrying that error's retry hint as
|
|
612
|
+
`vgi_rpc.RetryInfo`, so a caller retries instead of caching a refusal.
|
|
613
|
+
`IdentityUnavailableError` works too. Never throw a plain `Error` for an outage, and
|
|
614
|
+
never return `null` for one: both read as a definitive answer. Return `null` only
|
|
615
|
+
when the store answered and the credential is unknown; throw `GrantRefusedError` to
|
|
616
|
+
decline a grant.
|
|
617
|
+
|
|
544
618
|
## Runtimes & entry points
|
|
545
619
|
|
|
546
620
|
The package ships a backend-agnostic Arrow facade and selects an implementation at
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AttachOpaqueData, CatalogAttachResult, CatalogInfo, CopyFromFormatInfo, FunctionInfo, IndexInfo, MacroInfo, SchemaInfo, TableInfo, TransactionOpaqueData, ViewInfo } from "./interface.js";
|
|
1
|
+
import type { AttachOpaqueData, CatalogAttachResult, CatalogInfo, CopyFromFormatInfo, FunctionInfo, IndexInfo, MacroInfo, CatalogContentsResult, SchemaInfo, TableInfo, TransactionOpaqueData, ViewInfo } from "./interface.js";
|
|
2
2
|
import { CatalogInterface } from "./interface.js";
|
|
3
3
|
export declare class CompositeCatalogInterface extends CatalogInterface {
|
|
4
4
|
private readonly _backends;
|
|
@@ -28,6 +28,28 @@ export declare class CompositeCatalogInterface extends CatalogInterface {
|
|
|
28
28
|
} | null>;
|
|
29
29
|
viewGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo | null>;
|
|
30
30
|
macroGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<MacroInfo | null>;
|
|
31
|
+
schemaCreate(...args: Parameters<CatalogInterface["schemaCreate"]>): Promise<void>;
|
|
32
|
+
schemaDrop(...args: Parameters<CatalogInterface["schemaDrop"]>): Promise<void>;
|
|
33
|
+
tableCreate(...args: Parameters<CatalogInterface["tableCreate"]>): Promise<void>;
|
|
34
|
+
tableDrop(...args: Parameters<CatalogInterface["tableDrop"]>): Promise<void>;
|
|
35
|
+
tableCommentSet(...args: Parameters<CatalogInterface["tableCommentSet"]>): Promise<void>;
|
|
36
|
+
tableRename(...args: Parameters<CatalogInterface["tableRename"]>): Promise<void>;
|
|
37
|
+
tableColumnAdd(...args: Parameters<CatalogInterface["tableColumnAdd"]>): Promise<void>;
|
|
38
|
+
tableColumnDrop(...args: Parameters<CatalogInterface["tableColumnDrop"]>): Promise<void>;
|
|
39
|
+
tableColumnRename(...args: Parameters<CatalogInterface["tableColumnRename"]>): Promise<void>;
|
|
40
|
+
tableColumnDefaultSet(...args: Parameters<CatalogInterface["tableColumnDefaultSet"]>): Promise<void>;
|
|
41
|
+
tableColumnDefaultDrop(...args: Parameters<CatalogInterface["tableColumnDefaultDrop"]>): Promise<void>;
|
|
42
|
+
tableColumnTypeChange(...args: Parameters<CatalogInterface["tableColumnTypeChange"]>): Promise<void>;
|
|
43
|
+
tableNotNullSet(...args: Parameters<CatalogInterface["tableNotNullSet"]>): Promise<void>;
|
|
44
|
+
tableNotNullDrop(...args: Parameters<CatalogInterface["tableNotNullDrop"]>): Promise<void>;
|
|
45
|
+
viewCreate(...args: Parameters<CatalogInterface["viewCreate"]>): Promise<void>;
|
|
46
|
+
viewDrop(...args: Parameters<CatalogInterface["viewDrop"]>): Promise<void>;
|
|
47
|
+
viewRename(...args: Parameters<CatalogInterface["viewRename"]>): Promise<void>;
|
|
48
|
+
viewCommentSet(...args: Parameters<CatalogInterface["viewCommentSet"]>): Promise<void>;
|
|
49
|
+
macroCreate(...args: Parameters<CatalogInterface["macroCreate"]>): Promise<void>;
|
|
50
|
+
macroDrop(...args: Parameters<CatalogInterface["macroDrop"]>): Promise<void>;
|
|
51
|
+
catalogContents(attachOpaqueData: AttachOpaqueData, ifNoneMatch?: string | null): Promise<CatalogContentsResult>;
|
|
52
|
+
catalogContentsOwner(attachOpaqueData: AttachOpaqueData): CatalogInterface;
|
|
31
53
|
copyFromFormats(attachOpaqueData: AttachOpaqueData, transactionOpaqueData?: TransactionOpaqueData): Promise<CopyFromFormatInfo[]>;
|
|
32
54
|
transactionBegin(attachOpaqueData: AttachOpaqueData): Promise<Uint8Array | null>;
|
|
33
55
|
transactionCommit(attachOpaqueData: AttachOpaqueData, transactionOpaqueData: TransactionOpaqueData): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `content-hash` etag for `catalog_contents`.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors vgi-python's `catalog_contents_digest()` byte for byte, so the same
|
|
5
|
+
* snapshot (the same item bytes) gives the same etag in every SDK.
|
|
6
|
+
*/
|
|
7
|
+
import type { SchemaContents } from "../generated/vgi-protocol-types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Hex SHA-256 over a `catalog_contents` snapshot: the `content-hash` etag.
|
|
10
|
+
*
|
|
11
|
+
* Covers every schema's path and the exact item bytes of every kind, in
|
|
12
|
+
* order, each length-prefixed (8-byte little-endian) so no two different
|
|
13
|
+
* snapshots share an input:
|
|
14
|
+
*
|
|
15
|
+
* u64(#schemas) { u64(#path) {u64(len) utf8(part)}* u64(len) schema
|
|
16
|
+
* ( u64(#items) {u64(len) item}* ) x 8 kinds }*
|
|
17
|
+
*
|
|
18
|
+
* Deterministic because the items' encoding is (map columns are written in
|
|
19
|
+
* sorted key order, see `encodeASD`), so two builds of the same catalog hash
|
|
20
|
+
* alike.
|
|
21
|
+
*
|
|
22
|
+
* @param schemas The snapshot, in wire order.
|
|
23
|
+
* @returns The lowercase hex digest.
|
|
24
|
+
*/
|
|
25
|
+
export declare function catalogContentsDigest(schemas: readonly SchemaContents[]): Promise<string>;
|
|
@@ -2,8 +2,9 @@ export type AttachOpaqueData = Uint8Array;
|
|
|
2
2
|
export type TransactionOpaqueData = Uint8Array;
|
|
3
3
|
import type { MacroType } from "../generated/vgi-client.js";
|
|
4
4
|
export type { MacroType };
|
|
5
|
-
import type { CatalogAttachResult } from "../generated/vgi-
|
|
6
|
-
export type { CatalogAttachResult };
|
|
5
|
+
import type { CatalogAttachResult, CatalogAttachResultInit } from "../generated/vgi-protocol-types.js";
|
|
6
|
+
export type { CatalogAttachResult, CatalogAttachResultInit };
|
|
7
|
+
export { buildCatalogAttachResult } from "../generated/vgi-protocol-types.js";
|
|
7
8
|
import type { SchemaInfo } from "../generated/vgi-client.js";
|
|
8
9
|
export type { SchemaInfo };
|
|
9
10
|
export { encodeSchemaInfo, decodeSchemaInfo } from "../generated/vgi-client.js";
|
|
@@ -243,6 +244,45 @@ export declare function buildScanBranchesResult(branches: ScanBranchInput[], req
|
|
|
243
244
|
branches: Uint8Array[];
|
|
244
245
|
required_extensions: string[];
|
|
245
246
|
};
|
|
247
|
+
/**
|
|
248
|
+
* One schema and everything in it, as returned by
|
|
249
|
+
* `CatalogInterface.catalogContents` (vgi-python's `SchemaContentsInfo`).
|
|
250
|
+
*
|
|
251
|
+
* Each kind is complete: an empty list means the schema has none of that kind.
|
|
252
|
+
* The `catalog_contents` handler encodes every item with the same encoder the
|
|
253
|
+
* matching per-schema RPC uses, so the items are byte-for-byte what
|
|
254
|
+
* `catalog_schemas` / `catalog_schema_contents_*` would return.
|
|
255
|
+
*/
|
|
256
|
+
export interface SchemaContentsInfo {
|
|
257
|
+
schema: SchemaInfo;
|
|
258
|
+
tables: TableInfo[];
|
|
259
|
+
views: ViewInfo[];
|
|
260
|
+
scalar_functions: FunctionInfo[];
|
|
261
|
+
aggregate_functions: FunctionInfo[];
|
|
262
|
+
table_functions: FunctionInfo[];
|
|
263
|
+
scalar_macros: MacroInfo[];
|
|
264
|
+
table_macros: MacroInfo[];
|
|
265
|
+
indexes: IndexInfo[];
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* What `CatalogInterface.catalogContents` returns: a snapshot, or "not
|
|
269
|
+
* modified" (vgi-python's `CatalogContentsResult`).
|
|
270
|
+
*
|
|
271
|
+
* - `schemas`: one `SchemaContentsInfo` per schema. Must be empty (or absent)
|
|
272
|
+
* when `not_modified` is set.
|
|
273
|
+
* - `etag`: opaque validator for this snapshot (a generation counter, schema
|
|
274
|
+
* version, git sha, ...). The client sends it back as `if_none_match`.
|
|
275
|
+
* Null/absent means the catalog does not revalidate -- unless it opts in to
|
|
276
|
+
* the framework's content hash with `catalogContentsEtag = "content-hash"`.
|
|
277
|
+
* - `not_modified`: true when the request's `if_none_match` equals the
|
|
278
|
+
* current etag, so the catalog skipped building the snapshot. Requires
|
|
279
|
+
* `etag` (the matching validator).
|
|
280
|
+
*/
|
|
281
|
+
export interface CatalogContentsResult {
|
|
282
|
+
schemas?: SchemaContentsInfo[];
|
|
283
|
+
etag?: string | null;
|
|
284
|
+
not_modified?: boolean;
|
|
285
|
+
}
|
|
246
286
|
/**
|
|
247
287
|
* Catalog method return type — sync `T` or `Promise<T>`. The framework's
|
|
248
288
|
* dispatch handlers `await` every catalog call, so async overrides work
|
|
@@ -250,6 +290,36 @@ export declare function buildScanBranchesResult(branches: ScanBranchInput[], req
|
|
|
250
290
|
*/
|
|
251
291
|
type Awaitable<T> = T | Promise<T>;
|
|
252
292
|
export declare abstract class CatalogInterface {
|
|
293
|
+
/**
|
|
294
|
+
* Opt-in framework etag for `catalog_contents`. `"content-hash"`: when
|
|
295
|
+
* `catalogContents` returns no etag of its own, the worker uses the hex
|
|
296
|
+
* SHA-256 of the serialized snapshot (`catalogContentsDigest`) as the etag
|
|
297
|
+
* and turns a matching `if_none_match` into `not_modified` (it still builds
|
|
298
|
+
* the snapshot, but saves the transfer and the client's decode). `null`
|
|
299
|
+
* (default): no etag unless the catalog returns one. Off by default because
|
|
300
|
+
* for a catalog whose version is not frozen the client would revalidate with
|
|
301
|
+
* a full build on every transaction instead of a cheap `catalog_version`
|
|
302
|
+
* poll. Mirrors vgi-python's `catalog_contents_etag`.
|
|
303
|
+
*/
|
|
304
|
+
catalogContentsEtag: "content-hash" | null;
|
|
305
|
+
/**
|
|
306
|
+
* Whether `catalogContents` depends only on `version()`: not on the attach
|
|
307
|
+
* (`attach_opaque_data`, attach options), the caller's identity, or anything
|
|
308
|
+
* else. Together with `catalogVersionFrozen`, it lets the worker build the
|
|
309
|
+
* `catalog_contents` response once per (catalog, version) and serve the same
|
|
310
|
+
* serialized bytes to every caller. Default false.
|
|
311
|
+
*
|
|
312
|
+
* `ReadOnlyCatalogInterface` sets it (its items carry a fixed attach id,
|
|
313
|
+
* like vgi-python's), unless its `perAttachItemIds` opt-in is on. A
|
|
314
|
+
* subclass whose contents vary per caller or attach option sets it false.
|
|
315
|
+
*/
|
|
316
|
+
catalogContentsAttachIndependent: boolean;
|
|
317
|
+
/**
|
|
318
|
+
* Declares that `version()` never changes for this catalog instance (the
|
|
319
|
+
* attach result's `catalog_version_frozen`). Only the `catalog_contents`
|
|
320
|
+
* worker cache reads it. `ReadOnlyCatalogInterface` sets it.
|
|
321
|
+
*/
|
|
322
|
+
catalogVersionFrozen: boolean;
|
|
253
323
|
/**
|
|
254
324
|
* Which catalog an `attach_opaque_data` belongs to, or null if unknown.
|
|
255
325
|
*
|
|
@@ -348,6 +418,41 @@ export declare abstract class CatalogInterface {
|
|
|
348
418
|
* vgi-python's `CatalogInterface.copy_from_formats`.
|
|
349
419
|
*/
|
|
350
420
|
copyFromFormats(attachOpaqueData: AttachOpaqueData, transactionOpaqueData?: TransactionOpaqueData): Awaitable<CopyFromFormatInfo[]>;
|
|
421
|
+
/**
|
|
422
|
+
* Every schema and all of its contents, for the `catalog_contents` RPC.
|
|
423
|
+
*
|
|
424
|
+
* Served only to a client whose attach result set
|
|
425
|
+
* `supports_catalog_contents` (ReadOnlyCatalogInterface does). The default
|
|
426
|
+
* composes `schemas()` with the per-kind `schemaContents*()` calls, skipping
|
|
427
|
+
* a kind whose `estimated_object_count` is exactly 0 (a hard guarantee), and
|
|
428
|
+
* returns no etag -- so any catalog can serve it without new code. It runs
|
|
429
|
+
* with no transaction: the client caches the answer for the whole attach.
|
|
430
|
+
*
|
|
431
|
+
* Override it to build the snapshot more cheaply, or to revalidate: return
|
|
432
|
+
* an `etag` with the contents, and when `ifNoneMatch` equals the current
|
|
433
|
+
* etag return `{ etag, not_modified: true }` *before* building anything.
|
|
434
|
+
* Mirrors vgi-python's `CatalogInterface.catalog_contents`.
|
|
435
|
+
*
|
|
436
|
+
* @param ifNoneMatch The etag of the snapshot the client already holds, or
|
|
437
|
+
* null.
|
|
438
|
+
* @returns One `SchemaContentsInfo` per schema, in `schemas()` order (the
|
|
439
|
+
* handler sorts parents before children), plus its etag -- or
|
|
440
|
+
* `not_modified`.
|
|
441
|
+
*/
|
|
442
|
+
catalogContents(attachOpaqueData: AttachOpaqueData, _ifNoneMatch?: string | null): Promise<CatalogContentsResult>;
|
|
443
|
+
/**
|
|
444
|
+
* Whether the worker may build `catalog_contents` once per (catalog
|
|
445
|
+
* instance, version) and reuse the serialized response for every caller:
|
|
446
|
+
* `catalogContentsAttachIndependent && catalogVersionFrozen` by default.
|
|
447
|
+
*/
|
|
448
|
+
catalogContentsCacheable(): boolean;
|
|
449
|
+
/**
|
|
450
|
+
* The catalog that answers `catalog_contents` for this attach: whose
|
|
451
|
+
* `catalogContentsEtag` / `catalogContentsAttachIndependent` /
|
|
452
|
+
* `catalogVersionFrozen` the handler honours and whose instance keys the
|
|
453
|
+
* worker cache. `this`, except for a composite, which routes to its backend.
|
|
454
|
+
*/
|
|
455
|
+
catalogContentsOwner(_attachOpaqueData: AttachOpaqueData): CatalogInterface;
|
|
351
456
|
transactionBegin(attachOpaqueData: AttachOpaqueData): Awaitable<Uint8Array | null>;
|
|
352
457
|
transactionCommit(attachOpaqueData: AttachOpaqueData, transactionOpaqueData: TransactionOpaqueData): Awaitable<void>;
|
|
353
458
|
transactionRollback(attachOpaqueData: AttachOpaqueData, transactionOpaqueData: TransactionOpaqueData): Awaitable<void>;
|
|
@@ -2,7 +2,43 @@ import { CatalogInterface, type AttachOpaqueData, type TransactionOpaqueData, ty
|
|
|
2
2
|
import { type CatalogDescriptor } from "./descriptors.js";
|
|
3
3
|
import type { FunctionRegistry } from "../functions/registry.js";
|
|
4
4
|
import { type AttachOptionSpec } from "./attach-option.js";
|
|
5
|
+
/**
|
|
6
|
+
* The fixed attach id ReadOnlyCatalogInterface writes into its items (same
|
|
7
|
+
* bytes as vgi-python's `ReadOnlyCatalogInterface._FIXED_ATTACH_ID`). Shared:
|
|
8
|
+
* treat it as read-only (a typed array cannot be frozen).
|
|
9
|
+
*/
|
|
10
|
+
export declare const READ_ONLY_ITEM_ATTACH_ID: AttachOpaqueData;
|
|
5
11
|
export declare class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
12
|
+
/**
|
|
13
|
+
* Advertise `supports_catalog_contents` on attach, letting the client load
|
|
14
|
+
* the whole catalog with one `catalog_contents` call instead of
|
|
15
|
+
* `catalog_schemas` plus a `catalog_schema_contents_*` call per schema and
|
|
16
|
+
* kind. True by default — a descriptor catalog is static. Set it to false
|
|
17
|
+
* (before attach) to keep the client on the per-schema RPCs.
|
|
18
|
+
*/
|
|
19
|
+
supportsCatalogContents: boolean;
|
|
20
|
+
/** A descriptor catalog's version never changes (`catalog_version_frozen`). */
|
|
21
|
+
catalogVersionFrozen: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The catalog items (`SchemaInfo.attach_opaque_data`, and the attach value
|
|
24
|
+
* handed to a table function's bind when resolving a table's columns) carry
|
|
25
|
+
* one fixed id, `READ_ONLY_ITEM_ATTACH_ID`, not the per-ATTACH id -- as
|
|
26
|
+
* vgi-python's ReadOnlyCatalogInterface does. So the items are the same for
|
|
27
|
+
* every attach and caller, and the worker caches the `catalog_contents`
|
|
28
|
+
* response (see `catalogContentsCacheable`).
|
|
29
|
+
*
|
|
30
|
+
* `attach()` still mints a random per-ATTACH `attach_opaque_data` (sealed
|
|
31
|
+
* into the per-attach envelope the client holds), and function calls still
|
|
32
|
+
* receive it, so per-attach scoping in functions (the accumulate example) is
|
|
33
|
+
* unchanged.
|
|
34
|
+
*
|
|
35
|
+
* Set true (or pass `{ perAttachItemIds: true }` to the constructor) for a
|
|
36
|
+
* catalog whose items must echo the per-attach id. Its contents then depend
|
|
37
|
+
* on the attach, so the catalog_contents cache is off for it.
|
|
38
|
+
*/
|
|
39
|
+
perAttachItemIds: boolean;
|
|
40
|
+
/** Static contents: attach-independent unless `perAttachItemIds` is set. */
|
|
41
|
+
catalogContentsAttachIndependent: boolean;
|
|
6
42
|
private _descriptor;
|
|
7
43
|
private _registry;
|
|
8
44
|
private _attachments;
|
|
@@ -18,7 +54,9 @@ export declare class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
18
54
|
private _functionListings;
|
|
19
55
|
/** One frozen FunctionInfo per (declaring schema, function), shared by listings and `global_functions`. */
|
|
20
56
|
private _functionInfos;
|
|
21
|
-
constructor(descriptor: CatalogDescriptor, registry: FunctionRegistry
|
|
57
|
+
constructor(descriptor: CatalogDescriptor, registry: FunctionRegistry, options?: {
|
|
58
|
+
perAttachItemIds?: boolean;
|
|
59
|
+
});
|
|
22
60
|
/**
|
|
23
61
|
* Which schema declares `fn`, by object identity, or null if no schema does.
|
|
24
62
|
*
|
|
@@ -51,6 +89,12 @@ export declare class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
51
89
|
attach(name: string, options?: Record<string, unknown>, dataVersionSpec?: string | null, implementationVersion?: string | null): CatalogAttachResult | Promise<CatalogAttachResult>;
|
|
52
90
|
detach(attachOpaqueData: AttachOpaqueData): void;
|
|
53
91
|
version(attachOpaqueData: AttachOpaqueData, transactionOpaqueData?: TransactionOpaqueData): number | Promise<number>;
|
|
92
|
+
/**
|
|
93
|
+
* The attach value written into this catalog's items: the fixed
|
|
94
|
+
* `READ_ONLY_ITEM_ATTACH_ID`, or the caller's attach with `perAttachItemIds`.
|
|
95
|
+
*/
|
|
96
|
+
protected itemAttachId(attachOpaqueData: AttachOpaqueData): AttachOpaqueData;
|
|
97
|
+
catalogContentsCacheable(): boolean;
|
|
54
98
|
schemas(attachOpaqueData: AttachOpaqueData, transactionOpaqueData?: TransactionOpaqueData): SchemaInfo[];
|
|
55
99
|
private _estimatedObjectCount;
|
|
56
100
|
schemaGet(attachOpaqueData: AttachOpaqueData, path: string[], transactionOpaqueData?: TransactionOpaqueData): SchemaInfo | null;
|
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 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
|
*
|
|
@@ -92,6 +92,51 @@ export declare class VgiClient {
|
|
|
92
92
|
* ```
|
|
93
93
|
*/
|
|
94
94
|
catalogAttach(name: string, opts?: CatalogAttachOptions): Promise<CatalogAttachResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Load the whole catalog in one `catalog_contents` call: every schema (parents
|
|
97
|
+
* before children) with all of its tables, views, functions, macros and
|
|
98
|
+
* indexes. Only call it when the attach result set
|
|
99
|
+
* `supports_catalog_contents`; it is the committed catalog at
|
|
100
|
+
* `catalog_version` (no transaction).
|
|
101
|
+
*
|
|
102
|
+
* Pass the `etag` of a snapshot you hold as `ifNoneMatch`: when it is still
|
|
103
|
+
* current the worker answers `not_modified` with no schemas. `etag` null
|
|
104
|
+
* means the worker does not revalidate. Each schema's wire `path` is checked
|
|
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.
|
|
109
|
+
*/
|
|
110
|
+
catalogContents(attachOpaqueData: AttachOpaqueData, ifNoneMatch?: string | null): Promise<{
|
|
111
|
+
catalog_version: number;
|
|
112
|
+
etag: string | null;
|
|
113
|
+
not_modified: boolean;
|
|
114
|
+
schemas: SchemaContentsInfo[];
|
|
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;
|
|
95
140
|
/** Detach a previously-attached catalog. */
|
|
96
141
|
catalogDetach(attachOpaqueData: AttachOpaqueData): Promise<void>;
|
|
97
142
|
/**
|
|
@@ -135,6 +180,8 @@ export declare class VgiClient {
|
|
|
135
180
|
schemaContentsViews(attachOpaqueData: AttachOpaqueData, path: string[] | string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo[]>;
|
|
136
181
|
/** List functions in a schema, filtered by type. */
|
|
137
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[]>;
|
|
138
185
|
/** Get a table by name, or null if not found. */
|
|
139
186
|
tableGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[] | string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo | null>;
|
|
140
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
|
+
}
|