@query-farm/vgi 0.14.0 → 0.17.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.
@@ -32,6 +32,18 @@ export interface TableProcessParams<TArgs = Record<string, any>> {
32
32
  */
33
33
  atUnit?: string;
34
34
  atValue?: string;
35
+ /**
36
+ * Conditional-revalidation validator (the client's stored ETag). Set when the
37
+ * client holds a stale-but-revalidatable cached result and asks the worker to
38
+ * confirm freshness cheaply; a worker that advertised `revalidatable` compares
39
+ * it and, if unchanged, emits a 0-row batch tagged
40
+ * `cacheControlMetadata({ notModified: true })` instead of re-streaming.
41
+ * Undefined on a normal call.
42
+ */
43
+ ifNoneMatch?: string;
44
+ /** Conditional-revalidation validator (the client's stored Last-Modified).
45
+ * Companion to {@link ifNoneMatch}. Undefined on a normal call. */
46
+ ifModifiedSince?: string;
35
47
  }
36
48
  export interface TableFunctionConfig<TArgs = Record<string, any>, TState = null> {
37
49
  name: string;
@@ -0,0 +1,35 @@
1
+ import { type ProtocolConfig } from "../protocol/dispatch.js";
2
+ import type { LandingDescribeProvider } from "./describe-json.js";
3
+ export interface VgiFetchOptions {
4
+ /** Wire-protocol config (registry + catalogInterface). */
5
+ protocol: Omit<ProtocolConfig, "recoverExchangeState">;
6
+ /** HMAC key for state-token signing. Pass a stable, secret 32-byte key
7
+ * (e.g. derived from a Wrangler secret). Required because Workers don't
8
+ * preserve in-memory state across requests/instances. */
9
+ signingKey: Uint8Array;
10
+ /** State-token TTL in seconds (default 3600). */
11
+ tokenTtl?: number;
12
+ /** URL path prefix for VGI requests (default "/vgi"). Pass "" to mount at
13
+ * the root, which is what a dedicated single-worker HTTP server wants. */
14
+ prefix?: string;
15
+ /** Server ID for state-token attribution (default "vgi-cf"). */
16
+ serverId?: string;
17
+ /** CORS allowed origins. When set, CORS headers are added to all responses,
18
+ * and the preflight `OPTIONS` that browser clients (e.g. the hosted Cupola
19
+ * UI) send before `__describe__` will succeed. Omit to disable CORS. */
20
+ corsOrigins?: string;
21
+ /** Public source-repository URL, surfaced on the landing page. */
22
+ repositoryUrl?: string;
23
+ /** Enables the standardized VGI landing surface: `GET /` serves the shared
24
+ * landing.html, `GET /describe.json` the catalog contract, and
25
+ * `GET /describe/{catalog}/{schema}/{table}.json` lazy per-object columns.
26
+ * Build one with `createLandingDescribe(catalogInterface, workerInfo)`. */
27
+ landingDescribe?: LandingDescribeProvider;
28
+ }
29
+ /**
30
+ * Build a fetch handler suitable for `export default { fetch }` in a CF
31
+ * Worker module, or for `Bun.serve({ fetch })`. The returned handler is
32
+ * async-safe across Workers' isolate-per-request execution model — all state
33
+ * round-trips through the signed state token created here.
34
+ */
35
+ export declare function createVgiFetch(opts: VgiFetchOptions): (req: Request) => Promise<Response>;
@@ -1,3 +1,4 @@
1
+ export { CACHE_ETAG_KEY, CACHE_EXPIRES_KEY, CACHE_IF_MODIFIED_SINCE_KEY, CACHE_IF_NONE_MATCH_KEY, CACHE_LAST_MODIFIED_KEY, CACHE_NO_STORE_KEY, CACHE_NOT_MODIFIED_KEY, CACHE_REVALIDATABLE_KEY, CACHE_SCOPE_CATALOG, CACHE_SCOPE_KEY, CACHE_SCOPE_TRANSACTION, CACHE_STALE_IF_ERROR_KEY, CACHE_STALE_WHILE_REVALIDATE_KEY, CACHE_TTL_KEY, type CacheControl, cacheControlMetadata, type CacheScope, } from "./cache-control.js";
1
2
  export { defineScalarFunction, type ScalarFunctionConfig, type ScalarBindParameters, type ScalarParameterDef } from "./functions/scalar.js";
2
3
  export { defineTableFunction, type TableFunctionConfig, type TableBindParams, type TableProcessParams } from "./functions/table.js";
3
4
  export { defineAggregate, GROUP_COLUMN_NAME, type AggregateFunctionConfig, type AggregateBindParams, type AggregateUpdateParams, type AggregateFinalizeParams, } from "./functions/aggregate.js";
package/dist/index.js CHANGED
@@ -2546,6 +2546,55 @@ var init_storage = __esm(() => {
2546
2546
  });
2547
2547
  });
