@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 +21 -0
- package/README.md +114 -0
- package/assets/db-quality.yml +26 -0
- package/assets/drizzle-eslint.config.mjs +22 -0
- package/assets/prisma-lint.json +5 -0
- package/bin/codeality-db.mjs +2 -0
- package/dist/cli.js +1636 -0
- package/dist/cli.js.map +1 -0
- package/package.json +85 -0
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
|
+
]
|