diff --git a/bin/run-entrypoint.js b/bin/run-entrypoint.js index b9eee6fb4..4c10e534c 100644 --- a/bin/run-entrypoint.js +++ b/bin/run-entrypoint.js @@ -1,3 +1,4 @@ +import "./silence-sqlite-warning.js"; import { existsSync } from "node:fs"; import { fileURLToPath } from "node:url"; diff --git a/bin/silence-sqlite-warning.js b/bin/silence-sqlite-warning.js new file mode 100644 index 000000000..a3c1db657 --- /dev/null +++ b/bin/silence-sqlite-warning.js @@ -0,0 +1,14 @@ +// node:sqlite is a release candidate but still emits an ExperimentalWarning in every process that loads it. +// Built-ins load while the ES module graph links, before any app code runs, so this filter must be preloaded: +// run-entrypoint.js imports it first, and the agent daemon receives it through `--import`. +const emitWarning = process.emitWarning; + +process.emitWarning = function (warning, ...args) { + // emitWarning(warning, type) and emitWarning(warning, { type }) are both valid call shapes. + const type = args[0]?.type ?? args[0]; + const message = String(warning?.message ?? warning); + + if (type === "ExperimentalWarning" && message.startsWith("SQLite")) return; + + return emitWarning.call(process, warning, ...args); +}; diff --git a/install/install.ps1 b/install/install.ps1 new file mode 100644 index 000000000..b28ee957d --- /dev/null +++ b/install/install.ps1 @@ -0,0 +1,165 @@ +# DevSpace installer for Windows. +# +# powershell -c "irm https://raw.githubusercontent.com/Waishnav/devspace/main/install/install.ps1 | iex" +# +# Mirrors install.sh: uses a compatible Node already on PATH, otherwise downloads a checksum-verified Node 24 LTS +# for DevSpace alone, installs @waishnav/devspace into a user-owned npm prefix, writes `devspace` launchers that +# pin that Node, then runs `devspace init`. Rerun it to update. DevSpace itself also needs Git Bash on Windows. +# +# Environment overrides: DEVSPACE_VERSION (default: beta), DEVSPACE_INSTALL_DIR, DEVSPACE_BIN_DIR, DEVSPACE_NO_INIT=1, +# DEVSPACE_NO_MODIFY_PATH=1, DEVSPACE_PACKAGE_SPEC (see install.sh). + +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +$Package = "@waishnav/devspace" +# v1.1 is the first release installable without install scripts, and it is still on the beta tag. +# Switch this to latest once v1.1 ships as stable. +$Version = if ($env:DEVSPACE_VERSION) { $env:DEVSPACE_VERSION } else { "beta" } +$PackageSpec = if ($env:DEVSPACE_PACKAGE_SPEC) { $env:DEVSPACE_PACKAGE_SPEC } else { "$Package@$Version" } +$InstallDir = if ($env:DEVSPACE_INSTALL_DIR) { $env:DEVSPACE_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "devspace\install" } +$BinDir = if ($env:DEVSPACE_BIN_DIR) { $env:DEVSPACE_BIN_DIR } else { Join-Path $env:LOCALAPPDATA "devspace\bin" } +$NodeMajor = 24 +# Must match package.json engines.node. +$NodeMin = "22.19.0" +$NodeMaxMajor = 27 + +function Test-CompatibleNode([string]$Node) { + $script = 'const [maj,min,patch]=process.versions.node.split(".").map(Number);const [a,b,c]=process.argv[1].split(".").map(Number);const ok=maj>a||(maj===a&&(min>b||(min===b&&patch>=c)));process.exit(ok&&maj $null + return $LASTEXITCODE -eq 0 +} + +function Install-Node { + $arch = switch ($env:PROCESSOR_ARCHITECTURE) { + "AMD64" { "x64" } + "ARM64" { "arm64" } + default { throw "Unsupported CPU architecture: $($env:PROCESSOR_ARCHITECTURE)" } + } + $dist = "https://nodejs.org/dist/latest-v$NodeMajor.x" + $tmp = Join-Path ([IO.Path]::GetTempPath()) ("devspace-node-" + [Guid]::NewGuid()) + New-Item -ItemType Directory -Path $tmp | Out-Null + + try { + Write-Host "Downloading Node $NodeMajor LTS for win-$arch..." + $sums = (Invoke-WebRequest -UseBasicParsing "$dist/SHASUMS256.txt").Content -split "`n" + $line = $sums | Where-Object { $_ -match "\s(node-v[\d.]+-win-$arch\.zip)$" } | Select-Object -First 1 + if (-not $line) { throw "No Node $NodeMajor build is published for win-$arch." } + $expected, $file = $line.Trim() -split "\s+" + $zip = Join-Path $tmp $file + Invoke-WebRequest -UseBasicParsing "$dist/$file" -OutFile $zip + if ((Get-FileHash -Algorithm SHA256 $zip).Hash.ToLower() -ne $expected) { + throw "Checksum mismatch for $file; refusing to install it." + } + + Expand-Archive -Path $zip -DestinationPath $tmp + $nodeDir = Join-Path $InstallDir "node" + if (Test-Path $nodeDir) { Remove-Item -Recurse -Force $nodeDir } + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + Move-Item (Join-Path $tmp ($file -replace "\.zip$", "")) $nodeDir + Write-Host "Installed $($file -replace '\.zip$', '') into $nodeDir" + return (Join-Path $nodeDir "node.exe") + } finally { + Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue + } +} + +function Select-Node { + $managed = Join-Path $InstallDir "node\node.exe" + if ((Test-Path $managed) -and (Test-CompatibleNode $managed)) { return $managed } + + $system = Get-Command node -ErrorAction SilentlyContinue + if ($system -and (Test-CompatibleNode $system.Source)) { + # Pin the real binary, since version managers switch what `node` points to but keep each version on disk. + # nvm-windows repoints a symlinked directory, which process.execPath may report unresolved, so follow it. + $dir = Get-Item -LiteralPath (Split-Path $system.Source) + if ($dir.LinkType -and $dir.Target) { + $target = @($dir.Target)[0] + if (-not [IO.Path]::IsPathRooted($target)) { $target = Join-Path (Split-Path $dir.FullName) $target } + return Join-Path $target (Split-Path $system.Source -Leaf) + } + return (& $system.Source -p "process.execPath") + } + if ($system) { Write-Host "Found Node $(& $system.Source --version), but DevSpace needs >=$NodeMin <$NodeMaxMajor." } + + return Install-Node +} + +# cmd.exe reads .cmd files in the console code page, so a launcher cannot embed non-ASCII text literally. +# Spell paths under the user's profile folders through their environment variables instead, which cmd expands +# with full Unicode, and keep any literal % from being expanded. +function ConvertTo-CmdPath([string]$Path) { + $prefix = "" + $rest = $Path + foreach ($name in @("LOCALAPPDATA", "APPDATA", "USERPROFILE")) { + $root = [Environment]::GetEnvironmentVariable($name) + if ($root -and $Path.StartsWith($root, [StringComparison]::OrdinalIgnoreCase)) { + $prefix = "%$name%" + $rest = $Path.Substring($root.Length) + break + } + } + if ($rest -match "[^\x00-\x7F]") { + throw "Cannot write a launcher for $Path because cmd.exe cannot read its non-ASCII characters. If it is a Node runtime, move that Node to an ASCII path or remove it from PATH so the installer downloads its own; otherwise set DEVSPACE_INSTALL_DIR and DEVSPACE_BIN_DIR to ASCII paths. Then rerun." + } + return $prefix + $rest.Replace("%", "%%") +} + +$Node = Select-Node +$NodeDir = Split-Path $Node +$Npm = Join-Path $NodeDir "npm.cmd" +if (-not (Test-Path $Npm)) { throw "npm was not found next to $Node. Install npm for that Node and rerun." } + +Write-Host "Installing $PackageSpec with Node $(& $Node --version)..." +$prefix = Join-Path $InstallDir "npm" +New-Item -ItemType Directory -Path $prefix -Force | Out-Null +# npm resolves `node` from PATH, so put the selected Node first. +# DevSpace and its dependencies ship prebuilt binaries, so no install scripts are needed. +$env:Path = "$NodeDir;$env:Path" +& $Npm install --global --prefix $prefix --ignore-scripts --no-fund --no-audit --no-update-notifier --loglevel=error $PackageSpec +if ($LASTEXITCODE -ne 0) { throw "npm install failed." } + +$packageDir = Join-Path $prefix "node_modules\$Package" +if (-not (Test-Path (Join-Path $packageDir "bin\devspace.js"))) { throw "Install finished but $packageDir\bin\devspace.js is missing." } +New-Item -ItemType Directory -Path $BinDir -Force | Out-Null + +$cmdNode = ConvertTo-CmdPath $Node +foreach ($command in @("devspace", "devspace-agentd")) { + $cmdEntry = ConvertTo-CmdPath (Join-Path $packageDir "bin\$command.js") + # Pin the Node that installed DevSpace so a version manager switching `node` cannot break it. + # goto instead of an if (...) block: paths such as "Program Files (x86)" would close the block early. + $launcher = @( + "@echo off", + "rem Generated by the DevSpace installer. Rerun the installer to update.", + "if not exist `"$cmdNode`" goto missing", + "`"$cmdNode`" `"$cmdEntry`" %*", + "exit /b %errorlevel%", + ":missing", + "echo DevSpace's Node runtime is missing: `"$cmdNode`" 1>&2", + "echo Rerun the DevSpace installer. 1>&2", + "exit /b 1" + ) -join "`r`n" + Set-Content -Path (Join-Path $BinDir "$command.cmd") -Value $launcher -Encoding ASCII +} + +$userPath = [Environment]::GetEnvironmentVariable("Path", "User") +if (($userPath -split ";") -notcontains $BinDir) { + if ($env:DEVSPACE_NO_MODIFY_PATH -eq "1") { + Write-Host "Add $BinDir to your PATH to run devspace." + } else { + [Environment]::SetEnvironmentVariable("Path", (@($BinDir, $userPath) | Where-Object { $_ }) -join ";", "User") + Write-Host "Added $BinDir to your user PATH (open a new terminal to pick it up)." + } +} +$env:Path = "$BinDir;$env:Path" + +$devspace = Join-Path $BinDir "devspace.cmd" +$version = & $devspace --version +if ($LASTEXITCODE -ne 0) { throw "The installed launcher at $devspace did not run." } +Write-Host "DevSpace $version is installed at $devspace" + +if ($env:DEVSPACE_NO_INIT -ne "1") { + Write-Host "" + & $devspace init + if ($LASTEXITCODE -ne 0) { Write-Host "Setup did not finish. Run ``devspace init`` to continue." } +} diff --git a/install/install.sh b/install/install.sh new file mode 100755 index 000000000..994ccd6f2 --- /dev/null +++ b/install/install.sh @@ -0,0 +1,206 @@ +#!/bin/sh +# DevSpace installer for macOS, Linux, and WSL. +# +# curl -fsSL https://raw.githubusercontent.com/Waishnav/devspace/main/install/install.sh | sh +# +# Uses a compatible Node already on PATH, otherwise downloads a checksum-verified Node 24 LTS for DevSpace alone. +# Installs @waishnav/devspace into a user-owned npm prefix (no sudo), writes `devspace` launchers that pin that +# Node, then runs `devspace init`. Rerun it to update. +# +# Environment overrides: +# DEVSPACE_VERSION npm version or dist-tag to install (default: beta) +# DEVSPACE_INSTALL_DIR runtime and package location (default: ~/.local/share/devspace/install) +# DEVSPACE_BIN_DIR launcher location (default: ~/.local/bin) +# DEVSPACE_NO_INIT=1 skip running `devspace init` afterwards +# DEVSPACE_NO_MODIFY_PATH=1 never edit shell profiles +# DEVSPACE_PACKAGE_SPEC npm spec to install instead, such as a local tarball (for testing) + +set -eu + +PACKAGE="@waishnav/devspace" +# v1.1 is the first release installable without install scripts, and it is still on the beta tag. +# Switch this to latest once v1.1 ships as stable. +VERSION="${DEVSPACE_VERSION:-beta}" +PACKAGE_SPEC="${DEVSPACE_PACKAGE_SPEC:-$PACKAGE@$VERSION}" +INSTALL_DIR="${DEVSPACE_INSTALL_DIR:-$HOME/.local/share/devspace/install}" +BIN_DIR="${DEVSPACE_BIN_DIR:-$HOME/.local/bin}" +NODE_MAJOR=24 +# Must match package.json engines.node. +NODE_MIN="22.19.0" +NODE_MAX_MAJOR=27 + +info() { printf '%s\n' "$*"; } +fail() { printf 'error: %s\n' "$*" >&2; exit 1; } + +need() { command -v "$1" >/dev/null 2>&1 || fail "'$1' is required but not installed."; } + +# Single-quotes a value for POSIX sh, so generated files keep paths containing $, `, ", or spaces literal. +shell_quote() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; } + +# Succeeds when the given `node` binary satisfies NODE_MIN <= version < NODE_MAX_MAJOR. +node_is_compatible() { + "$1" -e ' + const [maj, min, patch] = process.versions.node.split(".").map(Number); + const [wantMaj, wantMin, wantPatch] = process.argv[1].split(".").map(Number); + const atLeast = maj > wantMaj || (maj === wantMaj && (min > wantMin || (min === wantMin && patch >= wantPatch))); + process.exit(atLeast && maj < Number(process.argv[2]) ? 0 : 1); + ' "$NODE_MIN" "$NODE_MAX_MAJOR" >/dev/null 2>&1 +} + +sha256() { + if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 + elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1 + else fail "sha256sum or shasum is required to verify the Node download." + fi +} + +detect_platform() { + case "$(uname -s)" in + Linux) os=linux ;; + Darwin) os=darwin ;; + MINGW* | MSYS* | CYGWIN*) fail "Use install.ps1 on Windows, or run this script inside WSL." ;; + *) fail "Unsupported OS: $(uname -s)" ;; + esac + + case "$(uname -m)" in + x86_64 | amd64) arch=x64 ;; + arm64 | aarch64) arch=arm64 ;; + *) fail "Unsupported CPU architecture: $(uname -m)" ;; + esac + + # Rosetta reports x64 on Apple silicon; prefer the native build. + if [ "$os" = darwin ] && [ "$arch" = x64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then + arch=arm64 + fi + + if [ "$os" = linux ] && { [ -f /etc/alpine-release ] || ldd --version 2>&1 | grep -qi musl; }; then + musl=1 + else + musl=0 + fi +} + +install_node() { + [ "$musl" = 0 ] || fail "nodejs.org has no musl build. Install Node $NODE_MIN+ with your package manager (apk add nodejs npm) and rerun." + need curl + need tar + + dist="https://nodejs.org/dist/latest-v$NODE_MAJOR.x" + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + + info "Downloading Node $NODE_MAJOR LTS for $os-$arch..." + curl -fsSL "$dist/SHASUMS256.txt" -o "$tmp/SHASUMS256.txt" + file=$(awk -v suffix="-$os-$arch.tar.gz" 'substr($2, length($2) - length(suffix) + 1) == suffix { print $2; exit }' "$tmp/SHASUMS256.txt") + [ -n "$file" ] || fail "No Node $NODE_MAJOR build is published for $os-$arch." + expected=$(awk -v name="$file" '$2 == name { print $1; exit }' "$tmp/SHASUMS256.txt") + + curl -fsSL "$dist/$file" -o "$tmp/$file" + [ "$(sha256 "$tmp/$file")" = "$expected" ] || fail "Checksum mismatch for $file; refusing to install it." + + rm -rf "$INSTALL_DIR/node" + mkdir -p "$INSTALL_DIR/node" + tar -xzf "$tmp/$file" -C "$INSTALL_DIR/node" --strip-components=1 + NODE="$INSTALL_DIR/node/bin/node" + info "Installed ${file%.tar.gz} into $INSTALL_DIR/node" +} + +select_node() { + if [ -x "$INSTALL_DIR/node/bin/node" ] && node_is_compatible "$INSTALL_DIR/node/bin/node"; then + NODE="$INSTALL_DIR/node/bin/node" + elif command -v node >/dev/null 2>&1 && node_is_compatible "$(command -v node)"; then + # Pin the real binary: version managers (nvm, fnm, mise, asdf, Volta) switch what `node` points to but + # keep every installed version on disk. Homebrew and Nix delete old versions on upgrade instead, so for + # them keep the stable link that `command -v` found. + NODE=$(node -p process.execPath) + case "$NODE" in + */Cellar/* | /nix/store/*) NODE=$(command -v node) ;; + esac + else + if command -v node >/dev/null 2>&1; then + info "Found Node $(node --version), but DevSpace needs >=$NODE_MIN <$NODE_MAX_MAJOR." + fi + install_node + fi + + NODE_BIN_DIR=$(dirname "$NODE") + [ -x "$NODE_BIN_DIR/npm" ] || fail "npm was not found next to $NODE. Install npm for that Node and rerun." +} + +install_package() { + info "Installing $PACKAGE_SPEC with Node $("$NODE" --version)..." + mkdir -p "$INSTALL_DIR/npm" + # npm's shebang resolves `node` from PATH, so put the selected Node first. + # DevSpace and its dependencies ship prebuilt binaries, so no install scripts are needed. + PATH="$NODE_BIN_DIR:$PATH" "$NODE_BIN_DIR/npm" install --global --prefix "$INSTALL_DIR/npm" \ + --ignore-scripts --no-fund --no-audit --no-update-notifier --loglevel=error "$PACKAGE_SPEC" +} + +write_launchers() { + package_dir="$INSTALL_DIR/npm/lib/node_modules/$PACKAGE" + [ -f "$package_dir/bin/devspace.js" ] || fail "Install finished but $package_dir/bin/devspace.js is missing." + mkdir -p "$BIN_DIR" + + for command in devspace devspace-agentd; do + launcher="$BIN_DIR/$command" + # Pin the Node that installed DevSpace so a version manager switching `node` cannot break it. + cat >"$launcher" <&2 + exit 1 +fi +exec "\$node" $(shell_quote "$package_dir/bin/$command.js") "\$@" +EOF + chmod 755 "$launcher" + done +} + +ensure_path() { + case ":$PATH:" in *":$BIN_DIR:"*) return ;; esac + + if [ "${DEVSPACE_NO_MODIFY_PATH:-0}" = 1 ]; then + info "Add $BIN_DIR to your PATH to run devspace." + return + fi + + line="export PATH=$(shell_quote "$BIN_DIR"):\"\$PATH\"" + + case "$(basename "${SHELL:-sh}")" in + zsh) profile="${ZDOTDIR:-$HOME}/.zshrc" ;; + bash) if [ "$os" = darwin ]; then profile="$HOME/.bash_profile"; else profile="$HOME/.bashrc"; fi ;; + fish) profile="$HOME/.config/fish/config.fish" + # fish single quotes escape only backslash and the quote itself. + line="fish_add_path '$(printf '%s' "$BIN_DIR" | sed "s/[\\\\']/\\\\&/g")'" ;; + *) profile="$HOME/.profile" ;; + esac + + if ! grep -Fqs "$line" "$profile"; then + mkdir -p "$(dirname "$profile")" + printf '\n# DevSpace\n%s\n' "$line" >>"$profile" + info "Added $BIN_DIR to PATH in $profile (open a new shell to pick it up)." + fi +} + +run_init() { + if [ "${DEVSPACE_NO_INIT:-0}" = 1 ]; then return; fi + + # Under `curl | sh` stdin is the script itself, so setup reads answers from the terminal. + if [ -r /dev/tty ] && [ -w /dev/tty ]; then + info "" + "$BIN_DIR/devspace" init /dev/tty 2>&1 || info "Setup did not finish. Run \`devspace init\` to continue." + else + info "No terminal available; run \`devspace init\` to finish setup." + fi +} + +detect_platform +select_node +install_package +write_launchers +ensure_path +version=$("$BIN_DIR/devspace" --version) || fail "The installed launcher at $BIN_DIR/devspace did not run." +info "DevSpace $version is installed at $BIN_DIR/devspace" +run_init diff --git a/package.json b/package.json index 5d0ed9be0..1fa9a2718 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,10 @@ "dev": "tsx scripts/dev.ts", "dev:seed": "tsx scripts/dev-state.ts seed", "dev:reset": "tsx scripts/dev-state.ts reset", - "postinstall": "node scripts/fix-node-pty-permissions.mjs", "prepack": "pnpm build", "schema:config": "tsx scripts/generate-config-schema.ts", - "start": "node dist/cli.js serve", - "test": "tsx --test --test-concurrency=1 \"src/**/*.test.ts\"", + "start": "node --import ./bin/silence-sqlite-warning.js dist/cli.js serve", + "test": "tsx --import ./bin/silence-sqlite-warning.js --test --test-concurrency=1 \"src/**/*.test.ts\"", "test:package-install": "tsx --test --test-concurrency=1 \"test/package-install-smoke.test.ts\"", "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p scripts/qa/tsconfig.json && tsc -p test/agents-replay/tsconfig.json", "lint": "oxlint", @@ -66,10 +65,9 @@ "@pierre/diffs": "^1.3.6", "ajv": "^8.20.0", "better-result": "^2.10.0", - "better-sqlite3": "^12.10.0", "cross-spawn": "^7.0.6", "diff": "^8.0.3", - "drizzle-orm": "^0.45.2", + "drizzle-orm": "1.0.0-rc.5", "express": "^5.2.1", "jsonc-parser": "^3.3.1", "koffi": "3.2.1", @@ -83,7 +81,6 @@ }, "devDependencies": { "@oxlint/plugins": "1.89.0", - "@types/better-sqlite3": "^7.6.13", "@types/express": "^5.0.6", "@types/node": "^25.9.1", "@types/react": "^19.2.15", @@ -101,6 +98,6 @@ "undici": "8.5.0" }, "optionalDependencies": { - "node-pty": "^1.1.0" + "@lydell/node-pty": "1.2.0-beta.18" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a7efa3f7..80b5d3a95 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,9 +53,6 @@ importers: better-result: specifier: ^2.10.0 version: 2.10.0 - better-sqlite3: - specifier: ^12.10.0 - version: 12.10.0 cross-spawn: specifier: ^7.0.6 version: 7.0.6 @@ -63,8 +60,8 @@ importers: specifier: ^8.0.3 version: 8.0.3 drizzle-orm: - specifier: ^0.45.2 - version: 0.45.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0) + specifier: 1.0.0-rc.5 + version: 1.0.0-rc.5(@opentelemetry/api@1.9.0)(zod@4.4.3) express: specifier: ^5.2.1 version: 5.2.1 @@ -99,9 +96,6 @@ importers: '@oxlint/plugins': specifier: 1.89.0 version: 1.89.0 - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 '@types/express': specifier: ^5.0.6 version: 5.0.6 @@ -133,9 +127,9 @@ importers: specifier: ^8.0.14 version: 8.0.16(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.3)(yaml@2.9.0) optionalDependencies: - node-pty: - specifier: ^1.1.0 - version: 1.1.0 + '@lydell/node-pty': + specifier: 1.2.0-beta.18 + version: 1.2.0-beta.18 packages: @@ -636,6 +630,39 @@ packages: cpu: [x64] os: [win32] + '@lydell/node-pty-darwin-arm64@1.2.0-beta.18': + resolution: {integrity: sha512-gJizeNHkkKCoggtj5Q0yf4btK6dPfr7E063SrOh1jHXp3IyjiErU4KwT7Cn29qb/OY5pBo/Utctenl03MyBtAg==} + cpu: [arm64] + os: [darwin] + + '@lydell/node-pty-darwin-x64@1.2.0-beta.18': + resolution: {integrity: sha512-xnJQz+i5v7pkR1xo14ffTwvMm2DoK3KqPACQaJqxtMVkM7ojv14/KmkaV2jte7Q0BIzfU+VbKVUL9Zmf23G8VQ==} + cpu: [x64] + os: [darwin] + + '@lydell/node-pty-linux-arm64@1.2.0-beta.18': + resolution: {integrity: sha512-fM8jRcZDZwG7ihEOx/R7N1aGHdT4Fd6IQj1d6Jp7Qb9FeDWlIwLuVlxUkJOsW4V1ZmJe1xZl2+WR0RjqMvb2+Q==} + cpu: [arm64] + os: [linux] + + '@lydell/node-pty-linux-x64@1.2.0-beta.18': + resolution: {integrity: sha512-LGikvfcvoPrxXO7o1gCsd5ErrRWnQFYLxjJfX0uwBpi0oGPiTERUBLzKspzZs7wjXZqF30mnNB/C09uotu5vRA==} + cpu: [x64] + os: [linux] + + '@lydell/node-pty-win32-arm64@1.2.0-beta.18': + resolution: {integrity: sha512-S6h4G3zYGe+pPQxhlNxg+MvJNUIwXFxhnUufx1QT45TgratKMxQfoGPidvnwoLwuXQ3hqQ7SsI93rH8RUoL09g==} + cpu: [arm64] + os: [win32] + + '@lydell/node-pty-win32-x64@1.2.0-beta.18': + resolution: {integrity: sha512-wBSMeAwEL04t694ln+QbLnc98YeF3mAwWm/NP8acYDAobPw/WK7KKpk8z07iyxv0gRH2Z2/L/f6vgtnBgxE0Bg==} + cpu: [x64] + os: [win32] + + '@lydell/node-pty@1.2.0-beta.18': + resolution: {integrity: sha512-ryy4liRcw6QBBKWDIbJgTW7Pw8NI2xjri/5s/LZg/jJ8ZVg8MgA1xI/vjpT5qTvDvPjUpU5NybJBrKRgK/QvlA==} + '@mariozechner/clipboard-darwin-arm64@0.3.9': resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==} engines: {node: '>= 10'} @@ -1180,9 +1207,6 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@types/better-sqlite3@7.6.13': - resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} - '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -1281,19 +1305,9 @@ packages: better-result@2.10.0: resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==} - better-sqlite3@12.10.0: - resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} - bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -1308,9 +1322,6 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1336,9 +1347,6 @@ packages: character-entities-legacy@3.0.0: resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -1390,14 +1398,6 @@ packages: supports-color: optional: true - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1425,43 +1425,88 @@ packages: resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} engines: {node: '>=0.3.1'} - drizzle-orm@0.45.2: - resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + drizzle-orm@1.0.0-rc.5: + resolution: {integrity: sha512-7BMYTsAZbQ9DcQCtLG4G202Hq6nc/e8o1EBKN6nBweRI8EGPFe/VdyvWQCsU9fuZSiRjNpELolUSi//JFBq+lw==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' + '@aws/aurora-dsql-node-postgres-connector': '>=0.1.9' + '@aws/aurora-dsql-postgresjs-connector': '>=0.3.0' '@cloudflare/workers-types': '>=4' + '@drizzle-team/minipg': '>=0.4.2' + '@effect/sql-d1': '>=4.0.0' + '@effect/sql-libsql': '>=4.0.0' + '@effect/sql-mysql2': '>=4.0.0' + '@effect/sql-pg': '>=4.0.0' + '@effect/sql-pglite': '>=4.0.0' + '@effect/sql-sqlite-bun': '>=4.0.0' + '@effect/sql-sqlite-do': '>=4.0.0' + '@effect/sql-sqlite-node': '>=4.0.0' + '@effect/sql-sqlite-wasm': '>=4.0.0' '@electric-sql/pglite': '>=0.2.0' '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' '@neondatabase/serverless': '>=0.10.0' - '@op-engineering/op-sqlite': '>=2' + '@op-engineering/op-sqlite': '>=17' '@opentelemetry/api': ^1.4.1 '@planetscale/database': '>=1.13' - '@prisma/client': '*' + '@sinclair/typebox': '>=0.34.8' + '@sqlitecloud/drivers': '>=1.0.653' '@tidbcloud/serverless': '*' + '@tursodatabase/database': '>=0.7.1' + '@tursodatabase/database-common': '>=0.7.1' + '@tursodatabase/database-wasm': '>=0.7.1' + '@tursodatabase/serverless': '>=1.4.0' + '@tursodatabase/sync': '>=0.7.1' '@types/better-sqlite3': '*' + '@types/mssql': ^9.1.4 '@types/pg': '*' '@types/sql.js': '*' '@upstash/redis': '>=1.34.7' '@vercel/postgres': '>=0.8.0' '@xata.io/client': '*' - better-sqlite3: '>=7' + arktype: '>=2.0.0' + better-sqlite3: '>=9.3.0' bun-types: '*' + effect: '>=4.0.0' expo-sqlite: '>=14.0.0' - gel: '>=2' - knex: '*' - kysely: '*' + mssql: ^11.0.1 mysql2: '>=2' pg: '>=8' postgres: '>=3' - prisma: '*' sql.js: '>=1' sqlite3: '>=5' + typebox: '>=1.2.0' + valibot: '>=1.0.0-beta.7' + zod: ^3.25.0 || ^4.0.0 peerDependenciesMeta: '@aws-sdk/client-rds-data': optional: true + '@aws/aurora-dsql-node-postgres-connector': + optional: true + '@aws/aurora-dsql-postgresjs-connector': + optional: true '@cloudflare/workers-types': optional: true + '@drizzle-team/minipg': + optional: true + '@effect/sql-d1': + optional: true + '@effect/sql-libsql': + optional: true + '@effect/sql-mysql2': + optional: true + '@effect/sql-pg': + optional: true + '@effect/sql-pglite': + optional: true + '@effect/sql-sqlite-bun': + optional: true + '@effect/sql-sqlite-do': + optional: true + '@effect/sql-sqlite-node': + optional: true + '@effect/sql-sqlite-wasm': + optional: true '@electric-sql/pglite': optional: true '@libsql/client': @@ -1476,12 +1521,26 @@ packages: optional: true '@planetscale/database': optional: true - '@prisma/client': + '@sinclair/typebox': + optional: true + '@sqlitecloud/drivers': optional: true '@tidbcloud/serverless': optional: true + '@tursodatabase/database': + optional: true + '@tursodatabase/database-common': + optional: true + '@tursodatabase/database-wasm': + optional: true + '@tursodatabase/serverless': + optional: true + '@tursodatabase/sync': + optional: true '@types/better-sqlite3': optional: true + '@types/mssql': + optional: true '@types/pg': optional: true '@types/sql.js': @@ -1492,17 +1551,17 @@ packages: optional: true '@xata.io/client': optional: true + arktype: + optional: true better-sqlite3: optional: true bun-types: optional: true - expo-sqlite: - optional: true - gel: + effect: optional: true - knex: + expo-sqlite: optional: true - kysely: + mssql: optional: true mysql2: optional: true @@ -1510,12 +1569,16 @@ packages: optional: true postgres: optional: true - prisma: - optional: true sql.js: optional: true sqlite3: optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -1534,9 +1597,6 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1569,10 +1629,6 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - express-rate-limit@8.5.2: resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} @@ -1628,9 +1684,6 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - finalhandler@2.1.1: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} @@ -1647,9 +1700,6 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1678,9 +1728,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -1744,9 +1791,6 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@7.0.5: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} @@ -1754,9 +1798,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ip-address@10.2.0: resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} @@ -1933,24 +1974,14 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1966,20 +1997,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - node-abi@3.92.0: - resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} - engines: {node: '>=10'} - - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -1997,9 +2018,6 @@ packages: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true - node-pty@1.1.0: - resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2087,12 +2105,6 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. - hasBin: true - proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} @@ -2107,9 +2119,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - pump@3.0.4: - resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} - pure-rand@8.4.2: resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} @@ -2135,10 +2144,6 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - react-dom@19.2.6: resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: @@ -2148,10 +2153,6 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -2242,12 +2243,6 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2265,26 +2260,12 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - 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-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strnum@2.3.0: resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -2307,9 +2288,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} @@ -2348,9 +2326,6 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -3003,6 +2978,34 @@ snapshots: '@koromix/koffi-win32-x64@3.2.1': optional: true + '@lydell/node-pty-darwin-arm64@1.2.0-beta.18': + optional: true + + '@lydell/node-pty-darwin-x64@1.2.0-beta.18': + optional: true + + '@lydell/node-pty-linux-arm64@1.2.0-beta.18': + optional: true + + '@lydell/node-pty-linux-x64@1.2.0-beta.18': + optional: true + + '@lydell/node-pty-win32-arm64@1.2.0-beta.18': + optional: true + + '@lydell/node-pty-win32-x64@1.2.0-beta.18': + optional: true + + '@lydell/node-pty@1.2.0-beta.18': + optionalDependencies: + '@lydell/node-pty-darwin-arm64': 1.2.0-beta.18 + '@lydell/node-pty-darwin-x64': 1.2.0-beta.18 + '@lydell/node-pty-linux-arm64': 1.2.0-beta.18 + '@lydell/node-pty-linux-x64': 1.2.0-beta.18 + '@lydell/node-pty-win32-arm64': 1.2.0-beta.18 + '@lydell/node-pty-win32-x64': 1.2.0-beta.18 + optional: true + '@mariozechner/clipboard-darwin-arm64@0.3.9': optional: true @@ -3413,10 +3416,6 @@ snapshots: tslib: 2.8.1 optional: true - '@types/better-sqlite3@7.6.13': - dependencies: - '@types/node': 25.9.1 - '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 @@ -3511,23 +3510,8 @@ snapshots: better-result@2.10.0: {} - better-sqlite3@12.10.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.3 - bignumber.js@9.3.1: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -3550,11 +3534,6 @@ snapshots: buffer-equal-constant-time@1.0.1: {} - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bytes@3.1.2: {} call-bind-apply-helpers@1.0.2: @@ -3575,8 +3554,6 @@ snapshots: character-entities-legacy@3.0.0: {} - chownr@1.1.4: {} - comma-separated-tokens@2.0.3: {} commander@12.1.0: {} @@ -3610,12 +3587,6 @@ snapshots: dependencies: ms: 2.1.3 - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - deep-extend@0.6.0: {} - depd@2.0.0: {} dequal@2.0.3: {} @@ -3632,11 +3603,10 @@ snapshots: diff@9.0.0: {} - drizzle-orm@0.45.2(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0): + drizzle-orm@1.0.0-rc.5(@opentelemetry/api@1.9.0)(zod@4.4.3): optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/better-sqlite3': 7.6.13 - better-sqlite3: 12.10.0 + zod: 4.4.3 dunder-proto@1.0.1: dependencies: @@ -3657,10 +3627,6 @@ snapshots: encodeurl@2.0.0: {} - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3708,8 +3674,6 @@ snapshots: dependencies: eventsource-parser: 3.1.0 - expand-template@2.0.3: {} - express-rate-limit@8.5.2(express@5.2.1): dependencies: express: 5.2.1 @@ -3791,8 +3755,6 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 - file-uri-to-path@1.0.0: {} - finalhandler@2.1.1: dependencies: debug: 4.4.3 @@ -3812,8 +3774,6 @@ snapshots: fresh@2.0.0: {} - fs-constants@1.0.0: {} - fsevents@2.3.3: optional: true @@ -3855,8 +3815,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.2 - github-from-package@0.0.0: {} - glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -3940,14 +3898,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} - ignore@7.0.5: {} inherits@2.0.4: {} - ini@1.3.8: {} - ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -4107,18 +4061,12 @@ snapshots: dependencies: mime-db: 1.54.0 - mimic-response@3.1.0: {} - minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 - minimist@1.2.8: {} - minipass@7.1.3: {} - mkdirp-classic@0.5.3: {} - ms@2.1.3: {} msgpackr-extract@3.0.4: @@ -4139,17 +4087,8 @@ snapshots: nanoid@3.3.12: {} - napi-build-utils@2.0.0: {} - negotiator@1.0.0: {} - node-abi@3.92.0: - dependencies: - semver: 7.8.4 - - node-addon-api@7.1.1: - optional: true - node-domexception@1.0.0: {} node-fetch@3.3.2: @@ -4165,11 +4104,6 @@ snapshots: detect-libc: 2.1.2 optional: true - node-pty@1.1.0: - dependencies: - node-addon-api: 7.1.1 - optional: true - object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4249,21 +4183,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.1.2 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.92.0 - pump: 3.0.4 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.4 - tunnel-agent: 0.6.0 - proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -4291,11 +4210,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - pump@3.0.4: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - pure-rand@8.4.2: {} qs@6.15.2: @@ -4325,13 +4239,6 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - react-dom@19.2.6(react@19.2.6): dependencies: react: 19.2.6 @@ -4339,12 +4246,6 @@ snapshots: react@19.2.6: {} - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -4476,14 +4377,6 @@ snapshots: signal-exit@3.0.7: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - sisteransi@1.0.5: {} source-map-js@1.2.1: {} @@ -4497,34 +4390,13 @@ snapshots: statuses@2.0.2: {} - string_decoder@1.3.0: - 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-json-comments@2.0.1: {} - strnum@2.3.0: {} - tar-fs@2.1.4: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.4 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -4544,10 +4416,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - type-is@2.1.0: dependencies: content-type: 2.0.0 @@ -4587,8 +4455,6 @@ snapshots: unpipe@1.0.0: {} - util-deprecate@1.0.2: {} - vary@1.1.2: {} vfile-message@4.0.3: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f7842d097..9363492f0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,6 @@ allowBuilds: '@google/genai': false - better-sqlite3: true esbuild: true koffi: true msgpackr-extract: false - node-pty: true protobufjs: false diff --git a/scripts/dev-state.ts b/scripts/dev-state.ts index 59e0b04db..e82b8569e 100644 --- a/scripts/dev-state.ts +++ b/scripts/dev-state.ts @@ -10,11 +10,11 @@ import { } from "node:fs/promises"; import { homedir } from "node:os"; import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; -import Database from "better-sqlite3"; import { parse, type ParseError } from "jsonc-parser"; import { migrateLegacyConfig } from "../src/config-migration.js"; import { devspaceConfigSchema, type DevspaceConfig } from "../src/config-schema.js"; import { databasePath } from "../src/db/client.js"; +import { backupSqlite, openSqlite } from "../src/db/sqlite.js"; import { expandHomePath } from "../src/roots.js"; const checkoutRoot = resolve(process.cwd()); @@ -145,10 +145,10 @@ async function readSourceConfig(configDir: string): Promise<{ config: DevspaceCo async function backupDatabase(sourcePath: string, destinationPath: string): Promise { await mkdir(dirname(destinationPath), { recursive: true }); - const source = new Database(sourcePath, { readonly: true, fileMustExist: true }); + const source = openSqlite(sourcePath, { readOnly: true }); try { - await source.backup(destinationPath); + await backupSqlite(source, destinationPath); await chmod(destinationPath, 0o600); } finally { source.close(); diff --git a/scripts/dev.ts b/scripts/dev.ts index a8f2ef06e..6c1300806 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -10,7 +10,7 @@ if (!hasConfig) { console.error("Development state is not initialized. Run `pnpm dev:seed` first."); process.exitCode = 1; } else { - const child = spawn("tsx", ["watch", "--clear-screen=false", "src/cli.ts", "serve"], { + const child = spawn("tsx", ["watch", "--clear-screen=false", "--import", "./bin/silence-sqlite-warning.js", "src/cli.ts", "serve"], { cwd: checkoutRoot, env: { ...process.env, diff --git a/scripts/fix-node-pty-permissions.mjs b/scripts/fix-node-pty-permissions.mjs deleted file mode 100644 index 04fdd8442..000000000 --- a/scripts/fix-node-pty-permissions.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { chmod } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -if (process.platform === "darwin") { - const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); - - for (const architecture of ["arm64", "x64"]) { - const helper = resolve( - projectRoot, - "node_modules", - "node-pty", - "prebuilds", - `darwin-${architecture}`, - "spawn-helper", - ); - - try { - await chmod(helper, 0o755); - } catch (error) { - if (error.code !== "ENOENT") throw error; - } - } -} diff --git a/src/cli-config.test.ts b/src/cli-config.test.ts index d5469460d..3b7444934 100644 --- a/src/cli-config.test.ts +++ b/src/cli-config.test.ts @@ -15,7 +15,7 @@ test("config set tells users to restart the running server", () => { const stdout = execFileSync( "node", - ["--import", "tsx", "src/cli.ts", "config", "set", "publicBaseUrl", "https://example.com"], + ["bin/devspace.js", "config", "set", "publicBaseUrl", "https://example.com"], { encoding: "utf8", env }, ); diff --git a/src/cli.test.ts b/src/cli.test.ts index fdc83722d..02fb343be 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -29,7 +29,7 @@ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.me test("-v and --version print the package version", () => { for (const flag of ["-v", "--version"]) { - const output = execFileSync("node", ["--import", "tsx", "src/cli.ts", flag], { + const output = execFileSync("node", ["bin/devspace.js", flag], { encoding: "utf8", env: { ...process.env, DEVSPACE_CONFIG_DIR: "/tmp/devspace-cli-version-test" }, }).trim(); @@ -204,7 +204,7 @@ const currentWorkspaceEnv = { DEVSPACE_WORKSPACE_ID: "ws_current", DEVSPACE_WORK // Runs the source CLI against the fixture config; pass `{}` to omit the current-workspace variables. function execCli(args: string[], workspaceEnv: NodeJS.ProcessEnv = currentWorkspaceEnv) { - return execFileAsync("node", ["--import", "tsx", "src/cli.ts", ...args], { + return execFileAsync("node", ["bin/devspace.js", ...args], { cwd: process.cwd(), encoding: "utf8", env: { ...process.env, ...cliConfigEnv, ...workspaceEnv }, diff --git a/src/cli.ts b/src/cli.ts index 04c6a5668..9dd793afe 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -83,7 +83,9 @@ type Command = | "version"; const require = createRequire(import.meta.url); -const SUPPORTED_NODE_RANGE = ">=20.12 <27"; + +// SAFETY: package.json always declares engines.node; it is the single source for the supported range. +const SUPPORTED_NODE_RANGE = (require("../package.json") as { engines: { node: string } }).engines.node; async function main(argv: string[]): Promise { assertSupportedNode(); @@ -371,20 +373,6 @@ async function runInit({ force }: { force: boolean }): Promise { } async function serve(): Promise { - const sqliteStatus = checkSqliteNative(); - - if (sqliteStatus !== "ok") { - throw new Error( - [ - "better-sqlite3 could not load for this Node runtime.", - sqliteStatus, - "", - "Try reinstalling or rebuilding dependencies under the active Node version:", - " npm rebuild better-sqlite3", - ].join("\n"), - ); - } - const config = loadConfig(); await runStartupWorktreeCleanup(config); const { configureHttpServer, createServer } = await import("./server.js"); @@ -474,11 +462,10 @@ async function runDoctor(): Promise { console.log(`Config file: ${files.configExists ? files.configPath : "missing"}`); console.log(`Auth file: ${files.authExists ? files.authPath : "missing"}`); console.log(`Node: ${process.version} (${nodeVersionStatus()})`); - console.log(`Node ABI: ${process.versions.modules}`); console.log(`Platform: ${process.platform} ${process.arch}`); console.log(`Git: ${checkGitAvailable()}`); console.log(`Bash shell: ${checkBashShell()}`); - console.log(`SQLite native dependency: ${checkSqliteNative()}`); + console.log(`SQLite: ${await checkSqlite()}`); try { const config = loadConfig(); @@ -1250,14 +1237,15 @@ function nodeVersionStatus(): string { class SetupCancelledError extends Error {} -function checkSqliteNative(): string { +async function checkSqlite(): Promise { try { - // SAFETY: The surrounding checks or construction already match this type. - const Database = require("better-sqlite3") as typeof import("better-sqlite3"); - const db = new Database(":memory:"); + const { openSqlite } = await import("./db/sqlite.js"); + const db = openSqlite(":memory:"); + // SAFETY: sqlite_version() always returns one text column. + const { version } = db.prepare("select sqlite_version() as version").get() as { version: string }; db.close(); - return "ok"; + return `ok (node:sqlite ${version})`; } catch (error) { return error instanceof Error ? error.message : String(error); } diff --git a/src/db/client.ts b/src/db/client.ts index 5711ffdd4..eeaf3bb51 100644 --- a/src/db/client.ts +++ b/src/db/client.ts @@ -1,11 +1,10 @@ import { chmodSync, mkdirSync } from "node:fs"; import { join } from "node:path"; -import Database from "better-sqlite3"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import * as schema from "./schema.js"; +import { drizzle } from "drizzle-orm/node-sqlite"; import { migrateDatabase } from "./migrations.js"; +import { openSqlite, type SqliteDatabase } from "./sqlite.js"; -export type SqliteDatabase = Database.Database; +export { transaction, type SqliteDatabase } from "./sqlite.js"; export type AppDatabase = ReturnType; @@ -23,12 +22,14 @@ export function openDatabase(stateDir: string): DatabaseHandle { mkdirSync(stateDir, { recursive: true, mode: 0o700 }); chmodSync(stateDir, 0o700); const path = databasePath(stateDir); - const sqlite = new Database(path); + const sqlite = openSqlite(path); chmodSync(path, 0o600); - sqlite.pragma("journal_mode = WAL"); - sqlite.pragma("synchronous = NORMAL"); - sqlite.pragma("busy_timeout = 5000"); - sqlite.pragma("foreign_keys = ON"); + sqlite.exec(` + pragma journal_mode = WAL; + pragma synchronous = NORMAL; + pragma busy_timeout = 5000; + pragma foreign_keys = ON; + `); migrateDatabase(sqlite); return { @@ -39,5 +40,5 @@ export function openDatabase(stateDir: string): DatabaseHandle { } function createDrizzleDatabase(sqlite: SqliteDatabase) { - return drizzle(sqlite, { schema }); + return drizzle({ client: sqlite }); } diff --git a/src/db/migrations.ts b/src/db/migrations.ts index ab04c39a9..b00e2590b 100644 --- a/src/db/migrations.ts +++ b/src/db/migrations.ts @@ -1,9 +1,9 @@ -import type Database from "better-sqlite3"; +import { transaction, type SqliteDatabase } from "./sqlite.js"; interface Migration { version: number; name: string; - up(sqlite: Database.Database): void; + up(sqlite: SqliteDatabase): void; } const migrations: Migration[] = [ @@ -114,8 +114,8 @@ const migrations: Migration[] = [ }, ]; -export function migrateDatabase(sqlite: Database.Database): void { - const migrate = sqlite.transaction(() => { +export function migrateDatabase(sqlite: SqliteDatabase): void { + transaction(sqlite, () => { sqlite.exec(` create table if not exists devspace_schema_migrations ( version integer primary key, @@ -158,12 +158,10 @@ export function migrateDatabase(sqlite: Database.Database): void { migration.up(sqlite); recordMigration.run(migration.version, migration.name, new Date().toISOString()); } - }); - - migrate.immediate(); + }, "immediate"); } -function migrateWorkspaceState(sqlite: Database.Database): void { +function migrateWorkspaceState(sqlite: SqliteDatabase): void { sqlite.exec(` create table if not exists workspace_sessions ( id text primary key, @@ -208,7 +206,7 @@ function migrateWorkspaceState(sqlite: Database.Database): void { addColumnIfMissing(sqlite, "workspace_sessions", "managed", "text not null default 'false'"); } -function migrateOAuthState(sqlite: Database.Database): void { +function migrateOAuthState(sqlite: SqliteDatabase): void { sqlite.exec(` create table if not exists oauth_clients ( client_id text primary key, @@ -251,7 +249,7 @@ function migrateOAuthState(sqlite: Database.Database): void { `); } -function migrateLocalAgentSessions(sqlite: Database.Database): void { +function migrateLocalAgentSessions(sqlite: SqliteDatabase): void { sqlite.exec(` create table if not exists local_agent_sessions ( id text primary key, @@ -282,7 +280,7 @@ function migrateLocalAgentSessions(sqlite: Database.Database): void { addColumnIfMissing(sqlite, "local_agent_sessions", "effort", "text"); } -function migrateWorkspaceConversationBindings(sqlite: Database.Database): void { +function migrateWorkspaceConversationBindings(sqlite: SqliteDatabase): void { sqlite.exec(` create table if not exists workspace_conversation_bindings ( conversation_scope_id text not null, @@ -301,12 +299,12 @@ function migrateWorkspaceConversationBindings(sqlite: Database.Database): void { `); } -function migrateLocalAgentStructuredErrors(sqlite: Database.Database): void { +function migrateLocalAgentStructuredErrors(sqlite: SqliteDatabase): void { addColumnIfMissing(sqlite, "local_agent_sessions", "error_code", "text"); addColumnIfMissing(sqlite, "local_agent_sessions", "error_retryable", "text"); } -function migrateLocalAgentEffortRename(sqlite: Database.Database): void { +function migrateLocalAgentEffortRename(sqlite: SqliteDatabase): void { // SAFETY: The surrounding checks or construction already match this type. const columns = sqlite.prepare("pragma table_info(local_agent_sessions)").all() as Array<{ name: string; @@ -335,7 +333,7 @@ function migrateLocalAgentEffortRename(sqlite: Database.Database): void { sqlite.exec("alter table local_agent_sessions rename column thinking to effort"); } -function migrateWorkspaceRecoveryState(sqlite: Database.Database): void { +function migrateWorkspaceRecoveryState(sqlite: SqliteDatabase): void { const workspaceStateExists = sqlite .prepare("select 1 from sqlite_master where type = 'table' and name = 'workspace_sessions'") .get(); @@ -345,7 +343,7 @@ function migrateWorkspaceRecoveryState(sqlite: Database.Database): void { addColumnIfMissing(sqlite, "workspace_sessions", "recovery_kind", "text"); } -function migrateLocalAgentTurns(sqlite: Database.Database): void { +function migrateLocalAgentTurns(sqlite: SqliteDatabase): void { sqlite.exec(` create table if not exists local_agent_turns ( id integer primary key autoincrement, @@ -369,7 +367,7 @@ function migrateLocalAgentTurns(sqlite: Database.Database): void { `); } -function migrateLocalAgentProviderInstances(sqlite: Database.Database): void { +function migrateLocalAgentProviderInstances(sqlite: SqliteDatabase): void { // SAFETY: The surrounding checks or construction already match this type. const columns = sqlite.prepare("pragma table_info(local_agent_sessions)").all() as Array<{ name: string; @@ -389,7 +387,7 @@ function migrateLocalAgentProviderInstances(sqlite: Database.Database): void { `); } -function migrateLocalAgentGenericAcpDriver(sqlite: Database.Database): void { +function migrateLocalAgentGenericAcpDriver(sqlite: SqliteDatabase): void { sqlite.exec(` update local_agent_sessions set driver = 'acp' @@ -397,7 +395,7 @@ function migrateLocalAgentGenericAcpDriver(sqlite: Database.Database): void { `); } -function migrateLocalAgentTurnEvents(sqlite: Database.Database): void { +function migrateLocalAgentTurnEvents(sqlite: SqliteDatabase): void { addColumnIfMissing(sqlite, "local_agent_turns", "turn_id", "text"); addColumnIfMissing(sqlite, "local_agent_turns", "error_detail", "text"); addColumnIfMissing(sqlite, "local_agent_sessions", "latest_turn_id", "text"); @@ -428,7 +426,7 @@ function migrateLocalAgentTurnEvents(sqlite: Database.Database): void { `); } -function migrateWorkspaceActivity(sqlite: Database.Database): void { +function migrateWorkspaceActivity(sqlite: SqliteDatabase): void { sqlite.exec(` create table if not exists workspace_turns ( id integer primary key autoincrement, @@ -475,7 +473,7 @@ function migrateWorkspaceActivity(sqlite: Database.Database): void { } function addColumnIfMissing( - sqlite: Database.Database, + sqlite: SqliteDatabase, table: "workspace_sessions" | "local_agent_sessions" | "local_agent_turns", column: string, definition: string, diff --git a/src/db/sqlite.ts b/src/db/sqlite.ts new file mode 100644 index 000000000..653176f17 --- /dev/null +++ b/src/db/sqlite.ts @@ -0,0 +1,49 @@ +import { backup, DatabaseSync, type DatabaseSyncOptions } from "node:sqlite"; + +export type SqliteDatabase = DatabaseSync; + +export function openSqlite(path: string, options: DatabaseSyncOptions = {}): SqliteDatabase { + return new DatabaseSync(path, options); +} + +/** Copies a live database to `path` using SQLite's online backup API. */ +export async function backupSqlite(source: SqliteDatabase, path: string): Promise { + await backup(source, path); +} + +let savepointCount = 0; + +/** Runs `run` atomically; nested calls become savepoints so an inner failure rolls back only its own work. */ +export function transaction( + database: SqliteDatabase, + run: () => T, + mode: "deferred" | "immediate" = "deferred", +): T { + if (database.isTransaction) { + const savepoint = `devspace_${++savepointCount}`; + database.exec(`savepoint ${savepoint}`); + + try { + const result = run(); + database.exec(`release ${savepoint}`); + + return result; + } catch (error) { + database.exec(`rollback to ${savepoint}`); + database.exec(`release ${savepoint}`); + throw error; + } + } + + database.exec(mode === "immediate" ? "begin immediate" : "begin"); + + try { + const result = run(); + database.exec("commit"); + + return result; + } catch (error) { + if (database.isTransaction) database.exec("rollback"); + throw error; + } +} diff --git a/src/dev-state.test.ts b/src/dev-state.test.ts index cca18933b..aa1027e07 100644 --- a/src/dev-state.test.ts +++ b/src/dev-state.test.ts @@ -6,7 +6,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import test, { after } from "node:test"; import { fileURLToPath } from "node:url"; -import Database from "better-sqlite3"; +import { openSqlite } from "./db/sqlite.js"; const root = await mkdtemp(join(tmpdir(), "devspace-dev-state-test-")); const checkoutRoot = join(root, "checkout"); @@ -30,7 +30,7 @@ test("seed copies source config and state, and reset reseeds only once source au })); await writeFile(join(sourceConfigDir, "skills", "example", "SKILL.md"), "example skill\n"); - const sourceDatabase = new Database(join(sourceStateDir, "devspace.sqlite")); + const sourceDatabase = openSqlite(join(sourceStateDir, "devspace.sqlite")); sourceDatabase.exec("create table marker (value text); insert into marker values ('source')"); sourceDatabase.close(); @@ -51,8 +51,8 @@ test("seed copies source config and state, and reset reseeds only once source au assert.equal(existsSync(join(devRoot, "config", "skills", "example", "SKILL.md")), true); const localDatabasePath = join(devRoot, "state", "devspace.sqlite"); - const localDatabase = new Database(localDatabasePath); - assert.equal(localDatabase.prepare("select value from marker").pluck().get(), "source"); + const localDatabase = openSqlite(localDatabasePath); + assert.equal(localDatabase.prepare("select value from marker").get()?.value, "source"); localDatabase.exec("insert into marker values ('local-only')"); localDatabase.close(); @@ -61,11 +61,11 @@ test("seed copies source config and state, and reset reseeds only once source au await rm(join(sourceConfigDir, "auth.json")); await assert.rejects(runDevState("reset"), /No auth\.json found/); - const preservedDatabase = new Database(localDatabasePath, { readonly: true }); + const preservedDatabase = openSqlite(localDatabasePath, { readOnly: true }); try { assert.deepEqual( - preservedDatabase.prepare("select value from marker order by rowid").pluck().all(), + preservedDatabase.prepare("select value from marker order by rowid").all().map((row) => row.value), ["source", "local-only"], ); } finally { @@ -77,8 +77,8 @@ test("seed copies source config and state, and reset reseeds only once source au })); await runDevState("reset"); - const resetDatabase = new Database(localDatabasePath, { readonly: true }); - assert.deepEqual(resetDatabase.prepare("select value from marker order by rowid").pluck().all(), ["source"]); + const resetDatabase = openSqlite(localDatabasePath, { readOnly: true }); + assert.deepEqual(resetDatabase.prepare("select value from marker order by rowid").all().map((row) => row.value), ["source"]); resetDatabase.close(); }); diff --git a/src/local-agent-client.ts b/src/local-agent-client.ts index 36880d927..078071344 100644 --- a/src/local-agent-client.ts +++ b/src/local-agent-client.ts @@ -702,7 +702,14 @@ export function spawnLocalAgentDaemon( ): void { const entrypoint = resolveDaemonEntrypoint(); - const child = spawn(process.execPath, [...daemonExecArgv(process.execArgv), entrypoint], { + const silenceSqliteWarning = new URL("../bin/silence-sqlite-warning.js", import.meta.url).href; + + const child = spawn(process.execPath, [ + ...daemonExecArgv(process.execArgv), + "--import", + silenceSqliteWarning, + entrypoint, + ], { detached: true, stdio: "ignore", windowsHide: true, diff --git a/src/local-agent-store.test.ts b/src/local-agent-store.test.ts index c917496c9..fb48ed219 100644 --- a/src/local-agent-store.test.ts +++ b/src/local-agent-store.test.ts @@ -5,7 +5,7 @@ import { mock } from "node:test"; import { syncBuiltinESMExports } from "node:module"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import Database from "better-sqlite3"; +import { openSqlite } from "./db/sqlite.js"; import { databasePath } from "./db/client.js"; import { LocalAgentStore } from "./local-agent-store.js"; @@ -216,7 +216,7 @@ assert.deepEqual(store.list({ workspaceId: "ws_1", workspaceRoot: join(root, "ot const legacyStateDir = join(root, "legacy-state"); mkdirSync(legacyStateDir, { recursive: true }); - const legacy = new Database(databasePath(legacyStateDir)); + const legacy = openSqlite(databasePath(legacyStateDir)); legacy.exec(` create table devspace_schema_migrations ( version integer primary key, @@ -274,7 +274,7 @@ assert.deepEqual(store.list({ workspaceId: "ws_1", workspaceRoot: join(root, "ot assert.equal(legacyRecord?.effort, "high"); assert.equal(legacyRecord?.providerInstanceId, "cursor"); assert.equal(legacyRecord?.driver, "acp"); - const inspect = new Database(databasePath(legacyStateDir), { readonly: true }); + const inspect = openSqlite(databasePath(legacyStateDir), { readOnly: true }); // SAFETY: The surrounding checks or construction already match this type. const driverColumn = (inspect.prepare("pragma table_info(local_agent_sessions)").all() as Array<{ name: string; notnull: number }>) diff --git a/src/local-agent-store.ts b/src/local-agent-store.ts index 1d4294820..8d0670481 100644 --- a/src/local-agent-store.ts +++ b/src/local-agent-store.ts @@ -1,7 +1,8 @@ import { randomUUID } from "node:crypto"; import { resolve } from "node:path"; import { Result, type Result as BetterResult } from "better-result"; -import { openDatabase, type DatabaseHandle } from "./db/client.js"; +import { openDatabase, transaction, type DatabaseHandle } from "./db/client.js"; +import { unsafeNarrow } from "./unsafe-narrow.js"; import { AgentStoreError, isProgrammerDefect } from "./local-agent-errors.js"; import type { LocalAgentTurnEvent, LocalAgentTurnEventBody } from "./local-agent-events.js"; import { validateAgentId } from "./local-agent-id.js"; @@ -165,36 +166,36 @@ export class LocalAgentStore { if (scope.workspaceId && scope.workspaceRoot) { // SAFETY: The surrounding checks or construction already match this type. - rows = this.database.sqlite + rows = unsafeNarrow(this.database.sqlite .prepare( `select * from local_agent_sessions where workspace_id = ? and workspace_root = ? order by updated_at desc`, ) - .all(scope.workspaceId, resolve(scope.workspaceRoot)) as LocalAgentRow[]; + .all(scope.workspaceId, resolve(scope.workspaceRoot))); } else if (scope.workspaceId) { // SAFETY: The surrounding checks or construction already match this type. - rows = this.database.sqlite + rows = unsafeNarrow(this.database.sqlite .prepare( `select * from local_agent_sessions where workspace_id = ? order by updated_at desc`, ) - .all(scope.workspaceId) as LocalAgentRow[]; + .all(scope.workspaceId)); } else if (scope.workspaceRoot) { // SAFETY: The surrounding checks or construction already match this type. - rows = this.database.sqlite + rows = unsafeNarrow(this.database.sqlite .prepare( `select * from local_agent_sessions where workspace_root = ? order by updated_at desc`, ) - .all(resolve(scope.workspaceRoot)) as LocalAgentRow[]; + .all(resolve(scope.workspaceRoot))); } else { // SAFETY: The surrounding checks or construction already match this type. - rows = this.database.sqlite + rows = unsafeNarrow(this.database.sqlite .prepare("select * from local_agent_sessions order by updated_at desc") - .all() as LocalAgentRow[]; + .all()); } return rows.map(rowToLocalAgentRecord); @@ -209,7 +210,7 @@ export class LocalAgentStore { } private createDispatch(input: CreateLocalAgentRecordInput): CreatedLocalAgentRecord { - return this.database.sqlite.transaction(() => { + return transaction(this.database.sqlite, () => { const generate = () => `agt_${randomUUID().replaceAll("-", "").slice(0, 8)}`; let id = input.id ?? generate(); validateAgentId(id); @@ -282,7 +283,7 @@ export class LocalAgentStore { ); return { record, created: true }; - }).immediate(); + }, "immediate"); } createResult(input: CreateLocalAgentRecordInput): BetterResult { @@ -396,7 +397,7 @@ export class LocalAgentStore { } beginTurn(agentId: string, input: BeginLocalAgentTurnInput): BegunLocalAgentTurn { - return this.database.sqlite.transaction(() => { + return transaction(this.database.sqlite, () => { const current = this.getById(agentId); if (!current) throw new Error(`Unknown subagent id: ${agentId}`); @@ -444,7 +445,7 @@ export class LocalAgentStore { if (!turn) throw new Error(`Unable to load the new turn for subagent ${agentId}.`); return { agent, turn }; - }).immediate(); + }, "immediate"); } beginTurnResult( @@ -459,7 +460,7 @@ export class LocalAgentStore { turnId: number, completion: FinishLocalAgentTurnInput, ): LocalAgentRecord { - return this.database.sqlite.transaction(() => { + return transaction(this.database.sqlite, () => { const turn = this.getTurnById(turnId); if (!turn || turn.agentId !== agentId) { @@ -521,7 +522,7 @@ export class LocalAgentStore { errorRetryable: completion.errorRetryable, errorDetail: completion.errorDetail, }); - }).immediate(); + }, "immediate"); } finishTurnResult( @@ -582,13 +583,13 @@ export class LocalAgentStore { listTurnEvents(turn: LocalAgentTurnRecord, afterSeq = 0): LocalAgentTurnEvent[] { // SAFETY: The surrounding checks or construction already match this type. - const rows = this.database.sqlite + const rows = unsafeNarrow(this.database.sqlite .prepare( `select seq, type, data, created_at from local_agent_turn_events where turn_row_id = ? and seq > ? order by seq asc`, ) - .all(turn.id, afterSeq) as LocalAgentTurnEventRow[]; + .all(turn.id, afterSeq)); return rows.map((row) => ({ // SAFETY: The surrounding checks or construction already match this type. @@ -623,15 +624,15 @@ export class LocalAgentStore { listTurns(agentId: string): LocalAgentTurnRecord[] { // SAFETY: The surrounding checks or construction already match this type. - const rows = this.database.sqlite + const rows = unsafeNarrow(this.database.sqlite .prepare("select * from local_agent_turns where agent_id = ? order by id asc") - .all(agentId) as LocalAgentTurnRow[]; + .all(agentId)); return rows.map(rowToLocalAgentTurnRecord); } reconcileActiveRuns(message = "DevSpace restarted while this agent turn was running."): number { - return this.database.sqlite.transaction(() => { + return transaction(this.database.sqlite, () => { const now = new Date().toISOString(); this.database.sqlite .prepare( @@ -651,7 +652,7 @@ export class LocalAgentStore { .run(message, now); return Number(result.changes); - }).immediate(); + }, "immediate"); } reconcileActiveRunsResult( diff --git a/src/oauth-store.test.ts b/src/oauth-store.test.ts index edb47bf9f..b580639fc 100644 --- a/src/oauth-store.test.ts +++ b/src/oauth-store.test.ts @@ -57,10 +57,10 @@ async function testDatabaseConfiguration(stateDir: string): Promise { const database = openDatabase(stateDir); try { - assert.equal(database.sqlite.pragma("journal_mode", { simple: true }), "wal"); - assert.equal(database.sqlite.pragma("synchronous", { simple: true }), 1); - assert.equal(database.sqlite.pragma("busy_timeout", { simple: true }), 5000); - assert.equal(database.sqlite.pragma("foreign_keys", { simple: true }), 1); + assert.equal(database.sqlite.prepare("pragma journal_mode").get()?.journal_mode, "wal"); + assert.equal(database.sqlite.prepare("pragma synchronous").get()?.synchronous, 1); + assert.equal(database.sqlite.prepare("pragma busy_timeout").get()?.timeout, 5000); + assert.equal(database.sqlite.prepare("pragma foreign_keys").get()?.foreign_keys, 1); const migrations = database.sqlite .prepare("select version, name from devspace_schema_migrations order by version") @@ -115,17 +115,15 @@ function testPersistenceAndTokenHashing(stateDir: string): void { const database = openDatabase(stateDir); try { - // SAFETY: The surrounding checks or construction already match this type. const accessHashes = database.sqlite .prepare("select token_hash from oauth_access_tokens") - .pluck() - .all() as string[]; + .all() + .map((row) => row.token_hash); - // SAFETY: The surrounding checks or construction already match this type. const refreshHashes = database.sqlite .prepare("select token_hash from oauth_refresh_tokens") - .pluck() - .all() as string[]; + .all() + .map((row) => row.token_hash); assert.deepEqual(accessHashes, [hashToken(accessToken)]); assert.deepEqual(refreshHashes, [hashToken(refreshToken)]); diff --git a/src/oauth-store.ts b/src/oauth-store.ts index 26f6fc43d..a1e548a5d 100644 --- a/src/oauth-store.ts +++ b/src/oauth-store.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import type { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js"; import { InvalidRequestError } from "@modelcontextprotocol/sdk/server/auth/errors.js"; import type { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; -import { openDatabase, type DatabaseHandle } from "./db/client.js"; +import { openDatabase, transaction, type DatabaseHandle } from "./db/client.js"; export interface PersistedAccessTokenRecord { clientId: string; @@ -146,7 +146,7 @@ export class SqliteOAuthStore { } saveTokenPair(pair: PersistedTokenPair, consumedRefreshTokenHash?: string): boolean { - const save = this.database.sqlite.transaction(() => { + return transaction(this.database.sqlite, () => { if (consumedRefreshTokenHash) { const now = Math.floor(Date.now() / 1000); @@ -166,9 +166,7 @@ export class SqliteOAuthStore { this.saveRefreshToken(pair.refreshTokenHash, pair.refreshToken); return true; - }); - - return save.immediate(); + }, "immediate"); } getRefreshToken(tokenHash: string): PersistedRefreshTokenRecord | undefined { diff --git a/src/process-sessions.ts b/src/process-sessions.ts index e8301b5e8..4502c7e54 100644 --- a/src/process-sessions.ts +++ b/src/process-sessions.ts @@ -455,16 +455,16 @@ export class ProcessSessionManager { } private async startPty(session: ProcessSession, input: StartCommandInput): Promise { - let nodePty: typeof import("node-pty"); + let nodePty: typeof import("@lydell/node-pty"); try { - nodePty = await import("node-pty"); + nodePty = await import("@lydell/node-pty"); } catch { - throw new Error("PTY support requires the optional node-pty dependency."); + throw new Error("PTY support requires the optional @lydell/node-pty dependency."); } const shell = resolveShellCommand(input.command); - let pty: import("node-pty").IPty; + let pty: import("@lydell/node-pty").IPty; pty = nodePty.spawn(shell.executable, shell.args, { cwd: input.cwd, diff --git a/src/workflow-store.ts b/src/workflow-store.ts index 0b74bbb0c..72b6f0e4f 100644 --- a/src/workflow-store.ts +++ b/src/workflow-store.ts @@ -1,5 +1,5 @@ import type { WireInput } from "./json-value.js"; -import { openDatabase, type DatabaseHandle } from "./db/client.js"; +import { openDatabase, transaction, type DatabaseHandle } from "./db/client.js"; import type { LocalAgentWorkspaceScope } from "./local-agent-store.js"; import { WorkflowError, type WorkflowCall, type WorkflowEvent, type WorkflowSnapshot } from "./workflow-types.js"; @@ -34,14 +34,14 @@ export class WorkflowStore { return run; } addCall(call: WorkflowCall): void { - this.database.sqlite.transaction(() => { + transaction(this.database.sqlite, () => { const run = this.get(call.runId)!; this.database.sqlite.prepare("insert into workflow_calls (run_id, call_index, agent_id, record_json) values (?, ?, ?, ?)") .run(call.runId, call.index, call.agentId, JSON.stringify(call)); run.callCount++; run.updatedAt = new Date().toISOString(); this.database.sqlite.prepare("update workflow_runs set record_json = ? where id = ?").run(JSON.stringify(run), run.id); - })(); + }); } calls(runId: string): WorkflowCall[] { return this.database.sqlite.prepare("select record_json from workflow_calls where run_id = ? order by call_index").all(runId) diff --git a/src/workspace-activity-store.ts b/src/workspace-activity-store.ts index 03d24bead..699ba8e63 100644 --- a/src/workspace-activity-store.ts +++ b/src/workspace-activity-store.ts @@ -1,6 +1,6 @@ import { optionalFields } from "./json-value.js"; import { and, asc, count, desc, eq, max, sql } from "drizzle-orm"; -import { openDatabase, type DatabaseHandle } from "./db/client.js"; +import { openDatabase, transaction, type DatabaseHandle } from "./db/client.js"; import { workspaceSessions, workspaceTurns, @@ -93,7 +93,7 @@ export class WorkspaceActivityStore { /** Returns the workspace's open turn, starting the next one when none is open. */ openTurn(workspaceId: string, now = new Date()): WorkspaceTurn { - const open = this.database.sqlite.transaction(() => { + const open = transaction(this.database.sqlite, () => { const existing = this.findOpenTurnRow(workspaceId); if (existing) return existing; @@ -114,9 +114,9 @@ export class WorkspaceActivityStore { }) .returning() .get(); - }); + }, "immediate"); - return this.withCallCounts(open.immediate()); + return this.withCallCounts(open); } currentTurn(workspaceId: string): WorkspaceTurn | undefined {