duckfn-docs-kit 0.1.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.
Files changed (65) hide show
  1. package/AGENTS.md +689 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/dom.d.ts +69 -0
  5. package/dist/home/DfkFeatures.d.ts +20 -0
  6. package/dist/home/DfkHero.d.ts +25 -0
  7. package/dist/home/DfkNextSteps.d.ts +16 -0
  8. package/dist/home/styles.d.ts +8 -0
  9. package/dist/index.d.ts +50 -0
  10. package/dist/index.js +2 -0
  11. package/dist/register-DKLiYs-F.js +2324 -0
  12. package/dist/register.d.ts +10 -0
  13. package/dist/remark.d.ts +21 -0
  14. package/dist/remark.js +15 -0
  15. package/dist/runtimeConfig-Bokbb8VH.js +106 -0
  16. package/dist/sql/DfkSql.d.ts +7 -0
  17. package/dist/sql/PreviewTabs.d.ts +37 -0
  18. package/dist/sql/client.d.ts +1 -0
  19. package/dist/sql/client.js +4 -0
  20. package/dist/sql/editor.d.ts +16 -0
  21. package/dist/sql/extensions.d.ts +108 -0
  22. package/dist/sql/extensions.js +198 -0
  23. package/dist/sql/remark.d.ts +88 -0
  24. package/dist/sql/remark.js +69 -0
  25. package/dist/sql/renderers.d.ts +44 -0
  26. package/dist/sql/runtime.d.ts +105 -0
  27. package/dist/sql/runtimeConfig.d.ts +80 -0
  28. package/dist/sql/styles.d.ts +6 -0
  29. package/dist/toc-toggle/TocToggle.d.ts +46 -0
  30. package/dist/toc-toggle/TocToggle.js +69 -0
  31. package/dist/toc-toggle/client.d.ts +1 -0
  32. package/dist/toc-toggle/client.js +9 -0
  33. package/dist/toc-toggle/plugin.d.ts +36 -0
  34. package/dist/toc-toggle/plugin.js +13 -0
  35. package/dist/types.d.ts +42 -0
  36. package/package.json +73 -0
  37. package/src/dom.ts +109 -0
  38. package/src/home/DfkFeatures.ts +78 -0
  39. package/src/home/DfkHero.ts +128 -0
  40. package/src/home/DfkNextSteps.ts +73 -0
  41. package/src/home/home.css +520 -0
  42. package/src/home/styles.ts +28 -0
  43. package/src/index.ts +59 -0
  44. package/src/kit.css +19 -0
  45. package/src/register.ts +39 -0
  46. package/src/remark.ts +60 -0
  47. package/src/sql/DfkSql.css +226 -0
  48. package/src/sql/DfkSql.ts +620 -0
  49. package/src/sql/PreviewTabs.ts +169 -0
  50. package/src/sql/client.ts +16 -0
  51. package/src/sql/editor.ts +75 -0
  52. package/src/sql/extensions.ts +470 -0
  53. package/src/sql/remark.ts +213 -0
  54. package/src/sql/renderers.ts +916 -0
  55. package/src/sql/runtime.ts +348 -0
  56. package/src/sql/runtimeConfig.ts +249 -0
  57. package/src/sql/sql.css +397 -0
  58. package/src/sql/styles.ts +24 -0
  59. package/src/theme/tokens.css +75 -0
  60. package/src/toc-toggle/TocToggle.css +69 -0
  61. package/src/toc-toggle/TocToggle.ts +172 -0
  62. package/src/toc-toggle/client.ts +20 -0
  63. package/src/toc-toggle/plugin.ts +54 -0
  64. package/src/types.ts +47 -0
  65. package/src/vite-env.d.ts +8 -0
