diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acddacbd..fddf23b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,22 +2,134 @@ name: CI on: push: + branches: [main] pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + # --- primary build + unit/golden tests --- build-and-test: + name: build + test (node ${{ matrix.node }}) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - node-version: [20, 22] - name: Node ${{ matrix.node-version }} + node: [20, 22] steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: ${{ matrix.node }} cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm build - run: pnpm test + + # --- create-dql-app smoke test --- + # Guards the "5 minutes to first dashboard" demo gate. + scaffold-smoke: + name: create-dql-app smoke + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: node packages/create-dql-app/test/smoke.mjs + + # --- docs build + internal link check --- + docs: + name: docs build + link check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: node apps/docs/scripts/link-check.mjs + - run: pnpm -F @duckcodeailabs/docs build + + # --- dql fmt --check across the repo --- + # Gates canonical .dql serialization — catches whitespace churn before + # it reaches main. + format-check: + name: dql fmt --check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm -F @duckcodeailabs/dql-cli build + - name: dql fmt --check + run: | + set -e + shopt -s globstar nullglob + files=(packages/create-dql-app/templates/**/*.dql) + if [ ${#files[@]} -eq 0 ]; then + echo "no .dql files to check — skipping" + exit 0 + fi + for f in "${files[@]}"; do + node apps/cli/dist/index.js fmt --check "$f" + done + + # --- 4,000-model stress test (perf gate) --- + # Gates: cold <30s, warm <2s. See docs.duckcode.ai/contribute/testing. + stress-test: + name: stress (4k-model synthetic project) + runs-on: ubuntu-latest + # Only run on main branch pushes — expensive. + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - name: generate 4,000-model synthetic dbt project + run: node scripts/bench/gen-dbt-project.mjs --models 4000 --out /tmp/stress + - name: run benchmark (gates cold <30s, warm <2s) + run: DQL_CLI="node $PWD/apps/cli/dist/index.js" node scripts/bench/run-bench.mjs /tmp/stress + + # --- E2E Playwright (notebook happy path) --- + # Runs the Jaffle Shop path in a headless browser. On PRs that don't + # touch the notebook app, we skip to keep CI fast. + e2e: + name: playwright e2e + runs-on: ubuntu-latest + if: github.event_name == 'push' || contains(github.event.pull_request.changed_files, 'apps/dql-notebook') + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm -F @duckcodeailabs/dql-notebook-app exec playwright install --with-deps chromium + continue-on-error: true + - run: pnpm -F @duckcodeailabs/dql-notebook-app test:e2e + continue-on-error: true # gate hardens to `false` once the suite lands + + # --- summary gate for branch protection --- + ci-ok: + name: ci-ok + runs-on: ubuntu-latest + needs: [build-and-test, scaffold-smoke, docs, format-check] + steps: + - run: echo "All required CI gates passed." diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..9b1dc99f --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,54 @@ +name: Deploy docs + +on: + push: + branches: [main] + paths: + - 'apps/docs/**' + - '.github/workflows/deploy-docs.yml' + workflow_dispatch: + +concurrency: + group: deploy-docs + cancel-in-progress: true + +jobs: + deploy: + name: Vercel production + runs-on: ubuntu-latest + if: github.repository == 'duckcode-ai/dql' + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_DOCS_PROJECT_ID }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: { version: 9 } + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Guard — secrets present + run: | + if [ -z "$VERCEL_TOKEN" ] || [ -z "$VERCEL_ORG_ID" ] || [ -z "$VERCEL_PROJECT_ID" ]; then + echo "::warning::Vercel secrets not configured; skipping deploy." + exit 0 + fi + + - run: pnpm install --frozen-lockfile + - run: pnpm -F @duckcodeailabs/docs build + + - name: Install Vercel CLI + run: pnpm add -g vercel@latest + + - name: Pull Vercel env + working-directory: apps/docs + run: vercel pull --yes --environment=production --token="$VERCEL_TOKEN" + + - name: Deploy + working-directory: apps/docs + run: | + vercel build --prod --token="$VERCEL_TOKEN" + vercel deploy --prebuilt --prod --token="$VERCEL_TOKEN" diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml new file mode 100644 index 00000000..7ea0f788 --- /dev/null +++ b/.github/workflows/release-desktop.yml @@ -0,0 +1,75 @@ +name: Release (desktop binaries) + +on: + push: + tags: ['v*.*.*'] + workflow_dispatch: + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - { os: macos-14, target: aarch64-apple-darwin, rust_target: aarch64-apple-darwin } + - { os: macos-13, target: x86_64-apple-darwin, rust_target: x86_64-apple-darwin } + - { os: ubuntu-22.04, target: x86_64-unknown-linux-gnu, rust_target: x86_64-unknown-linux-gnu } + - { os: windows-latest, target: x86_64-pc-windows-msvc, rust_target: x86_64-pc-windows-msvc } + runs-on: ${{ matrix.os }} + name: desktop (${{ matrix.target }}) + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: { version: 9 } + - uses: actions/setup-node@v4 + with: { node-version: 20, cache: pnpm } + - uses: dtolnay/rust-toolchain@stable + with: { targets: ${{ matrix.rust_target }} } + + - name: Install Linux deps + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf + + - run: pnpm install --frozen-lockfile + + - name: Build notebook frontend + run: pnpm -F @duckcodeailabs/dql-notebook-app build + + - name: Build Tauri bundle + working-directory: apps/desktop + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: pnpm tauri build --target ${{ matrix.rust_target }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: desktop-${{ matrix.target }} + path: | + apps/desktop/src-tauri/target/${{ matrix.rust_target }}/release/bundle/**/*.dmg + apps/desktop/src-tauri/target/${{ matrix.rust_target }}/release/bundle/**/*.AppImage + apps/desktop/src-tauri/target/${{ matrix.rust_target }}/release/bundle/**/*.deb + apps/desktop/src-tauri/target/${{ matrix.rust_target }}/release/bundle/**/*.rpm + apps/desktop/src-tauri/target/${{ matrix.rust_target }}/release/bundle/**/*.msi + + publish: + name: attach to GitHub Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: { path: artifacts } + - uses: softprops/action-gh-release@v2 + with: + files: artifacts/**/* + fail_on_unmatched_files: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6ee24d3..20f8957d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,3 +64,37 @@ jobs: path: apps/vscode-extension/*.vsix - if: ${{ env.VSCE_PAT != '' && (startsWith(github.ref, 'refs/tags/v') || inputs.publish_extension == true) }} run: pnpm --filter dql-language-support publish:vsce + + publish-homebrew: + # Open a PR against the tap repo with the regenerated formula. We don't + # push directly — the tap repo gets human review. + if: github.repository == 'duckcode-ai/dql' && startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + needs: publish-packages + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Resolve version from tag + id: v + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + - name: Render Homebrew formula + run: node packaging/homebrew/publish.mjs "${{ steps.v.outputs.version }}" + - name: Open PR on tap repo + env: + GH_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + if: env.GH_TOKEN != '' + run: | + set -e + git clone https://x-access-token:${GH_TOKEN}@github.com/duckcode-ai/homebrew-dql.git /tmp/tap + cp packaging/homebrew/dql.rb /tmp/tap/Formula/dql.rb + cd /tmp/tap + git config user.name "duckcode-release-bot" + git config user.email "release@duckcode.ai" + branch="release/v${{ steps.v.outputs.version }}" + git checkout -b "$branch" + git add Formula/dql.rb + git commit -m "dql ${{ steps.v.outputs.version }}" + git push -u origin "$branch" + gh pr create --title "dql ${{ steps.v.outputs.version }}" --body "Automated release PR for \`dql@${{ steps.v.outputs.version }}\`." diff --git a/.gitignore b/.gitignore index 2858b45f..0ae8109b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ dist npm-debug.log* pnpm-debug.log* jaffle-shop/ +!packages/create-dql-app/templates/jaffle-shop/ .claude/worktrees/ diff --git a/apps/cli/package.json b/apps/cli/package.json index dcb75eaf..924ae178 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-cli", - "version": "0.10.1", + "version": "1.0.1", "description": "Public CLI for parsing, formatting, testing, and certifying DQL blocks", "license": "Apache-2.0", "type": "module", diff --git a/apps/cli/src/args.ts b/apps/cli/src/args.ts index f1ca1d25..e526465f 100644 --- a/apps/cli/src/args.ts +++ b/apps/cli/src/args.ts @@ -15,6 +15,7 @@ export interface CLIFlags { template: string; connection: string; skipTests: boolean; + force?: boolean; } export interface ParsedArgs { @@ -42,6 +43,7 @@ export function parseArgs(argv: string[]): ParsedArgs { template: '', connection: '', skipTests: false, + force: false, }; let command: string | null = null; @@ -88,6 +90,8 @@ export function parseArgs(argv: string[]): ParsedArgs { flags.queryOnly = true; } else if (arg === '--skip-tests') { flags.skipTests = true; + } else if (arg === '--force' || arg === '-f') { + flags.force = true; } else if (!command) { command = arg; } else if (!file) { diff --git a/apps/cli/src/commands/compile.ts b/apps/cli/src/commands/compile.ts index a24c3b05..8fc05f84 100644 --- a/apps/cli/src/commands/compile.ts +++ b/apps/cli/src/commands/compile.ts @@ -19,7 +19,7 @@ import { writeFileSync, existsSync, readFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; -import { buildManifest, collectInputFiles, type DQLManifest } from '@duckcodeailabs/dql-core'; +import { buildManifest, collectInputFiles, resolveDbtManifestPath, type DQLManifest } from '@duckcodeailabs/dql-core'; import { ManifestCache } from '@duckcodeailabs/dql-project'; import type { CLIFlags } from '../args.js'; @@ -74,13 +74,9 @@ export async function runCompile( } } catch { /* use default */ } - // Auto-detect dbt manifest if not explicitly provided - if (!dbtManifestPath) { - const defaultDbtPath = join(projectRoot, 'target', 'manifest.json'); - if (existsSync(defaultDbtPath)) { - dbtManifestPath = defaultDbtPath; - } - } + // Resolve via explicit flag → dql.config.json `dbt:` → target/manifest.json + const resolvedDbt = resolveDbtManifestPath(projectRoot, dbtManifestPath); + if (resolvedDbt) dbtManifestPath = resolvedDbt; const noCache = allArgs.includes('--no-cache'); @@ -172,7 +168,31 @@ export async function runCompile( } console.log(`\n Manifest written to: ${manifestPath}`); - console.log(` ${cacheHit ? 'Served from cache' : 'Compiled'} in ${elapsed}ms\n`); + console.log(` ${cacheHit ? 'Served from cache' : 'Compiled'} in ${elapsed}ms`); + + // Surface non-fatal problems — a silent "0 blocks" should never happen again. + const diagnostics = manifest.diagnostics ?? []; + const errs = diagnostics.filter((d) => d.severity === 'error'); + const warns = diagnostics.filter((d) => d.severity === 'warning'); + if (diagnostics.length > 0) { + console.log(''); + if (errs.length > 0) { + console.log(` ${errs.length} error(s):`); + for (const d of errs) { + const where = d.filePath ? `${d.filePath}: ` : ''; + console.log(` ✗ ${where}${d.message}`); + } + } + if (warns.length > 0) { + console.log(` ${warns.length} warning(s):`); + for (const d of warns) { + const where = d.filePath ? `${d.filePath}: ` : ''; + console.log(` ⚠ ${where}${d.message}`); + } + } + if (errs.length > 0) process.exitCode = 1; + } + console.log(''); if (flags.verbose) { // Show block details diff --git a/apps/cli/src/commands/diff.ts b/apps/cli/src/commands/diff.ts new file mode 100644 index 00000000..d3d52fa9 --- /dev/null +++ b/apps/cli/src/commands/diff.ts @@ -0,0 +1,29 @@ +import { readFileSync } from 'node:fs'; +import { diffDQL, renderDiffText } from '@duckcodeailabs/dql-core'; +import type { CLIFlags } from '../args.js'; + +export async function runDiff( + beforePath: string | null, + rest: string[], + flags: CLIFlags, +): Promise { + const afterPath = rest[0]; + if (!beforePath || !afterPath) { + console.error('Usage: dql diff '); + process.exit(1); + } + + const before = readFileSync(beforePath, 'utf-8'); + const after = readFileSync(afterPath, 'utf-8'); + const report = diffDQL(before, after); + + if (flags.format === 'json') { + console.log(JSON.stringify(report, null, 2)); + } else { + console.log(renderDiffText(report)); + } + + // Exit 1 when there are changes — makes it scriptable as a gate + // (`dql diff a b && echo unchanged`), mirroring git-diff and fmt --check. + if (!report.identical) process.exit(1); +} diff --git a/apps/cli/src/commands/fmt.ts b/apps/cli/src/commands/fmt.ts index b56dce84..41cbab52 100644 --- a/apps/cli/src/commands/fmt.ts +++ b/apps/cli/src/commands/fmt.ts @@ -1,10 +1,10 @@ import { readFileSync, writeFileSync } from 'node:fs'; -import { formatDQL } from '@duckcodeailabs/dql-core'; +import { canonicalize } from '@duckcodeailabs/dql-core'; import type { CLIFlags } from '../args.js'; export async function runFmt(filePath: string, flags: CLIFlags): Promise { const source = readFileSync(filePath, 'utf-8'); - const formatted = formatDQL(source); + const formatted = canonicalize(source); const changed = source !== formatted; if (flags.check) { diff --git a/apps/cli/src/commands/init.ts b/apps/cli/src/commands/init.ts index 8d7bdde8..52ab9143 100644 --- a/apps/cli/src/commands/init.ts +++ b/apps/cli/src/commands/init.ts @@ -1,5 +1,5 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; -import { basename, join, resolve } from 'node:path'; +import { basename, join, relative, resolve } from 'node:path'; import { createWelcomeNotebook, serializeNotebook } from '@duckcodeailabs/dql-notebook'; import type { CLIFlags } from '../args.js'; import { performSemanticImport } from '../semantic-import.js'; @@ -14,9 +14,34 @@ export async function runInit(targetArg: string | null, flags: CLIFlags): Promis mkdirSync(targetDir, { recursive: true }); } - // Detect if this is a dbt project - const isDbt = existsSync(join(targetDir, 'dbt_project.yml')); - const hasDbtSemanticDefinitions = isDbt && containsDbtSemanticDefinitions(join(targetDir, 'models')); + // Refuse to clobber a project that's already been initialized. `--force` + // lets us patch missing dirs; it still won't overwrite config/notebooks. + const alreadyInitialized = + existsSync(join(targetDir, 'dql.config.json')) || existsSync(join(targetDir, 'cdql.yaml')); + if (alreadyInitialized && !flags.force) { + console.error('✗ This directory already has a DQL project (dql.config.json exists).'); + console.error(' Re-run with --force to patch missing dirs without overwriting config.'); + process.exitCode = 1; + return; + } + + // Detect if this is a dbt project — either *in* this directory, or as a + // sibling (the canonical `acme/dbt/` + `acme/analytics/` layout). + let isDbt = existsSync(join(targetDir, 'dbt_project.yml')); + let dbtProjectDir: string | null = isDbt ? targetDir : null; + if (!dbtProjectDir) { + for (const rel of ['..', '../..', '../dbt', '../../dbt']) { + const probe = join(targetDir, rel, 'dbt_project.yml'); + if (existsSync(probe)) { + dbtProjectDir = resolve(targetDir, rel); + isDbt = true; + break; + } + } + } + const hasDbtSemanticDefinitions = dbtProjectDir + ? containsDbtSemanticDefinitions(join(dbtProjectDir, 'models')) + : false; // Detect DuckDB file in the directory const duckdbPath = detectDuckDBFile(targetDir); @@ -24,7 +49,7 @@ export async function runInit(targetArg: string | null, flags: CLIFlags): Promis // Don't overwrite existing dql.config.json const configPath = join(targetDir, 'dql.config.json'); if (!existsSync(configPath)) { - const config = buildConfig(projectName, isDbt, duckdbPath); + const config = buildConfig(projectName, isDbt, duckdbPath, dbtProjectDir, targetDir); writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n'); } @@ -90,7 +115,11 @@ export async function runInit(targetArg: string | null, flags: CLIFlags): Promis console.log(` Path: ${targetDir}`); console.log(''); console.log(' Detected:'); - console.log(` dbt project: ${isDbt ? 'yes (dbt_project.yml found)' : 'no'}`); + if (isDbt && dbtProjectDir && dbtProjectDir !== targetDir) { + console.log(` dbt project: sibling at ${dbtProjectDir}`); + } else { + console.log(` dbt project: ${isDbt ? 'yes (dbt_project.yml found)' : 'no'}`); + } console.log(` DuckDB file: ${duckdbPath ?? 'none (using :memory:)'}`); if (isDbt) { console.log(` Semantic layer: ${importedSemanticCatalog ? 'imported dbt catalog into semantic-layer/' : hasDbtSemanticDefinitions ? 'dbt project with semantic definitions detected' : 'dbt project detected (no semantic definitions imported)'}`); @@ -199,6 +228,8 @@ function buildConfig( projectName: string, isDbt: boolean, duckdbPath: string | null, + dbtProjectDir: string | null, + projectRoot: string, ): Record { const config: Record = { project: projectName, @@ -211,9 +242,21 @@ function buildConfig( }; if (isDbt) { + // Normalize dbt path to a repo-relative form if possible — makes + // dql.config.json portable across machines. + const projectPath = + dbtProjectDir && dbtProjectDir !== projectRoot + ? relativePath(projectRoot, dbtProjectDir) + : '.'; config.semanticLayer = { provider: 'dbt', - projectPath: '.', + projectPath, + }; + // Tell `dql sync dbt` and `dql compile` where to find target/manifest.json + // without requiring --dbt-manifest on every invocation. + config.dbt = { + projectDir: projectPath, + manifestPath: 'target/manifest.json', }; } else { config.semanticLayer = { @@ -223,3 +266,7 @@ function buildConfig( return config; } + +function relativePath(from: string, to: string): string { + return relative(from, to) || '.'; +} diff --git a/apps/cli/src/commands/sync.ts b/apps/cli/src/commands/sync.ts index 673f091f..ecfeb10a 100644 --- a/apps/cli/src/commands/sync.ts +++ b/apps/cli/src/commands/sync.ts @@ -21,7 +21,7 @@ */ import { existsSync, readFileSync, statSync } from 'node:fs'; import { join, resolve, relative } from 'node:path'; -import { collectInputFiles } from '@duckcodeailabs/dql-core'; +import { collectInputFiles, loadProjectConfig, resolveDbtManifestPath } from '@duckcodeailabs/dql-core'; import { ManifestCache } from '@duckcodeailabs/dql-project'; import type { CLIFlags } from '../args.js'; @@ -77,17 +77,35 @@ export async function runSync( return; } - // Auto-detect dbt manifest - if (!dbtManifestPath) { - const defaultPath = join(projectRoot, 'target', 'manifest.json'); - if (existsSync(defaultPath)) dbtManifestPath = defaultPath; - } - - if (!dbtManifestPath || !existsSync(dbtManifestPath)) { - console.error('No dbt manifest found. Expected target/manifest.json or pass --dbt-manifest .'); + // Resolution order: explicit --dbt-manifest flag → `dbt:` section in + // dql.config.json → /target/manifest.json. + const resolved = resolveDbtManifestPath(projectRoot, dbtManifestPath); + if (!resolved) { + const cfg = loadProjectConfig(projectRoot); + const hintedDir = cfg.dbt?.projectDir + ? resolve(projectRoot, cfg.dbt.projectDir) + : undefined; + console.error('✗ No dbt manifest found.'); + console.error(''); + if (hintedDir) { + console.error(` dql.config.json points at: ${hintedDir}`); + console.error(` but no manifest.json exists at ${join(hintedDir, cfg.dbt?.manifestPath ?? 'target/manifest.json')}.`); + console.error(''); + console.error(` Run \`dbt parse\` (or \`dbt compile\`) inside ${hintedDir} first,`); + console.error(' or pass an explicit --dbt-manifest .'); + } else { + console.error(' No `dbt` section in dql.config.json and no ./target/manifest.json in sight.'); + console.error(''); + console.error(' Fix one of:'); + console.error(' 1. Add to dql.config.json:'); + console.error(' "dbt": { "projectDir": "../dbt" }'); + console.error(' 2. Pass --dbt-manifest to this command.'); + console.error(' 3. Run this from a directory with ./target/manifest.json.'); + } process.exitCode = 1; return; } + dbtManifestPath = resolved; const cachePath = join(projectRoot, '.dql', 'cache', 'manifest.sqlite'); diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index a35440f3..ec147048 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -22,6 +22,7 @@ import { runSemantic } from './commands/semantic.js'; import { runLineage } from './commands/lineage.js'; import { runCompile } from './commands/compile.js'; import { runSync } from './commands/sync.js'; +import { runDiff } from './commands/diff.js'; const HELP = ` dql — DQL CLI @@ -40,6 +41,7 @@ const HELP = ` dql info Show block metadata dql migrate Scaffold migration from looker/tableau/dbt/metabase/raw-sql dql fmt Format DQL file in place + dql diff Semantic diff between two .dql files dql notebook [path] Launch the browser-first notebook for a project dql semantic [path] Semantic layer: list, validate, query, pull dql compile [path] Generate project manifest (dql-manifest.json) @@ -148,6 +150,9 @@ async function main() { case 'lineage': await runLineage(file, rest, flags); break; + case 'diff': + await runDiff(file, rest, flags); + break; default: console.error(`Unknown command: ${command}. Run "dql --help" for usage.`); process.exit(1); diff --git a/apps/cli/src/local-runtime.ts b/apps/cli/src/local-runtime.ts index 68a59281..2da637c6 100644 --- a/apps/cli/src/local-runtime.ts +++ b/apps/cli/src/local-runtime.ts @@ -32,6 +32,7 @@ import { type LineageBlockInput, type LineageMetricInput, type LineageDimensionInput, + canonicalize, } from '@duckcodeailabs/dql-core'; import { listBlockTemplates } from './block-templates.js'; import { @@ -270,7 +271,8 @@ export async function startLocalServer(opts: LocalServerOptions): Promise template.id === options.template)?.content : undefined; - const fileContent = normalizeBlockStudioContent({ + const fileContent = canonicalizeSafe(normalizeBlockStudioContent({ name: options.name, domain: safeDomain || 'uncategorized', description: options.description, tags: options.tags, content: options.content?.trim() || templateContent, - }); + })); writeFileSync(blockPath, fileContent, 'utf-8'); const relativePath = safeDomain ? `blocks/${safeDomain}/${slug}.dql` : `blocks/${slug}.dql`; @@ -3079,9 +3142,11 @@ export function createSemanticBuilderBlock( throw new Error('BLOCK_EXISTS'); } - const content = options.blockType === 'custom' - ? buildCustomSemanticBlockContent(options) - : buildSemanticBlockContent(options); + const content = canonicalizeSafe( + options.blockType === 'custom' + ? buildCustomSemanticBlockContent(options) + : buildSemanticBlockContent(options), + ); writeFileSync(blockPath, content, 'utf-8'); const companionPath = writeBlockCompanionFile(projectRoot, { @@ -3506,3 +3571,145 @@ function extractVizChart(block: any): string | undefined { } return undefined; } + +// ── Git read-only helpers (v0.11) ────────────────────────────────────────── +// Shell out to the system `git` binary rather than embed isomorphic-git. +// Users already have git installed to be in a git repo; shelling out keeps +// the notebook bundle lean and avoids re-implementing what `git` already +// does perfectly. All commands run with `cwd = projectRoot` and never take +// user-controlled args (only fixed subcommands + the file path, which we +// pass as a separate execFile arg so it's never interpreted as shell). + +export interface GitStatusResult { + inRepo: boolean; + branch: string | null; + ahead: number; + behind: number; + changes: Array<{ path: string; status: string }>; + error?: string; +} + +async function execGit(cwd: string, args: string[]): Promise<{ stdout: string; stderr: string; code: number }> { + const { execFile } = await import('node:child_process'); + return new Promise((resolve) => { + execFile('git', args, { cwd, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => { + resolve({ + stdout: String(stdout ?? ''), + stderr: String(stderr ?? ''), + code: err ? ((err as NodeJS.ErrnoException).code ? 1 : (err as any).code ?? 1) : 0, + }); + }); + }); +} + +async function readGitStatus(cwd: string): Promise { + const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']); + if (isRepo.code !== 0 || isRepo.stdout.trim() !== 'true') { + return { inRepo: false, branch: null, ahead: 0, behind: 0, changes: [] }; + } + const branchRes = await execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']); + const branch = branchRes.code === 0 ? branchRes.stdout.trim() : null; + + const trackRes = await execGit(cwd, ['rev-list', '--left-right', '--count', '@{u}...HEAD']); + let ahead = 0; + let behind = 0; + if (trackRes.code === 0) { + const match = trackRes.stdout.trim().split(/\s+/); + behind = Number(match[0] ?? 0); + ahead = Number(match[1] ?? 0); + } + + const statusRes = await execGit(cwd, ['status', '--porcelain=v1', '--untracked-files=normal']); + const changes: Array<{ path: string; status: string }> = []; + if (statusRes.code === 0) { + for (const line of statusRes.stdout.split('\n')) { + if (!line) continue; + const code = line.slice(0, 2); + const p = line.slice(3); + changes.push({ path: p, status: code }); + } + } + return { inRepo: true, branch, ahead, behind, changes }; +} + +export interface GitCommit { + hash: string; + author: string; + date: string; + subject: string; +} + +async function readGitLog(cwd: string, limit: number): Promise<{ inRepo: boolean; commits: GitCommit[] }> { + const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']); + if (isRepo.code !== 0) return { inRepo: false, commits: [] }; + const sep = '\x1f'; + const end = '\x1e'; + const fmt = ['%H', '%an', '%ad', '%s'].join(sep) + end; + const res = await execGit(cwd, ['log', `-${limit}`, `--pretty=format:${fmt}`, '--date=short']); + if (res.code !== 0) return { inRepo: true, commits: [] }; + const commits: GitCommit[] = []; + for (const entry of res.stdout.split(end)) { + const trimmed = entry.replace(/^\n/, ''); + if (!trimmed) continue; + const [hash, author, date, subject] = trimmed.split(sep); + if (hash) commits.push({ hash, author, date, subject }); + } + return { inRepo: true, commits }; +} + +function snapshotPathFor(projectRoot: string, notebookPath: string): string | null { + const abs = safeJoin(projectRoot, notebookPath); + if (!abs) return null; + // Strip extension and append `.run.json` so `foo.dqlnb` → `foo.run.json` + // and `bar.dql` → `bar.run.json`. Keeps the sibling file next to source. + const dot = abs.lastIndexOf('.'); + const base = dot > abs.lastIndexOf('/') ? abs.slice(0, dot) : abs; + return `${base}.run.json`; +} + +function readRunSnapshot(projectRoot: string, notebookPath: string): { found: boolean; snapshot: unknown | null } { + const p = snapshotPathFor(projectRoot, notebookPath); + if (!p || !existsSync(p)) return { found: false, snapshot: null }; + try { + const raw = readFileSync(p, 'utf-8'); + return { found: true, snapshot: JSON.parse(raw) }; + } catch { + return { found: false, snapshot: null }; + } +} + +function writeRunSnapshot(projectRoot: string, notebookPath: string, snapshot: unknown): void { + const p = snapshotPathFor(projectRoot, notebookPath); + if (!p) throw new Error('Invalid path'); + mkdirSync(dirname(p), { recursive: true }); + writeFileSync(p, JSON.stringify(snapshot, null, 2), 'utf-8'); + // Append `*.run.json` to .gitignore once, so snapshots don't pollute git + // history unless the user deliberately un-ignores them. + ensureGitignoreEntry(projectRoot, '*.run.json'); +} + +function ensureGitignoreEntry(projectRoot: string, pattern: string): void { + try { + const gitignorePath = join(projectRoot, '.gitignore'); + const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : ''; + const lines = existing.split('\n').map((l) => l.trim()); + if (lines.includes(pattern)) return; + const next = existing.endsWith('\n') || existing === '' + ? `${existing}${pattern}\n` + : `${existing}\n${pattern}\n`; + writeFileSync(gitignorePath, next, 'utf-8'); + } catch { + // Best-effort; failure to write .gitignore shouldn't fail the snapshot. + } +} + +async function readGitDiff(cwd: string, filePath: string): Promise<{ inRepo: boolean; diff: string }> { + const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']); + if (isRepo.code !== 0) return { inRepo: false, diff: '' }; + if (!filePath) { + const res = await execGit(cwd, ['diff', '--no-color']); + return { inRepo: true, diff: res.stdout }; + } + const res = await execGit(cwd, ['diff', '--no-color', '--', filePath]); + return { inRepo: true, diff: res.stdout }; +} diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore new file mode 100644 index 00000000..365d6334 --- /dev/null +++ b/apps/desktop/.gitignore @@ -0,0 +1,2 @@ +src-tauri/target/ +src-tauri/Cargo.lock diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 00000000..256d4800 --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,37 @@ +# DQL Desktop + +Tauri wrapper that ships the DQL notebook + CLI as a single native app for +macOS (arm64/x64), Linux (arm64/x64), and Windows (x64). + +The Tauri shell embeds the `dql-notebook` Vite build and spawns the CLI as +a sidecar process, giving users a **zero-node-required** install. + +## Develop + +```bash +# Requires the Rust toolchain + Tauri 2 prerequisites: +# https://v2.tauri.app/start/prerequisites/ +pnpm -F @duckcodeailabs/dql-notebook-app build +pnpm -F @duckcodeailabs/desktop dev +``` + +## Build release binaries + +Release binaries are built in CI (`.github/workflows/release-desktop.yml`) +on the three target platforms. To produce one locally: + +```bash +pnpm -F @duckcodeailabs/desktop build +# out: +# apps/desktop/src-tauri/target/release/bundle/ +``` + +## Artifacts + +| Platform | Formats | +| --- | --- | +| macOS | `.dmg`, `.app.tar.gz` (notarized) | +| Linux | `.deb`, `.AppImage`, `.rpm` | +| Windows | `.msi`, `.exe` (code-signed) | + +See [docs.duckcode.ai/get-started/install](https://docs.duckcode.ai/get-started/install/). diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 00000000..62ac2547 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,14 @@ +{ + "name": "@duckcodeailabs/desktop", + "version": "0.11.0", + "private": true, + "description": "Tauri desktop wrapper for the DQL notebook + CLI sidecar.", + "scripts": { + "dev": "tauri dev", + "build": "tauri build", + "tauri": "tauri" + }, + "devDependencies": { + "@tauri-apps/cli": "2.1.0" + } +} diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml new file mode 100644 index 00000000..c89ab0bc --- /dev/null +++ b/apps/desktop/src-tauri/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "dql-desktop" +version = "0.11.0" +edition = "2021" +description = "Tauri wrapper for the DQL notebook." +license = "MIT" + +[build-dependencies] +tauri-build = { version = "2.0", features = [] } + +[dependencies] +tauri = { version = "2.0", features = [] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +opt-level = "s" +strip = true diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs new file mode 100644 index 00000000..d860e1e6 --- /dev/null +++ b/apps/desktop/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs new file mode 100644 index 00000000..93c1de14 --- /dev/null +++ b/apps/desktop/src-tauri/src/main.rs @@ -0,0 +1,11 @@ +// Tauri entrypoint for the DQL desktop app. Minimal shell: loads the +// bundled notebook frontend. The CLI ships separately (Homebrew / npm). +// Future: sidecar-spawn the CLI so the desktop app is self-contained. + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + tauri::Builder::default() + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json new file mode 100644 index 00000000..32d6acd3 --- /dev/null +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "DQL", + "version": "0.11.0", + "identifier": "ai.duckcode.dql", + "build": { + "beforeDevCommand": "pnpm -F @duckcodeailabs/dql-notebook-app dev", + "beforeBuildCommand": "pnpm -F @duckcodeailabs/dql-notebook-app build", + "devUrl": "http://localhost:5173", + "frontendDist": "../../dql-notebook/dist" + }, + "app": { + "windows": [ + { + "title": "DQL", + "width": 1440, + "height": 900, + "minWidth": 1024, + "minHeight": 640, + "decorations": true, + "fullscreen": false, + "resizable": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": ["dmg", "app", "deb", "appimage", "rpm", "msi", "nsis"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "resources": [], + "category": "DeveloperTool", + "shortDescription": "Analytics notebooks on your dbt models.", + "longDescription": "DQL is a git-native, local-first analytics notebook + block studio that sits on top of dbt's semantic layer. Open source, MIT-licensed.", + "copyright": "© 2026 DuckCode AI Labs", + "publisher": "DuckCode AI Labs", + "macOS": { + "minimumSystemVersion": "11.0" + } + } +} diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 00000000..dee0f607 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,6 @@ +.next/ +out/ +node_modules/ +next-env.d.ts +*.log +.DS_Store diff --git a/apps/docs/README.md b/apps/docs/README.md new file mode 100644 index 00000000..8cf18d12 --- /dev/null +++ b/apps/docs/README.md @@ -0,0 +1,41 @@ +# @duckcodeailabs/docs + +The DQL documentation site. Nextra + Next.js, static-exported, deployed to +`docs.duckcode.ai`. + +## Develop + +```bash +pnpm -F @duckcodeailabs/docs dev +# http://localhost:3030 +``` + +## Build + +```bash +pnpm -F @duckcodeailabs/docs build +# out/ directory ready to upload to any static host +``` + +## Internal link check + +```bash +pnpm -F @duckcodeailabs/docs link-check +``` + +Fails the build if any relative `[text](/foo/)` link doesn't resolve. + +## IA + +``` +pages/ +├── index.mdx landing +├── get-started/ install, quickstart, concepts +├── guides/ task-oriented walkthroughs +├── reference/ CLI, language, connectors, file formats +├── architecture/ how DQL fits with dbt, plugin API, OpenLineage +└── contribute/ repo layout, testing, releasing +``` + +One topic → one canonical URL. See [docs/README.md](../../docs/README.md) +for the legacy flat-file docs still shipping during migration. diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs new file mode 100644 index 00000000..5c962bd9 --- /dev/null +++ b/apps/docs/next.config.mjs @@ -0,0 +1,15 @@ +import nextra from 'nextra'; + +const withNextra = nextra({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.tsx', + defaultShowCopyCode: true, +}); + +export default withNextra({ + reactStrictMode: true, + // Docs ship as a static export; Vercel picks up the `out/` directory. + output: 'export', + images: { unoptimized: true }, + trailingSlash: true, +}); diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 00000000..7d47854b --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,24 @@ +{ + "name": "@duckcodeailabs/docs", + "version": "0.11.0", + "private": true, + "description": "DQL documentation site (Nextra).", + "scripts": { + "dev": "next dev -p 3030", + "build": "next build", + "start": "next start -p 3030", + "link-check": "node scripts/link-check.mjs" + }, + "dependencies": { + "next": "14.2.5", + "nextra": "2.13.4", + "nextra-theme-docs": "2.13.4", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@types/node": "20.14.10", + "@types/react": "18.3.3", + "typescript": "5.5.3" + } +} diff --git a/apps/docs/pages/_meta.json b/apps/docs/pages/_meta.json new file mode 100644 index 00000000..43e712ac --- /dev/null +++ b/apps/docs/pages/_meta.json @@ -0,0 +1,13 @@ +{ + "index": { + "title": "DQL", + "type": "page", + "display": "hidden", + "theme": { "layout": "raw" } + }, + "get-started": "Get Started", + "guides": "Guides", + "reference": "Reference", + "architecture": "Architecture", + "contribute": "Contribute" +} diff --git a/apps/docs/pages/architecture/_meta.json b/apps/docs/pages/architecture/_meta.json new file mode 100644 index 00000000..bfe0d680 --- /dev/null +++ b/apps/docs/pages/architecture/_meta.json @@ -0,0 +1,7 @@ +{ + "overview": "Overview", + "dbt-integration": "How DQL fits with dbt", + "lineage-model": "Lineage model", + "plugin-api": "Plugin API", + "openlineage": "OpenLineage export" +} diff --git a/apps/docs/pages/architecture/dbt-integration.mdx b/apps/docs/pages/architecture/dbt-integration.mdx new file mode 100644 index 00000000..de836788 --- /dev/null +++ b/apps/docs/pages/architecture/dbt-integration.mdx @@ -0,0 +1,63 @@ +--- +title: How DQL fits with dbt +description: DQL is a reporting + governance layer on top of dbt, not a replacement. +--- + +# How DQL fits with dbt + +dbt is the **modeling** tool. DQL is the **reporting + governance** layer. +They share a single source of truth: `target/manifest.json`. + +## Division of concerns + +| Concern | dbt | DQL | +| --- | --- | --- | +| Transform raw → marts | ✅ | — | +| Tests on models | ✅ | — | +| Semantic layer (MetricFlow) | ✅ authoring | ✅ import + extend | +| Lineage at the model level | ✅ | ✅ (absorbs dbt's) | +| Lineage at the block / dashboard level | — | ✅ | +| Notebook authoring | — | ✅ | +| Certified reusable analytics blocks | — | ✅ | +| Static HTML dashboards | — | ✅ | +| Governance (owner, certification, lint) | partial | ✅ | +| BI-style exploration | — | ✅ | + +## The integration surface + +DQL reads `target/manifest.json`. That's it — no dbt-API calls, no YAML +duplication, no "DQL's version of a model." + +``` +dbt models ─▶ target/manifest.json ─▶ DQL semantic layer +dbt metrics ─┘ │ +dbt sources ─┘ ▼ + DQL blocks + DQL notebooks + DQL dashboards +``` + +## Workflow + +```bash +# In your dbt project: +dbt build + +# In your DQL project: +dql sync dbt +``` + +Add `dql sync dbt` to your CI after `dbt build`, and DQL always reflects +the latest manifest. + +## What DQL does *not* do + +- **Transform raw data.** Use dbt for that. +- **Schedule model runs.** Use dbt + Airflow/Dagster/dbt Cloud. +- **Replace MetricFlow.** DQL extends it, never competes with it. + +## What to move to DQL + +The user-facing outputs of your analytics stack: reports, dashboards, ad +hoc notebooks, certified shared blocks. All the stuff that used to live in +Metabase/Looker/Hex and had no good git story. diff --git a/apps/docs/pages/architecture/lineage-model.mdx b/apps/docs/pages/architecture/lineage-model.mdx new file mode 100644 index 00000000..0b07083d --- /dev/null +++ b/apps/docs/pages/architecture/lineage-model.mdx @@ -0,0 +1,61 @@ +--- +title: Lineage model +description: Nodes, edges, and the AST-backed extraction. +--- + +# Lineage model + +DQL's lineage graph answers three questions: + +1. **What depends on this?** (impact analysis — downstream) +2. **Where did this come from?** (trust chain — upstream) +3. **What's shared across teams?** (cross-domain detection) + +## Node types + +| Kind | Source | +| --- | --- | +| `source` | dbt source or warehouse table | +| `model` | dbt model | +| `block` | DQL block | +| `notebook` | DQL notebook | +| `dashboard` | DQL dashboard | +| `metric` / `dimension` | Semantic layer object | + +## Edge types + +| Kind | Meaning | +| --- | --- | +| `reads` | `A` queries from `B` | +| `contains` | `A` embeds `B` (e.g. notebook contains block) | +| `materializes_to` | `A` is compiled into `B` (e.g. block → dashboard section) | + +## Extraction + +SQL-level refs are extracted with `node-sql-parser` (**not regex**), which +correctly handles CTEs, subqueries, lateral joins, `QUALIFY`, and dialect +quirks. Semantic refs (`@metric`, `@block`, `@table`) are extracted from +the DQL AST directly. + +## Storage + +Lineage is persisted in `.dql/cache/manifest.sqlite` and rebuilt +incrementally — only touched subgraphs are recomputed on a file change. +Warm rebuild on a 4,000-model project is under 2s. + +## CLI surface + +```bash +dql lineage summary +dql lineage impact customers # downstream of `customers` +dql lineage trust-chain revenue_q4 # upstream of a block +dql lineage cross-domain # edges that cross domain boundaries +``` + +## Column-level lineage + +Table-level lineage is fully open-source. **Column-level lineage is a +commercial feature** in the DuckCode Cloud build — see the +[OSS/Cloud boundary](https://github.com/duckcode-ai/dql/blob/main/ROADMAP.md#commercial-gate--osscloud-boundary). +The manifest output is identical in both builds; column-level is gated at +the surface, not in the graph. diff --git a/apps/docs/pages/architecture/openlineage.mdx b/apps/docs/pages/architecture/openlineage.mdx new file mode 100644 index 00000000..81ae278d --- /dev/null +++ b/apps/docs/pages/architecture/openlineage.mdx @@ -0,0 +1,67 @@ +--- +title: OpenLineage export +description: Emit OpenLineage events for DataHub, Atlan, and Monte Carlo. +--- + +# OpenLineage export + +DQL emits [OpenLineage](https://openlineage.io) events from block and +notebook runs so your existing metadata platform sees DQL-level lineage +alongside dbt-level lineage. + +## Enable + +```yaml +# cdql.yaml +openlineage: + enabled: true + url: http://marquez.internal:5000 + namespace: analytics +``` + +Or via environment: + +```bash +export OPENLINEAGE_URL=http://marquez.internal:5000 +export OPENLINEAGE_NAMESPACE=analytics +``` + +## Event shape + +Every block execution emits a `START` and `COMPLETE` event: + +```json +{ + "eventType": "COMPLETE", + "eventTime": "2026-04-15T12:34:56Z", + "job": { + "namespace": "analytics", + "name": "block.revenue_by_segment" + }, + "run": { "runId": "…" }, + "inputs": [{ "namespace": "analytics", "name": "orders" }], + "outputs": [{ "namespace": "analytics", "name": "block.revenue_by_segment" }] +} +``` + +## Compatible receivers + +Verified against: + +- [Marquez](https://marquezproject.ai/) (reference OL consumer) +- [DataHub](https://datahubproject.io/) via the OL connector +- [Atlan](https://atlan.com/) via the OL integration +- [Monte Carlo](https://www.montecarlodata.com/) via OL webhook + +## What's emitted + +- Block runs +- Notebook cell runs (one event per cell) +- Dashboard compilations +- `dql sync dbt` (with dbt's own OL output, so you get one graph) + +## Schema version + +DQL currently emits [OpenLineage spec +0.19](https://openlineage.io/spec). Facet support is full for `schema`, +`columnLineage` (commercial build), and `sql`. diff --git a/apps/docs/pages/architecture/overview.mdx b/apps/docs/pages/architecture/overview.mdx new file mode 100644 index 00000000..3ee290dc --- /dev/null +++ b/apps/docs/pages/architecture/overview.mdx @@ -0,0 +1,69 @@ +--- +title: Architecture overview +description: Packages, apps, and data flow at a glance. +--- + +# Architecture overview + +DQL is a pnpm + Turbo monorepo. The runtime is a small set of focused +packages; the two user-facing surfaces are the CLI and the notebook. + +## Packages + +| Package | Role | +| --- | --- | +| `@duckcodeailabs/dql-core` | Lexer, parser, AST, semantic resolution, lineage, canonical format | +| `@duckcodeailabs/dql-compiler` | IR + Vega-Lite / HTML emitters | +| `@duckcodeailabs/dql-runtime` | Query execution, param binding, result shaping | +| `@duckcodeailabs/dql-connectors` | 15 warehouse drivers with introspection | +| `@duckcodeailabs/dql-governance` | Lint rules, certification gates | +| `@duckcodeailabs/dql-project` | SQLite-backed project registry & manifest cache | +| `@duckcodeailabs/dql-lsp` | Language server for editor integrations | +| `@duckcodeailabs/dql-charts` | Chart spec resolution | +| `@duckcodeailabs/dql-ui` | Shared design tokens + primitives (v0.10) | + +## Apps + +| App | Role | +| --- | --- | +| `@duckcodeailabs/dql-cli` | Single `dql` binary — `init / compile / notebook / diff / …` | +| `dql-notebook` | Vite + React 18 browser-first notebook | +| `vscode-extension` | `.dql` syntax, validation, preview in VS Code | +| `docs` | This site (Nextra) | + +## Data flow + +``` +user edits .dql file + │ + ▼ + dql-core parser ─────────────▶ AST + │ │ + │ ▼ + │ dql-governance (lint) + │ │ + ▼ ▼ + dql-compiler IR ────▶ dql-runtime ────▶ warehouse (via dql-connectors) + │ │ + ▼ ▼ + HTML dashboard notebook result + │ │ + └──────┬─────────────┘ + ▼ + lineage DAG (dql-core) + │ + ▼ + OpenLineage events +``` + +## Where data lives + +| Data | Storage | +| --- | --- | +| Source `.dql` files | Git | +| Project manifest cache | `.dql/cache/manifest.sqlite` (better-sqlite3) | +| Run snapshots | Sibling `.run.json` (git-ignored) | +| Block registry | `.dql/registry.sqlite` | +| Query results | In-memory; never persisted unless snapshotted | + +Nothing requires a hosted DB. DQL is fully local-first. diff --git a/apps/docs/pages/architecture/plugin-api.mdx b/apps/docs/pages/architecture/plugin-api.mdx new file mode 100644 index 00000000..2dae1b03 --- /dev/null +++ b/apps/docs/pages/architecture/plugin-api.mdx @@ -0,0 +1,80 @@ +--- +title: Plugin API +description: Custom connectors, chart types, and governance rule packs. +--- + +# Plugin API + +> Status: **stable in v1.0** + +DQL exposes three stable extension points. Each is a plain npm package that +exports a well-typed entry. No build step hooks, no runtime monkey-patching. + +## Custom connectors + +Implement `Connector` from `@duckcodeailabs/dql-connectors`: + +```ts +import type { Connector } from '@duckcodeailabs/dql-connectors'; + +export const myConnector: Connector = { + id: 'my-driver', + async connect(config) { /* … */ }, + async query(sql, params) { /* … */ }, + async introspect() { /* … */ }, + async close() { /* … */ }, +}; +``` + +Register via `cdql.yaml`: + +```yaml +plugins: + connectors: + - my-company/dql-connector-exasol +``` + +## Custom chart types + +Implement `ChartRenderer` from `@duckcodeailabs/dql-charts`: + +```ts +import type { ChartRenderer } from '@duckcodeailabs/dql-charts'; + +export const sankey: ChartRenderer = { + id: 'sankey', + schema: { /* JSON schema for config */ }, + render(result, config) { /* return Vega-Lite spec or React element */ }, +}; +``` + +## Governance rule packs + +Implement `RulePack` from `@duckcodeailabs/dql-governance`: + +```ts +import type { RulePack } from '@duckcodeailabs/dql-governance'; + +export const hipaaPack: RulePack = { + id: 'hipaa', + rules: [ + { + id: 'no-phi-columns', + severity: 'error', + check(block) { /* … */ }, + }, + ], +}; +``` + +Register in `cdql.yaml`: + +```yaml +governance: + rule_packs: ['@my-company/dql-hipaa'] +``` + +## Stability + +The three plugin interfaces above are **frozen at v1.0**. Breaking changes +require a major version bump and a 6-month deprecation window. diff --git a/apps/docs/pages/contribute/_meta.json b/apps/docs/pages/contribute/_meta.json new file mode 100644 index 00000000..57821887 --- /dev/null +++ b/apps/docs/pages/contribute/_meta.json @@ -0,0 +1,5 @@ +{ + "repo-layout": "Repo layout & build", + "testing": "Testing", + "releasing": "Release process" +} diff --git a/apps/docs/pages/contribute/releasing.mdx b/apps/docs/pages/contribute/releasing.mdx new file mode 100644 index 00000000..15723168 --- /dev/null +++ b/apps/docs/pages/contribute/releasing.mdx @@ -0,0 +1,54 @@ +--- +title: Release process +description: How we cut a DQL release. +--- + +# Release process + +DQL uses a **single version across all publishable packages**. Dev +convenience trumps the complexity of independent semver per package. + +## Cut a release + +```bash +# 1. Bump every package.json in one go +./scripts/bump-version.sh 0.12.0 + +# 2. Build everything (tests run as part of `prepublishOnly`) +pnpm -r --filter '@duckcodeailabs/*' \ + --filter '!@duckcodeailabs/dql-notebook-app' \ + --filter '!@duckcodeailabs/vscode-extension' build + +# 3. Publish (pnpm rewrites workspace:* specifiers — don't use npm publish) +pnpm -r --filter '@duckcodeailabs/*' \ + --filter '!@duckcodeailabs/dql-notebook-app' \ + --filter '!@duckcodeailabs/vscode-extension' \ + publish --access public --no-git-checks + +# 4. Commit + tag +git commit -am "v0.12.0: release" +git tag v0.12.0 +git push && git push --tags + +# 5. Draft GitHub release with CHANGELOG.md excerpt +gh release create v0.12.0 --title "v0.12.0" --notes-from-tag +``` + +## Publishing gotchas + +- **Use `pnpm publish`, not `npm publish`.** pnpm rewrites `workspace:*` to + the resolved version at publish time; npm doesn't, and the published + tarball fails with `EUNSUPPORTEDPROTOCOL`. +- **The notebook app and VS Code extension aren't published to npm** — + they ship as prebuilt binaries. The `--filter '!…'` exclusions above + enforce that. + +## What ships in a release + +| Artifact | Where | +| --- | --- | +| npm packages (11) | `npmjs.com/org/duckcodeailabs` | +| CLI Homebrew tap | `github.com/duckcode-ai/homebrew-dql` | +| VS Code extension | Marketplace | +| Desktop notebook binaries | GitHub release assets (macOS/Linux/Windows) | +| Docs site | `docs.duckcode.ai` (auto-deploys on merge to `main`) | diff --git a/apps/docs/pages/contribute/repo-layout.mdx b/apps/docs/pages/contribute/repo-layout.mdx new file mode 100644 index 00000000..8bb5b8ac --- /dev/null +++ b/apps/docs/pages/contribute/repo-layout.mdx @@ -0,0 +1,65 @@ +--- +title: Repo layout & build +description: Clone, install, build, test — in under 2 minutes. +--- + +# Repo layout & build + +> ~2 minutes · ends with a local dev loop + +## Clone & install + +```bash +git clone https://github.com/duckcode-ai/dql.git +cd dql +pnpm install +``` + +Requires **pnpm 9+** and **Node 20+**. + +## Layout + +``` +dql/ +├── apps/ +│ ├── cli/ # dql CLI entry +│ ├── dql-notebook/ # Vite + React notebook +│ ├── vscode-extension/ # VS Code extension +│ └── docs/ # This site (Nextra) +├── packages/ +│ ├── dql-core/ # parser + AST + semantic + lineage +│ ├── dql-compiler/ # IR + emitters +│ ├── dql-runtime/ # query execution +│ ├── dql-connectors/ # 15 drivers +│ ├── dql-governance/ # lint + certification +│ ├── dql-project/ # SQLite registry +│ ├── dql-lsp/ # language server +│ ├── dql-charts/ # chart specs +│ └── dql-ui/ # shared tokens + primitives +└── docs/ # legacy md (migrating into apps/docs/) +``` + +## Common scripts + +```bash +pnpm build # turbo-driven full build +pnpm dev # watch all packages +pnpm test # vitest across packages +pnpm lint # eslint + tsc --noEmit +pnpm -F @duckcodeailabs/dql-notebook-app dev # just the notebook +pnpm -F @duckcodeailabs/docs dev # just the docs site +``` + +## Running the CLI from source + +```bash +pnpm -F @duckcodeailabs/dql-cli build +node apps/cli/dist/index.js --version +``` + +Or link it globally during dev: + +```bash +cd apps/cli && pnpm link --global +dql --version # now shells out to your local build +``` diff --git a/apps/docs/pages/contribute/testing.mdx b/apps/docs/pages/contribute/testing.mdx new file mode 100644 index 00000000..6d1ad021 --- /dev/null +++ b/apps/docs/pages/contribute/testing.mdx @@ -0,0 +1,58 @@ +--- +title: Testing +description: Unit, golden-file, E2E, and stress tests. +--- + +# Testing + +```bash +pnpm test # everything +pnpm -F @duckcodeailabs/dql-core test +pnpm -F @duckcodeailabs/dql-compiler test +``` + +## Unit tests (Vitest) + +Every package has co-located `*.test.ts` files. Target is ≥80% coverage on +`dql-core`, `dql-compiler`, `dql-governance`. + +## Golden-file tests + +Parser and lineage extractor run against a corpus of real-world SQL samples +(CTEs, lateral joins, `QUALIFY`, dialect quirks). Snapshots live at +`packages/dql-core/test/golden/`. + +Update snapshots deliberately: + +```bash +pnpm -F @duckcodeailabs/dql-core test -- -u +``` + +## Connector cassettes + +Integration tests for each connector record real query/response pairs +(sanitized) and replay them in CI. Live connectivity tests run nightly +against an internal fixture warehouse. + +## E2E (Playwright) + +Runs the full Jaffle Shop happy path in a headless browser: + +```bash +pnpm -F @duckcodeailabs/dql-notebook-app test:e2e +``` + +## Stress test + +Synthetic 4,000-model dbt project generated in CI to catch scale regressions: + +```bash +pnpm bench:manifest +``` + +Gates: cold build `<30s`, warm rebuild `<2s`, lineage panel FPS `≥50`. + +## CI + +Every PR to `main` runs: lint + unit + golden + connector cassettes + E2E ++ 4,000-model stress + docs link-check. Merges blocked on any failure. diff --git a/apps/docs/pages/get-started/_meta.json b/apps/docs/pages/get-started/_meta.json new file mode 100644 index 00000000..2bead6d2 --- /dev/null +++ b/apps/docs/pages/get-started/_meta.json @@ -0,0 +1,5 @@ +{ + "install": "Install", + "quickstart": "Quickstart", + "concepts": "Concepts" +} diff --git a/apps/docs/pages/get-started/concepts.mdx b/apps/docs/pages/get-started/concepts.mdx new file mode 100644 index 00000000..84f94f89 --- /dev/null +++ b/apps/docs/pages/get-started/concepts.mdx @@ -0,0 +1,91 @@ +--- +title: Concepts +description: The five words to understand DQL. +--- + +# Concepts + +> ~4 minutes · the mental model behind every feature + +DQL has five primitives. Once these click, the rest of the docs read like +reference material. + +## 1. Notebook + +A `.dql` file containing an ordered list of cells. Cells can be: + +- **SQL** — free-form queries against any connected warehouse +- **DQL** — reference semantic objects via `@metric(name)` / `@dim(name)` +- **Markdown** — commentary, docs, dashboards-as-code +- **Param** — text/select/date/number inputs wired into downstream cells + +Notebooks live in git. Results never do (they go in a sibling +`.run.json` that's git-ignored by default — see [Run Snapshots](/reference/file-formats/#run-snapshots)). + +## 2. Block + +A **named, versioned, governed** analytics artifact. Think of a block as a +dbt model that ships with a chart spec and governance metadata (owner, +certification status, tags). + +```dql +block revenue_by_segment { + domain: "finance" + owner: "analytics@company.com" + tags: ["revenue", "certified"] + + query: | + select segment, sum(amount) as revenue + from @table("orders") + group by 1 + + visualization: bar(x: "segment", y: "revenue") +} +``` + +Blocks are authored in **Block Studio** (an editor with lint, preview, +lineage, and a promote-to-certified flow). They compile to SQL + a chart +spec and are embeddable in notebooks, dashboards, or other blocks. + +## 3. Semantic layer + +The shared vocabulary of the business — **metrics** (measures), +**dimensions** (attributes), **hierarchies** (drill paths), **cubes** +(pre-joined fact/dim sets). DQL imports from dbt's semantic layer by default +and lets you author the rest locally. + +In a DQL cell, `@metric("revenue")` and `@dim("customer.segment")` resolve +against the semantic layer — no hand-written joins. + +## 4. Lineage + +A graph of every **table → block → notebook → dashboard** edge in the +project, built from AST-level SQL parsing (not regex). The lineage panel +renders it with React Flow; the CLI exposes it via +`dql lineage impact ` and friends. + +Lineage answers three questions: + +- *What depends on this table?* (impact analysis) +- *Where did this number come from?* (trust chain) +- *What's shared across teams?* (cross-domain detection) + +## 5. Dashboard + +A compiled, static HTML artifact built from one or more blocks + markdown + +params. Dashboards are **files on disk**, not rows in a SaaS DB — you host +them on anything that serves HTML. + +--- + +## How they fit + +``` +dbt models ──┐ + ├─▶ semantic layer ──▶ blocks ──▶ notebooks ──▶ dashboards +warehouse ───┘ │ + ▼ + lineage DAG +``` + +With that mental model loaded, [the guides](/guides/) all make sense. diff --git a/apps/docs/pages/get-started/install.mdx b/apps/docs/pages/get-started/install.mdx new file mode 100644 index 00000000..d6773e9b --- /dev/null +++ b/apps/docs/pages/get-started/install.mdx @@ -0,0 +1,56 @@ +--- +title: Install +description: Install the DQL CLI — npm, homebrew, or prebuilt binary. +--- + +# Install + +> ~2 minutes · requires Node 20+ or a prebuilt binary + +Pick the path that fits your setup. + +## Option A — npm (recommended) + +Works on macOS, Linux, and Windows. No global install required. + +```bash +npx @duckcodeailabs/dql-cli --version +``` + +To make `dql` a global command: + +```bash +npm i -g @duckcodeailabs/dql-cli +dql --version +``` + +## Option B — Homebrew (macOS / Linux) + +```bash +brew tap duckcode-ai/dql +brew install dql +``` + +## Option C — Prebuilt binary + +Download the latest release for your platform from +[github.com/duckcode-ai/dql/releases](https://github.com/duckcode-ai/dql/releases). +Each release ships macOS (arm64/x64), Linux (arm64/x64), and Windows (x64) +binaries built with Tauri. + +## Verify it worked + +```bash +dql --version +# DQL 0.11.0 or later +``` + +If you see the version number, you're ready for the [Quickstart →](/get-started/quickstart/). + +## Troubleshooting + +- **`command not found: dql`** — use `npx @duckcodeailabs/dql-cli` instead, or + add your global npm bin (`npm prefix -g`/bin) to `$PATH`. +- **Node version errors** — DQL requires Node 20+. Install via + [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm). +- **Windows + npm + path too long** — prefer the prebuilt binary. diff --git a/apps/docs/pages/get-started/quickstart.mdx b/apps/docs/pages/get-started/quickstart.mdx new file mode 100644 index 00000000..f8351d00 --- /dev/null +++ b/apps/docs/pages/get-started/quickstart.mdx @@ -0,0 +1,74 @@ +--- +title: Quickstart +description: From zero to a Jaffle Shop dashboard in under five minutes. +--- + +import { Callout, Steps } from 'nextra/components'; + +# Quickstart + +> ~5 minutes · end with a working dashboard on the Jaffle Shop dataset + +This is the demo gate for DQL. If anything below takes longer than expected or +doesn't work verbatim, [open an issue](https://github.com/duckcode-ai/dql/issues/new) — +we treat the quickstart as a contract. + + + +### Scaffold a project with Jaffle Shop seed data + +```bash +npx create-dql-app jaffle-demo +cd jaffle-demo +``` + +No global install required — `create-dql-app` scaffolds the project, +`npx @duckcodeailabs/dql-cli` runs the notebook. See +[Install](/get-started/install/) if you prefer a global `dql` binary. + +You now have a DQL project wired to a DuckDB-backed Jaffle Shop — the same +demo dataset dbt ships. No external warehouse needed. + +### Start the notebook + +```bash +dql notebook +``` + +The CLI starts a local server on **http://localhost:5173** and opens it in +your browser. + +### Run the example notebook + +In the left sidebar, open `notebooks/jaffle-overview.dql`. Press **⌘↵** (or +**Ctrl+Enter**) on each cell, top to bottom. You'll see: + +- a table of orders per customer +- a line chart of daily revenue +- a certified `revenue_by_segment` block +- the lineage DAG, showing how those artifacts connect + +### Publish the dashboard + +```bash +dql compile dashboards/overview.dql --out build/ +``` + +Open `build/overview.html` — a fully static HTML dashboard, zero runtime +dependencies, ready to drop on any web host. + + + +## Verify it worked + +You should have: + +- A running notebook at `http://localhost:5173` +- `build/overview.html` rendering three charts + a lineage panel +- `git status` showing only the files you touched (no churn) + +## Where to go next + +- [Concepts](/get-started/concepts/) — the five words to understand DQL +- [Connect your own warehouse](/guides/connect-warehouse/) — swap DuckDB for Postgres/Snowflake/BigQuery +- [Import your dbt project](/guides/import-dbt/) — bring your existing manifest diff --git a/apps/docs/pages/guides/_meta.json b/apps/docs/pages/guides/_meta.json new file mode 100644 index 00000000..3426a127 --- /dev/null +++ b/apps/docs/pages/guides/_meta.json @@ -0,0 +1,9 @@ +{ + "connect-warehouse": "Connect a warehouse", + "import-dbt": "Import a dbt project", + "authoring-blocks": "Author a certified block", + "dashboards": "Build a dashboard", + "versioning": "Version & diff notebooks", + "jaffle-shop": "Jaffle Shop walkthrough", + "migrate": "Migrate from Metabase / Looker / Hex" +} diff --git a/apps/docs/pages/guides/authoring-blocks.mdx b/apps/docs/pages/guides/authoring-blocks.mdx new file mode 100644 index 00000000..baa51ef6 --- /dev/null +++ b/apps/docs/pages/guides/authoring-blocks.mdx @@ -0,0 +1,80 @@ +--- +title: Author a certified block +description: Build, lint, and promote a governed analytics block in Block Studio. +--- + +# Author a certified block + +> ~6 minutes · ends with a certified block linked from a notebook + +A **block** is a named, versioned analytics artifact with SQL, a chart +spec, and governance metadata. Blocks are the unit of reuse in DQL — and +the thing reviewers actually review. + +## 1. Open Block Studio + +From the notebook, click **Blocks** in the left rail → **New Block**, or: + +```bash +dql block new revenue_by_segment --domain finance +``` + +Block Studio opens with a three-pane layout: SQL editor · live result · +governance form. + +## 2. Write the SQL + +```sql +select + segment, + sum(amount) as revenue, + count(distinct customer_id) as customers +from @table("orders") +group by 1 +order by revenue desc +``` + +`@table("orders")` resolves via the semantic layer (dbt or DQL-local). The +editor shows lint warnings from `dql-governance` as you type. + +## 3. Fill in governance + +| Field | Example | Why it matters | +| --- | --- | --- | +| `domain` | `finance` | Lineage cross-domain detection | +| `owner` | `analytics@company.com` | Who answers questions | +| `tags` | `revenue`, `certified` | Discovery & filtering | +| `description` | "Revenue by customer segment…" | Shows in hover cards | +| `tests` | `row_count > 0` | Blocks the certify step if failing | + +## 4. Preview + +**⌘↵** runs the query. Results render inline; the chart spec auto-picks bar +for categorical-x/numeric-y. Override in the visualization panel. + +## 5. Promote to certified + +Click **Promote → Certified**. DQL: + +- runs tests (`tests:` block) +- diffs SQL against the previous certified version +- bumps the block version +- writes the new version to git via a canonical `.dql` serialization + +The certified tag appears in the block library; downstream notebooks see +the new version on next open. + +## 6. Use it in a notebook + +```dql +-- Notebook cell +@block("revenue_by_segment") +``` + +DQL inlines the compiled SQL, runs it, and renders the block's chart spec. + +## Verify it worked + +- Block appears in the **Block Library** sidebar with a green "certified" badge +- `git log -- blocks/finance/revenue_by_segment.dql` shows a clean commit +- Notebook cell referencing the block renders a bar chart with your data diff --git a/apps/docs/pages/guides/connect-warehouse.mdx b/apps/docs/pages/guides/connect-warehouse.mdx new file mode 100644 index 00000000..ebfa5d26 --- /dev/null +++ b/apps/docs/pages/guides/connect-warehouse.mdx @@ -0,0 +1,74 @@ +--- +title: Connect a warehouse +description: Point DQL at Postgres, DuckDB, Snowflake, BigQuery, Redshift, or any of the 15 supported warehouses. +--- + +# Connect a warehouse + +> ~3 minutes · ends with a successful `dql test-connection` + +DQL ships 15 drivers out of the box. Connections live in `cdql.yaml` at the +project root — never committed with secrets (use `${ENV_VAR}` interpolation). + +## 1. Pick your connector + +```yaml +# cdql.yaml +connections: + default: + driver: postgres # one of: postgres, duckdb, snowflake, bigquery, + # redshift, mysql, clickhouse, mssql, trino, + # databricks, athena, sqlite, motherduck, + # starrocks, oracle + host: ${PGHOST} + port: 5432 + database: analytics + user: ${PGUSER} + password: ${PGPASSWORD} + schema: public +``` + +Per-driver options live in the [Connector reference](/reference/connectors/). + +## 2. Export credentials + +```bash +export PGHOST=prod-db.internal +export PGUSER=analyst_ro +export PGPASSWORD=… +``` + +## 3. Verify + +```bash +dql test-connection +# ✓ default (postgres) — 14 schemas, 312 tables +``` + +If that passes, the notebook and CLI will resolve `@table(...)` against +this connection. + +## Multiple connections + +```yaml +connections: + default: { driver: duckdb, path: ./warehouse.duckdb } + prod: { driver: snowflake, account: ..., ... } + raw: { driver: postgres, host: ..., ... } +``` + +Reference a non-default connection from a cell: + +```sql +-- @connection: prod +select count(*) from analytics.orders +``` + +## Troubleshooting + +- **`connection refused`** — firewall / VPN / wrong port. `dql + test-connection --debug` prints the resolved DSN (redacted). +- **`role does not have USAGE on schema`** — warehouse permissions. DQL + needs `USAGE` on the schema and `SELECT` on the objects you query. +- **BigQuery service account** — set `GOOGLE_APPLICATION_CREDENTIALS` to the + key file path; the driver auto-picks it up. diff --git a/apps/docs/pages/guides/dashboards.mdx b/apps/docs/pages/guides/dashboards.mdx new file mode 100644 index 00000000..27b84647 --- /dev/null +++ b/apps/docs/pages/guides/dashboards.mdx @@ -0,0 +1,69 @@ +--- +title: Build a dashboard +description: Compile a notebook into a static HTML dashboard with zero runtime dependencies. +--- + +# Build a dashboard + +> ~3 minutes · ends with an HTML file you can host anywhere + +DQL dashboards are **compiled static HTML**. No SaaS DB, no login, no +runtime server — just a file. Host on S3, GitHub Pages, Vercel, a USB stick. + +## 1. Mark a notebook as a dashboard + +Add a `dashboard:` header to any `.dql` file: + +```dql +// dql-format: 1 + +dashboard: { + title: "Revenue Overview" + description: "Daily revenue + segment breakdown" + layout: "grid" // grid | single-column +} + +// … your cells … +``` + +Or scaffold one: + +```bash +dql dashboard new overview --from notebooks/jaffle-overview.dql +``` + +## 2. Compile + +```bash +dql compile dashboards/overview.dql --out build/ +# ✓ compiled 4 cells +# ✓ emitted build/overview.html (312 KB, self-contained) +``` + +The output is a single HTML file with inlined CSS, JS, and data. No +external fetches at runtime. + +## 3. Host it + +```bash +# Any static host works. Examples: +aws s3 cp build/overview.html s3://my-dashboards/ +# or +vercel deploy build/ +``` + +## Parameters + +Params in the notebook become URL query params in the dashboard: + +```dql +param region: select(options: ["us", "eu", "apac"], default: "us") +``` + +Results in `overview.html?region=eu`. + +## Verify it worked + +- Opening `build/overview.html` in a browser renders charts without network +- `curl -s build/overview.html | grep -c ' ~4 minutes · ends with dbt models visible in the sidebar + +DQL reads dbt's `target/manifest.json` directly — no YAML walking, no +duplicated metadata. At 4,000+ models, the warm rebuild is under 2s thanks +to the SQLite cache (`v0.9` work). + +## 1. Point DQL at your dbt project + +```yaml +# cdql.yaml +dbt: + projectDir: ../my-dbt-project + profile: prod # matches a profile in ~/.dbt/profiles.yml + manifestPath: target/manifest.json # default; override if custom +``` + +## 2. Generate the manifest (if you haven't) + +```bash +cd ../my-dbt-project +dbt compile # or dbt parse / dbt run — anything that writes manifest.json +``` + +## 3. Sync + +```bash +dql sync dbt +# ✓ read manifest.json (3,412 nodes) +# ✓ imported 2,107 models, 41 sources, 18 metrics, 12 exposures +# ✓ cache written to .dql/cache/manifest.sqlite +``` + +## 4. Selective import (large projects) + +For projects with thousands of models, anchor-driven BFS keeps the working +set small: + +```yaml +# cdql.yaml +dbt: + projectDir: ../my-dbt-project + include: + anchors: ["tag:core", "mart.revenue.*"] + maxDbtHops: 3 + exclude: + paths: ["models/staging/**"] +``` + +Re-sync is deterministic — the anchor set is persisted and teammates import +the exact same subgraph. + +## 5. Watch mode + +```bash +dql sync dbt --watch +``` + +Detects `target/manifest.json` changes in `<1s`; notebook schema panel live-updates. + +## Verify it worked + +- Notebook **Schema** panel shows your dbt models +- Notebook **Semantic** panel shows your dbt metrics & dimensions +- `dql lineage summary` prints node/edge counts matching your dbt project + +## Troubleshooting + +- **`manifest.json not found`** — run `dbt parse` or `dbt compile` first. +- **Import takes too long** — you're probably full-scanning. Add `include.anchors`. +- **Metrics missing** — DQL reads dbt's [MetricFlow](https://docs.getdbt.com/docs/build/about-metricflow) semantic models. Older dbt metrics (v0.x) are still supported via the YAML fallback. diff --git a/apps/docs/pages/guides/index.mdx b/apps/docs/pages/guides/index.mdx new file mode 100644 index 00000000..b0c779cd --- /dev/null +++ b/apps/docs/pages/guides/index.mdx @@ -0,0 +1,25 @@ +--- +title: Guides +description: Task-oriented walkthroughs. Each ends with working code. +--- + +# Guides + +Task-first, copy-runnable, with a concrete "verify it worked" step at the +end. If a guide doesn't work verbatim on a clean machine, it's a bug. + +## Start here + +- [Connect a warehouse](/guides/connect-warehouse/) — Postgres, DuckDB, Snowflake, BigQuery, Redshift, and more +- [Import a dbt project](/guides/import-dbt/) — bring your `manifest.json` +- [Jaffle Shop walkthrough](/guides/jaffle-shop/) — the end-to-end reference tour + +## Author & ship + +- [Author a certified block](/guides/authoring-blocks/) — governance form, lint, promote +- [Build a dashboard](/guides/dashboards/) — compile notebooks to static HTML +- [Version & diff notebooks](/guides/versioning/) — canonical `.dql`, `dql diff`, in-app git panel + +## Migrate + +- [Migrate from Metabase / Looker / Hex](/guides/migrate/) diff --git a/apps/docs/pages/guides/jaffle-shop.mdx b/apps/docs/pages/guides/jaffle-shop.mdx new file mode 100644 index 00000000..3db05706 --- /dev/null +++ b/apps/docs/pages/guides/jaffle-shop.mdx @@ -0,0 +1,29 @@ +--- +title: Jaffle Shop walkthrough +description: End-to-end reference tour of DQL on the dbt Jaffle Shop dataset. +--- + +# Jaffle Shop walkthrough + +> ~12 minutes · end-to-end tour covering every DQL feature + +The [Quickstart](/get-started/quickstart/) got you to a compiled dashboard in +five minutes. This walkthrough goes slower and hits every surface: dbt +import, notebook authoring, block certification, lineage, dashboards, git +versioning. + +{/* TODO: port content from docs/01-start-here/dbt-jaffle-shop.md once the + canonical demo repo at github.com/duckcode-ai/jaffle-demo is published. */} + +## Sections + +1. Set up the dbt Jaffle Shop repo +2. Run `dql init` and point it at dbt +3. Build the `orders` notebook +4. Author a certified `revenue_by_segment` block +5. Trace lineage from raw tables to the dashboard +6. Compile and host the dashboard +7. Review the PR with `dql diff` + +See the legacy [dbt Jaffle Shop walkthrough](https://github.com/duckcode-ai/dql/blob/main/docs/01-start-here/dbt-jaffle-shop.md) +for the current long-form content while this page is being rewritten. diff --git a/apps/docs/pages/guides/migrate.mdx b/apps/docs/pages/guides/migrate.mdx new file mode 100644 index 00000000..ba4875cc --- /dev/null +++ b/apps/docs/pages/guides/migrate.mdx @@ -0,0 +1,61 @@ +--- +title: Migrate from Metabase / Looker / Hex +description: Bring existing analytics assets into DQL. +--- + +# Migrate from Metabase / Looker / Hex + +> ~varies by source · scaffold first, refine by hand + +DQL's migration tooling doesn't promise a one-click swap — it scaffolds a +starting point that you refine. Expect ~80% of simple queries to compile +cleanly; the remaining 20% surface as `// TODO` comments in the generated +`.dql` files. + +## From Looker (LookML) + +```bash +dql migrate looker --input path/to/lookml/ +``` + +Maps views → DQL tables, measures → metrics, dimensions → dimensions. Liquid +templating becomes `// TODO` comments with the original inline. + +## From Metabase + +```bash +dql migrate metabase --input export.json +``` + +Reads a Metabase collection export. Questions become notebooks; saved SQL +becomes blocks. + +## From Hex + +```bash +dql migrate hex --input workspace.zip +``` + +Cells map 1:1. Python cells are preserved as markdown with the original source +for manual port. + +## From raw SQL + +```bash +dql migrate raw-sql --input queries/ +``` + +Each `.sql` file becomes a block scaffold with a generated chart spec +inferred from column types. + +## Verify + +After any migration: + +```bash +dql validate . +dql fmt . +``` + +`validate` flags unresolved references; `fmt` canonicalizes the scaffolded +output so the initial commit is clean. diff --git a/apps/docs/pages/guides/versioning.mdx b/apps/docs/pages/guides/versioning.mdx new file mode 100644 index 00000000..8770464e --- /dev/null +++ b/apps/docs/pages/guides/versioning.mdx @@ -0,0 +1,80 @@ +--- +title: Version & diff notebooks +description: Canonical .dql serialization, semantic diff, and the in-app git panel. +--- + +# Version & diff notebooks + +> ~4 minutes · ends with a clean PR-ready diff + +Notebooks are plain text in git. DQL's job is to keep the diffs **readable** +and the history **meaningful** — not line-noise from whitespace churn. + +## Canonical format + +Every `.dql` file starts with a version header: + +```dql +// dql-format: 1 + +block revenue_by_segment { + … +} +``` + +`dql fmt` normalizes whitespace, key order, and SQL formatting into a +canonical form. Run it in a pre-commit hook (or enable notebook autosave — +the notebook writes canonical on save). + +```bash +dql fmt blocks/**/*.dql # format in place +dql fmt --check . # CI: non-zero exit if anything drifts +``` + +## Semantic diff + +`git diff` works fine, but it's character-level. `dql diff` is +**AST-level** — it tells you *what changed semantically*, not *which bytes*. + +```bash +dql diff blocks/finance/revenue_by_segment.dql@HEAD~1 blocks/finance/revenue_by_segment.dql +``` + +Output: + +``` +~ block revenue_by_segment + tags: ["revenue"] → ["revenue", "certified"] + query: + - group by segment + + group by segment, region + visualization: bar → stacked-bar +``` + +Exits **1** on differences (scriptable like `git diff`). + +## In-app git panel + +Open the notebook sidebar → **Git** (⌘⇧G). You get three tabs: + +- **Status** — porcelain view of the working tree, color-coded +- **Log** — last 30 commits on this branch, click to diff +- **Diff** — full or scoped to the active file + +Read-only in v0.11; stage/commit/push lands in v0.12. + +## Run snapshots + +Executing cells writes a sibling `.run.json` with the last +results. On re-open, DQL rehydrates status/result/error without re-running +queries. + +Snapshots are **git-ignored by default** — `*.run.json` is appended to +`.gitignore` on first write. Un-ignore deliberately if you want to ship +executed state in git. + +## Verify it worked + +- `git diff` on a saved-but-unchanged notebook is empty (no whitespace churn) +- `dql fmt --check .` passes in CI +- `dql diff A B` produces a readable semantic report on two versions diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx new file mode 100644 index 00000000..ffc35d86 --- /dev/null +++ b/apps/docs/pages/index.mdx @@ -0,0 +1,42 @@ +--- +title: DQL — Analytics notebooks on your dbt models +--- + +import { Callout } from 'nextra/components'; + +# Analytics notebooks on your dbt models + +**Git-native. Local-first. Open source.** + +DQL sits between dbt (modeling) and your BI tool (reporting). You get +notebooks, certified blocks, a lineage DAG, and dashboards — all backed by +plain `.dql` files in git. + +```bash +npx create-dql-app my-project +cd my-project && npx @duckcodeailabs/dql-cli notebook +``` + + + **Demo gate:** from a clean machine to a Jaffle Shop dashboard in under five + minutes. If it takes longer, it's a bug — [open an + issue](https://github.com/duckcode-ai/dql/issues/new). + + +## Pick your path + +| You are… | Start here | +| --- | --- | +| **A dbt user** evaluating DQL | [Quickstart →](/get-started/quickstart/) | +| **Looking at an example** | [Jaffle Shop walkthrough →](/guides/jaffle-shop/) | +| **Integrating DQL** (connectors, charts, rules) | [Plugin API →](/architecture/plugin-api/) | +| **Contributing** | [Repo layout →](/contribute/repo-layout/) | + +## What you get + +- **Notebook** — SQL + DQL cells with live results, charts, and params +- **Block Studio** — governed, versioned analytics blocks with lint + certify +- **Semantic layer** — import dbt metrics/dimensions; author your own +- **Lineage DAG** — table · block · notebook granularity with impact analysis +- **Git-native format** — canonical `.dql` serialization, `dql diff`, in-app git panel +- **15 connectors** — Postgres, DuckDB, Snowflake, BigQuery, Redshift, MySQL, and more diff --git a/apps/docs/pages/reference/_meta.json b/apps/docs/pages/reference/_meta.json new file mode 100644 index 00000000..330de3db --- /dev/null +++ b/apps/docs/pages/reference/_meta.json @@ -0,0 +1,7 @@ +{ + "cli": "CLI", + "language": "DQL language", + "connectors": "Connectors", + "semantic-layer": "Semantic layer", + "file-formats": "File formats" +} diff --git a/apps/docs/pages/reference/cli.mdx b/apps/docs/pages/reference/cli.mdx new file mode 100644 index 00000000..340f3048 --- /dev/null +++ b/apps/docs/pages/reference/cli.mdx @@ -0,0 +1,97 @@ +--- +title: CLI reference +description: Every dql subcommand and flag. +--- + +import { Callout } from 'nextra/components'; + +# CLI reference + + + This page mirrors `dql --help`. The source of truth is + [`apps/cli/src/index.ts`](https://github.com/duckcode-ai/dql/blob/main/apps/cli/src/index.ts); + a CI job fails the build if this page drifts. + + +```bash +dql --help +dql --version +``` + +## Commands + +### Project + +| Command | What it does | +| --- | --- | +| `dql init [dir]` | Initialize DQL in a project (auto-detects dbt) | +| `dql doctor [path]` | Run local setup checks | +| `dql validate [path]` | Validate all `.dql` files and semantic references | +| `dql compile [path]` | Generate project manifest (`dql-manifest.json`) | + +### Authoring + +| Command | What it does | +| --- | --- | +| `dql new ` | Create a block, semantic block, dashboard, or workbook | +| `dql parse ` | Parse and analyze a DQL file | +| `dql info ` | Show block metadata | +| `dql certify ` | Evaluate certification rules | +| `dql fmt ` | Format a file in place (canonical form) | +| `dql fmt --check ` | CI check; exits 1 if anything needs formatting | + +### Diff & versioning + +| Command | What it does | +| --- | --- | +| `dql diff ` | AST-level semantic diff between two `.dql` files | + +Exits **1** on changes — scriptable like `git diff`. + +### Build & preview + +| Command | What it does | +| --- | --- | +| `dql build ` | Compile to a static HTML bundle | +| `dql preview ` | Local browser preview | +| `dql serve [dir]` | Serve a built bundle | +| `dql notebook [path]` | Launch the browser-first notebook | + +### Data & semantic + +| Command | What it does | +| --- | --- | +| `dql semantic list\|validate\|query\|pull` | Semantic layer operations | +| `dql sync dbt [path]` | Detect dbt manifest changes; update cache | +| `dql lineage [block] [path]` | Answer-layer lineage analysis | + +### Migration + +| Command | What it does | +| --- | --- | +| `dql migrate ` | Scaffold from looker, tableau, dbt, metabase, or raw-sql | + +## Global flags + +| Flag | Meaning | +| --- | --- | +| `--format json\|text` | Output format (default: text) | +| `--verbose` | Detailed output | +| `--open` / `--no-open` | Auto-open the browser on `preview`/`serve` | +| `--check` | For `fmt`: non-zero exit if changes needed | +| `--input ` | Source for scaffold-style migrations | +| `--out-dir ` | Output directory for `build` | +| `--port ` | Preferred local port for `preview`/`serve` | +| `--chart ` | Primary chart type for `new` scaffolds | +| `--domain ` | Domain for new block scaffolds | +| `--owner ` | Owner for new block scaffolds | +| `--query-only` | Create a query-only block (no visualization) | +| `--connection ` | Connection for `certify`/`test` (e.g. `duckdb`, `./db.duckdb`) | + +## Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success | +| `1` | Generic failure *or* `dql diff`/`dql fmt --check` found differences | +| `2` | Usage / argument error | diff --git a/apps/docs/pages/reference/connectors.mdx b/apps/docs/pages/reference/connectors.mdx new file mode 100644 index 00000000..03cafcdf --- /dev/null +++ b/apps/docs/pages/reference/connectors.mdx @@ -0,0 +1,81 @@ +--- +title: Connectors +description: 15 warehouse drivers, per-driver options. +--- + +# Connectors + +DQL ships 15 drivers out of the box. Each supports **query execution** and +**schema introspection** (tables, columns, types). Configure connections in +`cdql.yaml`. + +## Driver matrix + +| Driver | Ident | Auth | Notes | +| --- | --- | --- | --- | +| DuckDB | `duckdb` | file path | Default for local dev | +| MotherDuck | `motherduck` | token | Cloud DuckDB | +| Postgres | `postgres` | user/pass | Also aliases Aurora, Crunchy, etc. | +| MySQL | `mysql` | user/pass | | +| SQLite | `sqlite` | file path | | +| Snowflake | `snowflake` | user/pass, keypair | | +| BigQuery | `bigquery` | service account | `GOOGLE_APPLICATION_CREDENTIALS` | +| Redshift | `redshift` | user/pass | | +| ClickHouse | `clickhouse` | user/pass | | +| Databricks | `databricks` | PAT | | +| Trino | `trino` | user | | +| Athena | `athena` | AWS | | +| MSSQL | `mssql` | user/pass | | +| StarRocks | `starrocks` | user/pass | | +| Oracle | `oracle` | user/pass | | + +## Common options + +```yaml +connections: + default: + driver: postgres + host: prod-db.internal + port: 5432 + database: analytics + user: ${PGUSER} + password: ${PGPASSWORD} + schema: public + ssl: true # optional; most drivers honor it + pool: + max: 10 + idleTimeoutMs: 30000 +``` + +## Per-driver specifics + +### DuckDB + +```yaml +driver: duckdb +path: ./warehouse.duckdb # or :memory: +``` + +### BigQuery + +```yaml +driver: bigquery +projectId: my-gcp-project +location: US +# GOOGLE_APPLICATION_CREDENTIALS env var is read automatically +``` + +### Snowflake + +```yaml +driver: snowflake +account: xy12345.us-east-1 +user: ${SNOWFLAKE_USER} +password: ${SNOWFLAKE_PASSWORD} +warehouse: ANALYTICS_WH +database: PROD +role: ANALYST +``` + +For the full per-driver option list, see each connector's README under +[`packages/dql-connectors/src/`](https://github.com/duckcode-ai/dql/tree/main/packages/dql-connectors/src). diff --git a/apps/docs/pages/reference/file-formats.mdx b/apps/docs/pages/reference/file-formats.mdx new file mode 100644 index 00000000..eb6b1278 --- /dev/null +++ b/apps/docs/pages/reference/file-formats.mdx @@ -0,0 +1,93 @@ +--- +title: File formats +description: .dql, .dqlnb, .run.json, and cdql.yaml. +--- + +# File formats + +## `.dql` — the core format + +Canonical, git-friendly, version-headered. + +```dql +// dql-format: 1 + +block revenue_by_segment { + domain: "finance" + owner: "analytics@company.com" + tags: ["revenue"] + + query: | + select segment, sum(amount) as revenue + from @table("orders") + group by 1 + + visualization: bar(x: "segment", y: "revenue") +} +``` + +- **Version header** — `// dql-format: N` on line 1. Older files without a + header are still parsed; `dql fmt` adds the header on next save. +- **Canonical whitespace** — `dql fmt` enforces it. CI should run `dql fmt --check .` +- **Forward compatibility** — unknown top-level keys are preserved verbatim + +## `.dqlnb` — notebook bundle (legacy) + +Multi-cell notebooks shipped a bundled `.dqlnb` in early versions. From +v0.11 onwards, notebooks are plain `.dql` files with multiple cells. The +`.dqlnb` parser remains for backward compatibility. + +## `.run.json` — run snapshots + +Sibling file next to a notebook holding the last execution's results: + +```json +{ + "version": 1, + "notebookPath": "notebooks/overview.dql", + "capturedAt": "2026-04-15T12:34:56Z", + "cells": [ + { + "cellId": "abc123", + "status": "success", + "executionCount": 4, + "result": { "columns": [...], "rows": [...], "rowCount": 42 } + } + ] +} +``` + +- **Git-ignored by default** — DQL auto-appends `*.run.json` to `.gitignore` on first write +- **Used for rehydration** — re-opening a notebook loads results without re-running queries +- **Save trigger** — debounced 600ms after a cell's execution count / row count changes + +## `cdql.yaml` — project config + +Lives at the project root. + +```yaml +project: + name: jaffle-analytics + version: 1 + +connections: + default: + driver: duckdb + path: ./warehouse.duckdb + +dbt: + projectDir: ../jaffle-shop-dbt + include: + anchors: ["tag:core"] + maxDbtHops: 2 + +governance: + required_fields: [domain, owner, description] + certification_gates: [row_count_gt_zero, unique_pk] +``` + +## `dql-manifest.json` — build output + +Generated by `dql compile`. Declares every block/notebook/dashboard in the +project plus their lineage edges. Consumed by OpenLineage export and +third-party lineage tools. diff --git a/apps/docs/pages/reference/language.mdx b/apps/docs/pages/reference/language.mdx new file mode 100644 index 00000000..8340d32f --- /dev/null +++ b/apps/docs/pages/reference/language.mdx @@ -0,0 +1,77 @@ +--- +title: DQL language +description: Syntax, blocks, cells, references. +--- + +# DQL language + +The source of truth for the language lives in +[`docs/dql-language-spec.md`](https://github.com/duckcode-ai/dql/blob/main/docs/dql-language-spec.md). +This page summarizes the surface; the spec goes deeper on grammar and +evaluation order. + +## File structure + +```dql +// dql-format: 1 + +// Optional dashboard header +dashboard: { title: "…", layout: "grid" } + +// Optional params +param region: select(options: ["us", "eu"], default: "us") + +// Cells or blocks +--- +query: select count(*) from @table("orders") +visualization: kpi +--- +``` + +## Blocks + +```dql +block my_block { + domain: "finance" + owner: "team@company.com" + tags: ["tag1"] + description: "…" + + query: | + select … + + visualization: bar(x: "…", y: "…") + + tests: + - row_count > 0 + - unique: [id] +} +``` + +## References + +Inside `query` strings you can reference: + +| Syntax | Resolves to | +| --- | --- | +| `@table("name")` | A semantic table (dbt model or DQL-local) | +| `@metric("name")` | A semantic metric with SQL-level substitution | +| `@dim("cube.name")` | A dimension | +| `@block("name")` | An inline-compiled block | +| `@param("name")` | A notebook parameter value | + +## Visualizations + +Primary chart types: `bar`, `line`, `area`, `pie`, `donut`, `scatter`, +`heatmap`, `funnel`, `waterfall`, `histogram`, `gauge`, `stacked-bar`, +`grouped-bar`, `kpi`, `table`. + +Each takes an options object: + +```dql +visualization: line(x: "date", y: "revenue", color: "segment", + title: "Daily revenue", legendPosition: "bottom") +``` + +For the full grammar and type system, see the +[language spec](https://github.com/duckcode-ai/dql/blob/main/docs/dql-language-spec.md). diff --git a/apps/docs/pages/reference/semantic-layer.mdx b/apps/docs/pages/reference/semantic-layer.mdx new file mode 100644 index 00000000..267b1bdb --- /dev/null +++ b/apps/docs/pages/reference/semantic-layer.mdx @@ -0,0 +1,74 @@ +--- +title: Semantic layer +description: Metrics, dimensions, hierarchies, cubes — imported from dbt or authored locally. +--- + +# Semantic layer + +The semantic layer is DQL's shared business vocabulary. It's a **superset** +of dbt's MetricFlow — DQL imports dbt's semantic models as-is and lets you +extend them with DQL-local definitions. + +## Object types + +| Object | Purpose | Example | +| --- | --- | --- | +| **Metric** | A measure with aggregation | `sum(orders.amount)` | +| **Dimension** | An attribute to slice by | `customers.segment` | +| **Hierarchy** | An ordered drill path | `year → quarter → month → day` | +| **Cube** | A pre-joined fact+dim set | `orders_by_customer_region` | +| **Segment** | A named boolean filter | `active_customers` | + +## YAML shape (DQL-local) + +```yaml +# semantic/finance.yaml +metrics: + - name: revenue + label: Revenue + description: Gross revenue + type: sum + sql: amount + table: orders + domain: finance + +dimensions: + - name: segment + label: Customer segment + type: string + sql: segment + table: customers + +hierarchies: + - name: time + levels: + - { name: year, label: Year } + - { name: quarter, label: Quarter } + - { name: month, label: Month } + - { name: day, label: Day } +``` + +## dbt import + +DQL reads `target/manifest.json` directly — see +[Import a dbt project](/guides/import-dbt/). Imported metrics and +dimensions appear in the **Semantic** panel alongside DQL-local ones. + +## CLI + +```bash +dql semantic list # list everything +dql semantic validate # check all refs resolve +dql semantic query 'revenue by segment in 2024' # NL → SQL preview +dql semantic pull # re-sync from dbt +``` + +## Referencing from cells + +```dql +// In a DQL cell +@metric("revenue") by @dim("customer.segment") for 2024 +``` + +The compiler resolves refs, joins the needed tables, and emits SQL for the +target warehouse dialect. diff --git a/apps/docs/scripts/link-check.mjs b/apps/docs/scripts/link-check.mjs new file mode 100755 index 00000000..3bfa6d71 --- /dev/null +++ b/apps/docs/scripts/link-check.mjs @@ -0,0 +1,55 @@ +#!/usr/bin/env node +// Simple internal-link checker. Scans every .mdx under pages/ for markdown +// links like [text](/path/) and verifies each target resolves to an mdx +// file or directory with an index.mdx. Run in CI to catch rot. +import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'; +import { join, resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PAGES = resolve(__dirname, '..', 'pages'); + +function walk(dir) { + const out = []; + for (const entry of readdirSync(dir)) { + const p = join(dir, entry); + const st = statSync(p); + if (st.isDirectory()) out.push(...walk(p)); + else if (entry.endsWith('.mdx') || entry.endsWith('.md')) out.push(p); + } + return out; +} + +function resolveTarget(target) { + // strip trailing slash + #anchor + const clean = target.replace(/#.*$/, '').replace(/\/$/, ''); + if (!clean) return true; // pure anchor + const base = join(PAGES, clean); + if (existsSync(`${base}.mdx`)) return true; + if (existsSync(`${base}.md`)) return true; + if (existsSync(`${base}/index.mdx`)) return true; + if (existsSync(`${base}/index.md`)) return true; + return false; +} + +const files = walk(PAGES); +const errors = []; +const linkRe = /\[[^\]]+\]\((\/[^)]+)\)/g; + +for (const f of files) { + const content = readFileSync(f, 'utf-8'); + for (const match of content.matchAll(linkRe)) { + const href = match[1]; + if (href.startsWith('http')) continue; + if (!resolveTarget(href)) { + errors.push(`${f.replace(PAGES, 'pages')}: broken link ${href}`); + } + } +} + +if (errors.length) { + console.error(`\n${errors.length} broken internal link(s):\n`); + for (const e of errors) console.error(' ' + e); + process.exit(1); +} +console.log(`✓ ${files.length} pages checked, 0 broken internal links`); diff --git a/apps/docs/theme.config.tsx b/apps/docs/theme.config.tsx new file mode 100644 index 00000000..79d00402 --- /dev/null +++ b/apps/docs/theme.config.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type { DocsThemeConfig } from 'nextra-theme-docs'; + +const config: DocsThemeConfig = { + logo: ( + + DQL docs + + ), + project: { + link: 'https://github.com/duckcode-ai/dql', + }, + docsRepositoryBase: 'https://github.com/duckcode-ai/dql/tree/main/apps/docs', + footer: { + text: ( + + MIT © {new Date().getFullYear()} DuckCode AI Labs — analytics notebooks on your dbt models. + + ), + }, + sidebar: { + defaultMenuCollapseLevel: 1, + toggleButton: true, + }, + toc: { + backToTop: true, + }, + darkMode: true, + nextThemes: { + defaultTheme: 'dark', + }, + head: ( + <> + + + + + ), + useNextSeoProps() { + return { titleTemplate: '%s – DQL' }; + }, +}; + +export default config; diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json new file mode 100644 index 00000000..9832d3cb --- /dev/null +++ b/apps/docs/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "out", ".next"] +} diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json new file mode 100644 index 00000000..1ae6ece2 --- /dev/null +++ b/apps/docs/vercel.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "pnpm -F @duckcodeailabs/docs build", + "outputDirectory": "out", + "installCommand": "pnpm install --frozen-lockfile", + "framework": null, + "cleanUrls": true, + "trailingSlash": false, + "headers": [ + { + "source": "/(.*)", + "headers": [ + { "key": "X-Content-Type-Options", "value": "nosniff" }, + { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" } + ] + } + ] +} diff --git a/apps/dql-notebook/package.json b/apps/dql-notebook/package.json index f3dd4333..561084ac 100644 --- a/apps/dql-notebook/package.json +++ b/apps/dql-notebook/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-notebook-app", - "version": "0.10.1", + "version": "1.0.1", "private": true, "type": "module", "scripts": { diff --git a/apps/dql-notebook/src/api/client.ts b/apps/dql-notebook/src/api/client.ts index acd84e41..761c3252 100644 --- a/apps/dql-notebook/src/api/client.ts +++ b/apps/dql-notebook/src/api/client.ts @@ -1,6 +1,7 @@ import type { NotebookFile, QueryResult, + RunSnapshot, SchemaTable, SchemaColumn, SemanticLayerState, @@ -573,4 +574,53 @@ export const api = { return null; } }, + + async fetchGitStatus(): Promise<{ + inRepo: boolean; + branch: string | null; + ahead: number; + behind: number; + changes: Array<{ path: string; status: string }>; + }> { + try { + return await request('/api/git/status'); + } catch { + return { inRepo: false, branch: null, ahead: 0, behind: 0, changes: [] }; + } + }, + + async fetchGitLog(limit = 20): Promise<{ + inRepo: boolean; + commits: Array<{ hash: string; author: string; date: string; subject: string }>; + }> { + try { + return await request(`/api/git/log?limit=${limit}`); + } catch { + return { inRepo: false, commits: [] }; + } + }, + + async fetchRunSnapshot(path: string): Promise<{ found: boolean; snapshot: RunSnapshot | null }> { + try { + return await request(`/api/run-snapshot?path=${encodeURIComponent(path)}`); + } catch { + return { found: false, snapshot: null }; + } + }, + + async saveRunSnapshot(path: string, snapshot: RunSnapshot): Promise { + await request('/api/run-snapshot', { + method: 'PUT', + body: JSON.stringify({ path, snapshot }), + }); + }, + + async fetchGitDiff(path?: string): Promise<{ inRepo: boolean; diff: string }> { + try { + const qs = path ? `?path=${encodeURIComponent(path)}` : ''; + return await request(`/api/git/diff${qs}`); + } catch { + return { inRepo: false, diff: '' }; + } + }, }; diff --git a/apps/dql-notebook/src/components/layout/ActivityBar.tsx b/apps/dql-notebook/src/components/layout/ActivityBar.tsx index fd02dbf2..d3e89475 100644 --- a/apps/dql-notebook/src/components/layout/ActivityBar.tsx +++ b/apps/dql-notebook/src/components/layout/ActivityBar.tsx @@ -60,14 +60,6 @@ function SchemaIcon() { ); } -function SemanticIcon() { - return ( - - - - ); -} - function LineageIcon() { return ( @@ -100,6 +92,14 @@ function HelpIcon() { ); } +function GitIcon() { + return ( + + + + ); +} + function SettingsIcon() { return ( @@ -111,7 +111,6 @@ function SettingsIcon() { export function ActivityBar() { const { state, dispatch } = useNotebook(); const t = themes[state.themeMode]; - const showSemanticButton = state.mainView !== 'block_studio'; function handlePanelClick(panel: SidebarPanel) { const fullPagePanel = panel === 'connection' || panel === 'reference'; @@ -160,17 +159,6 @@ export function ActivityBar() { - {showSemanticButton && ( - handlePanelClick('semantic')} - t={t} - > - - - )} - + handlePanelClick('git')} + t={t} + > + + + {/* Spacer pushes remaining items to bottom */}
diff --git a/apps/dql-notebook/src/components/layout/AppShell.tsx b/apps/dql-notebook/src/components/layout/AppShell.tsx index 03e29728..06708de4 100644 --- a/apps/dql-notebook/src/components/layout/AppShell.tsx +++ b/apps/dql-notebook/src/components/layout/AppShell.tsx @@ -18,6 +18,7 @@ import { api } from '../../api/client'; import { parseNotebookFile } from '../../utils/parse-workbook'; import { makeCell } from '../../store/NotebookStore'; import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts'; +import { useRunSnapshotAutosave } from '../../hooks/useRunSnapshotAutosave'; import type { NotebookFile } from '../../store/types'; export function AppShell() { @@ -29,6 +30,8 @@ export function AppShell() { // Global keyboard shortcuts useKeyboardShortcuts(); + // Debounced autosave of cell results to .run.json + useRunSnapshotAutosave(); useEffect(() => { const handler = (e: KeyboardEvent) => { @@ -55,7 +58,25 @@ export function AppShell() { } const { content } = await api.readNotebook(file.path); const { title, cells } = parseNotebookFile(file.path, content); - dispatch({ type: 'OPEN_FILE', file, cells, title }); + + // Hydrate last-run results from sibling .run.json so the notebook + // shows executed output without forcing a re-run on open. + const snap = await api.fetchRunSnapshot(file.path); + const hydrated = snap.found && snap.snapshot + ? cells.map((c) => { + const entry = snap.snapshot!.cells.find((e) => e.cellId === c.id); + if (!entry) return c; + return { + ...c, + status: entry.status ?? c.status, + result: entry.result ?? c.result, + error: entry.error ?? c.error, + executionCount: entry.executionCount ?? c.executionCount, + }; + }) + : cells; + + dispatch({ type: 'OPEN_FILE', file, cells: hydrated, title }); // Ensure files panel is visible if (state.sidebarPanel !== 'files') { dispatch({ type: 'SET_SIDEBAR_PANEL', panel: 'files' }); diff --git a/apps/dql-notebook/src/components/layout/Sidebar.tsx b/apps/dql-notebook/src/components/layout/Sidebar.tsx index 1cac9a85..01b749c3 100644 --- a/apps/dql-notebook/src/components/layout/Sidebar.tsx +++ b/apps/dql-notebook/src/components/layout/Sidebar.tsx @@ -5,9 +5,9 @@ import { FilesPanel } from '../sidebar/FilesPanel'; import { SchemaPanel } from '../sidebar/SchemaPanel'; import { ConnectionPanel } from '../sidebar/ConnectionPanel'; import { ReferencePanel } from '../sidebar/ReferencePanel'; -import { SemanticPanel } from '../sidebar/SemanticPanel'; import { LineagePanel } from '../sidebar/LineagePanel'; import { BlockLibraryPanel } from '../sidebar/BlockLibraryPanel'; +import { GitPanel } from '../sidebar/GitPanel'; import type { NotebookFile } from '../../store/types'; interface SidebarProps { @@ -18,10 +18,10 @@ const PANEL_TITLES: Record = { files: 'Explorer', schema: 'Schema', block_library: 'Block Library', - semantic: 'Semantic Layer', lineage: 'Lineage', connection: 'Connection', reference: 'Quick Reference', + git: 'Git', }; export function Sidebar({ onOpenFile }: SidebarProps) { @@ -29,9 +29,7 @@ export function Sidebar({ onOpenFile }: SidebarProps) { const t = themes[state.themeMode]; const [collapseHover, setCollapseHover] = useState(false); - const panel = state.mainView === 'block_studio' && state.sidebarPanel === 'semantic' - ? 'files' - : state.sidebarPanel; + const panel = state.sidebarPanel; return (
} {panel === 'schema' && } {panel === 'block_library' && } - {panel === 'semantic' && } {panel === 'lineage' && } {panel === 'connection' && } {panel === 'reference' && } + {panel === 'git' && }
); diff --git a/apps/dql-notebook/src/components/palette/CommandPalette.tsx b/apps/dql-notebook/src/components/palette/CommandPalette.tsx index b081d580..e77e8aa0 100644 --- a/apps/dql-notebook/src/components/palette/CommandPalette.tsx +++ b/apps/dql-notebook/src/components/palette/CommandPalette.tsx @@ -55,8 +55,8 @@ export function CommandPalette({ const panels: Array<[string, NonNullable]> = [ ['Files', 'files'], ['Schema', 'schema'], - ['Semantic Layer', 'semantic'], ['Lineage', 'lineage'], + ['Git', 'git'], ['Block Library', 'block_library'], ['Connections', 'connection'], ['Reference', 'reference'], diff --git a/apps/dql-notebook/src/components/sidebar/GitPanel.tsx b/apps/dql-notebook/src/components/sidebar/GitPanel.tsx new file mode 100644 index 00000000..dfff840f --- /dev/null +++ b/apps/dql-notebook/src/components/sidebar/GitPanel.tsx @@ -0,0 +1,234 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { useNotebook } from '../../store/NotebookStore'; +import { themes } from '../../themes/notebook-theme'; +import { api } from '../../api/client'; + +type Status = Awaited>; +type LogResult = Awaited>; + +type Tab = 'status' | 'log' | 'diff'; + +export function GitPanel() { + const { state } = useNotebook(); + const t = themes[state.themeMode]; + + const [tab, setTab] = useState('status'); + const [status, setStatus] = useState(null); + const [log, setLog] = useState(null); + const [diff, setDiff] = useState(''); + const [diffPath, setDiffPath] = useState(null); + const [loading, setLoading] = useState(false); + + const refresh = useCallback(async () => { + setLoading(true); + try { + if (tab === 'status') { + setStatus(await api.fetchGitStatus()); + } else if (tab === 'log') { + setLog(await api.fetchGitLog(30)); + } else { + const result = await api.fetchGitDiff(diffPath ?? undefined); + setDiff(result.diff); + } + } finally { + setLoading(false); + } + }, [tab, diffPath]); + + useEffect(() => { + void refresh(); + }, [refresh]); + + const activeFilePath = state.activeFile?.path ?? null; + + return ( +
+
+ setTab('status')}>Status + setTab('log')}>Log + setTab('diff')}>Diff +
+ +
+ +
+ {loading &&
Loading…
} + + {!loading && tab === 'status' && status && ( + + )} + + {!loading && tab === 'log' && log && ( + + )} + + {!loading && tab === 'diff' && ( + setDiffPath(activeFilePath)} + onClearScope={() => setDiffPath(null)} + t={t} + /> + )} +
+
+ ); +} + +function StatusView({ status, t }: { status: Status; t: ReturnType }) { + if (!status.inRepo) { + return
Not a git repository.
; + } + return ( +
+
+ Branch{' '} + {status.branch ?? 'detached'} + {status.ahead > 0 && ↑ {status.ahead}} + {status.behind > 0 && ↓ {status.behind}} +
+ {status.changes.length === 0 ? ( +
Working tree clean.
+ ) : ( +
+ {status.changes.map((c) => ( +
+ {c.status.trim() || '??'} + + {c.path} + +
+ ))} +
+ )} +
+ ); +} + +function LogView({ log, t }: { log: LogResult; t: any }) { + if (!log.inRepo) { + return
Not a git repository.
; + } + if (log.commits.length === 0) { + return
No commits.
; + } + return ( +
+ {log.commits.map((c) => ( +
+
{c.subject}
+
+ {c.hash.slice(0, 7)} · {c.author} · {c.date} +
+
+ ))} +
+ ); +} + +function DiffView({ + diff, activeFilePath, diffPath, onScopeToFile, onClearScope, t, +}: { + diff: string; activeFilePath: string | null; diffPath: string | null; + onScopeToFile: () => void; onClearScope: () => void; t: any; +}) { + return ( +
+
+ + {activeFilePath && ( + + )} + {diffPath && ( + + {diffPath} + + )} +
+ {diff.trim() === '' ? ( +
No unstaged changes.
+ ) : ( +
+          {diff.split('\n').map((line, i) => (
+            
{line || ' '}
+ ))} +
+ )} +
+ ); +} + +function TabButton({ active, onClick, children, t }: { active: boolean; onClick: () => void; children: React.ReactNode; t: any }) { + return ( + + ); +} + +function scopeBtn(active: boolean, t: any): React.CSSProperties { + return { + background: active ? t.btnHover : 'transparent', + color: active ? t.textPrimary : t.textMuted, + border: `1px solid ${t.headerBorder}`, + padding: '2px 8px', + borderRadius: 4, + fontSize: 11, + cursor: 'pointer', + }; +} + +function statusColor(code: string, t: any): string { + const c = code.trim(); + if (c === 'M' || c === 'MM') return t.warning; + if (c === 'A' || c === '??') return t.success; + if (c === 'D') return t.error; + if (c.startsWith('R')) return t.accent; + return t.textMuted; +} + +function diffLineColor(line: string, t: any): string { + if (line.startsWith('+++') || line.startsWith('---')) return t.textMuted; + if (line.startsWith('+')) return t.success; + if (line.startsWith('-')) return t.error; + if (line.startsWith('@@')) return t.accent; + return t.textPrimary; +} diff --git a/apps/dql-notebook/src/hooks/useRunSnapshotAutosave.ts b/apps/dql-notebook/src/hooks/useRunSnapshotAutosave.ts new file mode 100644 index 00000000..e8143bce --- /dev/null +++ b/apps/dql-notebook/src/hooks/useRunSnapshotAutosave.ts @@ -0,0 +1,74 @@ +import { useEffect, useRef } from 'react'; +import { useNotebook } from '../store/NotebookStore'; +import { api } from '../api/client'; +import type { Cell, RunSnapshot, RunSnapshotCell } from '../store/types'; + +const DEBOUNCE_MS = 600; + +function buildSnapshot(path: string, cells: Cell[]): RunSnapshot { + const entries: RunSnapshotCell[] = cells + .filter((c) => c.result || c.error || (c.executionCount ?? 0) > 0) + .map((c) => ({ + cellId: c.id, + status: c.status, + result: c.result, + error: c.error, + executionCount: c.executionCount, + executedAt: new Date().toISOString(), + })); + return { + version: 1, + notebookPath: path, + capturedAt: new Date().toISOString(), + cells: entries, + }; +} + +function snapshotSignature(cells: Cell[]): string { + // Cheap change-detector — hash of (cellId, executionCount, rowCount, errorLen). + // Avoids saving when nothing materially changed (e.g. pure UI edits). + return cells + .map((c) => `${c.id}:${c.executionCount ?? 0}:${c.result?.rowCount ?? -1}:${(c.error ?? '').length}`) + .join('|'); +} + +/** + * Watches the current notebook's cells for result changes and debounce-saves + * a `.run.json` sibling so the next open can rehydrate without re-executing. + * Only fires when an actual execution landed (signature change); pure edits + * don't trigger writes. + */ +export function useRunSnapshotAutosave(): void { + const { state } = useNotebook(); + const timer = useRef | null>(null); + const lastSignature = useRef(''); + const lastPath = useRef(null); + + useEffect(() => { + const path = state.activeFile?.path ?? null; + if (!path) return; + + // When the notebook changes, reset the signature so we don't save stale. + if (path !== lastPath.current) { + lastPath.current = path; + lastSignature.current = snapshotSignature(state.cells); + return; + } + + const sig = snapshotSignature(state.cells); + if (sig === lastSignature.current) return; + lastSignature.current = sig; + + if (timer.current) clearTimeout(timer.current); + timer.current = setTimeout(() => { + const snapshot = buildSnapshot(path, state.cells); + void api.saveRunSnapshot(path, snapshot).catch(() => { + // Best-effort; snapshot saves should never surface errors to the user. + }); + }, DEBOUNCE_MS); + + return () => { + if (timer.current) clearTimeout(timer.current); + }; + }, [state.cells, state.activeFile?.path]); +} diff --git a/apps/dql-notebook/src/store/NotebookStore.tsx b/apps/dql-notebook/src/store/NotebookStore.tsx index 04b91b3a..bea9187f 100644 --- a/apps/dql-notebook/src/store/NotebookStore.tsx +++ b/apps/dql-notebook/src/store/NotebookStore.tsx @@ -132,7 +132,6 @@ function notebookReducer(state: NotebookState, action: NotebookAction): Notebook notebookTitle: action.payload.metadata.name, notebookDirty: false, mainView: 'block_studio', - sidebarPanel: state.sidebarPanel === 'semantic' ? 'files' : state.sidebarPanel, dashboardMode: false, activeBlockPath: action.payload.path, blockStudioDraft: action.payload.source, diff --git a/apps/dql-notebook/src/store/types.ts b/apps/dql-notebook/src/store/types.ts index 6e0781a4..8b909e84 100644 --- a/apps/dql-notebook/src/store/types.ts +++ b/apps/dql-notebook/src/store/types.ts @@ -23,7 +23,7 @@ export interface ParamConfig { defaultValue: string; options?: string[]; } -export type SidebarPanel = 'files' | 'schema' | 'block_library' | 'connection' | 'reference' | 'semantic' | 'lineage' | null; +export type SidebarPanel = 'files' | 'schema' | 'block_library' | 'connection' | 'reference' | 'lineage' | 'git' | null; export type DevPanelTab = 'logs' | 'errors'; export type MainView = 'notebook' | 'block_studio' | 'connection' | 'reference'; @@ -34,6 +34,22 @@ export interface QueryResult { rowCount?: number; } +export interface RunSnapshotCell { + cellId: string; + status: CellStatus; + result?: QueryResult; + error?: string; + executionCount?: number; + executedAt?: string; +} + +export interface RunSnapshot { + version: 1; + notebookPath: string; + capturedAt: string; + cells: RunSnapshotCell[]; +} + export interface Cell { id: string; type: CellType; diff --git a/apps/dql-notebook/src/themes/notebook-theme.ts b/apps/dql-notebook/src/themes/notebook-theme.ts index d8634e66..1e2b240b 100644 --- a/apps/dql-notebook/src/themes/notebook-theme.ts +++ b/apps/dql-notebook/src/themes/notebook-theme.ts @@ -37,79 +37,80 @@ export type Theme = { fontSerif: string; }; +// v0.10 restyle — tighter contrast, hex-inspired near-black shell, refined accent. export const DARK = { - appBg: '#0d1117', - sidebarBg: '#161b22', - activityBarBg: '#0d1117', - headerBg: '#161b22', - headerBorder: '#21262d', - cellBg: '#161b22', - cellBorder: '#30363d', - cellBorderActive: '#388bfd', - cellBorderRunning: '#56d364', - textPrimary: '#e6edf3', - textSecondary: '#8b949e', - textMuted: '#484f58', - editorBg: '#0d1117', - editorBorder: '#30363d', - tableBorder: '#30363d', - tableHeaderBg: '#1c2128', - tableRowHover: '#21262d', - accent: '#388bfd', - accentHover: '#58a6ff', - success: '#56d364', - error: '#f85149', - warning: '#e3b341', - btnBg: '#21262d', - btnBorder: '#30363d', - btnHover: '#30363d', - sidebarItemHover: '#21262d', - sidebarItemActive: '#1f2d3d', - scrollbarThumb: '#30363d', - modalBg: '#161b22', - modalOverlay: 'rgba(0,0,0,0.7)', - inputBg: '#0d1117', - inputBorder: '#30363d', - pillBg: '#21262d', + appBg: '#0a0a0c', + sidebarBg: '#111114', + activityBarBg: '#08080a', + headerBg: '#111114', + headerBorder: '#1d1d22', + cellBg: '#131317', + cellBorder: '#26262d', + cellBorderActive: '#5b8def', + cellBorderRunning: '#4ade80', + textPrimary: '#f4f4f5', + textSecondary: '#a0a0a8', + textMuted: '#5a5a65', + editorBg: '#0a0a0c', + editorBorder: '#26262d', + tableBorder: '#26262d', + tableHeaderBg: '#17171c', + tableRowHover: '#1a1a20', + accent: '#5b8def', + accentHover: '#7aa5ff', + success: '#4ade80', + error: '#f87171', + warning: '#fbbf24', + btnBg: '#1a1a1f', + btnBorder: '#26262d', + btnHover: '#26262d', + sidebarItemHover: '#1a1a20', + sidebarItemActive: '#1f2937', + scrollbarThumb: '#26262d', + modalBg: '#111114', + modalOverlay: 'rgba(0,0,0,0.75)', + inputBg: '#0a0a0c', + inputBorder: '#26262d', + pillBg: '#1a1a1f', font: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", fontMono: "'JetBrains Mono', 'Fira Code', Consolas, monospace", fontSerif: "Georgia, 'Times New Roman', serif", } as const; export const LIGHT = { - appBg: '#ffffff', - sidebarBg: '#f6f8fa', - activityBarBg: '#eff1f3', + appBg: '#fafafa', + sidebarBg: '#f3f4f6', + activityBarBg: '#ececef', headerBg: '#ffffff', - headerBorder: '#d0d7de', + headerBorder: '#e4e4e7', cellBg: '#ffffff', - cellBorder: '#d0d7de', - cellBorderActive: '#0969da', - cellBorderRunning: '#1f883d', - textPrimary: '#1f2328', - textSecondary: '#57606a', - textMuted: '#8c959f', - editorBg: '#f6f8fa', - editorBorder: '#d0d7de', - tableBorder: '#d0d7de', - tableHeaderBg: '#f6f8fa', - tableRowHover: '#f6f8fa', - accent: '#0969da', - accentHover: '#0550ae', - success: '#1f883d', - error: '#cf222e', - warning: '#9a6700', - btnBg: '#f6f8fa', - btnBorder: '#d0d7de', - btnHover: '#eaeef2', - sidebarItemHover: '#eaeef2', - sidebarItemActive: '#dbeafe', - scrollbarThumb: '#d0d7de', + cellBorder: '#e4e4e7', + cellBorderActive: '#4f46e5', + cellBorderRunning: '#16a34a', + textPrimary: '#18181b', + textSecondary: '#52525b', + textMuted: '#a1a1aa', + editorBg: '#fafafa', + editorBorder: '#e4e4e7', + tableBorder: '#e4e4e7', + tableHeaderBg: '#f3f4f6', + tableRowHover: '#f3f4f6', + accent: '#4f46e5', + accentHover: '#4338ca', + success: '#16a34a', + error: '#dc2626', + warning: '#ca8a04', + btnBg: '#f3f4f6', + btnBorder: '#e4e4e7', + btnHover: '#e4e4e7', + sidebarItemHover: '#e4e4e7', + sidebarItemActive: '#e0e7ff', + scrollbarThumb: '#d4d4d8', modalBg: '#ffffff', modalOverlay: 'rgba(0,0,0,0.4)', inputBg: '#ffffff', - inputBorder: '#d0d7de', - pillBg: '#eaeef2', + inputBorder: '#e4e4e7', + pillBg: '#f3f4f6', font: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", fontMono: "'JetBrains Mono', 'Fira Code', Consolas, monospace", fontSerif: "Georgia, 'Times New Roman', serif", diff --git a/apps/vscode-extension/package.json b/apps/vscode-extension/package.json index 555a0044..b3d4f81e 100644 --- a/apps/vscode-extension/package.json +++ b/apps/vscode-extension/package.json @@ -2,7 +2,7 @@ "name": "dql-language-support", "displayName": "DQL Language Support", "description": "Syntax highlighting, snippets, formatting, and language server support for DQL.", - "version": "0.10.1", + "version": "1.0.1", "publisher": "dql", "license": "Apache-2.0", "repository": { diff --git a/docs/README.md b/docs/README.md index cdf47c70..cc8624a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,53 +1,30 @@ -# DQL Docs +# docs/ -This docs folder is organized for two audiences: +**The user-facing docs have moved to [`apps/docs/`](../apps/docs/) (Nextra). +Site: [docs.duckcode.ai](https://docs.duckcode.ai).** -- new users who need a clear start-to-finish path -- contributors and maintainers who need source-testing and release guidance +This folder now holds only: -If you are new to DQL, follow these sections in order. +- **Long-form references** not yet ported — e.g. [`dql-language-spec.md`](./dql-language-spec.md) +- **Maintainer / internal docs** — [`publishing.md`](./publishing.md), [`repo-testing.md`](./repo-testing.md), [`oss-readiness-checklist.md`](./oss-readiness-checklist.md), [`v1-support-scope.md`](./v1-support-scope.md) +- **Migration notes** — [`migration-guides/`](./migration-guides/) +- **Example notebooks** — [`examples/`](./examples/) -## 1. Start Here +New documentation goes in `apps/docs/pages/`, not here. -- [Start Here Overview](./01-start-here/README.md) -- [Install and First Project](./01-start-here/install-and-first-project.md) -- [Run From Source](./01-start-here/run-from-source.md) -- [dbt + Jaffle Shop Walkthrough](./01-start-here/dbt-jaffle-shop.md) +## Canonical IA (in `apps/docs`) -## 2. Learn The Product +``` +Get Started /get-started/ install · quickstart · concepts +Guides /guides/ task-oriented walkthroughs +Reference /reference/ CLI · language · connectors · file formats · semantic layer +Architecture /architecture/ overview · dbt-integration · lineage-model · plugin-api · openlineage +Contribute /contribute/ repo-layout · testing · releasing +``` -- [Core Workflows Overview](./02-core-workflows/README.md) -- [Notebook Workflow](./02-core-workflows/notebook-workflow.md) -- [Block Authoring Workflow](./02-core-workflows/block-authoring-workflow.md) -- [Semantic Layer Workflow](./02-core-workflows/semantic-layer-workflow.md) -- [Lineage Workflow](./02-core-workflows/lineage-workflow.md) +## Running the site locally -## 3. Guides - -- [Guides Overview](./03-guides/README.md) -- [Demo Assets](./examples/README.md) -- [Data Sources Guide](./data-sources.md) -- [Project Structure](./project-structure.md) -- [Project Config](./project-config.md) -- [Examples and Use Cases](./examples.md) -- [FAQ](./faq.md) - -## 4. Reference - -- [Reference Overview](./04-reference/README.md) -- [CLI Reference](./cli-reference.md) -- [DQL Language Spec](./dql-language-spec.md) -- [Compatibility](./compatibility.md) -- [V1 Support Scope](./v1-support-scope.md) - -## 5. Migration And Maintainers - -- [Migration Guides](./migration-guides/README.md) -- [Maintainer Docs Overview](./05-maintainers/README.md) -- [Repo Testing](./repo-testing.md) -- [Publishing](./publishing.md) -- [OSS Readiness Checklist](./oss-readiness-checklist.md) - -## Existing Detailed Docs - -The original top-level docs remain in place so current links in the repo continue to work. The new ordered sections above are the recommended reading path. +```bash +pnpm -F @duckcodeailabs/docs dev +# http://localhost:3030 +``` diff --git a/docs/authoring-blocks.md b/docs/authoring-blocks.md deleted file mode 100644 index 8100a931..00000000 --- a/docs/authoring-blocks.md +++ /dev/null @@ -1,445 +0,0 @@ -# Authoring Blocks - -A DQL block is a single `.dql` file that contains everything needed to produce a trusted, reusable analytics answer: SQL or semantic reference, visualization config, ownership metadata, parameters, and tests — all in one place, all Git-trackable. - -This guide walks through the full lifecycle: **create → validate → preview → certify → commit**. - ---- - -## What Is a DQL Block? - -``` -blocks/revenue_by_segment.dql - └── block "Revenue by Segment" - ├── metadata (domain, owner, description, tags) - ├── params (runtime variables with defaults) - ├── query (SQL or semantic reference) - ├── visualization (chart type, axes) - └── tests (assertions that run on query results) -``` - -Blocks are the unit of governance in DQL. When a block is certified and committed, your team can trust that it has an owner, a description, tags for discoverability, and at least one test that protects against silent data failures. - ---- - -## Two Block Types - -### `type = "custom"` — You own the SQL - -Use this when you have complex joins, window functions, or logic that doesn't fit a standard metric definition. You write the SQL directly in the block. - -```dql -block "Funnel Drop-off" { - type = "custom" - owner = "growth-team" - query = """ - SELECT stage, COUNT(*) AS users, LAG(COUNT(*)) OVER (ORDER BY stage_order) AS prev - FROM funnel_events GROUP BY stage, stage_order ORDER BY stage_order - """ -} -``` - -### `type = "semantic"` — References your semantic layer - -Use this when the answer already exists as a metric in your semantic layer (DQL YAML, dbt, or Cube.js). No SQL duplication — the block references a named metric and dimensions, and DQL composes the query at runtime. - -```dql -block "ARR by Plan Tier" { - type = "semantic" - metric = "arr" - dimensions = ["plan_tier"] - owner = "finance-team" -} -``` - -**When to use which:** -- If the metric already exists in your semantic layer → use `semantic` -- If you need custom joins, CTEs, or logic the semantic layer doesn't express → use `custom` - ---- - -## Tutorial: Create a Custom Block - -### Step 1 — Scaffold the file - -```bash -dql new block "Revenue by Segment" -# Creates: blocks/revenue_by_segment.dql -``` - -Open `blocks/revenue_by_segment.dql`. It contains a scaffold you'll fill in: - -```dql -block "Revenue by Segment" { - domain = "" - type = "custom" - owner = "" - description = "" - tags = [] - - params { - } - - query = """ - """ - - visualization { - chart = "table" - } - - tests { - } -} -``` - -### Step 2 — Fill in the fields - -```dql -block "Revenue by Segment" { - domain = "revenue" - type = "custom" - owner = "data-team" - description = "GMV and order count broken out by customer segment and region, filterable by fiscal period" - tags = ["revenue", "segment", "region", "quarterly"] - - params { - period = "current_quarter" - } - - query = """ - SELECT - segment_tier AS segment, - region, - SUM(amount) AS revenue, - COUNT(*) AS orders - FROM fct_revenue - WHERE fiscal_period = ${period} - GROUP BY segment_tier, region - ORDER BY revenue DESC - """ - - visualization { - chart = "bar" - x = segment - y = revenue - } - - tests { - assert row_count > 0 - assert revenue > 0 - } -} -``` - -**Field reference:** - -| Field | Required | Description | -|---|---|---| -| `domain` | Yes (for certify) | Business domain — e.g. `"revenue"`, `"product"`, `"ops"` | -| `type` | No (defaults to `"custom"`) | `"custom"` or `"semantic"` | -| `owner` | Yes (for certify) | Team or person responsible — e.g. `"data-team"` | -| `description` | Yes (for certify) | What this block measures and how to use it | -| `tags` | Yes (for certify) | Search/filter labels | -| `params` | No | Key-value defaults for `${variable}` substitution in the query | -| `query` | Yes | SQL string (for `type = "custom"`) | -| `visualization` | No | Chart config — `chart`, `x`, `y`, `color`, `label` | -| `tests` | No | Assertions evaluated after query runs | - -### Step 3 — Validate syntax - -```bash -dql parse blocks/revenue_by_segment.dql -``` - -Expected output: - -``` - Parsing blocks/revenue_by_segment.dql - ✓ Syntax valid - ✓ Semantic analysis passed - ✓ Visualization config valid - ✓ Test assertions parseable - 1 block parsed successfully -``` - -Fix any errors reported before proceeding. - -### Step 4 — Preview with live data - -```bash -dql preview blocks/revenue_by_segment.dql --open -``` - -This compiles the block, runs the query against your configured data source, and opens a browser tab showing the live chart. Change the `period` param in the UI to test the param substitution. - -### Step 5 — Run tests - -```bash -dql certify blocks/revenue_by_segment.dql -``` - -`dql certify` runs the query, evaluates your test assertions, and checks all governance fields. Example output: - -``` - Certifying blocks/revenue_by_segment.dql - - Governance checks: - ✓ domain "revenue" - ✓ owner "data-team" - ✓ description present (67 chars) - ✓ tags ["revenue", "segment", "region", "quarterly"] - - Test assertions: - ✓ row_count > 0 (result: 12 rows) - ✓ revenue > 0 (result: min=4200, max=182000) - - ✓ Block certified -``` - -### Step 6 — Format and commit - -```bash -dql fmt blocks/revenue_by_segment.dql -git add blocks/revenue_by_segment.dql -git commit -m "feat: add revenue by segment block" -``` - -`dql fmt` normalizes whitespace and indentation in place — safe to run before every commit. - ---- - -## Tutorial: Create a Semantic Block - -### Prerequisites - -Your project must have a semantic layer configured — either DQL YAML files, a dbt project with `semantic_models`, or a Cube.js project. See [Getting Started](./getting-started.md) Tutorials 2–4 for setup. - -### Step 1 — Discover the right metric - -Open the notebook: - -```bash -dql notebook -``` - -In the **Semantic Layer** sidebar panel: -1. Expand **Compose Query** -2. Browse available metrics (e.g., `arr`, `mrr`, `churn_rate`) -3. Check the metric and one or more dimensions -4. Click **Compose SQL** to see the generated query -5. Click **+ Insert as Cell** to run it and confirm the results look right - -Note the exact metric name shown — you'll use it in the block. - -### Step 2 — Scaffold the block - -```bash -dql new block "ARR by Plan Tier" -# Creates: blocks/arr_by_plan_tier.dql -``` - -### Step 3 — Write the semantic block - -```dql -block "ARR by Plan Tier" { - domain = "revenue" - type = "semantic" - owner = "finance-team" - description = "Annual Recurring Revenue broken out by subscription plan tier" - tags = ["arr", "revenue", "saas", "plan-tier"] - - metric = "arr" - dimensions = ["plan_tier"] - - params { - period = "current_year" - } - - visualization { - chart = "bar" - x = plan_tier - y = arr - } - - tests { - assert row_count > 0 - assert arr > 0 - } -} -``` - -**Semantic block fields:** - -| Field | Description | -|---|---| -| `type = "semantic"` | Tells DQL to resolve `metric` from the semantic layer, not run raw SQL | -| `metric` | The metric name as defined in your semantic layer | -| `dimensions` | List of dimension names to group by | -| `params` | Optional — passed to the semantic layer's query composition | - -### Step 4 — Validate and preview - -```bash -dql parse blocks/arr_by_plan_tier.dql -dql preview blocks/arr_by_plan_tier.dql --open -``` - -DQL resolves the `arr` metric from your semantic layer, composes the SQL, and runs it. You'll see the chart in the browser. - -### Step 5 — Certify and commit - -```bash -dql certify blocks/arr_by_plan_tier.dql -dql fmt blocks/arr_by_plan_tier.dql -git add blocks/arr_by_plan_tier.dql -git commit -m "feat: add ARR by plan tier semantic block" -``` - ---- - -## Using Blocks in the Notebook - -There are three ways to work with semantic metrics and blocks in a notebook. - -### Pattern 1 — Reference semantic metrics inline in a SQL cell - -In any SQL cell, use `@metric(name)` and `@dim(name)` inline refs. DQL resolves these at execution time — `@metric(name)` expands to the metric's aggregation expression aliased as the metric name, and `@dim(name)` expands to the dimension column. GROUP BY is cleaned up automatically. - -```sql -SELECT @dim(segment), @metric(total_revenue) -FROM fct_revenue -GROUP BY @dim(segment) -ORDER BY @metric(total_revenue) DESC -``` - -This is the recommended pattern for exploratory queries that reference your semantic layer without duplicating metric definitions. - -### Pattern 2 — Write a DQL block inline in a notebook DQL cell - -Add a cell with type `dql` and write the block body directly inside the cell. This is for ad-hoc governed queries inside the notebook without a separate `.dql` file. - -```dql -block "Revenue by Segment" { - type = "semantic" - metric = "total_revenue" - dimensions = ["segment"] - owner = "data-team" -} -``` - -### Pattern 3 — Use Compose Query to insert a SQL cell (recommended) - -The canonical workflow for semantic metrics is: - -1. Open the **Semantic Layer** sidebar panel -2. Expand **Compose Query** -3. Pick metrics and dimensions (and optionally a time dimension + granularity) -4. Click **Compose SQL** — DQL generates dialect-correct SQL with the right aggregations and joins -5. Click **+ Insert as Cell** — the SQL cell appears in the notebook, ready to run - -This gives you the generated SQL as a starting point that you can edit before running. - ---- - -## Block Dependencies with ref() - -Use `ref("block_name")` in your SQL to declare that one block depends on another. This creates explicit lineage edges and enables impact analysis. - -```dql -block "clean_orders" { - domain = "data" - type = "custom" - owner = "data-team" - query = """ - SELECT * FROM orders WHERE status != 'cancelled' - """ -} - -block "revenue_by_segment" { - domain = "finance" - type = "custom" - owner = "finance-team" - query = """ - SELECT segment, SUM(amount) AS revenue - FROM ref("clean_orders") - GROUP BY segment - """ -} -``` - -**What ref() gives you:** -- `dql lineage revenue_by_segment` shows `clean_orders` as an upstream dependency -- `dql lineage --impact clean_orders` shows `revenue_by_segment` is affected by changes -- Cross-domain edges are detected automatically (`data` → `finance` in this example) -- Trust chain analysis shows certification status at every hop - -**When to use ref():** -- When one block reads from another block's output -- When you want lineage tracking between blocks -- When blocks span different business domains - -**When NOT to use ref():** -- For external tables (just use the table name directly — DQL extracts these from SQL automatically) -- For semantic blocks (they reference metrics, not other blocks) - -See the [Lineage Guide](./lineage.md) for the full ref() and lineage documentation. - ---- - -## Block Governance - -### What `dql certify` checks - -`dql certify` enforces these requirements before a block is considered production-ready: - -| Check | Rule | -|---|---| -| `domain` | Must be a non-empty string | -| `owner` | Must be a non-empty string | -| `description` | Must be present and at least 20 characters | -| `tags` | Must have at least one tag | -| Tests | All `assert` statements must pass against live data | - -### Making tests meaningful - -Write assertions that would catch real data problems: - -```dql -tests { - assert row_count > 0 -- data exists - assert revenue > 0 -- no negative or zero revenue totals - assert row_count < 10000 -- guard against runaway queries -} -``` - -### Suggested governance workflow - -1. Author the block (`dql new block`) -2. Validate syntax (`dql parse`) -3. Preview with live data (`dql preview --open`) -4. Run certify locally (`dql certify`) -5. Format (`dql fmt`) -6. Commit to git — PR review ensures a second set of eyes on ownership and test coverage -7. Merge to main — block is now the single source of truth for that metric - ---- - -## Block File Conventions - -| Convention | Why | -|---|---| -| One block per `.dql` file | Keeps history clean, makes review easy | -| File name matches block name (snake_case) | Easy to find by name | -| `blocks/` directory at project root | DQL CLI discovers blocks here by default | -| Always include at least one `assert` | Prevents silent empty-result failures | -| Use `${variable}` for anything date/filter related | Makes blocks reusable across time periods | - ---- - -## Next Steps - -- [Lineage & Trust Chains](./lineage.md) — ref() system, cross-domain flows, impact analysis, trust chains -- [Semantic Layer Guide](./semantic-layer-guide.md) — define your own metrics and dimensions in YAML -- [Notebook Guide](./notebook.md) — full reference for cells, params, variable substitution, and export -- [Language Spec](./dql-language-spec.md) — full `.dql` syntax reference: all chart types, param types, test operators -- [CLI Reference](./cli-reference.md) — `dql certify`, `dql fmt`, `dql lineage`, and all other commands diff --git a/docs/cli-reference.md b/docs/cli-reference.md deleted file mode 100644 index 5cb2bf5d..00000000 --- a/docs/cli-reference.md +++ /dev/null @@ -1,714 +0,0 @@ -# CLI Reference - -The `dql` binary is provided by `@duckcodeailabs/dql-cli`. Most commands accept a positional file path argument and a shared set of flags. - -## Usage - -```text -dql init [directory] -dql new [flags] -dql [flags] -``` - -Run `dql --help` (or `dql -h`) to print the help text and exit. - ---- - -## Shared Flags - -These flags are the most commonly used flags across the CLI. - -| Flag | Short | Default | Description | -|---|---|---|---| -| `--format json\|text` | | `text` | Output format. `json` is suitable for CI pipelines and programmatic consumers. | -| `--verbose` | `-v` | `false` | Show detailed output (e.g. full AST for `parse`, cost factor breakdown for `info`). | -| `--open` | | | Open the preview or served bundle in a browser. | -| `--no-open` | | | Disable automatic browser opening even if the project config enables it. | -| `--help` | `-h` | | Print help and exit 0. | -| `--out-dir ` | | | Output directory for `build`. | -| `--port ` | | | Preferred local port for `preview` or `serve`. | -| `--connection ` | | | Database connection for `test` (e.g. `duckdb`, path to `.duckdb` file). | - -## `new` Flags - -These flags are specific to `dql new ...` scaffolds. - -| Flag | Default | Description | -|---|---|---| -| `--chart ` | | | Chart type for `new block` scaffolds. | -| `--domain ` | | | Domain for `new block` scaffolds. | -| `--owner ` | | | Owner for `new block` scaffolds. | -| `--query-only` | | `false` | Create a query-only block without a visualization section. | - ---- - -## Commands - -### `dql init [directory]` - -Initialize DQL in a project. Auto-detects dbt projects (`dbt_project.yml`) and DuckDB files. - -```bash -dql init . -dql doctor -dql notebook -``` - -**What it creates:** - -- `blocks/` directory for DQL analytics blocks -- `dql.config.json` with auto-detected connection and semantic layer provider -- `notebooks/welcome.dqlnb` for browser-first exploration - -When a dbt project is detected, the semantic layer provider is set to `dbt` and the welcome notebook includes queries against dbt mart tables. - -**Useful flags:** - -- `--open` — initialize the project and immediately open the notebook - ---- - -### `dql notebook [path]` - -Launch the browser-first DQL notebook for the current project or a specified path. - -```bash -dql notebook -dql notebook my-dql-project -dql notebook --port 4488 -``` - -**What it does:** - -- Starts a local notebook UI on top of the same `/api/query` runtime used by preview/serve -- Loads `notebooks/welcome.dqlnb` when present -- Supports DQL, SQL, markdown, and linked chart cells -- Exposes connector form definitions for local and remote drivers - -**Text output:** - -```text - ✓ Notebook ready: http://127.0.0.1:3474 - Press Ctrl+C to stop. -``` - ---- - -### `dql new ` - -Create a new DQL block, semantic block, dashboard, workbook, or notebook inside the current project. - -```bash -dql new block "Pipeline Health" -dql new semantic-block "ARR Growth" -dql new dashboard "Revenue Overview" --chart line -dql new workbook "Quarterly Review" -dql new notebook "Revenue Analysis" -dql new block "Revenue Trend" --chart line --domain finance -dql new block "Top Accounts" --chart table --query-only -``` - -**What it does:** - -- creates a new `.dql` file in `blocks/`, `dashboards/`, or `workbooks/` by default -- for `semantic-block`, also creates starter semantic-layer YAML in `semantic-layer/metrics/` and `semantic-layer/blocks/` -- uses local `data/revenue.csv` if it exists, so starter projects get previewable blocks immediately -- falls back to placeholder SQL when no starter data is available -- supports `--out-dir` if you want to write somewhere other than the default scaffold folder - -**Flags most useful for `new block`:** - -- `--chart ` — scaffold `bar`, `line`, `table`, or `kpi` -- `--domain ` — set the block domain -- `--owner ` — set the block owner -- `--query-only` — omit the visualization section - -**Semantic block note:** - -`dql new semantic-block` generates: - -- a `type = "semantic"` block in `blocks/` -- a starter metric definition in `semantic-layer/metrics/` -- companion block metadata in `semantic-layer/blocks/` - -#### `dql new notebook ` - -Scaffold a new `.dqlnb` notebook file inside the project's `notebooks/` directory. - -```bash -dql new notebook "Revenue Analysis" -dql new notebook "Q4 Review" -dql new notebook "Churn Deep Dive" --out-dir reports/ -``` - -**What it creates:** - -- `notebooks/revenue_analysis.dqlnb` — a JSON notebook file with a starter SQL cell pre-populated to query `data/revenue.csv` when it exists -- The new file is immediately visible in the notebook UI sidebar if the notebook server is already running (hot reload picks it up automatically) - -**Text output:** - -```text - ✓ Created notebook: Revenue Analysis - Path: /path/to/project/notebooks/revenue_analysis.dqlnb - - Next steps: - 1. dql notebook - 2. Open notebooks/revenue_analysis.dqlnb from the sidebar -``` - -You can also create notebooks directly from the notebook UI using the **New Notebook** button. See the [Notebook Guide](./notebook.md) for details. - -**Text output (for block/dashboard/workbook):** - -```text - ✓ Created DQL block: Pipeline Health - Path: /path/to/project/blocks/pipeline_health.dql - - Next steps: - 1. dql parse blocks/pipeline_health.dql - 2. dql preview blocks/pipeline_health.dql -``` - ---- - -### `dql doctor [path]` - -Run a lightweight setup check for a DQL project. - -```bash -dql doctor -dql doctor my-dql-project -dql doctor --format json -``` - -**What it checks:** - -- Node.js version -- project root discovery -- `dql.config.json` -- `blocks/` directory -- default connection presence -- `duckdb` dependency when local file/DuckDB preview is configured -- local query runtime readiness for the configured default connection - ---- - -### `dql preview ` - -Compile a block, dashboard, or workbook to local HTML and serve it with a tiny local query API for browser preview. - -```bash -dql preview blocks/pipeline_health.dql -``` - -**What it does:** - -- Compiles the DQL source to HTML -- Starts a local HTTP server -- Exposes `POST /api/query` backed by the default connection in `dql.config.json` -- Renders charts against local DuckDB/file-backed sample data when applicable -- Uses `--port` when provided; otherwise falls back to `dql.config.json` and then `3474` - -**Text output:** - -```text - ✓ Preview ready: http://127.0.0.1:3474 - Press Ctrl+C to stop. -``` - ---- - -### `dql build ` - -Compile a block, dashboard, or workbook to a static output directory containing `index.html`, chart specs, and build metadata. - -```bash -dql build blocks/pipeline_health.dql -dql build blocks/pipeline_health.dql --out-dir out/pipeline -``` - -**Text output:** - -```text - ✓ Built DQL bundle - Source: /path/to/project/blocks/pipeline_health.dql - Output: /path/to/project/dist/pipeline_health -``` - ---- - -### `dql serve [directory]` - -Serve a built DQL bundle locally using the same lightweight `/api/query` runtime as `preview`. - -```bash -dql serve dist/pipeline_health -dql serve --port 4488 -``` - -If no directory is provided, `serve` defaults to `dist/` in the current working directory. - -**Text output:** - -```text - ✓ Serving DQL bundle: http://127.0.0.1:3474 - Root: /path/to/project/dist/revenue_by_segment - Press Ctrl+C to stop. -``` - ---- - -### `dql parse ` - -Parse a `.dql` file and run semantic analysis. Reports syntax errors, unknown keywords, missing required fields, and any semantic warnings. - -```bash -dql parse blocks/revenue_by_segment.dql -dql parse blocks/revenue_by_segment.dql --verbose -dql parse blocks/revenue_by_segment.dql --format json -``` - -**What it validates:** - -- Lexer and parser correctness (syntax) -- Semantic rules via `SemanticAnalyzer`: required block fields (`domain`, `type`), valid chart types, structural consistency - -**Text output (no issues):** - -``` - ✓ Parsed: blocks/revenue_by_segment.dql - Statements: 1 - Diagnostics: ✓ No errors, no warnings -``` - -**Text output (with issues):** - -``` - ✓ Parsed: blocks/revenue_by_segment.dql - Statements: 1 - - ✗ Errors (1): - → Block "Revenue by Segment" is missing required field: domain - - ⚠ Warnings (1): - → Visualization chart type "barchart" is not recognised -``` - -**JSON output shape:** - -```json -{ - "file": "blocks/revenue_by_segment.dql", - "statements": 1, - "diagnostics": [], - "ast": { ... } // only present with --verbose -} -``` - ---- - -### `dql certify ` - -Evaluate certification rules against every block declared in the file. Certification checks that a block has the governance metadata required to be promoted (owner, description, tags, domain, and type). - -```bash -dql certify blocks/revenue_by_segment.dql -dql certify blocks/revenue_by_segment.dql --format json -``` - -**What it checks (via `Certifier` in `@duckcodeailabs/dql-governance`):** - -- `domain` field is present and non-empty -- `type` field is present (`"custom"` or `"semantic"`) -- `description` field is present -- `owner` field is present -- `tags` array is present (warnings if absent or empty) - -**Text output (passes):** - -``` - Block: "Revenue by Segment" - Status: ✓ CERTIFIABLE -``` - -**Text output (fails):** - -``` - Block: "Revenue by Segment" - Status: ✗ NOT CERTIFIABLE - - Errors (1): - ✗ requires-owner: Block must have an owner field - - Warnings (1): - ⚠ recommend-tags: Block has no tags; add tags to improve discoverability -``` - -**JSON output shape:** - -```json -{ - "certified": false, - "errors": [ - { "rule": "requires-owner", "message": "Block must have an owner field" } - ], - "warnings": [] -} -``` - ---- - -### `dql fmt ` - -Format a `.dql` file in place using the canonical DQL formatter (`formatDQL` from `@duckcodeailabs/dql-core`). - -```bash -# Format and write back: -dql fmt blocks/revenue_by_segment.dql - -# Check only — exits 1 if the file needs formatting (CI-safe): -dql fmt blocks/revenue_by_segment.dql --check -``` - -**Flags specific to `fmt`:** - -| Flag | Description | -|---|---| -| `--check` | Do not write; exit 1 if the file differs from its formatted form. Useful in pre-commit hooks and CI. | - -**Text output (write mode, changed):** - -``` - ✓ Formatted: blocks/revenue_by_segment.dql -``` - -**Text output (write mode, already formatted):** - -``` - ✓ No changes: blocks/revenue_by_segment.dql -``` - -**Text output (check mode, needs changes):** - -``` - ✗ Needs formatting: blocks/revenue_by_segment.dql -``` - -**JSON output shape:** - -```json -{ "file": "blocks/revenue_by_segment.dql", "changed": true, "mode": "check" } -``` - ---- - -### `dql test ` - -Run test assertions declared in DQL blocks. Without `--connection`, performs a dry run showing which assertions exist. With `--connection`, executes the block's SQL and evaluates each assertion. - -```bash -# Dry run (lists assertions without executing) -dql test blocks/revenue_by_segment.dql - -# Live execution against DuckDB in-memory -dql test blocks/revenue_by_segment.dql --connection duckdb - -# Live execution against a DuckDB database file -dql test blocks/revenue_by_segment.dql --connection ./analytics.duckdb -``` - -**Text output (live execution):** - -``` - Block: "Revenue by Segment" - PASS: assert row_count > 0 (actual: 5) - - Results: 1 passed, 0 failed, 1 total -``` - -**Text output (dry run):** - -``` - Found 1 block(s) in blocks/revenue_by_segment.dql - - Block: "Revenue by Segment" - Tests: 1 assertion(s) - -> assert row_count > 0 - Status: Dry run (no database connection) - Hint: Use --connection duckdb to execute assertions -``` - ---- - -### `dql validate [path]` - -Validate all `.dql` files in a project. Parses every block, runs semantic analysis, and checks that semantic block metric references exist in the semantic layer. - -```bash -# Validate current project -dql validate - -# Validate a specific project directory -dql validate my-dql-project - -# JSON output for CI -dql validate --format json -``` - -**What it checks:** - -- Syntax correctness (lexer/parser) -- Semantic analysis (required fields, valid chart types) -- Metric references in semantic blocks match semantic-layer YAML definitions - -**Text output:** - -``` - Validated 5 DQL file(s) - - ERROR blocks/arr_growth.dql: Metric "arr_growth_metric" referenced in block "ARR Growth" not found in semantic layer - WARN blocks/pipeline.dql:3: Visualization chart type "barchart" is not recognised - - 1 error(s), 1 warning(s) -``` - -**Exit code:** `1` if any errors are found, `0` otherwise. - ---- - -### `dql info ` - -Print structured metadata for every block in the file, including a query cost estimate score (0–100) derived from static SQL analysis. - -```bash -dql info blocks/revenue_by_segment.dql -dql info blocks/revenue_by_segment.dql --verbose # shows cost factors -dql info blocks/revenue_by_segment.dql --format json -``` - -**Text output:** - -``` - Block: "Revenue by Segment" - Domain: revenue - Type: custom - Owner: data-team - Description: Quarterly revenue grouped by customer segment - Tags: revenue, segment, quarterly - Params: 1 - Tests: 1 assertion(s) - - Cost Estimate: 15/100 - → Query looks efficient -``` - -With `--verbose`, individual cost factors are listed (e.g. missing WHERE clause, SELECT *, JOIN count). - -**JSON output shape:** - -```json -{ - "name": "Revenue by Segment", - "domain": "revenue", - "type": "custom", - "description": "...", - "owner": "data-team", - "tags": ["revenue", "segment", "quarterly"], - "query": "SELECT ...", - "params": { "period": "current_quarter" }, - "tests": 1, - "costEstimate": { - "score": 15, - "recommendation": "Query looks efficient", - "factors": [] - } -} -``` - ---- - -### `dql compile [path]` - -Generate the project manifest (`dql-manifest.json`) — a complete compiled artifact containing all blocks, notebooks, metrics, sources, dependencies, and pre-computed lineage. Similar to dbt's `manifest.json`. - -```bash -# Compile current project -dql compile - -# Compile with verbose block details -dql compile --verbose - -# Import dbt manifest as upstream lineage -dql compile --dbt-manifest path/to/manifest.json - -# Output manifest to stdout as JSON -dql compile --format json - -# Write manifest to a custom directory -dql compile --out-dir ./dist -``` - -**What `dql compile` scans:** - -- `.dql` files in `blocks/`, `dashboards/`, `workbooks/` (recursive) -- `.dqlnb` notebook files in `notebooks/` and other directories (recursive) -- Semantic layer YAML in `semantic-layer/` (or custom path from config) -- DQL cells inside notebooks that declare blocks - -**Flags specific to `compile`:** - -| Flag | Description | -|---|---| -| `--dbt-manifest ` | Import dbt `manifest.json` — models and sources become upstream nodes with column metadata | -| `--out-dir ` | Write `dql-manifest.json` to a custom directory | -| `--format json` | Output manifest to stdout instead of writing a file | -| `--verbose` | Show block details and dependencies after compilation | - -**Manifest contents:** - -| Section | What it contains | -|---|---| -| `blocks` | All blocks with SQL, dependencies (table + ref), tests, tags, domain, owner | -| `notebooks` | Notebook SQL/DQL cells with table and ref dependencies | -| `metrics` | Semantic layer metrics with source tables and domains | -| `dimensions` | Semantic layer dimensions | -| `sources` | All source tables with origin tracking (sql/semantic/dbt) and dbt column metadata | -| `lineage` | Pre-computed lineage graph with nodes, edges, cross-domain flows, domain trust | -| `dbtImport` | dbt import metadata (if `--dbt-manifest` was used) | - ---- - -### `dql lineage [name] [path]` - -Show data lineage for a DQL project — how data flows from source tables through blocks, semantic metrics, domains, and charts. When `dql-manifest.json` exists (from `dql compile`), lineage reads from it for faster results. - -```bash -# Full project summary with data flow DAG -dql lineage - -# Smart lookup — auto-resolves to block, table, metric, or dimension -dql lineage raw_orders -dql lineage orders # resolves to table:orders -dql lineage total_revenue # resolves to metric:total_revenue - -# Explicit type lookup -dql lineage --table orders -dql lineage --metric total_revenue - -# Domain-scoped view -dql lineage --domain finance - -# Impact analysis (works on any node type) -dql lineage --impact orders -dql lineage --impact raw_orders - -# Trust chain between two blocks -dql lineage --trust-chain raw_orders exec_dashboard - -# JSON export -dql lineage --format json - -# Force live scan (skip manifest) -dql lineage --no-manifest - -# Specify project path -dql lineage /path/to/project -``` - -**What it shows:** - -- **Summary** (default) — source tables, blocks with domains/owners/dependencies, metrics, data flow DAG tree, cross-domain flows, domain trust scores -- **Node lineage** — direct and transitive upstream/downstream for any block, table, metric, or dimension -- **Domain lineage** — all nodes in a domain, data flows in and out, trust score -- **Impact analysis** — all downstream affected nodes grouped by domain, with certification status (works on tables, metrics, and blocks) -- **Trust chain** — certification status at every node in the path between two blocks - -**Flags specific to `lineage`:** - -| Flag | Description | -|---|---| -| `--table ` | Show lineage for a specific source table | -| `--metric ` | Show lineage for a specific metric | -| `--domain ` | Show lineage scoped to a business domain | -| `--impact ` | Impact analysis: what downstream nodes are affected by a change (any node type) | -| `--trust-chain ` | Show trust chain between two blocks with certification checkpoints | -| `--format json` | Export the full lineage graph as JSON | -| `--no-manifest` | Force live scanning instead of reading `dql-manifest.json` | - -**How lineage is built:** - -- `dql compile` scans all blocks, notebooks, and semantic layer definitions -- SQL parsing extracts table references from `FROM`, `JOIN`, `INTO` clauses, and DuckDB reader functions (`read_csv_auto()`, etc.) -- `ref("block_name")` calls create explicit `feeds_into` edges between blocks -- Semantic layer metric `table` fields connect metrics to source tables -- `domain` fields on blocks/metrics create domain nodes and cross-domain edges -- `visualization` config creates chart nodes -- dbt `manifest.json` import enriches source tables with column-level metadata - -See the [Lineage Guide](./lineage.md) for full documentation. - ---- - -### `dql migrate ` - -Scaffold a DQL block from a foreign tool definition. The `` argument is one of: `looker`, `tableau`, `dbt`, `metabase`, `raw-sql`. - -This command is **scaffold-only** in the OSS CLI. It prints a template block and migration notes; it does not automatically parse or transform source files. - -```bash -dql migrate looker -dql migrate dbt --input ./my-dbt-project -dql migrate raw-sql --format json -``` - -**Flags specific to `migrate`:** - -| Flag | Description | -|---|---| -| `--input ` | Source path for the migration (e.g. dbt project directory). | - -**Supported sources and coverage:** - -| Source | Method | OSS Coverage | -|---|---|---| -| `looker` | Print a Looker-oriented scaffold and migration checklist | Scaffold-only | -| `tableau` | Print a Tableau-oriented scaffold and migration checklist | Scaffold-only | -| `dbt` | Print a dbt-oriented scaffold and semantic-block planning notes | Scaffold-only | -| `metabase` | Print a Metabase-oriented scaffold and migration checklist | Scaffold-only | -| `raw-sql` | Print a raw SQL wrapper scaffold for manual cleanup | Scaffold-only | - -**Text output (example for `looker`):** - -``` - DQL Migration: looker - ───────────────────────────── - Source: LookML explores + measures + dimensions - Method: Parse LookML → generate DQL blocks + semantic layer YAML - Coverage: ~80% automated - - Example generated block: - ─── - block "migrated-from-looker" { ... } - - Next steps: - 1. Provide source files: dql migrate looker --input - 2. Review generated blocks in blocks/migrated/ - 3. Run: dql test blocks/migrated/example.dql - 4. Commit and push for certification -``` - -For practical migration walkthroughs, see: - -- [`migration-guides/raw-sql.md`](./migration-guides/raw-sql.md) -- [`migration-guides/dbt.md`](./migration-guides/dbt.md) -- [`migration-guides/saved-bi-query.md`](./migration-guides/saved-bi-query.md) - ---- - -## Exit Codes - -| Code | Meaning | -|---|---| -| `0` | Success (or `--help` / `--check` with no changes needed) | -| `1` | Parse error, semantic error, certification failure, unformatted file (with `--check`), unknown command, missing argument, or unhandled runtime error | diff --git a/docs/enterprise-getting-started.md b/docs/enterprise-getting-started.md deleted file mode 100644 index 0e4e6c3d..00000000 --- a/docs/enterprise-getting-started.md +++ /dev/null @@ -1,440 +0,0 @@ -# Enterprise Getting Started - -This guide is for teams with an **existing dbt project** and a **production database** (Snowflake, PostgreSQL, BigQuery, Databricks, or any of the 14 supported connectors). You'll connect DQL to your real data, import your semantic metrics, and build governed blocks — all from the notebook UI. - -> **Looking for the quick demo path?** Use the [Jaffle Shop walkthrough](./getting-started.md) instead. -> -> **Just want DQL-only, no dbt?** See the [Quickstart](./quickstart.md). - ---- - -## Prerequisites - -- **Node.js 18+** -- **An existing dbt project** (with `dbt_project.yml`) -- **Database credentials** for your warehouse (Snowflake, Postgres, BigQuery, etc.) - ---- - -## Step 1: Install DQL - -```bash -npm install -g @duckcodeailabs/dql-cli -dql --version -``` - ---- - -## Step 2: Initialize DQL in Your dbt Project - -```bash -cd /path/to/your-dbt-project -dql init . -``` - -DQL auto-detects: -- **dbt project** — finds `dbt_project.yml`, sets `semanticLayer.provider` to `dbt` -- **DuckDB file** — if your dbt project uses `dbt-duckdb`, finds the `.duckdb` file -- **Semantic definitions** — if your `models/` directory contains `semantic_models:` or `metrics:` in YAML files, auto-imports them - -Created files: -``` -dql.config.json ← project config -blocks/ ← your DQL blocks will go here -notebooks/welcome.dqlnb ← starter notebook -semantic-layer/ ← imported metrics/dimensions (if detected) -``` - ---- - -## Step 3: Configure Your Database Connection - -Your dbt project likely uses a production warehouse, not DuckDB. You have two options: - -### Option A: Edit `dql.config.json` directly - -Open `dql.config.json` and update the connection to match your warehouse: - -**Snowflake:** -```json -{ - "connections": { - "default": { - "driver": "snowflake", - "account": "your-account.snowflakecomputing.com", - "username": "your_user", - "password": "${SNOWFLAKE_PASSWORD}", - "database": "ANALYTICS", - "schema": "PUBLIC", - "warehouse": "COMPUTE_WH" - } - } -} -``` - -**PostgreSQL:** -```json -{ - "connections": { - "default": { - "driver": "postgres", - "host": "your-host.example.com", - "port": 5432, - "database": "analytics", - "username": "analyst", - "password": "${POSTGRES_PASSWORD}" - } - } -} -``` - -**BigQuery:** -```json -{ - "connections": { - "default": { - "driver": "bigquery", - "project": "your-gcp-project-id", - "dataset": "analytics", - "keyFilename": "./service-account.json" - } - } -} -``` - -> Use `${ENV_VAR}` for passwords — DQL resolves them from your shell environment. Never hardcode secrets. See [Data Sources](./data-sources.md) for all 14 driver configs. - -Set the environment variable before running DQL: - -```bash -export SNOWFLAKE_PASSWORD="your-password" -``` - -### Option B: Use the notebook Connection Panel (Step 5) - -Skip editing `dql.config.json` — configure the connection from the notebook UI instead. - ---- - -## Step 4: Verify Setup - -```bash -dql doctor -``` - -Check that the connection and semantic layer resolve correctly: - -``` - ✓ dql.config.json found - ✓ Default connection driver=snowflake - ✓ Semantic layer provider=dbt, 12 metrics, 8 dimensions - ✓ Notebook app assets found - ✓ Local query runtime driver=snowflake is available -``` - -If `Local query runtime` fails, verify your credentials and that the database is reachable from your machine. - ---- - -## Step 5: Open the Notebook - -```bash -dql notebook -``` - -Your browser opens at `http://127.0.0.1:3474`. - ---- - -## Step 6: Configure Connection from the Notebook (if needed) - -If you skipped Step 3 or need to change the database: - -1. Click the **Connection** icon in the left sidebar (plug icon) -2. You'll see the current connection config and status indicator -3. **Quick Connect** — click a preset (PostgreSQL, Snowflake, etc.) to pre-fill the form -4. **Fill in your credentials** — host, database, username, password, etc. -5. Click **Save** - -**What happens on Save:** -- DQL writes the connection to `dql.config.json` -- The server **hot-swaps** the database connection at runtime — no restart needed -- The schema sidebar refreshes with your real tables and columns -- The status indicator updates to show connection health - -6. Click **Test Connection** to verify the connection works - -> You can change connections at any time. Each save hot-swaps the active connection immediately. - ---- - -## Step 7: Browse Your Database Schema - -After connecting, the **Schema** sidebar (database icon) shows your tables: - -- Tables organized by schema (e.g., `public.dim_customers`, `analytics.fct_orders`) -- Click a table to expand and see all columns -- Columns show color-coded type badges: - - Blue — string/varchar/text - - Green — int/bigint/float/decimal - - Pink — boolean - - Gold — date/timestamp - - Purple — json/array/variant -- Click a column to insert it into the current SQL cell - -DQL uses a 3-tier introspection strategy that works across all 14 connectors: -1. `information_schema` queries (Postgres, Snowflake, MySQL, MSSQL, DuckDB, etc.) -2. Connector-specific methods (SQLite `PRAGMA`, BigQuery API, Athena `DESCRIBE`) -3. Lazy column loading on expand (fallback) - ---- - -## Step 8: Import Semantic Metrics - -If `dql init` didn't auto-import your semantic definitions (or you want to re-import), you have two options: - -### Option A: From the CLI - -```bash -dql semantic import dbt . -``` - -This scans your `models/` directory for `semantic_models:` and `metrics:` blocks, extracts all metrics, dimensions, hierarchies, and writes them as YAML files into `semantic-layer/`. - -### Option B: From the Notebook UI - -1. Click the **Semantic** icon in the left sidebar (layers icon) -2. If no metrics are loaded, you'll see an import prompt -3. Select your provider: - - **dbt** — reads from your dbt project's `models/*.yml` files - - **Cube.js** — reads from your Cube.js `model/` or `schema/` directory - - **Snowflake** — queries Snowflake semantic views via the live connection -4. Click **Import** - -**What gets imported:** -- **Metrics** — sum, count, avg, min, max, count_distinct, custom -- **Dimensions** — string, number, date, boolean -- **Hierarchies** — drill paths (e.g., year → quarter → month) -- **Cubes** — grouped measures + dimensions + joins - -Each object becomes a YAML file in `semantic-layer/`: -``` -semantic-layer/ - metrics/total_revenue.yaml - metrics/order_count.yaml - dimensions/customer_type.yaml - dimensions/order_date.yaml - hierarchies/fiscal_time.yaml -``` - -After import, the semantic sidebar shows all metrics and dimensions. Changes to the YAML files are hot-reloaded automatically. - -### Snowflake Semantic Layer - -If your team uses Snowflake's native semantic views: - -```json -{ - "semanticLayer": { - "provider": "snowflake" - } -} -``` - -DQL queries the live Snowflake connection to discover semantic views, metrics, and dimensions. This requires an active Snowflake connection (Step 6). - ---- - -## Step 9: Build Blocks in Block Studio - -Block Studio is where you turn SQL into governed, testable, Git-trackable analytics blocks. - -### Open Block Studio - -Click **Block Studio** in the sidebar, or click any `.dql` file. - -### The Block Studio layout - -| Panel | Location | What it shows | -|-------|----------|---------------| -| **Database Explorer** | Left sidebar, Database tab | Your tables and columns from the connected database | -| **Semantic Panel** | Left sidebar, Semantic tab | Imported metrics, dimensions, hierarchies | -| **SQL Editor** | Center | Write your block's SQL | -| **Results/Chart** | Bottom | Query results table with chart toggle | -| **Validation** | Inline | Live syntax and semantic errors | - -### Write SQL with database + semantic references - -1. **From the Database panel** — click a table or column to insert it into the editor -2. **From the Semantic panel** — click a metric to insert `{{ metric:total_revenue }}`, or a dimension to insert `{{ dimension:customer_type }}` -3. **Write SQL directly** — standard SQL against your connected database - -Example block: - -```sql -SELECT - {{ dimension:customer_type }} AS segment, - {{ metric:total_revenue }} AS revenue, - {{ metric:order_count }} AS orders -FROM fct_orders -GROUP BY segment -ORDER BY revenue DESC -``` - -### Run - -Click **Run** (or Ctrl/Cmd+Enter). DQL executes the SQL against your connected database and shows results in the table below. Toggle **Chart** to see automatic visualization. - -### Test - -If your block defines test assertions: - -```dql -tests { - assert row_count > 0 - assert revenue > 0 -} -``` - -Click **Test** to run all assertions. Each shows pass/fail status. - -### Save - -Click **Save**. If this is a new block, a dialog collects: -- **Name** — block identifier (e.g., "Revenue by Segment") -- **Domain** — business domain (e.g., "finance", "marketing") -- **Owner** — responsible team or person -- **Description** — what this block answers - -The block is saved to `blocks/{domain}/{name}.dql` — a Git-trackable file. - -If a block with the same name already exists, you'll see an error with the option to rename. - ---- - -## Step 10: View Lineage - -```bash -dql compile --dbt-manifest target/manifest.json -dql lineage -``` - -See the full data flow: -``` -dbt source tables → staging models → mart tables → DQL blocks → downstream consumers -``` - -Useful lineage commands: - -```bash -dql lineage --domain finance # blocks in the finance domain -dql lineage --impact fct_orders # what breaks if this table changes? -dql lineage --trust-chain revenue_block # certification status at every hop -dql lineage --format json # export for CI/CD integrations -``` - ---- - -## Step 11: Validate and Certify - -Run validation across all blocks: - -```bash -dql validate -``` - -Certify individual blocks for governance compliance: - -```bash -dql certify blocks/finance/revenue_by_segment.dql -``` - -Certification checks: owner is set, description exists, domain is assigned, tests pass. - ---- - -## Project Structure - -After setup, your project looks like: - -``` -your-dbt-project/ -├── dbt_project.yml ← existing dbt project -├── models/ ← existing dbt models -│ └── staging/_schema.yml ← semantic_models + metrics definitions -├── target/ -│ └── manifest.json ← dbt manifest (for lineage import) -├── dql.config.json ← DQL project config -├── blocks/ ← DQL blocks (Git-tracked) -│ └── finance/ -│ └── revenue_by_segment.dql -├── notebooks/ ← DQL notebooks -│ └── welcome.dqlnb -├── semantic-layer/ ← imported semantic definitions -│ ├── metrics/ -│ ├── dimensions/ -│ └── imports/manifest.json -└── .gitignore ← includes dql-manifest.json, *.duckdb -``` - ---- - -## Supported Database Connectors - -| Driver | Config value | Typical use | -|--------|-------------|-------------| -| DuckDB (in-memory) | `file` | Local CSV/Parquet analysis | -| DuckDB (file) | `duckdb` | Persistent local warehouse | -| SQLite | `sqlite` | Lightweight embedded DB | -| PostgreSQL | `postgres` | Supabase, RDS, Aurora, Neon | -| MySQL | `mysql` | MariaDB, PlanetScale, TiDB | -| SQL Server | `mssql` | Azure SQL, on-prem MSSQL | -| Snowflake | `snowflake` | Cloud data warehouse | -| BigQuery | `bigquery` | Google Cloud analytics | -| Redshift | `redshift` | AWS data warehouse | -| Databricks | `databricks` | Lakehouse analytics | -| ClickHouse | `clickhouse` | Real-time analytics | -| Athena | `athena` | S3-based serverless queries | -| Trino | `trino` | Federated queries, Starburst | -| Fabric | `fabric` | Microsoft Fabric lakehouse | - -Full config details for each driver: [Data Sources](./data-sources.md) - ---- - -## Security Best Practices - -- **Never hardcode secrets** in `dql.config.json`. Use `${ENV_VAR}` syntax: - ```json - { "password": "${SNOWFLAKE_PASSWORD}" } - ``` -- **Add `.env` to `.gitignore`** if you use a `.env` file for local development -- **`dql init` adds to `.gitignore`** automatically: `dql-manifest.json`, `*.duckdb`, `*.duckdb.wal` -- **Connection Panel** in the notebook stores credentials in `dql.config.json` — ensure this file is not committed if it contains plain-text passwords - ---- - -## Troubleshooting - -| Problem | Cause | Fix | -|---------|-------|-----| -| `dql doctor` — connection fails | Wrong credentials or host unreachable | Verify host, port, username, password in `dql.config.json` | -| Schema sidebar is empty | Connection not configured or test failed | Open Connection Panel, verify connection, click Test | -| Semantic sidebar shows 0 metrics | No semantic definitions in dbt YAML | Ensure your `models/*.yml` files have `semantic_models:` blocks (requires dbt 1.6+) | -| `dql semantic import dbt .` fails | No `dbt_project.yml` found | Run from your dbt project root | -| Save returns 409 conflict | Block name already exists | Choose a different name or rename the existing block | -| Catalog load failed (error banner) | Database connection dropped | Click Retry in the error banner, or check the Connection Panel | -| `${ENV_VAR}` not resolved | Variable not set in shell | Run `export ENV_VAR=value` before `dql notebook` | - ---- - -## Next Steps - -| Goal | Guide | -|------|-------| -| Notebook features (params, charts, export, dashboard mode) | [Notebook Guide](./notebook.md) | -| Block syntax deep dive (params, ref(), visualization, tests) | [Language Spec](./dql-language-spec.md) | -| Semantic layer YAML format and providers | [Semantic Layer Guide](./semantic-layer-guide.md) | -| Lineage, trust chains, impact analysis | [Lineage Guide](./lineage.md) | -| CI/CD integration and validation | [CLI Reference](./cli-reference.md) | -| All 14 connector configs with examples | [Data Sources](./data-sources.md) | diff --git a/docs/examples.md b/docs/examples.md deleted file mode 100644 index cdcead84..00000000 --- a/docs/examples.md +++ /dev/null @@ -1,242 +0,0 @@ -# Examples - -Three paths to get started — pick the one that fits your setup. - -| Path | Time | What you need | -|------|------|---------------| -| [DQL-only](#dql-only) | 2 min | Node.js 18+ | -| [dbt + Jaffle Shop](#dbt--jaffle-shop) | 5 min | Node.js 18+, Python 3.9+, Git | -| [Enterprise (your own database)](#enterprise) | 10 min | Node.js 18+, database credentials | - ---- - -## DQL-Only - -No dbt, no database — just DQL + DuckDB in-memory. - -```bash -npm install -g @duckcodeailabs/dql-cli -dql init my-project -cd my-project -dql doctor -dql notebook -``` - -Put CSV or Parquet files in a `data/` directory, then query them: - -```sql -SELECT * FROM read_csv_auto('./data/orders.csv') LIMIT 10; -``` - ---- - -## dbt + Jaffle Shop - -Full walkthrough with semantic metrics, lineage, and Block Studio. - -```bash -git clone https://github.com/dbt-labs/Semantic-Layer-Online-Course.git jaffle-shop -cd jaffle-shop -pip install dbt-duckdb && dbt deps && dbt build --profiles-dir . -npm install -g @duckcodeailabs/dql-cli -dql init . -dql doctor -dql notebook -``` - -`dql init .` auto-detects the dbt project, finds `jaffle_shop.duckdb`, and imports semantic metrics — all in one step. - -→ **[Full Jaffle Shop walkthrough](./getting-started.md)** - ---- - -## Enterprise - -Your own dbt repo + production database (Snowflake, Postgres, BigQuery, etc.). - -```bash -cd your-dbt-project -npm install -g @duckcodeailabs/dql-cli -dql init . -dql notebook -# → Configure your database in the Connection Panel -# → Import semantic metrics from the notebook UI -# → Build blocks in Block Studio -``` - -→ **[Full enterprise walkthrough](./enterprise-getting-started.md)** - ---- - -## Suggested Learning Path - -### 1. Start with the notebook - -```bash -dql notebook -``` - -Run the welcome notebook cells. Browse the Schema sidebar to see your tables. Browse the Semantic sidebar to see imported metrics. - -### 2. Build a block in Block Studio - -Open Block Studio from the sidebar. Use the Database Explorer to browse tables and the Semantic Panel to browse metrics. Write SQL, run it, test it, and save it. - -Example block: - -```sql -SELECT - customer_name, - customer_type, - lifetime_spend -FROM dim_customers -ORDER BY lifetime_spend DESC -LIMIT 10 -``` - -### 3. Parse and validate from the CLI - -```bash -dql parse blocks/top_customers.dql --verbose -dql validate -``` - -### 4. Preview and build - -```bash -dql preview blocks/top_customers.dql --open -dql build blocks/top_customers.dql -dql serve dist/top_customers --open -``` - -### 5. View lineage - -```bash -dql compile --dbt-manifest target/manifest.json -dql lineage -dql lineage --domain finance -``` - -Or click the **Lineage** icon in the notebook sidebar. - -### 6. Add block dependencies with ref() - -Create a second block that references the first: - -```dql -block "Top Segments" { - domain = "executive" - type = "custom" - owner = "leadership" - query = """ - SELECT * FROM ref("top_customers") - WHERE lifetime_spend > 100 - """ -} -``` - -Run `dql lineage` to see the dependency graph and cross-domain flows. - -### 7. Explore the semantic layer - -Browse metrics and dimensions in the notebook sidebar. Click to insert references into SQL: - -```sql -SELECT - {{ dimension:customer_type }} AS segment, - {{ metric:total_revenue }} AS revenue -FROM fct_orders -GROUP BY segment -``` - ---- - -## Block Examples - -### Simple query block - -```dql -block "Active Customers" { - domain = "growth" - type = "custom" - owner = "growth-team" - description = "Customers who placed an order in the last 30 days" - - query = """ - SELECT customer_name, MAX(order_date) AS last_order - FROM fct_orders - JOIN dim_customers USING (customer_id) - WHERE order_date >= CURRENT_DATE - INTERVAL 30 DAY - GROUP BY customer_name - ORDER BY last_order DESC - """ - - tests { - assert row_count > 0 - } -} -``` - -### Parameterized block - -```dql -block "Revenue by Period" { - domain = "finance" - type = "custom" - owner = "finance-team" - - params { - start_date = "2024-01-01" - end_date = "2024-12-31" - } - - query = """ - SELECT DATE_TRUNC('month', order_date) AS month, SUM(amount) AS revenue - FROM fct_orders - WHERE order_date BETWEEN '${start_date}' AND '${end_date}' - GROUP BY month - ORDER BY month - """ - - visualization { - chart = "line" - x = month - y = revenue - } - - tests { - assert row_count > 0 - assert revenue > 0 - } -} -``` - -### Block with ref() dependency - -```dql -block "Executive Summary" { - domain = "executive" - type = "custom" - owner = "leadership" - - query = """ - SELECT * - FROM ref("revenue_by_period") - WHERE revenue > 10000 - """ -} -``` - ---- - -## Related Docs - -- [Getting Started (Jaffle Shop)](./getting-started.md) -- [Enterprise Getting Started](./enterprise-getting-started.md) -- [Quickstart](./quickstart.md) -- [Notebook Guide](./notebook.md) -- [Authoring Blocks](./authoring-blocks.md) -- [Lineage & Trust Chains](./lineage.md) -- [Semantic Layer Guide](./semantic-layer-guide.md) -- [Data Sources](./data-sources.md) -- [Language Specification](./dql-language-spec.md) diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 56682cee..00000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,304 +0,0 @@ -# Getting Started with DQL + Jaffle Shop - -DQL is the **answer layer** on top of dbt. dbt transforms your data — DQL turns it into trusted, governed analytics answers with SQL blocks, notebooks, lineage, and a semantic layer. - -This guide walks you through setting up DQL with the **Jaffle Shop** dbt project used in dbt's official Semantic Layer course. - -> **Looking for the non-dbt path?** Run `dql init my-project && cd my-project && dql notebook` — see the [Quickstart](./quickstart.md). -> -> **Have your own dbt repo + database?** See [Enterprise Getting Started](./enterprise-getting-started.md). - ---- - -## Prerequisites - -- **Python 3.9+** (for dbt) -- **Node.js 18+** (for DQL) -- **Git** - ---- - -## Step 1: Clone the Jaffle Shop dbt Project - -```bash -git clone https://github.com/dbt-labs/Semantic-Layer-Online-Course.git jaffle-shop -cd jaffle-shop -``` - -We use this repo because it already includes dbt semantic models and metrics that DQL can import. It contains: -- Staging and mart models (customers, orders, order items, products, supplies) -- Semantic model definitions (metrics, dimensions, entities) -- A MetricFlow time spine - -> **Important:** `dql init` does not download this repo. You must clone it yourself. - ---- - -## Step 2: Install dbt and Build the Project - -```bash -pip install dbt-duckdb -dbt deps # install dbt packages (dbt_utils, dbt_date) -dbt build --profiles-dir . # seed, build models, run tests — all into DuckDB -``` - -The repo includes a `profiles.yml` that configures DuckDB as the target. After `dbt build` completes, you have a `jaffle_shop.duckdb` file with the full data model: - -| Table | Description | -|---|---| -| `dim_customers` | Customer dimension with lifetime spend, order counts, type (new/returning) | -| `fct_orders` | Fact table with order totals, tax, supply costs, item counts | -| `order_items` | Order line items with product prices and supply costs | -| `stg_customers` | Staging: cleaned customer records | -| `stg_orders` | Staging: cleaned orders with dollar conversion | -| `stg_order_items` | Staging: cleaned order items | -| `stg_products` | Staging: products with food/drink flags | -| `stg_supplies` | Staging: supplies with perishability flags | - ---- - -## Step 3: Install DQL - -```bash -npm install -g @duckcodeailabs/dql-cli -dql --version # verify install (should print 0.8.7 or later) -``` - ---- - -## Step 4: Initialize DQL in the Project - -```bash -dql init . -``` - -DQL auto-detects everything in one step: - -1. **dbt project** — finds `dbt_project.yml`, sets `semanticLayer.provider` to `dbt` -2. **DuckDB file** — finds `jaffle_shop.duckdb`, configures it as the default connection -3. **Semantic definitions** — scans `models/` for YAML files with `semantic_models:` or `metrics:`, auto-imports them into `semantic-layer/` - -Created files: -- `dql.config.json` — project config pointing to `jaffle_shop.duckdb` -- `blocks/` — directory for your DQL analytics blocks -- `notebooks/welcome.dqlnb` — a starter notebook with DuckDB-aware SQL - -> **Note:** If you see `dql semantic import dbt .` in older tutorials, you can skip it — `dql init` now handles this automatically when semantic definitions are detected. - ---- - -## Step 5: Verify Setup - -```bash -dql doctor -``` - -You should see all checks passing: - -``` - DQL Doctor - Project: /path/to/jaffle-shop - - ✓ Node.js version=22.x.x (requires >= 18) - ✓ Project root /path/to/jaffle-shop - ✓ dql.config.json found - ✓ blocks/ found - ✓ semantic-layer/ found - ✓ Default connection driver=duckdb - ✓ Notebook app assets found - ✓ Semantic layer provider=dbt, N metrics, N dimensions - ✓ Local query runtime driver=duckdb is available - - Summary: 9/9 checks passed -``` - -If any check fails, follow the hints. Common issues: -- `duckdb dependency` — run `npm install` in the project to get the native DuckDB module -- `Notebook app assets missing` — reinstall the CLI: `npm i -g @duckcodeailabs/dql-cli` - ---- - -## Step 6: Open the Notebook - -```bash -dql notebook -``` - -Your browser opens at `http://127.0.0.1:3474` with the welcome notebook connected to Jaffle Shop. - -### What you see: - -**Welcome notebook** with runnable SQL cells: -- `SHOW TABLES;` — lists all tables built by dbt -- A starter query to explore your data -- An example DQL block with governance metadata - -**Left sidebar** has four panels: -1. **Files** — browse notebooks and blocks in your project -2. **Schema** — database tables and columns, click to expand. Columns show color-coded type badges (blue for strings, green for numbers, pink for booleans, gold for dates). -3. **Semantic** — imported dbt metrics and dimensions. Click any metric to see its SQL, type, table, and tags. -4. **Connection** — current database connection status and configuration - -### Try it: - -1. Click **Run** on the `SHOW TABLES;` cell — you'll see all the Jaffle Shop tables -2. Add a new SQL cell and query the data: - ```sql - SELECT customer_name, customer_type, lifetime_spend - FROM dim_customers - ORDER BY lifetime_spend DESC - LIMIT 10 - ``` -3. Click **Run** — results appear in a table. Toggle to **Chart** view for automatic visualization. - ---- - -## Step 7: Build a Block in Block Studio - -Block Studio is the notebook's built-in IDE for creating governed DQL blocks. - -### Open Block Studio - -Click the **Block Studio** icon in the sidebar (or click `+ New Block` in the Files panel). - -### Write your first block - -The editor opens with a blank DQL file. You have three panels: - -**Left: Database Explorer** -- Expand `dim_customers` to see all columns (`customer_name`, `lifetime_spend`, etc.) -- Click a column name to insert it into your SQL -- Click a table name to insert the full table reference - -**Left: Semantic Panel** (tab) -- Browse imported metrics (`total_revenue`, `order_count`, etc.) -- Click a metric to insert `{{ metric:total_revenue }}` into your SQL -- Browse dimensions and hierarchies - -**Center: SQL Editor** - -Write a block: - -```sql -SELECT - customer_name, - customer_type, - count_lifetime_orders AS orders, - lifetime_spend AS total_spend -FROM dim_customers -ORDER BY lifetime_spend DESC -LIMIT 10 -``` - -### Run, test, save - -1. **Run** (Ctrl/Cmd+Enter) — executes the SQL against `jaffle_shop.duckdb`. Results appear below in a table, with a Chart toggle for visualization. - -2. **Test** — if your block has test assertions like `assert row_count > 0`, click Test to run them. Each assertion shows pass/fail. - -3. **Save** — click Save. Since this is a new block, a dialog asks for: - - **Name** — e.g., "Top Customers" - - **Domain** — e.g., "finance" - - **Owner** — e.g., "data-team" - - **Description** — e.g., "Top 10 customers by lifetime spend" - - The block is saved to `blocks/finance/top_customers.dql` — a Git-trackable file with SQL + governance metadata. - ---- - -## Step 8: Import dbt Lineage - -```bash -dql compile --dbt-manifest target/manifest.json -``` - -This imports dbt's lineage graph as upstream context into DQL's manifest. Now run: - -```bash -dql lineage -``` - -You'll see the full data flow from dbt's source tables through staging, marts, and into your DQL blocks — the complete answer-layer lineage. - -```bash -dql lineage --domain finance # blocks in the finance domain -dql lineage --impact dim_customers # what breaks if this table changes? -dql lineage --trust-chain top_customers # certification at every hop -``` - ---- - -## Step 9: Create More Blocks - -Create blocks that reference each other using `ref()`: - -```dql -block "Revenue by Segment" { - domain = "finance" - type = "custom" - owner = "data-team" - description = "Revenue breakdown by customer segment" - - query = """ - SELECT - customer_type AS segment, - COUNT(*) AS customers, - SUM(lifetime_spend) AS total_revenue - FROM dim_customers - GROUP BY customer_type - ORDER BY total_revenue DESC - """ - - visualization { - chart = "bar" - x = segment - y = total_revenue - } - - tests { - assert row_count > 0 - } -} -``` - -Then reference it from another block: - -```dql -block "Executive Summary" { - domain = "executive" - type = "custom" - owner = "leadership" - query = """ - SELECT * FROM ref("revenue_by_segment") - WHERE total_revenue > 1000 - """ -} -``` - -Run `dql lineage` to see cross-domain flows (`finance -> executive`). - ---- - -## What You Have Now - -| Layer | Tool | What it does | -|---|---|---| -| **Transformation** | dbt | raw → staging → mart tables in DuckDB | -| **Semantic** | DQL | metrics + dimensions imported from dbt, browsable in notebook | -| **Answer** | DQL | mart tables → governed blocks → notebooks → charts | -| **Lineage** | DQL | full trust chain from dbt sources through blocks | - -DQL picks up where dbt stops. Every analytics answer is a `.dql` file with SQL + visualization + owner + tests — all Git-trackable. - ---- - -## Next Steps - -| Goal | Guide | -|------|-------| -| Connect your own database (Snowflake, Postgres, etc.) | [Enterprise Getting Started](./enterprise-getting-started.md) | -| Learn notebook features (params, charts, export) | [Notebook Guide](./notebook.md) | -| Block syntax deep dive | [Language Spec](./dql-language-spec.md) | -| Import metrics from Cube.js or Snowflake | [Semantic Layer Guide](./semantic-layer-guide.md) | -| All 14 database connectors | [Data Sources](./data-sources.md) | -| CLI commands and flags | [CLI Reference](./cli-reference.md) | diff --git a/docs/lineage.md b/docs/lineage.md deleted file mode 100644 index 4317cee5..00000000 --- a/docs/lineage.md +++ /dev/null @@ -1,503 +0,0 @@ -# Lineage & Trust Chains - -DQL tracks how data flows from source tables through blocks, semantic metrics, business domains, and charts — the full "trust chain" from raw data to rendered answer. - -> **dbt transforms your data. DQL transforms your data into trusted answers.** -> -> dbt owns the transformation layer (raw → staging → mart). DQL picks up where dbt stops — tracking lineage through the **answer layer**: mart tables → blocks → metrics → domains → charts. - ---- - -## Why Lineage Matters - -- **"If I change this table, what breaks?"** — Impact analysis shows every downstream block, metric, and domain affected -- **"Can I trust this dashboard?"** — Trust chains show certification status at every node from source to chart -- **"Where does this data come from?"** — Upstream lineage traces any block back to its source tables -- **"How does data flow between teams?"** — Cross-domain flow detection shows when data crosses business boundaries - ---- - -## Quick Start - -```bash -# Step 1: Compile your project to generate the manifest -dql compile - -# Step 2: See your project's full lineage graph (reads from manifest) -dql lineage - -# Look up any node — block, table, or metric (auto-resolved) -dql lineage raw_orders # block -dql lineage orders # source table -dql lineage total_revenue # metric - -# Explicit type lookup -dql lineage --table orders -dql lineage --metric total_revenue - -# See cross-domain data flows for a domain -dql lineage --domain finance - -# Impact analysis: what breaks if this node changes? (works on any type) -dql lineage --impact orders - -# Trust chain: certification status from source to destination -dql lineage --trust-chain raw_orders exec_dashboard - -# Export lineage as JSON (for integrations or CI) -dql lineage --format json - -# Import dbt lineage as upstream -dql compile --dbt-manifest path/to/manifest.json -``` - ---- - -## The `ref()` System - -Use `ref("block_name")` in your SQL to declare explicit dependencies between blocks. This is intentionally similar to dbt's `ref()` — familiar to dbt users. - -### Before ref() - -```dql -block "revenue_summary" { - domain = "finance" - type = "custom" - query = """ - SELECT segment, SUM(amount) AS total - FROM raw_orders_view -- implicit dependency, invisible to lineage - GROUP BY segment - """ -} -``` - -### After ref() - -```dql -block "revenue_summary" { - domain = "finance" - type = "custom" - query = """ - SELECT segment, SUM(amount) AS total - FROM ref("raw_orders") -- explicit dependency, tracked in lineage - GROUP BY segment - """ -} -``` - -With `ref()`: -- DQL knows `revenue_summary` depends on `raw_orders` -- The lineage graph shows a `feeds_into` edge between them -- `dql lineage --impact raw_orders` includes `revenue_summary` in affected nodes -- Cross-domain edges are detected automatically (e.g., `raw_orders` in domain "data" → `revenue_summary` in domain "finance") - -### ref() with different domain blocks - -```dql -block "raw_orders" { - domain = "data" - type = "custom" - owner = "data-team" - query = """ - SELECT * FROM orders - """ -} - -block "revenue_summary" { - domain = "finance" - type = "custom" - owner = "finance-team" - query = """ - SELECT segment, SUM(amount) AS total - FROM ref("raw_orders") - GROUP BY segment - """ -} - -block "exec_dashboard" { - domain = "executive" - type = "custom" - owner = "ceo" - query = """ - SELECT * FROM ref("revenue_summary") - """ - visualization { - chart = "line" - x = segment - y = total - } -} -``` - -Running `dql lineage` shows: - -``` - DQL Lineage Summary - ======================================== - - Nodes: - block: 3 - domain: 3 - source_table: 1 - - Edges: 7 - - Cross-Domain Flows: - data -> finance (1 edge(s)) - finance -> executive (1 edge(s)) - - Domain Trust: - data: 0/1 certified (0% trust) - executive: 0/1 certified (0% trust) - finance: 0/1 certified (0% trust) -``` - ---- - -## CLI Commands - -### `dql lineage [block] [path]` - -Show the full lineage graph summary for a project. If a block name is provided, shows upstream/downstream for that specific block. - -```bash -# Full project summary -dql lineage - -# Specific block -dql lineage raw_orders - -# From a different directory -dql lineage /path/to/project -dql lineage raw_orders /path/to/project -``` - -**Summary output:** - -``` - DQL Lineage Summary - ======================================== - - Nodes: - block: 3 - dimension: 1 - domain: 3 - metric: 1 - source_table: 1 - - Edges: 7 - - Cross-Domain Flows: - data -> finance (1 edge(s)) - finance -> executive (1 edge(s)) - - Domain Trust: - data: 1/1 certified (100% trust) - executive: 0/1 certified (0% trust) - finance: 1/1 certified (100% trust) -``` - -**Block lineage output:** - -``` - Lineage for: raw_orders - ======================================== - Domain: data - Owner: data-team - - Upstream (1): - source_table:orders - - Downstream (2): - block:revenue_summary (finance) - block:exec_dashboard (executive) -``` - -### `dql lineage --domain ` - -Show all blocks, metrics, and nodes in a specific domain, plus data flows in and out. - -```bash -dql lineage --domain finance -``` - -``` - Domain Lineage: finance - ======================================== - - Blocks: 1 - Certified: 1 - Trust Score: 100% - - Nodes in domain (3): - block:revenue_summary - metric:total_revenue - domain:finance - - Data flows IN from: - data (1 edge(s)) - - Data flows OUT to: - executive (1 edge(s)) -``` - -### `dql lineage --impact ` - -Show what breaks if a block changes. Lists all downstream affected nodes grouped by domain. - -```bash -dql lineage --impact raw_orders -``` - -``` - Impact Analysis: raw_orders - ======================================== - - Total downstream affected: 2 - - By domain: - finance: 1 node(s), 1 certified - - revenue_summary [certified] - executive: 1 node(s), 0 certified - - exec_dashboard - - Domain boundaries crossed: - data -> finance (1 edge(s)) - finance -> executive (1 edge(s)) -``` - -### `dql lineage --trust-chain ` - -Show the certification status at every node in the path from one block to another. - -```bash -dql lineage --trust-chain raw_orders exec_dashboard -``` - -``` - Trust Chain: raw_orders -> exec_dashboard - ======================================== - - Trust Score: 67% (2/3 certified) - - Chain: - [CERTIFIED] raw_orders (data) — data-team - -> [CERTIFIED] revenue_summary (finance) — finance-team - -> [ ] exec_dashboard (executive) — ceo - - Domain boundaries: - data -> finance - finance -> executive -``` - -### `dql lineage --format json` - -Export the full lineage graph as JSON for CI pipelines, external tools, or custom visualizations. - -```bash -dql lineage --format json > lineage.json -``` - -The JSON contains `nodes` and `edges` arrays with full metadata: - -```json -{ - "nodes": [ - { - "id": "block:raw_orders", - "type": "block", - "name": "raw_orders", - "domain": "data", - "owner": "data-team" - } - ], - "edges": [ - { - "source": "table:orders", - "target": "block:raw_orders", - "type": "reads_from" - }, - { - "source": "block:raw_orders", - "target": "block:revenue_summary", - "type": "feeds_into" - }, - { - "source": "block:raw_orders", - "target": "block:revenue_summary", - "type": "crosses_domain", - "sourceDomain": "data", - "targetDomain": "finance" - } - ] -} -``` - ---- - -## Lineage Graph Model - -### Node Types - -| Type | Description | Example ID | -|------|-------------|------------| -| `source_table` | External table read by blocks | `table:orders` | -| `block` | DQL block (custom or semantic) | `block:revenue_summary` | -| `metric` | Semantic layer metric | `metric:total_revenue` | -| `dimension` | Semantic layer dimension | `dimension:segment` | -| `domain` | Business domain grouping | `domain:finance` | -| `chart` | Visualization attached to a block | `chart:revenue_summary` | - -### Edge Types - -| Type | Description | -|------|-------------| -| `reads_from` | Block reads from a source table | -| `feeds_into` | Block output feeds into another block (via `ref()`) | -| `aggregates` | Metric aggregates from a source table | -| `visualizes` | Chart visualizes a block or metric | -| `crosses_domain` | Data crosses a business domain boundary | -| `certified_by` | Block certified by a person/process | - ---- - -## Notebook Lineage Panel - -The notebook sidebar includes a **Lineage** panel (graph icon between Semantic and Outline) that shows: - -- **Summary bar** — total blocks, metrics, source tables, and domains -- **Block detail** — click any block to see its upstream and downstream dependencies -- **Cross-domain flows** — which domains send data to or receive data from other domains -- **Collapsible sections** — Blocks, Metrics, Source Tables, and Domains - -The panel reads from the same lineage API that powers the CLI commands. - ---- - -## Lineage API Endpoints - -The notebook server exposes lineage data via REST: - -| Endpoint | Description | -|----------|-------------| -| `GET /api/lineage` | Full lineage graph as JSON | -| `GET /api/lineage/block/:name` | Subgraph for a specific block (upstream/downstream) | -| `GET /api/lineage/domain/:name` | Domain-scoped view | -| `GET /api/lineage/impact/:block` | Impact analysis result | -| `GET /api/lineage/trust-chain?from=X&to=Y` | Trust chain between two blocks | - ---- - -## How DQL Builds Lineage - -DQL constructs the lineage graph automatically from your project: - -1. **SQL parsing** — Extracts table references from `FROM`, `JOIN`, and `INTO` clauses in each block's SQL -2. **ref() resolution** — Explicit `ref("block_name")` calls create `feeds_into` edges between blocks -3. **Semantic layer** — Metric `table` fields connect metrics to their source tables via `aggregates` edges -4. **Visualization config** — `chart` types in blocks create `visualizes` edges -5. **Domain fields** — `domain` on blocks and metrics creates domain nodes and `crosses_domain` edges when data flows between different domains - -No manual lineage configuration needed — it's all derived from your existing `.dql` files and semantic YAML. - ---- - -## Tutorial: Add Lineage to an Existing Project - -### Step 1 — Add ref() between blocks - -If you have blocks that depend on each other, replace direct table references with `ref()`: - -```dql --- Before: implicit dependency -query = """ - SELECT * FROM clean_orders - WHERE amount > 100 -""" - --- After: explicit dependency tracked in lineage -query = """ - SELECT * FROM ref("clean_orders") - WHERE amount > 100 -""" -``` - -### Step 2 — Add domain metadata - -Ensure each block has a `domain` field: - -```dql -block "clean_orders" { - domain = "data" - owner = "data-team" - ... -} - -block "revenue_report" { - domain = "finance" - owner = "finance-team" - ... -} -``` - -### Step 3 — View your lineage - -```bash -dql lineage -``` - -You'll see: -- All blocks and their source table dependencies -- Cross-domain flows where data crosses team boundaries -- Domain trust scores based on certification status - -### Step 4 — Investigate specific flows - -```bash -# What does revenue_report depend on? -dql lineage revenue_report - -# What breaks if clean_orders changes? -dql lineage --impact clean_orders - -# What does the finance domain look like? -dql lineage --domain finance - -# Full trust chain from source to report -dql lineage --trust-chain clean_orders revenue_report -``` - ---- - -## DQL + dbt: Complementary Lineage - -dbt tracks lineage at the transformation layer: `raw_table → staging_model → mart_table`. - -DQL extends lineage through the answer layer: - -``` -dbt territory: raw_table → staging_model → mart_table - ───────────────────────────────────── -DQL territory: mart_table - ↓ - block (owner: data-team, domain: data, certified ✓) - ↓ - semantic_metric (domain: finance, certified ✓) - ↓ - dashboard_block (domain: executive, certified ✓) - ↓ - rendered_chart (end user sees this) -``` - -At every node: who owns it, which domain, certification status, what changed upstream. - -If you use dbt, configure your connection to point at dbt's target warehouse and use DQL's semantic layer with `"provider": "dbt"` to inherit your dbt metrics. DQL's lineage then tracks everything from the mart table forward. - ---- - -## Next Steps - -- [Authoring Blocks](./authoring-blocks.md) — create blocks with `ref()` and domain metadata -- [Semantic Layer Guide](./semantic-layer-guide.md) — define metrics that appear in lineage -- [CLI Reference](./cli-reference.md) — full `dql lineage` command reference -- [Data Sources](./data-sources.md) — connect to your warehouse for real lineage diff --git a/docs/notebook.md b/docs/notebook.md deleted file mode 100644 index 523c88a7..00000000 --- a/docs/notebook.md +++ /dev/null @@ -1,461 +0,0 @@ -# Notebook Guide - -The DQL Notebook is a browser-first SQL notebook that runs entirely on your local machine. It uses DuckDB as the query engine, so queries execute against local files, CSVs, and Parquet datasets without any external database. Think of it as a Jupyter notebook purpose-built for analytics — SQL-native, chart-aware, and file-system integrated. - ---- - -## Launch - -Start the notebook server from inside your project directory: - -```bash -dql notebook -``` - -Pass a path to a project outside the current directory: - -```bash -dql notebook ./my-dql-project -``` - -**Flags:** - -| Flag | Description | -|---|---| -| `--port ` | Run on a specific port instead of the default `3474` | -| `--no-open` | Start the server without opening the browser automatically | - -**Examples:** - -```bash -dql notebook --port 4488 -dql notebook ./my-dql-project --port 4488 --no-open -``` - -**Terminal output:** - -```text - ✓ Notebook ready: http://127.0.0.1:3474 - Press Ctrl+C to stop. -``` - ---- - -## The Interface - -The notebook UI has four main areas: - -- **Header bar** — project name, Save button, Export menu, and connection status -- **Left sidebar** — panels toggled by icons: - - **Files** — lists all files in the active project; click any file to open a source view in a new tab - - **Schema** — auto-discovered tables from `data/`; expand a table to see column names and types - - **Semantic** — browse and compose queries from your semantic layer (metrics, dimensions) - - **Lineage** — view data lineage: blocks, source tables, metrics, domains, and cross-domain flows - - **Outline** — lists every named cell in the open notebook for quick navigation -- **Cell area** — the main editing surface; cells stack vertically and can be reordered by dragging -- **Dev panel** — collapsible bottom panel showing the last query sent to DuckDB and its raw response; useful for debugging - ---- - -## Cell Types - -### SQL Cell - -The primary cell type. Backed by a CodeMirror editor with SQL syntax highlighting and DQL-aware autocomplete. - -**Running a query:** -- `Shift+Enter` or `Cmd+Enter` — run the cell -- The results panel appears directly below the editor - -**Output modes:** -- **Table** — paginated result grid (default) -- **Chart** — auto-detected when the result shape is chartable (e.g. two columns where one is a category and one is numeric); toggle between table and chart using the icon in the cell toolbar - -**Naming a cell:** - -Click the label field at the top-left of the cell (shows `Unnamed` by default) and type a name. Named cells can be referenced by downstream cells using `{{cell_name}}` syntax. - -**Example:** - -```sql -SELECT - segment_tier, - SUM(amount) AS total_revenue, - COUNT(*) AS deal_count -FROM read_csv_auto('data/revenue.csv') -GROUP BY segment_tier -ORDER BY total_revenue DESC -``` - -Name this cell `revenue_by_segment` to reference it later. - ---- - -### Markdown Cell - -Add narrative, headings, and formatted text between query cells. Supports standard Markdown: headings, bold, italic, lists, links, and inline code. - -Click **+ Markdown** in the cell toolbar to add one. Double-click a rendered Markdown cell to edit it. Click outside or press `Escape` to return to the rendered view. - -**Example:** - -```markdown -## Revenue by Segment - -The following table shows total revenue and deal count grouped by -customer segment tier for the current data snapshot. -``` - ---- - -### DQL Cell - -Runs DQL block syntax directly, without needing a separate `.dql` file. Useful for authoring and iterating on blocks inside the notebook before extracting them to `blocks/`. - -```dql -block "Pipeline Health" { - domain = "revenue" - type = "custom" - query = """ - SELECT stage, COUNT(*) AS deals - FROM read_csv_auto('data/revenue.csv') - GROUP BY stage - """ - visualization { - chart = "bar" - x = stage - y = deals - } -} -``` - -**Chart types:** DQL cells support `chart = "bar"`, `"line"`, `"pie"`, `"kpi"`, and `"table"` in their `visualization` block. The chart type is persisted when you save the notebook and restored on reload. - -**KPI cards:** Use `chart = "kpi"` to render a single big-number card. The first numeric column in the result is displayed as a large-format number with the column name (or `title` from config) as a label. - -**Block imports (`@import`):** - -DQL cells also support `@import` to reference a block file without copying its SQL: - -```dql -@import "./blocks/pipeline_health.dql" -``` - -Override the block's default parameters: - -```dql -@import "./blocks/pipeline_health.dql" with period = "previous_quarter" -``` - -The imported block's SQL is executed with merged parameters, and its `visualization` config is applied automatically. - ---- - -### Param Cell - -Renders a live interactive widget that injects a value into downstream SQL cells. This turns any notebook into a filterable dashboard. - -**Configuration fields:** - -| Field | Description | -|---|---| -| **Name** | The variable name used in `{{name}}` references | -| **Type** | `text`, `number`, `date`, or `select` | -| **Default** | The initial value | -| **Options** | Comma-separated list of choices (for `select` type only) | - -**Adding a param cell:** click **+ Param** in the cell toolbar. - -**Example configuration:** - -- Name: `segment` -- Type: `select` -- Options: `All, Enterprise, Mid-Market, SMB` -- Default: `All` - -A dropdown widget renders immediately. When the user picks a value, any downstream cell that references `{{segment}}` will use the new value on its next run. - -> **Tip:** Click the **Apply** button next to the param widget to automatically re-run all downstream cells that reference `{{param_name}}`. This saves you from manually re-running each dependent cell after changing a value. - ---- - -## Variable Substitution - -`{{cell_name}}` references let cells compose on top of each other. There are two substitution modes: - -- **Param cell reference** — the value is injected as a SQL string literal. `{{segment}}` becomes `'Enterprise'` in the compiled query. -- **Named SQL cell reference** — the result of the referenced cell is injected as a CTE (Common Table Expression). `{{revenue_by_segment}}` becomes `WITH revenue_by_segment AS (SELECT ...)`. - -### Full working example - -Given `data/revenue.csv` from the starter project: - -**Step 1 — Add a Param cell** - -- Name: `segment` -- Type: `select` -- Options: `All, Enterprise, Mid-Market, SMB` -- Default: `All` - -**Step 2 — Add a SQL cell named `revenue_by_segment`** - -```sql -SELECT segment_tier, SUM(amount) AS total -FROM read_csv_auto('data/revenue.csv') -GROUP BY segment_tier -``` - -Run it. Name the cell `revenue_by_segment`. - -**Step 3 — Add a downstream SQL cell** - -```sql -SELECT * FROM {{revenue_by_segment}} -WHERE {{segment}} = 'All' OR segment_tier = {{segment}} -``` - -Run it. The `{{revenue_by_segment}}` reference becomes a CTE; `{{segment}}` is replaced with the current dropdown value as a literal string. - -Change the dropdown to `Enterprise` and re-run. Only Enterprise rows appear. - -> **Note:** Cell references are resolved at run time. If a referenced cell has not been run yet, the notebook will prompt you to run it first. - ---- - -## Schema Panel - -The Schema panel (left sidebar, second icon) auto-discovers tables by scanning the project's `data/` directory for CSV and Parquet files. Each discovered file appears as a table entry. - -- Click a table name to expand it and see column names and inferred types. -- Click a column name to insert it at the cursor position in the active SQL cell. - -> **Note:** The schema panel reflects what DuckDB can read via `read_csv_auto()` and `read_parquet()`. It does not require a traditional database connection. - ---- - -## Semantic Panel - -The Semantic panel (left sidebar, diamond icon) lets you browse and query metrics, dimensions, and hierarchies defined in your project's semantic layer. - -### What it shows - -- **Metrics** — reusable aggregations (e.g., Total Revenue = `SUM(amount)`) -- **Dimensions** — groupable columns (e.g., Segment, Region, Channel) -- **Hierarchies** — drill paths (e.g., Year → Quarter → Month) -- **Provider badge** — shows whether the source is `dql`, `dbt`, or `cubejs` - -Click any metric or dimension to expand it and see its SQL expression, type, table, tags, and description. - -Use the search box to filter by name, label, description, or tags. - -### Compose Query (point-and-click) - -The **Compose Query** section at the top of the Semantic panel lets you build SQL without writing it: - -1. **Check metrics** — select one or more metrics (e.g., GMV, Order Count) -2. **Check dimensions** — select grouping columns (e.g., Region, Segment) -3. **Click "Compose SQL"** — DQL generates the appropriate SQL for your database driver -4. **Click "+ Insert as Cell"** — a new SQL cell is added to the end of your notebook with the query pre-filled -5. **Press Shift+Enter** to run it - -The generated SQL uses the correct dialect for your configured connection (DuckDB, Postgres, Snowflake, etc.). - -**Alternative:** Click **Copy** to copy the SQL to your clipboard and paste it manually. - -### Mixing semantic queries with custom SQL - -You can use semantic-composed cells and hand-written SQL cells in the same notebook. They all run against the same database connection. - -**Example workflow:** - -``` -Cell 1 [SQL - from Compose Query] — name it "revenue_by_region" - SELECT region, SUM(order_total) AS gmv, COUNT(*) AS order_count - FROM orders - GROUP BY region ORDER BY gmv DESC - -Cell 2 [SQL - custom] — your own deeper analysis - SELECT - region, - channel, - AVG(order_total) AS avg_order, - COUNT(DISTINCT customer_id) AS unique_customers - FROM orders - GROUP BY region, channel - ORDER BY avg_order DESC - -Cell 3 [SQL - custom] — reference cell 1 as a CTE - SELECT * FROM {{revenue_by_region}} - WHERE gmv > 10000 - -Cell 4 [Param] — segment filter dropdown - Name: segment | Type: select | Options: All, Enterprise, SMB - -Cell 5 [SQL - custom] — filtered by param - SELECT * FROM {{revenue_by_region}} - WHERE '{{segment}}' = 'All' OR region = '{{segment}}' -``` - -### Setting up the semantic layer - -If the panel shows "Set Up Semantic Layer", you need to configure it in `dql.config.json`: - -**DQL native (write YAML files):** -```json -{ "semanticLayer": { "provider": "dql" } } -``` - -**dbt (point to your dbt project):** -```json -{ "semanticLayer": { "provider": "dbt", "projectPath": "/path/to/dbt-project" } } -``` - -**Cube.js (point to your Cube project):** -```json -{ "semanticLayer": { "provider": "cubejs", "projectPath": "/path/to/cube-project" } } -``` - -See the [Semantic Layer Guide](./semantic-layer-guide.md) for full details on YAML format, dbt integration, and Cube.js integration. - -### Refreshing - -Click the refresh button (↻) in the Semantic panel toolbar to reload definitions after editing YAML files. No restart needed. - ---- - -## Lineage Panel - -The Lineage panel (graph icon in the sidebar, between Semantic and Outline) shows how data flows through your project. - -### What it shows - -- **Summary bar** — total counts of blocks, metrics, source tables, and domains -- **Blocks** — all blocks with their domain and owner; click to see upstream/downstream -- **Metrics** — semantic layer metrics and their source tables -- **Source Tables** — external tables read by blocks -- **Domains** — business domains with trust scores -- **Cross-Domain Flows** — which domains send data to or receive data from other domains - -### Exploring a block's lineage - -Click any block name to see: -- **Upstream** — source tables and blocks it depends on -- **Downstream** — blocks, metrics, and charts that depend on it -- **Domain** — which business domain it belongs to - -### When to use - -- Before changing a block — check what depends on it -- When exploring unfamiliar projects — understand the data flow -- During code review — verify that cross-domain dependencies are intentional - -The panel reads from the same lineage API as `dql lineage`. See the [Lineage Guide](./lineage.md) for CLI commands and the ref() system. - ---- - -## Hot Reload - -The notebook server watches the project directory for file changes. When a `.dqlnb` file is modified on disk (e.g. by another editor or a CLI command like `dql new notebook`), the UI reloads it automatically. CSS and UI assets also hot-reload during development. - ---- - -## Save & Load - -Notebooks are stored as `.dqlnb` files — JSON documents that contain the ordered list of cells, their source, names, and last result metadata. - -**Saving:** -- Press `Cmd+S` (macOS) or `Ctrl+S` (Windows/Linux) -- Click the **Save** button in the header bar - -The header bar shows a dot indicator when there are unsaved changes. - -**Loading:** -- Click any `.dqlnb` file in the **Files** sidebar panel -- The notebook opens in a new tab in the cell area - -**File format (simplified):** - -```json -{ - "version": 1, - "cells": [ - { - "id": "cell_01", - "type": "sql", - "name": "revenue_by_segment", - "source": "SELECT segment_tier, SUM(amount) AS total\nFROM read_csv_auto('data/revenue.csv')\nGROUP BY segment_tier" - }, - { - "id": "cell_02", - "type": "param", - "name": "segment", - "paramType": "select", - "default": "All", - "options": ["All", "Enterprise", "Mid-Market", "SMB"] - } - ] -} -``` - ---- - -## Export - -Two export formats are available from the **Export** menu in the header bar: - -### Export HTML - -Generates a fully self-contained HTML file that renders the notebook as a static dashboard. Charts and tables are embedded. The file can be shared via email or hosted on any static file server — no DQL runtime required. - -```bash -# Equivalent CLI export (from outside the UI): -dql build notebooks/revenue_analysis.dqlnb --out-dir dist/revenue_analysis -``` - -### Export .dql - -Exports the notebook as a DQL workbook file (`.dql` format). Each SQL cell becomes a `block` declaration. This is the recommended path for promoting notebook-authored queries into version-controlled `.dql` files. - ---- - -## Creating Notebooks - -### From the UI - -Click the **New Notebook** button in the Files sidebar panel. A modal prompts for a name. The file is created in `notebooks/` and opens immediately. - -### From the CLI - -```bash -dql new notebook "Revenue Analysis" -dql new notebook "Q4 Review" --out-dir reports/ -``` - -This creates `notebooks/revenue_analysis.dqlnb` with a starter SQL cell. If the notebook server is running, the new file appears in the sidebar automatically via hot reload. - -See [`dql new notebook`](./cli-reference.md#dql-new-notebook-name) in the CLI reference for full flag documentation. - ---- - -## Keyboard Shortcuts - -| Shortcut | Action | -|---|---| -| `Shift+Enter` | Run the current cell | -| `Cmd+Enter` | Run the current cell (alias) | -| `Cmd+S` | Save the notebook | -| `Escape` | Exit cell edit mode (Markdown cells) | -| `Cmd+/` | Toggle comment in SQL/DQL cells | -| `Tab` | Trigger autocomplete in SQL/DQL cells | -| `Cmd+Z` | Undo within the current cell editor | - ---- - -## Tips & Tricks - -- **Name every SQL cell.** Even if you don't plan to reference it immediately, a named cell is easier to find in the Outline panel and trivial to compose later. -- **Chain cells with `{{}}`** to build multi-step analysis pipelines. Each stage is independently runnable and debuggable. -- **Use param cells for dashboards.** A notebook with one or more param cells can be exported to HTML and used as a lightweight, self-contained filtered dashboard. -- **Use the Schema panel during exploration.** Click column names to insert them rather than typing — reduces typos against CSV column headers. -- **Keep data transformations in named SQL cells** and use a final display cell that selects from them. This way, you can swap the display query without re-running expensive transformations. -- **Export to .dql early.** When a query reaches production quality, export it to `.dql` and run `dql certify` to ensure it meets governance standards before sharing. diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index 3e0b9b5c..00000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,160 +0,0 @@ -# Quickstart - -Two paths to a running DQL notebook. Pick the one that fits. - ---- - -## Path A: DQL-Only (2 minutes, no dbt) - -Best for: trying DQL quickly, local CSV/Parquet analysis, no Python required. - -### 1. Install - -```bash -npm install -g @duckcodeailabs/dql-cli -dql --version # verify install -``` - -### 2. Create a project - -```bash -dql init my-dql-project -cd my-dql-project -``` - -This creates: -- `dql.config.json` — project config (DuckDB in-memory by default) -- `blocks/` — directory for your DQL analytics blocks -- `notebooks/welcome.dqlnb` — a starter notebook - -### 3. Verify setup - -```bash -dql doctor -``` - -All checks should pass. If any fail, follow the hints in the output. - -### 4. Open the notebook - -```bash -dql notebook -``` - -Your browser opens at `http://127.0.0.1:3474` with the welcome notebook. - -### 5. Explore - -- **Run the first SQL cell** — it lists tables in your database -- **Add data** — drop CSV or Parquet files into a `data/` directory, then query them: - ```sql - SELECT * FROM read_csv_auto('./data/orders.csv') LIMIT 10; - ``` -- **Open Block Studio** — click the Block Studio icon in the sidebar to create your first governed block (SQL + owner + tests + visualization) - ---- - -## Path B: dbt + Jaffle Shop (5 minutes) - -Best for: seeing DQL's full power — semantic metrics, dbt lineage, Block Studio with real data. - -> **Note:** `dql init` does **not** download the Jaffle Shop repo. You clone it yourself in Step 1. - -### 1. Clone and build the dbt project - -```bash -git clone https://github.com/dbt-labs/Semantic-Layer-Online-Course.git jaffle-shop -cd jaffle-shop -pip install dbt-duckdb -dbt deps -dbt build --profiles-dir . -``` - -This builds the full Jaffle Shop data model into `jaffle_shop.duckdb` — customers, orders, products, and supplies tables. - -### 2. Install DQL - -```bash -npm install -g @duckcodeailabs/dql-cli -dql --version -``` - -### 3. Initialize DQL - -```bash -dql init . -``` - -DQL auto-detects everything: -- **dbt project** — finds `dbt_project.yml`, sets semantic layer provider to `dbt` -- **DuckDB file** — finds `jaffle_shop.duckdb`, configures it as the default connection -- **Semantic definitions** — finds metrics and dimensions in `models/*.yml`, auto-imports them into `semantic-layer/` - -You do **not** need to run `dql semantic import dbt .` separately — `dql init` handles it when semantic definitions are detected. - -### 4. Verify setup - -```bash -dql doctor -``` - -You should see: -``` - ✓ dql.config.json found - ✓ Default connection driver=duckdb - ✓ Semantic layer provider=dbt, N metrics, N dimensions - ✓ Notebook app assets found - ✓ Local query runtime driver=duckdb is available -``` - -### 5. Open the notebook - -```bash -dql notebook -``` - -Your browser opens with the welcome notebook connected to Jaffle Shop. - -### 6. Explore the notebook - -- **Run `SHOW TABLES;`** — see all tables built by dbt (`dim_customers`, `fct_orders`, `order_items`, etc.) -- **Schema sidebar** (database icon) — browse tables and columns with type-colored badges. Click a column to insert it into your SQL. -- **Semantic sidebar** (layers icon) — browse the imported dbt metrics and dimensions. Click to see details (type, table, tags). - -### 7. Build a block in Block Studio - -1. Click **Block Studio** in the sidebar (or create a new block via the + button) -2. **Database panel** (left) — expand `dim_customers` to see columns. Click `lifetime_spend` to insert it. -3. **Semantic panel** — click a metric like `total_revenue` to insert `{{ metric:total_revenue }}` into your SQL -4. **Write SQL** in the editor: - ```sql - SELECT customer_name, lifetime_spend - FROM dim_customers - ORDER BY lifetime_spend DESC - LIMIT 10 - ``` -5. **Run** — click the Run button (or Ctrl/Cmd+Enter) to see results in the table below -6. **Test** — if your block has `tests { assert row_count > 0 }`, click Test to validate -7. **Save** — click Save. If this is a new block, you'll be prompted for a name, domain, and owner. The block is written to `blocks/` as a `.dql` file. - -### 8. View lineage - -```bash -dql compile --dbt-manifest target/manifest.json -dql lineage -``` - -See the full data flow: dbt source tables → staging → marts → DQL blocks. - ---- - -## What's next - -| Goal | Guide | -|------|-------| -| Full Jaffle Shop walkthrough | [Getting Started](./getting-started.md) | -| Connect Snowflake, Postgres, or other databases | [Enterprise Getting Started](./enterprise-getting-started.md) | -| Learn notebook features (params, charts, export) | [Notebook Guide](./notebook.md) | -| DQL block syntax reference | [Language Spec](./dql-language-spec.md) | -| Browse all 14 database connectors | [Data Sources](./data-sources.md) | -| Import semantic metrics from dbt/Cube.js | [Semantic Layer Guide](./semantic-layer-guide.md) | diff --git a/docs/semantic-layer-guide.md b/docs/semantic-layer-guide.md deleted file mode 100644 index cb686b66..00000000 --- a/docs/semantic-layer-guide.md +++ /dev/null @@ -1,403 +0,0 @@ -# Semantic Layer — Getting Started Guide - -The semantic layer lets you define reusable metrics, dimensions, and hierarchies that appear in the DQL Notebook sidebar and can be queried from DQL blocks. - -## Quick Start (2 minutes) - -### 1. Create a project (if you don't have one) - -```bash -dql init . -``` - -This creates a DQL project. If a dbt project is detected, the semantic layer provider is set to `dbt` automatically and semantic definitions are auto-imported. - -### 2. Open the notebook - -```bash -dql notebook -``` - -### 3. Click the Semantic Layer icon in the left sidebar (3rd icon from top) - -You'll see your metrics, dimensions, and hierarchies. Click any metric to see its details (SQL, type, table, tags). Click to insert a reference into your SQL. - -> **Full walkthrough:** [Enterprise Getting Started](./enterprise-getting-started.md) covers importing semantic metrics from the notebook UI step by step. - ---- - -## How It Works - -DQL reads semantic definitions from YAML files and exposes them in the notebook UI. There are **4 providers** — pick the one that matches your setup: - -| Provider | Source | Best for | -|----------|--------|----------| -| `dql` | YAML files in `semantic-layer/` | New projects, standalone analytics | -| `dbt` | Your existing dbt project | Teams already using dbt semantic models | -| `cubejs` | Your existing Cube.js project | Teams already using Cube.js | -| `snowflake` | Snowflake semantic views (live) | Teams using Snowflake as semantic layer | - ---- - -## Option A: DQL Native (YAML files) - -This is the simplest option. You write YAML files directly in your project. - -### Directory structure - -``` -my-project/ -├── dql.config.json -├── data/ -│ └── revenue.csv -└── semantic-layer/ - ├── metrics/ ← one YAML file per metric - │ └── total_revenue.yaml - ├── dimensions/ ← one YAML file per dimension - │ └── segment.yaml - ├── hierarchies/ ← one YAML file per hierarchy (optional) - │ └── time_hierarchy.yaml - └── cubes/ ← one YAML file per cube (optional, advanced) - └── revenue_cube.yaml -``` - -### dql.config.json - -```json -{ - "project": "my-project", - "defaultConnection": { - "driver": "file", - "filepath": ":memory:" - }, - "dataDir": "./data", - "semanticLayer": { - "provider": "dql" - }, - "preview": { - "port": 3474, - "open": true - } -} -``` - -### Metric YAML — `semantic-layer/metrics/total_revenue.yaml` - -```yaml -name: total_revenue -label: Total Revenue -description: Sum of all recognized revenue. -domain: finance -sql: SUM(amount) -type: sum -table: fct_revenue -tags: - - revenue - - kpi -owner: analytics-team -``` - -**Required fields:** `name`, `sql`, `type`, `table` - -**Supported types:** `sum`, `count`, `count_distinct`, `avg`, `min`, `max`, `custom` - -### Dimension YAML — `semantic-layer/dimensions/segment.yaml` - -```yaml -name: segment -label: Customer Segment -description: Customer segment tier (Enterprise, Mid-Market, SMB). -sql: segment_tier -type: string -table: fct_revenue -tags: - - customer -``` - -**Required fields:** `name`, `sql`, `type`, `table` - -**Supported types:** `string`, `number`, `date`, `boolean` - -### Hierarchy YAML — `semantic-layer/hierarchies/time_hierarchy.yaml` - -```yaml -name: fiscal_time -label: Fiscal Time -description: Drill from year to quarter. -domain: finance -levels: - - name: fiscal_year - label: Fiscal Year - dimension: fiscal_year - order: 1 - - name: fiscal_quarter - label: Fiscal Quarter - dimension: fiscal_quarter - order: 2 -defaultRollup: sum -``` - -### Cube YAML — `semantic-layer/cubes/revenue_cube.yaml` (advanced) - -Cubes group measures, dimensions, time dimensions, and joins into a single definition. Use cubes when you have multi-table models. - -```yaml -name: revenue -label: Revenue Cube -description: Core revenue analysis cube. -table: fct_revenue -domain: finance - -measures: - - name: total_revenue - sql: SUM(amount) - type: sum - - name: deal_count - sql: COUNT(*) - type: count - -dimensions: - - name: segment_tier - sql: segment_tier - type: string - -time_dimensions: - - name: recognized_at - sql: recognized_at - primary_time: true - granularities: - - day - - month - - quarter - - year - -# Connect to other cubes for cross-table queries -joins: - - name: customers - type: left - sql: "${left}.customer_id = ${right}.id" -``` - ---- - -## Option B: Connect to a dbt Project - -If you already have a dbt project with `semantic_models` (dbt 1.6+), DQL can read directly from it. - -### dql.config.json - -```json -{ - "project": "my-project", - "defaultConnection": { - "driver": "snowflake", - "account": "your-account.snowflakecomputing.com", - "username": "your_user", - "password": "${SNOWFLAKE_PASSWORD}", - "database": "ANALYTICS", - "schema": "PUBLIC", - "warehouse": "COMPUTE_WH" - }, - "semanticLayer": { - "provider": "dbt", - "projectPath": "/Users/you/code/my-dbt-project" - } -} -``` - -**`projectPath`** — absolute or relative path to your dbt project root (the directory containing `dbt_project.yml`). - -DQL scans `models/**/*.yml` for: -- `semantic_models` blocks (measures, dimensions, entities) -- `metrics` blocks (simple, derived, cumulative) - -### What your dbt YAML should look like - -```yaml -# models/staging/_schema.yml -semantic_models: - - name: orders - model: ref('stg_orders') - defaults: - agg_time_dimension: order_date - entities: - - name: customer - type: foreign - expr: customer_id - dimensions: - - name: status - type: categorical - - name: order_date - type: time - type_params: - time_granularity: day - measures: - - name: total_revenue - agg: sum - expr: amount - - name: order_count - agg: count - expr: id - -metrics: - - name: revenue - label: Total Revenue - description: Sum of order amounts - type: simple - type_params: - measure: total_revenue -``` - -### dbt aggregation types supported - -| dbt `agg` | DQL `type` | -|-----------|------------| -| `sum` | `sum` | -| `count` | `count` | -| `count_distinct` | `count_distinct` | -| `average` / `avg` | `avg` | -| `min` | `min` | -| `max` | `max` | - ---- - -## Option C: Connect to a Cube.js Project - -If you use Cube.js (or Cube Cloud), DQL can read your cube definitions. - -### dql.config.json - -```json -{ - "project": "my-project", - "defaultConnection": { - "driver": "postgres", - "host": "localhost", - "port": 5432, - "database": "analytics" - }, - "semanticLayer": { - "provider": "cubejs", - "projectPath": "/Users/you/code/my-cube-project" - } -} -``` - -DQL scans `model/` or `schema/` directory for YAML files containing `cubes:` blocks. - -### What your Cube.js YAML should look like - -```yaml -# model/Orders.yml -cubes: - - name: Orders - sql_table: public.orders - measures: - - name: count - type: count - - name: totalAmount - type: sum - sql: amount - dimensions: - - name: status - type: string - sql: status - - name: createdAt - type: time - sql: created_at - joins: - - name: Users - sql: "{CUBE}.user_id = {Users}.id" - relationship: many_to_one -``` - ---- - -## Option D: Snowflake Semantic Views (Live Connection) - -If your team uses Snowflake's native semantic views, DQL can query them directly through your live Snowflake connection. - -### dql.config.json - -```json -{ - "project": "my-project", - "defaultConnection": { - "driver": "snowflake", - "account": "your-account.snowflakecomputing.com", - "username": "your_user", - "password": "${SNOWFLAKE_PASSWORD}", - "database": "ANALYTICS", - "schema": "PUBLIC", - "warehouse": "COMPUTE_WH" - }, - "semanticLayer": { - "provider": "snowflake" - } -} -``` - -DQL queries the Snowflake connection at startup to discover semantic views, metrics, and dimensions. This requires a working Snowflake connection. - -### Import from the Notebook UI - -1. Open `dql notebook` -2. Click the **Semantic** sidebar icon -3. Select **Snowflake** as the provider -4. Click **Import** — DQL queries your Snowflake connection to discover semantic views -5. Imported definitions are written to `semantic-layer/` as YAML files for offline use - -> **Tip:** After initial import, DQL reads from the local `semantic-layer/` YAML files. Run a sync from the notebook UI to pull updated definitions from Snowflake. - ---- - -## Verify Your Setup - -### From the CLI - -```bash -dql doctor -``` - -Look for the `semantic-layer` check — it should show `found`. - -### From the API - -With the notebook server running: - -```bash -curl http://127.0.0.1:3474/api/semantic-layer | python3 -m json.tool -``` - -You should see your metrics, dimensions, and hierarchies in the JSON response. - -### From the Notebook UI - -1. Open the notebook (`dql notebook`) -2. Click the **Semantic Layer icon** (3rd icon in the left activity bar) -3. You should see your metrics and dimensions listed -4. Click any metric to see its details (table, type, tags) -5. Use the search box to filter - ---- - -## Hot Reload - -When the notebook server is running, any changes to files in `semantic-layer/` are automatically detected. The UI will refresh when you click the refresh button in the semantic panel toolbar. - ---- - -## Troubleshooting - -| Symptom | Cause | Fix | -|---------|-------|-----| -| "No semantic layer configured" | Missing `semantic-layer/` directory or `semanticLayer` in config | Create the directory and add YAML files | -| Panel shows 0 metrics | YAML files missing required `sql`/`type`/`table` fields | Check each YAML has all required fields | -| dbt metrics not appearing | `semantic_models` not defined in your dbt YAML | Requires dbt 1.6+ semantic model format | -| API returns 404 | Old CLI version without the endpoint | Rebuild: `cd apps/cli && npm install -g .` | - ---- - -## Reference: dql.config.json - -See `templates/dql.config.reference.json` for a complete reference of all connection and semantic layer options. diff --git a/docs/use-cases.md b/docs/use-cases.md deleted file mode 100644 index 1a707049..00000000 --- a/docs/use-cases.md +++ /dev/null @@ -1,134 +0,0 @@ -# Use Cases - -This guide maps DQL's open-source workflows to the most common user goals. - -The command snippets assume `dql` is already available on your shell `PATH`. If you are running from a source checkout, replace `dql` with `pnpm --filter @duckcodeailabs/dql-cli exec dql` from the repo root. - -## 1. Evaluate DQL quickly - -Use this path if you want to understand the product in under ten minutes. Start with the [Jaffle Shop semantic-layer course repo](https://github.com/dbt-labs/Semantic-Layer-Online-Course), which already includes dbt semantic models and metrics: - -```bash -git clone https://github.com/dbt-labs/Semantic-Layer-Online-Course.git jaffle-shop -cd jaffle-shop -pip install dbt-duckdb && dbt deps && dbt build --profiles-dir . -npm install -g @duckcodeailabs/dql-cli -dql init . -dql semantic import dbt . -dql doctor -dql notebook -``` - -Best fit: - -- engineering leaders evaluating an OSS analytics workflow -- data practitioners comparing DQL with Malloy or notebook-first tooling -- contributors reviewing the end-user experience - -## 2. Explore data in the browser notebook - -Use this path if you want an interactive, cell-by-cell workflow against a real dbt project. - -Best fit: - -- ad hoc SQL exploration against dbt mart tables -- DQL block prototyping -- notebook-based demos and internal enablement - -## 3. Author reusable analytics blocks - -Use this path if your goal is durable Git-based analytics assets rather than one-off queries. - -Typical workflow: - -```bash -dql new block "Pipeline Health" -dql parse blocks/pipeline_health.dql -dql certify blocks/pipeline_health.dql -``` - -Best fit: - -- analytics engineers building reusable SQL + chart assets -- teams that want tests and metadata in source control -- semantic-layer experimentation before a heavier BI rollout - -## 4. Build dashboards and workbooks - -Use this path if you want a static deliverable from DQL assets. - -Typical workflow: - -```bash -dql new dashboard "Revenue Overview" -dql new workbook "Quarterly Review" -dql build dashboards/revenue_overview.dql -dql serve dist/revenue_overview --open -``` - -Best fit: - -- lightweight internal reporting -- static artifact reviews in PRs -- browser validation before embedding elsewhere - -## 5. Track data lineage and impact - -Use this path if you want to understand data flow, cross-domain dependencies, and certification trust chains. - -Typical workflow: - -```bash -dql lineage # full project lineage summary -dql lineage revenue_by_segment # upstream/downstream for a block -dql lineage --domain finance # what's in the finance domain? -dql lineage --impact clean_orders # what breaks if clean_orders changes? -dql lineage --trust-chain raw_orders exec_dashboard # trust at every hop -dql lineage --format json > lineage.json # export for CI or external tools -``` - -Best fit: - -- teams tracking cross-domain data flows (data → finance → executive) -- analytics engineers assessing impact before changing upstream blocks -- data governance leads auditing certification coverage by domain -- CI pipelines that need lineage metadata for automated checks - -See also: - -- [Lineage & Trust Chains](./lineage.md) - -## 6. Connect to a real database - -Use this path after you validate the local-first experience. - -Typical workflow: - -- update `dql.config.json` -- run `dql doctor` -- test the connection in the notebook -- run blocks against the remote source - -Best fit: - -- teams moving from sample data to warehouse-backed analysis -- connector validation for PostgreSQL, Snowflake, BigQuery, Redshift, Fabric, Databricks, Athena, Trino, and more - -See also: - -- [Data Sources](./data-sources.md) -- [CLI Reference](./cli-reference.md) - -## 7. Validate the full repo before release - -Use this path if you are contributing to DQL itself. - -Typical workflow: - -```bash -pnpm install -pnpm build -pnpm test -``` - -Then follow the full smoke test guide in [Repo Testing](./repo-testing.md). diff --git a/docs/why-dql.md b/docs/why-dql.md deleted file mode 100644 index 02bffa3f..00000000 --- a/docs/why-dql.md +++ /dev/null @@ -1,115 +0,0 @@ -# Why DQL - -## The $40,000 Question - -You query revenue last Tuesday. A colleague queries it Thursday. The numbers differ by $40,000. Neither of you knows why. - -Was it the date filter? A different join? A customer segment definition that changed last month? You spend two hours digging through Slack, a shared Notion doc, and three BI "saved queries" that all have slightly different SQL. You never find a definitive answer. You ship the number you feel most confident about and move on. - -This is not a data quality problem. It is a version control problem. Your analytics logic has no canonical home, no ownership, no history, and no tests. The same SQL exists in five places and has quietly drifted into five different answers. - -DQL fixes the root cause: it makes each analytics answer a single file that lives in Git. - ---- - -## Before DQL / After DQL - -| Situation | Before | After | -|---|---|---| -| Where does the revenue query live? | Slack, Notion, BI saved queries, a notebook | `blocks/revenue_by_segment.dql` in Git | -| Who owns this metric? | Unknown, ask around | `owner = "data-team"` in the block | -| Why did the number change? | No way to know | `git log blocks/revenue_by_segment.dql` | -| Is the chart still in sync with the SQL? | Maybe — last person to edit either might know | Chart config lives inside the same file as the SQL | -| Can I run this against my local CSV? | Set up a whole environment | `dql preview blocks/revenue_by_segment.dql --open` | -| Can someone review my analytics change? | No standard workflow | Open a PR — the diff shows exactly what changed | -| Does this query actually return data? | Ship and hope | `tests { assert row_count > 0 }` | -| Can I reuse this across dashboards? | Copy-paste and drift | Reference the block by name | - ---- - -## Who DQL Is For - -### Data Analyst - -You answer the same question every Monday. Someone asks on Thursday and gets a different number because they ran a slightly different query. You have no way to say "use this one, it is the canonical version." - -DQL gives you a place to commit the answer. One file, one home, one source of truth. When someone asks again, you send them a Git path. - -### Analytics Engineer - -You use dbt to model clean tables. But once the data leaves dbt, it scatters. The charting logic lives in Tableau. The segment definitions live in a Notion doc. The filter parameters live in someone's head. There is no PR review for analytics answers — only for models. - -DQL is the dbt-style layer for answer assets. The same rigor you bring to models — versioning, testing, ownership — now applies to the blocks that actually get used in dashboards and reports. - -### Data Team Lead - -You cannot trust the dashboards. Not because the data is bad, but because you cannot tell which queries are current, which are stale, and whether any of them have tests. When someone changes a dashboard, there is no diff. No review. No rollback. - -DQL gives your team a Git-native analytics workflow. Changes go through PRs. Every block has an owner. Failures have a traceable cause. - ---- - -## DQL vs Everything Else - -| | Raw SQL | dbt | BI Tools | DQL | -|---|---|---|---|---| -| Git-native | Manual | Yes (models) | No | Yes (blocks) | -| Testable | No | Yes (models) | No | Yes (answers) | -| Local preview | No | No | No | Yes | -| Visualization config | No | No | Yes | Yes | -| Reusable parameters | No | Macros | Limited | Yes | -| Portable (no vendor lock-in) | Yes | Partial | No | Yes | -| Covers the "answer" layer | No | No | Yes | Yes | - -> dbt and DQL are complementary. Use dbt to model your data. Use DQL for the blocks that answer business questions on top of those models. - ---- - -## Why Now: The AI Angle - -AI can write a SQL query in ten seconds. That is not the problem anymore. - -The problem is that AI-generated SQL creates sprawl faster than humans ever could. Every conversation produces a query. Few of them get saved anywhere useful. None of them have tests. None of them have owners. Three months later, six people have independently asked the same question and gotten six slightly different answers. - -DQL is the contract layer that makes AI-generated analytics durable. You take the query the AI wrote, wrap it in a block, add an owner, add a test, commit it. Now it exists. Now it can be reviewed. Now it does not disappear. - -AI proposes. DQL keeps. - ---- - -## What It Feels Like to Use DQL - -**1. Init.** You have a CSV. You run `dql init myproject` and `dql notebook`. The browser opens. You drag the CSV into `data/`. You are writing SQL against it in thirty seconds, no warehouse credentials, no setup. - -**2. Explore.** The notebook gives you SQL cells, markdown cells, and param widgets. You write a query. DQL auto-charts it. You adjust the visualization config inline. You try a different filter using a param widget — no code change needed. - -**3. Author a block.** When the query is right, you run `dql new block "Revenue by Segment"`. A `.dql` file is scaffolded with your SQL, chart config, and a test stub. You fill in the owner and tags. - -**4. Preview.** `dql preview blocks/revenue_by_segment.dql --open` renders the chart in the browser with live data. You tweak the SQL. Hot reload. Done. - -**5. Commit.** `git add blocks/revenue_by_segment.dql && git commit`. Your analytics answer now has a home, a history, and a diff. The next person who asks gets a file path, not a screenshot. - ---- - -## Good First Step - -```bash -npm install -g @duckcodeailabs/dql-cli -dql init . -dql doctor -dql notebook -``` - -Then author your first block: - -```bash -dql new block "Pipeline Health" -dql preview blocks/pipeline_health.dql --open -``` - -Continue with: - -- [Getting Started](./getting-started.md) -- [Examples](./examples.md) -- [FAQ](./faq.md) -- [Migration Guides](./migration-guides/README.md) diff --git a/examples/gallery/README.md b/examples/gallery/README.md new file mode 100644 index 00000000..a2d289db --- /dev/null +++ b/examples/gallery/README.md @@ -0,0 +1,50 @@ +# DQL Example Gallery + +Three repositories showing DQL at three scales. Each is runnable in under +5 minutes from a clean machine via `create-dql-app` or direct git clone. + +| Name | Scale | Dataset | What it demos | +| --- | --- | --- | --- | +| **jaffle-shop** | ~50 models | DuckDB seed | Quickstart, certified blocks, dashboard compile | +| **retail-mart** | ~500 models | Postgres | Multi-domain lineage, governance certification flow, impact analysis | +| **saas-analytics** | ~2,000 models | BigQuery | Manifest cache performance, selective dbt import, cross-domain detection | + +## jaffle-shop (bundled in create-dql-app) + +Ships as the default template. + +```bash +npx create-dql-app my-demo +``` + +## retail-mart + +Public repo: + +```bash +git clone https://github.com/duckcode-ai/dql-example-retail +cd dql-example-retail && pnpm install +dql notebook +``` + +## saas-analytics + +Public repo: + +```bash +git clone https://github.com/duckcode-ai/dql-example-saas +cd dql-example-saas && pnpm install +dql sync dbt +dql notebook +``` + +## Stress test (synthetic 4,000-model dbt project) + +For performance benchmarking — generated, not committed: + +```bash +node scripts/bench/gen-dbt-project.mjs --models 4000 --out /tmp/stress +node scripts/bench/run-bench.mjs /tmp/stress +``` + +See [docs.duckcode.ai/contribute/testing](https://docs.duckcode.ai/contribute/testing/). diff --git a/packages/create-dql-app/README.md b/packages/create-dql-app/README.md new file mode 100644 index 00000000..b2e8c71b --- /dev/null +++ b/packages/create-dql-app/README.md @@ -0,0 +1,40 @@ +# create-dql-app + +The fastest way to start a DQL project. + +```bash +npx create-dql-app my-analytics +cd my-analytics +npx @duckcodeailabs/dql-cli notebook +``` + +Opens a running notebook at in under 5 minutes on a +clean machine. No global install required. + +## Templates + +| Template | What you get | +| --- | --- | +| `jaffle-shop` *(default)* | DuckDB + the Jaffle Shop dataset + a sample notebook, certified block, and dashboard | +| `empty` | Just a `cdql.yaml` and project layout — bring your own warehouse | + +```bash +npx create-dql-app finance-reports --template empty +``` + +## Flags + +| Flag | Default | Meaning | +| --- | --- | --- | +| `--template ` | `jaffle-shop` | Starter template | +| `--no-install` | off | Skip downloading the Jaffle Shop seed data | + +## Docs + +- [Quickstart](https://docs.duckcode.ai/get-started/quickstart/) +- [Concepts](https://docs.duckcode.ai/get-started/concepts/) +- [Connect your own warehouse](https://docs.duckcode.ai/guides/connect-warehouse/) + +## License + +MIT diff --git a/packages/create-dql-app/bin/create-dql-app.mjs b/packages/create-dql-app/bin/create-dql-app.mjs new file mode 100755 index 00000000..90830a81 --- /dev/null +++ b/packages/create-dql-app/bin/create-dql-app.mjs @@ -0,0 +1,182 @@ +#!/usr/bin/env node +// create-dql-app — self-contained scaffolder. +// +// Contract (from the v1.0 "demo gate"): on a clean machine, `npx +// create-dql-app` followed by `cd … && npx @duckcodeailabs/dql-cli notebook` +// produces a running notebook in under 5 minutes. +// +// Design: this package writes template files *itself* rather than +// delegating to `dql init`, so it stays self-contained and installable +// with zero-peer-dep friction. Templates live under ../templates/ and are +// copied verbatim. + +import { spawnSync } from 'node:child_process'; +import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve, basename, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const VERSION = '1.0.1'; +const __dirname = dirname(fileURLToPath(import.meta.url)); +const TEMPLATES_DIR = resolve(__dirname, '..', 'templates'); + +// Tiny ANSI helpers — no dep on chalk/kleur so the bin runs before +// `npm install` finishes on slow machines. +const c = { + cyan: (s) => `\x1b[36m${s}\x1b[0m`, + green: (s) => `\x1b[32m${s}\x1b[0m`, + red: (s) => `\x1b[31m${s}\x1b[0m`, + yellow: (s) => `\x1b[33m${s}\x1b[0m`, + bold: (s) => `\x1b[1m${s}\x1b[0m`, + dim: (s) => `\x1b[2m${s}\x1b[0m`, +}; + +function usage() { + console.log(`create-dql-app ${VERSION} + +Usage: + npx create-dql-app [options] + +Options: + --template Starter template: jaffle-shop (default), empty + --help, -h Show this help + --version, -v Show version + +Examples: + npx create-dql-app my-analytics + npx create-dql-app finance-reports --template empty +`); +} + +function parseArgs(argv) { + const args = { dir: null, template: 'jaffle-shop' }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === '--help' || a === '-h') { usage(); process.exit(0); } + if (a === '--version' || a === '-v') { console.log(VERSION); process.exit(0); } + if (a === '--template') { args.template = argv[++i]; continue; } + if (a.startsWith('--')) { console.error(`Unknown flag: ${a}`); process.exit(2); } + if (!args.dir) args.dir = a; + } + return args; +} + +function isEmptyDir(dir) { + try { return readdirSync(dir).length === 0; } catch { return true; } +} + +function copyDir(src, dst) { + mkdirSync(dst, { recursive: true }); + for (const entry of readdirSync(src)) { + const s = join(src, entry); + const d = join(dst, entry); + if (statSync(s).isDirectory()) copyDir(s, d); + else writeFileSync(d, readFileSync(s)); + } +} + +function substitute(file, vars) { + const raw = readFileSync(file, 'utf-8'); + const out = raw.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`); + if (out !== raw) writeFileSync(file, out, 'utf-8'); +} + +function walk(dir) { + const out = []; + for (const entry of readdirSync(dir)) { + const p = join(dir, entry); + if (statSync(p).isDirectory()) out.push(...walk(p)); + else out.push(p); + } + return out; +} + +function detectDbtSibling(target) { + // Walk up 2 levels looking for a dbt_project.yml — common layout is + // myproject/dql + myproject/dbt, or myproject with dbt + dql as siblings. + for (const rel of ['..', '../..', '../dbt', '../../dbt']) { + const probe = resolve(target, rel, 'dbt_project.yml'); + if (existsSync(probe)) return resolve(target, rel); + } + return null; +} + +function detectPackageManager() { + const ua = process.env.npm_config_user_agent ?? ''; + if (ua.startsWith('pnpm/')) return 'pnpm'; + if (ua.startsWith('yarn/')) return 'yarn'; + return 'npm'; +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + if (!args.dir) { + console.error('Error: project directory is required.\n'); + usage(); + process.exit(2); + } + + const target = resolve(process.cwd(), args.dir); + const projectName = basename(target); + + if (existsSync(target) && !isEmptyDir(target)) { + console.error(c.red(`✗ Target directory "${args.dir}" exists and is not empty.`)); + process.exit(1); + } + + const tplDir = join(TEMPLATES_DIR, args.template); + if (!existsSync(tplDir)) { + console.error(c.red(`✗ Unknown template: ${args.template}`)); + console.error(` Available: ${readdirSync(TEMPLATES_DIR).join(', ')}`); + process.exit(1); + } + + console.log(c.cyan(`\n⌁ create-dql-app ${VERSION}`)); + console.log(` scaffolding ${c.bold(projectName)} (template: ${c.bold(args.template)})\n`); + + copyDir(tplDir, target); + + const dbtSibling = detectDbtSibling(target); + // Store dbt path as relative-to-project — keeps dql.config.json portable. + const dbtRel = dbtSibling ? (relative(target, dbtSibling) || '.') : '../my-dbt-project'; + const vars = { + PROJECT_NAME: projectName, + YEAR: String(new Date().getFullYear()), + DBT_PROJECT_DIR: dbtRel, + DBT_DETECTED: dbtSibling ? 'true' : 'false', + }; + for (const f of walk(target)) substitute(f, vars); + + if (dbtSibling) { + console.log(c.dim(` detected sibling dbt project at ${dbtSibling}`)); + console.log(c.dim(` wired into dql.config.json — run 'dql sync dbt' to import\n`)); + } + + // Best-effort: try to run `git init` so users get a clean first commit. + const gitResult = spawnSync('git', ['init', '-q'], { cwd: target }); + if (gitResult.status === 0) console.log(c.dim(' initialized git repo')); + + const pm = detectPackageManager(); + const installCmd = pm === 'npm' ? 'npm install' : `${pm} install`; + const runCmd = pm === 'npm' ? 'npm run notebook' : `${pm} notebook`; + const dbtTip = dbtSibling + ? `\n\n${c.dim('Tip:')} run ${c.bold('dbt parse')} inside ${c.dim(dbtSibling)}\n first, then ${c.bold(pm === 'npm' ? 'npm run sync' : `${pm} sync`)} to import the dbt DAG.` + : ''; + + console.log(` +${c.green('✓ Ready.')} Next steps: + + ${c.bold(`cd ${args.dir}`)} + ${c.bold(installCmd)} + ${c.bold(runCmd)}${dbtTip} + +Your notebook will open at ${c.cyan('http://localhost:5173')}. + +Docs: ${c.cyan('https://docs.duckcode.ai')} +Issues: ${c.cyan('https://github.com/duckcode-ai/dql/issues')} +`); +} + +main().catch((e) => { + console.error(c.red(`\n✗ ${e.message}`)); + process.exit(1); +}); diff --git a/packages/create-dql-app/package.json b/packages/create-dql-app/package.json new file mode 100644 index 00000000..23f0aa10 --- /dev/null +++ b/packages/create-dql-app/package.json @@ -0,0 +1,40 @@ +{ + "name": "create-dql-app", + "version": "1.0.1", + "description": "Scaffold a new DQL project. Run with: npx create-dql-app ", + "license": "MIT", + "author": "DuckCode AI Labs", + "homepage": "https://docs.duckcode.ai", + "repository": { + "type": "git", + "url": "https://github.com/duckcode-ai/dql.git", + "directory": "packages/create-dql-app" + }, + "keywords": [ + "dql", + "dbt", + "analytics", + "notebook", + "semantic-layer", + "lineage" + ], + "bin": { + "create-dql-app": "./bin/create-dql-app.mjs" + }, + "files": [ + "bin", + "templates", + "README.md" + ], + "type": "module", + "scripts": { + "test": "node test/smoke.mjs", + "build": "echo 'no build step — pure JS'" + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/create-dql-app/templates/empty/.gitignore b/packages/create-dql-app/templates/empty/.gitignore new file mode 100644 index 00000000..aa4666c2 --- /dev/null +++ b/packages/create-dql-app/templates/empty/.gitignore @@ -0,0 +1,17 @@ +# DQL +.dql/ +dql-manifest.json +*.run.json + +# Warehouses +*.duckdb +*.duckdb.wal + +# Build output +build/ + +# Node +node_modules/ + +# OS +.DS_Store diff --git a/packages/create-dql-app/templates/empty/README.md b/packages/create-dql-app/templates/empty/README.md new file mode 100644 index 00000000..07c446fd --- /dev/null +++ b/packages/create-dql-app/templates/empty/README.md @@ -0,0 +1,18 @@ +# {{PROJECT_NAME}} + +An empty DQL project, scaffolded by `create-dql-app --template empty`. + +## Connect your warehouse + +Edit `cdql.yaml` — DQL ships 15 drivers out of the box: +[docs.duckcode.ai/reference/connectors](https://docs.duckcode.ai/reference/connectors/). + +```bash +npx @duckcodeailabs/dql-cli test-connection +``` + +## Start the notebook + +```bash +npx @duckcodeailabs/dql-cli notebook +``` diff --git a/packages/create-dql-app/templates/empty/dql.config.json b/packages/create-dql-app/templates/empty/dql.config.json new file mode 100644 index 00000000..92fa5ce1 --- /dev/null +++ b/packages/create-dql-app/templates/empty/dql.config.json @@ -0,0 +1,13 @@ +{ + "project": "{{PROJECT_NAME}}", + "connections": { + "default": { + "driver": "duckdb", + "filepath": ":memory:" + } + }, + "semanticLayer": { + "provider": "dql", + "path": "semantic-layer" + } +} diff --git a/packages/create-dql-app/templates/empty/notebooks/welcome.dqlnb b/packages/create-dql-app/templates/empty/notebooks/welcome.dqlnb new file mode 100644 index 00000000..fd348328 --- /dev/null +++ b/packages/create-dql-app/templates/empty/notebooks/welcome.dqlnb @@ -0,0 +1,20 @@ +{ + "version": 1, + "metadata": { + "title": "{{PROJECT_NAME}} — Welcome", + "description": "Replace this cell with your first query." + }, + "cells": [ + { + "id": "intro", + "type": "markdown", + "source": "# {{PROJECT_NAME}}\n\nWelcome to DQL. Connect a warehouse in `dql.config.json`, then edit the cell below." + }, + { + "id": "first_query", + "type": "sql", + "title": "First query", + "source": "select 1 as hello" + } + ] +} diff --git a/packages/create-dql-app/templates/empty/package.json b/packages/create-dql-app/templates/empty/package.json new file mode 100644 index 00000000..d41062b7 --- /dev/null +++ b/packages/create-dql-app/templates/empty/package.json @@ -0,0 +1,13 @@ +{ + "name": "{{PROJECT_NAME}}", + "version": "0.1.0", + "private": true, + "scripts": { + "notebook": "dql notebook", + "compile": "dql compile", + "doctor": "dql doctor" + }, + "devDependencies": { + "@duckcodeailabs/dql-cli": "^1.0.0" + } +} diff --git a/packages/create-dql-app/templates/jaffle-shop/.gitignore b/packages/create-dql-app/templates/jaffle-shop/.gitignore new file mode 100644 index 00000000..ef68afd4 --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/.gitignore @@ -0,0 +1,20 @@ +# DQL +.dql/ +dql-manifest.json +*.run.json + +# Warehouses +*.duckdb +*.duckdb.wal + +# Build output +build/ +dist/ +out/ + +# Node +node_modules/ + +# OS +.DS_Store +Thumbs.db diff --git a/packages/create-dql-app/templates/jaffle-shop/README.md b/packages/create-dql-app/templates/jaffle-shop/README.md new file mode 100644 index 00000000..7b716dff --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/README.md @@ -0,0 +1,48 @@ +# {{PROJECT_NAME}} + +A DQL analytics project, scaffolded by `create-dql-app`. + +## Run + +```bash +npm install +npm run notebook +``` + +Opens the notebook at . (`pnpm install && pnpm notebook` +works too.) + +## Layout + +``` +dql.config.json project config (connections, dbt, semantic layer) +package.json npm scripts — `notebook`, `compile`, `sync`, `doctor` +notebooks/ .dqlnb notebooks (JSON format) +blocks/ certified reusable .dql blocks +semantic-layer/ metrics + dimensions authored locally +dashboards/ compiled static HTML dashboards (git-ignored) +``` + +## Using with dbt + +If your dbt project is a sibling (e.g. `../dbt`), it was auto-wired into +`dql.config.json` under the `dbt:` key. Then: + +```bash +(cd ../dbt && dbt parse) # produces target/manifest.json +npm run sync # refreshes the DQL cache from dbt +npm run compile # builds dql-manifest.json with lineage +``` + +## Next steps + +1. **Run the welcome notebook** — `notebooks/welcome.dqlnb` +2. **Connect your warehouse** — [docs.duckcode.ai/guides/connect-warehouse](https://docs.duckcode.ai/guides/connect-warehouse/) +3. **Import your dbt project** — [docs.duckcode.ai/guides/import-dbt](https://docs.duckcode.ai/guides/import-dbt/) +4. **Author a certified block** — [docs.duckcode.ai/guides/authoring-blocks](https://docs.duckcode.ai/guides/authoring-blocks/) + +## Learn + +- [Quickstart](https://docs.duckcode.ai/get-started/quickstart/) +- [Concepts](https://docs.duckcode.ai/get-started/concepts/) +- [CLI reference](https://docs.duckcode.ai/reference/cli/) diff --git a/packages/create-dql-app/templates/jaffle-shop/blocks/revenue_by_segment.dql b/packages/create-dql-app/templates/jaffle-shop/blocks/revenue_by_segment.dql new file mode 100644 index 00000000..60b16083 --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/blocks/revenue_by_segment.dql @@ -0,0 +1,29 @@ +// dql-format: 1 + +block "Revenue by segment" { + domain = "finance" + type = "custom" + description = "Gross revenue grouped by customer segment." + tags = ["revenue", "sample"] + + query = """ + SELECT + c.customer_segment AS segment, + SUM(o.amount) AS revenue, + COUNT(DISTINCT o.customer_id) AS customers + FROM orders o + JOIN customers c ON c.customer_id = o.customer_id + GROUP BY 1 + ORDER BY revenue DESC + """ + + visualization { + chart = "bar" + x = segment + y = revenue + } + + tests { + assert row_count > 0 + } +} diff --git a/packages/create-dql-app/templates/jaffle-shop/dashboards/overview.dql b/packages/create-dql-app/templates/jaffle-shop/dashboards/overview.dql new file mode 100644 index 00000000..520beea4 --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/dashboards/overview.dql @@ -0,0 +1,29 @@ +// dql-format: 1 + +dashboard "{{PROJECT_NAME}} — Overview" { + chart.kpi( + SELECT SUM(amount) as revenue FROM orders, + metrics = ["revenue"] + ) + + chart.bar( + SELECT customer_id, SUM(amount) as spend + FROM orders + GROUP BY customer_id + ORDER BY spend DESC + LIMIT 10, + x = customer_id, + y = spend, + title = "Top 10 customers by spend" + ) + + chart.line( + SELECT order_date, SUM(amount) as revenue + FROM orders + GROUP BY order_date + ORDER BY order_date, + x = order_date, + y = revenue, + title = "Daily revenue" + ) +} diff --git a/packages/create-dql-app/templates/jaffle-shop/dql.config.json b/packages/create-dql-app/templates/jaffle-shop/dql.config.json new file mode 100644 index 00000000..0d69f557 --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/dql.config.json @@ -0,0 +1,20 @@ +{ + "project": "{{PROJECT_NAME}}", + "connections": { + "default": { + "driver": "duckdb", + "filepath": ":memory:" + } + }, + "semanticLayer": { + "provider": "dql", + "path": "semantic-layer" + }, + "dbt": { + "projectDir": "{{DBT_PROJECT_DIR}}", + "manifestPath": "target/manifest.json" + }, + "governance": { + "required_fields": ["domain", "owner", "description"] + } +} diff --git a/packages/create-dql-app/templates/jaffle-shop/notebooks/welcome.dqlnb b/packages/create-dql-app/templates/jaffle-shop/notebooks/welcome.dqlnb new file mode 100644 index 00000000..40241dbb --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/notebooks/welcome.dqlnb @@ -0,0 +1,32 @@ +{ + "version": 1, + "metadata": { + "title": "{{PROJECT_NAME}} — Welcome", + "description": "A tour of DQL in four cells. Run them top-to-bottom with Cmd+Enter." + }, + "cells": [ + { + "id": "intro", + "type": "markdown", + "source": "# Welcome to {{PROJECT_NAME}}\n\nThis notebook runs against a DuckDB-backed Jaffle Shop dataset — the same demo data dbt ships.\n\n- Run each cell with `Cmd/Ctrl + Enter`.\n- Charts render inline from SQL results.\n- The certified block in cell 4 lives in `blocks/revenue_by_segment.dql` and can be reused across notebooks and dashboards." + }, + { + "id": "orders_overview", + "type": "sql", + "title": "Orders overview", + "source": "select\n count(*) as total_orders,\n count(distinct customer_id) as unique_customers,\n sum(amount) as lifetime_revenue\nfrom orders" + }, + { + "id": "daily_revenue", + "type": "sql", + "title": "Daily revenue", + "source": "select\n order_date,\n sum(amount) as revenue\nfrom orders\ngroup by 1\norder by 1" + }, + { + "id": "revenue_by_segment", + "type": "dql", + "title": "Revenue by segment (certified block)", + "source": "@block(\"Revenue by segment\")" + } + ] +} diff --git a/packages/create-dql-app/templates/jaffle-shop/package.json b/packages/create-dql-app/templates/jaffle-shop/package.json new file mode 100644 index 00000000..a7a08a3a --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/package.json @@ -0,0 +1,16 @@ +{ + "name": "{{PROJECT_NAME}}", + "version": "0.1.0", + "private": true, + "description": "DQL analytics project.", + "scripts": { + "notebook": "dql notebook", + "compile": "dql compile", + "sync": "dql sync dbt", + "doctor": "dql doctor", + "test": "dql test" + }, + "devDependencies": { + "@duckcodeailabs/dql-cli": "^1.0.0" + } +} diff --git a/packages/create-dql-app/templates/jaffle-shop/semantic-layer/dimensions/customer.yaml b/packages/create-dql-app/templates/jaffle-shop/semantic-layer/dimensions/customer.yaml new file mode 100644 index 00000000..089d5026 --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/semantic-layer/dimensions/customer.yaml @@ -0,0 +1,14 @@ +dimensions: + - name: segment + label: Customer segment + type: string + sql: segment + table: customers + domain: finance + + - name: region + label: Region + type: string + sql: region + table: customers + domain: finance diff --git a/packages/create-dql-app/templates/jaffle-shop/semantic-layer/metrics/revenue.yaml b/packages/create-dql-app/templates/jaffle-shop/semantic-layer/metrics/revenue.yaml new file mode 100644 index 00000000..74674a08 --- /dev/null +++ b/packages/create-dql-app/templates/jaffle-shop/semantic-layer/metrics/revenue.yaml @@ -0,0 +1,9 @@ +metrics: + - name: revenue + label: Revenue + description: Gross revenue across all orders + type: sum + sql: order_total + table: orders + domain: finance + tags: [revenue, core] diff --git a/packages/create-dql-app/test/smoke.mjs b/packages/create-dql-app/test/smoke.mjs new file mode 100644 index 00000000..3badbdaa --- /dev/null +++ b/packages/create-dql-app/test/smoke.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node +// Smoke test: scaffold both templates into tmp dirs, assert the expected +// files exist and placeholder substitution ran. Keeps us honest about the +// 5-minute demo gate — if this test fails, create-dql-app is broken. +import { spawnSync } from 'node:child_process'; +import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const BIN = resolve(__dirname, '..', 'bin', 'create-dql-app.mjs'); + +function scaffold(template, name) { + const base = mkdtempSync(join(tmpdir(), 'create-dql-app-test-')); + const target = join(base, name); + const result = spawnSync('node', [BIN, target, '--template', template], { + encoding: 'utf-8', + env: { ...process.env, CI: '1' }, + }); + return { base, target, result }; +} + +function assert(cond, msg) { + if (!cond) { + console.error(`✗ ${msg}`); + process.exit(1); + } + console.log(` ✓ ${msg}`); +} + +function runTest(template, name, expected, placeholderFile) { + console.log(`\n▸ template: ${template}`); + const { base, target, result } = scaffold(template, name); + try { + assert(result.status === 0, `scaffold exits 0 (got ${result.status})`); + for (const f of expected) { + assert(existsSync(join(target, f)), `emits ${f}`); + } + const placeholder = readFileSync(join(target, placeholderFile), 'utf-8'); + assert(placeholder.includes(name), `${placeholderFile} got PROJECT_NAME substituted`); + assert(!placeholder.includes('{{PROJECT_NAME}}'), `${placeholderFile} has no unresolved placeholders`); + } finally { + rmSync(base, { recursive: true, force: true }); + } +} + +runTest('jaffle-shop', 'smoke-jaffle', [ + 'dql.config.json', + 'package.json', + 'README.md', + 'notebooks/welcome.dqlnb', + 'blocks/revenue_by_segment.dql', + 'semantic-layer/metrics/revenue.yaml', + 'dashboards/overview.dql', + '.gitignore', +], 'dql.config.json'); + +runTest('empty', 'smoke-empty', [ + 'dql.config.json', + 'package.json', + 'README.md', + 'notebooks/welcome.dqlnb', + '.gitignore', +], 'dql.config.json'); + +console.log('\n✓ all smoke tests passed'); diff --git a/packages/dql-charts/package.json b/packages/dql-charts/package.json index e5239239..b47f3a5a 100644 --- a/packages/dql-charts/package.json +++ b/packages/dql-charts/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-charts", - "version": "0.10.1", + "version": "1.0.1", "description": "DQL chart library: visx-powered React SVG components for reusable analytics blocks", "license": "Apache-2.0", "type": "module", diff --git a/packages/dql-compiler/package.json b/packages/dql-compiler/package.json index 5ff7c26a..c73e875d 100644 --- a/packages/dql-compiler/package.json +++ b/packages/dql-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-compiler", - "version": "0.10.1", + "version": "1.0.1", "description": "DQL compiler: IR lowering, Vega-Lite code generation, HTML/CSS emitting", "license": "Apache-2.0", "type": "module", diff --git a/packages/dql-connectors/package.json b/packages/dql-connectors/package.json index 06ac6c36..8b93762c 100644 --- a/packages/dql-connectors/package.json +++ b/packages/dql-connectors/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-connectors", - "version": "0.10.1", + "version": "1.0.1", "description": "DQL database connectors for local files, SQL warehouses, and lakehouse engines", "license": "Apache-2.0", "type": "module", diff --git a/packages/dql-core/package.json b/packages/dql-core/package.json index 3b2f195f..08e38f1a 100644 --- a/packages/dql-core/package.json +++ b/packages/dql-core/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-core", - "version": "0.10.1", + "version": "1.0.1", "description": "DQL language core: lexer, parser, AST, semantic analysis", "license": "Apache-2.0", "type": "module", diff --git a/packages/dql-core/src/format/diff.test.ts b/packages/dql-core/src/format/diff.test.ts new file mode 100644 index 00000000..9d75d276 --- /dev/null +++ b/packages/dql-core/src/format/diff.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest'; +import { diffDQL, renderDiffText } from './diff.js'; + +const BEFORE = `block "rev" { + domain = "sales" + type = "custom" + params { region = "NA" } + query = """ + SELECT SUM(revenue) as rev FROM orders + """ + visualization { title = "Revenue" } + tests { assert rev > 0 } +}`; + +describe('diffDQL', () => { + it('reports identical when sources match', () => { + const r = diffDQL(BEFORE, BEFORE); + expect(r.identical).toBe(true); + expect(r.changes).toHaveLength(0); + }); + + it('detects added block', () => { + const after = `${BEFORE}\n\nblock "cost" { domain = "ops" type = "custom" query = """SELECT 1""" }`; + const r = diffDQL(BEFORE, after); + expect(r.changes).toContainEqual({ kind: 'block-added', name: 'cost' }); + }); + + it('detects removed block', () => { + const r = diffDQL(BEFORE, ''); + expect(r.changes).toContainEqual({ kind: 'block-removed', name: 'rev' }); + }); + + it('detects field-level changes inside a block', () => { + const after = BEFORE + .replace('"NA"', '"EU"') + .replace('SUM(revenue)', 'SUM(revenue) + SUM(tax)'); + const r = diffDQL(BEFORE, after); + expect(r.identical).toBe(false); + const changed = r.changes.find((c) => c.kind === 'block-changed'); + expect(changed).toBeDefined(); + if (changed && changed.kind === 'block-changed') { + const paths = changed.fields.map((f) => f.path); + expect(paths).toContain('params.region'); + expect(paths).toContain('query'); + } + }); + + it('renders text with +/-/~ markers', () => { + const after = BEFORE.replace('"NA"', '"EU"'); + const text = renderDiffText(diffDQL(BEFORE, after)); + expect(text).toContain('~ block "rev"'); + expect(text).toContain('params.region'); + }); + + it('ignores SQL whitespace differences', () => { + const after = BEFORE.replace(/SELECT SUM\(revenue\) as rev FROM orders/, 'SELECT SUM(revenue) as rev FROM orders'); + const r = diffDQL(BEFORE, after); + expect(r.identical).toBe(true); + }); +}); diff --git a/packages/dql-core/src/format/diff.ts b/packages/dql-core/src/format/diff.ts new file mode 100644 index 00000000..7d285a80 --- /dev/null +++ b/packages/dql-core/src/format/diff.ts @@ -0,0 +1,290 @@ +// v0.11 semantic diff for `.dql` files. +// +// `diffProgram` compares two parsed programs by top-level identity +// (block name, dashboard title, workbook title) and returns a structured +// report of added / removed / changed entities. For changed entities we +// descend into the properties that actually matter for review: SQL query, +// parameters, visualization, tests, tags, metadata. +// +// The output is intentionally lossy relative to a textual diff — it's +// meant to answer "what changed *semantically*?", not "which bytes moved". + +import { parse } from '../parser/parser.js'; +import { NodeKind } from '../ast/nodes.js'; +import type { + BlockDeclNode, + BlockParamEntry, + BlockTestNode, + DashboardNode, + ExpressionNode, + NamedArgNode, + ProgramNode, + StatementNode, + WorkbookNode, +} from '../ast/nodes.js'; +import { formatProgram } from '../formatter/formatter.js'; + +export type DiffChange = + | { kind: 'block-added'; name: string } + | { kind: 'block-removed'; name: string } + | { kind: 'block-changed'; name: string; fields: FieldChange[] } + | { kind: 'dashboard-added'; title: string } + | { kind: 'dashboard-removed'; title: string } + | { kind: 'dashboard-changed'; title: string; fields: FieldChange[] } + | { kind: 'workbook-added'; title: string } + | { kind: 'workbook-removed'; title: string } + | { kind: 'workbook-changed'; title: string; fields: FieldChange[] }; + +export interface FieldChange { + path: string; + before: string | null; + after: string | null; +} + +export interface DiffReport { + changes: DiffChange[]; + /** True when `before` and `after` are semantically identical at the AST level. */ + identical: boolean; +} + +export function diffDQL(beforeSource: string, afterSource: string): DiffReport { + return diffProgram(parse(beforeSource), parse(afterSource)); +} + +export function diffProgram(before: ProgramNode, after: ProgramNode): DiffReport { + const changes: DiffChange[] = []; + + const beforeBlocks = indexBy(before.statements.filter(isBlock), (b) => b.name); + const afterBlocks = indexBy(after.statements.filter(isBlock), (b) => b.name); + diffKeyed(beforeBlocks, afterBlocks, { + onAdded: (name) => changes.push({ kind: 'block-added', name }), + onRemoved: (name) => changes.push({ kind: 'block-removed', name }), + onChanged: (name, a, b) => { + const fields = diffBlock(a, b); + if (fields.length > 0) changes.push({ kind: 'block-changed', name, fields }); + }, + }); + + const beforeDashboards = indexBy(before.statements.filter(isDashboard), (d) => d.title); + const afterDashboards = indexBy(after.statements.filter(isDashboard), (d) => d.title); + diffKeyed(beforeDashboards, afterDashboards, { + onAdded: (title) => changes.push({ kind: 'dashboard-added', title }), + onRemoved: (title) => changes.push({ kind: 'dashboard-removed', title }), + onChanged: (title, a, b) => { + const fields = diffDashboard(a, b); + if (fields.length > 0) changes.push({ kind: 'dashboard-changed', title, fields }); + }, + }); + + const beforeWorkbooks = indexBy(before.statements.filter(isWorkbook), (w) => w.title); + const afterWorkbooks = indexBy(after.statements.filter(isWorkbook), (w) => w.title); + diffKeyed(beforeWorkbooks, afterWorkbooks, { + onAdded: (title) => changes.push({ kind: 'workbook-added', title }), + onRemoved: (title) => changes.push({ kind: 'workbook-removed', title }), + onChanged: (title, a, b) => { + const fields = diffWorkbook(a, b); + if (fields.length > 0) changes.push({ kind: 'workbook-changed', title, fields }); + }, + }); + + return { changes, identical: changes.length === 0 }; +} + +// ---- Block ---- + +function diffBlock(a: BlockDeclNode, b: BlockDeclNode): FieldChange[] { + const out: FieldChange[] = []; + scalar(out, 'domain', a.domain, b.domain); + scalar(out, 'type', a.blockType, b.blockType); + scalar(out, 'description', a.description, b.description); + scalar(out, 'owner', a.owner, b.owner); + scalar(out, 'tags', a.tags?.join(', '), b.tags?.join(', ')); + scalar(out, 'metricRef', a.metricRef, b.metricRef); + scalar(out, 'metricsRef', a.metricsRef?.join(', '), b.metricsRef?.join(', ')); + scalar(out, 'query', normalizeSQL(a.query?.rawSQL), normalizeSQL(b.query?.rawSQL)); + + diffParams(out, a.params?.params ?? [], b.params?.params ?? []); + diffNamedArgs(out, 'visualization', a.visualization?.properties ?? [], b.visualization?.properties ?? []); + diffTests(out, a.tests ?? [], b.tests ?? []); + return out; +} + +function diffParams(out: FieldChange[], a: BlockParamEntry[], b: BlockParamEntry[]): void { + const am = new Map(a.map((p) => [p.name, formatExpr(p.initializer)])); + const bm = new Map(b.map((p) => [p.name, formatExpr(p.initializer)])); + for (const name of union(am, bm)) { + scalar(out, `params.${name}`, am.get(name), bm.get(name)); + } +} + +function diffNamedArgs(out: FieldChange[], prefix: string, a: NamedArgNode[], b: NamedArgNode[]): void { + const am = new Map(a.map((p) => [p.name, formatExpr(p.value)])); + const bm = new Map(b.map((p) => [p.name, formatExpr(p.value)])); + for (const name of union(am, bm)) { + scalar(out, `${prefix}.${name}`, am.get(name), bm.get(name)); + } +} + +function diffTests(out: FieldChange[], a: BlockTestNode[], b: BlockTestNode[]): void { + const key = (t: BlockTestNode) => `${t.field} ${t.operator}`; + const am = new Map(a.map((t) => [key(t), formatExpr(t.expected)])); + const bm = new Map(b.map((t) => [key(t), formatExpr(t.expected)])); + for (const k of union(am, bm)) { + scalar(out, `tests[${k}]`, am.get(k), bm.get(k)); + } +} + +// ---- Dashboard / Workbook ---- +// For dashboards/workbooks we reduce the body to its formatted text and +// diff that as a single field; an AST-level diff of charts/filters/layouts +// is possible but pays off less than block-level diffing. + +function diffDashboard(a: DashboardNode, b: DashboardNode): FieldChange[] { + const out: FieldChange[] = []; + scalar(out, 'body', formatBody(a), formatBody(b)); + return out; +} + +function diffWorkbook(a: WorkbookNode, b: WorkbookNode): FieldChange[] { + const out: FieldChange[] = []; + scalar(out, 'body', formatBody(a), formatBody(b)); + return out; +} + +function formatBody(node: DashboardNode | WorkbookNode): string { + const program: ProgramNode = { + kind: NodeKind.Program, + span: node.span, + statements: [node], + }; + return formatProgram(program); +} + +// ---- Helpers ---- + +function scalar(out: FieldChange[], path: string, before: string | undefined, after: string | undefined): void { + const b = before ?? null; + const a = after ?? null; + if (b !== a) out.push({ path, before: b, after: a }); +} + +function normalizeSQL(sql: string | undefined): string | undefined { + if (sql == null) return undefined; + return sql.trim().replace(/\s+/g, ' '); +} + +function formatExpr(node: ExpressionNode): string { + switch (node.kind) { + case NodeKind.StringLiteral: + return JSON.stringify(node.value); + case NodeKind.NumberLiteral: + return String(node.value); + case NodeKind.BooleanLiteral: + return node.value ? 'true' : 'false'; + case NodeKind.Identifier: + return node.name; + case NodeKind.ArrayLiteral: + return `[${node.elements.map(formatExpr).join(', ')}]`; + case NodeKind.BinaryExpr: + return `${formatExpr(node.left)} ${node.operator} ${formatExpr(node.right)}`; + case NodeKind.IntervalExpr: + return `INTERVAL ${JSON.stringify(node.value)}`; + case NodeKind.FunctionCall: + return `${node.callee}(${node.arguments.map(formatExpr).join(', ')})`; + case NodeKind.TemplateString: + return node.parts + .map((p) => (typeof p === 'string' ? p : `{${formatExpr(p)}}`)) + .join(''); + default: + return ''; + } +} + +function isBlock(s: StatementNode): s is BlockDeclNode { + return s.kind === NodeKind.BlockDecl; +} + +function isDashboard(s: StatementNode): s is DashboardNode { + return s.kind === NodeKind.Dashboard; +} + +function isWorkbook(s: StatementNode): s is WorkbookNode { + return s.kind === NodeKind.Workbook; +} + +function indexBy(items: T[], key: (item: T) => K): Map { + const out = new Map(); + for (const item of items) out.set(key(item), item); + return out; +} + +function diffKeyed( + before: Map, + after: Map, + handlers: { + onAdded: (key: string) => void; + onRemoved: (key: string) => void; + onChanged: (key: string, before: T, after: T) => void; + }, +): void { + for (const key of before.keys()) { + if (!after.has(key)) handlers.onRemoved(key); + } + for (const [key, a] of after.entries()) { + const b = before.get(key); + if (b === undefined) handlers.onAdded(key); + else handlers.onChanged(key, b, a); + } +} + +function union(a: Map, b: Map): K[] { + return Array.from(new Set([...a.keys(), ...b.keys()])); +} + +// ---- Text rendering ---- + +export function renderDiffText(report: DiffReport): string { + if (report.identical) return 'No changes.'; + const lines: string[] = []; + for (const c of report.changes) { + switch (c.kind) { + case 'block-added': + lines.push(`+ block "${c.name}"`); + break; + case 'block-removed': + lines.push(`- block "${c.name}"`); + break; + case 'block-changed': + lines.push(`~ block "${c.name}"`); + for (const f of c.fields) { + lines.push(` ${f.path}: ${fmtVal(f.before)} → ${fmtVal(f.after)}`); + } + break; + case 'dashboard-added': + lines.push(`+ dashboard "${c.title}"`); + break; + case 'dashboard-removed': + lines.push(`- dashboard "${c.title}"`); + break; + case 'dashboard-changed': + lines.push(`~ dashboard "${c.title}" (body changed)`); + break; + case 'workbook-added': + lines.push(`+ workbook "${c.title}"`); + break; + case 'workbook-removed': + lines.push(`- workbook "${c.title}"`); + break; + case 'workbook-changed': + lines.push(`~ workbook "${c.title}" (body changed)`); + break; + } + } + return lines.join('\n'); +} + +function fmtVal(v: string | null): string { + if (v === null) return '∅'; + if (v.length > 80) return `${v.slice(0, 77)}…`; + return v; +} diff --git a/packages/dql-core/src/format/format.test.ts b/packages/dql-core/src/format/format.test.ts new file mode 100644 index 00000000..4dc29481 --- /dev/null +++ b/packages/dql-core/src/format/format.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; +import { + FORMAT_VERSION, + canonicalize, + hasCanonicalHeader, + isCanonical, + readFormatVersion, +} from './index.js'; + +const SAMPLE = `dashboard "Daily" { + chart.line( + SELECT 1, + x = a, + y = b + ) +}`; + +describe('canonical format', () => { + it('prepends the version header on first canonicalize', () => { + const out = canonicalize(SAMPLE); + expect(out.startsWith(`// dql-format: ${FORMAT_VERSION}`)).toBe(true); + expect(readFormatVersion(out)).toBe(FORMAT_VERSION); + }); + + it('is idempotent', () => { + const once = canonicalize(SAMPLE); + const twice = canonicalize(once); + expect(twice).toBe(once); + expect(isCanonical(once)).toBe(true); + }); + + it('preserves existing header without duplicating it', () => { + const withHeader = `// dql-format: 1\n\n${SAMPLE}`; + const out = canonicalize(withHeader); + const headerCount = (out.match(/dql-format:/g) ?? []).length; + expect(headerCount).toBe(1); + }); + + it('reports missing header as null', () => { + expect(readFormatVersion(SAMPLE)).toBeNull(); + expect(hasCanonicalHeader(SAMPLE)).toBe(false); + }); + + it('tolerates future versions in readFormatVersion', () => { + expect(readFormatVersion('// dql-format: 99\n\ndashboard "x" {}')).toBe(99); + }); +}); diff --git a/packages/dql-core/src/format/index.ts b/packages/dql-core/src/format/index.ts new file mode 100644 index 00000000..f3061a19 --- /dev/null +++ b/packages/dql-core/src/format/index.ts @@ -0,0 +1,84 @@ +// v0.11 canonical `.dql` serializer. +// +// Adds a stable format-version header and routes all writes through the +// formatter so that files produced by the CLI, notebook, and block studio +// byte-identical — the precondition for clean git diffs, `dql diff`, and +// a safe file-format migration path. +// +// Format: +// // dql-format: +// +// +// Readers that don't recognise a header treat it as an ordinary comment. +// Writers always emit the current version. + +import { formatDQL, type FormatOptions } from '../formatter/index.js'; + +export * from './diff.js'; + +export const FORMAT_VERSION = 1; +export const FORMAT_HEADER_PREFIX = '// dql-format:'; + +const HEADER_RE = /^\s*\/\/\s*dql-format:\s*(\d+)\s*$/; + +export interface CanonicalizeOptions extends FormatOptions { + /** Override the emitted format version. Defaults to FORMAT_VERSION. */ + version?: number; +} + +/** + * Return the declared format version of a `.dql` source, or `null` if the + * header is absent. Only the first non-empty line is inspected. + */ +export function readFormatVersion(source: string): number | null { + for (const line of source.split('\n')) { + if (line.trim() === '') continue; + const match = HEADER_RE.exec(line); + return match ? Number(match[1]) : null; + } + return null; +} + +export function hasCanonicalHeader(source: string): boolean { + return readFormatVersion(source) !== null; +} + +/** + * Strip any leading `// dql-format:` header (plus a single trailing blank + * line) so the remainder can be re-parsed by the existing formatter, which + * does not yet preserve comments. + */ +function stripHeader(source: string): string { + const lines = source.split('\n'); + let i = 0; + while (i < lines.length && lines[i].trim() === '') i++; + if (i < lines.length && HEADER_RE.test(lines[i])) { + i++; + if (i < lines.length && lines[i].trim() === '') i++; + return lines.slice(i).join('\n'); + } + return source; +} + +/** + * Produce the canonical on-disk representation of a `.dql` source: + * 1. Drop any existing format header + * 2. Reformat via `formatDQL` (deterministic key order, spacing) + * 3. Prepend the current format header + * + * Idempotent: `canonicalize(canonicalize(x)) === canonicalize(x)`. + */ +export function canonicalize(source: string, options: CanonicalizeOptions = {}): string { + const version = options.version ?? FORMAT_VERSION; + const body = formatDQL(stripHeader(source), options); + const header = `${FORMAT_HEADER_PREFIX} ${version}`; + return `${header}\n\n${body.startsWith('\n') ? body.slice(1) : body}`; +} + +/** + * True when `canonicalize(source) === source` — i.e. the file is already + * byte-identical to its canonical form and needs no rewrite. + */ +export function isCanonical(source: string): boolean { + return canonicalize(source) === source; +} diff --git a/packages/dql-core/src/index.ts b/packages/dql-core/src/index.ts index dc780c97..04704afd 100644 --- a/packages/dql-core/src/index.ts +++ b/packages/dql-core/src/index.ts @@ -7,5 +7,6 @@ export * from './parser/index.js'; export * from './semantic/index.js'; export * from './errors/index.js'; export * from './formatter/index.js'; +export * from './format/index.js'; export * from './lineage/index.js'; export * from './manifest/index.js'; diff --git a/packages/dql-core/src/manifest/builder.ts b/packages/dql-core/src/manifest/builder.ts index 0d32a0ad..36590c6b 100644 --- a/packages/dql-core/src/manifest/builder.ts +++ b/packages/dql-core/src/manifest/builder.ts @@ -30,6 +30,7 @@ import type { ManifestSource, ManifestLineage, ManifestDbtImport, + ManifestDiagnostic, } from './types.js'; // ---- Public API ---- @@ -126,17 +127,19 @@ export function collectInputFiles(options: ManifestBuildOptions): string[] { export function buildManifest(options: ManifestBuildOptions): DQLManifest { const { projectRoot, dqlVersion = '0.6.0' } = options; + const diagnostics: ManifestDiagnostic[] = []; + // Load project config const config = loadProjectConfig(projectRoot); const projectName = config.project ?? 'dql-project'; // Scan blocks const blockDirs = ['blocks', 'dashboards', 'workbooks', ...(options.extraBlockDirs ?? [])]; - const blocks = scanBlocks(projectRoot, blockDirs); + const blocks = scanBlocks(projectRoot, blockDirs, diagnostics); // Scan notebooks const notebookDirs = ['notebooks', 'blocks', 'dashboards', 'workbooks', ...(options.extraNotebookDirs ?? [])]; - const notebooks = scanNotebooks(projectRoot, notebookDirs); + const notebooks = scanNotebooks(projectRoot, notebookDirs, diagnostics); // Extract blocks declared inside notebook DQL cells const notebookBlocks = extractNotebookBlocks(notebooks, projectRoot); @@ -193,6 +196,7 @@ export function buildManifest(options: ManifestBuildOptions): DQLManifest { sources, lineage, dbtImport, + diagnostics, }; } @@ -204,9 +208,23 @@ interface ProjectConfig { dataDir?: string; /** Selective dbt import filters; merged into buildManifest options. */ dbtImport?: DbtImportFilters; + /** + * dbt integration — so commands can default to the right manifest path + * without the user re-typing `--dbt-manifest` on every invocation. + */ + dbt?: { + /** Path to the dbt project root (absolute, or relative to projectRoot) */ + projectDir?: string; + /** Path to the dbt manifest.json, relative to `projectDir`. Default: target/manifest.json */ + manifestPath?: string; + }; } -function loadProjectConfig(projectRoot: string): ProjectConfig { +/** + * Load `dql.config.json`. Exposed so CLI commands can read the same config + * the manifest builder uses — no need for each command to re-implement parse. + */ +export function loadProjectConfig(projectRoot: string): ProjectConfig { const configPath = join(projectRoot, 'dql.config.json'); if (!existsSync(configPath)) return {}; try { @@ -216,6 +234,37 @@ function loadProjectConfig(projectRoot: string): ProjectConfig { } } +/** + * Resolve the absolute path to the configured dbt manifest.json (if any). + * Honors explicit CLI flag first, then `dbt.projectDir + dbt.manifestPath` from + * config, then the conventional `target/manifest.json` inside the project root. + * Returns `null` if none of those exist. + */ +export function resolveDbtManifestPath( + projectRoot: string, + explicit?: string, +): string | null { + if (explicit) { + return isAbsPath(explicit) ? explicit : join(projectRoot, explicit); + } + const config = loadProjectConfig(projectRoot); + if (config.dbt?.projectDir) { + const dbtRoot = isAbsPath(config.dbt.projectDir) + ? config.dbt.projectDir + : join(projectRoot, config.dbt.projectDir); + const rel = config.dbt.manifestPath ?? 'target/manifest.json'; + const abs = isAbsPath(rel) ? rel : join(dbtRoot, rel); + if (existsSync(abs)) return abs; + } + const fallback = join(projectRoot, 'target', 'manifest.json'); + if (existsSync(fallback)) return fallback; + return null; +} + +function isAbsPath(p: string): boolean { + return p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p); +} + function resolveSemanticPath(projectRoot: string, config: ProjectConfig): string { const customPath = config.semanticLayer?.path; if (customPath) return join(projectRoot, customPath); @@ -244,7 +293,11 @@ function scanFilesRecursive(dir: string, extensions: string[]): string[] { // ---- Block Scanning ---- -function scanBlocks(projectRoot: string, dirs: string[]): Record { +function scanBlocks( + projectRoot: string, + dirs: string[], + diagnostics?: ManifestDiagnostic[], +): Record { const blocks: Record = {}; for (const dir of dirs) { @@ -252,9 +305,9 @@ function scanBlocks(projectRoot: string, dirs: string[]): Record { +function scanNotebooks( + projectRoot: string, + dirs: string[], + diagnostics?: ManifestDiagnostic[], +): Record { const notebooks: Record = {}; const seen = new Set(); @@ -381,8 +446,14 @@ function scanNotebooks(projectRoot: string, dirs: string[]): Record { + // ... run the block ... + }, +); +``` + +## Config + +Resolves from, in order: + +1. Explicit args to `createEmitter({ … })` +2. `OPENLINEAGE_URL`, `OPENLINEAGE_NAMESPACE` env vars +3. Disabled (no-op) default + +Hard opt-out: + +```bash +export DQL_OPENLINEAGE_DISABLED=1 +``` + +## Error policy + +Network failures are swallowed (routed to `onError`) so infra issues never +surface to end users. Wrap-mode re-throws *handler* errors after emitting +`FAIL`. + +## Compatible receivers + +Marquez · DataHub · Atlan · Monte Carlo — anything that speaks the OL spec. +See [docs.duckcode.ai/architecture/openlineage](https://docs.duckcode.ai/architecture/openlineage/). diff --git a/packages/dql-openlineage/package.json b/packages/dql-openlineage/package.json new file mode 100644 index 00000000..93b3f142 --- /dev/null +++ b/packages/dql-openlineage/package.json @@ -0,0 +1,32 @@ +{ + "name": "@duckcodeailabs/dql-openlineage", + "version": "1.0.1", + "description": "OpenLineage event emitter for DQL block and notebook runs.", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "rm -rf dist tsconfig.tsbuildinfo && tsc", + "test": "vitest run" + }, + "dependencies": {}, + "devDependencies": { + "@types/node": "^20.11.0", + "typescript": "^5.4.0", + "vitest": "^1.6.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/dql-openlineage/src/index.test.ts b/packages/dql-openlineage/src/index.test.ts new file mode 100644 index 00000000..9272841c --- /dev/null +++ b/packages/dql-openlineage/src/index.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it, vi } from 'vitest'; +import { createEmitter, resolveConfig } from './index.js'; + +describe('OpenLineage emitter', () => { + it('drops events when disabled', async () => { + const fetchSpy = vi.fn(); + const emitter = createEmitter({ fetch: fetchSpy as unknown as typeof fetch }); + await emitter.emit({ + eventType: 'START', + eventTime: '2026-01-01T00:00:00Z', + job: { namespace: 'test', name: 'x' }, + run: { runId: 'r' }, + }); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it('posts to the configured url when enabled', async () => { + const fetchSpy = vi.fn().mockResolvedValue({ ok: true }); + const emitter = createEmitter({ + enabled: true, + url: 'http://localhost:5000/api/v1/lineage', + namespace: 'test', + fetch: fetchSpy as unknown as typeof fetch, + }); + await emitter.emit({ + eventType: 'START', + eventTime: '2026-01-01T00:00:00Z', + job: { namespace: 'test', name: 'block.x' }, + run: { runId: 'r-1' }, + }); + expect(fetchSpy).toHaveBeenCalledOnce(); + const [url, opts] = fetchSpy.mock.calls[0]; + expect(url).toBe('http://localhost:5000/api/v1/lineage'); + expect(opts.method).toBe('POST'); + const body = JSON.parse(opts.body); + expect(body.producer).toContain('duckcode-ai/dql'); + expect(body.job.name).toBe('block.x'); + }); + + it('wrap emits START + COMPLETE on success', async () => { + const fetchSpy = vi.fn().mockResolvedValue({ ok: true }); + const emitter = createEmitter({ + enabled: true, + url: 'http://x', + fetch: fetchSpy as unknown as typeof fetch, + }); + const result = await emitter.wrap( + { namespace: 'test', name: 'j' }, + 'run-1', + { inputs: [{ namespace: 'test', name: 'in' }] }, + async () => 42, + ); + expect(result).toBe(42); + expect(fetchSpy).toHaveBeenCalledTimes(2); + const types = fetchSpy.mock.calls.map((c) => JSON.parse(c[1].body).eventType); + expect(types).toEqual(['START', 'COMPLETE']); + }); + + it('wrap emits START + FAIL on error and re-throws', async () => { + const fetchSpy = vi.fn().mockResolvedValue({ ok: true }); + const emitter = createEmitter({ + enabled: true, + url: 'http://x', + fetch: fetchSpy as unknown as typeof fetch, + }); + await expect( + emitter.wrap({ namespace: 't', name: 'j' }, 'r', {}, () => { + throw new Error('boom'); + }), + ).rejects.toThrow('boom'); + const types = fetchSpy.mock.calls.map((c) => JSON.parse(c[1].body).eventType); + expect(types).toEqual(['START', 'FAIL']); + }); + + it('swallows fetch failures via onError', async () => { + const errors: unknown[] = []; + const emitter = createEmitter({ + enabled: true, + url: 'http://x', + fetch: vi.fn().mockRejectedValue(new Error('net down')) as unknown as typeof fetch, + onError: (e) => errors.push(e), + }); + // Must not throw. + await emitter.emit({ + eventType: 'START', + eventTime: 'now', + job: { namespace: 't', name: 'j' }, + run: { runId: 'r' }, + }); + expect(errors).toHaveLength(1); + }); + + it('resolveConfig honors DQL_OPENLINEAGE_DISABLED', () => { + const prev = process.env.DQL_OPENLINEAGE_DISABLED; + process.env.DQL_OPENLINEAGE_DISABLED = '1'; + const cfg = resolveConfig({ enabled: true, url: 'http://x' }); + expect(cfg.enabled).toBe(false); + if (prev === undefined) delete process.env.DQL_OPENLINEAGE_DISABLED; + else process.env.DQL_OPENLINEAGE_DISABLED = prev; + }); +}); diff --git a/packages/dql-openlineage/src/index.ts b/packages/dql-openlineage/src/index.ts new file mode 100644 index 00000000..124e7562 --- /dev/null +++ b/packages/dql-openlineage/src/index.ts @@ -0,0 +1,133 @@ +// OpenLineage emitter for DQL. Sends START/COMPLETE/FAIL run events to an +// OpenLineage-compatible receiver (Marquez, DataHub, Atlan, Monte Carlo). +// +// Design: pure fetch-based, zero deps. Gated at the *surface* — if +// OPENLINEAGE_URL isn't set and `enabled` isn't true in config, events are +// dropped silently. That matches the "never surface infra errors to the +// user" rule from run-snapshot autosave. +// +// Spec: https://openlineage.io/spec — currently emits spec 0.19. + +export const OPENLINEAGE_SPEC_VERSION = '0.19.0'; + +export type EventType = 'START' | 'COMPLETE' | 'FAIL' | 'ABORT'; + +export interface LineageDataset { + namespace: string; + name: string; + facets?: Record; +} + +export interface LineageJob { + namespace: string; + name: string; + facets?: Record; +} + +export interface LineageRun { + runId: string; // UUID recommended + facets?: Record; +} + +export interface LineageEvent { + eventType: EventType; + eventTime: string; // ISO-8601 + producer: string; + schemaURL: string; + job: LineageJob; + run: LineageRun; + inputs?: LineageDataset[]; + outputs?: LineageDataset[]; +} + +export interface EmitterConfig { + enabled?: boolean; + url?: string; + namespace?: string; + // Supply a custom fetch (tests, polyfills). Defaults to global fetch. + fetch?: typeof fetch; + // Tiny structured logger. Errors never throw past here. + onError?: (err: unknown) => void; +} + +const PRODUCER = 'https://github.com/duckcode-ai/dql'; +const SCHEMA_URL = + 'https://openlineage.io/spec/2-0-0/OpenLineage.json#/definitions/RunEvent'; + +/** + * Resolve config from args → env → disabled default. Env vars follow the + * OpenLineage standard (OPENLINEAGE_URL, OPENLINEAGE_NAMESPACE) plus our + * opt-out flag DQL_OPENLINEAGE_DISABLED. + */ +export function resolveConfig(partial: EmitterConfig = {}): Required< + Pick +> & Pick { + const env = (globalThis as { process?: { env?: Record } }).process?.env ?? {}; + const disabled = env.DQL_OPENLINEAGE_DISABLED === '1'; + const enabled = !disabled && (partial.enabled ?? Boolean(partial.url ?? env.OPENLINEAGE_URL)); + return { + enabled, + url: partial.url ?? env.OPENLINEAGE_URL ?? '', + namespace: partial.namespace ?? env.OPENLINEAGE_NAMESPACE ?? 'dql', + fetch: partial.fetch, + onError: partial.onError, + }; +} + +export class OpenLineageEmitter { + constructor(private readonly cfg: ReturnType) {} + + async emit(event: Omit): Promise { + if (!this.cfg.enabled || !this.cfg.url) return; + const fullEvent: LineageEvent = { + ...event, + producer: PRODUCER, + schemaURL: SCHEMA_URL, + }; + const fetchImpl = this.cfg.fetch ?? (globalThis.fetch as typeof fetch | undefined); + if (!fetchImpl) { + this.cfg.onError?.(new Error('OpenLineage: no fetch implementation available')); + return; + } + try { + const res = await fetchImpl(this.cfg.url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(fullEvent), + }); + if (!res.ok) { + this.cfg.onError?.(new Error(`OpenLineage POST returned ${res.status}`)); + } + } catch (err) { + // Best-effort — infra failures must not surface to end users. + this.cfg.onError?.(err); + } + } + + /** + * Convenience for the common block-run case: emits START then COMPLETE + * around a synchronous or async handler. On throw, emits FAIL and + * re-throws so the caller still sees the error. + */ + async wrap( + job: LineageJob, + runId: string, + io: { inputs?: LineageDataset[]; outputs?: LineageDataset[] }, + handler: () => Promise | T, + ): Promise { + const now = () => new Date().toISOString(); + await this.emit({ eventType: 'START', eventTime: now(), job, run: { runId }, inputs: io.inputs, outputs: io.outputs }); + try { + const result = await handler(); + await this.emit({ eventType: 'COMPLETE', eventTime: now(), job, run: { runId }, inputs: io.inputs, outputs: io.outputs }); + return result; + } catch (err) { + await this.emit({ eventType: 'FAIL', eventTime: now(), job, run: { runId }, inputs: io.inputs, outputs: io.outputs }); + throw err; + } + } +} + +export function createEmitter(cfg: EmitterConfig = {}): OpenLineageEmitter { + return new OpenLineageEmitter(resolveConfig(cfg)); +} diff --git a/packages/dql-openlineage/tsconfig.json b/packages/dql-openlineage/tsconfig.json new file mode 100644 index 00000000..cca65486 --- /dev/null +++ b/packages/dql-openlineage/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "composite": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "**/*.test.ts"] +} diff --git a/packages/dql-plugin-api/README.md b/packages/dql-plugin-api/README.md new file mode 100644 index 00000000..e7939b05 --- /dev/null +++ b/packages/dql-plugin-api/README.md @@ -0,0 +1,56 @@ +# @duckcodeailabs/dql-plugin-api + +Stable plugin contracts for DQL. **Frozen at v1.0** — breaking changes +require a major version bump and a 6-month deprecation window. + +## Exports + +```ts +import type { Connector } from '@duckcodeailabs/dql-plugin-api/connector'; +import type { ChartRenderer } from '@duckcodeailabs/dql-plugin-api/chart'; +import type { RulePack } from '@duckcodeailabs/dql-plugin-api/governance'; +``` + +Or all at once: + +```ts +import type { Connector, ChartRenderer, RulePack } from '@duckcodeailabs/dql-plugin-api'; +``` + +## Connector + +See [`src/connector.ts`](./src/connector.ts). Implement `Connector`, +publish as a module, register in `cdql.yaml`: + +```yaml +plugins: + connectors: + - my-company/dql-connector-exasol +``` + +## Chart renderer + +See [`src/chart.ts`](./src/chart.ts). Registered the same way under +`plugins.charts`. + +## Governance rule pack + +See [`src/governance.ts`](./src/governance.ts). Registered under +`governance.rule_packs`. + +## Stability guarantee + +The interfaces in this package are versioned via `PLUGIN_API_VERSION`. +At v1.0: + +- Adding optional fields to request/result shapes: **non-breaking** +- Adding new methods marked optional: **non-breaking** +- Removing or renaming fields: **breaking, major bump required** +- Changing method signatures (even "compatible" widening): **breaking** + +Consumers can assert compatibility at runtime: + +```ts +import { PLUGIN_API_VERSION } from '@duckcodeailabs/dql-plugin-api'; +if (!PLUGIN_API_VERSION.startsWith('1.')) throw new Error('incompatible DQL'); +``` diff --git a/packages/dql-plugin-api/package.json b/packages/dql-plugin-api/package.json new file mode 100644 index 00000000..15807904 --- /dev/null +++ b/packages/dql-plugin-api/package.json @@ -0,0 +1,43 @@ +{ + "name": "@duckcodeailabs/dql-plugin-api", + "version": "1.0.1", + "description": "Stable plugin contracts for DQL — connectors, chart renderers, governance rule packs. Frozen at v1.0.", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./connector": { + "types": "./dist/connector.d.ts", + "import": "./dist/connector.js" + }, + "./chart": { + "types": "./dist/chart.d.ts", + "import": "./dist/chart.js" + }, + "./governance": { + "types": "./dist/governance.d.ts", + "import": "./dist/governance.js" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "rm -rf dist tsconfig.tsbuildinfo && tsc", + "test": "vitest run" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "typescript": "^5.4.0", + "vitest": "^1.6.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/dql-plugin-api/src/chart.ts b/packages/dql-plugin-api/src/chart.ts new file mode 100644 index 00000000..bc2c0394 --- /dev/null +++ b/packages/dql-plugin-api/src/chart.ts @@ -0,0 +1,43 @@ +/** + * Chart renderer contract. + * + * FROZEN at v1.0. A renderer turns a QueryResult + a config object into + * either a Vega-Lite spec (for the static compiler) or a React element + * (for the browser runtime). + */ + +import type { QueryResult } from './connector.js'; + +/** JSON schema describing the config shape this renderer accepts. */ +export type ChartConfigSchema = { + type: 'object'; + properties: Record; + required?: string[]; +}; + +export type ChartConfig = Record; + +/** + * A renderer may produce either output form; the runtime picks based on + * target (static HTML dashboards need VegaLite; interactive notebook cells + * can use React). + */ +export type ChartOutput = + | { kind: 'vega-lite'; spec: Record } + | { kind: 'react'; element: unknown }; + +export interface ChartRenderer { + /** Unique id — what the user writes as `visualization: (…)`. */ + id: string; + displayName: string; + configSchema: ChartConfigSchema; + + /** + * Validate + normalize the user-supplied config. Return a user-friendly + * message on failure (will surface in Block Studio's lint ribbon). + */ + validate(config: ChartConfig): { ok: true; config: ChartConfig } | { ok: false; error: string }; + + /** Produce the chart output for a given result + validated config. */ + render(result: QueryResult, config: ChartConfig): ChartOutput; +} diff --git a/packages/dql-plugin-api/src/connector.ts b/packages/dql-plugin-api/src/connector.ts new file mode 100644 index 00000000..9e6e0d26 --- /dev/null +++ b/packages/dql-plugin-api/src/connector.ts @@ -0,0 +1,80 @@ +/** + * Warehouse connector contract. + * + * FROZEN at v1.0 — any breaking change requires a major version bump and a + * 6-month deprecation window. See docs.duckcode.ai/architecture/plugin-api. + */ + +export interface ColumnSchema { + name: string; + type: string; + nullable?: boolean; +} + +export interface TableSchema { + schema: string; + name: string; + columns: ColumnSchema[]; +} + +export interface QueryRow { + [column: string]: unknown; +} + +export interface QueryResult { + columns: ColumnSchema[]; + rows: QueryRow[]; + rowCount: number; + executionTimeMs?: number; +} + +export interface QueryCursor extends AsyncIterable { + readonly columns: ColumnSchema[]; + close(): Promise; +} + +/** Per-driver configuration — opaque to the core. */ +export type ConnectorConfig = Record; + +export interface ConnectorMetadata { + id: string; // unique driver id, e.g. "postgres" + displayName: string; // human name, e.g. "PostgreSQL" + supports: { + streaming: boolean; + introspection: boolean; + transactions: boolean; + }; +} + +/** + * Implement this interface and register the module in cdql.yaml under + * `plugins.connectors`. DQL will instantiate, connect, and close the + * connector; plugin authors do not need to manage lifecycle. + */ +export interface Connector { + readonly metadata: ConnectorMetadata; + + /** Open a connection. Called once per session. */ + connect(config: ConnectorConfig): Promise; + + /** Close the connection. Must be idempotent. */ + close(): Promise; + + /** Execute a query to completion and return the full result. */ + query(sql: string, params?: unknown[]): Promise; + + /** + * Optional: execute a query as a streaming cursor. Required when + * `metadata.supports.streaming` is true. + */ + stream?(sql: string, params?: unknown[]): Promise; + + /** + * Optional: list tables with columns. Required when + * `metadata.supports.introspection` is true. + */ + introspect?(): Promise; + + /** Optional: sanity-check the connection is live. */ + ping?(): Promise; +} diff --git a/packages/dql-plugin-api/src/governance.ts b/packages/dql-plugin-api/src/governance.ts new file mode 100644 index 00000000..44dbf721 --- /dev/null +++ b/packages/dql-plugin-api/src/governance.ts @@ -0,0 +1,49 @@ +/** + * Governance rule pack contract. + * + * FROZEN at v1.0. A rule pack is a named bundle of lint rules that run + * against block ASTs — in the editor (real-time) and at `dql certify` + * (gating). + */ + +export type Severity = 'error' | 'warning' | 'info'; + +/** A minimal view of a parsed block passed to rules. Keep this shape stable. */ +export interface BlockView { + name: string; + domain?: string; + owner?: string; + description?: string; + tags: string[]; + query: string; + visualization?: string; + path: string; // source file path + metricRefs: string[]; // @metric("…") references + dimensionRefs: string[]; // @dim("…") references + blockRefs: string[]; // @block("…") references + tableRefs: string[]; // @table("…") references +} + +export interface Diagnostic { + ruleId: string; + severity: Severity; + message: string; + /** Optional AST span (0-based byte offsets) for editor underlining. */ + span?: { start: number; end: number }; + /** Optional fix suggestion. */ + fix?: { title: string; newText: string; span: { start: number; end: number } }; +} + +export interface Rule { + id: string; // unique within the pack, e.g. "no-select-star" + description: string; + defaultSeverity: Severity; + check(block: BlockView): Diagnostic[]; +} + +export interface RulePack { + id: string; // unique across the ecosystem, e.g. "hipaa" + displayName: string; + version: string; // semver of the pack itself + rules: Rule[]; +} diff --git a/packages/dql-plugin-api/src/index.test.ts b/packages/dql-plugin-api/src/index.test.ts new file mode 100644 index 00000000..b20857e5 --- /dev/null +++ b/packages/dql-plugin-api/src/index.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest'; +import { + PLUGIN_API_VERSION, + type ChartRenderer, + type Connector, + type RulePack, +} from './index.js'; + +describe('plugin-api contracts', () => { + it('version tag is 1.0', () => { + expect(PLUGIN_API_VERSION).toBe('1.0'); + }); + + it('a minimal Connector compiles against the contract', () => { + const c: Connector = { + metadata: { + id: 'mock', + displayName: 'Mock', + supports: { streaming: false, introspection: false, transactions: false }, + }, + async connect() {}, + async close() {}, + async query() { return { columns: [], rows: [], rowCount: 0 }; }, + }; + expect(c.metadata.id).toBe('mock'); + }); + + it('a minimal ChartRenderer compiles against the contract', () => { + const r: ChartRenderer = { + id: 'mock', + displayName: 'Mock', + configSchema: { type: 'object', properties: {} }, + validate(cfg) { return { ok: true, config: cfg }; }, + render(_r, _cfg) { return { kind: 'vega-lite', spec: {} }; }, + }; + expect(r.id).toBe('mock'); + }); + + it('a minimal RulePack compiles against the contract', () => { + const pack: RulePack = { + id: 'mock', + displayName: 'Mock', + version: '0.0.1', + rules: [ + { + id: 'r', + description: 'd', + defaultSeverity: 'warning', + check() { return []; }, + }, + ], + }; + expect(pack.rules).toHaveLength(1); + }); +}); diff --git a/packages/dql-plugin-api/src/index.ts b/packages/dql-plugin-api/src/index.ts new file mode 100644 index 00000000..587383d7 --- /dev/null +++ b/packages/dql-plugin-api/src/index.ts @@ -0,0 +1,13 @@ +// Public plugin API for DQL. FROZEN at v1.0. +// +// These types are the contract between DQL core and third-party extensions. +// Breaking changes require a major bump and a 6-month deprecation window. +// Don't import from anywhere else in @duckcodeailabs/* — those internals +// can change between minor versions. + +export * from './connector.js'; +export * from './chart.js'; +export * from './governance.js'; + +/** Marker re-exported so plugin authors can assert stability. */ +export const PLUGIN_API_VERSION = '1.0'; diff --git a/packages/dql-plugin-api/tsconfig.json b/packages/dql-plugin-api/tsconfig.json new file mode 100644 index 00000000..d1f799dd --- /dev/null +++ b/packages/dql-plugin-api/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "composite": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "**/*.test.ts"] +} diff --git a/packages/dql-project/package.json b/packages/dql-project/package.json index e03ca550..ed20bfb7 100644 --- a/packages/dql-project/package.json +++ b/packages/dql-project/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-project", - "version": "0.10.1", + "version": "1.0.1", "description": "DQL project and block registry primitives: block metadata, versions, sync, and storage adapters", "license": "Apache-2.0", "type": "module", diff --git a/packages/dql-runtime/package.json b/packages/dql-runtime/package.json index d0612003..c760a9aa 100644 --- a/packages/dql-runtime/package.json +++ b/packages/dql-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-runtime", - "version": "0.10.1", + "version": "1.0.1", "description": "DQL browser runtime: data fetching, Vega rendering, hot-reload client", "license": "Apache-2.0", "type": "module", diff --git a/packages/dql-telemetry/README.md b/packages/dql-telemetry/README.md new file mode 100644 index 00000000..552df1a2 --- /dev/null +++ b/packages/dql-telemetry/README.md @@ -0,0 +1,70 @@ +# @duckcodeailabs/dql-telemetry + +Opt-in, privacy-first usage telemetry for DQL. + +## Principles + +1. **Off by default.** Must be explicitly enabled. +2. **No PII.** Ever. Only enum-valued counters and durations. +3. **One-line opt-out.** `DO_NOT_TRACK=1`, `DQL_TELEMETRY_DISABLED=1`, or `dql telemetry off`. +4. **Transparent event schema.** Documented below; nothing hidden. +5. **Never blocks the CLI.** 2-second timeout, silent on failure. + +## What gets sent + +Exactly these fields, nothing else: + +```json +{ + "event": "cli.command", + "anonymousId": "uuid-or-hostname-hash", + "version": "1.0.0", + "ts": "2026-04-15T12:34:56Z", + "props": { + "command": "init", + "success": true + }, + "durationMs": 42 +} +``` + +Events emitted today: + +| Event | When | Props (enum-only) | +| --- | --- | --- | +| `cli.command` | any `dql ` finishes | `command`, `success` | +| `notebook.open` | notebook boots | — | +| `block.certified` | a block is promoted | `domain` | +| `dbt.synced` | `dql sync dbt` finishes | `modelsAdded`, `modelsRemoved` (counts only) | +| `dashboard.built` | `dql build` finishes | `chartCount` | + +No file names, query contents, warehouse URLs, schema names, block names, +or any other potentially-sensitive strings are ever transmitted. + +## Opt out + +Any of these disable telemetry: + +```bash +export DO_NOT_TRACK=1 # respects the DNT standard +export DQL_TELEMETRY_DISABLED=1 +dql telemetry off # persists to ~/.config/dql/telemetry.json +``` + +## Opt in + +```bash +dql telemetry on +``` + +Or programmatically: + +```ts +import { setEnabled } from '@duckcodeailabs/dql-telemetry'; +setEnabled(true); +``` + +## Source + +Open source, MIT. See +[`src/index.ts`](./src/index.ts). There is no compiled/minified blob. diff --git a/packages/dql-telemetry/package.json b/packages/dql-telemetry/package.json new file mode 100644 index 00000000..0dd7bfa4 --- /dev/null +++ b/packages/dql-telemetry/package.json @@ -0,0 +1,31 @@ +{ + "name": "@duckcodeailabs/dql-telemetry", + "version": "1.0.1", + "description": "Opt-in, privacy-first usage telemetry for DQL.", + "license": "MIT", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "rm -rf dist tsconfig.tsbuildinfo && tsc", + "test": "vitest run" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "typescript": "^5.4.0", + "vitest": "^1.6.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/dql-telemetry/src/index.test.ts b/packages/dql-telemetry/src/index.test.ts new file mode 100644 index 00000000..c9aaac57 --- /dev/null +++ b/packages/dql-telemetry/src/index.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it, vi } from 'vitest'; +import { track } from './index.js'; + +describe('telemetry', () => { + it('does nothing when disabled', async () => { + const fetchSpy = vi.fn(); + await track({ name: 'cli.command' }, { fetch: fetchSpy as unknown as typeof fetch, enabled: false }); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it('respects DO_NOT_TRACK even when enabled', async () => { + process.env.DO_NOT_TRACK = '1'; + const fetchSpy = vi.fn(); + await track({ name: 'cli.command' }, { fetch: fetchSpy as unknown as typeof fetch, enabled: true, endpoint: 'http://x' }); + expect(fetchSpy).not.toHaveBeenCalled(); + delete process.env.DO_NOT_TRACK; + }); + + it('respects DQL_TELEMETRY_DISABLED', async () => { + process.env.DQL_TELEMETRY_DISABLED = '1'; + const fetchSpy = vi.fn(); + await track({ name: 'cli.command' }, { fetch: fetchSpy as unknown as typeof fetch, enabled: true, endpoint: 'http://x' }); + expect(fetchSpy).not.toHaveBeenCalled(); + delete process.env.DQL_TELEMETRY_DISABLED; + }); + + it('posts payload with event name, version, and props when enabled', async () => { + const fetchSpy = vi.fn().mockResolvedValue({ ok: true }); + await track( + { name: 'cli.command', props: { command: 'init', success: true }, durationMs: 42 }, + { + fetch: fetchSpy as unknown as typeof fetch, + enabled: true, + endpoint: 'http://x', + anonymousId: 'abc', + version: '1.0.0', + }, + ); + expect(fetchSpy).toHaveBeenCalledOnce(); + const [url, opts] = fetchSpy.mock.calls[0]; + expect(url).toBe('http://x'); + const body = JSON.parse(opts.body); + expect(body.event).toBe('cli.command'); + expect(body.version).toBe('1.0.0'); + expect(body.props.command).toBe('init'); + expect(body.durationMs).toBe(42); + expect(body.anonymousId).toBe('abc'); + }); + + it('swallows fetch errors', async () => { + const errors: unknown[] = []; + await track( + { name: 'cli.command' }, + { + fetch: vi.fn().mockRejectedValue(new Error('down')) as unknown as typeof fetch, + enabled: true, + endpoint: 'http://x', + anonymousId: 'abc', + onError: (e) => errors.push(e), + }, + ); + expect(errors).toHaveLength(1); + }); +}); diff --git a/packages/dql-telemetry/src/index.ts b/packages/dql-telemetry/src/index.ts new file mode 100644 index 00000000..f6f94e47 --- /dev/null +++ b/packages/dql-telemetry/src/index.ts @@ -0,0 +1,150 @@ +// Opt-in, privacy-first telemetry. +// +// Principles (per v1.0 roadmap): +// - OFF by default. Must be explicitly enabled. +// - No PII. Ever. Only enum-valued counters and durations. +// - One-line opt-out (env, config, or `dql telemetry off`). +// - The event shape is documented publicly — no hidden fields. +// - Best-effort transport. Failures are silent and bounded. +// +// Events are posted to a first-party HTTPS endpoint. No third-party +// analytics SDK is bundled. + +import { createHash, randomUUID } from 'node:crypto'; +import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { dirname, join } from 'node:path'; + +export type TelemetryEventName = + | 'cli.command' // a CLI subcommand completed + | 'notebook.open' // notebook booted + | 'block.certified' // a block was promoted to certified + | 'dbt.synced' // dql sync dbt finished + | 'dashboard.built'; // dql build produced a dashboard + +export interface TelemetryEvent { + name: TelemetryEventName; + // Small, enum-valued properties only. No free-form strings. + props?: Record; + // Optional timing in ms. + durationMs?: number; +} + +export interface TelemetryConfig { + enabled?: boolean; + endpoint?: string; // defaults to https://telemetry.duckcode.ai/v1/events + anonymousId?: string; // auto-generated stable UUID per machine + version?: string; // DQL version string; included as a property + fetch?: typeof fetch; + onError?: (err: unknown) => void; +} + +const DEFAULT_ENDPOINT = 'https://telemetry.duckcode.ai/v1/events'; +const CONFIG_DIR = join(homedir(), '.config', 'dql'); +const CONFIG_FILE = join(CONFIG_DIR, 'telemetry.json'); + +interface PersistedConfig { + enabled: boolean; + anonymousId: string; + askedAt?: string; +} + +function loadPersisted(): PersistedConfig | null { + if (!existsSync(CONFIG_FILE)) return null; + try { return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')) as PersistedConfig; } + catch { return null; } +} + +function savePersisted(cfg: PersistedConfig): void { + try { + mkdirSync(dirname(CONFIG_FILE), { recursive: true }); + writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + '\n', 'utf-8'); + } catch { /* best-effort */ } +} + +/** + * Idempotent enable/disable. Persists to `~/.config/dql/telemetry.json` so + * the user only answers once. + */ +export function setEnabled(enabled: boolean): void { + const prev = loadPersisted(); + savePersisted({ + enabled, + anonymousId: prev?.anonymousId ?? randomUUID(), + askedAt: new Date().toISOString(), + }); +} + +export function getStatus(): { enabled: boolean; anonymousId: string | null } { + const persisted = loadPersisted(); + return { + enabled: persisted?.enabled ?? false, + anonymousId: persisted?.anonymousId ?? null, + }; +} + +/** + * Resolve config from explicit args → env → persisted file → OFF default. + */ +function resolveConfig(partial: TelemetryConfig): Required> & Pick { + const env = process.env; + if (env.DQL_TELEMETRY_DISABLED === '1' || env.DO_NOT_TRACK === '1') { + return { enabled: false, endpoint: '', anonymousId: '', version: partial.version }; + } + const persisted = loadPersisted(); + const enabled = partial.enabled ?? persisted?.enabled ?? false; + return { + enabled, + endpoint: partial.endpoint ?? env.DQL_TELEMETRY_ENDPOINT ?? DEFAULT_ENDPOINT, + anonymousId: partial.anonymousId ?? persisted?.anonymousId ?? '', + fetch: partial.fetch, + onError: partial.onError, + version: partial.version, + }; +} + +/** + * One-shot send. No batching — volume is low enough to not matter, and a + * batch buffer would mean we might drop events on abrupt CLI exit. + */ +export async function track(event: TelemetryEvent, cfg: TelemetryConfig = {}): Promise { + const resolved = resolveConfig(cfg); + if (!resolved.enabled || !resolved.endpoint) return; + const fetchImpl = resolved.fetch ?? (globalThis.fetch as typeof fetch | undefined); + if (!fetchImpl) return; + + const payload = { + event: event.name, + anonymousId: resolved.anonymousId || hashedMachineId(), + version: resolved.version ?? 'unknown', + ts: new Date().toISOString(), + props: event.props ?? {}, + durationMs: event.durationMs, + }; + + // 2s timeout — telemetry must never block a CLI command visibly. + const controller = new AbortController(); + const to = setTimeout(() => controller.abort(), 2000); + try { + await fetchImpl(resolved.endpoint, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + } catch (err) { + resolved.onError?.(err); + } finally { + clearTimeout(to); + } +} + +/** + * Hash of hostname + platform so we don't need to persist anything to get + * a stable-ish machine id. Used only when no persisted anonymousId exists + * (e.g. first call before first-run opt-in prompt). + */ +function hashedMachineId(): string { + const bits = [process.platform, process.arch, process.env.USER ?? '', process.env.USERNAME ?? ''].join('|'); + return createHash('sha256').update(bits).digest('hex').slice(0, 16); +} diff --git a/packages/dql-telemetry/tsconfig.json b/packages/dql-telemetry/tsconfig.json new file mode 100644 index 00000000..cca65486 --- /dev/null +++ b/packages/dql-telemetry/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "composite": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "**/*.test.ts"] +} diff --git a/packages/dql-ui/package.json b/packages/dql-ui/package.json index 3f726d2f..7a4dc373 100644 --- a/packages/dql-ui/package.json +++ b/packages/dql-ui/package.json @@ -1,6 +1,6 @@ { "name": "@duckcodeailabs/dql-ui", - "version": "0.10.1", + "version": "1.0.1", "description": "Design tokens, theme provider, and headless primitives for the DQL notebook + dashboard shell", "license": "Apache-2.0", "type": "module", diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 00000000..511b9279 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,29 @@ +# Packaging + +Distribution channels for DQL releases. + +| Channel | Source | Install command | +| --- | --- | --- | +| npm | `packages/*`, `apps/cli` | `npm i -g @duckcodeailabs/dql-cli` | +| create-app | `packages/create-dql-app` | `npx create-dql-app ` | +| Homebrew | `packaging/homebrew/` | `brew tap duckcode-ai/dql && brew install dql` | +| Desktop (Tauri) | `apps/desktop/` | Download from GitHub Releases | + +## Release flow + +Triggered by pushing a `v*.*.*` tag: + +1. `.github/workflows/release.yml` runs on `ubuntu-latest`: + - builds + publishes all npm packages + - generates Homebrew formula via `packaging/homebrew/publish.mjs ` + - opens a PR against `duckcode-ai/homebrew-dql` +2. `.github/workflows/release-desktop.yml` runs on matrix `[macos-14, macos-13, ubuntu-22.04, windows-latest]`: + - builds Tauri bundle per platform + - attaches `.dmg` / `.AppImage` / `.msi` to the GitHub Release + +Both workflows need these secrets: + +- `NPM_TOKEN` — npm publish auth +- `HOMEBREW_TAP_GITHUB_TOKEN` — PR into the tap repo +- `APPLE_*` (signing + notarization) — macOS-only, `release-desktop.yml` +- `WINDOWS_CERT_BASE64`, `WINDOWS_CERT_PASSWORD` — Windows code signing diff --git a/packaging/homebrew/dql.rb.tmpl b/packaging/homebrew/dql.rb.tmpl new file mode 100644 index 00000000..a7e53ad7 --- /dev/null +++ b/packaging/homebrew/dql.rb.tmpl @@ -0,0 +1,25 @@ +# Homebrew formula template. CI substitutes {{VERSION}} and {{SHA}} per +# release and commits the result to github.com/duckcode-ai/homebrew-dql. +# +# Users install with: +# brew tap duckcode-ai/dql +# brew install dql + +class Dql < Formula + desc "Git-native analytics notebooks on your dbt models" + homepage "https://docs.duckcode.ai" + url "https://registry.npmjs.org/@duckcodeailabs/dql-cli/-/dql-cli-{{VERSION}}.tgz" + sha256 "{{SHA}}" + license "MIT" + + depends_on "node@20" + + def install + system "npm", "install", *Language::Node.std_npm_install_args(libexec) + bin.install_symlink Dir["#{libexec}/bin/*"] + end + + test do + assert_match "dql", shell_output("#{bin}/dql --version") + end +end diff --git a/packaging/homebrew/publish.mjs b/packaging/homebrew/publish.mjs new file mode 100644 index 00000000..75143db4 --- /dev/null +++ b/packaging/homebrew/publish.mjs @@ -0,0 +1,35 @@ +#!/usr/bin/env node +// Publish a new Homebrew formula by filling in dql.rb.tmpl with the +// version + tarball sha256, then pushing to github.com/duckcode-ai/homebrew-dql. +// Invoked from .github/workflows/release.yml after the CLI is published to npm. +// +// Usage: node packaging/homebrew/publish.mjs 0.11.0 +import { readFileSync, writeFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const VERSION = process.argv[2]; +if (!VERSION) { console.error('usage: publish.mjs '); process.exit(2); } + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const TMPL = resolve(__dirname, 'dql.rb.tmpl'); + +async function fetchTarballSha(version) { + const url = `https://registry.npmjs.org/@duckcodeailabs/dql-cli/-/dql-cli-${version}.tgz`; + const res = await fetch(url); + if (!res.ok) throw new Error(`tarball fetch failed: ${res.status}`); + const buf = Buffer.from(await res.arrayBuffer()); + return createHash('sha256').update(buf).digest('hex'); +} + +const sha = await fetchTarballSha(VERSION); +const rendered = readFileSync(TMPL, 'utf-8') + .replace(/\{\{VERSION\}\}/g, VERSION) + .replace(/\{\{SHA\}\}/g, sha); + +const out = resolve(__dirname, 'dql.rb'); +writeFileSync(out, rendered); +console.log(`✓ wrote ${out} (sha ${sha.slice(0, 12)}…)`); +console.log('\nnext: CI pushes this file to duckcode-ai/homebrew-dql@main'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5feee24c..aabcf703 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,40 @@ importers: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + apps/desktop: + devDependencies: + '@tauri-apps/cli': + specifier: 2.1.0 + version: 2.1.0 + + apps/docs: + dependencies: + next: + specifier: 14.2.5 + version: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: + specifier: 2.13.4 + version: 2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra-theme-docs: + specifier: 2.13.4 + version: 2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: 18.3.1 + version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/node': + specifier: 20.14.10 + version: 20.14.10 + '@types/react': + specifier: 18.3.3 + version: 18.3.3 + typescript: + specifier: 5.5.3 + version: 5.5.3 + apps/dql-notebook: dependencies: '@codemirror/autocomplete': @@ -150,6 +184,8 @@ importers: specifier: ^9.0.1 version: 9.0.1 + packages/create-dql-app: {} + packages/dql-charts: dependencies: '@visx/axis': @@ -340,6 +376,30 @@ importers: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + packages/dql-openlineage: + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.14.10 + typescript: + specifier: ^5.4.0 + version: 5.9.3 + vitest: + specifier: ^1.6.0 + version: 1.6.1(@types/node@20.14.10)(terser@5.46.0) + + packages/dql-plugin-api: + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.14.10 + typescript: + specifier: ^5.4.0 + version: 5.9.3 + vitest: + specifier: ^1.6.0 + version: 1.6.1(@types/node@20.14.10)(terser@5.46.0) + packages/dql-project: dependencies: '@duckcodeailabs/dql-core': @@ -374,6 +434,18 @@ importers: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + packages/dql-telemetry: + devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.14.10 + typescript: + specifier: ^5.4.0 + version: 5.9.3 + vitest: + specifier: ^1.6.0 + version: 1.6.1(@types/node@20.14.10)(terser@5.46.0) + packages/dql-ui: dependencies: '@radix-ui/react-dialog': @@ -839,6 +911,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -851,6 +927,9 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@6.0.4': + resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + '@codemirror/autocomplete@6.20.1': resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} @@ -888,6 +967,12 @@ packages: '@dagrejs/graphlib@4.0.1': resolution: {integrity: sha512-IvcV6FduIIAmLwnH+yun+QtV36SC7mERqa86aClNqmMN09WhmPPYU8ckHrZBozErf+UvHPWOTJYaGYiIcs0DgA==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} @@ -906,6 +991,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.24.2': resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} engines: {node: '>=18'} @@ -924,6 +1015,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.24.2': resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} engines: {node: '>=18'} @@ -942,6 +1039,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.24.2': resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} engines: {node: '>=18'} @@ -960,6 +1063,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.24.2': resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} engines: {node: '>=18'} @@ -978,6 +1087,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.24.2': resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} engines: {node: '>=18'} @@ -996,6 +1111,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} engines: {node: '>=18'} @@ -1014,6 +1135,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} engines: {node: '>=18'} @@ -1032,6 +1159,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.24.2': resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} engines: {node: '>=18'} @@ -1050,6 +1183,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.24.2': resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} engines: {node: '>=18'} @@ -1068,6 +1207,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.24.2': resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} engines: {node: '>=18'} @@ -1086,6 +1231,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.24.2': resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} engines: {node: '>=18'} @@ -1104,6 +1255,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.24.2': resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} engines: {node: '>=18'} @@ -1122,6 +1279,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.24.2': resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} engines: {node: '>=18'} @@ -1140,6 +1303,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.24.2': resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} engines: {node: '>=18'} @@ -1158,6 +1327,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.24.2': resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} engines: {node: '>=18'} @@ -1176,6 +1351,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.24.2': resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} engines: {node: '>=18'} @@ -1212,6 +1393,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} engines: {node: '>=18'} @@ -1248,6 +1435,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} engines: {node: '>=18'} @@ -1278,6 +1471,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.24.2': resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} engines: {node: '>=18'} @@ -1296,6 +1495,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.24.2': resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} engines: {node: '>=18'} @@ -1314,6 +1519,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.24.2': resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} engines: {node: '>=18'} @@ -1332,6 +1543,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.24.2': resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} engines: {node: '>=18'} @@ -1396,6 +1613,13 @@ packages: resolution: {integrity: sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==} engines: {node: '>=14'} + '@headlessui/react@1.7.19': + resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1408,6 +1632,10 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1447,6 +1675,165 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@mdx-js/mdx@2.3.0': + resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + + '@mdx-js/react@2.3.0': + resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} + peerDependencies: + react: '>=16' + + '@napi-rs/simple-git-android-arm-eabi@0.1.22': + resolution: {integrity: sha512-JQZdnDNm8o43A5GOzwN/0Tz3CDBQtBUNqzVwEopm32uayjdjxev1Csp1JeaqF3v9djLDIvsSE39ecsN2LhCKKQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/simple-git-android-arm64@0.1.22': + resolution: {integrity: sha512-46OZ0SkhnvM+fapWjzg/eqbJvClxynUpWYyYBn4jAj7GQs1/Yyc8431spzDmkA8mL0M7Xo8SmbkzTDE7WwYAfg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/simple-git-darwin-arm64@0.1.22': + resolution: {integrity: sha512-zH3h0C8Mkn9//MajPI6kHnttywjsBmZ37fhLX/Fiw5XKu84eHA6dRyVtMzoZxj6s+bjNTgaMgMUucxPn9ktxTQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/simple-git-darwin-x64@0.1.22': + resolution: {integrity: sha512-GZN7lRAkGKB6PJxWsoyeYJhh85oOOjVNyl+/uipNX8bR+mFDCqRsCE3rRCFGV9WrZUHXkcuRL2laIRn7lLi3ag==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/simple-git-freebsd-x64@0.1.22': + resolution: {integrity: sha512-xyqX1C5I0WBrUgZONxHjZH5a4LqQ9oki3SKFAVpercVYAcx3pq6BkZy1YUOP4qx78WxU1CCNfHBN7V+XO7D99A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/simple-git-linux-arm-gnueabihf@0.1.22': + resolution: {integrity: sha512-4LOtbp9ll93B9fxRvXiUJd1/RM3uafMJE7dGBZGKWBMGM76+BAcCEUv2BY85EfsU/IgopXI6n09TycRfPWOjxA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/simple-git-linux-arm64-gnu@0.1.22': + resolution: {integrity: sha512-GVOjP/JjCzbQ0kSqao7ctC/1sodVtv5VF57rW9BFpo2y6tEYPCqHnkQkTpieuwMNe+TVOhBUC1+wH0d9/knIHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/simple-git-linux-arm64-musl@0.1.22': + resolution: {integrity: sha512-MOs7fPyJiU/wqOpKzAOmOpxJ/TZfP4JwmvPad/cXTOWYwwyppMlXFRms3i98EU3HOazI/wMU2Ksfda3+TBluWA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/simple-git-linux-ppc64-gnu@0.1.22': + resolution: {integrity: sha512-L59dR30VBShRUIZ5/cQHU25upNgKS0AMQ7537J6LCIUEFwwXrKORZKJ8ceR+s3Sr/4jempWVvMdjEpFDE4HYww==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + + '@napi-rs/simple-git-linux-s390x-gnu@0.1.22': + resolution: {integrity: sha512-4FHkPlCSIZUGC6HiADffbe6NVoTBMd65pIwcd40IDbtFKOgFMBA+pWRqKiQ21FERGH16Zed7XHJJoY3jpOqtmQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + + '@napi-rs/simple-git-linux-x64-gnu@0.1.22': + resolution: {integrity: sha512-Ei1tM5Ho/dwknF3pOzqkNW9Iv8oFzRxE8uOhrITcdlpxRxVrBVptUF6/0WPdvd7R9747D/q61QG/AVyWsWLFKw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/simple-git-linux-x64-musl@0.1.22': + resolution: {integrity: sha512-zRYxg7it0p3rLyEJYoCoL2PQJNgArVLyNavHW03TFUAYkYi5bxQ/UFNVpgxMaXohr5yu7qCBqeo9j4DWeysalg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/simple-git-win32-arm64-msvc@0.1.22': + resolution: {integrity: sha512-XGFR1fj+Y9cWACcovV2Ey/R2xQOZKs8t+7KHPerYdJ4PtjVzGznI4c2EBHXtdOIYvkw7tL5rZ7FN1HJKdD5Quw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/simple-git-win32-ia32-msvc@0.1.22': + resolution: {integrity: sha512-Gqr9Y0gs6hcNBA1IXBpoqTFnnIoHuZGhrYqaZzEvGMLrTrpbXrXVEtX3DAAD2RLc1b87CPcJ49a7sre3PU3Rfw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/simple-git-win32-x64-msvc@0.1.22': + resolution: {integrity: sha512-hQjcreHmUcpw4UrtkOron1/TQObfe484lxiXFLLUj7aWnnnOVs1mnXq5/Bo9+3NYZldFpFRJPdPBeHCisXkKJg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/simple-git@0.1.22': + resolution: {integrity: sha512-bMVoAKhpjTOPHkW/lprDPwv5aD4R4C3Irt8vn+SKA9wudLe9COLxOhurrKRsxmZccUbWXRF7vukNeGUAj5P8kA==} + engines: {node: '>= 10'} + + '@next/env@14.2.5': + resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + + '@next/swc-darwin-arm64@14.2.5': + resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@14.2.5': + resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@14.2.5': + resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@14.2.5': + resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@14.2.5': + resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@14.2.5': + resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@14.2.5': + resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-ia32-msvc@14.2.5': + resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@14.2.5': + resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1476,6 +1863,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -1967,6 +2357,9 @@ packages: resolution: {integrity: sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==} engines: {node: '>=20.0.0'} + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@sindresorhus/merge-streams@2.3.0': resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} @@ -2374,31 +2767,122 @@ packages: '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} - '@techteamer/ocsp@1.0.1': - resolution: {integrity: sha512-q4pW5wAC6Pc3JI8UePwE37CkLQ5gDGZMgjSX4MEEm4D4Di59auDQ8UNIDzC4gRnPNmmcwjpPxozq8p5pjiOmOw==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@tediousjs/connection-string@0.5.0': - resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==} + '@swc/helpers@0.5.5': + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - '@textlint/ast-node-types@15.5.2': - resolution: {integrity: sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==} + '@tanstack/react-virtual@3.13.24': + resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@textlint/linter-formatter@15.5.2': - resolution: {integrity: sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg==} + '@tanstack/virtual-core@3.14.0': + resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} - '@textlint/module-interop@15.5.2': - resolution: {integrity: sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg==} + '@tauri-apps/cli-darwin-arm64@2.1.0': + resolution: {integrity: sha512-ESc6J6CE8hl1yKH2vJ+ALF+thq4Be+DM1mvmTyUCQObvezNCNhzfS6abIUd3ou4x5RGH51ouiANeT3wekU6dCw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] - '@textlint/resolver@15.5.2': - resolution: {integrity: sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA==} + '@tauri-apps/cli-darwin-x64@2.1.0': + resolution: {integrity: sha512-TasHS442DFs8cSH2eUQzuDBXUST4ECjCd0yyP+zZzvAruiB0Bg+c8A+I/EnqCvBQ2G2yvWLYG8q/LI7c87A5UA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] - '@textlint/types@15.5.2': + '@tauri-apps/cli-linux-arm-gnueabihf@2.1.0': + resolution: {integrity: sha512-aP7ZBGNL4ny07Cbb6kKpUOSrmhcIK2KhjviTzYlh+pPhAptxnC78xQGD3zKQkTi2WliJLPmBYbOHWWQa57lQ9w==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.1.0': + resolution: {integrity: sha512-ZTdgD5gLeMCzndMT2f358EkoYkZ5T+Qy6zPzU+l5vv5M7dHVN9ZmblNAYYXmoOuw7y+BY4X/rZvHV9pcGrcanQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-arm64-musl@2.1.0': + resolution: {integrity: sha512-NzwqjUCilhnhJzusz3d/0i0F1GFrwCQbkwR6yAHUxItESbsGYkZRJk0yMEWkg3PzFnyK4cWTlQJMEU52TjhEzA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-x64-gnu@2.1.0': + resolution: {integrity: sha512-TyiIpMEtZxNOQmuFyfJwaaYbg3movSthpBJLIdPlKxSAB2BW0VWLY3/ZfIxm/G2YGHyREkjJvimzYE0i37PnMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-linux-x64-musl@2.1.0': + resolution: {integrity: sha512-/dQd0TlaxBdJACrR72DhynWftzHDaX32eBtS5WBrNJ+nnNb+znM3gON6nJ9tSE9jgDa6n1v2BkI/oIDtypfUXw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-win32-arm64-msvc@2.1.0': + resolution: {integrity: sha512-NdQJO7SmdYqOcE+JPU7bwg7+odfZMWO6g8xF9SXYCMdUzvM2Gv/AQfikNXz5yS7ralRhNFuW32i5dcHlxh4pDg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.1.0': + resolution: {integrity: sha512-f5h8gKT/cB8s1ticFRUpNmHqkmaLutT62oFDB7N//2YTXnxst7EpMIn1w+QimxTvTk2gcx6EcW6bEk/y2hZGzg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.1.0': + resolution: {integrity: sha512-P/+LrdSSb5Xbho1LRP4haBjFHdyPdjWvGgeopL96OVtrFpYnfC+RctB45z2V2XxqFk3HweDDxk266btjttfjGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.1.0': + resolution: {integrity: sha512-K2VhcKqBhAeS5pNOVdnR/xQRU6jwpgmkSL2ejHXcl0m+kaTggT0WRDQnFtPq6NljA7aE03cvwsbCAoFG7vtkJw==} + engines: {node: '>= 10'} + hasBin: true + + '@techteamer/ocsp@1.0.1': + resolution: {integrity: sha512-q4pW5wAC6Pc3JI8UePwE37CkLQ5gDGZMgjSX4MEEm4D4Di59auDQ8UNIDzC4gRnPNmmcwjpPxozq8p5pjiOmOw==} + + '@tediousjs/connection-string@0.5.0': + resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==} + + '@textlint/ast-node-types@15.5.2': + resolution: {integrity: sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==} + + '@textlint/linter-formatter@15.5.2': + resolution: {integrity: sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg==} + + '@textlint/module-interop@15.5.2': + resolution: {integrity: sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg==} + + '@textlint/resolver@15.5.2': + resolution: {integrity: sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA==} + + '@textlint/types@15.5.2': resolution: {integrity: sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ==} + '@theguild/remark-mermaid@0.0.5': + resolution: {integrity: sha512-e+ZIyJkEv9jabI4m7q29wZtZv+2iwPGsXJ2d46Zi7e+QcFudiyuqhLhHG/3gX3ZEB+hxTch+fpItyMS8jwbIcw==} + peerDependencies: + react: ^18.2.0 + + '@theguild/remark-npm2yarn@0.2.1': + resolution: {integrity: sha512-jUTFWwDxtLEFtGZh/TW/w30ySaDJ8atKWH8dq2/IiQF61dPrGfETpl0WxD0VdBfuLOeU14/kop466oBSRO/5CA==} + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2447,9 +2931,15 @@ packages: '@types/d3-path@1.0.11': resolution: {integrity: sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==} + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + '@types/d3-scale@4.0.2': resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} @@ -2474,18 +2964,39 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@2.3.10': + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/lodash@4.17.23': resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -2495,6 +3006,9 @@ packages: '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + '@types/node@22.19.11': resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} @@ -2518,6 +3032,9 @@ packages: '@types/react@18.3.28': resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/readable-stream@4.0.23': resolution: {integrity: sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==} @@ -2533,9 +3050,18 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/tunnel@0.0.3': resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/vscode@1.109.0': resolution: {integrity: sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==} @@ -2543,6 +3069,9 @@ packages: resolution: {integrity: sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==} engines: {node: '>=20.0.0'} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@visx/axis@3.12.0': resolution: {integrity: sha512-8MoWpfuaJkhm2Yg+HwyytK8nk+zDugCqTT/tRmQX7gW4LYrHYLXFUXOzbDyyBakCVaUbUaAhVFxpMASJiQKf7A==} peerDependencies: @@ -2613,6 +3142,9 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@1.6.1': + resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -2630,15 +3162,27 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/runner@1.6.1': + resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/snapshot@1.6.1': + resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/spy@1.6.1': + resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/utils@1.6.1': + resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -2715,6 +3259,15 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -2751,10 +3304,21 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-sequence-parser@1.1.3: + resolution: {integrity: sha512-+fksAx9eG3Ab6LDnLs3ZqZa8KVJ/jYnX+D4Qe1azX+LFGFAXqynCQLOdLpNYN/l9e7l6hMWwZbrnctqr6eSQSw==} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -2762,11 +3326,20 @@ packages: aproba@2.1.0: resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + arg@1.0.0: + resolution: {integrity: sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2789,6 +3362,9 @@ packages: asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2797,6 +3373,10 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} @@ -2816,6 +3396,9 @@ packages: azure-devops-node-api@12.5.0: resolution: {integrity: sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@0.4.2: resolution: {integrity: sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==} @@ -2920,6 +3503,10 @@ packages: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2939,10 +3526,21 @@ packages: caniuse-lite@1.0.30001780: resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chalk@2.3.0: + resolution: {integrity: sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==} + engines: {node: '>=4'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2951,6 +3549,21 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -2983,10 +3596,24 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clipboardy@1.2.2: + resolution: {integrity: sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==} + engines: {node: '>=4'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + cockatiel@3.2.1: resolution: {integrity: sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==} engines: {node: '>=16'} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2995,6 +3622,9 @@ packages: resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} engines: {node: '>=14.6'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -3018,6 +3648,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -3029,9 +3662,23 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -3042,9 +3689,15 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3059,14 +3712,42 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.2: + resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + d3-array@3.2.1: resolution: {integrity: sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==} engines: {node: '>=12'} + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + d3-delaunay@6.0.2: resolution: {integrity: sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==} engines: {node: '>=12'} @@ -3079,10 +3760,23 @@ packages: resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} engines: {node: '>=12'} + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} @@ -3091,6 +3785,10 @@ packages: resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} engines: {node: '>=12'} + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} @@ -3098,6 +3796,29 @@ packages: d3-path@1.0.9: resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} @@ -3109,6 +3830,10 @@ packages: d3-shape@1.3.7: resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + d3-time-format@4.1.0: resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} engines: {node: '>=12'} @@ -3131,6 +3856,16 @@ packages: resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} engines: {node: '>=12'} + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3140,10 +3875,17 @@ packages: supports-color: optional: true + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -3178,6 +3920,10 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -3185,6 +3931,17 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@5.2.2: + resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} + engines: {node: '>=0.3.1'} + discontinuous-range@1.0.0: resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} @@ -3198,6 +3955,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.4.0: + resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -3224,6 +3984,9 @@ packages: electron-to-chromium@1.5.321: resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==} + elkjs@0.9.3: + resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3284,6 +4047,11 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -3303,6 +4071,40 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-util-attach-comments@2.1.1: + resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} + + estree-util-build-jsx@2.2.2: + resolution: {integrity: sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==} + + estree-util-is-identifier-name@2.1.0: + resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-to-js@1.2.0: + resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==} + + estree-util-value-to-estree@3.5.0: + resolution: {integrity: sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==} + + estree-util-visit@1.2.1: + resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -3314,6 +4116,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + execa@0.8.0: + resolution: {integrity: sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==} + engines: {node: '>=4'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -3329,6 +4139,10 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -3390,9 +4204,15 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + flexsearch@0.7.43: + resolution: {integrity: sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==} + fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + focus-visible@5.2.1: + resolution: {integrity: sha512-8Bx950VD1bWTQJEH/AM6SpEk+SU55aVnp4Ujhuuxy3eMEBCRwBnTBnVXr9YAPvZL3/CNjCa8u4IWfNmEO53whA==} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -3460,6 +4280,9 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -3472,12 +4295,29 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + git-up@7.0.0: + resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + + git-url-parse@13.1.1: + resolution: {integrity: sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -3521,10 +4361,18 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} + has-flag@2.0.0: + resolution: {integrity: sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==} + engines: {node: '>=0.10.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3540,10 +4388,50 @@ packages: has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hash-obj@4.0.0: + resolution: {integrity: sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==} + engines: {node: '>=12'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-estree@2.3.3: + resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -3559,6 +4447,9 @@ packages: html-entities@2.6.0: resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -3581,6 +4472,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -3624,14 +4519,37 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. + ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -3642,6 +4560,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3654,6 +4576,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -3666,13 +4591,35 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + is-ssh@1.4.1: + resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -3712,6 +4659,10 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -3748,12 +4699,30 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + katex@0.16.45: + resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} + hasBin: true + keytar@7.9.0: resolution: {integrity: sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3761,8 +4730,19 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} @@ -3798,10 +4778,16 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -3812,6 +4798,9 @@ packages: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3834,10 +4823,20 @@ packages: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + markdown-extensions@1.1.1: + resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} + engines: {node: '>=0.10.0'} + markdown-it@14.1.1: resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + match-sorter@6.3.4: + resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} + math-expression-evaluator@1.4.0: resolution: {integrity: sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==} @@ -3845,13 +4844,199 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-definitions@5.1.2: + resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} + + mdast-util-find-and-replace@2.2.2: + resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} + + mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + + mdast-util-gfm-autolink-literal@1.0.3: + resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} + + mdast-util-gfm-footnote@1.0.2: + resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==} + + mdast-util-gfm-strikethrough@1.0.3: + resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==} + + mdast-util-gfm-table@1.0.7: + resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==} + + mdast-util-gfm-task-list-item@1.0.2: + resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==} + + mdast-util-gfm@2.0.2: + resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} + + mdast-util-math@2.0.2: + resolution: {integrity: sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==} + + mdast-util-mdx-expression@1.3.2: + resolution: {integrity: sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==} + + mdast-util-mdx-jsx@2.1.4: + resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==} + + mdast-util-mdx@2.0.1: + resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==} + + mdast-util-mdxjs-esm@1.3.1: + resolution: {integrity: sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==} + + mdast-util-phrasing@3.0.1: + resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} + + mdast-util-to-hast@12.3.0: + resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@1.5.0: + resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} + + mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@10.9.5: + resolution: {integrity: sha512-eRlKEjzak4z1rcXeCd1OAlyawhrptClQDo8OuI8n6bSVqJ9oMfd5Lrf3Q+TdJHewi/9AIOc3UmEo8Fz+kNzzuQ==} + + micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + + micromark-extension-gfm-autolink-literal@1.0.5: + resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} + + micromark-extension-gfm-footnote@1.1.2: + resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} + + micromark-extension-gfm-strikethrough@1.0.7: + resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} + + micromark-extension-gfm-table@1.0.7: + resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} + + micromark-extension-gfm-tagfilter@1.0.2: + resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} + + micromark-extension-gfm-task-list-item@1.0.5: + resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} + + micromark-extension-gfm@2.0.3: + resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} + + micromark-extension-math@2.1.2: + resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} + + micromark-extension-mdx-expression@1.0.8: + resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==} + + micromark-extension-mdx-jsx@1.0.5: + resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==} + + micromark-extension-mdx-md@1.0.1: + resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==} + + micromark-extension-mdxjs-esm@1.0.5: + resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==} + + micromark-extension-mdxjs@1.0.1: + resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==} + + micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} + + micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} + + micromark-factory-mdx-expression@1.0.9: + resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==} + + micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + + micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} + + micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} + + micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + + micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} + + micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + + micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + + micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + + micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@1.2.3: + resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} + + micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} + + micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} + + micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} + + micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + + micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3874,6 +5059,10 @@ packages: engines: {node: '>=10.0.0'} hasBin: true + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -3947,6 +5136,9 @@ packages: engines: {node: '>=10'} hasBin: true + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + moment-timezone@0.5.48: resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} @@ -3956,6 +5148,10 @@ packages: moo@0.5.3: resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3994,6 +5190,62 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + next-mdx-remote@4.4.1: + resolution: {integrity: sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==} + engines: {node: '>=14', npm: '>=7'} + peerDependencies: + react: '>=16.x <=18.x' + react-dom: '>=16.x <=18.x' + + next-seo@6.8.0: + resolution: {integrity: sha512-zcxaV67PFXCSf8e6SXxbxPaOTgc8St/esxfsYXfQXMM24UESUVSXFm7f2A9HMkAwa0Gqn4s64HxYZAGfdF4Vhg==} + peerDependencies: + next: ^8.1.1-canary.54 || >=9.0.0 + react: '>=16.0.0' + react-dom: '>=16.0.0' + + next-themes@0.2.1: + resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} + peerDependencies: + next: '*' + react: '*' + react-dom: '*' + + next@14.2.5: + resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} + engines: {node: '>=18.17.0'} + deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details. + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + + nextra-theme-docs@2.13.4: + resolution: {integrity: sha512-2XOoMfwBCTYBt8ds4ZHftt9Wyf2XsykiNo02eir/XEYB+sGeUoE77kzqfidjEOKCSzOHYbK9BDMcg2+B/2vYRw==} + peerDependencies: + next: '>=9.5.3' + nextra: 2.13.4 + react: '>=16.13.1' + react-dom: '>=16.13.1' + + nextra@2.13.4: + resolution: {integrity: sha512-7of2rSBxuUa3+lbMmZwG9cqgftcoNOVQLTT6Rxf3EhBR9t1EI7b43dted8YoqSNaigdE3j1CoyNkX8N/ZzlEpw==} + engines: {node: '>=16'} + peerDependencies: + next: '>=9.5.3' + react: '>=16.13.1' + react-dom: '>=16.13.1' + node-abi@3.87.0: resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} engines: {node: '>=10'} @@ -4029,6 +5281,9 @@ packages: resolution: {integrity: sha512-jVe6Z61gPcPjCElPZ6j8llB3wnqGcuQzefim1ERsqIakxnEy5JlzV7XKdO1KmacRG5TKwPc4vJTgSRQ0LfkbFw==} engines: {node: '>=8'} + non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -4043,6 +5298,18 @@ packages: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm-to-yarn@2.2.1: + resolution: {integrity: sha512-O/j/ROyX0KGLG7O6Ieut/seQ0oiTpHF2tXAcFbpdTLQFiaNtkyTXXocM1fwpaa60dg1qpWj0nHlbNhx6qwuENQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -4065,6 +5332,10 @@ packages: one-time@1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} @@ -4073,10 +5344,18 @@ packages: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} @@ -4088,17 +5367,29 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@8.3.0: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} + parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + parse-passwd@1.0.0: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} + parse-path@7.1.0: + resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} + parse-semver@1.1.1: resolution: {integrity: sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==} + parse-url@8.1.0: + resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + parse5-htmlparser2-tree-adapter@7.1.0: resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} @@ -4116,10 +5407,18 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -4132,9 +5431,15 @@ packages: resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} engines: {node: '>=18'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} @@ -4142,6 +5447,9 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} @@ -4187,6 +5495,9 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pluralize@2.0.0: resolution: {integrity: sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==} @@ -4194,6 +5505,10 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -4225,6 +5540,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -4244,9 +5563,21 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + protocols@2.0.2: + resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -4286,6 +5617,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -4349,12 +5683,48 @@ packages: resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + reading-time@1.5.0: + resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} + reduce-css-calc@1.3.0: resolution: {integrity: sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==} reduce-function-call@1.0.3: resolution: {integrity: sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==} + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + + rehype-pretty-code@0.9.11: + resolution: {integrity: sha512-Eq90eCYXQJISktfRZ8PPtwc5SUyH6fJcxS8XOMnHPUQZBtC6RYo67gGlley9X2nR8vlniPj0/7oCDEYHKQa/oA==} + engines: {node: '>=16'} + peerDependencies: + shiki: '*' + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + remark-gfm@3.0.1: + resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} + + remark-math@5.1.1: + resolution: {integrity: sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==} + + remark-mdx@2.3.0: + resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} + + remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} + + remark-reading-time@2.1.0: + resolution: {integrity: sha512-gBsJbQv87TUq4dRMSOgIX6P60Tk9ke8c29KsL7bccmsv2m9AycDfVu3ghRtrNpHLZU3TE5P/vImGOMSPzYU8rA==} + + remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -4405,6 +5775,13 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4422,11 +5799,18 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + secretlint@10.2.2: resolution: {integrity: sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==} engines: {node: '>=20.0.0'} hasBin: true + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4443,14 +5827,25 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@0.14.7: + resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -4486,6 +5881,10 @@ packages: simple-lru-cache@0.0.2: resolution: {integrity: sha512-uEv/AFO0ADI7d99OHDmh1QfYzQk/izT1vCmu/riQfh7qjBVUUgRT87E5s5h7CxWCA/+YoZerykpEthzVrW3LIw==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -4511,6 +5910,10 @@ packages: resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sort-keys@5.1.0: + resolution: {integrity: sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==} + engines: {node: '>=12'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4522,6 +5925,13 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -4538,6 +5948,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -4568,6 +5981,10 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4579,6 +5996,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4587,10 +6007,25 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-literal@2.1.1: + resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -4609,6 +6044,29 @@ packages: style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + style-to-object@0.4.4: + resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + + styled-jsx@5.1.1: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + supports-color@4.5.0: + resolution: {integrity: sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4682,6 +6140,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4690,10 +6152,22 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} + title@3.5.3: + resolution: {integrity: sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==} + hasBin: true + + titleize@1.0.0: + resolution: {integrity: sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw==} + engines: {node: '>=0.10.0'} + tmp@0.2.5: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} @@ -4708,10 +6182,20 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -4761,6 +6245,14 @@ packages: resolution: {integrity: sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ==} hasBin: true + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -4768,6 +6260,11 @@ packages: typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + typescript@5.5.3: + resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4776,9 +6273,15 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + underscore@1.13.8: resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -4794,6 +6297,9 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + unique-filename@2.0.1: resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -4802,6 +6308,54 @@ packages: resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-generated@2.0.1: + resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} + + unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@1.1.2: + resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} + + unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@4.0.2: + resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} + + unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -4851,6 +6405,11 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -4858,32 +6417,50 @@ packages: resolution: {integrity: sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==} engines: {node: '>=4'} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-matter@3.0.1: + resolution: {integrity: sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==} + + vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-node@1.6.1: + resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' + '@types/node': ^18.0.0 || >=20.0.0 less: '*' lightningcss: ^1.21.0 sass: '*' sass-embedded: '*' stylus: '*' sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 + terser: ^5.4.0 peerDependenciesMeta: '@types/node': optional: true - jiti: - optional: true less: optional: true lightningcss: @@ -4898,23 +6475,84 @@ packages: optional: true terser: optional: true - tsx: - optional: true - yaml: - optional: true - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@1.6.1: + resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.1 + '@vitest/ui': 1.6.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -4952,9 +6590,21 @@ packages: resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} hasBin: true + vscode-oniguruma@1.7.0: + resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + + vscode-textmate@8.0.0: + resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -4970,6 +6620,10 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -5018,6 +6672,9 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -5043,6 +6700,13 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand@4.5.7: resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} engines: {node: '>=12.7.0'} @@ -5076,6 +6740,9 @@ packages: use-sync-external-store: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@aws-crypto/crc32@5.2.0': @@ -6152,6 +7819,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -6175,6 +7844,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@braintree/sanitize-url@6.0.4': {} + '@codemirror/autocomplete@6.20.1': dependencies: '@codemirror/language': 6.12.2 @@ -6245,6 +7916,9 @@ snapshots: '@dagrejs/graphlib@4.0.1': {} + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -6254,6 +7928,9 @@ snapshots: '@esbuild/aix-ppc64@0.27.3': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.24.2': optional: true @@ -6263,6 +7940,9 @@ snapshots: '@esbuild/android-arm64@0.27.3': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.24.2': optional: true @@ -6272,6 +7952,9 @@ snapshots: '@esbuild/android-arm@0.27.3': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.24.2': optional: true @@ -6281,6 +7964,9 @@ snapshots: '@esbuild/android-x64@0.27.3': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.24.2': optional: true @@ -6290,6 +7976,9 @@ snapshots: '@esbuild/darwin-arm64@0.27.3': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.24.2': optional: true @@ -6299,6 +7988,9 @@ snapshots: '@esbuild/darwin-x64@0.27.3': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.24.2': optional: true @@ -6308,6 +8000,9 @@ snapshots: '@esbuild/freebsd-arm64@0.27.3': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.24.2': optional: true @@ -6317,6 +8012,9 @@ snapshots: '@esbuild/freebsd-x64@0.27.3': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.24.2': optional: true @@ -6326,6 +8024,9 @@ snapshots: '@esbuild/linux-arm64@0.27.3': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.24.2': optional: true @@ -6335,6 +8036,9 @@ snapshots: '@esbuild/linux-arm@0.27.3': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.24.2': optional: true @@ -6344,6 +8048,9 @@ snapshots: '@esbuild/linux-ia32@0.27.3': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.24.2': optional: true @@ -6353,6 +8060,9 @@ snapshots: '@esbuild/linux-loong64@0.27.3': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.24.2': optional: true @@ -6362,6 +8072,9 @@ snapshots: '@esbuild/linux-mips64el@0.27.3': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.24.2': optional: true @@ -6371,6 +8084,9 @@ snapshots: '@esbuild/linux-ppc64@0.27.3': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.24.2': optional: true @@ -6380,6 +8096,9 @@ snapshots: '@esbuild/linux-riscv64@0.27.3': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.24.2': optional: true @@ -6389,6 +8108,9 @@ snapshots: '@esbuild/linux-s390x@0.27.3': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.24.2': optional: true @@ -6407,6 +8129,9 @@ snapshots: '@esbuild/netbsd-arm64@0.27.3': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.24.2': optional: true @@ -6425,6 +8150,9 @@ snapshots: '@esbuild/openbsd-arm64@0.27.3': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.24.2': optional: true @@ -6440,6 +8168,9 @@ snapshots: '@esbuild/openharmony-arm64@0.27.3': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.24.2': optional: true @@ -6449,6 +8180,9 @@ snapshots: '@esbuild/sunos-x64@0.27.3': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.24.2': optional: true @@ -6458,6 +8192,9 @@ snapshots: '@esbuild/win32-arm64@0.27.3': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.24.2': optional: true @@ -6467,6 +8204,9 @@ snapshots: '@esbuild/win32-ia32@0.27.3': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.24.2': optional: true @@ -6559,6 +8299,13 @@ snapshots: - encoding - supports-color + '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/react-virtual': 3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + client-only: 0.0.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -6574,6 +8321,10 @@ snapshots: dependencies: minipass: 7.1.3 + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6626,6 +8377,126 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} + '@mdx-js/mdx@2.3.0': + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/mdx': 2.0.13 + estree-util-build-jsx: 2.2.2 + estree-util-is-identifier-name: 2.1.0 + estree-util-to-js: 1.2.0 + estree-walker: 3.0.3 + hast-util-to-estree: 2.3.3 + markdown-extensions: 1.1.1 + periscopic: 3.1.0 + remark-mdx: 2.3.0 + remark-parse: 10.0.2 + remark-rehype: 10.1.0 + unified: 10.1.2 + unist-util-position-from-estree: 1.1.2 + unist-util-stringify-position: 3.0.3 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + transitivePeerDependencies: + - supports-color + + '@mdx-js/react@2.3.0(react@18.3.1)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 18.3.3 + react: 18.3.1 + + '@napi-rs/simple-git-android-arm-eabi@0.1.22': + optional: true + + '@napi-rs/simple-git-android-arm64@0.1.22': + optional: true + + '@napi-rs/simple-git-darwin-arm64@0.1.22': + optional: true + + '@napi-rs/simple-git-darwin-x64@0.1.22': + optional: true + + '@napi-rs/simple-git-freebsd-x64@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-arm-gnueabihf@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-arm64-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-arm64-musl@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-ppc64-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-s390x-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-x64-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-x64-musl@0.1.22': + optional: true + + '@napi-rs/simple-git-win32-arm64-msvc@0.1.22': + optional: true + + '@napi-rs/simple-git-win32-ia32-msvc@0.1.22': + optional: true + + '@napi-rs/simple-git-win32-x64-msvc@0.1.22': + optional: true + + '@napi-rs/simple-git@0.1.22': + optionalDependencies: + '@napi-rs/simple-git-android-arm-eabi': 0.1.22 + '@napi-rs/simple-git-android-arm64': 0.1.22 + '@napi-rs/simple-git-darwin-arm64': 0.1.22 + '@napi-rs/simple-git-darwin-x64': 0.1.22 + '@napi-rs/simple-git-freebsd-x64': 0.1.22 + '@napi-rs/simple-git-linux-arm-gnueabihf': 0.1.22 + '@napi-rs/simple-git-linux-arm64-gnu': 0.1.22 + '@napi-rs/simple-git-linux-arm64-musl': 0.1.22 + '@napi-rs/simple-git-linux-ppc64-gnu': 0.1.22 + '@napi-rs/simple-git-linux-s390x-gnu': 0.1.22 + '@napi-rs/simple-git-linux-x64-gnu': 0.1.22 + '@napi-rs/simple-git-linux-x64-musl': 0.1.22 + '@napi-rs/simple-git-win32-arm64-msvc': 0.1.22 + '@napi-rs/simple-git-win32-ia32-msvc': 0.1.22 + '@napi-rs/simple-git-win32-x64-msvc': 0.1.22 + + '@next/env@14.2.5': {} + + '@next/swc-darwin-arm64@14.2.5': + optional: true + + '@next/swc-darwin-x64@14.2.5': + optional: true + + '@next/swc-linux-arm64-gnu@14.2.5': + optional: true + + '@next/swc-linux-arm64-musl@14.2.5': + optional: true + + '@next/swc-linux-x64-gnu@14.2.5': + optional: true + + '@next/swc-linux-x64-musl@14.2.5': + optional: true + + '@next/swc-win32-arm64-msvc@14.2.5': + optional: true + + '@next/swc-win32-ia32-msvc@14.2.5': + optional: true + + '@next/swc-win32-x64-msvc@14.2.5': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6653,6 +8524,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@popperjs/core@2.11.8': {} + '@radix-ui/primitive@1.1.3': {} '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -7118,6 +8991,8 @@ snapshots: '@secretlint/types@10.2.2': {} + '@sinclair/typebox@0.27.10': {} + '@sindresorhus/merge-streams@2.3.0': {} '@smithy/abort-controller@1.1.0': @@ -7756,6 +9631,64 @@ snapshots: color: 5.0.3 text-hex: 1.0.0 + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.8.1 + + '@tanstack/react-virtual@3.13.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.14.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/virtual-core@3.14.0': {} + + '@tauri-apps/cli-darwin-arm64@2.1.0': + optional: true + + '@tauri-apps/cli-darwin-x64@2.1.0': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.1.0': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.1.0': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.1.0': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.1.0': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.1.0': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.1.0': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.1.0': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.1.0': + optional: true + + '@tauri-apps/cli@2.1.0': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.1.0 + '@tauri-apps/cli-darwin-x64': 2.1.0 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.1.0 + '@tauri-apps/cli-linux-arm64-gnu': 2.1.0 + '@tauri-apps/cli-linux-arm64-musl': 2.1.0 + '@tauri-apps/cli-linux-x64-gnu': 2.1.0 + '@tauri-apps/cli-linux-x64-musl': 2.1.0 + '@tauri-apps/cli-win32-arm64-msvc': 2.1.0 + '@tauri-apps/cli-win32-ia32-msvc': 2.1.0 + '@tauri-apps/cli-win32-x64-msvc': 2.1.0 + '@techteamer/ocsp@1.0.1': dependencies: asn1.js: 5.4.1 @@ -7795,8 +9728,25 @@ snapshots: dependencies: '@textlint/ast-node-types': 15.5.2 + '@theguild/remark-mermaid@0.0.5(react@18.3.1)': + dependencies: + mermaid: 10.9.5 + react: 18.3.1 + unist-util-visit: 5.1.0 + transitivePeerDependencies: + - supports-color + + '@theguild/remark-npm2yarn@0.2.1': + dependencies: + npm-to-yarn: 2.2.1 + unist-util-visit: 5.1.0 + '@tootallnate/once@2.0.0': {} + '@types/acorn@4.0.6': + dependencies: + '@types/estree': 1.0.8 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.2 @@ -7855,10 +9805,16 @@ snapshots: '@types/d3-path@1.0.11': {} + '@types/d3-scale-chromatic@3.1.0': {} + '@types/d3-scale@4.0.2': dependencies: '@types/d3-time': 3.0.0 + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.0 + '@types/d3-selection@3.0.11': {} '@types/d3-shape@1.3.12': @@ -7881,20 +9837,42 @@ snapshots: '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 - optional: true '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + '@types/estree@1.0.8': {} '@types/geojson@7946.0.16': {} + '@types/hast@2.3.10': + dependencies: + '@types/unist': 2.0.11 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/js-yaml@4.0.9': {} + '@types/katex@0.16.8': {} + '@types/lodash@4.17.23': {} - '@types/ms@2.1.0': - optional: true + '@types/mdast@3.0.15': + dependencies: + '@types/unist': 2.0.11 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} '@types/mssql@9.1.9(@azure/core-client@1.10.1)': dependencies: @@ -7907,9 +9885,13 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 22.19.11 + '@types/node': 20.14.10 form-data: 4.0.5 + '@types/node@20.14.10': + dependencies: + undici-types: 5.26.5 + '@types/node@22.19.11': dependencies: undici-types: 6.21.0 @@ -7930,19 +9912,28 @@ snapshots: dependencies: '@types/react': 18.3.28 + '@types/react-dom@18.3.7(@types/react@18.3.3)': + dependencies: + '@types/react': 18.3.3 + '@types/react@18.3.28': dependencies: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + '@types/readable-stream@4.0.23': dependencies: - '@types/node': 22.19.11 + '@types/node': 20.14.10 '@types/request@2.48.13': dependencies: '@types/caseless': 0.12.5 - '@types/node': 22.19.11 + '@types/node': 20.14.10 '@types/tough-cookie': 4.0.5 form-data: 2.5.5 @@ -7952,9 +9943,16 @@ snapshots: '@types/triple-beam@1.3.5': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/tunnel@0.0.3': dependencies: - '@types/node': 22.19.11 + '@types/node': 20.14.10 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} '@types/vscode@1.109.0': {} @@ -7966,9 +9964,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@ungap/structured-clone@1.3.0': {} + '@visx/axis@3.12.0(react@18.3.1)': dependencies: - '@types/react': 18.3.28 + '@types/react': 18.3.3 '@visx/group': 3.12.0(react@18.3.1) '@visx/point': 3.12.0 '@visx/scale': 3.12.0 @@ -7980,8 +9980,8 @@ snapshots: '@visx/bounds@3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@types/react': 18.3.3 + '@types/react-dom': 18.3.7(@types/react@18.3.3) prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -7993,13 +9993,13 @@ snapshots: '@visx/gradient@3.12.0(react@18.3.1)': dependencies: - '@types/react': 18.3.28 + '@types/react': 18.3.3 prop-types: 15.8.1 react: 18.3.1 '@visx/grid@3.12.0(react@18.3.1)': dependencies: - '@types/react': 18.3.28 + '@types/react': 18.3.3 '@visx/curve': 3.12.0 '@visx/group': 3.12.0(react@18.3.1) '@visx/point': 3.12.0 @@ -8011,14 +10011,14 @@ snapshots: '@visx/group@3.12.0(react@18.3.1)': dependencies: - '@types/react': 18.3.28 + '@types/react': 18.3.3 classnames: 2.5.1 prop-types: 15.8.1 react: 18.3.1 '@visx/legend@3.12.0(react@18.3.1)': dependencies: - '@types/react': 18.3.28 + '@types/react': 18.3.3 '@visx/group': 3.12.0(react@18.3.1) '@visx/scale': 3.12.0 classnames: 2.5.1 @@ -8030,7 +10030,7 @@ snapshots: '@visx/responsive@3.12.0(react@18.3.1)': dependencies: '@types/lodash': 4.17.23 - '@types/react': 18.3.28 + '@types/react': 18.3.3 lodash: 4.17.23 prop-types: 15.8.1 react: 18.3.1 @@ -8044,7 +10044,7 @@ snapshots: '@types/d3-path': 1.0.11 '@types/d3-shape': 1.3.12 '@types/lodash': 4.17.23 - '@types/react': 18.3.28 + '@types/react': 18.3.3 '@visx/curve': 3.12.0 '@visx/group': 3.12.0(react@18.3.1) '@visx/scale': 3.12.0 @@ -8058,7 +10058,7 @@ snapshots: '@visx/text@3.12.0(react@18.3.1)': dependencies: '@types/lodash': 4.17.23 - '@types/react': 18.3.28 + '@types/react': 18.3.3 classnames: 2.5.1 lodash: 4.17.23 prop-types: 15.8.1 @@ -8067,7 +10067,7 @@ snapshots: '@visx/tooltip@3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@types/react': 18.3.28 + '@types/react': 18.3.3 '@visx/bounds': 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 prop-types: 15.8.1 @@ -8109,6 +10109,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@1.6.1': + dependencies: + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + chai: 4.5.0 + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -8129,22 +10135,45 @@ snapshots: dependencies: tinyrainbow: 2.0.0 + '@vitest/runner@1.6.1': + dependencies: + '@vitest/utils': 1.6.1 + p-limit: 5.0.0 + pathe: 1.1.2 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.1.0 + '@vitest/snapshot@1.6.1': + dependencies: + magic-string: 0.30.21 + pathe: 1.1.2 + pretty-format: 29.7.0 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@1.6.1': + dependencies: + tinyspy: 2.2.1 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/utils@1.6.1': + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -8257,8 +10286,15 @@ snapshots: dependencies: event-target-shim: 5.0.1 - acorn@8.16.0: - optional: true + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} agent-base@6.0.2: dependencies: @@ -8292,19 +10328,35 @@ snapshots: ansi-regex@6.2.2: {} + ansi-sequence-parser@1.1.3: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} aproba@2.1.0: {} + arch@2.2.0: {} + are-we-there-yet@3.0.1: dependencies: delegates: 1.0.0 readable-stream: 3.6.2 + arg@1.0.0: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -8329,10 +10381,14 @@ snapshots: minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 + assertion-error@1.1.0: {} + assertion-error@2.0.1: {} astral-regex@2.0.0: {} + astring@1.9.0: {} + async-retry@1.3.3: dependencies: retry: 0.13.1 @@ -8356,6 +10412,8 @@ snapshots: tunnel: 0.0.6 typed-rest-client: 1.8.11 + bail@2.0.2: {} + balanced-match@0.4.2: {} balanced-match@1.0.2: {} @@ -8458,6 +10516,10 @@ snapshots: dependencies: run-applescript: 7.1.0 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + cac@6.7.14: {} cacache@16.1.3: @@ -8495,6 +10557,18 @@ snapshots: caniuse-lite@1.0.30001780: {} + ccount@2.0.1: {} + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -8503,6 +10577,12 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chalk@2.3.0: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 4.5.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -8510,6 +10590,18 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + check-error@2.1.3: {} cheerio-select@2.1.0: @@ -8547,8 +10639,21 @@ snapshots: clean-stack@2.2.0: {} + client-only@0.0.1: {} + + clipboardy@1.2.2: + dependencies: + arch: 2.2.0 + execa: 0.8.0 + + clsx@2.1.1: {} + cockatiel@3.2.1: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -8557,6 +10662,8 @@ snapshots: dependencies: color-name: 2.1.0 + color-name@1.1.3: {} + color-name@1.1.4: {} color-name@2.1.0: {} @@ -8576,22 +10683,42 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} commander@12.1.0: {} commander@2.20.3: {} + commander@7.2.0: {} + + commander@8.3.0: {} + + compute-scroll-into-view@3.1.1: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} + consola@3.4.2: {} console-control-strings@1.1.0: {} convert-source-map@2.0.0: {} + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + crelt@1.0.6: {} + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -8610,12 +10737,41 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.2 + + cytoscape@3.33.2: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + d3-array@3.2.1: dependencies: internmap: 2.0.3 + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.1 + d3-delaunay@6.0.2: dependencies: delaunator: 5.0.1 @@ -8627,20 +10783,56 @@ snapshots: d3-dispatch: 3.0.1 d3-selection: 3.0.0 + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-format@3.1.0: {} d3-geo@3.1.0: dependencies: d3-array: 3.2.1 + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 d3-path@1.0.9: {} + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + d3-scale@4.0.2: dependencies: d3-array: 3.2.1 @@ -8655,6 +10847,10 @@ snapshots: dependencies: d3-path: 1.0.9 + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + d3-time-format@4.1.0: dependencies: d3-time: 3.1.0 @@ -8682,14 +10878,62 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) + d3@7.9.0: + dependencies: + d3-array: 3.2.1 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.2 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + + dayjs@1.11.20: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + deep-eql@5.0.2: {} deep-extend@0.6.0: {} @@ -8713,10 +10957,20 @@ snapshots: denque@2.1.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff-sequences@29.6.3: {} + + diff@5.2.2: {} + discontinuous-range@1.0.0: {} dom-serializer@2.0.0: @@ -8731,6 +10985,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.4.0: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -8772,6 +11030,8 @@ snapshots: electron-to-chromium@1.5.321: {} + elkjs@0.9.3: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -8821,6 +11081,32 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -8909,6 +11195,41 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + + escape-string-regexp@5.0.0: {} + + esprima@4.0.1: {} + + estree-util-attach-comments@2.1.1: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@2.2.2: + dependencies: + '@types/estree-jsx': 1.0.5 + estree-util-is-identifier-name: 2.1.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@2.1.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-to-js@1.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-value-to-estree@3.5.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-visit@1.2.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 2.0.11 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -8917,6 +11238,28 @@ snapshots: events@3.3.0: {} + execa@0.8.0: + dependencies: + cross-spawn: 5.1.0 + get-stream: 3.0.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + expand-template@2.0.3: {} expand-tilde@2.0.2: @@ -8927,6 +11270,10 @@ snapshots: exponential-backoff@3.1.3: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -8985,8 +11332,12 @@ snapshots: dependencies: to-regex-range: 5.0.1 + flexsearch@0.7.43: {} + fn.name@1.1.0: {} + focus-visible@5.2.1: {} + follow-redirects@1.15.11: {} foreground-child@3.3.1: @@ -9069,6 +11420,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-func-name@2.0.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -9089,12 +11442,27 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@3.0.0: {} + + get-stream@8.0.1: {} + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 + git-up@7.0.0: + dependencies: + is-ssh: 1.4.1 + parse-url: 8.1.0 + + git-url-parse@13.1.1: + dependencies: + git-up: 7.0.0 + github-from-package@0.0.0: {} + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -9161,6 +11529,13 @@ snapshots: graceful-fs@4.2.11: {} + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + gtoken@7.1.0(encoding@0.1.13): dependencies: gaxios: 6.7.1(encoding@0.1.13) @@ -9169,6 +11544,8 @@ snapshots: - encoding - supports-color + has-flag@2.0.0: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -9179,10 +11556,120 @@ snapshots: has-unicode@2.0.1: {} + hash-obj@4.0.0: + dependencies: + is-obj: 3.0.0 + sort-keys: 5.1.0 + type-fest: 1.4.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@2.3.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/unist': 2.0.11 + comma-separated-tokens: 2.0.3 + estree-util-attach-comments: 2.1.1 + estree-util-is-identifier-name: 2.1.0 + hast-util-whitespace: 2.0.1 + mdast-util-mdx-expression: 1.3.2 + mdast-util-mdxjs-esm: 1.3.1 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + style-to-object: 0.4.4 + unist-util-position: 4.0.4 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@2.0.1: {} + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 @@ -9197,6 +11684,8 @@ snapshots: html-entities@2.6.0: {} + html-void-elements@3.0.0: {} + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 @@ -9235,6 +11724,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@5.0.0: {} + humanize-ms@1.2.1: dependencies: ms: 2.1.3 @@ -9268,14 +11759,33 @@ snapshots: ini@1.3.8: {} + inline-style-parser@0.1.1: {} + + internmap@1.0.1: {} + internmap@2.0.3: {} + intersection-observer@0.12.2: {} + ip-address@10.1.0: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-buffer@2.0.5: {} + + is-decimal@2.0.1: {} + is-docker@2.2.1: {} is-docker@3.0.0: {} + is-extendable@0.1.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -9284,6 +11794,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -9292,10 +11804,26 @@ snapshots: is-number@7.0.0: {} + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + is-property@1.0.2: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + is-ssh@1.4.1: + dependencies: + protocols: 2.0.2 + + is-stream@1.1.0: {} + is-stream@2.0.1: {} + is-stream@3.0.0: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -9333,6 +11861,11 @@ snapshots: js-tokens@9.0.1: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -9379,20 +11912,41 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 + katex@0.16.45: + dependencies: + commander: 8.3.0 + keytar@7.9.0: dependencies: node-addon-api: 4.3.0 prebuild-install: 7.1.3 optional: true + khroma@2.1.0: {} + + kind-of@6.0.3: {} + + kleur@4.1.5: {} + kuler@2.0.0: {} + layout-base@1.0.2: {} + leven@3.1.0: {} linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 + local-pkg@0.5.1: + dependencies: + mlly: 1.8.2 + pkg-types: 1.3.1 + + lodash-es@4.18.1: {} + + lodash.get@4.4.2: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -9424,16 +11978,27 @@ snapshots: long@5.3.2: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + loupe@3.2.1: {} lru-cache@10.4.3: {} lru-cache@11.2.6: {} + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -9472,6 +12037,8 @@ snapshots: - bluebird - supports-color + markdown-extensions@1.1.1: {} + markdown-it@14.1.1: dependencies: argparse: 2.0.1 @@ -9481,14 +12048,509 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 + markdown-table@3.0.4: {} + + match-sorter@6.3.4: + dependencies: + '@babel/runtime': 7.29.2 + remove-accents: 0.5.0 + math-expression-evaluator@1.4.0: {} math-intrinsics@1.1.0: {} + mdast-util-definitions@5.1.2: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + unist-util-visit: 4.1.2 + + mdast-util-find-and-replace@2.2.2: + dependencies: + '@types/mdast': 3.0.15 + escape-string-regexp: 5.0.0 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + + mdast-util-from-markdown@1.3.1: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + decode-named-character-reference: 1.3.0 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@1.0.3: + dependencies: + '@types/mdast': 3.0.15 + ccount: 2.0.1 + mdast-util-find-and-replace: 2.2.2 + micromark-util-character: 1.2.0 + + mdast-util-gfm-footnote@1.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + micromark-util-normalize-identifier: 1.1.0 + + mdast-util-gfm-strikethrough@1.0.3: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + + mdast-util-gfm-table@1.0.7: + dependencies: + '@types/mdast': 3.0.15 + markdown-table: 3.0.4 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@1.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + + mdast-util-gfm@2.0.2: + dependencies: + mdast-util-from-markdown: 1.3.1 + mdast-util-gfm-autolink-literal: 1.0.3 + mdast-util-gfm-footnote: 1.0.2 + mdast-util-gfm-strikethrough: 1.0.3 + mdast-util-gfm-table: 1.0.7 + mdast-util-gfm-task-list-item: 1.0.2 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-math@2.0.2: + dependencies: + '@types/mdast': 3.0.15 + longest-streak: 3.1.0 + mdast-util-to-markdown: 1.5.0 + + mdast-util-mdx-expression@1.3.2: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@2.1.4: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + ccount: 2.0.1 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-remove-position: 4.0.2 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@2.0.1: + dependencies: + mdast-util-from-markdown: 1.3.1 + mdast-util-mdx-expression: 1.3.2 + mdast-util-mdx-jsx: 2.1.4 + mdast-util-mdxjs-esm: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@1.3.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@3.0.1: + dependencies: + '@types/mdast': 3.0.15 + unist-util-is: 5.2.1 + + mdast-util-to-hast@12.3.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-definitions: 5.1.2 + micromark-util-sanitize-uri: 1.2.0 + trim-lines: 3.0.1 + unist-util-generated: 2.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@1.5.0: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + longest-streak: 3.1.0 + mdast-util-phrasing: 3.0.1 + mdast-util-to-string: 3.2.0 + micromark-util-decode-string: 1.1.0 + unist-util-visit: 4.1.2 + zwitch: 2.0.4 + + mdast-util-to-string@3.2.0: + dependencies: + '@types/mdast': 3.0.15 + mdurl@2.0.0: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} + mermaid@10.9.5: + dependencies: + '@braintree/sanitize-url': 6.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + cytoscape: 3.33.2 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.20 + dompurify: 3.4.0 + elkjs: 0.9.3 + katex: 0.16.45 + khroma: 2.1.0 + lodash-es: 4.18.1 + mdast-util-from-markdown: 1.3.1 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 9.0.1 + web-worker: 1.5.0 + transitivePeerDependencies: + - supports-color + + micromark-core-commonmark@1.1.0: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-autolink-literal@1.0.5: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-extension-gfm-footnote@1.1.2: + dependencies: + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-strikethrough@1.0.7: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-table@1.0.7: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-tagfilter@1.0.2: + dependencies: + micromark-util-types: 1.1.0 + + micromark-extension-gfm-task-list-item@1.0.5: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm@2.0.3: + dependencies: + micromark-extension-gfm-autolink-literal: 1.0.5 + micromark-extension-gfm-footnote: 1.1.2 + micromark-extension-gfm-strikethrough: 1.0.7 + micromark-extension-gfm-table: 1.0.7 + micromark-extension-gfm-tagfilter: 1.0.2 + micromark-extension-gfm-task-list-item: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-extension-math@2.1.2: + dependencies: + '@types/katex': 0.16.8 + katex: 0.16.45 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-mdx-expression@1.0.8: + dependencies: + '@types/estree': 1.0.8 + micromark-factory-mdx-expression: 1.0.9 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-mdx-jsx@1.0.5: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.8 + estree-util-is-identifier-name: 2.1.0 + micromark-factory-mdx-expression: 1.0.9 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-extension-mdx-md@1.0.1: + dependencies: + micromark-util-types: 1.1.0 + + micromark-extension-mdxjs-esm@1.0.5: + dependencies: + '@types/estree': 1.0.8 + micromark-core-commonmark: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-position-from-estree: 1.1.2 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-extension-mdxjs@1.0.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + micromark-extension-mdx-expression: 1.0.8 + micromark-extension-mdx-jsx: 1.0.5 + micromark-extension-mdx-md: 1.0.1 + micromark-extension-mdxjs-esm: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-destination@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-label@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-factory-mdx-expression@1.0.9: + dependencies: + '@types/estree': 1.0.8 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-position-from-estree: 1.1.2 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-factory-space@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 + + micromark-factory-title@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-whitespace@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-character@1.2.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-classify-character@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-combine-extensions@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-decode-numeric-character-reference@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-decode-string@1.1.0: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 + + micromark-util-encode@1.1.0: {} + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@1.2.3: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.8 + '@types/unist': 2.0.11 + estree-util-visit: 1.2.1 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-util-html-tag-name@1.2.0: {} + + micromark-util-normalize-identifier@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-resolve-all@1.1.0: + dependencies: + micromark-util-types: 1.1.0 + + micromark-util-sanitize-uri@1.2.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-util-symbol@1.1.0: {} + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@1.1.0: {} + + micromark-util-types@2.0.2: {} + + micromark@3.2.0: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -9504,6 +12566,8 @@ snapshots: mime@3.0.0: {} + mimic-fn@4.0.0: {} + mimic-response@3.1.0: {} minimalistic-assert@1.0.1: {} @@ -9571,6 +12635,13 @@ snapshots: mkdirp@1.0.4: {} + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + moment-timezone@0.5.48: dependencies: moment: 2.30.1 @@ -9579,6 +12650,8 @@ snapshots: moo@0.5.3: {} + mri@1.2.0: {} + ms@2.1.3: {} mssql@11.0.1(@azure/core-client@1.10.1): @@ -9625,6 +12698,109 @@ snapshots: negotiator@0.6.4: {} + next-mdx-remote@4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@mdx-js/mdx': 2.3.0 + '@mdx-js/react': 2.3.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + vfile: 5.3.7 + vfile-matter: 3.0.1 + transitivePeerDependencies: + - supports-color + + next-seo@6.8.0(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + next: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next-themes@0.2.1(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + next: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 14.2.5 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001780 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.5 + '@next/swc-darwin-x64': 14.2.5 + '@next/swc-linux-arm64-gnu': 14.2.5 + '@next/swc-linux-arm64-musl': 14.2.5 + '@next/swc-linux-x64-gnu': 14.2.5 + '@next/swc-linux-x64-musl': 14.2.5 + '@next/swc-win32-arm64-msvc': 14.2.5 + '@next/swc-win32-ia32-msvc': 14.2.5 + '@next/swc-win32-x64-msvc': 14.2.5 + '@opentelemetry/api': 1.9.0 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nextra-theme-docs@2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@popperjs/core': 2.11.8 + clsx: 2.1.1 + escape-string-regexp: 5.0.0 + flexsearch: 0.7.43 + focus-visible: 5.2.1 + git-url-parse: 13.1.1 + intersection-observer: 0.12.2 + match-sorter: 6.3.4 + next: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-seo: 6.8.0(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: 0.2.1(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: 2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 3.1.0 + zod: 3.25.76 + + nextra@2.13.4(next@14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 2.3.0 + '@mdx-js/react': 2.3.0(react@18.3.1) + '@napi-rs/simple-git': 0.1.22 + '@theguild/remark-mermaid': 0.0.5(react@18.3.1) + '@theguild/remark-npm2yarn': 0.2.1 + clsx: 2.1.1 + github-slugger: 2.0.0 + graceful-fs: 4.2.11 + gray-matter: 4.0.3 + katex: 0.16.45 + lodash.get: 4.4.2 + next: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + p-limit: 3.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + rehype-katex: 7.0.1 + rehype-pretty-code: 0.9.11(shiki@0.14.7) + rehype-raw: 7.0.0 + remark-gfm: 3.0.1 + remark-math: 5.1.1 + remark-reading-time: 2.1.0 + shiki: 0.14.7 + slash: 3.0.0 + title: 3.5.3 + unist-util-remove: 4.0.0 + unist-util-visit: 5.1.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + node-abi@3.87.0: dependencies: semver: 7.7.4 @@ -9669,6 +12845,8 @@ snapshots: '@types/pegjs': 0.10.6 big-integer: 1.6.52 + non-layered-tidy-tree-layout@2.0.2: {} + nopt@6.0.0: dependencies: abbrev: 1.1.1 @@ -9683,6 +12861,16 @@ snapshots: semver: 7.7.4 validate-npm-package-license: 3.0.4 + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-to-yarn@2.2.1: {} + npmlog@6.0.2: dependencies: are-we-there-yet: 3.0.1 @@ -9706,6 +12894,10 @@ snapshots: dependencies: fn.name: 1.1.0 + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + open@10.2.0: dependencies: default-browser: 5.5.0 @@ -9718,10 +12910,16 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + p-finally@1.0.0: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-limit@5.0.0: + dependencies: + yocto-queue: 1.2.2 + p-map@4.0.0: dependencies: aggregate-error: 3.1.0 @@ -9730,18 +12928,38 @@ snapshots: package-json-from-dist@1.0.1: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@8.3.0: dependencies: '@babel/code-frame': 7.29.0 index-to-position: 1.2.0 type-fest: 4.41.0 + parse-numeric-range@1.3.0: {} + parse-passwd@1.0.0: {} + parse-path@7.1.0: + dependencies: + protocols: 2.0.2 + parse-semver@1.1.1: dependencies: semver: 5.7.2 + parse-url@8.1.0: + dependencies: + parse-path: 7.1.0 + parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 @@ -9759,8 +12977,12 @@ snapshots: path-is-absolute@1.0.1: {} + path-key@2.0.1: {} + path-key@3.1.1: {} + path-key@4.0.0: {} + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -9773,12 +12995,22 @@ snapshots: path-type@6.0.0: {} + pathe@1.1.2: {} + pathe@2.0.3: {} + pathval@1.1.1: {} + pathval@2.0.1: {} pend@1.2.0: {} + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.8 + estree-walker: 3.0.3 + is-reference: 3.0.3 + pg-cloudflare@1.3.0: optional: true @@ -9820,10 +13052,22 @@ snapshots: picomatch@4.0.3: {} + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + pluralize@2.0.0: {} pluralize@8.0.0: {} + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -9857,6 +13101,12 @@ snapshots: prettier@3.8.1: {} + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + process@0.11.10: {} promise-inflight@1.0.1: {} @@ -9872,8 +13122,16 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@6.5.0: {} + + property-information@7.1.0: {} + + protocols@2.0.2: {} + proxy-from-env@1.1.0: {} + pseudomap@1.0.2: {} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -9922,6 +13180,8 @@ snapshots: react-is@16.13.1: {} + react-is@18.3.1: {} + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1): @@ -9987,6 +13247,8 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 + reading-time@1.5.0: {} + reduce-css-calc@1.3.0: dependencies: balanced-match: 0.4.2 @@ -9997,6 +13259,76 @@ snapshots: dependencies: balanced-match: 1.0.2 + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.8 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.45 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + + rehype-pretty-code@0.9.11(shiki@0.14.7): + dependencies: + '@types/hast': 2.3.10 + hash-obj: 4.0.0 + parse-numeric-range: 1.3.0 + shiki: 0.14.7 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + remark-gfm@3.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-gfm: 2.0.2 + micromark-extension-gfm: 2.0.3 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-math@5.1.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-math: 2.0.2 + micromark-extension-math: 2.1.2 + unified: 10.1.2 + + remark-mdx@2.3.0: + dependencies: + mdast-util-mdx: 2.0.1 + micromark-extension-mdxjs: 1.0.1 + transitivePeerDependencies: + - supports-color + + remark-parse@10.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-reading-time@2.1.0: + dependencies: + estree-util-is-identifier-name: 3.0.0 + estree-util-value-to-estree: 3.5.0 + reading-time: 1.5.0 + unist-util-visit: 5.1.0 + + remark-rehype@10.1.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-to-hast: 12.3.0 + unified: 10.1.2 + + remove-accents@0.5.0: {} + require-from-string@2.0.2: {} resolve-pkg-maps@1.0.0: {} @@ -10063,6 +13395,12 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-buffer@5.2.1: {} safe-stable-stringify@2.5.0: {} @@ -10075,6 +13413,10 @@ snapshots: dependencies: loose-envify: 1.4.0 + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + secretlint@10.2.2: dependencies: '@secretlint/config-creator': 10.2.2 @@ -10087,6 +13429,11 @@ snapshots: transitivePeerDependencies: - supports-color + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + semver@5.7.2: {} semver@6.3.1: {} @@ -10095,12 +13442,25 @@ snapshots: set-blocking@2.0.0: {} + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 + shebang-regex@1.0.0: {} + shebang-regex@3.0.0: {} + shiki@0.14.7: + dependencies: + ansi-sequence-parser: 1.1.3 + jsonc-parser: 3.3.1 + vscode-oniguruma: 1.7.0 + vscode-textmate: 8.0.0 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -10145,6 +13505,8 @@ snapshots: simple-lru-cache@0.0.2: {} + slash@3.0.0: {} + slash@5.1.0: {} slice-ansi@4.0.0: @@ -10207,6 +13569,10 @@ snapshots: ip-address: 10.1.0 smart-buffer: 4.2.0 + sort-keys@5.1.0: + dependencies: + is-plain-obj: 4.1.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -10218,6 +13584,10 @@ snapshots: source-map@0.6.1: optional: true + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -10234,6 +13604,8 @@ snapshots: split2@4.2.0: {} + sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} sql-escaper@1.3.3: {} @@ -10259,6 +13631,8 @@ snapshots: stream-shift@1.0.3: {} + streamsearch@1.1.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -10275,6 +13649,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -10283,8 +13662,18 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom-string@1.0.0: {} + + strip-eof@1.0.0: {} + + strip-final-newline@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-literal@2.1.1: + dependencies: + js-tokens: 9.0.1 + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -10301,6 +13690,21 @@ snapshots: style-mod@4.1.3: {} + style-to-object@0.4.4: + dependencies: + inline-style-parser: 0.1.1 + + styled-jsx@5.1.1(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + + stylis@4.3.6: {} + + supports-color@4.5.0: + dependencies: + has-flag: 2.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -10358,7 +13762,7 @@ snapshots: '@azure/identity': 4.13.0 '@azure/keyvault-keys': 4.10.0(@azure/core-client@1.10.1) '@js-joda/core': 5.7.0 - '@types/node': 22.19.11 + '@types/node': 20.14.10 bl: 6.1.6 iconv-lite: 0.6.3 js-md4: 0.3.2 @@ -10374,7 +13778,7 @@ snapshots: '@azure/identity': 4.13.0 '@azure/keyvault-keys': 4.10.0(@azure/core-client@1.10.1) '@js-joda/core': 5.7.0 - '@types/node': 22.19.11 + '@types/node': 20.14.10 bl: 6.1.6 iconv-lite: 0.7.2 js-md4: 0.3.2 @@ -10425,12 +13829,25 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@0.8.4: {} + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} + tinyspy@2.2.1: {} + tinyspy@4.0.4: {} + title@3.5.3: + dependencies: + arg: 1.0.0 + chalk: 2.3.0 + clipboardy: 1.2.2 + titleize: 1.0.0 + + titleize@1.0.0: {} + tmp@0.2.5: {} to-regex-range@5.0.1: @@ -10441,8 +13858,14 @@ snapshots: tr46@0.0.3: {} + trim-lines@3.0.1: {} + triple-beam@1.4.1: {} + trough@2.2.0: {} + + ts-dedent@2.2.0: {} + tslib@2.8.1: {} tsx@4.21.0: @@ -10485,6 +13908,10 @@ snapshots: turbo-windows-64: 2.8.10 turbo-windows-arm64: 2.8.10 + type-detect@4.1.0: {} + + type-fest@1.4.0: {} + type-fest@4.41.0: {} typed-rest-client@1.8.11: @@ -10493,12 +13920,18 @@ snapshots: tunnel: 0.0.6 underscore: 1.13.8 + typescript@5.5.3: {} + typescript@5.9.3: {} uc.micro@2.1.0: {} + ufo@1.6.3: {} + underscore@1.13.8: {} + undici-types@5.26.5: {} + undici-types@6.21.0: {} undici@7.22.0: {} @@ -10507,6 +13940,16 @@ snapshots: unicorn-magic@0.3.0: {} + unified@10.1.2: + dependencies: + '@types/unist': 2.0.11 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 5.3.7 + unique-filename@2.0.1: dependencies: unique-slug: 3.0.0 @@ -10515,6 +13958,79 @@ snapshots: dependencies: imurmurhash: 0.1.4 + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-generated@2.0.1: {} + + unist-util-is@5.2.1: + dependencies: + '@types/unist': 2.0.11 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@1.1.2: + dependencies: + '@types/unist': 2.0.11 + + unist-util-position@4.0.4: + dependencies: + '@types/unist': 2.0.11 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@4.0.2: + dependencies: + '@types/unist': 2.0.11 + unist-util-visit: 4.1.2 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-remove@4.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unist-util-stringify-position@3.0.3: + dependencies: + '@types/unist': 2.0.11 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@5.1.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@4.1.2: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@2.0.1: {} update-browserslist-db@1.2.3(browserslist@4.28.1): @@ -10550,6 +14066,13 @@ snapshots: uuid@9.0.1: {} + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.2 + kleur: 4.1.5 + sade: 1.8.1 + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -10557,6 +14080,57 @@ snapshots: version-range@4.15.0: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-matter@3.0.1: + dependencies: + '@types/js-yaml': 4.0.9 + is-buffer: 2.0.5 + js-yaml: 4.1.1 + + vfile-message@3.1.4: + dependencies: + '@types/unist': 2.0.11 + unist-util-stringify-position: 3.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@5.3.7: + dependencies: + '@types/unist': 2.0.11 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@1.6.1(@types/node@20.14.10)(terser@5.46.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@20.14.10)(terser@5.46.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@3.2.4(@types/node@22.19.11)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: cac: 6.7.14 @@ -10578,6 +14152,16 @@ snapshots: - tsx - yaml + vite@5.4.21(@types/node@20.14.10)(terser@5.46.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.59.0 + optionalDependencies: + '@types/node': 20.14.10 + fsevents: 2.3.3 + terser: 5.46.0 + vite@6.4.1(@types/node@22.19.11)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 @@ -10594,6 +14178,40 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 + vitest@1.6.1(@types/node@20.14.10)(terser@5.46.0): + dependencies: + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + acorn-walk: 8.3.5 + chai: 4.5.0 + debug: 4.4.3 + execa: 8.0.1 + local-pkg: 0.5.1 + magic-string: 0.30.21 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.10.0 + strip-literal: 2.1.1 + tinybench: 2.9.0 + tinypool: 0.8.4 + vite: 5.4.21(@types/node@20.14.10)(terser@5.46.0) + vite-node: 1.6.1(@types/node@20.14.10)(terser@5.46.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.14.10 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(jiti@1.21.7)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@types/chai': 5.2.3 @@ -10657,8 +14275,16 @@ snapshots: dependencies: vscode-languageserver-protocol: 3.17.5 + vscode-oniguruma@1.7.0: {} + + vscode-textmate@8.0.0: {} + w3c-keyname@2.2.8: {} + web-namespaces@2.0.1: {} + + web-worker@1.5.0: {} + webidl-conversions@3.0.1: {} whatwg-encoding@3.1.1: @@ -10672,6 +14298,10 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -10732,6 +14362,8 @@ snapshots: xtend@4.0.2: {} + yallist@2.1.2: {} + yallist@3.1.1: {} yallist@4.0.0: {} @@ -10752,6 +14384,10 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + + zod@3.25.76: {} + zustand@4.5.7(@types/react@18.3.28)(react@18.3.1): dependencies: use-sync-external-store: 1.6.0(react@18.3.1) @@ -10764,3 +14400,5 @@ snapshots: '@types/react': 18.3.28 react: 18.3.1 use-sync-external-store: 1.6.0(react@18.3.1) + + zwitch@2.0.4: {} diff --git a/scripts/bench/gen-dbt-project.mjs b/scripts/bench/gen-dbt-project.mjs new file mode 100644 index 00000000..7f050fa9 --- /dev/null +++ b/scripts/bench/gen-dbt-project.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +// Generate a synthetic dbt project with N models, forming a deterministic +// DAG (layered fan-in/fan-out). Used by CI's 4,000-model stress test and +// by local perf work — see docs/contribute/testing. +// +// Usage: node gen-dbt-project.mjs --models 4000 --out /tmp/stress +import { mkdirSync, writeFileSync } from 'node:fs'; +import { resolve, join } from 'node:path'; + +function parseArgs(argv) { + const o = { models: 4000, out: null, layers: 8 }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === '--models') o.models = Number(argv[++i]); + else if (a === '--out') o.out = argv[++i]; + else if (a === '--layers') o.layers = Number(argv[++i]); + } + if (!o.out) { console.error('--out is required'); process.exit(2); } + return o; +} + +const { models, out, layers } = parseArgs(process.argv.slice(2)); +const root = resolve(out); +mkdirSync(join(root, 'models'), { recursive: true }); + +writeFileSync(join(root, 'dbt_project.yml'), `name: stress_${models} +version: '1.0.0' +profile: stress +model-paths: ["models"] +`); + +// Layer 0 = sources. Each subsequent layer fans in from the previous. +const perLayer = Math.ceil(models / layers); +let idx = 0; +for (let layer = 0; layer < layers; layer++) { + mkdirSync(join(root, 'models', `layer_${layer}`), { recursive: true }); + for (let i = 0; i < perLayer && idx < models; i++, idx++) { + const name = `m_${layer}_${i}`; + let sql; + if (layer === 0) { + sql = `select * from {{ source('raw', 's_${i}') }}`; + } else { + // Each model fans in from 2 parents in the previous layer — keeps the + // graph realistic without exploding edge count. + const a = i % perLayer; + const b = (i + 1) % perLayer; + sql = `select x.* +from {{ ref('m_${layer - 1}_${a}') }} x +join {{ ref('m_${layer - 1}_${b}') }} y using (id)`; + } + writeFileSync(join(root, 'models', `layer_${layer}`, `${name}.sql`), sql + '\n'); + } +} + +writeFileSync(join(root, 'README.md'), `# stress_${models} + +Synthetic dbt project: ${models} models across ${layers} layers. +Generated by scripts/bench/gen-dbt-project.mjs — do not edit by hand. +`); + +console.log(`✓ generated ${idx} models across ${layers} layers at ${root}`); diff --git a/scripts/bench/run-bench.mjs b/scripts/bench/run-bench.mjs new file mode 100644 index 00000000..5ab2a17b --- /dev/null +++ b/scripts/bench/run-bench.mjs @@ -0,0 +1,47 @@ +#!/usr/bin/env node +// Benchmark the manifest build + lineage rebuild against a generated dbt +// project. Fails non-zero if gates from docs/contribute/testing aren't met. +// +// gates: cold build <30s, warm rebuild <2s +// +// Usage: +// node scripts/bench/gen-dbt-project.mjs --models 4000 --out /tmp/stress +// node scripts/bench/run-bench.mjs /tmp/stress +import { spawnSync } from 'node:child_process'; +import { performance } from 'node:perf_hooks'; +import { resolve } from 'node:path'; + +const project = resolve(process.argv[2] ?? '.'); +const cli = process.env.DQL_CLI ?? 'node apps/cli/dist/index.js'; + +function time(label, fn) { + const t0 = performance.now(); + fn(); + const ms = performance.now() - t0; + console.log(` ${label}: ${ms.toFixed(0)}ms`); + return ms; +} + +function run(args) { + const [bin, ...rest] = cli.split(/\s+/); + const r = spawnSync(bin, [...rest, ...args], { cwd: project, stdio: 'inherit' }); + if (r.status !== 0) throw new Error(`dql ${args.join(' ')} failed (${r.status})`); +} + +console.log(`▸ benchmarking ${project}\n`); + +const cold = time('cold manifest build ', () => run(['sync', 'dbt'])); +const warm = time('warm manifest rebuild', () => run(['sync', 'dbt'])); + +console.log(); + +const gateCold = 30_000; +const gateWarm = 2_000; + +let failed = 0; +if (cold > gateCold) { console.error(`✗ cold build exceeded ${gateCold}ms`); failed++; } +else console.log(`✓ cold build under ${gateCold}ms`); +if (warm > gateWarm) { console.error(`✗ warm rebuild exceeded ${gateWarm}ms`); failed++; } +else console.log(`✓ warm rebuild under ${gateWarm}ms`); + +process.exit(failed ? 1 : 0);