@query-farm/vgi 0.25.3 → 0.26.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.
@@ -0,0 +1,122 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ // SPDX-License-Identifier: LicenseRef-QueryFarm-Source-Available-1.0
3
+
4
+ // The standardized VGI landing surface.
5
+ //
6
+ // This lives here, not in `@query-farm/vgi-rpc`, because everything it serves
7
+ // is VGI vocabulary: a page that renders catalogs, schemas, tables, functions
8
+ // and macros, and the browser build of *this package's* client that reads them
9
+ // over the protocol. vgi-rpc is generic RPC over Arrow and depends on nothing
10
+ // here — yet it used to vendor both assets, which put a compiled artifact of
11
+ // the higher layer inside the lower one and added ~900 KB to every consumer of
12
+ // vgi-rpc, whether or not they served a VGI page. vgi-python already drew the
13
+ // line this way: its landing surface is in `vgi/http/`, and `vgi_rpc/http/` has
14
+ // neither asset.
15
+ //
16
+ // It reaches the request through vgi-rpc's `extraRoutes` hook rather than by
17
+ // wrapping the returned fetch handler, because ordering matters. Contributed
18
+ // routes run at the same pipeline position the surface occupied when it lived
19
+ // inside vgi-rpc — after the OAuth browser-redirect branch, and before the
20
+ // generic landing page and the 404. Wrapping from outside would answer ahead of
21
+ // that redirect, so a worker with OAuth configured would show this page to a
22
+ // browser that should have been sent to its identity provider first.
23
+ //
24
+ // That redirect is the only thing guarding these two routes, and only for
25
+ // browsers: vgi-rpc gates GET pages on `authenticate && pkceConfig`, and a
26
+ // non-browser caller that fails auth falls through to normal page serving. So
27
+ // on a worker with no PKCE configured, the page and the client bundle are
28
+ // public — as the generic landing page always was. Worth knowing before
29
+ // treating anything on this surface as private; vgi-rpc-python is stricter and
30
+ // answers 401 here.
31
+
32
+ import type { ExtraRouteContext, ExtraRouteHandler } from "@query-farm/vgi-rpc";
33
+ import { LANDING_HTML_BYTES } from "./landing-html.js";
34
+ import { CLIENT_BUNDLE_BYTES } from "./client-bundle.js";
35
+
36
+ /**
37
+ * Worker identity for the standardized VGI landing surface.
38
+ *
39
+ * The shared `landing.html` reads catalog metadata by speaking the VGI protocol
40
+ * through the client bundle the worker serves beside it, so nothing about the
41
+ * catalog belongs here. What the protocol has no method for — which worker this
42
+ * is, what it is called, what version it runs — rides on the JSON status
43
+ * document at `GET {prefix}/?format=json`.
44
+ */
45
+ export interface LandingInfo {
46
+ /** Worker name shown as the page heading, e.g. "ishares". */
47
+ name: string;
48
+ /** One-line description shown under the heading. */
49
+ doc?: string;
50
+ /** Worker version string shown in the footer. */
51
+ version?: string;
52
+ /** Override the Cupola base URL the "Explore" links point at. */
53
+ cupolaBase?: string;
54
+ }
55
+
56
+ /** Default Cupola deployment the page's "Explore" links point at. */
57
+ export const DEFAULT_CUPOLA_BASE = "https://cupola.query-farm.services";
58
+
59
+ /**
60
+ * Build the VGI landing surface as a route contributed to vgi-rpc's HTTP
61
+ * handler.
62
+ *
63
+ * Serves two paths, both relative to the handler's mount prefix:
64
+ *
65
+ * - `GET {prefix}/` — the shared `landing.html` for browsers, or a JSON status
66
+ * document for health checks, `?format=json`, and the page's own identity
67
+ * read. Content negotiation matches vgi-python's `LandingPageResource`.
68
+ * - `GET {prefix}/vgi-client.js` — the browser build the page imports.
69
+ *
70
+ * Anything else returns `null`, which lets normal routing continue.
71
+ */
72
+ export function createLandingRoutes(info: LandingInfo): ExtraRouteHandler {
73
+ return (request: Request, ctx: ExtraRouteContext): Response | null => {
74
+ const path = ctx.url.pathname;
75
+ const { prefix } = ctx;
76
+
77
+ if (path === prefix || path === `${prefix}/`) {
78
+ const accept = request.headers.get("Accept") ?? "";
79
+ const wantJson =
80
+ ctx.url.searchParams.get("format") === "json" ||
81
+ (accept.includes("application/json") && !accept.includes("text/html"));
82
+ if (wantJson) {
83
+ // Rebuilt per request rather than cached: `oauthActive` and `serverId`
84
+ // come from the handler, so this cannot be pre-rendered at construction
85
+ // time the way it was when it lived inside vgi-rpc.
86
+ const body = JSON.stringify({
87
+ status: "ok",
88
+ server_id: ctx.serverId,
89
+ protocol: "vgi",
90
+ worker: info.name,
91
+ doc: info.doc ?? "",
92
+ version: info.version ?? "",
93
+ lang: "typescript",
94
+ oauth: ctx.oauthActive,
95
+ cupola_base: info.cupolaBase ?? DEFAULT_CUPOLA_BASE,
96
+ });
97
+ const headers = new Headers({ "Content-Type": "application/json" });
98
+ ctx.addCorsHeaders(headers);
99
+ return new Response(body, { status: 200, headers });
100
+ }
101
+ const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
102
+ ctx.addCorsHeaders(headers);
103
+ return new Response(LANDING_HTML_BYTES as unknown as BodyInit, { status: 200, headers });
104
+ }
105
+
106
+ // Served by the worker rather than from a CDN: this page is same-origin
107
+ // with an authenticated worker and carries its session cookie, so
108
+ // third-party script here would run with full access to that origin — and a
109
+ // CDN dependency would break air-gapped deployments that today need nothing
110
+ // but the worker.
111
+ if (path === `${prefix}/vgi-client.js`) {
112
+ const headers = new Headers({
113
+ "Content-Type": "text/javascript; charset=utf-8",
114
+ "Cache-Control": "public, max-age=3600",
115
+ });
116
+ ctx.addCorsHeaders(headers);
117
+ return new Response(CLIENT_BUNDLE_BYTES as unknown as BodyInit, { status: 200, headers });
118
+ }
119
+
120
+ return null;
121
+ };
122
+ }
package/src/index.core.ts CHANGED
@@ -157,7 +157,8 @@ export { buildVgiProtocol, type ProtocolConfig } from "./protocol/dispatch.js";
157
157
  // speaking the protocol through the client bundle the worker serves beside it,
158
158
  // so a worker supplies only its own identity via `createHttpHandler`'s
159
159
  // `landingInfo` option.
160
- export type { LandingInfo } from "@query-farm/vgi-rpc";
160
+ export type { LandingInfo } from "./http/landing.js";
161
+ export { createLandingRoutes } from "./http/landing.js";
161
162
 
162
163
  // Wire schemas — needed by workers that synthesize TableInfo.bind_result /
163
164
  // TableInfo.scan_function bytes to short-circuit per-scan RPCs.
@@ -73,7 +73,8 @@ export type { ProtocolConfig } from "./protocol/dispatch.js";
73
73
  // Landing surface. CF workers pass their identity as `landingInfo` so `GET /`
74
74
  // serves the standardized VGI landing page, and `GET /vgi-client.js` the
75
75
  // browser client build it reads the catalog with.
76
- export type { LandingInfo } from "@query-farm/vgi-rpc";
76
+ export type { LandingInfo } from "./http/landing.js";
77
+ export { createLandingRoutes } from "./http/landing.js";
77
78
 
78
79
  // Backend-agnostic Arrow facade. Same source resolves to arrow-js on
79
80
  // Node/Bun and flechette under workerd/worker/browser via the package's