@@ -0,0 +1,10 @@
1
+ import 'iconify-icon';
2
+ /**
3
+ * Defines the `dfk-*` custom elements. Idempotent and SSR-safe: it is a no-op
4
+ * outside the browser, and re-running it never throws
5
+ * "already been registered with the custom element registry".
6
+ *
7
+ * Call it once at module scope in the docs site (and anywhere else that uses
8
+ * the components); importing the classes alone does not register anything.
9
+ */
10
+ export declare function registerDfkElements(): void;
@@ -0,0 +1,21 @@
1
+ import type { Plugin } from 'unified';
2
+ /**
3
+ * Replaces a build-time version placeholder in the docs tree.
4
+ *
5
+ * Written as a plain text substitution so it can live in a shared package: the
6
+ * version value is passed in by the consuming site (it is the one thing that is
7
+ * *not* shared), and only "pure text" carriers are touched —
8
+ * `text` / `inlineCode` / `code` nodes. MDX expression nodes (`{expr}`) and ESM
9
+ * nodes are deliberately left alone, so this never interferes with MDX's own
10
+ * evaluation.
11
+ *
12
+ * This is Node-side build code: it must not touch `window` / `document`.
13
+ */
14
+ export declare const DEFAULT_VERSION_PLACEHOLDER = "{{DUCKFN_VERSION}}";
15
+ export interface VersionPlaceholderOptions {
16
+ /** The real version string to substitute in, e.g. `0.0.13`. */
17
+ version: string;
18
+ /** Override the token if a site uses a different one. */
19
+ placeholder?: string;
20
+ }
21
+ export declare const remarkVersionPlaceholder: Plugin<[VersionPlaceholderOptions]>;
package/dist/remark.js ADDED
@@ -0,0 +1,15 @@
1
+ //#region src/remark.ts
2
+ var e = "{{DUCKFN_VERSION}}", t = /* @__PURE__ */ new Set([
3
+ "text",
4
+ "inlineCode",
5
+ "code"
6
+ ]), n = ({ version: n, placeholder: r = e }) => (e) => {
7
+ let i = (e) => {
8
+ if (typeof e != "object" || !e) return;
9
+ let a = e, o = a.value;
10
+ typeof a.type == "string" && typeof o == "string" && t.has(a.type) && o.includes(r) && (a.value = o.split(r).join(n)), Array.isArray(a.children) && a.children.forEach(i);
11
+ };
12
+ i(e);
13
+ };
14
+ //#endregion
15
+ export { e as DEFAULT_VERSION_PLACEHOLDER, n as remarkVersionPlaceholder };
@@ -0,0 +1,106 @@
1
+ //#region src/sql/runtimeConfig.ts
2
+ var e = "dfk-sql-runtime", t = /^[a-z][a-z0-9_]*$/i, n = /^https?:\/\/[^\s'";`<>\\]+$/i, r = /* @__PURE__ */ new Set(["community", "core"]), i = /^[\w.-]+\/[\w.-]+$/, a = /^[^\s'";`<>\\?#]+$/, o = /^[a-z][a-z0-9+.-]*:/i;
3
+ function s(e) {
4
+ return /^https?:\/\//i.test(e);
5
+ }
6
+ function c(e) {
7
+ let t = e.split(/[?#]/, 1)[0], n = t.slice(t.lastIndexOf("/") + 1), r = n.indexOf(".");
8
+ return r === -1 ? n : n.slice(0, r);
9
+ }
10
+ function l(e) {
11
+ if (typeof e == "string") return h(e, "A preload entry");
12
+ if (typeof e != "object" || !e || Array.isArray(e)) throw Error(`A preload entry must be a string, {name, repository?} or {url, release?}: ${_(e)}`);
13
+ let t = e, n = t.name !== void 0;
14
+ if (n === (t.url !== void 0)) throw Error(`A preload entry needs exactly one of \`name\` (load by name) or \`url\` (load a file): ${_(e)}`);
15
+ return n ? d(t) : f(t);
16
+ }
17
+ function u(e) {
18
+ if (typeof e != "object" || !e || Array.isArray(e)) throw Error(`Expected an object: ${_(e)}`);
19
+ let t = e;
20
+ g(t, ["allowUnsignedExtensions", "preload"], "The config");
21
+ let { allowUnsignedExtensions: n } = t;
22
+ if (n !== void 0 && typeof n != "boolean") throw Error(`\`allowUnsignedExtensions\` must be a boolean: ${_(n)}`);
23
+ let r = t.preload ?? [];
24
+ if (!Array.isArray(r)) throw Error(`\`preload\` must be an array: ${_(r)}`);
25
+ let i = r.map((e, t) => {
26
+ try {
27
+ return l(e);
28
+ } catch (e) {
29
+ throw Error(`preload[${t}]: ${v(e)}`);
30
+ }
31
+ });
32
+ return n === void 0 ? { preload: i } : {
33
+ allowUnsignedExtensions: n,
34
+ preload: i
35
+ };
36
+ }
37
+ function d(e) {
38
+ g(e, ["name", "repository"], "A name preload entry");
39
+ let t = h(e.name, "`name`"), { repository: i } = e;
40
+ if (i === void 0) return { name: t };
41
+ if (typeof i == "string") {
42
+ let e = i.toLowerCase();
43
+ if (r.has(e)) return {
44
+ name: t,
45
+ repository: e
46
+ };
47
+ if (n.test(i)) return {
48
+ name: t,
49
+ repository: i
50
+ };
51
+ }
52
+ throw Error(`\`repository\` must be 'community', 'core' or an http(s) URL without quotes: ${_(i)}`);
53
+ }
54
+ function f(e) {
55
+ g(e, ["url", "release"], "A url preload entry");
56
+ let n = e.url;
57
+ if (typeof n != "string" || !a.test(n)) throw Error(`\`url\` must be a file path or http(s) URL without quotes, whitespace or backslashes: ${_(n)}`);
58
+ let r = s(n);
59
+ if (!r && o.test(n)) throw Error(`\`url\` must be a site-relative path or an http(s) URL: ${_(n)}`);
60
+ let i = r ? n : p(n);
61
+ if (!t.test(c(i))) throw Error(`The last path segment of \`url\` must start with the extension name before its first dot (that base names the entry symbol, e.g. duckfn.duckdb_extension.wasm): ${i}`);
62
+ let { release: l } = e;
63
+ if (l === void 0) return { url: i };
64
+ if (r) throw Error(`\`release\` copies the asset into the site's static directory, so \`url\` has to be site-relative: ${i}`);
65
+ return {
66
+ url: i,
67
+ release: m(l)
68
+ };
69
+ }
70
+ function p(e) {
71
+ if (e.startsWith("//")) throw Error(`Protocol-relative URLs are not supported: ${e}`);
72
+ let t = e.replace(/^\/+/, ""), n = t.split("/");
73
+ if (t === "" || n.some((e) => e === "" || e === "." || e === "..")) throw Error(`\`url\` must be a plain site-relative file path: ${e}`);
74
+ return t;
75
+ }
76
+ function m(e) {
77
+ if (typeof e != "object" || !e || Array.isArray(e)) throw Error(`\`release\` must be {repository, asset}: ${_(e)}`);
78
+ let t = e;
79
+ g(t, ["repository", "asset"], "`release`");
80
+ let { repository: n, asset: r } = t;
81
+ if (typeof n != "string" || !i.test(n)) throw Error(`\`release.repository\` must look like 'owner/name': ${_(n)}`);
82
+ if (typeof r != "string" || !a.test(r) || r.includes("/")) throw Error(`\`release.asset\` is the release asset's file name (no slashes): ${_(r)}`);
83
+ return {
84
+ repository: n,
85
+ asset: r
86
+ };
87
+ }
88
+ function h(e, n) {
89
+ if (typeof e != "string" || !t.test(e)) throw Error(`${n} must be a bare SQL identifier ([a-z][a-z0-9_]*): ${_(e)}`);
90
+ return e;
91
+ }
92
+ function g(e, t, n) {
93
+ for (let r of Object.keys(e)) if (!t.includes(r)) throw Error(`${n} has an unknown key \`${r}\` (expected: ${t.join(" / ")})`);
94
+ }
95
+ function _(e) {
96
+ try {
97
+ return JSON.stringify(e) ?? String(e);
98
+ } catch {
99
+ return String(e);
100
+ }
101
+ }
102
+ function v(e) {
103
+ return e instanceof Error ? e.message : String(e);
104
+ }
105
+ //#endregion
106
+ export { c as a, u as c, n as i, t as n, s as o, r, l as s, e as t };
@@ -0,0 +1,7 @@
1
+ import { HTMLElementBase } from '../dom';
2
+ export declare class DfkSql extends HTMLElementBase {
3
+ #private;
4
+ constructor();
5
+ connectedCallback(): void;
6
+ disconnectedCallback(): void;
7
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Every renderer's result shell: a tab strip plus the panels behind it.
3
+ *
4
+ * The strip is one tab per preview row plus a `Table` tab that always comes
5
+ * **last**; a renderer with a single view passes no items at all, so a plain
6
+ * table result is a strip holding nothing but that trailing `Table` tab. Every
7
+ * result therefore has the same chrome — which is where the fullscreen toggle
8
+ * lives.
9
+ *
10
+ * Retained mode: every button and panel is built in the constructor and held in
11
+ * a field. Activating a tab mutates the nodes it owns (`hidden`, `classList`,
12
+ * `aria-selected`, `tabIndex`) — there is no rebuild, and a panel is filled the
13
+ * first time it is shown rather than up front.
14
+ *
15
+ * The table panel is mounted on first activation on purpose: VTable measures
16
+ * its container when it is constructed, and a `hidden` panel measures to zero.
17
+ */
18
+ /** One preview row: its tab label and how to fill its panel. */
19
+ export interface PreviewTabItem {
20
+ label: string;
21
+ mount(panel: HTMLElement): void;
22
+ }
23
+ /** What `PreviewTabs` needs from the caller to own the trailing table tab. */
24
+ export interface PreviewTableHandle {
25
+ dispose(): void;
26
+ }
27
+ export declare class PreviewTabs {
28
+ #private;
29
+ /**
30
+ * @param corner Node parked at the right end of the strip, outside the
31
+ * scrolling tab list. `<dfk-sql>` passes its fullscreen toggle: it owns that
32
+ * button's state, so it owns the node and only lends it here.
33
+ */
34
+ constructor(host: HTMLElement, items: readonly PreviewTabItem[], tableLabel: string, mountTable: (panel: HTMLElement) => Promise<PreviewTableHandle>, corner?: HTMLElement);
35
+ /** Releases the table (if it was ever shown) and empties every panel. */
36
+ dispose(): void;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { t as e } from "../register-DKLiYs-F.js";
2
+ //#region src/sql/client.ts
3
+ typeof window < "u" && e();
4
+ //#endregion
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The CodeMirror 6 editor that *is* the code view of a runnable SQL block.
3
+ *
4
+ * Every CodeMirror module arrives through dynamic `import()` inside
5
+ * {@link mountSqlEditor}: a page full of SQL examples pays nothing for the
6
+ * editor on its critical path, and Docusaurus' Node prerender never evaluates
7
+ * any of it.
8
+ */
9
+ export interface SqlEditor {
10
+ getValue(): string;
11
+ setValue(value: string): void;
12
+ /** Soft-wraps long lines, or stops wrapping them (the block's wrap toggle). */
13
+ setWrap(wrapped: boolean): void;
14
+ destroy(): void;
15
+ }
16
+ export declare function mountSqlEditor(container: HTMLElement, value: string, onChange: (value: string) => void): Promise<SqlEditor>;
@@ -0,0 +1,108 @@
1
+ import { type PreloadEntry } from './runtimeConfig';
2
+ /**
3
+ * `duckfn-docs-kit/sql/extensions` — the Docusaurus plugin behind the
4
+ * runnable-SQL examples' extensions.
5
+ *
6
+ * It does two things, both driven by one ordered `preload` list configured in
7
+ * `docusaurus.config.ts`:
8
+ *
9
+ * 1. At startup (dev server and build alike) it fetches every `release`
10
+ * source from that GitHub repository's **latest** release into the site's
11
+ * `static/` directory, where the file is then served same-origin.
12
+ * Downloads are cached locally and only re-fetched when the release
13
+ * asset's sha256 differs (GitHub's own `digest` field is the comparison).
14
+ * 2. It injects the resolved preload list as one JSON `<script>` tag into
15
+ * every page; the browser runtime (`sql/runtime`) reads it once and loads
16
+ * the extensions in order while the DuckDB instance initialises.
17
+ *
18
+ * It also injects the kit's client bootstrap (`sql/client.ts`) on every page
19
+ * through `getClientModules()`: docs pages never import the kit's React tree,
20
+ * so the `dfk-*` element registration has to come from a client module — and
21
+ * it comes from here, not from a file the site keeps by hand.
22
+ *
23
+ * The site-relative `url` of an entry doubles as the fetch destination *and*
24
+ * the runtime path, so the two can never drift: with baseUrl `/duckfn/`,
25
+ * `duckdb-extensions/duckfn.duckdb_extension.wasm` lands in
26
+ * `<siteDir>/static/duckdb-extensions/duckfn.duckdb_extension.wasm` and is
27
+ * preloaded from `/duckfn/duckdb-extensions/duckfn.duckdb_extension.wasm`.
28
+ * (Docusaurus serves `static/` under each locale's baseUrl, so the localized
29
+ * value from the plugin context is the right prefix for every build.)
30
+ *
31
+ * This is Node-side build code: it must not import any browser module, and
32
+ * the browser side must not import this file (the shared contract lives in
33
+ * `./runtimeConfig`).
34
+ */
35
+ /**
36
+ * The slice of Docusaurus' plugin API this module touches, typed structurally
37
+ * instead of importing `@docusaurus/types`: the kit stays free of Docusaurus
38
+ * dependencies, and the consuming site's own typecheck proves compatibility
39
+ * when the returned module lands in its `plugins` list.
40
+ */
41
+ export interface DfkExtensionsContext {
42
+ siteDir: string;
43
+ siteConfig: {
44
+ baseUrl: string;
45
+ };
46
+ }
47
+ interface DfkHtmlTag {
48
+ tagName: string;
49
+ attributes: Record<string, string>;
50
+ innerHTML: string;
51
+ }
52
+ export interface DfkExtensionsPlugin {
53
+ name: string;
54
+ getClientModules(): string[];
55
+ loadContent(): Promise<void>;
56
+ injectHtmlTags(): {
57
+ headTags: DfkHtmlTag[];
58
+ };
59
+ }
60
+ /** The plugin module Docusaurus calls with its `LoadContext` and options. */
61
+ export type DfkExtensionsPluginModule = (context: DfkExtensionsContext) => DfkExtensionsPlugin;
62
+ /** Options for {@link dfkExtensions}. */
63
+ export interface DfkExtensionsOptions {
64
+ /**
65
+ * The ordered list of extensions every page preloads while the shared
66
+ * DuckDB instance initialises — official names, `{name, repository}`
67
+ * entries, or `{url}` files (optionally fetched from a GitHub release).
68
+ * See `PreloadEntry` in `./runtimeConfig` for the exact shapes.
69
+ */
70
+ preload?: PreloadEntry[];
71
+ /**
72
+ * Let `LOAD` accept extensions whose signature does not verify. Needed
73
+ * whenever a preloaded file is not signed with DuckDB's keys — the GitHub
74
+ * release assets of a third-party extension are not — and merged with the
75
+ * per-block setting of whichever block initialises the runtime first.
76
+ */
77
+ allowUnsignedExtensions?: boolean;
78
+ /**
79
+ * Where release assets are cached between builds. Relative paths resolve
80
+ * against the Docusaurus site directory; defaults to
81
+ * `<siteDir>/.cache/duckfn-docs-kit`.
82
+ */
83
+ cacheDir?: string;
84
+ /** A GitHub token for the release API (rate limits, private repositories). Defaults to `process.env.GITHUB_TOKEN`. */
85
+ token?: string;
86
+ }
87
+ /**
88
+ * Builds the Docusaurus plugin. Usage in `docusaurus.config.ts`:
89
+ *
90
+ * ```ts
91
+ * import {dfkExtensions} from 'duckfn-docs-kit/sql/extensions';
92
+ *
93
+ * plugins: [
94
+ * dfkExtensions({
95
+ * allowUnsignedExtensions: true,
96
+ * preload: [
97
+ * {name: 'inet'},
98
+ * {
99
+ * url: 'duckdb-extensions/duckfn.duckdb_extension.wasm',
100
+ * release: {repository: 'shijianjs/duckfn', asset: 'duckfn-wasm_eh.duckdb_extension.wasm'},
101
+ * },
102
+ * ],
103
+ * }),
104
+ * ],
105
+ * ```
106
+ */
107
+ export declare function dfkExtensions(options?: DfkExtensionsOptions): DfkExtensionsPluginModule;
108
+ export {};
@@ -0,0 +1,198 @@
1
+ import { o as e, s as t, t as n } from "../runtimeConfig-Bokbb8VH.js";
2
+ import { createRequire as r } from "node:module";
3
+ import i from "node:path";
4
+ import { createHash as a } from "node:crypto";
5
+ import { mkdir as o, readFile as s, rename as c, stat as l, unlink as u, writeFile as d } from "node:fs/promises";
6
+ //#region src/sql/extensions.ts
7
+ var f = "[dfk-extensions] ";
8
+ function p(a = {}) {
9
+ let { allowUnsignedExtensions: o = !1, preload: s = [], cacheDir: c, token: l = process.env.GITHUB_TOKEN } = a, u = null, d = () => (u ??= {
10
+ ...o ? { allowUnsignedExtensions: !0 } : {},
11
+ preload: s.map((e, n) => {
12
+ try {
13
+ return t(e);
14
+ } catch (e) {
15
+ throw Error(`duckfn-docs-kit preload[${n}]: ${M(e)}`);
16
+ }
17
+ })
18
+ }, u);
19
+ return (t) => ({
20
+ name: "dfk-extensions",
21
+ getClientModules() {
22
+ return [r(i.join(t.siteDir, "package.json")).resolve("duckfn-docs-kit/sql/client")];
23
+ },
24
+ async loadContent() {
25
+ let n = d(), { siteDir: r } = t, a = i.resolve(r, c ?? ".cache/duckfn-docs-kit");
26
+ for (let t of n.preload) {
27
+ if (typeof t == "string" || !("url" in t)) continue;
28
+ let n = i.join(r, "static", t.url), o = `static/${t.url}`;
29
+ if (t.release) await m(t.release, {
30
+ dest: n,
31
+ label: o,
32
+ cacheRoot: a,
33
+ token: l
34
+ });
35
+ else if (!e(t.url) && !await A(n)) throw Error(`${f}preload url "${t.url}" has no file: place the file under static/${t.url}, or point the entry at a GitHub release source`);
36
+ }
37
+ },
38
+ injectHtmlTags() {
39
+ let e = d();
40
+ if (!e.allowUnsignedExtensions && e.preload.length === 0) return { headTags: [] };
41
+ let r = {
42
+ ...e,
43
+ preload: e.preload.map((e) => O(e, t.siteConfig.baseUrl))
44
+ };
45
+ return { headTags: [{
46
+ tagName: "script",
47
+ attributes: {
48
+ id: n,
49
+ type: "application/json"
50
+ },
51
+ innerHTML: JSON.stringify(r).replaceAll("<", "\\u003c")
52
+ }] };
53
+ }
54
+ });
55
+ }
56
+ async function m(e, t) {
57
+ let { dest: n, label: r, cacheRoot: i, token: a } = t, o = (t) => console.log(`${f}${e.repository}#${e.asset}: ${t}`), s = await h(e, {
58
+ cacheRoot: i,
59
+ token: a,
60
+ log: o
61
+ });
62
+ await E(n, s.bytes, s.sha256, o, r);
63
+ }
64
+ async function h(e, t) {
65
+ let { cacheRoot: n, token: r, log: i } = t, a = C(n, e), o = await w(a), s = (t) => {
66
+ if (!o) throw t;
67
+ return console.warn(`${f}${e.repository}#${e.asset}: ${M(t)} — using the cached copy (${j(o.sha256)})`), o;
68
+ }, c;
69
+ try {
70
+ c = await _(e.repository, r);
71
+ } catch (e) {
72
+ return s(e);
73
+ }
74
+ let l = v(c, e), u = x(l.digest);
75
+ if (o && u !== null && o.sha256 === u) return i(`up to date (${j(u)})`), o;
76
+ i(`downloading${c.tag_name ? ` ${c.tag_name}` : ""}…`);
77
+ let d;
78
+ try {
79
+ d = await y(l, r);
80
+ } catch (e) {
81
+ return s(e);
82
+ }
83
+ let p = S(d);
84
+ if (u !== null && p !== u) throw Error(`${f}${e.repository}#${e.asset}: sha256 mismatch, expected ${u}, downloaded ${p}`);
85
+ let m = c.tag_name ?? "";
86
+ return await T(a, d, p, m), i(`cached ${j(p)}`), {
87
+ bytes: d,
88
+ sha256: p,
89
+ tag: m
90
+ };
91
+ }
92
+ var g = "https://api.github.com";
93
+ async function _(e, t) {
94
+ let n = b(t, "application/vnd.github+json"), r = await fetch(`${g}/repos/${e}/releases/latest`, { headers: n });
95
+ if (!r.ok) throw Error(`GitHub API ${r.status} for ${e}: ${(await r.text()).slice(0, 200)}`);
96
+ return await r.json();
97
+ }
98
+ function v(e, t) {
99
+ let n = e.assets ?? [], r = n.find((e) => e.name === t.asset);
100
+ if (!r || typeof r.browser_download_url != "string") {
101
+ let r = n.map((e) => e.name).filter(Boolean).join(", ") || "none";
102
+ throw Error(`${f}release ${e.tag_name ?? "?"} of ${t.repository} has no asset "${t.asset}" (available: ${r})`);
103
+ }
104
+ return r;
105
+ }
106
+ async function y(e, t) {
107
+ let n = await fetch(e.browser_download_url, {
108
+ headers: b(t, "application/octet-stream"),
109
+ redirect: "follow"
110
+ });
111
+ if (!n.ok) throw Error(`asset download failed with ${n.status}: ${e.browser_download_url}`);
112
+ return Buffer.from(await n.arrayBuffer());
113
+ }
114
+ function b(e, t) {
115
+ let n = {
116
+ Accept: t,
117
+ "User-Agent": "duckfn-docs-kit",
118
+ "X-GitHub-Api-Version": "2022-11-28"
119
+ };
120
+ return e && (n.Authorization = `Bearer ${e}`), n;
121
+ }
122
+ function x(e) {
123
+ if (typeof e != "string") return null;
124
+ let [t, n] = e.split(":");
125
+ return t !== "sha256" || n === void 0 || !/^[0-9a-f]{64}$/i.test(n) ? null : n.toLowerCase();
126
+ }
127
+ function S(e) {
128
+ return a("sha256").update(e).digest("hex");
129
+ }
130
+ function C(e, t) {
131
+ let n = i.join(e, t.repository.replace("/", "__"));
132
+ return {
133
+ file: i.join(n, t.asset),
134
+ meta: i.join(n, `${t.asset}.json`)
135
+ };
136
+ }
137
+ async function w(e) {
138
+ try {
139
+ let [t, n] = await Promise.all([s(e.file), s(e.meta, "utf8")]), r = JSON.parse(n);
140
+ return typeof r.sha256 != "string" || !/^[0-9a-f]{64}$/.test(r.sha256) ? null : {
141
+ bytes: t,
142
+ sha256: r.sha256,
143
+ tag: r.tag ?? ""
144
+ };
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+ async function T(e, t, n, r) {
150
+ await o(i.dirname(e.file), { recursive: !0 }), await D(e.file, t);
151
+ let a = {
152
+ sha256: n,
153
+ tag: r,
154
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
155
+ };
156
+ await D(e.meta, Buffer.from(`${JSON.stringify(a, null, 2)}\n`));
157
+ }
158
+ async function E(e, t, n, r, a) {
159
+ let c = await s(e).catch(() => null);
160
+ if (c && S(c) === n) {
161
+ r(`${a} up to date`);
162
+ return;
163
+ }
164
+ await o(i.dirname(e), { recursive: !0 }), await D(e, t), r(`wrote ${a} (${t.length} bytes)`);
165
+ }
166
+ async function D(e, t) {
167
+ let n = `${e}.tmp-${process.pid.toString(36)}-${Date.now().toString(36)}`;
168
+ await d(n, t);
169
+ for (let t = 1;; t += 1) try {
170
+ await c(n, e);
171
+ return;
172
+ } catch (e) {
173
+ if (t >= 3) throw await u(n).catch(() => void 0), e;
174
+ await new Promise((e) => setTimeout(e, 50 * t));
175
+ }
176
+ }
177
+ function O(t, n) {
178
+ return typeof t == "string" || !("url" in t) ? t : e(t.url) ? { url: t.url } : { url: k(n, t.url) };
179
+ }
180
+ function k(e, t) {
181
+ let n = e.replace(/^\/+|\/+$/g, "");
182
+ return n === "" ? `/${t}` : `/${n}/${t}`;
183
+ }
184
+ async function A(e) {
185
+ try {
186
+ return await l(e), !0;
187
+ } catch {
188
+ return !1;
189
+ }
190
+ }
191
+ function j(e) {
192
+ return e.slice(0, 12);
193
+ }
194
+ function M(e) {
195
+ return e instanceof Error ? e.message : String(e);
196
+ }
197
+ //#endregion
198
+ export { p as dfkExtensions };
@@ -0,0 +1,88 @@
1
+ import type { Plugin } from 'unified';
2
+ /**
3
+ * Turns a fenced SQL block whose metastring is a JSON config with
4
+ * `{"type":"duckfn", …}` into a `<dfk-sql>` custom element, so the docs site
5
+ * can render it as a runnable example.
6
+ *
7
+ * The original `code` node is kept as the element's *child*: Docusaurus'
8
+ * `codeCompatPlugin` still stamps `metastring` onto it and the classic theme
9
+ * renders it as a regular `@theme/CodeBlock`. That child is the block's
10
+ * prerendered text and nothing else — `<dfk-sql>` has no default slot and hides
11
+ * unslotted children through CSS, because the code view is a CodeMirror editor.
12
+ * The SQL text and the parsed config travel as string attributes (`sql` /
13
+ * `config`) — React 19 reconciles string props onto custom elements as
14
+ * attributes, so they survive prerendering and hydration.
15
+ *
16
+ * The element also gets a prerendered *placeholder* (see {@link skeleton}): one
17
+ * bar per line of the SQL, which is what the reader sees before this package's
18
+ * JS arrives and `<dfk-sql>` upgrades.
19
+ *
20
+ * Unlike Docusaurus' own `key=value` metastring format, the config here is
21
+ * JSON, which allows nested fields (`option: {…}`) for future renderers.
22
+ *
23
+ * This is Node-side build code: it must not touch `window` / `document`, and it
24
+ * must not import any browser module (type-only imports are fine).
25
+ */
26
+ /** The JSON payload written after the info string of a runnable SQL block. */
27
+ export interface RunnableSqlConfig {
28
+ /** Marks the block as a duckfn runnable example; the only value today. */
29
+ type: 'duckfn';
30
+ /**
31
+ * Which result renderer to use. Defaults to `table` at runtime, except that a
32
+ * single-column single-row result degrades to `text` (a bare scalar reads
33
+ * better as a line than as a 1×1 table).
34
+ *
35
+ * `html` and `iframe` are the same renderer: both sandbox the markup in an
36
+ * iframe, so scripts run with an opaque origin.
37
+ */
38
+ show?: 'table' | 'html' | 'iframe' | 'svg' | 'text';
39
+ /**
40
+ * The column holding the markup, for the preview renderers. A single-column
41
+ * result is unambiguous and is used as-is.
42
+ */
43
+ field?: string;
44
+ /** The column to label each preview tab with; falls back to `Row N`. */
45
+ tab_name?: string;
46
+ /** Presentation knobs for the preview renderers; see `option.width` etc. */
47
+ option?: {
48
+ /** CSS length for the preview box (e.g. `'100%'`, `'640px'`). */
49
+ width?: string;
50
+ height?: string;
51
+ /**
52
+ * `sandbox` tokens for the `iframe` renderer, replacing the default
53
+ * `allow-scripts`. Only set this to *widen* what the report may do — the
54
+ * default deliberately omits `allow-same-origin`.
55
+ */
56
+ sandbox?: string;
57
+ };
58
+ /**
59
+ * duckfn community extensions to `LOAD` before running the block. The kit
60
+ * never hard-codes an extension name; the docs source names what it needs.
61
+ */
62
+ extensions?: string[];
63
+ /** A repository serving the extensions, instead of the DuckDB default. */
64
+ repository?: string;
65
+ /**
66
+ * Allows `LOAD` to accept extensions without a valid signature. Opt-in
67
+ * per block, and only meaningful for the *first* block that initialises the
68
+ * shared runtime — `open()` fixes it for the instance.
69
+ */
70
+ allowUnsignedExtensions?: boolean;
71
+ /**
72
+ * Forward-compatible fields: the remark plugin passes the whole object
73
+ * through untouched, so a newer kit version can read new keys without the
74
+ * docs source changing.
75
+ */
76
+ [key: string]: unknown;
77
+ }
78
+ export interface RunnableSqlOptions {
79
+ /**
80
+ * Reserved for future remark-level options (kept so sites passing an empty
81
+ * options object keep typechecking). Site-wide extension preloading is
82
+ * configured on the `dfkExtensions` plugin (`sql/extensions`), not here.
83
+ */
84
+ [key: string]: unknown;
85
+ }
86
+ /** The custom element the plugin emits; must match `register.ts`. */
87
+ export declare const DFK_SQL_TAG = "dfk-sql";
88
+ export declare const remarkRunnableSql: Plugin<[RunnableSqlOptions?]>;