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.
Files changed (101) hide show
  1. package/AGENTS_BACKEND.md +52 -7
  2. package/BREAKING_CHANGES.md +86 -5
  3. package/CHANGELOG.md +57 -0
  4. package/README.md +110 -3
  5. package/dist/bin/generate-schema.js +89 -77
  6. package/dist/bin/generate-tables.js +72 -62
  7. package/dist/bin/utils.d.ts.map +1 -1
  8. package/dist/bin/utils.js +47 -33
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/lib/agent/manifest.d.ts +3 -2
  12. package/dist/lib/agent/manifest.d.ts.map +1 -1
  13. package/dist/lib/agent/manifest.js +102 -80
  14. package/dist/lib/cli/mysql-introspect.d.ts +4 -9
  15. package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
  16. package/dist/lib/cli/mysql-introspect.js +8 -7
  17. package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
  18. package/dist/lib/cli/pg-introspect.js +0 -1
  19. package/dist/lib/cli/schema-codegen.d.ts +3 -0
  20. package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
  21. package/dist/lib/cli/schema-codegen.js +3 -0
  22. package/dist/lib/cli/tables-codegen.d.ts +8 -1
  23. package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
  24. package/dist/lib/cli/tables-codegen.js +100 -118
  25. package/dist/lib/condition-methods.d.ts +14 -4
  26. package/dist/lib/condition-methods.d.ts.map +1 -1
  27. package/dist/lib/condition-methods.js +18 -4
  28. package/dist/lib/db.d.ts +7 -2
  29. package/dist/lib/db.d.ts.map +1 -1
  30. package/dist/lib/db.js +24 -6
  31. package/dist/lib/dialect.d.ts +2 -1
  32. package/dist/lib/dialect.d.ts.map +1 -1
  33. package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
  34. package/dist/lib/engine/bulk/bulk-upsert.js +44 -36
  35. package/dist/lib/engine/rest/delete.d.ts.map +1 -1
  36. package/dist/lib/engine/rest/get.d.ts.map +1 -1
  37. package/dist/lib/engine/rest/get.js +5 -4
  38. package/dist/lib/engine/rest/insert.d.ts.map +1 -1
  39. package/dist/lib/engine/rest/insert.js +1 -1
  40. package/dist/lib/engine/rest/update.d.ts.map +1 -1
  41. package/dist/lib/engine/rest/update.js +26 -19
  42. package/dist/lib/engine/search/aggregations.d.ts +33 -0
  43. package/dist/lib/engine/search/aggregations.d.ts.map +1 -0
  44. package/dist/lib/engine/search/aggregations.js +109 -0
  45. package/dist/lib/engine/search/conditions.d.ts +31 -0
  46. package/dist/lib/engine/search/conditions.d.ts.map +1 -0
  47. package/dist/lib/engine/search/conditions.js +131 -0
  48. package/dist/lib/engine/search/fields.d.ts +49 -0
  49. package/dist/lib/engine/search/fields.d.ts.map +1 -0
  50. package/dist/lib/engine/search/fields.js +142 -0
  51. package/dist/lib/engine/search/join-families.d.ts +35 -0
  52. package/dist/lib/engine/search/join-families.d.ts.map +1 -0
  53. package/dist/lib/engine/search/join-families.js +345 -0
  54. package/dist/lib/engine/search/joins.d.ts +55 -0
  55. package/dist/lib/engine/search/joins.d.ts.map +1 -0
  56. package/dist/lib/engine/search/joins.js +81 -0
  57. package/dist/lib/engine/search/order-by.d.ts +35 -0
  58. package/dist/lib/engine/search/order-by.d.ts.map +1 -0
  59. package/dist/lib/engine/search/order-by.js +104 -0
  60. package/dist/lib/engine/search/search.d.ts.map +1 -1
  61. package/dist/lib/engine/search/search.js +106 -791
  62. package/dist/lib/engine/validate.d.ts +0 -5
  63. package/dist/lib/engine/validate.d.ts.map +1 -1
  64. package/dist/lib/engine/write-helpers.d.ts +25 -17
  65. package/dist/lib/engine/write-helpers.d.ts.map +1 -1
  66. package/dist/lib/engine/write-helpers.js +86 -44
  67. package/dist/lib/errors.d.ts +24 -0
  68. package/dist/lib/errors.d.ts.map +1 -1
  69. package/dist/lib/errors.js +59 -0
  70. package/dist/lib/read-access.d.ts +12 -1
  71. package/dist/lib/read-access.d.ts.map +1 -1
  72. package/dist/lib/read-access.js +20 -4
  73. package/dist/lib/schema/helpers.d.ts +0 -15
  74. package/dist/lib/schema/helpers.d.ts.map +1 -1
  75. package/dist/lib/schema/helpers.js +7 -4
  76. package/dist/lib/schema/search.d.ts.map +1 -1
  77. package/dist/lib/schema/search.js +107 -128
  78. package/dist/lib/sql-api.d.ts.map +1 -1
  79. package/dist/lib/table-helpers.d.ts.map +1 -1
  80. package/dist/lib/table-helpers.js +48 -0
  81. package/dist/lib/tenant.d.ts +66 -5
  82. package/dist/lib/tenant.d.ts.map +1 -1
  83. package/dist/lib/tenant.js +209 -48
  84. package/dist/routes/auto/plugin.d.ts.map +1 -1
  85. package/dist/routes/auto/plugin.js +6 -0
  86. package/dist/routes/auto/route-helpers.d.ts +0 -7
  87. package/dist/routes/auto/route-helpers.d.ts.map +1 -1
  88. package/dist/routes/auto/route-helpers.js +58 -33
  89. package/dist/routes/auto/search.routes.js +0 -1
  90. package/dist/types/plugin.d.ts +19 -0
  91. package/dist/types/plugin.d.ts.map +1 -1
  92. package/dist/types/table.d.ts +1 -1
  93. package/dist/types/tenant.d.ts +26 -2
  94. package/dist/types/tenant.d.ts.map +1 -1
  95. package/docs/GLOSSARY.md +30 -0
  96. package/docs/adr/0006-raw-db-errors.md +2 -1
  97. package/docs/adr/0010-joins-do-not-run-route-guards.md +4 -1
  98. package/docs/adr/0012-anti-duplication-tooling.md +59 -0
  99. package/docs/adr/0013-sanitized-db-errors.md +114 -0
  100. package/docs/adr/README.md +4 -1
  101. 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;AAElE,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,KAAK,EAAE,WAAW,CAAC;CACpB"}
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"}
@@ -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.
@@ -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.14",
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
- "typescript": "^5.7.3"
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",