@syntopica/db-quality 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) BusiRocket
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # @syntopica/db-quality
2
+
3
+ Database quality gate for the projects in this estate: lints Supabase
4
+ migrations, Prisma schemas, Drizzle code and SQLite files without a database,
5
+ audits a live Supabase project, carries existing debt in a baseline and runs
6
+ everything as one gate with honest exit codes. Brings to databases what
7
+ `@syntopica/eslint-config`, `cargo-baseline` and `codeality-py` bring to code.
8
+
9
+ - **Design spec:**
10
+ [docs/superpowers/specs/2026-09-25-db-quality-design.md](https://github.com/syntopica/codeality/blob/main/docs/superpowers/specs/2026-09-25-db-quality-design.md)
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add -D @syntopica/db-quality squawk-cli prisma-lint eslint eslint-plugin-drizzle typescript-eslint
16
+ ```
17
+
18
+ Install only the peers your stacks need: `squawk-cli` for Supabase migrations,
19
+ `prisma-lint` for Prisma, the three ESLint packages for Drizzle. The Supabase
20
+ CLI, `sqlite3` and `uvx` are external executables.
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ codeality-db init [--check|--apply|--force] # codeality-db.json, db:gate script, CI workflow
26
+ codeality-db check [--json] # static findings, never writes
27
+ codeality-db audit --linked|--db-url <url> # live Supabase advisors, inspect, Soda
28
+ codeality-db gate # check or baseline check, then the linked audit
29
+ codeality-db baseline create|update|check # record and enforce the debt you carry
30
+ ```
31
+
32
+ `--project <dir>` before the command runs against another directory.
33
+
34
+ ## Configuration
35
+
36
+ `codeality-db.json`, written by `init` from the stacks it detects:
37
+
38
+ ```json
39
+ {
40
+ "schemaVersion": 1,
41
+ "supabase": { "migrations": "supabase/migrations" },
42
+ "prisma": { "schema": "prisma/schema.prisma" },
43
+ "drizzle": { "roots": ["src"], "objectNames": ["db", "tx"] },
44
+ "sqlite": { "files": ["data/app.db"] },
45
+ "audit": { "inGate": true, "bloatThreshold": 5, "soda": "db-quality/soda" },
46
+ "disable": ["BDB100/prefer-bigint-over-int"]
47
+ }
48
+ ```
49
+
50
+ Every section is optional. `audit.soda` names a directory holding a Soda Core
51
+ `checks.yml`; it runs only with `--db-url`, because a linked project carries no
52
+ database password.
53
+
54
+ ## Findings
55
+
56
+ | Code | Source | Severity | What it means |
57
+ | ------------------ | --------------------------- | ----------- | ------------------------------------------------------------------ |
58
+ | `BDB001` | permissive-policy | warn | a policy uses `using (true)` or `with check (true)` |
59
+ | `BDB002` | rls-enabled-no-policy | info | RLS on, no policy in any migration: service role only |
60
+ | `BDB003` | table-without-rls | warn | a `public` table never enables row level security |
61
+ | `BDB004` | auth-uid-not-wrapped | warn | `auth.uid()` in a policy without `(select ...)`: evaluated per row |
62
+ | `BDB005` | definer-without-search-path | warn | `SECURITY DEFINER` function without `set search_path` |
63
+ | `BDB100/<rule>` | squawk | as squawk | migration lock and schema hazards, Supabase profile |
64
+ | `BDB200/<rule>` | prisma-lint | warn | relation field without an index |
65
+ | `BDB300/<rule>` | eslint-plugin-drizzle | error | `delete` or `update` without `.where()` |
66
+ | `BDB401`-`BDB403` | sqlite3 | error/warn | integrity, dangling foreign keys, table without primary key |
67
+ | `BDB500/<name>` | Supabase advisors | as Supabase | splinter security and performance lints on the live project |
68
+ | `BDB601`, `BDB602` | Supabase inspect | info/warn | never-scanned index, table bloat over `audit.bloatThreshold` |
69
+ | `BDB700/<check>` | Soda Core | error/warn | a failed or warned data check from `<audit.soda>/checks.yml` |
70
+
71
+ Disable a code for a project with `"disable": ["BDB100/prefer-bigint-over-int"]`
72
+ in `codeality-db.json`.
73
+
74
+ ## Baseline
75
+
76
+ `baseline create` records every current finding's fingerprint in
77
+ `.codeality-db-baseline.json`; from then on `gate` runs `baseline-check` and
78
+ fails only on findings the baseline does not carry. Fingerprints hash the code,
79
+ the path and the normalised statement, never the line number, so inserting a
80
+ migration above a known finding does not renew it.
81
+ `baseline check --check-stale` also fails on entries nothing reports any more,
82
+ so dead debt is not carried forever.
83
+
84
+ ## Squawk under Supabase
85
+
86
+ Supabase runs each migration inside one transaction with the CLI's own timeouts,
87
+ so four squawk rules describe a deployment model these projects do not have and
88
+ are excluded: `prefer-robust-stmts`, `require-lock-timeout`,
89
+ `require-statement-timeout`, `require-concurrent-index-creation`.
90
+
91
+ ## The audit needs the owning account
92
+
93
+ `supabase db advisors --linked` answers 401 or 403 when the logged-in CLI
94
+ account has no privileges on the project. The gate reports that as
95
+ `failed-to-run` (exit 3) rather than passing silently; set
96
+ `"audit": { "inGate": false }` for a CI job that has no token.
97
+
98
+ With `--db-url`, the advisors and inspect run only when the host is a Supabase
99
+ one (`*.supabase.co` or `*.pooler.supabase.com`); any other Postgres gets the
100
+ Soda stage alone and a `supabase: skipped` notice. A non-Supabase URL with no
101
+ `audit.soda` configured is a configuration error (exit 2).
102
+
103
+ ## Exit codes
104
+
105
+ | Code | Meaning |
106
+ | ---- | --------------------------------------------------------------- |
107
+ | 0 | All blocking checks passed. |
108
+ | 1 | Findings. |
109
+ | 2 | Invalid usage or invalid configuration. |
110
+ | 3 | Infrastructure failure: a required tool is missing or unusable. |
111
+
112
+ ## License
113
+
114
+ MIT
@@ -0,0 +1,26 @@
1
+ name: Database quality
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ db-quality:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
13
+ with:
14
+ persist-credentials: false
15
+ - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
16
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
17
+ with:
18
+ node-version: 24
19
+ cache: pnpm
20
+ - run: pnpm install --frozen-lockfile
21
+ # The linked audit needs the account that owns the project. Without a
22
+ # token the gate reports failed-to-run on purpose; set audit.inGate to
23
+ # false in codeality-db.json to run the static stages only.
24
+ - run: pnpm exec codeality-db gate
25
+ env:
26
+ SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
@@ -0,0 +1,22 @@
1
+ // Run by codeality-db from the consumer's project directory with
2
+ // `--no-config-lookup`, so the project's own ESLint setup is neither read nor
3
+ // changed. The plugin and the parser resolve from wherever this file is
4
+ // installed, which is why both are peer dependencies of the package.
5
+ import drizzle from 'eslint-plugin-drizzle'
6
+ import tseslint from 'typescript-eslint'
7
+
8
+ const drizzleObjectName = (
9
+ process.env.CODEALITY_DB_DRIZZLE_OBJECTS ?? 'db,tx'
10
+ ).split(',')
11
+
12
+ export default [
13
+ {
14
+ files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.js', '**/*.mjs'],
15
+ languageOptions: { parser: tseslint.parser },
16
+ plugins: { drizzle },
17
+ rules: {
18
+ 'drizzle/enforce-delete-with-where': ['error', { drizzleObjectName }],
19
+ 'drizzle/enforce-update-with-where': ['error', { drizzleObjectName }],
20
+ },
21
+ },
22
+ ]
@@ -0,0 +1,5 @@
1
+ {
2
+ "rules": {
3
+ "require-field-index": ["error", { "forAllRelations": true }]
4
+ }
5
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/cli.js'