duckfn-docs-kit 0.2.0 → 0.3.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,134 @@
1
+ /**
2
+ * `duckfn-docs-kit/sql/harness` — the browser page the offline SQL verifier
3
+ * (`sql/browserRunner`) drives with Playwright.
4
+ *
5
+ * It is deliberately thin: it reuses the site's own runtime (`sql/runtime`) so
6
+ * a verified block runs through exactly the code path a reader's **Run** click
7
+ * does — same `AsyncDuckDB` wiring, same ordered extension preload, same
8
+ * "multi-statement returns the last result" `execute()`. The only difference is
9
+ * where the engine comes from: instead of the jsDelivr CDN, the runner serves
10
+ * `duckdb-*.wasm` and its worker script same-origin and hands those URLs in
11
+ * through {@link window.DFK_HARNESS_BUNDLE}, which `runtime.init` accepts as a
12
+ * {@link LocalBundle}.
13
+ *
14
+ * The two contracts the page reads are injected by the served `harness.html`:
15
+ *
16
+ * - `window.DFK_HARNESS_BUNDLE` — `{mainModule, mainWorker, pthreadWorker?}`,
17
+ * the locally-served engine bundle (an absolute same-origin URL each).
18
+ * - the `<script id="dfk-sql-runtime">` JSON tag — the preload list and
19
+ * `allowUnsignedExtensions`, in the very shape the site plugin injects
20
+ * (see `sql/runtimeConfig`), so nothing here re-implements extension loading.
21
+ *
22
+ * The runner communicates through `window.__dfkReady`, `window.__dfkRun` and
23
+ * `window.__dfkQuery`, each reached with a Playwright `page.evaluate` (which
24
+ * awaits the returned promise and marshals the value back to Node). Page reload
25
+ * per content file is what gives "one instance per page": a fresh document
26
+ * resets the module-level singleton, so pages never share a DuckDB instance
27
+ * while the blocks of one page keep sharing its connection (via
28
+ * `DuckDBRuntime.execute`).
29
+ */
30
+ import {DuckDBRuntime, type LocalBundle} from './runtime';
31
+
32
+ declare global {
33
+ interface Window {
34
+ /** The locally-served engine bundle, injected by the runner's harness.html. */
35
+ DFK_HARNESS_BUNDLE?: LocalBundle;
36
+ /** Resolves once the instance is up and the site preloads have loaded. */
37
+ __dfkReady?: Promise<void>;
38
+ /** Runs one block and reports rows/columns, or a captured error message. */
39
+ __dfkRun?: (sql: string) => Promise<HarnessRunResult>;
40
+ /**
41
+ * Runs one statement and returns its actual result — the rows, not just a
42
+ * count. Used by the vfs browser probe (`browserRunner.query`) to read the
43
+ * value each statement produced; the docs verifier never needs it.
44
+ */
45
+ __dfkQuery?: (sql: string) => Promise<HarnessQueryResult>;
46
+ }
47
+ }
48
+
49
+ /** What {@link window.__dfkRun} resolves with; mirrors the runner's `RunResult`. */
50
+ export interface HarnessRunResult {
51
+ rows: number;
52
+ columns: number;
53
+ /** Set when the block failed; `rows`/`columns` are then 0. */
54
+ error?: string;
55
+ }
56
+
57
+ /** What {@link window.__dfkQuery} resolves with: the marshalled result rows. */
58
+ export interface HarnessQueryResult {
59
+ columns: string[];
60
+ rows: Record<string, unknown>[];
61
+ error?: string;
62
+ }
63
+
64
+ async function ready(): Promise<void> {
65
+ const bundle = window.DFK_HARNESS_BUNDLE;
66
+ if (!bundle) {
67
+ throw new Error('sql/harness: window.DFK_HARNESS_BUNDLE is not set');
68
+ }
69
+ const runtime = DuckDBRuntime.getInstance();
70
+ const allowUnsigned = readAllowUnsigned();
71
+ await runtime.init({bundle, allowUnsignedExtensions: allowUnsigned});
72
+ }
73
+
74
+ /**
75
+ * `allowUnsignedExtensions` is fixed at `open()` time, so the harness has to
76
+ * hand it to `init()` directly rather than let the runtime read it lazily from
77
+ * the config tag. Reading the same tag here (idempotently) keeps the injected
78
+ * config the single source of truth — the runtime still reads the preload list
79
+ * from it inside `#create()`.
80
+ */
81
+ function readAllowUnsigned(): boolean {
82
+ const text = document
83
+ .getElementById('dfk-sql-runtime')
84
+ ?.textContent?.trim();
85
+ if (!text) {
86
+ return false;
87
+ }
88
+ try {
89
+ const parsed = JSON.parse(text) as {allowUnsignedExtensions?: boolean};
90
+ return parsed.allowUnsignedExtensions === true;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ async function run(sql: string): Promise<HarnessRunResult> {
97
+ const result = await DuckDBRuntime.getInstance().execute(sql);
98
+ if (result.error) {
99
+ return {rows: 0, columns: 0, error: result.error};
100
+ }
101
+ return {rows: result.rows.length, columns: result.columns.length};
102
+ }
103
+
104
+ async function query(sql: string): Promise<HarnessQueryResult> {
105
+ const result = await DuckDBRuntime.getInstance().execute(sql);
106
+ if (result.error) {
107
+ return {columns: [], rows: [], error: result.error};
108
+ }
109
+ // Rebuild each row as a plain, own-enumerable object. `execute()` hands back
110
+ // Apache-Arrow row objects whose values sit behind getters, so Playwright's
111
+ // `page.evaluate` serializer would marshal them to `{}`; reading the getters
112
+ // here, in-page, and copying to a literal survives the hop. BigInt values
113
+ // (DuckDB integers) become strings for the same reason.
114
+ const rows = result.rows.map((row) => {
115
+ const plain: Record<string, unknown> = {};
116
+ for (const column of result.columns) {
117
+ const value = row[column];
118
+ plain[column] =
119
+ typeof value === 'bigint'
120
+ ? value.toString()
121
+ : value === null || value === undefined
122
+ ? null
123
+ : typeof value === 'object'
124
+ ? JSON.stringify(value)
125
+ : value;
126
+ }
127
+ return plain;
128
+ });
129
+ return {columns: result.columns, rows};
130
+ }
131
+
132
+ window.__dfkReady = ready();
133
+ window.__dfkRun = run;
134
+ window.__dfkQuery = query;
@@ -65,6 +65,27 @@ export type RuntimeState = 'idle' | 'loading' | 'ready' | 'error';
65
65
  export interface RuntimeOptions {
66
66
  /** Let `LOAD` accept an extension whose signature does not verify. */
67
67
  allowUnsignedExtensions?: boolean;
68
+ /**
69
+ * Serve the DuckDB-Wasm engine from explicit same-origin URLs instead of the
70
+ * jsDelivr CDN. Used by the offline SQL verifier (`sql/browserRunner`): the
71
+ * harness passes the locally-served `duckdb-*.wasm` / worker script so a CI
72
+ * run never reaches the network. When set, `#create()` skips
73
+ * `getJsDelivrBundles()`/`selectBundle()` and the cross-origin blob-worker
74
+ * wrapper (the worker is same-origin here, so it is constructed directly).
75
+ */
76
+ bundle?: LocalBundle;
77
+ }
78
+
79
+ /**
80
+ * A locally-served DuckDB-Wasm bundle: absolute same-origin URLs for the
81
+ * engine wasm and its worker script. `pthreadWorker` is only needed for the
82
+ * cross-origin-isolated (COI) bundle; the default non-COI `eh`/`mvp` bundles run
83
+ * single-threaded and leave it unset.
84
+ */
85
+ export interface LocalBundle {
86
+ mainModule: string;
87
+ mainWorker: string;
88
+ pthreadWorker?: string;
68
89
  }
69
90
 
70
91
  /** Options for {@link DuckDBRuntime.loadExtension}. */
@@ -96,6 +117,8 @@ export class DuckDBRuntime {
96
117
  #db: DuckdbWasm.AsyncDuckDB | null = null;
97
118
  #conn: DuckdbWasm.AsyncDuckDBConnection | null = null;
98
119
  #allowUnsigned = false;
120
+ /** A locally-served engine bundle (offline verifier); `null` means CDN. */
121
+ #bundle: LocalBundle | null = null;
99
122
  /** The injected site config; read (and validated) once on first init. */
100
123
  #site: SiteRuntimeConfig | null = null;
101
124
  /** Loaded / in-flight extensions, keyed by repository + name, or by URL. */
@@ -125,6 +148,9 @@ export class DuckDBRuntime {
125
148
  if (options.allowUnsignedExtensions) {
126
149
  this.#allowUnsigned = true;
127
150
  }
151
+ if (options.bundle) {
152
+ this.#bundle = options.bundle;
153
+ }
128
154
  if (this.#state === 'ready') {
129
155
  return Promise.resolve();
130
156
  }
@@ -154,6 +180,24 @@ export class DuckDBRuntime {
154
180
 
155
181
  async #create(preload: readonly PreloadEntry[]): Promise<void> {
156
182
  const duckdb = await import('@duckdb/duckdb-wasm');
183
+
184
+ // Offline verifier path: the engine and its worker are served same-origin
185
+ // by the runner, so the worker is constructed directly (no cross-origin
186
+ // blob wrapper) and no CDN bundle is selected.
187
+ if (this.#bundle) {
188
+ const worker = new Worker(this.#bundle.mainWorker);
189
+ this.#db = new duckdb.AsyncDuckDB(new duckdb.ConsoleLogger(), worker);
190
+ await this.#db.instantiate(this.#bundle.mainModule, this.#bundle.pthreadWorker ?? null);
191
+ await this.#db.open({allowUnsignedExtensions: this.#allowUnsigned});
192
+ this.#conn = await this.#db.connect();
193
+ for (const entry of preload) {
194
+ await this.#loadEntry(entry);
195
+ }
196
+ this.#state = 'ready';
197
+ this.#message = '';
198
+ return;
199
+ }
200
+
157
201
  const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
158
202
  if (!bundle.mainWorker) {
159
203
  throw new Error('The selected DuckDB-Wasm bundle has no worker script');
package/src/sql/sql.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /* Light-DOM styles for `<dfk-sql>`: the result container. It lives in the
2
2
  element's light DOM (not the shadow root) because VTable injects a
3
3
  *document-level* stylesheet that a shadow boundary could not host — the same
4
- light-DOM exception as the TOC toggle (see AGENTS.md rule 9). It is created
4
+ light-DOM exception as the TOC toggle (see CONVENTIONS.md rule 9). It is created
5
5
  only when a query runs, so the light DOM still starts empty (rule 11). Every
6
6
  class carries the `dfk-sql-` prefix so nothing can collide with the host site.
7
7
  *
package/src/sql/verify.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  * publishes, so a broken example is caught by CI instead of by a reader
4
4
  * clicking **Run**.
5
5
  *
6
- * It is the same environment the site gives a block: DuckDB-Wasm in a worker,
6
+ * It is the same environment the site gives a block: DuckDB-Wasm in a browser,
7
7
  * the site's extension preloaded, one instance per page and the page's blocks
8
- * sharing a connection (see `sql/nodeRunner.ts` for why it is this target and
9
- * not the blocking one).
8
+ * sharing a connection (see `sql/browserRunner.ts` for why this is a real
9
+ * browser and not the Node worker the kit used to run).
10
10
  *
11
11
  * A block may fail *on purpose* — half the guide ends on a statement that
12
12
  * demonstrates an error. Such a block says so in its own metadata
@@ -15,12 +15,11 @@
15
15
  * on has to be data rather than a string match on a comment. Only blocks that
16
16
  * did not behave as declared make the command exit non-zero.
17
17
  */
18
- import {mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync} from 'node:fs';
19
- import {tmpdir} from 'node:os';
18
+ import {readdirSync, statSync, writeFileSync} from 'node:fs';
20
19
  import {join, resolve} from 'node:path';
21
20
 
22
21
  import {collectRunnableSql, expectsError, type RunnableSqlBlock} from './collect';
23
- import {WasmSqlRunner, type WasmPlatform} from './nodeRunner';
22
+ import {BrowserSqlRunner, type WasmPlatform} from './browserRunner';
24
23
 
25
24
  export interface VerifyOptions {
26
25
  /** Docs site root; defaults to the working directory. */
@@ -39,11 +38,10 @@ export interface VerifyOptions {
39
38
  /** Per-block timeout in milliseconds; a hang is reported instead of blocking CI. */
40
39
  timeoutMs?: number;
41
40
  /**
42
- * Directory the blocks run in. Defaults to a fresh temporary directory that
43
- * is removed afterwards: a block may `COPY … TO 'a.csv'`, and on Node
44
- * DuckDB's file system is the real one, relative to the working directory.
41
+ * The browser executable that runs the blocks. Defaults to a detected
42
+ * Chrome/Edge; the `DFK_BROWSER` environment variable is the same override.
45
43
  */
46
- workingDir?: string;
44
+ browser?: string;
47
45
  /** Write the full result list here as JSON. */
48
46
  reportFile?: string;
49
47
  }
@@ -79,8 +77,6 @@ const DEFAULT_TIMEOUT_MS = 30_000;
79
77
 
80
78
  /** Runs every runnable block of the site and returns the outcome of each. */
81
79
  export async function verifySqlDocs(options: VerifyOptions = {}): Promise<VerifyReport> {
82
- // Everything is resolved to absolute paths first: the run changes the working
83
- // directory (see below).
84
80
  const siteDir = resolve(options.siteDir ?? process.cwd());
85
81
  const blocks = collectRunnableSql({
86
82
  siteDir,
@@ -94,26 +90,10 @@ export async function verifySqlDocs(options: VerifyOptions = {}): Promise<Verify
94
90
  : defaultExtension(siteDir);
95
91
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
96
92
 
97
- // A block may write files — `COPY (SELECT 1) TO 'a.csv'` — and on Node the
98
- // file system behind DuckDB is the real one, resolved against the working
99
- // directory. Giving the run a scratch directory of its own keeps the docs tree
100
- // clean instead of dropping test residue into it.
101
- const scratch = options.workingDir
102
- ? resolve(options.workingDir)
103
- : mkdtempSync(join(tmpdir(), 'duckfn-sql-verify-'));
104
- mkdirSync(scratch, {recursive: true});
105
- const previousCwd = process.cwd();
106
- process.chdir(scratch);
107
-
108
- let results: BlockResult[];
109
- try {
110
- results = await runPages(blocks, extension, options, timeoutMs);
111
- } finally {
112
- process.chdir(previousCwd);
113
- if (!options.workingDir) {
114
- rmSync(scratch, {recursive: true, force: true});
115
- }
116
- }
93
+ // No working directory: in a browser DuckDB's file system is the instance's
94
+ // own memory, so `COPY … TO` / `dfn_file_write_*` never touch the docs tree —
95
+ // they land in the page and vanish on the next `newPage()`.
96
+ const results = await runPages(blocks, extension, options, timeoutMs);
117
97
 
118
98
  const report: VerifyReport = {
119
99
  blocks: results,
@@ -135,10 +115,11 @@ async function runPages(
135
115
  options: VerifyOptions,
136
116
  timeoutMs: number,
137
117
  ): Promise<BlockResult[]> {
138
- const runner = await WasmSqlRunner.create({
118
+ const runner = await BrowserSqlRunner.create({
139
119
  extension,
140
120
  platform: options.platform,
141
121
  engine: options.engine,
122
+ browser: options.browser,
142
123
  });
143
124
  const results: BlockResult[] = [];
144
125
  try {
@@ -158,7 +139,7 @@ async function runPages(
158
139
 
159
140
  /** Runs one block and judges the result against the block's own declaration. */
160
141
  async function runBlock(
161
- runner: WasmSqlRunner,
142
+ runner: BrowserSqlRunner,
162
143
  block: RunnableSqlBlock,
163
144
  timeoutMs: number,
164
145
  ): Promise<BlockResult> {
@@ -278,9 +259,9 @@ export async function cliMain(argv: readonly string[]): Promise<void> {
278
259
 
279
260
  const USAGE = `Usage: duckfn-sql-verify [options]
280
261
 
281
- Runs every runnable SQL block of a duckfn docs site in DuckDB-Wasm, and checks
282
- that each one behaves as its own metadata declares ("expect": "error" for a
283
- block that demonstrates a failure).
262
+ Runs every runnable SQL block of a duckfn docs site in a headless browser
263
+ (DuckDB-Wasm), and checks that each one behaves as its own metadata declares
264
+ ("expect": "error" for a block that demonstrates a failure).
284
265
 
285
266
  --site <dir> Docs site root (default: the working directory)
286
267
  --content <dir> Content directory, relative to the site root (repeatable;
@@ -291,8 +272,9 @@ block that demonstrates a failure).
291
272
  --platform <eh|mvp> DuckDB-Wasm bundle, which must match the extension build
292
273
  (default: eh)
293
274
  --engine <path> Engine wasm override
275
+ --browser <path> Browser executable (default: a detected Chrome/Edge,
276
+ or the DFK_BROWSER environment variable)
294
277
  --timeout <ms> Per-block timeout (default: 30000)
295
- --working-dir <dir> Directory the blocks run in (default: a temporary one)
296
278
  --report <file> Write the full result list as JSON
297
279
  --quiet Only report unexpected behaviour
298
280
  --help Show this help
@@ -330,12 +312,12 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
330
312
  case '--engine':
331
313
  options.engine = next();
332
314
  break;
315
+ case '--browser':
316
+ options.browser = next();
317
+ break;
333
318
  case '--timeout':
334
319
  options.timeoutMs = Number(next());
335
320
  break;
336
- case '--working-dir':
337
- options.workingDir = next();
338
- break;
339
321
  case '--report':
340
322
  options.reportFile = next();
341
323
  break;
@@ -1,51 +0,0 @@
1
- export interface DuckdbQueryResult {
2
- numRows: number;
3
- schema: {
4
- fields: {
5
- name: string;
6
- }[];
7
- };
8
- }
9
- export interface DuckdbConnection {
10
- query(sql: string): Promise<DuckdbQueryResult>;
11
- }
12
- export type WasmPlatform = 'eh' | 'mvp';
13
- export interface RunnerOptions {
14
- /**
15
- * The extension to `LOAD`: a local `.duckdb_extension.wasm` path (served to
16
- * the worker over a loopback http server) or an absolute `http(s)` URL.
17
- */
18
- extension: string;
19
- /**
20
- * DuckDB-Wasm platform. Must match how the extension was built — a site
21
- * serving `duckfn-wasm_eh.duckdb_extension.wasm` runs the `eh` bundle.
22
- */
23
- platform?: WasmPlatform;
24
- /** The engine wasm; defaults to the one shipped beside the worker bundle. */
25
- engine?: string;
26
- }
27
- export interface RunResult {
28
- rows: number;
29
- columns: number;
30
- }
31
- /**
32
- * One DuckDB-Wasm instance at a time, driven from Node.
33
- *
34
- * `newPage()` is what a docs site does per page load: a fresh instance, a fresh
35
- * connection, the extension loaded again. Blocks of one page then share state
36
- * (a table created in one block is visible to the next), while pages stay
37
- * isolated — which is why the runner is used one page at a time rather than
38
- * over a single long-lived connection.
39
- */
40
- export declare class WasmSqlRunner {
41
- #private;
42
- private constructor();
43
- static create(options: RunnerOptions): Promise<WasmSqlRunner>;
44
- /** Drops the current instance and starts a fresh page: new instance, new connection. */
45
- newPage(): Promise<void>;
46
- /** Runs one block; the caller decides whether a failure is expected. */
47
- run(sql: string): Promise<RunResult>;
48
- /** Where the fetched extension was staged, for diagnostics. */
49
- get stagingDir(): string | null;
50
- close(): Promise<void>;
51
- }
@@ -1,115 +0,0 @@
1
- import { createRequire as e } from "node:module";
2
- import { basename as t, dirname as n, join as r } from "node:path";
3
- import { mkdirSync as i, readFileSync as a, rmSync as o } from "node:fs";
4
- import { createServer as s } from "node:http";
5
- import { Worker as c } from "node:worker_threads";
6
- import { homedir as l } from "node:os";
7
- //#region src/sql/nodeRunner.ts
8
- var u = e(import.meta.url), d = "@duckdb/duckdb-wasm/dist/duckdb-node.cjs", f = class {
9
- #e;
10
- #t = /* @__PURE__ */ new Map();
11
- onmessage = null;
12
- onerror = null;
13
- onclose = null;
14
- constructor(e) {
15
- this.#e = new c(u.resolve(d), { workerData: {
16
- mod: e,
17
- name: "",
18
- type: ""
19
- } }), this.#e.on("message", (e) => this.#n("message", e)), this.#e.on("error", (e) => this.#n("error", e)), this.#e.on("exit", () => this.#n("close"));
20
- }
21
- #n(e, t) {
22
- let n = {
23
- type: e,
24
- data: t,
25
- target: this,
26
- currentTarget: this
27
- }, r = this[`on${e}`];
28
- typeof r == "function" && r(n);
29
- for (let t of this.#t.get(e) ?? []) t(n);
30
- }
31
- addEventListener(e, t) {
32
- this.#t.set(e, [...this.#t.get(e) ?? [], t]);
33
- }
34
- removeEventListener(e, t) {
35
- this.#t.set(e, (this.#t.get(e) ?? []).filter((e) => e !== t));
36
- }
37
- postMessage(e, t) {
38
- this.#e.postMessage(e, t ?? []);
39
- }
40
- terminate() {
41
- return this.#e.terminate();
42
- }
43
- }, p = class e {
44
- #e = null;
45
- #t = null;
46
- #n = null;
47
- #r = null;
48
- #i;
49
- #a;
50
- #o;
51
- constructor(e, t, n) {
52
- this.#i = e, this.#a = t, this.#o = n;
53
- }
54
- static async create(t) {
55
- let i = t.platform ?? "eh", a = u.resolve(`@duckdb/duckdb-wasm/dist/duckdb-node-${i}.worker.cjs`), o = t.engine ?? r(n(a), `duckdb-${i}.wasm`), s = /^https?:\/\//i.test(t.extension) ? null : await m(t.extension), c = s ? s.url : t.extension, l = new e(c, o, a);
56
- return l.#n = s ? s.server : null, l.#r = s ? h(s.stagingHost, s.stagingSegment) : null, l;
57
- }
58
- async newPage() {
59
- this.#e && (await this.#e.terminate(), this.#e = null, this.#t = null);
60
- let e = await import(
61
- /* @vite-ignore */
62
- d
63
- ), t = new e.AsyncDuckDB(new e.ConsoleLogger(), new f(this.#o));
64
- await t.instantiate(this.#a, null), await t.open({ allowUnsignedExtensions: !0 });
65
- let n = await t.connect();
66
- await n.query(`LOAD '${this.#i}'`), this.#e = t, this.#t = n;
67
- }
68
- async run(e) {
69
- let t = this.#t;
70
- if (!t) throw Error("sql/verify: no page is open — call newPage() first");
71
- let n = await t.query(e);
72
- return {
73
- rows: n.numRows,
74
- columns: n.schema.fields.length
75
- };
76
- }
77
- get stagingDir() {
78
- return this.#r;
79
- }
80
- async close() {
81
- this.#e && (await this.#e.terminate(), this.#e = null, this.#t = null), this.#n &&= (await new Promise((e) => this.#n?.close(() => e())), null);
82
- }
83
- };
84
- async function m(e) {
85
- let n = a(e), r = t(e), i = g(e), o = s((e, t) => {
86
- t.writeHead(200, {
87
- "content-type": "application/octet-stream",
88
- "content-length": n.length
89
- }), t.end(n);
90
- }), c = process.platform === "win32" ? 80 : 0;
91
- await new Promise((e, t) => {
92
- o.once("error", (e) => {
93
- t(/* @__PURE__ */ Error(`sql/verify: cannot serve the extension on port ${c} (${e.code}): a port in the URL would put a colon into DuckDB's staging path, which is not a legal Windows path — free port 80, or load the extension from an http(s) URL with --extension`));
94
- }), o.listen(c, "localhost", e);
95
- });
96
- let l = o.address(), u = typeof l == "object" && l ? l.port : c, d = u === 80 ? "localhost" : `localhost:${u}`;
97
- return {
98
- server: o,
99
- url: `http://${d}/${i}/${r}`,
100
- stagingHost: d,
101
- stagingSegment: i
102
- };
103
- }
104
- function h(e, t) {
105
- let n = r(l(), ".duckdb", "extensions", e, t);
106
- return o(n, {
107
- recursive: !0,
108
- force: !0
109
- }), i(n, { recursive: !0 }), n;
110
- }
111
- function g(e) {
112
- return t(e).split(".")[0] ?? "";
113
- }
114
- //#endregion
115
- export { p as WasmSqlRunner };