2548
2548
 
2549
+ // src/cache-control.ts
2550
+ var CACHE_TTL_KEY = "vgi.cache.ttl";
2551
+ var CACHE_EXPIRES_KEY = "vgi.cache.expires";
2552
+ var CACHE_NO_STORE_KEY = "vgi.cache.no_store";
2553
+ var CACHE_SCOPE_KEY = "vgi.cache.scope";
2554
+ var CACHE_ETAG_KEY = "vgi.cache.etag";
2555
+ var CACHE_LAST_MODIFIED_KEY = "vgi.cache.last_modified";
2556
+ var CACHE_REVALIDATABLE_KEY = "vgi.cache.revalidatable";
2557
+ var CACHE_STALE_WHILE_REVALIDATE_KEY = "vgi.cache.stale_while_revalidate";
2558
+ var CACHE_STALE_IF_ERROR_KEY = "vgi.cache.stale_if_error";
2559
+ var CACHE_NOT_MODIFIED_KEY = "vgi.cache.not_modified";
2560
+ var CACHE_IF_NONE_MATCH_KEY = "vgi.cache.if_none_match";
2561
+ var CACHE_IF_MODIFIED_SINCE_KEY = "vgi.cache.if_modified_since";
2562
+ var CACHE_SCOPE_CATALOG = "catalog";
2563
+ var CACHE_SCOPE_TRANSACTION = "transaction";
2564
+ var VALID_SCOPES = [CACHE_SCOPE_CATALOG, CACHE_SCOPE_TRANSACTION];
2565
+ function cacheControlMetadata(cc, extra) {
2566
+ const scope = cc.scope ?? CACHE_SCOPE_CATALOG;
2567
+ if (!VALID_SCOPES.includes(scope)) {
2568
+ throw new Error(`CacheControl.scope must be one of ${VALID_SCOPES.join(", ")}, got ${JSON.stringify(scope)}`);
2569
+ }
2570
+ for (const name of ["ttl", "staleWhileRevalidate", "staleIfError"]) {
2571
+ const value = cc[name];
2572
+ if (value !== undefined && value < 0) {
2573
+ throw new Error(`CacheControl.${name} must be >= 0, got ${value}`);
2574
+ }
2575
+ }
2576
+ const md = new Map(extra ?? []);
2577
+ if (cc.ttl !== undefined)
2578
+ md.set(CACHE_TTL_KEY, String(cc.ttl));
2579
+ if (cc.expires !== undefined)
2580
+ md.set(CACHE_EXPIRES_KEY, cc.expires);
2581
+ if (cc.noStore)
2582
+ md.set(CACHE_NO_STORE_KEY, "1");
2583
+ md.set(CACHE_SCOPE_KEY, scope);
2584
+ if (cc.etag !== undefined)
2585
+ md.set(CACHE_ETAG_KEY, cc.etag);
2586
+ if (cc.lastModified !== undefined)
2587
+ md.set(CACHE_LAST_MODIFIED_KEY, cc.lastModified);
2588
+ if (cc.revalidatable)
2589
+ md.set(CACHE_REVALIDATABLE_KEY, "1");
2590
+ if (cc.staleWhileRevalidate !== undefined)
2591
+ md.set(CACHE_STALE_WHILE_REVALIDATE_KEY, String(cc.staleWhileRevalidate));
2592
+ if (cc.staleIfError !== undefined)
2593
+ md.set(CACHE_STALE_IF_ERROR_KEY, String(cc.staleIfError));
2594
+ if (cc.notModified)
2595
+ md.set(CACHE_NOT_MODIFIED_KEY, "1");
2596
+ return md;
2597
+ }
2549
2598
  // src/functions/scalar.ts
2550
2599
  init_arrow();
2551
2600
 
