@query-farm/vgi 0.15.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;
@@ -11267,6 +11322,145 @@ class VgiClient {
11267
11322
  // src/index.core.ts
11268
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";
11269
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
+ }
11270
11464
  export {
11271
11465
  utf82 as utf8,
11272
11466
  union2 as union,
@@ -11402,6 +11596,7 @@ export {
11402
11596
  constraintSpecFields,
11403
11597
  columnFromArray,
11404
11598
  codecFor,
11599
+ cacheControlMetadata,
11405
11600
  bytes,
11406
11601
  buildVgiProtocol,
11407
11602
  buildScanBranchesResult,
@@ -11475,6 +11670,20 @@ export {
11475
11670
  CatalogFunctionType,
11476
11671
  CatalogAlreadyExistsError,
11477
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,
11478
11687
  BoundStorage,
11479
11688
  BindResultSchema,
11480
11689
  AuthContext,
@@ -11482,4 +11691,4 @@ export {
11482
11691
  ArgumentValidationError
11483
11692
  };
11484
11693
 
11485
- //# debugId=B7CCB77D8D0375B864756E2164756E21
11694
+ //# debugId=0510FF19793B06C764756E2164756E21