@query-farm/vgi 0.37.1 → 0.38.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 +19 -1
- package/dist/client-entry.js +141 -4
- package/dist/client-entry.js.map +9 -8
- package/dist/codec/asd.d.ts +8 -0
- 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 +5 -2
- package/dist/index.js +574 -36
- package/dist/index.js.map +20 -16
- 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 +53 -0
- package/src/codec/asd.ts +96 -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 +21 -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,6 +1,6 @@
|
|
|
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 AttachOpaqueData, type TransactionOpaqueData, type CatalogInfo, type ScanFunctionResult } from "../catalog/interface.js";
|
|
4
4
|
import { VgiClientError } from "./errors.js";
|
|
5
5
|
export { VgiClientError };
|
|
6
6
|
import type { VgiClientOptions, TableFunctionOptions, ScalarFunctionOptions, TableInOutFunctionOptions, OnCreateConflict, CatalogFunctionType, CatalogMacroType, CatalogAttachOptions, AttachOptionValue } from "./types.js";
|
|
@@ -92,6 +92,24 @@ 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
|
+
catalogContents(attachOpaqueData: AttachOpaqueData, ifNoneMatch?: string | null): Promise<{
|
|
108
|
+
catalog_version: number;
|
|
109
|
+
etag: string | null;
|
|
110
|
+
not_modified: boolean;
|
|
111
|
+
schemas: SchemaContentsInfo[];
|
|
112
|
+
}>;
|
|
95
113
|
/** Detach a previously-attached catalog. */
|
|
96
114
|
catalogDetach(attachOpaqueData: AttachOpaqueData): Promise<void>;
|
|
97
115
|
/**
|
package/dist/client-entry.js
CHANGED
|
@@ -2284,7 +2284,84 @@ function normalizeSchemaPath(path) {
|
|
|
2284
2284
|
|
|
2285
2285
|
// src/codec/asd.ts
|
|
2286
2286
|
function encodeASD(schema, obj) {
|
|
2287
|
-
return serializeBatch(batchFromRows([obj], schema));
|
|
2287
|
+
return serializeBatch(batchFromRows([sortMapsForSchema(schema, obj)], schema));
|
|
2288
|
+
}
|
|
2289
|
+
function compareCodePoints(a, b) {
|
|
2290
|
+
const n = Math.min(a.length, b.length);
|
|
2291
|
+
for (let i = 0;i < n; i++) {
|
|
2292
|
+
const x = a.charCodeAt(i);
|
|
2293
|
+
const y = b.charCodeAt(i);
|
|
2294
|
+
if (x === y)
|
|
2295
|
+
continue;
|
|
2296
|
+
const xs = x >= 55296 && x <= 57343;
|
|
2297
|
+
const ys = y >= 55296 && y <= 57343;
|
|
2298
|
+
if (xs !== ys)
|
|
2299
|
+
return xs ? 1 : -1;
|
|
2300
|
+
return x - y;
|
|
2301
|
+
}
|
|
2302
|
+
return a.length - b.length;
|
|
2303
|
+
}
|
|
2304
|
+
function mapPairs(raw) {
|
|
2305
|
+
if (Array.isArray(raw))
|
|
2306
|
+
return raw.map((e) => Array.isArray(e) ? [e[0], e[1]] : [e?.key, e?.value]);
|
|
2307
|
+
if (raw instanceof Map)
|
|
2308
|
+
return Array.from(raw.entries());
|
|
2309
|
+
if (typeof raw[Symbol.iterator] === "function")
|
|
2310
|
+
return Array.from(raw);
|
|
2311
|
+
return Object.entries(raw);
|
|
2312
|
+
}
|
|
2313
|
+
function compareKeys(a, b) {
|
|
2314
|
+
if (typeof a === "string" && typeof b === "string")
|
|
2315
|
+
return compareCodePoints(a, b);
|
|
2316
|
+
if ((typeof a === "number" || typeof a === "bigint") && (typeof b === "number" || typeof b === "bigint")) {
|
|
2317
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2318
|
+
}
|
|
2319
|
+
return compareCodePoints(String(a), String(b));
|
|
2320
|
+
}
|
|
2321
|
+
var HAS_MAP = new WeakMap;
|
|
2322
|
+
function typeHasMap(type) {
|
|
2323
|
+
const cached = HAS_MAP.get(type);
|
|
2324
|
+
if (cached !== undefined)
|
|
2325
|
+
return cached;
|
|
2326
|
+
let has = isMap(type);
|
|
2327
|
+
if (!has && (isList(type) || isStruct(type))) {
|
|
2328
|
+
has = type.children.some((c) => typeHasMap(c.type));
|
|
2329
|
+
}
|
|
2330
|
+
HAS_MAP.set(type, has);
|
|
2331
|
+
return has;
|
|
2332
|
+
}
|
|
2333
|
+
function sortMapsForType(type, value) {
|
|
2334
|
+
if (value == null || !typeHasMap(type))
|
|
2335
|
+
return value;
|
|
2336
|
+
if (isMap(type)) {
|
|
2337
|
+
const entries = type.children[0].type;
|
|
2338
|
+
const valueType = entries.children[1].type;
|
|
2339
|
+
return mapPairs(value).map(([k, v]) => [k, sortMapsForType(valueType, v)]).sort((x, y) => compareKeys(x[0], y[0]));
|
|
2340
|
+
}
|
|
2341
|
+
if (isList(type)) {
|
|
2342
|
+
const child = type.children[0].type;
|
|
2343
|
+
return Array.from(value, (v) => sortMapsForType(child, v));
|
|
2344
|
+
}
|
|
2345
|
+
if (isStruct(type)) {
|
|
2346
|
+
const out = { ...value };
|
|
2347
|
+
for (const cf of type.children)
|
|
2348
|
+
out[cf.name] = sortMapsForType(cf.type, value[cf.name]);
|
|
2349
|
+
return out;
|
|
2350
|
+
}
|
|
2351
|
+
return value;
|
|
2352
|
+
}
|
|
2353
|
+
function sortMapsForSchema(schema, obj) {
|
|
2354
|
+
let out = null;
|
|
2355
|
+
for (const f of schema.fields) {
|
|
2356
|
+
if (!typeHasMap(f.type))
|
|
2357
|
+
continue;
|
|
2358
|
+
const v = obj[f.name];
|
|
2359
|
+
if (v == null)
|
|
2360
|
+
continue;
|
|
2361
|
+
out ??= { ...obj };
|
|
2362
|
+
out[f.name] = sortMapsForType(f.type, v);
|
|
2363
|
+
}
|
|
2364
|
+
return out ?? obj;
|
|
2288
2365
|
}
|
|
2289
2366
|
function decodeASD(schema, bytes) {
|
|
2290
2367
|
const batch = deserializeBatch(bytes);
|
|
@@ -2540,6 +2617,18 @@ var ForeignKeyInfoSchema = schema2([
|
|
|
2540
2617
|
field2("referenced_table", utf82(), false),
|
|
2541
2618
|
field2("referenced_schema_path", list2(field2("item", utf82(), true)), false)
|
|
2542
2619
|
]);
|
|
2620
|
+
var SchemaContentsSchema = schema2([
|
|
2621
|
+
field2("path", list2(field2("item", utf82(), true)), false),
|
|
2622
|
+
field2("schema", binary2(), false),
|
|
2623
|
+
field2("tables", list2(field2("item", binary2(), true)), false),
|
|
2624
|
+
field2("views", list2(field2("item", binary2(), true)), false),
|
|
2625
|
+
field2("scalar_functions", list2(field2("item", binary2(), true)), false),
|
|
2626
|
+
field2("aggregate_functions", list2(field2("item", binary2(), true)), false),
|
|
2627
|
+
field2("table_functions", list2(field2("item", binary2(), true)), false),
|
|
2628
|
+
field2("scalar_macros", list2(field2("item", binary2(), true)), false),
|
|
2629
|
+
field2("table_macros", list2(field2("item", binary2(), true)), false),
|
|
2630
|
+
field2("indexes", list2(field2("item", binary2(), true)), false)
|
|
2631
|
+
]);
|
|
2543
2632
|
var AggregateBindRequestSchema = schema2([
|
|
2544
2633
|
field2("function_name", utf82(), false),
|
|
2545
2634
|
field2("arguments", binary2(), false),
|
|
@@ -2862,11 +2951,18 @@ var CatalogAttachResultSchema = schema2([
|
|
|
2862
2951
|
field2("global_functions", list2(field2("item", binary2(), true)), false),
|
|
2863
2952
|
field2("global_function_prefix", utf82(), false),
|
|
2864
2953
|
field2("resolved_data_version", utf82(), true),
|
|
2865
|
-
field2("resolved_implementation_version", utf82(), true)
|
|
2954
|
+
field2("resolved_implementation_version", utf82(), true),
|
|
2955
|
+
field2("supports_catalog_contents", bool2(), false)
|
|
2866
2956
|
]);
|
|
2867
2957
|
var CatalogCatalogsResultSchema = schema2([
|
|
2868
2958
|
field2("items", list2(field2("item", binary2(), true)), false)
|
|
2869
2959
|
]);
|
|
2960
|
+
var CatalogContentsResultSchema = schema2([
|
|
2961
|
+
field2("catalog_version", int642(), false),
|
|
2962
|
+
field2("etag", utf82(), true),
|
|
2963
|
+
field2("not_modified", bool2(), false),
|
|
2964
|
+
field2("schemas", list2(field2("item", struct2([field2("path", list2(field2("item", utf82(), true)), false), field2("schema", binary2(), false), field2("tables", list2(field2("item", binary2(), true)), false), field2("views", list2(field2("item", binary2(), true)), false), field2("scalar_functions", list2(field2("item", binary2(), true)), false), field2("aggregate_functions", list2(field2("item", binary2(), true)), false), field2("table_functions", list2(field2("item", binary2(), true)), false), field2("scalar_macros", list2(field2("item", binary2(), true)), false), field2("table_macros", list2(field2("item", binary2(), true)), false), field2("indexes", list2(field2("item", binary2(), true)), false)]), true)), false)
|
|
2965
|
+
]);
|
|
2870
2966
|
var CatalogCopyFromFormatsResultSchema = schema2([
|
|
2871
2967
|
field2("items", list2(field2("item", binary2(), true)), false)
|
|
2872
2968
|
]);
|
|
@@ -2985,6 +3081,10 @@ var CatalogAttachParamsSchema = schema2([
|
|
|
2985
3081
|
field2("request", binary2(), false)
|
|
2986
3082
|
]);
|
|
2987
3083
|
var CatalogCatalogsParamsSchema = schema2([]);
|
|
3084
|
+
var CatalogContentsParamsSchema = schema2([
|
|
3085
|
+
field2("attach_opaque_data", binary2(), false),
|
|
3086
|
+
field2("if_none_match", utf82(), true)
|
|
3087
|
+
]);
|
|
2988
3088
|
var CatalogCopyFromFormatsParamsSchema = schema2([
|
|
2989
3089
|
field2("attach_opaque_data", binary2(), false),
|
|
2990
3090
|
field2("transaction_opaque_data", binary2(), true)
|
|
@@ -3315,10 +3415,14 @@ var TableFunctionStatisticsParamsSchema = schema2([
|
|
|
3315
3415
|
field2("request", binary2(), false)
|
|
3316
3416
|
]);
|
|
3317
3417
|
|
|
3418
|
+
// src/generated/vgi-protocol-types.ts
|
|
3419
|
+
var decodeCatalogContentsResponse = (b) => decodeASD(CatalogContentsResultSchema, b);
|
|
3420
|
+
|
|
3318
3421
|
// src/generated/vgi-client.ts
|
|
3319
3422
|
var decodeCatalogInfo = (b) => decodeASD(CatalogInfoSchema, b);
|
|
3320
3423
|
var encodeFunctionInfo = (v) => encodeASD(FunctionInfoSchema, v);
|
|
3321
3424
|
var decodeFunctionInfo = (b) => decodeASD(FunctionInfoSchema, b);
|
|
3425
|
+
var decodeIndexInfo = (b) => decodeASD(IndexInfoSchema, b);
|
|
3322
3426
|
var encodeMacroInfo = (v) => encodeASD(MacroInfoSchema, v);
|
|
3323
3427
|
var decodeMacroInfo = (b) => decodeASD(MacroInfoSchema, b);
|
|
3324
3428
|
var encodeSchemaInfo = (v) => encodeASD(SchemaInfoSchema, v);
|
|
@@ -3833,7 +3937,40 @@ class VgiClient {
|
|
|
3833
3937
|
global_functions: inner.global_functions ? (Array.isArray(inner.global_functions) ? inner.global_functions : [...inner.global_functions]).map(toUint8Array) : [],
|
|
3834
3938
|
global_function_prefix: inner.global_function_prefix ?? "",
|
|
3835
3939
|
resolved_data_version: inner.resolved_data_version ?? null,
|
|
3836
|
-
resolved_implementation_version: inner.resolved_implementation_version ?? null
|
|
3940
|
+
resolved_implementation_version: inner.resolved_implementation_version ?? null,
|
|
3941
|
+
supports_catalog_contents: inner.supports_catalog_contents ?? false
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
async catalogContents(attachOpaqueData, ifNoneMatch = null) {
|
|
3945
|
+
const result = await this.rpc.call("catalog_contents", {
|
|
3946
|
+
attach_opaque_data: attachOpaqueData,
|
|
3947
|
+
if_none_match: ifNoneMatch
|
|
3948
|
+
});
|
|
3949
|
+
if (!result)
|
|
3950
|
+
throw new VgiClientError("catalog_contents returned null");
|
|
3951
|
+
const response = decodeCatalogContentsResponse(toUint8Array(result.result));
|
|
3952
|
+
const schemas = response.schemas.map((c) => {
|
|
3953
|
+
const schema = decodeSchemaInfo(c.schema);
|
|
3954
|
+
if (JSON.stringify(c.path) !== JSON.stringify(schema.path)) {
|
|
3955
|
+
throw new VgiClientError(`catalog_contents: SchemaContents.path ${JSON.stringify(c.path)} != SchemaInfo.path ${JSON.stringify(schema.path)}`);
|
|
3956
|
+
}
|
|
3957
|
+
return {
|
|
3958
|
+
schema,
|
|
3959
|
+
tables: c.tables.map(decodeTableInfo),
|
|
3960
|
+
views: c.views.map(decodeViewInfo),
|
|
3961
|
+
scalar_functions: c.scalar_functions.map(decodeFunctionInfo),
|
|
3962
|
+
aggregate_functions: c.aggregate_functions.map(decodeFunctionInfo),
|
|
3963
|
+
table_functions: c.table_functions.map(decodeFunctionInfo),
|
|
3964
|
+
scalar_macros: c.scalar_macros.map(decodeMacroInfo),
|
|
3965
|
+
table_macros: c.table_macros.map(decodeMacroInfo),
|
|
3966
|
+
indexes: c.indexes.map(decodeIndexInfo)
|
|
3967
|
+
};
|
|
3968
|
+
});
|
|
3969
|
+
return {
|
|
3970
|
+
catalog_version: Number(response.catalog_version ?? 0),
|
|
3971
|
+
etag: response.etag ?? null,
|
|
3972
|
+
not_modified: Boolean(response.not_modified),
|
|
3973
|
+
schemas
|
|
3837
3974
|
};
|
|
3838
3975
|
}
|
|
3839
3976
|
async catalogDetach(attachOpaqueData) {
|
|
@@ -4412,4 +4549,4 @@ export {
|
|
|
4412
4549
|
typeSignature
|
|
4413
4550
|
};
|
|
4414
4551
|
|
|
4415
|
-
//# debugId=
|
|
4552
|
+
//# debugId=AD4326E5920F408164756E2164756E21
|