@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.
- package/README.md +53 -0
- package/dist/cache-control.d.ts +90 -0
- package/dist/client-entry.js +3862 -1
- package/dist/client-entry.js.map +34 -3
- package/dist/functions/table.d.ts +12 -0
- package/dist/http/fetch.d.ts +35 -0
- package/dist/index.core.d.ts +1 -0
- package/dist/index.js +217 -2
- package/dist/index.js.map +9 -7
- package/dist/serve-entry.d.ts +84 -0
- package/dist/serve-entry.js +6428 -0
- package/dist/serve-entry.js.map +62 -0
- package/dist/worker-cf-entry.d.ts +4 -22
- package/dist/worker-cf-entry.js +451 -24
- package/dist/worker-cf-entry.js.map +9 -6
- package/package.json +18 -6
- package/src/cache-control.ts +133 -0
- package/src/functions/table.ts +25 -1
- package/src/http/describe-json.ts +6 -1
- package/src/http/fetch.ts +80 -0
- package/src/index.core.ts +21 -0
- package/src/serve-entry.ts +233 -0
- package/src/worker-cf-entry.ts +7 -51
|
@@ -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>;
|
package/dist/index.core.d.ts
CHANGED
|
@@ -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
|
-
|
|
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=
|
|
11694
|
+
//# debugId=0510FF19793B06C764756E2164756E21
|