diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..200b94a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgresql://driftlock:driftlock@localhost:5432/driftlock diff --git a/README.md b/README.md index 8389ee5..6105da8 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ # DriftLock -**Dependabot for API changes.** +**Self-maintaining APIs.** -Your vendor renames a field. Your code breaks silently. You find out at 2am. +API providers announce changes. DriftLock applies them to your codebase. -DriftLock notices the change before you do, opens a PR with the fix, and you review and merge. +When Stripe ships a breaking change or a new feature, DriftLock scans your codebase, identifies affected usages, and opens a PR with the fix. [Website](https://driftlock.dev) · [Discord](https://discord.gg/driftlock) · [Issues](https://github.com/nerdev-co/DriftLock/issues) @@ -22,35 +22,50 @@ DriftLock notices the change before you do, opens a PR with the fix, and you rev --- -## Why DriftLock +```mermaid +flowchart LR + A[Vendor API Changes] --> B[DriftLock] + B --> C[Find Affected Code] + C --> D[Understand API Diff] + D --> E[Generate Fix] + E --> F[Pull Request] + F --> G[You Review & Merge] +``` -You already use Dependabot for dependency updates. Renovate for version bumps. CodeRabbit for AI review. +--- -But when Stripe renames `charge.amount` to `charge.value` — nothing catches it. +## Why DriftLock -Changelogs don't get read. Docs drift from reality. SDK migration guides sit in bookmarks you'll never open. **30%+ of downtime at a major cloud provider was traced to unnoticed external API changes.** +API communication is broken. Breaking changes ship with little warning. Useful features quietly launch and go unnoticed. Changelogs don't get read. -The cost always lands on you — the consumer — not the vendor who made the change. +The cost always lands on you (the consumer), not the vendor who made the change. -DriftLock fills the gap: it watches your actual API usage, compares it against what the vendor's API _actually returns_ today, and opens a PR when they diverge. No vendor cooperation. No spec publication. No manual doc-checking. +DriftLock makes APIs self-maintaining. When a vendor changes something, your codebase updates automatically. You review the PR and merge. No manual scanning. No migration guides. No 2am pages. --- ## How it works -```text -Install GitHub App → Discover call sites → Classify tests → -Probe sandbox → Diff specs → Open PR → Report coverage +```mermaid +flowchart LR + A[Install GitHub App] --> B[Discover Call Sites] + B --> C[Classify Tests] + C --> D[Probe API] + D --> E[Diff API Shapes] + E --> F[Generate Fix PR] + F --> G[Review & Merge] ``` -| Step | What happens | -| ------------ | ------------------------------------------------------------- | -| **Discover** | Static analysis finds every `stripe.*` call in your codebase | -| **Classify** | Identifies which tests hit real sandbox vs. mocked | -| **Probe** | Runs your tests, captures actual request/response shapes | -| **Diff** | Compares new snapshot against previous — shape change = drift | -| **Fix** | Opens a PR with the diff and a suggested fix | -| **Report** | Shows which call sites are monitored, blind, or untested | +| Step | What happens | +| ------------ | -------------------------------------------------------- | +| **Discover** | Static analysis finds every API call in your codebase | +| **Classify** | Identifies which tests hit real sandbox vs. mocked | +| **Probe** | Runs your tests, captures actual request/response shapes | +| **Diff** | Compares current shapes against target version | +| **Fix** | Opens PRs with the diffs and suggested fixes | +| **Report** | Shows which call sites are monitored, blind, or untested | + +The goal: when Stripe ships a change, your codebase updates automatically. You just review and merge. --- @@ -76,21 +91,49 @@ driftlock fix ./repo ## What you're used to vs. what DriftLock does -| Today | With DriftLock | -| -------------------------------------- | --------------------------------------- | -| Read changelogs manually (you don't) | Automated drift detection | -| Find out when prod breaks | Get a PR before it breaks | -| "Something changed, no idea what" | "Field X renamed to Y on this endpoint" | -| Fix it yourself, hope you got it right | Suggested fix, ready to merge | -| No idea which tests are real | Coverage report per call site | +| Today | With DriftLock | +| -------------------------------------- | -------------------------------------- | +| Avoid upgrades because they're tedious | Automated codebase scanning | +| Manually find affected call sites | All affected calls found automatically | +| Copy-paste migration guide changes | Fix diffs generated and ready to merge | +| Weeks to upgrade, so you put it off | Minutes to review a PR | +| Stuck on old versions | Stay current with minimal effort | + +--- + +## Why not Renovate / Dependabot? + +They update the version number in `package.json`. They don't change your code. + +When `stripe.charges.create({ amount: 100 })` needs to become `stripe.charges.create({ value: 100 })`, Renovate doesn't touch that. DriftLock does. + +| Renovate | DriftLock | +| --------------------- | --------------------------- | +| Bumps version | Updates your code | +| Handles `npm install` | Handles call site migration | +| Dependency management | Code migration | + +--- + +## Why not just semver? + +Semver is a convention, not a guarantee. Many APIs don't follow it strictly. And even when they do, upgrading major versions means manually finding and fixing every affected call site — which is why teams avoid it. + +DriftLock works regardless of versioning scheme. It monitors the actual API surface, not the version number. + +--- + +## Why not just test coverage? + +High test coverage helps — if your tests aren't mocked. Most are. DriftLock classifies which tests actually hit the real API vs. which just mock the response. You can't catch API drift with mocked tests. --- ## First target: Stripe -Stripe has mature test mode, huge installed base, predictable API versioning, and plenty of design partners. +Stripe has mature test mode, huge installed base, and plenty of teams stuck on old API versions. First vendor — not the only one. -Support for Twilio, Shopify, and others is on the roadmap. +Twilio, Shopify, and others are on the roadmap. --- diff --git a/apps/cli/package.json b/apps/cli/package.json index cfac633..4c835d9 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -11,9 +11,7 @@ "scripts": { "build": "bun build ./index.ts --outdir ./dist --target node --external @driftlock/* --external ssh2 --external cpu-features", "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts", - "test": "bun test", - "test:watch": "bun test --watch" + "lint": "eslint . --ext .ts" }, "dependencies": { "@driftlock/agent": "workspace:*", diff --git a/bun.lock b/bun.lock index 43371ac..4654b53 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,6 @@ "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "c8": "^9.1.0", - "drizzle-kit": "^0.31.10", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "prettier": "^3.2.0", @@ -58,6 +57,20 @@ "typescript": "^5.3.0", }, }, + "packages/db": { + "name": "@driftlock/db", + "version": "0.1.0", + "dependencies": { + "@driftlock/core": "workspace:*", + "drizzle-orm": "^0.36.0", + "postgres": "^3.4.0", + }, + "devDependencies": { + "@types/node": "^20.11.0", + "drizzle-kit": "^0.28.0", + "typescript": "^5.3.0", + }, + }, "packages/git": { "name": "@driftlock/git", "version": "0.1.0", @@ -126,6 +139,8 @@ "@driftlock/core": ["@driftlock/core@workspace:packages/core"], + "@driftlock/db": ["@driftlock/db@workspace:packages/db"], + "@driftlock/git": ["@driftlock/git@workspace:packages/git"], "@driftlock/parser": ["@driftlock/parser@workspace:packages/parser"], @@ -140,57 +155,51 @@ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], @@ -396,7 +405,9 @@ "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], - "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], + "drizzle-kit": ["drizzle-kit@0.28.1", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ=="], + + "drizzle-orm": ["drizzle-orm@0.36.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@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", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -412,7 +423,9 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -474,8 +487,6 @@ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], @@ -636,6 +647,8 @@ "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], @@ -728,8 +741,6 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsx": ["tsx@4.23.13", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw=="], - "turbo": ["turbo@1.13.4", "", { "optionalDependencies": { "turbo-darwin-64": "1.13.4", "turbo-darwin-arm64": "1.13.4", "turbo-linux-64": "1.13.4", "turbo-linux-arm64": "1.13.4", "turbo-windows-64": "1.13.4", "turbo-windows-arm64": "1.13.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-1q7+9UJABuBAHrcC4Sxp5lOqYS5mvxRrwa33wpIyM18hlOCpRD/fTJNxZ0vhbMcJmz15o9kkVm743mPn7p6jpQ=="], "turbo-darwin-64": ["turbo-darwin-64@1.13.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-A0eKd73R7CGnRinTiS7txkMElg+R5rKFp9HV7baDiEL4xTG1FIg/56Vm7A5RVgg8UNgG2qNnrfatJtb+dRmNdw=="], @@ -814,8 +825,6 @@ "ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - "tsx/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -880,58 +889,6 @@ "ora/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - "inquirer/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], "inquirer/ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7f72095 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + postgres: + image: postgres:16 + ports: + - "5432:5432" + environment: + POSTGRES_USER: driftlock + POSTGRES_PASSWORD: driftlock + POSTGRES_DB: driftlock + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: diff --git a/docs/competitive-analysis.md b/docs/competitive-analysis.md new file mode 100644 index 0000000..6331c29 --- /dev/null +++ b/docs/competitive-analysis.md @@ -0,0 +1,222 @@ +# Competitive Analysis: Self-Maintaining APIs + +> **Last updated:** September 2026 +> **Status:** Active research — we reviewed these projects silently to avoid repeating their mistakes and to understand the landscape before building. + +--- + +## Why this exists + +The "self-maintaining APIs" concept is gaining traction. YC published an RFS for it. Multiple teams are building solutions. We tracked their development quietly to: + +1. Understand what approaches work and what don't +2. Avoid architectural mistakes others have made +3. Identify gaps we can fill +4. Reference their work transparently + +This is not a threat analysis — it's market intelligence. + +--- + +## The landscape + +| Project | Stage | Approach | Language | LLM | Traction | +|---------|-------|----------|----------|-----|----------| +| **DriftLock** (us) | Active development | Static analysis + sandbox snapshots + diff | TypeScript/Bun | OpenAI | Building | +| **Ripple** (Aakash2408) | Demo stage | Spec diff + consumer finding + fix gen | Go (inferred) | Claude | 0 stars, private core | +| **HelpPR** (pedapudi-pavansai) | Most mature | OpenAPI monitoring + static analysis + LLM | Python + React | Claude | 0 stars, 28 commits | +| **banningwill-AdAstra** | Prototype | AST visitor + fix gen | Python | Claude | 0 stars, 2 commits | +| **RajaDheeraj** | Prototype | Agent-based (regex + LLM) | Python + React | Gemini | 0 stars, 3 commits | +| **Ability.ai** | Platform play | Agent runtime + knowledge graph | N/A | Multi-model | 544 stars (Trinity) | + +--- + +## Detailed analysis + +### 1. Ripple (Aakash2408) + +**GitHub:** https://github.com/Aakash2408/ripple (private core) +**Demo repos:** https://github.com/Aakash2408/ripple-demo-api, https://github.com/Aakash2408/ripple-demo-frontend, https://github.com/Aakash2408/ripple-payments-api, https://github.com/Aakash2408/ripple-sdk-node, https://github.com/Aakash2408/ripple-sdk-java, https://github.com/Aakash2408/ripple-sdk-python + +**What they built:** +- Core engine (Go, private) that diffs old vs new API specs +- 10 diff engines (OpenAPI, Protobuf, GraphQL, DB/Prisma, AsyncAPI, Avro, tRPC, Thrift, JSON Schema, Smithy) +- 5-strategy consumer finder (grep, import graph, git co-change history, playbooks, multi-invoker) +- Template-based + LLM (Claude) fix generation +- GitHub App + GitHub Action + Docker self-hosted agent + +**What we learned from them:** +- The **git co-change history** strategy is clever — if two files always change together in commits, they're likely coupled. We should consider this. +- Their **ensemble consumer-finding** approach (5 strategies) is more robust than single-strategy detection +- Supporting 10 contract types is ambitious but unverified — all demos only show OpenAPI +- The **PropBench** benchmark (268 scenarios) is self-created — no independent validation + +**Gaps we identified:** +- Core source code is private — no independent audit possible +- All demo repos are trivial (1-2 files, 2-5KB each) +- No auto-generated PRs have been merged — all remain open +- Solo founder, no team +- No production validation or case studies +- Landing page returns 404 + +**Our takeaway:** Their architecture is interesting but unproven at scale. We should focus on production-ready tooling rather than demo-stage breadth. + +--- + +### 2. HelpPR (pedapudi-pavansai) + +**GitHub:** https://github.com/pedapudi-pavansai/HelpPR + +**What they built:** +- Full platform: React frontend + FastAPI backend + MongoDB +- OpenAPI spec monitoring and breaking change detection +- Deterministic static analysis + bounded LLM reasoning (Claude) +- AWS ECS Fargate deployment with Terraform +- GitHub App integration + +**What we learned from them:** +- Their **deterministic-first, LLM-second** approach is smart — scan mechanically first, only use AI for ambiguous cases +- The **backend module structure** (detector, diff, languages, llm, patcher, pipeline, scanner, watcher) is well-organized +- Using MongoDB for persistence makes sense for their scale +- The **watcher** module for upstream API monitoring is a feature we haven't built yet + +**Gaps we identified:** +- Multi-language support is listed as "future improvement" — not built yet +- AST-based code transformations not implemented +- No evidence of production use +- Most complex architecture — higher barrier to entry +- 0 stars, no community adoption + +**Our takeaway:** Their deterministic-first approach validates our static analysis strategy. We should consider adding an upstream API watcher. + +--- + +### 3. banningwill-AdAstra/self-maintaining-apis + +**GitHub:** https://github.com/banningwill-AdAstra/self-maintaining-apis + +**What they built:** +- Python AST visitor that detects deprecated OpenAI SDK patterns +- Claude-powered fix generation +- GitHub Actions integration (push + weekly schedule) +- 4 detection rules for OpenAI SDK deprecations + +**What we learned from them:** +- **AST-based detection** is more accurate than regex — it understands code structure +- Running scans on a **weekly schedule** (even without code changes) catches upstream deprecations +- The **three modes** (detect, detect+diff, detect+apply) give users control + +**Gaps we identified:** +- Python-only detection +- Only 4 OpenAI SDK rules implemented +- No validation of LLM-generated fixes +- Very early stage (2 commits) + +**Our takeaway:** AST-based detection is worth considering for higher accuracy. Weekly scheduled scans are a feature we should add. + +--- + +### 4. RajaDheeraj/self-maintaining-apis + +**GitHub:** https://github.com/RajaDheeraj/self-maintaining-apis + +**What they built:** +- FastAPI + React agent-based system +- Google Gemini function-calling for autonomous code search and fix +- Strict fix validation (revert + diff check) +- Single hardcoded migration pattern (get_user → retrieve_user) + +**What we learned from them:** +- Their **strict validation** approach (revert the edit, diff against original) ensures fixes don't introduce new bugs +- The **agent-based architecture** (LLM autonomously searches, reads, proposes) is ambitious but risky + +**Gaps we identified:** +- Hardcoded to a single migration pattern +- Python-only +- No CI/CD integration +- 12-second rate-limit sleeps (Gemini free tier) +- 3 commits, 0 stars + +**Our takeaway:** Strict fix validation is a good idea. Agent-based approaches are too unpredictable for production use. + +--- + +### 5. Ability.ai + +**Website:** https://www.ability.ai +**Article:** https://www.ability.ai/blog/self-maintaining-apis-downtime + +**What they built:** +- Trinity: Open-source (Apache 2.0) AI agent runtime platform +- Cornelius: Self-improving cognitive core (knowledge graph) +- Self-maintaining APIs as a use case on their platform + +**What we learned from them:** +- The **30% downtime stat** they cite is the same one in our YC application — it's becoming the standard pitch +- Their **open-core model** (open source + enterprise features) is a proven business model +- The **MCP integration** (90+ tools) and channel integrations (Slack, WhatsApp) represent substantial platform work + +**Gaps we identified:** +- Self-maintaining APIs appears to be a vision/roadmap item, not a shipped product +- No published pricing +- SOC 2 still "in progress" +- 544 stars suggests early traction + +**Our takeaway:** They're a platform play, not a direct competitor. Their article validates the problem space. + +--- + +## Common patterns across all projects + +1. **Everyone cites the 30% downtime stat** — it's becoming the standard pitch for this space +2. **No project has meaningful traction** — all have 0 stars (except Ability.ai's platform) +3. **Python dominates** — most competitors are Python-based +4. **LLM-powered fixes are universal** — everyone uses Claude or Gemini for complex fixes +5. **Deterministic detection first** — the better projects scan mechanically before using AI +6. **No production validation** — nobody has case studies or real users yet + +--- + +## What we do differently + +| Dimension | Competitors | DriftLock | +|-----------|-------------|-----------| +| **Language** | Mostly Python | TypeScript/Bun (faster, type-safe) | +| **Detection** | Spec diffing or regex | Static analysis + sandbox snapshots | +| **Fix validation** | Some (RajaDheeraj) | Built-in (snapshot diffing) | +| **Test classification** | None | Monitored/blind/untested | +| **Persistence** | In-memory or MongoDB | PostgreSQL + Drizzle ORM | +| **Architecture** | Monolithic or agent-based | Modular monorepo | +| **Approach** | Vendor provides specs | We infer from code + sandbox | + +--- + +## Gaps we can fill + +1. **Test classification** — nobody else distinguishes mocked vs real tests +2. **Sandbox snapshots** — nobody captures actual request/response shapes from test runs +3. **TypeScript/Bun** — faster development cycle, better type safety +4. **Production-ready** — modular architecture, proper database, CI/CD from day one +5. **Vendor-agnostic** — we don't need vendors to publish specs + +--- + +## What we should consider adopting + +1. **Git co-change history** (from Ripple) — detect coupled files +2. **AST-based detection** (from banningwill-AdAstra) — more accurate than regex +3. **Weekly scheduled scans** (from banningwill-AdAstra) — catch upstream deprecations +4. **Strict fix validation** (from RajaDheeraj) — ensure fixes don't introduce bugs +5. **Deterministic-first approach** (from HelpPR) — scan mechanically, use AI only for ambiguous cases + +--- + +## References + +- Ripple: https://github.com/Aakash2408/ripple +- Ripple demo repos: https://github.com/Aakash2408/ripple-demo-api, https://github.com/Aakash2408/ripple-demo-frontend, https://github.com/Aakash2408/ripple-payments-api, https://github.com/Aakash2408/ripple-sdk-node, https://github.com/Aakash2408/ripple-sdk-java, https://github.com/Aakash2408/ripple-sdk-python +- HelpPR: https://github.com/pedapudi-pavansai/HelpPR +- banningwill-AdAstra: https://github.com/banningwill-AdAstra/self-maintaining-apis +- RajaDheeraj: https://github.com/RajaDheeraj/self-maintaining-apis +- Ability.ai: https://www.ability.ai/blog/self-maintaining-apis-downtime +- YC RFS: https://www.youtube.com/shorts/c3TxAUir2R8 diff --git a/docs/yc-application.md b/docs/yc-application.md index be5127f..76f6345 100644 --- a/docs/yc-application.md +++ b/docs/yc-application.md @@ -6,9 +6,9 @@ Based on the W26/S26 application format. Draft answers below — refine the voic ## 1. What does your company do? -Driftlock watches the third-party APIs your codebase depends on, catches when a vendor changes something underneath you, and opens a suggested-fix PR — without ever needing the vendor's cooperation. +Driftlock makes APIs self-maintaining. When a vendor ships a breaking change or a new feature, Driftlock scans your codebase, identifies affected usages, and opens a PR with the fix — automatically. -Think Dependabot, but for API contracts instead of package versions. +API providers shouldn't just announce changes. They should apply them. ## 2. What is the problem you are solving? diff --git a/packages/agent/package.json b/packages/agent/package.json index 542457d..789c676 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -7,9 +7,7 @@ "type": "module", "scripts": { "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts", - "test": "bun test", - "test:watch": "bun test --watch" + "lint": "eslint . --ext .ts" }, "dependencies": { "@driftlock/core": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 4a174bd..1d02372 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,9 +7,7 @@ "type": "module", "scripts": { "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts", - "test": "bun test", - "test:watch": "bun test --watch" + "lint": "eslint . --ext .ts" }, "devDependencies": { "@types/bun": "latest", diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts new file mode 100644 index 0000000..e0ffbba --- /dev/null +++ b/packages/db/drizzle.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + schema: "./schema.ts", + out: "./migrations", + dialect: "postgresql", + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +}); diff --git a/packages/db/index.ts b/packages/db/index.ts new file mode 100644 index 0000000..dedfd15 --- /dev/null +++ b/packages/db/index.ts @@ -0,0 +1,28 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; +import * as schema from "./schema"; + +export { schema }; + +export type Database = ReturnType>; + +let _db: Database | null = null; + +export function getDb(): Database { + if (!_db) { + const connectionString = process.env.DATABASE_URL; + if (!connectionString) { + throw new Error("DATABASE_URL environment variable is required"); + } + const client = postgres(connectionString); + _db = drizzle(client, { schema }); + } + return _db; +} + +export function createDb(connectionString: string): Database { + const client = postgres(connectionString); + return drizzle(client, { schema }); +} + +export * from "./schema"; diff --git a/packages/db/migrations/0000_salty_rumiko_fujikawa.sql b/packages/db/migrations/0000_salty_rumiko_fujikawa.sql new file mode 100644 index 0000000..4ced757 --- /dev/null +++ b/packages/db/migrations/0000_salty_rumiko_fujikawa.sql @@ -0,0 +1,87 @@ +CREATE TYPE "public"."confidence" AS ENUM('high', 'medium', 'low');--> statement-breakpoint +CREATE TYPE "public"."drift_status" AS ENUM('detected', 'fix_generated', 'pr_opened', 'merged', 'closed', 'false_positive');--> statement-breakpoint +CREATE TYPE "public"."fix_type" AS ENUM('field_rename', 'type_coercion', 'null_check', 'default_value', 'custom');--> statement-breakpoint +CREATE TYPE "public"."http_method" AS ENUM('GET', 'POST', 'PUT', 'DELETE', 'PATCH');--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "call_sites" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "repository_id" uuid NOT NULL, + "file_path" text NOT NULL, + "line" integer NOT NULL, + "method" varchar(255) NOT NULL, + "endpoint" varchar(510) NOT NULL, + "http_method" "http_method" NOT NULL, + "request_shape" jsonb DEFAULT '{}'::jsonb NOT NULL, + "response_fields" text[] DEFAULT '{}' NOT NULL, + "test_files" text[] DEFAULT '{}' NOT NULL, + "last_checked_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "drift_events" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "call_site_id" uuid NOT NULL, + "detected_at" timestamp DEFAULT now() NOT NULL, + "old_snapshot_id" uuid NOT NULL, + "new_snapshot_id" uuid NOT NULL, + "diff_summary" jsonb DEFAULT '{}'::jsonb NOT NULL, + "suggested_fix" jsonb, + "confidence" "confidence" NOT NULL, + "pr_number" integer, + "status" "drift_status" DEFAULT 'detected' NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "repositories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "owner" varchar(255) NOT NULL, + "name" varchar(255) NOT NULL, + "full_name" varchar(510) NOT NULL, + "installation_id" integer NOT NULL, + "default_branch" varchar(255) DEFAULT 'main' NOT NULL, + "language" text[] DEFAULT '{}' NOT NULL, + "last_analyzed_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "snapshots" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "call_site_id" uuid NOT NULL, + "captured_at" timestamp DEFAULT now() NOT NULL, + "request_shape" jsonb DEFAULT '{}'::jsonb NOT NULL, + "response_shape" jsonb DEFAULT '{}'::jsonb NOT NULL, + "test_command" text NOT NULL, + "exit_code" integer NOT NULL, + "duration" integer NOT NULL, + "traffic_captured" integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "call_sites" ADD CONSTRAINT "call_sites_repository_id_repositories_id_fk" FOREIGN KEY ("repository_id") REFERENCES "public"."repositories"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "drift_events" ADD CONSTRAINT "drift_events_call_site_id_call_sites_id_fk" FOREIGN KEY ("call_site_id") REFERENCES "public"."call_sites"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "drift_events" ADD CONSTRAINT "drift_events_old_snapshot_id_snapshots_id_fk" FOREIGN KEY ("old_snapshot_id") REFERENCES "public"."snapshots"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "drift_events" ADD CONSTRAINT "drift_events_new_snapshot_id_snapshots_id_fk" FOREIGN KEY ("new_snapshot_id") REFERENCES "public"."snapshots"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "snapshots" ADD CONSTRAINT "snapshots_call_site_id_call_sites_id_fk" FOREIGN KEY ("call_site_id") REFERENCES "public"."call_sites"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..0373c99 --- /dev/null +++ b/packages/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,458 @@ +{ + "id": "39edfdce-4347-4cd9-82a7-f412a9889678", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.call_sites": { + "name": "call_sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "line": { + "name": "line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(510)", + "primaryKey": false, + "notNull": true + }, + "http_method": { + "name": "http_method", + "type": "http_method", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "request_shape": { + "name": "request_shape", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "response_fields": { + "name": "response_fields", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "test_files": { + "name": "test_files", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "call_sites_repository_id_repositories_id_fk": { + "name": "call_sites_repository_id_repositories_id_fk", + "tableFrom": "call_sites", + "tableTo": "repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.drift_events": { + "name": "drift_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "call_site_id": { + "name": "call_site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "detected_at": { + "name": "detected_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "old_snapshot_id": { + "name": "old_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "new_snapshot_id": { + "name": "new_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "diff_summary": { + "name": "diff_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "suggested_fix": { + "name": "suggested_fix", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "confidence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "drift_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'detected'" + } + }, + "indexes": {}, + "foreignKeys": { + "drift_events_call_site_id_call_sites_id_fk": { + "name": "drift_events_call_site_id_call_sites_id_fk", + "tableFrom": "drift_events", + "tableTo": "call_sites", + "columnsFrom": [ + "call_site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "drift_events_old_snapshot_id_snapshots_id_fk": { + "name": "drift_events_old_snapshot_id_snapshots_id_fk", + "tableFrom": "drift_events", + "tableTo": "snapshots", + "columnsFrom": [ + "old_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "drift_events_new_snapshot_id_snapshots_id_fk": { + "name": "drift_events_new_snapshot_id_snapshots_id_fk", + "tableFrom": "drift_events", + "tableTo": "snapshots", + "columnsFrom": [ + "new_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.repositories": { + "name": "repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner": { + "name": "owner", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "varchar(510)", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "language": { + "name": "language", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "last_analyzed_at": { + "name": "last_analyzed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.snapshots": { + "name": "snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "call_site_id": { + "name": "call_site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "request_shape": { + "name": "request_shape", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "response_shape": { + "name": "response_shape", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "test_command": { + "name": "test_command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "traffic_captured": { + "name": "traffic_captured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "snapshots_call_site_id_call_sites_id_fk": { + "name": "snapshots_call_site_id_call_sites_id_fk", + "tableFrom": "snapshots", + "tableTo": "call_sites", + "columnsFrom": [ + "call_site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.confidence": { + "name": "confidence", + "schema": "public", + "values": [ + "high", + "medium", + "low" + ] + }, + "public.drift_status": { + "name": "drift_status", + "schema": "public", + "values": [ + "detected", + "fix_generated", + "pr_opened", + "merged", + "closed", + "false_positive" + ] + }, + "public.fix_type": { + "name": "fix_type", + "schema": "public", + "values": [ + "field_rename", + "type_coercion", + "null_check", + "default_value", + "custom" + ] + }, + "public.http_method": { + "name": "http_method", + "schema": "public", + "values": [ + "GET", + "POST", + "PUT", + "DELETE", + "PATCH" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json new file mode 100644 index 0000000..1bb4171 --- /dev/null +++ b/packages/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1789467857613, + "tag": "0000_salty_rumiko_fujikawa", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 0000000..d24acf1 --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,26 @@ +{ + "name": "@driftlock/db", + "version": "0.1.0", + "private": true, + "main": "./index.ts", + "types": "./index.ts", + "type": "module", + "scripts": { + "typecheck": "tsc --noEmit", + "lint": "eslint . --ext .ts", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio" + }, + "dependencies": { + "@driftlock/core": "workspace:*", + "drizzle-orm": "^0.36.0", + "postgres": "^3.4.0" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "drizzle-kit": "^0.28.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/db/schema.ts b/packages/db/schema.ts new file mode 100644 index 0000000..c9cbe48 --- /dev/null +++ b/packages/db/schema.ts @@ -0,0 +1,101 @@ +import { + pgTable, + uuid, + varchar, + text, + integer, + jsonb, + timestamp, + pgEnum, +} from "drizzle-orm/pg-core"; + +export const httpMethodEnum = pgEnum("http_method", [ + "GET", + "POST", + "PUT", + "DELETE", + "PATCH", +]); + +export const confidenceEnum = pgEnum("confidence", ["high", "medium", "low"]); + +export const driftStatusEnum = pgEnum("drift_status", [ + "detected", + "fix_generated", + "pr_opened", + "merged", + "closed", + "false_positive", +]); + +export const fixTypeEnum = pgEnum("fix_type", [ + "field_rename", + "type_coercion", + "null_check", + "default_value", + "custom", +]); + +export const repositories = pgTable("repositories", { + id: uuid("id").primaryKey().defaultRandom(), + owner: varchar("owner", { length: 255 }).notNull(), + name: varchar("name", { length: 255 }).notNull(), + fullName: varchar("full_name", { length: 510 }).notNull(), + installationId: integer("installation_id").notNull(), + defaultBranch: varchar("default_branch", { length: 255 }).notNull().default("main"), + language: text("language").array().notNull().default([]), + lastAnalyzedAt: timestamp("last_analyzed_at"), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().defaultNow(), +}); + +export const callSites = pgTable("call_sites", { + id: uuid("id").primaryKey().defaultRandom(), + repositoryId: uuid("repository_id") + .notNull() + .references(() => repositories.id, { onDelete: "cascade" }), + filePath: text("file_path").notNull(), + line: integer("line").notNull(), + method: varchar("method", { length: 255 }).notNull(), + endpoint: varchar("endpoint", { length: 510 }).notNull(), + httpMethod: httpMethodEnum("http_method").notNull(), + requestShape: jsonb("request_shape").notNull().default({}), + responseFields: text("response_fields").array().notNull().default([]), + testFiles: text("test_files").array().notNull().default([]), + lastCheckedAt: timestamp("last_checked_at"), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().defaultNow(), +}); + +export const snapshots = pgTable("snapshots", { + id: uuid("id").primaryKey().defaultRandom(), + callSiteId: uuid("call_site_id") + .notNull() + .references(() => callSites.id, { onDelete: "cascade" }), + capturedAt: timestamp("captured_at").notNull().defaultNow(), + requestShape: jsonb("request_shape").notNull().default({}), + responseShape: jsonb("response_shape").notNull().default({}), + testCommand: text("test_command").notNull(), + exitCode: integer("exit_code").notNull(), + duration: integer("duration").notNull(), + trafficCaptured: integer("traffic_captured").notNull().default(0), +}); + +export const driftEvents = pgTable("drift_events", { + id: uuid("id").primaryKey().defaultRandom(), + callSiteId: uuid("call_site_id") + .notNull() + .references(() => callSites.id, { onDelete: "cascade" }), + detectedAt: timestamp("detected_at").notNull().defaultNow(), + oldSnapshotId: uuid("old_snapshot_id") + .notNull() + .references(() => snapshots.id), + newSnapshotId: uuid("new_snapshot_id") + .notNull() + .references(() => snapshots.id), + diffSummary: jsonb("diff_summary").notNull().default({}), + suggestedFix: jsonb("suggested_fix"), + confidence: confidenceEnum("confidence").notNull(), + prNumber: integer("pr_number"), + status: driftStatusEnum("status").notNull().default("detected"), +}); diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json new file mode 100644 index 0000000..2d5f23d --- /dev/null +++ b/packages/db/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/git/package.json b/packages/git/package.json index 6da6cb1..d83c3bf 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -7,9 +7,7 @@ "type": "module", "scripts": { "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts", - "test": "bun test", - "test:watch": "bun test --watch" + "lint": "eslint . --ext .ts" }, "dependencies": { "@driftlock/core": "workspace:*", diff --git a/packages/parser/package.json b/packages/parser/package.json index cf8f1e9..13de6c8 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -7,9 +7,7 @@ "type": "module", "scripts": { "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts", - "test": "bun test", - "test:watch": "bun test --watch" + "lint": "eslint . --ext .ts" }, "dependencies": { "@driftlock/core": "workspace:*", diff --git a/packages/sandbox/index.ts b/packages/sandbox/index.ts index 25f9899..e916537 100644 --- a/packages/sandbox/index.ts +++ b/packages/sandbox/index.ts @@ -1,4 +1,3 @@ export * as proxy from "./proxy"; -export * as runner from "./runner"; export { SandboxRunner } from "./runner"; export type { SandboxConfig, SandboxResult, TrafficCapture } from "./runner"; diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index 15dbfe4..e1113f4 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -7,9 +7,7 @@ "type": "module", "scripts": { "typecheck": "tsc --noEmit", - "lint": "eslint . --ext .ts", - "test": "bun test", - "test:watch": "bun test --watch" + "lint": "eslint . --ext .ts" }, "dependencies": { "@driftlock/core": "workspace:*",