fastify-auto-sqlapi 0.2.0 → 0.2.2
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.md +50 -0
- package/AGENTS_BACKEND.md +312 -9
- package/AGENTS_FRONTEND.md +1 -1
- package/BREAKING_CHANGES.md +132 -0
- package/CHANGELOG.md +170 -0
- package/README.md +255 -877
- package/dist/bin/generate-schema.js +49 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/agent/manifest.d.ts +2 -0
- package/dist/lib/agent/manifest.d.ts.map +1 -1
- package/dist/lib/agent/manifest.js +4 -0
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +15 -5
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +53 -14
- package/dist/lib/cli/schema-codegen.d.ts +2 -1
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +17 -4
- package/dist/lib/cli/tables-codegen.d.ts +4 -0
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +125 -27
- package/dist/lib/db.d.ts +6 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +8 -2
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +6 -5
- package/dist/lib/engine/read-hooks.d.ts +19 -0
- package/dist/lib/engine/read-hooks.d.ts.map +1 -0
- package/dist/lib/engine/read-hooks.js +20 -0
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +10 -6
- 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 +15 -11
- package/dist/lib/engine/search/join-families.d.ts +11 -2
- package/dist/lib/engine/search/join-families.d.ts.map +1 -1
- package/dist/lib/engine/search/join-families.js +11 -13
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +14 -7
- package/dist/lib/engine/validate.d.ts +3 -3
- package/dist/lib/engine/validate.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.d.ts +7 -3
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +29 -30
- 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/schema/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/schema/bulk-upsert.js +2 -1
- package/dist/lib/schema/get.d.ts +14 -0
- package/dist/lib/schema/get.d.ts.map +1 -0
- package/dist/lib/schema/get.js +18 -0
- package/dist/lib/schema/helpers.d.ts +21 -3
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +37 -5
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +7 -3
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +3 -11
- package/dist/lib/schema/update.d.ts.map +1 -1
- package/dist/lib/schema/update.js +7 -3
- package/dist/lib/sql-api.d.ts +9 -9
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/sql-api.js +2 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +169 -0
- package/dist/lib/tenant.d.ts +43 -8
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +169 -54
- package/dist/lib/write-access.d.ts +36 -0
- package/dist/lib/write-access.d.ts.map +1 -0
- package/dist/lib/write-access.js +58 -0
- package/dist/routes/auto/get.routes.d.ts.map +1 -1
- package/dist/routes/auto/get.routes.js +3 -2
- package/dist/routes/auto/plugin.d.ts.map +1 -1
- package/dist/routes/auto/plugin.js +8 -2
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +13 -4
- package/dist/routes/setup-swagger.d.ts.map +1 -0
- package/dist/{lib → routes}/sql-api-decorator.d.ts +1 -1
- package/dist/routes/sql-api-decorator.d.ts.map +1 -0
- package/dist/{lib → routes}/sql-api-decorator.js +4 -4
- package/dist/types/cli.d.ts +27 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/dml.d.ts +6 -4
- package/dist/types/dml.d.ts.map +1 -1
- package/dist/types/plugin.d.ts +22 -3
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/request.d.ts +24 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +1 -0
- package/dist/types/schema.d.ts +15 -0
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/search.d.ts +3 -0
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/table.d.ts +84 -11
- package/dist/types/table.d.ts.map +1 -1
- package/dist/types/tenant.d.ts +26 -2
- package/dist/types/tenant.d.ts.map +1 -1
- package/dist/types/validation.d.ts +3 -3
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/docs/GLOSSARY.md +3 -1
- package/docs/adr/0004-updates-always-open.md +2 -1
- 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/0013-sanitized-db-errors.md +114 -0
- package/docs/adr/0014-what-follows-a-write-join.md +84 -0
- package/docs/adr/0015-non-writable-columns.md +86 -0
- package/docs/adr/0016-a-view-is-a-table-config.md +92 -0
- package/docs/adr/0017-the-schema-is-the-contract.md +85 -0
- package/docs/adr/0018-one-seam-to-the-http-framework.md +68 -0
- package/docs/adr/README.md +6 -0
- package/package.json +1 -1
- package/dist/lib/setup-swagger.d.ts.map +0 -1
- package/dist/lib/sql-api-decorator.d.ts.map +0 -1
- /package/dist/{lib → routes}/setup-swagger.d.ts +0 -0
- /package/dist/{lib → routes}/setup-swagger.js +0 -0
|
@@ -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,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.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# 0014. A secondary runs the child table's transform, not its verdicts
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted
|
|
4
|
+
- **Date**: 2026-08-23
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
[ADR 0010](./0010-joins-do-not-run-route-guards.md) drew the line for reads: route-level
|
|
9
|
+
controls (`onRequests`, `operations`) do not follow a join, configuration-level ones
|
|
10
|
+
(`readExclude`, `tenantScope`, the relation's schema) do. The write side never had that line
|
|
11
|
+
drawn, and it had drifted into an inconsistent middle.
|
|
12
|
+
|
|
13
|
+
`processSecondaries` already consulted the child's configuration for two things — it stripped
|
|
14
|
+
the child's `excludeFromCreation` and enforced the child's `tenantScope` — while its
|
|
15
|
+
`beforeInsert` was silently skipped. The gap was invisible until a table started using that
|
|
16
|
+
hook to change a value's *representation* rather than to add a value: a column encrypted on
|
|
17
|
+
the way in was stored as ciphertext through `POST /rest/patient`, and as plaintext through
|
|
18
|
+
`POST /rest/visit` with the same row sent under `secondaries.patient`. Nothing failed; the
|
|
19
|
+
data was simply wrong in a way only a later read would show, and `afterRead` would then
|
|
20
|
+
"decrypt" a value that was never encrypted.
|
|
21
|
+
|
|
22
|
+
The reverse extension is not obviously right either. `validate` and `afterInsert` are also
|
|
23
|
+
per-table configuration, and running them for children would be the symmetric move — but they
|
|
24
|
+
do not transform a record, they *decide* about it: `validate` rejects the request, and
|
|
25
|
+
`afterInsert` fires side effects. Both would change when and how a host's write fails, for
|
|
26
|
+
consumers that never opted in.
|
|
27
|
+
|
|
28
|
+
## Decision
|
|
29
|
+
|
|
30
|
+
**A secondary is a write like any other for everything that shapes the record, and not for
|
|
31
|
+
anything that renders a verdict on it.** The child's configuration is applied in this order,
|
|
32
|
+
mirroring the single-insert pipeline of [ADR 0005](./0005-insert-pipeline-sanitize-before-hooks.md):
|
|
33
|
+
|
|
34
|
+
1. the child's `excludeFromCreation` strip (client-input whitelist);
|
|
35
|
+
2. the child's `beforeInsert` (may mutate, including on excluded fields);
|
|
36
|
+
3. conversion to DB column format;
|
|
37
|
+
4. the engine's FK auto-fill, which is authoritative over both the payload and the hook;
|
|
38
|
+
5. the child's `tenantScope` enforcement.
|
|
39
|
+
|
|
40
|
+
The child's `validate`, `validateBulk`, `afterInsert`, `beforeDelete` and `afterDelete` do not
|
|
41
|
+
run. `processDeletions` is unchanged: it removes rows, it does not shape them.
|
|
42
|
+
|
|
43
|
+
| Child-table config | Applies to a secondary |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `excludeFromCreation` | yes |
|
|
46
|
+
| `beforeInsert` | yes |
|
|
47
|
+
| `tenantScope` | yes |
|
|
48
|
+
| `upsertMap` | yes (via the host's `upsertMap`) |
|
|
49
|
+
| `validate` / `validateBulk` | no |
|
|
50
|
+
| `afterInsert` | no |
|
|
51
|
+
| `onRequests` / `operations` | no (ADR 0010) |
|
|
52
|
+
|
|
53
|
+
The host's own `validate` receives the secondaries and is the place to reject them.
|
|
54
|
+
|
|
55
|
+
## Alternatives considered
|
|
56
|
+
|
|
57
|
+
- **Leave `beforeInsert` skipped and document it** — rejected: the failure is silent and
|
|
58
|
+
writes bad data. A hook that assigns `createdBy` merely produced a null column when skipped;
|
|
59
|
+
a hook that encodes a value produced a row that reads back as garbage, and no error marks
|
|
60
|
+
the moment it happened.
|
|
61
|
+
- **Run the child's full pipeline (`validate` + `afterInsert`)** — rejected for now, not on
|
|
62
|
+
principle. `validate` would make a host's insert start failing on child rows that were
|
|
63
|
+
accepted the day before, and `afterInsert` would fire N side effects inside the host's
|
|
64
|
+
transaction with no ordering contract against the host's own `afterInsert`. Both are
|
|
65
|
+
behaviour changes that need their own decision; the host's `validate` already receives the
|
|
66
|
+
secondaries.
|
|
67
|
+
- **A separate `beforeSecondaryInsert` hook on the host** — rejected: a second name for
|
|
68
|
+
"prepare a row of this table", declared on the wrong table. The transform belongs to the
|
|
69
|
+
table that owns the column, which is the same rule `afterRead` follows on the read side.
|
|
70
|
+
- **Run the child hook before the exclusion strip** — rejected: it inverts ADR 0005 and lets
|
|
71
|
+
a client value survive on an excluded field.
|
|
72
|
+
|
|
73
|
+
## Consequences
|
|
74
|
+
|
|
75
|
+
- Behaviour change (CHANGELOG + BREAKING_CHANGES): a child table's `beforeInsert` now runs for
|
|
76
|
+
rows written as secondaries, where it was silently skipped. A hook that was written assuming
|
|
77
|
+
it only ever saw the table's own route — one that reads `req.params`, or increments a counter
|
|
78
|
+
per call — now runs in a context it did not expect.
|
|
79
|
+
- The hook receives the **transaction** connection, not the pool: what it writes rolls back
|
|
80
|
+
with the host's write.
|
|
81
|
+
- `beforeInsert` is now the one place where "how this table's rows are shaped on the way in"
|
|
82
|
+
is written, on both entry paths — the dual of `afterRead` on the way out.
|
|
83
|
+
- The asymmetry between transform and verdict is now a stated boundary, so the next reader
|
|
84
|
+
finds a decision instead of what looks like the same oversight in a different hook.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 0015. A column the database computes is not writable — `writeExclude` (narrows 0004)
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted
|
|
4
|
+
- **Date**: 2026-08-28
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
[ADR 0004](./0004-updates-always-open.md) rejected `excludeFromUpdate`: a static per-field
|
|
9
|
+
write block, proposed to protect `isAdmin`, `createdAt` and ownership columns. It lost on one
|
|
10
|
+
argument — those cases are **session-dependent**, a static list cannot express them, and
|
|
11
|
+
offering one would give a false sense of security to consumers who used it for exactly that.
|
|
12
|
+
That reasoning stands unchanged.
|
|
13
|
+
|
|
14
|
+
It does not, however, cover a case the plugin met later: a column the database itself refuses
|
|
15
|
+
to be told about.
|
|
16
|
+
|
|
17
|
+
```sql
|
|
18
|
+
subtotal INTEGER GENERATED ALWAYS AS (qty * price) STORED
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Both engines reject the whole statement — not the field — when a write so much as names such a
|
|
22
|
+
column: `428C9 cannot insert a non-DEFAULT value into column "subtotal"` on PostgreSQL,
|
|
23
|
+
`ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN` on MySQL. Yet nothing in the generated schema said
|
|
24
|
+
so, so the column was offered as an ordinary writable field, and a client that sent it back
|
|
25
|
+
what a read had just returned got a **500** — a raw driver error, per
|
|
26
|
+
[ADR 0006](./0006-raw-db-errors.md) — for a payload nobody could have made valid.
|
|
27
|
+
|
|
28
|
+
The same blind spot hid `GENERATED ... AS IDENTITY`, which has no `column_default` to reveal
|
|
29
|
+
that the database fills it in: the generated field came out mandatory and the table could not
|
|
30
|
+
be inserted into at all.
|
|
31
|
+
|
|
32
|
+
## Decision
|
|
33
|
+
|
|
34
|
+
Fields the database computes are excluded from every write path. The CLI records them in the
|
|
35
|
+
generated Schema's `generatedFields`, and the runtime removes them from the insert, update and
|
|
36
|
+
bulk-upsert bodies (main and secondaries) and again inside the engines, which `sqlApi.*`
|
|
37
|
+
reaches without those schemas.
|
|
38
|
+
|
|
39
|
+
`ITable.writeExclude` exposes the same mechanism for the columns introspection cannot know
|
|
40
|
+
about — one a trigger owns, one a migration is about to drop.
|
|
41
|
+
|
|
42
|
+
This narrows 0004 rather than reversing it, and the boundary is what the exclusion is *about*:
|
|
43
|
+
|
|
44
|
+
- **a fact about the column** — the database rejects the value whoever assigned it, so there is
|
|
45
|
+
no configuration in which offering the field helps anyone. This ADR.
|
|
46
|
+
- **a rule about the caller** — who may change what, when. Still product logic, still
|
|
47
|
+
`beforeUpdate` / `validate` / a dedicated endpoint. Still ADR 0004.
|
|
48
|
+
|
|
49
|
+
Three consequences follow from that boundary and are enforced:
|
|
50
|
+
|
|
51
|
+
- `writeExclude` runs **after** the write hooks — the opposite of `excludeFromCreation`, which
|
|
52
|
+
is sanitized before them so a hook can assign the field ([ADR 0005](./0005-insert-pipeline-sanitize-before-hooks.md)).
|
|
53
|
+
A hook cannot put back a value the database will refuse.
|
|
54
|
+
- `defineTable` rejects a field that is also in `readExclude`: neither readable nor writable is
|
|
55
|
+
what removing it from the Schema means.
|
|
56
|
+
- `defineTable` rejects the primary key, which the update body needs to identify the row.
|
|
57
|
+
|
|
58
|
+
## Alternatives considered
|
|
59
|
+
|
|
60
|
+
- **`Schema.generatedFields` only, no public key** — rejected: it covers the introspected case
|
|
61
|
+
and nothing else, and a consumer whose schema is hand-written or whose column is owned by a
|
|
62
|
+
trigger would be back to a driver error with no way out.
|
|
63
|
+
- **Let the write through and map the driver error to a 400** — rejected: it contradicts
|
|
64
|
+
[ADR 0006](./0006-raw-db-errors.md) (no SQLSTATE→HTTP mapping), it costs a round trip to the
|
|
65
|
+
database to learn what the schema already knew, and the field would still be advertised as
|
|
66
|
+
writable in Swagger and in the agent manifest.
|
|
67
|
+
- **Trim the column out of the generated Schema** — rejected: that removes it from reads too,
|
|
68
|
+
and a computed column is precisely the one a client wants to read.
|
|
69
|
+
- **Reopen 0004 and add a general `excludeFromUpdate`** — rejected: the false-sense-of-security
|
|
70
|
+
argument is untouched by anything here. The documentation for `writeExclude` says what it is
|
|
71
|
+
not for, and the key deliberately has no per-caller or per-role dimension that would invite
|
|
72
|
+
the misuse 0004 warned about.
|
|
73
|
+
|
|
74
|
+
## Consequences
|
|
75
|
+
|
|
76
|
+
- Under Fastify's default `removeAdditional: true`, a computed column in a write body is
|
|
77
|
+
**stripped silently** rather than rejected; the row is written with the value the database
|
|
78
|
+
computed. With `removeAdditional: false` the closed body schema answers 400. Either way the
|
|
79
|
+
driver error is gone — which was the point.
|
|
80
|
+
- A consumer upgrading gets the fix from **regenerating the Schema files alone**: the exclusion
|
|
81
|
+
travels in `generatedFields`, not in the `Table*.ts` the generator never overwrites.
|
|
82
|
+
- The generated `Table*.ts` names the computed columns in a comment and leaves `writeExclude`
|
|
83
|
+
commented out, and `sqlapi-generate-schema` prints them per table — a generated source file
|
|
84
|
+
is not always read, its command's output is.
|
|
85
|
+
- Reads are untouched: a computed column stays projected, filterable, orderable and
|
|
86
|
+
aggregatable, because it is a real column with a real value.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# 0016. A view is a table config — the generator refuses to invent its primary key
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted
|
|
4
|
+
- **Date**: 2026-08-28
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Views were never designed for, and they worked anyway: both introspections read
|
|
9
|
+
`information_schema.columns`, which lists view columns exactly like table columns, so
|
|
10
|
+
`sqlapi-generate-schema` produced a Schema and searching the result worked with no special
|
|
11
|
+
support. Three things underneath that were wrong.
|
|
12
|
+
|
|
13
|
+
**The primary key was invented in silence.** A view carries no `PRIMARY KEY` constraint, so
|
|
14
|
+
`is_primary` is false on every column and the generated Schema had no `primaryKey`. The table
|
|
15
|
+
generator then fell through to its heuristic for hand-written schemas: `id` if present,
|
|
16
|
+
otherwise **the first integer column**, otherwise the first column of any kind. On
|
|
17
|
+
`SELECT status, count(*) AS order_count ... GROUP BY status` that hands back `order_count` — a
|
|
18
|
+
count — as the key, and `get/:id`, `update` and `delete` then address rows by it. Nothing said
|
|
19
|
+
so, in the file or on the console.
|
|
20
|
+
|
|
21
|
+
**Nothing validated `primary` at all.** `defineTable` checked `tenantScope`, `readExclude`,
|
|
22
|
+
`computedFields` and alias uniqueness, but a `primary` naming a field the schema does not have
|
|
23
|
+
passed straight through and became a raw SQL error on the first request — a 500, per
|
|
24
|
+
[ADR 0006](./0006-raw-db-errors.md).
|
|
25
|
+
|
|
26
|
+
**PostgreSQL materialized views were invisible.** They are not in `information_schema` at all;
|
|
27
|
+
they live only in `pg_catalog`. They were skipped without a word — and a materialized view is
|
|
28
|
+
precisely the kind of expensive read one wants to expose.
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
A view **is** a table config. No `ViewName` file, no `View*` type, no `readOnly` flag: the
|
|
33
|
+
plugin has one concept for "a relation with columns you can query" and a view is that one. A
|
|
34
|
+
second name would fork the `dbTables` keys, the generator, the docs and the manifest to express
|
|
35
|
+
nothing the existing vocabulary cannot.
|
|
36
|
+
|
|
37
|
+
What changes is what may be **assumed**, and all of it lives in the generator and in startup
|
|
38
|
+
validation, not in the runtime semantics:
|
|
39
|
+
|
|
40
|
+
- Introspection reports `is_view` on both engines, and PostgreSQL materialized views are read
|
|
41
|
+
from `pg_catalog` and reported the same way. The generated Schema carries `isView: true`.
|
|
42
|
+
- For a view the generator **never guesses a key from a column type**. It accepts `id` when the
|
|
43
|
+
view has one — a strong enough signal — and says in the file that the key was *inferred, not
|
|
44
|
+
read from the database*. With no `id`, it writes a `TODO_pick_a_unique_column` placeholder
|
|
45
|
+
rather than a wrong answer.
|
|
46
|
+
- A view's generated config starts read-only: `operations: ['search', 'get']`, or
|
|
47
|
+
`['search']` when the key is unresolved. `defaultOrder` names a real column, so search never
|
|
48
|
+
falls back to the placeholder, and the config **works as generated** — the TODO is a note for
|
|
49
|
+
whoever wants to widen it, not a defect.
|
|
50
|
+
- `defineTable` now validates `primary` against the schema fields. The exception is exact and
|
|
51
|
+
narrow: a table exposing **only** `search` and naming its own `defaultOrder` never reads
|
|
52
|
+
`primary`, so it is allowed. Anything else fails at startup.
|
|
53
|
+
- `sqlapi-generate-schema` prints the views it found and both assumptions it had to make. A
|
|
54
|
+
generated source file is not always read; the output of the command that produced it is.
|
|
55
|
+
|
|
56
|
+
Writes through a view are **not** blocked at runtime. A view simple enough for the engine to
|
|
57
|
+
make updatable accepts INSERT and UPDATE on both PostgreSQL and MySQL, and using one as a
|
|
58
|
+
projection or permission layer is a legitimate pattern the plugin has no business refusing. The
|
|
59
|
+
read-only start is the *generated template* — a file the developer then owns — which is a
|
|
60
|
+
different thing from the runtime default that [ADR 0002](./0002-open-by-default.md) settles.
|
|
61
|
+
That default is untouched: a hand-written view config with no `operations` still exposes
|
|
62
|
+
everything.
|
|
63
|
+
|
|
64
|
+
## Alternatives considered
|
|
65
|
+
|
|
66
|
+
- **`ViewName` files / a distinct type** — rejected: it forks a concept for no expressive gain
|
|
67
|
+
and breaks the one-name-per-concept rule the glossary exists to enforce.
|
|
68
|
+
- **A `readOnly: true` flag on `defineTable`** — rejected: a second spelling of
|
|
69
|
+
`operations: ['search', 'get']`.
|
|
70
|
+
- **Blocking writes to views at runtime** — rejected: simple views are genuinely updatable on
|
|
71
|
+
both engines (verified), and the plugin cannot tell which ones from the schema alone. It
|
|
72
|
+
would remove a working feature to prevent an error the database already reports.
|
|
73
|
+
- **Keep guessing the key, just add a comment** — rejected: the wrong-key failure is silent and
|
|
74
|
+
addresses *rows*, which is the worst kind. A placeholder that refuses to start is the loud
|
|
75
|
+
version of the same information.
|
|
76
|
+
- **Skip views entirely, or leave materialized views unsupported and document it** — rejected:
|
|
77
|
+
they already worked for reads, which is the case that matters, and silence about a relation
|
|
78
|
+
the database has is the least defensible of the options.
|
|
79
|
+
|
|
80
|
+
## Consequences
|
|
81
|
+
|
|
82
|
+
- A view without an `id` column needs one manual edit before it can serve anything but
|
|
83
|
+
`search`. That is the point: the edit is a decision about uniqueness that only the author of
|
|
84
|
+
the view can make.
|
|
85
|
+
- `defineTable` now throws at startup on a `primary` that names no schema field. A config that
|
|
86
|
+
relied on that tolerance — a primary key deliberately kept out of the Schema — has to name a
|
|
87
|
+
schema field or restrict itself to `operations: ['search']`.
|
|
88
|
+
- Materialized views appear in generation runs where they did not before, so a project that
|
|
89
|
+
regenerates with `--all` will find new `Table*.ts` files for them.
|
|
90
|
+
- The `is_view` flag is generator-facing. Nothing in the request path branches on it: a view
|
|
91
|
+
searches, filters, orders, paginates, joins and (when the database allows) writes through the
|
|
92
|
+
same code as a base table.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# 0017. The database schema is the API contract — by default, and only by default
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted
|
|
4
|
+
- **Date**: 2026-08-28
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The first objection this plugin meets is always the same: *it couples the API to the database.*
|
|
9
|
+
Change a column and you change the API, and through it every client.
|
|
10
|
+
|
|
11
|
+
The objection is real and worth answering precisely, because the imprecise answer ("it's fine
|
|
12
|
+
for internal tools") concedes more than it has to and defends less than it should.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
The database schema is the API contract **by default**. That is the mechanism, not a shortcut,
|
|
17
|
+
and it rests on three things.
|
|
18
|
+
|
|
19
|
+
**The coupling is exposed, not created.** A hand-written CRUD layer over the same tables is
|
|
20
|
+
coupled to exactly the same schema — it just retypes it. Rename a column there and you edit the
|
|
21
|
+
model, the DTO, the mapper, the validator, the Swagger annotation and the client, in six places
|
|
22
|
+
instead of one. What that layer genuinely buys is different: an indirection that lets the
|
|
23
|
+
contract stay still while the schema moves. That is a real thing to want, and it is paid for
|
|
24
|
+
continuously and cashed in rarely. This plugin makes the opposite bet, and states it.
|
|
25
|
+
|
|
26
|
+
**It is checked, not implied.** The Schema files are generated from `information_schema`, so a
|
|
27
|
+
schema change that breaks the API breaks it when you regenerate and compile — for TypeScript
|
|
28
|
+
consumers, at build time. A hand-written mapper that nobody remembered to update fails in
|
|
29
|
+
production instead. (Untyped clients — a browser fetch — still find out at runtime; the
|
|
30
|
+
generated Swagger and `GET {prefix}/agent/manifest.md` are what they check against.)
|
|
31
|
+
|
|
32
|
+
**It is a default, and every part of it is overridable.** The exposed shape is not forced to be
|
|
33
|
+
the stored shape:
|
|
34
|
+
|
|
35
|
+
| To decouple | Use |
|
|
36
|
+
|---|---|
|
|
37
|
+
| a column exists but must never be read | `readExclude` |
|
|
38
|
+
| a column exists but must never be written | `writeExclude` ([ADR 0015](./0015-non-writable-columns.md)) |
|
|
39
|
+
| a column must not exist for the API at all | trim it out of the Schema |
|
|
40
|
+
| the API type must be stricter than the column | `schemaOverrides` |
|
|
41
|
+
| the API value is derived, not stored | `computedFields` |
|
|
42
|
+
| the stored representation is not the API one | `afterRead` (decrypt, unpack, rescale) |
|
|
43
|
+
| a table must not have all seven routes | `operations` |
|
|
44
|
+
| a relation must be named for the API, not the DB | the relation `alias`, plus its `fields` allowlist |
|
|
45
|
+
| a filter has no column behind it | `extraFilters` + `extendedCondition` |
|
|
46
|
+
| different callers need different surfaces | register the plugin more than once, under different prefixes |
|
|
47
|
+
|
|
48
|
+
And when none of that fits, the operation is not a CRUD operation: write the route by hand and
|
|
49
|
+
call `app.sqlApi.*` inside it. That is not the fallback path — it is the design. The custom
|
|
50
|
+
route keeps filters, joins, tenant scoping, hooks and validation; what it does not keep is the
|
|
51
|
+
assumption that the endpoint looks like a table.
|
|
52
|
+
|
|
53
|
+
**Where the objection is right.** A public or third-party API — consumers you cannot redeploy
|
|
54
|
+
alongside your schema, a contract with a version number and a deprecation policy — is a surface
|
|
55
|
+
whose stability has to outlive refactors. There an indirection that survives schema movement
|
|
56
|
+
earns its cost, and generating that surface from the schema is the wrong default. Do not
|
|
57
|
+
auto-expose it: keep those tables off the auto routes with `operations`, or behind their own
|
|
58
|
+
hand-written endpoints, and let the plugin serve the surface it is for.
|
|
59
|
+
|
|
60
|
+
That surface is the internal one: back-office and admin tools where superadmin, admin and
|
|
61
|
+
tenant-admin roles need full control over the domain, the schema *is* the domain model, and the
|
|
62
|
+
people who change a column are the people who change the screen that shows it. There, the
|
|
63
|
+
translation layer has no reader — it exists only to be kept in sync with the thing it
|
|
64
|
+
translates.
|
|
65
|
+
|
|
66
|
+
## Alternatives considered
|
|
67
|
+
|
|
68
|
+
- **A mapping/DTO layer inside the plugin** — rejected: that is the hand-written layer again,
|
|
69
|
+
re-expressed as configuration instead of code, with the same maintenance cost and less
|
|
70
|
+
freedom. The per-concern levers above cover what most projects actually need to hide or
|
|
71
|
+
reshape, and a custom route covers the rest without inventing a second schema language.
|
|
72
|
+
- **Frozen schema snapshots, versioned independently of the database** — rejected: a second
|
|
73
|
+
source of truth to keep in sync, which is precisely what [ADR 0001](./0001-no-orm-raw-sql.md)
|
|
74
|
+
refused when it refused an ORM's models and migrations.
|
|
75
|
+
- **Saying nothing and letting readers work it out** — rejected: it is the first question every
|
|
76
|
+
reviewer asks, and an unanswered one reads as an unconsidered one.
|
|
77
|
+
|
|
78
|
+
## Consequences
|
|
79
|
+
|
|
80
|
+
- Renaming a column changes the API field. Accepted: it is one regeneration, and typed
|
|
81
|
+
consumers fail to compile rather than failing quietly.
|
|
82
|
+
- The plugin is not the right tool for a versioned public API, and says so in the README rather
|
|
83
|
+
than leaving it to be discovered.
|
|
84
|
+
- Every "the API should not look like the table" request has a specific answer in the table
|
|
85
|
+
above, or is a custom route. Neither is a gap in the plugin.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 0018. The HTTP framework is reachable through one seam — `ApiRequest`
|
|
2
|
+
|
|
3
|
+
- **Status**: accepted
|
|
4
|
+
- **Date**: 2026-08-29
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The question was what it would take to serve the same engine over something other than Fastify.
|
|
9
|
+
Measuring first turned up a codebase that was already almost free of one:
|
|
10
|
+
|
|
11
|
+
- of 74 files under `src/`, 26 named a Fastify type and **not one imported a value from it**;
|
|
12
|
+
- **nothing under `src/lib/` ever read a property off the request** — no `body`, no `params`, no
|
|
13
|
+
`log`. It arrives from the route, is forwarded to `getTenantId`, `validate` and the hooks, and
|
|
14
|
+
is otherwise untouched;
|
|
15
|
+
- `httpError` produces a plain `Error` carrying `statusCode`. Fastify happens to read that; the
|
|
16
|
+
plugin does not produce it *for* Fastify.
|
|
17
|
+
|
|
18
|
+
So the engine, the schema builders and the write pipeline depend on a web framework for exactly
|
|
19
|
+
one thing: the **name of a type they never dereference**. That dependency was spread across
|
|
20
|
+
thirteen files, where every new hook signature quietly widened it, and nothing prevented an
|
|
21
|
+
engine from starting to read `request.headers` tomorrow.
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
The request type is declared once, in `src/types/request.ts`, and everything in the core refers
|
|
26
|
+
to that name:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export type ApiRequest = FastifyRequest;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`.dependency-cruiser.mjs` enforces it: `http-framework-behind-one-seam` fails the build on a
|
|
33
|
+
`fastify` import — **including a type-only one** — anywhere under `src/lib/`, `src/types/` or
|
|
34
|
+
`src/bin/` other than that file. The two Fastify bridge helpers that lived in `lib/`
|
|
35
|
+
(`setup-swagger`, `sql-api-decorator`) moved to `src/routes/`, which is where the adapter is, so
|
|
36
|
+
the rule needs no exceptions.
|
|
37
|
+
|
|
38
|
+
A plain alias, not a generic parameter on `ITable`. `ITable` appears as a bare type in over a
|
|
39
|
+
hundred places; threading `<Req>` through all of them — and through `DbTables`, and every
|
|
40
|
+
`*Params` — changes the public type surface today to buy a property that only pays off once the
|
|
41
|
+
core ships as a package of its own. The alias makes the seam **one file**, which is the whole
|
|
42
|
+
requirement, and leaves the generic available later without having spent anything on it now.
|
|
43
|
+
|
|
44
|
+
This decision is about where the dependency lives, not about supporting a second framework. It
|
|
45
|
+
records what the measurement found, and stops it from silently degrading.
|
|
46
|
+
|
|
47
|
+
## Alternatives considered
|
|
48
|
+
|
|
49
|
+
- **`ITable<F, Req = FastifyRequest>`** — rejected for now, as above: the churn is large, it
|
|
50
|
+
changes the public types, and mixed `DbTables` maps get awkward. It is the right shape for a
|
|
51
|
+
three-package split and can be done then, from this seam, in one file's worth of decisions.
|
|
52
|
+
- **Leave `FastifyRequest` inline and write the constraint in the docs** — rejected: a property
|
|
53
|
+
that thirteen files must remember is a property that lasts until the next hook is added.
|
|
54
|
+
Gates, not documentation (ADR 0012).
|
|
55
|
+
- **Make the core read nothing by typing the request as `unknown`** — rejected: it would break
|
|
56
|
+
every consumer's typed hook to express something the consumer does not need to know.
|
|
57
|
+
|
|
58
|
+
## Consequences
|
|
59
|
+
|
|
60
|
+
- Consumers see no change: `ApiRequest` *is* `FastifyRequest`, and it is exported from the
|
|
61
|
+
package for anyone declaring a hook outside a `defineTable` call.
|
|
62
|
+
- The core cannot regress. An engine that starts naming a Fastify type fails `npm run verify`.
|
|
63
|
+
- A second HTTP adapter is now a question about `src/routes/` (582 lines) plus what Fastify does
|
|
64
|
+
for free — request validation against the generated schemas, and response serialization, which
|
|
65
|
+
is part of the contract rather than plumbing — and not a question about the engine at all.
|
|
66
|
+
- Fixing the rule so it could fire revealed that `no-static-optional-peer` had never been able
|
|
67
|
+
to: `includeOnly: '^src/'` dropped every npm edge from the graph. It is live now, qualified to
|
|
68
|
+
what its name says (static, value-level imports of optional peers).
|
package/docs/adr/README.md
CHANGED
|
@@ -27,6 +27,12 @@ not by editing history.
|
|
|
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
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) |
|
|
31
|
+
| [0014](./0014-what-follows-a-write-join.md) | A secondary runs the child table's transform, not its verdicts |
|
|
32
|
+
| [0015](./0015-non-writable-columns.md) | A column the database computes is not writable — `writeExclude` (narrows 0004) |
|
|
33
|
+
| [0016](./0016-a-view-is-a-table-config.md) | A view is a table config — the generator refuses to invent its primary key |
|
|
34
|
+
| [0017](./0017-the-schema-is-the-contract.md) | The database schema is the API contract — by default, and only by default |
|
|
35
|
+
| [0018](./0018-one-seam-to-the-http-framework.md) | The HTTP framework is reachable through one seam — `ApiRequest` |
|
|
30
36
|
|
|
31
37
|
## When to write one
|
|
32
38
|
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setup-swagger.d.ts","sourceRoot":"","sources":["../../src/lib/setup-swagger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAkB,MAAM,aAAa,CAAC;AAEvE,wBAAsB,YAAY,CAChC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sql-api-decorator.d.ts","sourceRoot":"","sources":["../../src/lib/sql-api-decorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,eAAe;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,IAAI,CA2BN"}
|
|
File without changes
|
|
File without changes
|