@@ -3989,6 +4038,12 @@ function defineTableFunction(config) {
3989
4038
  onTick: (pState, tickMetadata) => {
3990
4039
  if (!tickMetadata)
3991
4040
  return;
4041
+ const ifNoneMatch = tickMetadata.get(CACHE_IF_NONE_MATCH_KEY);
4042
+ const ifModifiedSince = tickMetadata.get(CACHE_IF_MODIFIED_SINCE_KEY);
4043
+ if (ifNoneMatch !== undefined)
4044
+ pState.processParams.ifNoneMatch = ifNoneMatch;
4045
+ if (ifModifiedSince !== undefined)
4046
+ pState.processParams.ifModifiedSince = ifModifiedSince;
3992
4047
  const encoded = tickMetadata.get("vgi_pushdown_filters");
3993
4048
  if (!encoded)
3994
4049
  return;
@@ -10172,7 +10227,13 @@ async function buildSchemas(iface, aod) {
10172
10227
  });
10173
10228
  functions.push(...macrosRaw.map((m) => macroToObject(m)));
10174
10229
  functions.sort((a, b) => String(a.type).localeCompare(String(b.type)) || String(a.name).localeCompare(String(b.name)));
10175
- schemas.push({ name: si.name, tables, views, functions });
10230
+ const schemaObj = { name: si.name };
10231
+ if (si.comment)
10232
+ schemaObj.doc = si.comment;
10233
+ schemaObj.tables = tables;
10234
+ schemaObj.views = views;
10235
+ schemaObj.functions = functions;
10236
+ schemas.push(schemaObj);
10176
10237
  counts.schemas += 1;
10177
10238
  counts.tables += tables.length;
10178
10239
  counts.views += views.length;
@@ -11261,6 +11322,145 @@ class VgiClient {
11261
11322
  // src/index.core.ts
11262
11323
  import { str, bytes, int as int3, int32 as int323, float, float32 as float323, bool as bool3, toSchema, OutputCollector, AuthContext } from "@query-farm/vgi-rpc";
11263
11324
  import { subprocessConnect, httpConnect, tcpConnect } from "@query-farm/vgi-rpc";
11325
+ // src/worker.ts
11326
+ import { VgiRpcServer, serveUnix, serveTcp, serveStream as serveStreamRpc } from "@query-farm/vgi-rpc";
11327
+ function parseTcpSpec(spec, out) {
11328
+ const idx = spec.lastIndexOf(":");
11329
+ if (idx >= 0) {
11330
+ const h = spec.slice(0, idx);
11331
+ out.tcpHost = h === "" ? "127.0.0.1" : h;
11332
+ out.tcpPort = Number(spec.slice(idx + 1));
11333
+ } else {
11334
+ out.tcpHost = "127.0.0.1";
11335
+ out.tcpPort = Number(spec);
11336
+ }
11337
+ }
11338
+ function parseLauncherArgs(argv) {
11339
+ const out = {};
11340
+ for (let i = 0;i < argv.length; i++) {
11341
+ const a = argv[i];
11342
+ if (a === "--unix" && i + 1 < argv.length) {
11343
+ out.unixPath = argv[++i];
11344
+ } else if (a.startsWith("--unix=")) {
11345
+ out.unixPath = a.slice("--unix=".length);
11346
+ } else if (a === "--tcp" && i + 1 < argv.length) {
11347
+ parseTcpSpec(argv[++i], out);
11348
+ } else if (a.startsWith("--tcp=")) {
11349
+ parseTcpSpec(a.slice("--tcp=".length), out);
11350
+ } else if (a === "--idle-timeout" && i + 1 < argv.length) {
11351
+ out.idleTimeout = Number(argv[++i]);
11352
+ } else if (a.startsWith("--idle-timeout=")) {
11353
+ out.idleTimeout = Number(a.slice("--idle-timeout=".length));
11354
+ }
11355
+ }
11356
+ return out;
11357
+ }
11358
+
11359
+ class Worker {
11360
+ _registry;
11361
+ _catalogInterface;
11362
+ _catalogName;
11363
+ constructor(config) {
11364
+ this._registry = new FunctionRegistry;
11365
+ this._catalogName = config.catalogName;
11366
+ if (config.functions) {
11367
+ for (const func of config.functions) {
11368
+ this._registry.register(func);
11369
+ }
11370
+ }
11371
+ if (config.catalog) {
11372
+ for (const schema3 of config.catalog.schemas) {
11373
+ if (schema3.functions) {
11374
+ for (const func of schema3.functions) {
11375
+ this._registry.register(func);
11376
+ }
11377
+ }
11378
+ }
11379
+ }
11380
+ if (config.catalogInterface) {
11381
+ this._catalogInterface = config.catalogInterface;
11382
+ } else if (config.catalog) {
11383
+ const base = new ReadOnlyCatalogInterface(config.catalog, this._registry);
11384
+ this._catalogInterface = config.catalogInterfaceFactory ? config.catalogInterfaceFactory(base) : base;
11385
+ }
11386
+ }
11387
+ run(argv = process.argv.slice(2)) {
11388
+ process.stderr.write(`[worker] starting, pid=${process.pid}
11389
+ `);
11390
+ try {
11391
+ const protocol = this.buildProtocol();
11392
+ process.stderr.write(`[worker] protocol built
11393
+ `);
11394
+ const launcher = parseLauncherArgs(argv);
11395
+ if (launcher.unixPath !== undefined) {
11396
+ const envOverride = process.env.VGI_WORKER_IDLE_TIMEOUT;
11397
+ const idleTimeout = envOverride !== undefined && envOverride !== "" ? Number(envOverride) : launcher.idleTimeout;
11398
+ process.stderr.write(`[worker] AF_UNIX mode: ${launcher.unixPath} idle=${idleTimeout ?? 300}s${envOverride !== undefined && envOverride !== "" ? " (env override)" : ""}
11399
+ `);
11400
+ serveUnix(protocol, {
11401
+ unixPath: launcher.unixPath,
11402
+ idleTimeout
11403
+ }).then((handle) => handle.done).then(() => {
11404
+ process.stderr.write(`[worker] serveUnix done (idle shutdown)
11405
+ `);
11406
+ }).catch((err2) => {
11407
+ process.stderr.write(`Worker error: ${err2.message}
11408
+ ${err2.stack}
11409
+ `);
11410
+ process.exit(1);
11411
+ });
11412
+ return;
11413
+ }
11414
+ if (launcher.tcpPort !== undefined) {
11415
+ const envOverride = process.env.VGI_WORKER_IDLE_TIMEOUT;
11416
+ const idleTimeout = envOverride !== undefined && envOverride !== "" ? Number(envOverride) : launcher.idleTimeout;
11417
+ process.stderr.write(`[worker] TCP mode: ${launcher.tcpHost}:${launcher.tcpPort} idle=${idleTimeout ?? 300}s${envOverride !== undefined && envOverride !== "" ? " (env override)" : ""}
11418
+ `);
11419
+ serveTcp(protocol, {
11420
+ host: launcher.tcpHost,
11421
+ port: launcher.tcpPort,
11422
+ idleTimeout
11423
+ }).then((handle) => handle.done).then(() => {
11424
+ process.stderr.write(`[worker] serveTcp done (idle shutdown)
11425
+ `);
11426
+ }).catch((err2) => {
11427
+ process.stderr.write(`Worker error: ${err2.message}
11428
+ ${err2.stack}
11429
+ `);
11430
+ process.exit(1);
11431
+ });
11432
+ return;
11433
+ }
11434
+ const server = new VgiRpcServer(protocol);
11435
+ process.stderr.write(`[worker] server created, calling run()
11436
+ `);
11437
+ server.run().then(() => {
11438
+ process.stderr.write(`[worker] server.run() resolved cleanly
11439
+ `);
11440
+ }).catch((err2) => {
11441
+ process.stderr.write(`Worker error: ${err2.message}
11442
+ ${err2.stack}
11443
+ `);
11444
+ process.exit(1);
11445
+ });
11446
+ } catch (err2) {
11447
+ process.stderr.write(`Worker init error: ${err2.message}
11448
+ ${err2.stack}
11449
+ `);
11450
+ process.exit(1);
11451
+ }
11452
+ }
11453
+ buildProtocol() {
11454
+ return buildVgiProtocol({
11455
+ registry: this._registry,
11456
+ catalogInterface: this._catalogInterface,
11457
+ catalogName: this._catalogName
11458
+ });
11459
+ }
11460
+ async serveStream(readable, writable) {
11461
+ await serveStreamRpc(this.buildProtocol(), { readable, writable });
11462
+ }
11463
+ }
11264
11464
  export {
11265
11465
  utf82 as utf8,
11266
11466
  union2 as union,
@@ -11396,6 +11596,7 @@ export {
11396
11596
  constraintSpecFields,
11397
11597
  columnFromArray,
11398
11598
  codecFor,
11599
+ cacheControlMetadata,
11399
11600
  bytes,
11400
11601
  buildVgiProtocol,
11401
11602
  buildScanBranchesResult,
@@ -11469,6 +11670,20 @@ export {
11469
11670
  CatalogFunctionType,
11470
11671
  CatalogAlreadyExistsError,
11471
11672
  CUPOLA_BASE,
11673
+ CACHE_TTL_KEY,
11674
+ CACHE_STALE_WHILE_REVALIDATE_KEY,
11675
+ CACHE_STALE_IF_ERROR_KEY,
11676
+ CACHE_SCOPE_TRANSACTION,
11677
+ CACHE_SCOPE_KEY,
11678
+ CACHE_SCOPE_CATALOG,
11679
+ CACHE_REVALIDATABLE_KEY,
11680
+ CACHE_NO_STORE_KEY,
11681
+ CACHE_NOT_MODIFIED_KEY,
11682
+ CACHE_LAST_MODIFIED_KEY,
11683
+ CACHE_IF_NONE_MATCH_KEY,
11684
+ CACHE_IF_MODIFIED_SINCE_KEY,
11685
+ CACHE_EXPIRES_KEY,
11686
+ CACHE_ETAG_KEY,
11472
11687
  BoundStorage,
11473
11688
  BindResultSchema,
11474
11689
  AuthContext,
@@ -11476,4 +11691,4 @@ export {
11476
11691
  ArgumentValidationError
11477
11692
  };
11478
11693
 
11479
- //# debugId=55E5F7DD831BB4DD64756E2164756E21
11694
+ //# debugId=0510FF19793B06C764756E2164756E21