fastify-auto-sqlapi 0.1.14 → 0.2.1
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/AGENTS_BACKEND.md +52 -7
- package/BREAKING_CHANGES.md +86 -5
- package/CHANGELOG.md +57 -0
- package/README.md +110 -3
- package/dist/bin/generate-schema.js +89 -77
- package/dist/bin/generate-tables.js +72 -62
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +47 -33
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/agent/manifest.d.ts +3 -2
- package/dist/lib/agent/manifest.d.ts.map +1 -1
- package/dist/lib/agent/manifest.js +102 -80
- package/dist/lib/cli/mysql-introspect.d.ts +4 -9
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +8 -7
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +0 -1
- package/dist/lib/cli/schema-codegen.d.ts +3 -0
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +3 -0
- package/dist/lib/cli/tables-codegen.d.ts +8 -1
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +100 -118
- package/dist/lib/condition-methods.d.ts +14 -4
- package/dist/lib/condition-methods.d.ts.map +1 -1
- package/dist/lib/condition-methods.js +18 -4
- package/dist/lib/db.d.ts +7 -2
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +24 -6
- package/dist/lib/dialect.d.ts +2 -1
- package/dist/lib/dialect.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +44 -36
- package/dist/lib/engine/rest/delete.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +5 -4
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +1 -1
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +26 -19
- package/dist/lib/engine/search/aggregations.d.ts +33 -0
- package/dist/lib/engine/search/aggregations.d.ts.map +1 -0
- package/dist/lib/engine/search/aggregations.js +109 -0
- package/dist/lib/engine/search/conditions.d.ts +31 -0
- package/dist/lib/engine/search/conditions.d.ts.map +1 -0
- package/dist/lib/engine/search/conditions.js +131 -0
- package/dist/lib/engine/search/fields.d.ts +49 -0
- package/dist/lib/engine/search/fields.d.ts.map +1 -0
- package/dist/lib/engine/search/fields.js +142 -0
- package/dist/lib/engine/search/join-families.d.ts +35 -0
- package/dist/lib/engine/search/join-families.d.ts.map +1 -0
- package/dist/lib/engine/search/join-families.js +345 -0
- package/dist/lib/engine/search/joins.d.ts +55 -0
- package/dist/lib/engine/search/joins.d.ts.map +1 -0
- package/dist/lib/engine/search/joins.js +81 -0
- package/dist/lib/engine/search/order-by.d.ts +35 -0
- package/dist/lib/engine/search/order-by.d.ts.map +1 -0
- package/dist/lib/engine/search/order-by.js +104 -0
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +106 -791
- package/dist/lib/engine/validate.d.ts +0 -5
- package/dist/lib/engine/validate.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.d.ts +25 -17
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +86 -44
- package/dist/lib/errors.d.ts +24 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +59 -0
- package/dist/lib/read-access.d.ts +12 -1
- package/dist/lib/read-access.d.ts.map +1 -1
- package/dist/lib/read-access.js +20 -4
- package/dist/lib/schema/helpers.d.ts +0 -15
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +7 -4
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +107 -128
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +48 -0
- package/dist/lib/tenant.d.ts +66 -5
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +209 -48
- package/dist/routes/auto/plugin.d.ts.map +1 -1
- package/dist/routes/auto/plugin.js +6 -0
- package/dist/routes/auto/route-helpers.d.ts +0 -7
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +58 -33
- package/dist/routes/auto/search.routes.js +0 -1
- package/dist/types/plugin.d.ts +19 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/table.d.ts +1 -1
- package/dist/types/tenant.d.ts +26 -2
- package/dist/types/tenant.d.ts.map +1 -1
- package/docs/GLOSSARY.md +30 -0
- package/docs/adr/0006-raw-db-errors.md +2 -1
- package/docs/adr/0010-joins-do-not-run-route-guards.md +4 -1
- package/docs/adr/0012-anti-duplication-tooling.md +59 -0
- package/docs/adr/0013-sanitized-db-errors.md +114 -0
- package/docs/adr/README.md +4 -1
- package/package.json +29 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/types/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,MAAM,EAAE,gBAAgB,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/types/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,MAAM,EAAE,gBAAgB,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB"}
|
package/docs/GLOSSARY.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
One name per concept. Use these words in code, tests and docs — a second name for an
|
|
4
|
+
existing concept makes search fail and the concept get built twice.
|
|
5
|
+
|
|
6
|
+
| Term | Meaning |
|
|
7
|
+
|---|---|
|
|
8
|
+
| Table config | `defineTable()` result describing one table's routes, guards and joins. (not: model, entity) |
|
|
9
|
+
| Schema | TypeBox definition of a table's columns, generated from the DB. (not: model) |
|
|
10
|
+
| Relation | `buildRelation()` link between two schemas, identified by an alias. (not: association) |
|
|
11
|
+
| Alias | Request/response key naming one relation; defaults to the joined table name. |
|
|
12
|
+
| `joinMustExist` | Child→main 1:N filter via EXISTS; never returns child rows. |
|
|
13
|
+
| `joinMultiple` | Child→main 1:N side query; child rows land in `result.joinMultiple.<alias>`. |
|
|
14
|
+
| `joinGroup` | Child→main 1:N aggregations in `result.joinGroup.<alias>`. |
|
|
15
|
+
| `joinLeft` | Parent→main N:1 real LEFT JOIN; needs `unique: true` on the relation. |
|
|
16
|
+
| Secondaries | Child rows written in the same request as the main row. (not: nested, children) |
|
|
17
|
+
| Deletions | Child rows removed in the same request as the main row update. |
|
|
18
|
+
| Computed field | Server-side derived value resolved during search. (not: virtual, derived) |
|
|
19
|
+
| Extra filter | Non-column search filter declared per table. (not: custom filter) |
|
|
20
|
+
| Extended condition | Consumer-supplied SQL fragment merged into a search WHERE. |
|
|
21
|
+
| Dotted notation | `orderBy`/condition key counted in parts: 1-part `field`, 2-part `alias.field`, 3-part `alias.fn.field`. (not: parti) |
|
|
22
|
+
| Tenant scope | Row-level isolation: `direct` (own column), `indirect` (via FK) or `anyOf`. |
|
|
23
|
+
| `anyOf` scope | Tenant scope over several owner columns: the row is visible to any of them. |
|
|
24
|
+
| Anchor | The owner column an `anyOf` write names to tie a new row to the caller. |
|
|
25
|
+
| Engine | Dialect-agnostic function performing one operation. (not: service, handler) |
|
|
26
|
+
| Route | Fastify plugin exposing an engine over HTTP. (not: controller) |
|
|
27
|
+
| Dialect | Per-DB SQL differences (`postgres` \| `mysql`). |
|
|
28
|
+
| QueryClient | Connection wrapper the engines run SQL through. (not: db, connection) |
|
|
29
|
+
| Paginator | Request object carrying page and items-per-page. (not: pagination, pager) |
|
|
30
|
+
| Manifest | Runtime description of a deployment's tables, for agent clients. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# 0006. Raw DB errors surface as 500 — no SQLSTATE→HTTP mapping
|
|
2
2
|
|
|
3
|
-
- **Status**: accepted
|
|
3
|
+
- **Status**: accepted on the mapping; message exposure narrowed by
|
|
4
|
+
[0013](./0013-sanitized-db-errors.md)
|
|
4
5
|
- **Date**: 2026-07-23 (confirmed by the maintainer in June 2026)
|
|
5
6
|
|
|
6
7
|
## Context
|
|
@@ -97,4 +97,7 @@ programmatic `sqlApi.search()` path.
|
|
|
97
97
|
narrow. Not decided here.
|
|
98
98
|
- `tenantScope` remains the one request-derived protection that crosses joins, and is the
|
|
99
99
|
precedent for the shape any future one should take: table configuration plus a plugin-level
|
|
100
|
-
resolver, not a route hook.
|
|
100
|
+
resolver, not a route hook. It crosses joins **unconditionally** only since the tenant stopped
|
|
101
|
+
being resolved from the addressed table alone; before that, a host declaring no scope of its
|
|
102
|
+
own carried none into its join targets, which made the row above true only when the host
|
|
103
|
+
happened to be scoped too (see BREAKING_CHANGES.md).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 0012. Repository rules are enforced by executable gates, not by documentation
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted
|
|
4
|
+
- **Date**: 2026-08-01
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Every session starts with an empty context window. An agent that cannot find existing code
|
|
9
|
+
rewrites it, invents a second name for an existing concept, and scatters logic across the
|
|
10
|
+
tree. Documentation does not prevent this: it is only read when someone remembers to read
|
|
11
|
+
it, and `CLAUDE.md` is weighted context, not configuration.
|
|
12
|
+
|
|
13
|
+
The repository had no ESLint, no Prettier, no git hooks and no CI beyond publish-on-tag.
|
|
14
|
+
Measured starting point: 2.50% duplicated lines (3.27% of tokens, 18 clones), naming drift
|
|
15
|
+
effectively zero, layering already clean, and `fastify-plugin` imported but absent from
|
|
16
|
+
`package.json` — a real packaging bug that no human review had caught.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
Anything that must not happen is a hook or a lint rule. Prose is reserved for what cannot
|
|
21
|
+
be executed.
|
|
22
|
+
|
|
23
|
+
Gates are **silent and zero-tolerance by default**; pre-existing debt is switched off and
|
|
24
|
+
surfaced by `npm run debt` (`lint:strict`, `dup:strict`, `depcruise:strict`,
|
|
25
|
+
`knip:strict`). npm scripts are the source of truth — git hooks only call them — so any
|
|
26
|
+
gate can be run without touching git history.
|
|
27
|
+
|
|
28
|
+
Rejected the ratchet-on-a-warning-count approach that was tried first: a run that always
|
|
29
|
+
prints 241 warnings trains everyone to ignore its output, and the number drifts upward
|
|
30
|
+
unnoticed. A rule is either enforced or explicitly deferred, never "warned about".
|
|
31
|
+
|
|
32
|
+
## Alternatives considered
|
|
33
|
+
|
|
34
|
+
- **Baseline thresholds with warnings** (`--max-warnings 241`) — rejected after
|
|
35
|
+
implementing it: noisy output is ignored output, and the boundary is invisible.
|
|
36
|
+
- **Barrel enforcement via `no-restricted-imports`** — rejected: the repo has one barrel,
|
|
37
|
+
`src/index.ts`, which is the npm surface. Blocking deep imports would break every
|
|
38
|
+
internal import and require restructuring into per-module barrels. `dependency-cruiser`
|
|
39
|
+
encodes the same boundaries without moving a single file.
|
|
40
|
+
- **Intent tags with a closed vocabulary, and `cspell`** — rejected: 64 files, coherent
|
|
41
|
+
naming, one maintainer. A half-maintained tag system is worse than none, and spellchecking
|
|
42
|
+
a 951-line README full of SQL identifiers is pure noise.
|
|
43
|
+
- **Session worklog** — rejected: without a compaction ritual nobody will run, an
|
|
44
|
+
append-only log becomes context noise within weeks.
|
|
45
|
+
|
|
46
|
+
## Consequences
|
|
47
|
+
|
|
48
|
+
- New violations fail immediately; the deferred list in `eslint.config.js` is the debt
|
|
49
|
+
ledger and may only shrink.
|
|
50
|
+
- `docs/INDEX.md` is generated and CI fails when it is stale, like a lockfile. It is
|
|
51
|
+
excluded from the npm package: publishing 204 internal declarations would invite
|
|
52
|
+
consumers to import what is not public API.
|
|
53
|
+
- Type-only import cycles through `src/types.ts` are deferred, not fixed. Verified they
|
|
54
|
+
are erased on emit — the compiled `dist/` graph has zero cycles.
|
|
55
|
+
- `knip` checks exports and types with no exclusions. The 16 symbols that were exported but
|
|
56
|
+
used only inside their own file lost the keyword; the 5 that only unit tests use are
|
|
57
|
+
tagged `@testonly`, which documents why they stay exported instead of hiding them.
|
|
58
|
+
- Maintainer scripts require Node ≥22.18 (native TypeScript type stripping). The published
|
|
59
|
+
package still supports Node ≥18.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# 0013. Database error messages are sanitized on the wire by default
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted — narrows [0006](./0006-raw-db-errors.md)
|
|
4
|
+
- **Date**: 2026-08-05
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
[ADR 0006](./0006-raw-db-errors.md) rejected two distinct proposals in one decision: mapping
|
|
9
|
+
SQLSTATE codes to HTTP semantics, and sanitizing driver messages. The first rejection holds
|
|
10
|
+
— the mapping is dialect-specific and encodes product decisions the plugin should not make.
|
|
11
|
+
The second rested on "hiding details is an environment policy, and Fastify already gives
|
|
12
|
+
consumers the hook for it", and three facts undermine it:
|
|
13
|
+
|
|
14
|
+
- The plugin installs its own `setErrorHandler` in the routes' child scope
|
|
15
|
+
(`src/routes/auto/plugin.ts`), and it sends `error.message` verbatim for any status code.
|
|
16
|
+
A consumer handler registered on the parent scope never sees these errors, so the escape
|
|
17
|
+
route 0006 points at does not exist for the main-plugin path.
|
|
18
|
+
- The posture is inconsistent with the rest of the codebase. `maxItemsPerPage`,
|
|
19
|
+
`maxBulkItems`, prototype-key filtering and the join field allowlist are all guards the
|
|
20
|
+
plugin applies by default where the cost is zero. A constraint name describing the schema
|
|
21
|
+
to whoever triggered a duplicate is the same class of free hardening.
|
|
22
|
+
- [ADR 0002](./0002-open-by-default.md) amplifies the exposure: the scenario the plugin
|
|
23
|
+
optimizes for — internal scaffolding wired up in five minutes — is exactly the one where
|
|
24
|
+
nobody wrote an error handler.
|
|
25
|
+
|
|
26
|
+
The failure mode is silent. A consumer who never thinks about it ships the leak and gets no
|
|
27
|
+
signal.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
Errors escaping an auto-generated route handler are replaced with a bare
|
|
32
|
+
`500 Internal Server Error` carrying no database detail. Errors the plugin raises itself are
|
|
33
|
+
deliberate `4xx` with messages written for clients (`httpError`, `validationErrors`) and pass
|
|
34
|
+
through untouched — the rule is `statusCode` in `[400, 500)`, nothing else.
|
|
35
|
+
|
|
36
|
+
No status code is remapped: a `500` stays a `500`. ADR 0006 stands on that point.
|
|
37
|
+
|
|
38
|
+
Nothing is lost. The original error is logged through `request.log.error` and attached as the
|
|
39
|
+
`cause` of the error the consumer's `setErrorHandler` receives, so unique-violation → `409`
|
|
40
|
+
remains implementable, and remains the consumer's decision. The sanitized body carries
|
|
41
|
+
`requestId` — the same value Fastify binds as `reqId` on the request logger — so the client
|
|
42
|
+
can name the log line holding the real error instead of describing a `500` that happened
|
|
43
|
+
"around then".
|
|
44
|
+
|
|
45
|
+
Developing against the API is a real need the log does not serve — a client building on it,
|
|
46
|
+
an agent especially, may have no way to read the server's log, and a round trip per failure
|
|
47
|
+
is expensive. `exposeDebugInfo: true` answers it by **adding** a `debugInfo` payload (driver
|
|
48
|
+
message, `code`, `constraint`/`detail`, stack) beside the fields above, which keep the shape
|
|
49
|
+
they have in production. Client-side error handling therefore does not fork between
|
|
50
|
+
environments — the difference is extra data, not different data.
|
|
51
|
+
|
|
52
|
+
`exposeDebugInfo` derives from nothing. Not `debug`, not `NODE_ENV`, not the log level. The
|
|
53
|
+
deciding argument is that **`debug` belongs to the consumer**: whoever registers the plugin
|
|
54
|
+
decides how it is wired — a constant in one deployment, an environment variable flipped
|
|
55
|
+
during an incident in another — so the library cannot assume its lifecycle, and an option
|
|
56
|
+
whose value the library does not control must not decide what leaves the server. Beyond that,
|
|
57
|
+
`debug` writes to the log, an access-controlled internal channel; the response body is
|
|
58
|
+
external, and under ADR 0002 often unauthenticated. And the posture wanted during a
|
|
59
|
+
production incident — log everything, tell clients nothing — stops being expressible the
|
|
60
|
+
moment one switch drives both.
|
|
61
|
+
|
|
62
|
+
The guard sits in the handler built by `registerForAllTables`
|
|
63
|
+
(`src/routes/auto/route-helpers.ts`) — the single choke point every auto route passes
|
|
64
|
+
through, including route plugins registered standalone without the main plugin. The
|
|
65
|
+
`debugInfo` payload is copied from the driver error by allowlist, never spread: an error
|
|
66
|
+
object that starts carrying the connection config must not reach a client through it.
|
|
67
|
+
|
|
68
|
+
## Alternatives considered
|
|
69
|
+
|
|
70
|
+
- **`fastify.setErrorHandler` in the plugin** — rejected: the plugin is registered through
|
|
71
|
+
`fp()`, so a handler installed at that level would hijack error rendering for the
|
|
72
|
+
consumer's entire application, not just the auto routes.
|
|
73
|
+
- **Sanitizing inside the existing child-scope handler in `plugin.ts`** — rejected: route
|
|
74
|
+
plugins registered standalone (`searchRoutes` alone, per AGENTS_BACKEND.md "Register only
|
|
75
|
+
specific routes") never reach that handler, and would keep leaking.
|
|
76
|
+
- **Leaving it to the consumer, as 0006 decided** — rejected for the three reasons in
|
|
77
|
+
Context; the deciding one is that the plugin's own child-scope handler makes the
|
|
78
|
+
prescribed consumer remedy unreachable.
|
|
79
|
+
- **Deriving the behaviour from `debug`** — rejected, for the reasons in Decision: the
|
|
80
|
+
consumer owns that option and may wire it to an environment variable, it currently governs
|
|
81
|
+
an internal channel only, and coupling makes "verbose log, silent response" unexpressible.
|
|
82
|
+
An override (`debug: true` + an explicit opt-out) would keep the combination reachable but
|
|
83
|
+
leaves the default pointing the dangerous way, which is the failure mode this ADR exists to
|
|
84
|
+
close.
|
|
85
|
+
- **Keying it on `NODE_ENV`** — rejected. `NODE_ENV !== 'production'` is fail-open: the
|
|
86
|
+
variable is unset often enough in real deployments (a bare `node dist/server.js`, many
|
|
87
|
+
container images, systemd units) that the leak would come back precisely where nothing was
|
|
88
|
+
configured. `NODE_ENV === 'development'` would at least fail closed, but a security-relevant
|
|
89
|
+
response decided by the shell instead of by the registration call is action at a distance.
|
|
90
|
+
- **Replacing `message` with the driver text instead of adding `debugInfo`** — rejected: it
|
|
91
|
+
makes one field mean different things in different environments, so client error handling
|
|
92
|
+
has to be written twice. The additive payload costs nothing and carries more, structured.
|
|
93
|
+
- **Echoing only the SQLSTATE code** (`23505`, no names) — rejected: a third middle mode is
|
|
94
|
+
harder to reason about than a boolean, and reading meaning into SQLSTATE is the direction
|
|
95
|
+
0006 closed.
|
|
96
|
+
- **Sending the detail only to localhost callers** — rejected: it makes the response depend
|
|
97
|
+
on network topology, which reverse proxies routinely flatten.
|
|
98
|
+
|
|
99
|
+
## Consequences
|
|
100
|
+
|
|
101
|
+
- Constraint violations reach clients as `{"statusCode":500,"error":"Internal Server
|
|
102
|
+
Error","message":"Internal Server Error","requestId":"…"}`. Diagnosing one requires the
|
|
103
|
+
server log — which is where the error was already going, under that same id.
|
|
104
|
+
- Behavioural breaking change for consumers who parsed driver messages out of `500` bodies.
|
|
105
|
+
They read `debugInfo` instead, and only where the deployment opted in.
|
|
106
|
+
- Development needs one more line (`exposeDebugInfo: true`) than it did. That is the price of
|
|
107
|
+
the option meaning exactly one thing: what a client sees is readable from the registration
|
|
108
|
+
call alone, without knowing how `debug` or the environment happen to be wired.
|
|
109
|
+
- A deployment that leaves `exposeDebugInfo` on by mistake is back to disclosing schema
|
|
110
|
+
detail. The name is the mitigation: `expose` is the word that makes a reader stop, which
|
|
111
|
+
`debug` is not.
|
|
112
|
+
- Errors thrown by consumer `onRequests` hooks are outside the handler and unaffected: that
|
|
113
|
+
is consumer code running before the plugin's SQL surface, and its messages are the
|
|
114
|
+
consumer's to write.
|
package/docs/adr/README.md
CHANGED
|
@@ -26,11 +26,14 @@ not by editing history.
|
|
|
26
26
|
| [0009](./0009-schema-exclusion-config-blacklist.md) | Schema exclusion is a config blacklist — excluded schemas are removed as orphans |
|
|
27
27
|
| [0010](./0010-joins-do-not-run-route-guards.md) | A declared join is a read grant — joins do not run the target table's route guards |
|
|
28
28
|
| [0011](./0011-join-fields-allowlist.md) | Per-relation field restriction is an allowlist, not a blocklist |
|
|
29
|
+
| [0012](./0012-anti-duplication-tooling.md) | Repository rules are enforced by executable gates, not by documentation |
|
|
30
|
+
| [0013](./0013-sanitized-db-errors.md) | Database error messages are sanitized on the wire by default (narrows 0006) |
|
|
29
31
|
|
|
30
32
|
## When to write one
|
|
31
33
|
|
|
32
34
|
Only when a decision was genuinely debated, or is counter-intuitive enough that a reasonable
|
|
33
|
-
outsider would mistake it for a bug. Plain conventions belong in [AGENTS.md](../../AGENTS.md)
|
|
35
|
+
outsider would mistake it for a bug. Plain conventions belong in [AGENTS.md](../../AGENTS.md)
|
|
36
|
+
for consumers, or in `CLAUDE.md` at the repository root for contributors.
|
|
34
37
|
|
|
35
38
|
## Format
|
|
36
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify-auto-sqlapi",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Fastify plugin for automatic CRUD API generation from PostgreSQL, MySQL and MariaDB tables",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"docs",
|
|
22
|
+
"!docs/INDEX.md",
|
|
22
23
|
"AGENTS.md",
|
|
23
24
|
"AGENTS_BACKEND.md",
|
|
24
25
|
"AGENTS_FRONTEND.md",
|
|
@@ -28,6 +29,21 @@
|
|
|
28
29
|
"scripts": {
|
|
29
30
|
"clean": "rm -rf dist",
|
|
30
31
|
"build": "rm -rf dist && tsc",
|
|
32
|
+
"lint": "eslint . --max-warnings 0",
|
|
33
|
+
"lint:fix": "eslint . --fix",
|
|
34
|
+
"lint:strict": "LINT_STRICT=1 eslint .",
|
|
35
|
+
"typecheck": "tsc --noEmit && tsc -p scripts/tsconfig.json",
|
|
36
|
+
"index": "node scripts/generate-index.ts",
|
|
37
|
+
"index:check": "node scripts/generate-index.ts --check",
|
|
38
|
+
"find-similar": "node scripts/find-similar.ts",
|
|
39
|
+
"dup": "jscpd src",
|
|
40
|
+
"dup:strict": "jscpd src --reporters console",
|
|
41
|
+
"knip": "knip --max-issues 0 --treat-config-hints-as-errors --treat-tag-hints-as-errors",
|
|
42
|
+
"depcruise": "depcruise src --config .dependency-cruiser.mjs --output-type err",
|
|
43
|
+
"depcruise:strict": "DEPCRUISE_STRICT=1 depcruise src --config .dependency-cruiser.mjs --output-type err",
|
|
44
|
+
"debt": "npm run lint:strict; npm run dup:strict; npm run depcruise:strict",
|
|
45
|
+
"check": "npm run lint && npm run typecheck",
|
|
46
|
+
"verify": "npm run check && npm run build && npm run index:check && npm run dup && npm run depcruise && npm run knip && npm run test:unit",
|
|
31
47
|
"prepublishOnly": "npm run build && npm run test:unit",
|
|
32
48
|
"test": "npm run test:unit && npm run test:integration:pg && npm run test:integration:mysql",
|
|
33
49
|
"test:unit": "node --test test/unit/*.test.js",
|
|
@@ -67,18 +83,29 @@
|
|
|
67
83
|
"homepage": "https://github.com/LucaRainone/fastify-auto-sqlapi#readme",
|
|
68
84
|
"dependencies": {
|
|
69
85
|
"@sinclair/typebox": "^0.34.14",
|
|
86
|
+
"fastify-plugin": "^5.1.0",
|
|
70
87
|
"node-condition-builder": "^1.2.0"
|
|
71
88
|
},
|
|
72
89
|
"devDependencies": {
|
|
90
|
+
"@eslint/js": "^10.0.1",
|
|
73
91
|
"@fastify/postgres": "^6.0.2",
|
|
74
92
|
"@fastify/swagger": "^9.0.0",
|
|
75
93
|
"@fastify/swagger-ui": "^5.0.0",
|
|
76
94
|
"@types/node": "^22.13.4",
|
|
77
95
|
"@types/pg": "^8.11.11",
|
|
96
|
+
"dependency-cruiser": "^18.1.0",
|
|
97
|
+
"eslint": "^10.8.0",
|
|
98
|
+
"eslint-plugin-sonarjs": "^4.2.0",
|
|
78
99
|
"fastify": "5.8.5",
|
|
100
|
+
"fuse.js": "^7.5.0",
|
|
101
|
+
"jscpd": "^5.0.14",
|
|
102
|
+
"knip": "^6.31.0",
|
|
103
|
+
"lefthook": "^2.1.10",
|
|
79
104
|
"mysql2": "^3.22.0",
|
|
80
105
|
"pg": "^8.13.1",
|
|
81
|
-
"
|
|
106
|
+
"ts-morph": "^28.0.0",
|
|
107
|
+
"typescript": "^5.7.3",
|
|
108
|
+
"typescript-eslint": "^8.65.0"
|
|
82
109
|
},
|
|
83
110
|
"peerDependencies": {
|
|
84
111
|
"@fastify/mysql": ">=4.0.0",
|