fastify-auto-sqlapi 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/AGENTS.md +24 -111
  2. package/AGENTS_BACKEND.md +282 -1332
  3. package/AGENTS_FRONTEND.md +70 -47
  4. package/BREAKING_CHANGES.md +131 -0
  5. package/CHANGELOG.md +139 -0
  6. package/README.md +1 -1
  7. package/dist/bin/generate-schema.js +2 -2
  8. package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
  9. package/dist/lib/cli/mysql-introspect.js +24 -1
  10. package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
  11. package/dist/lib/cli/pg-introspect.js +21 -2
  12. package/dist/lib/cli/schema-codegen.d.ts +1 -1
  13. package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
  14. package/dist/lib/cli/schema-codegen.js +31 -3
  15. package/dist/lib/db.d.ts.map +1 -1
  16. package/dist/lib/db.js +187 -60
  17. package/dist/lib/dialect.d.ts +15 -10
  18. package/dist/lib/dialect.d.ts.map +1 -1
  19. package/dist/lib/dialect.js +12 -31
  20. package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
  21. package/dist/lib/engine/bulk/bulk-upsert.js +2 -1
  22. package/dist/lib/engine/rest/insert.d.ts.map +1 -1
  23. package/dist/lib/engine/rest/insert.js +2 -1
  24. package/dist/lib/engine/write-helpers.d.ts +13 -1
  25. package/dist/lib/engine/write-helpers.d.ts.map +1 -1
  26. package/dist/lib/engine/write-helpers.js +35 -0
  27. package/dist/lib/schema/helpers.d.ts +7 -0
  28. package/dist/lib/schema/helpers.d.ts.map +1 -1
  29. package/dist/lib/schema/helpers.js +18 -0
  30. package/dist/lib/schema/insert.d.ts.map +1 -1
  31. package/dist/lib/schema/insert.js +3 -1
  32. package/dist/lib/table-helpers.d.ts.map +1 -1
  33. package/dist/lib/table-helpers.js +41 -0
  34. package/dist/lib/tenant.d.ts +6 -0
  35. package/dist/lib/tenant.d.ts.map +1 -1
  36. package/dist/lib/tenant.js +16 -0
  37. package/dist/routes/auto/bulk-delete.routes.js +1 -1
  38. package/dist/routes/auto/bulk-upsert.routes.js +1 -1
  39. package/dist/routes/auto/delete.routes.js +1 -1
  40. package/dist/routes/auto/get.routes.js +1 -1
  41. package/dist/routes/auto/insert.routes.js +1 -1
  42. package/dist/routes/auto/route-helpers.d.ts +5 -2
  43. package/dist/routes/auto/route-helpers.d.ts.map +1 -1
  44. package/dist/routes/auto/route-helpers.js +1 -1
  45. package/dist/routes/auto/search.routes.js +3 -3
  46. package/dist/routes/auto/update.routes.js +1 -1
  47. package/dist/types/cli.d.ts +11 -0
  48. package/dist/types/cli.d.ts.map +1 -1
  49. package/dist/types/schema.d.ts +7 -0
  50. package/dist/types/schema.d.ts.map +1 -1
  51. package/docs/adr/0020-no-conditional-or-mass-update-route.md +68 -0
  52. package/docs/adr/0021-upsert-targets-a-verified-unique-key.md +153 -0
  53. package/docs/adr/README.md +2 -0
  54. package/package.json +4 -4
package/AGENTS.md CHANGED
@@ -1,121 +1,34 @@
1
- # fastify-auto-sqlapi — Agent Instructions
1
+ # fastify-auto-sqlapi — agent instructions
2
2
 
3
- You are configuring a Fastify server that uses `fastify-auto-sqlapi` to auto-generate CRUD APIs from database tables. Follow these instructions precisely.
3
+ A Fastify plugin that generates REST CRUD endpoints (search, get, insert, update, delete, bulk upsert, bulk delete) from database tables. No ORM, raw SQL. PostgreSQL, MySQL, MariaDB.
4
4
 
5
- ## Overview
5
+ ## Which file to read
6
6
 
7
- The plugin generates REST endpoints (search, get, insert, update, delete, bulk upsert, bulk delete) from database table definitions. No ORM — raw SQL. Supports **PostgreSQL**, **MySQL**, and **MariaDB**. The consumer defines table configurations, and the plugin handles routing, validation, and query execution.
7
+ - Calling the API (fetch, list page, form, agent tool): AGENTS_FRONTEND.md, whole file. It is the complete request grammar.
8
+ - This deployment's tables, fields and join aliases: `GET {prefix}/agent/manifest.md` (when `agentManifest` is on) or Swagger. The docs give the grammar, the manifest gives the vocabulary.
9
+ - Setting up the plugin, configuring tables, joins, tenant scope, hooks, validation, querying from custom routes/scripts/jobs (`sqlApi`), raw SQL in hooks (`db`): AGENTS_BACKEND.md.
10
+ - Migrating from the old join API (`joinFilters`, `joins`, `joinGroups`): BREAKING_CHANGES.md.
11
+ - A behavior that looks wrong: docs/adr/ first. These include open-by-default `operations`, non-transactional bulk, always-updatable fields, raw DB errors, and joins not running the target's route guards. They are recorded decisions, not gaps.
8
12
 
9
- **The database schema is the API contract, on purpose.** The exposed shape follows the stored shape *by default*, and every part of that default is overridable — `readExclude`, `writeExclude`, `excludeColumns` (CLI config), `schemaOverrides`, `computedFields`, `afterRead`, `operations`, relation `alias`/`fields`, `extraFilters`. Two consequences for how you work:
13
+ These files are the contract. **Do not read the plugin's sources** (`node_modules/fastify-auto-sqlapi/dist/`): they are internal, carry no stability guarantee, and say nothing about which behaviors are deliberate. If the docs do not answer, say so and ask. Never copy an internal helper into consumer code.
10
14
 
11
- - Do not propose a DTO or mapping layer, and do not reshape a response by hand in a route wrapper. Reach for the lever that matches the concern (the table is in [ADR 0017](./docs/adr/0017-the-schema-is-the-contract.md)); when none fits, the operation is not CRUD — write a custom route and call `app.sqlApi.*` inside it, which keeps filters, joins, tenant scoping, hooks and validation.
12
- - The target surface is internal: back-office and admin tools where the schema *is* the domain model. If the user is building a **public or third-party API** — consumers they cannot redeploy with their schema, a versioned contract — say so, and keep those tables off the auto routes via `operations` rather than generating the surface from the schema.
15
+ ## The database schema is the API contract, on purpose
13
16
 
14
- ## Start here
17
+ The exposed shape follows the stored shape by default, and every part of that default is overridable with these levers:
18
+ - `readExclude`, `writeExclude`, `excludeColumns` (CLI)
19
+ - `schemaOverrides`, `computedFields`, `afterRead`
20
+ - `operations`, relation `alias`/`fields`, `extraFilters`
15
21
 
16
- These files are the **contract**. Everything a consumer can rely on is documented here; find
17
- the answer in the table below and stop there.
22
+ This has two consequences:
23
+ - **Do not add a DTO/mapping layer or reshape responses in a route wrapper.** Use the matching lever (ADR 0017). When none fits, the operation is not CRUD: write a custom route that calls `app.sqlApi.*`. It keeps filters, joins, tenant scoping, hooks and validation.
24
+ - **The target is internal surfaces** (back-office and admin tools where the schema is the domain model). If the user is building a public or third-party API (consumers who cannot redeploy with the schema, a versioned contract), say so, and keep those tables off the auto routes via `operations`.
18
25
 
19
- | I need to… | Read |
20
- |---|---|
21
- | Call the API from a client (fetch, list page, form, agent tool) | **[AGENTS_FRONTEND.md](./AGENTS_FRONTEND.md)** — whole file, it is the complete request grammar |
22
- | Know *this* deployment's tables, fields and join aliases | `GET {prefix}/agent/manifest.md` (when `agentManifest` is on), or Swagger — the docs give the grammar, the manifest gives the vocabulary |
23
- | Add or configure a table, joins, tenant scope, hooks, validation | **[AGENTS_BACKEND.md](./AGENTS_BACKEND.md)** — see its section map |
24
- | Query the data from a custom route, a script or a background job | AGENTS_BACKEND.md → *SqlApi* |
25
- | Run raw SQL inside a hook | AGENTS_BACKEND.md → *QueryClient API* |
26
- | Migrate from the old join API (`joinFilters`, `joins`, `joinGroups`) | **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** |
27
- | Understand why a behavior that looks wrong is intended | **[docs/adr/](./docs/adr/README.md)** |
26
+ ## Naming
28
27
 
29
- **Do not read the plugin's own sources.** `node_modules/fastify-auto-sqlapi/dist/` is compiled
30
- output: internal helpers, no stability guarantee, and no indication of which behaviors are
31
- deliberate. An answer reverse-engineered from it is right today and wrong at the next release.
32
- If a question is not answered by the files above, say so and ask — do not infer the API from
33
- the implementation, and never copy an internal helper into consumer code.
28
+ - camelCase schema field names everywhere: request/response bodies, `defineTable` keys, `buildRelation`, hooks, validators, `sqlApi.*`.
29
+ - Real DB column names only where you address the database yourself:
30
+ - `db.*` (QueryClient) record keys, `where` and `conflictKeys`, plus the real table name `Schema.tableName`;
31
+ - `tenantScope` columns;
32
+ - SQL you write (`extendedCondition`, computed `expr`), where columns come from `Schema.col('field')` / `qiCol('field')` and are never typed literally.
34
33
 
35
- ## Naming: camelCase in code, real column names only when you address the DB directly
36
-
37
- Everything the plugin exposes is a **camelCase schema field name**. The database is free to name
38
- its columns however it likes: snake_case columns (`organization_id`) are converted to camelCase
39
- (`organizationId`) for the code, and columns already in camelCase (e.g. betterauth) are preserved
40
- verbatim through the `colMap` the CLI generates. Either way `Schema.col('organizationId')` returns
41
- the real column — the mapping lives in the generated Schema, so no naming convention is imposed on
42
- the database and none has to be remembered. One rule: **write camelCase everywhere except where
43
- you name a column to the database yourself.**
44
-
45
- | camelCase — schema field names | real column names — whatever the table uses |
46
- |---|---|
47
- | every request and response body: `filters`, `conditions`, `orderBy`, `selectComputed`, `main`, secondaries | `db.*` (QueryClient) calls: record keys, `where`, `conflictKeys` — and the real table name (`Schema.tableName`), not the schema variable |
48
- | `defineTable` keys: `primary`, `excludeFromCreation`, `readExclude`, `schemaOverrides`, `buildUpsertRule(Schema, […])` | `tenantScope`: `column`, `anyOf[…]`, `through.localField` / `through.foreignField` |
49
- | `buildRelation(Main, mainField, Join, joinField)` | any SQL string you write yourself — `extendedCondition`, `computedFields.expr`: get the column with `Schema.col('field')` or `qiCol('field')`, never type it literally |
50
- | hooks and validators — `validate`, `beforeInsert`/`afterInsert`, `beforeUpdate`/`afterUpdate`, `beforeDelete`/`afterDelete`, `afterRead` — receive and mutate camelCase records | |
51
- | `sqlApi.search / get / insert / update / delete / bulkUpsert / bulkDelete` — same shapes as the HTTP bodies | |
52
-
53
- Two consequences worth remembering: a `tenantScope` names the table's own columns (as in
54
- `{ anyOf: ['requester_agent_id', 'target_agent_id'] }` for a snake_case table) while the client
55
- still anchors the write by sending `requesterAgentId`; and `sqlApi` and `db` are **not**
56
- interchangeable — `sqlApi` is the high-level API (camelCase, hooks, tenant, joins), `db` is the SQL
57
- layer underneath it (real column names, no hooks, no tenant).
58
-
59
- ## Documentation
60
-
61
- - **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** — **READ FIRST when migrating.** Maps the old join API (`joinFilters`, `joins`, `joinGroups`) to the new alias-based one (`joinMustExist`, `joinMultiple`, `joinGroup`, `joinLeft`). Covers `buildRelation` new options-object signature (alias defaults to `joinSchema.tableName`, plus `selection` and `unique`), request/response key renames, secondaries key renames, dotted notation rules, and validation/400 errors. Common to backend and frontend.
62
-
63
- - **[AGENTS_BACKEND.md](./AGENTS_BACKEND.md)** — Setup workflow, CLI, schema/table generation, `defineTable()` complete reference, `buildRelation` (alias + unique), `extraFilters` + `extendedCondition`, **SqlApi** (programmatic high-level API for custom routes), ConditionBuilder API, QueryClient API, Swagger, multi-tenant configuration, **validation** (`validate` + `validateBulk`), hooks, key conventions, common backend patterns, FAQ/gotchas, dialect differences.
64
-
65
- - **[AGENTS_FRONTEND.md](./AGENTS_FRONTEND.md)** — Compact client-side request grammar (LLM-oriented, also the system-prompt reference for runtime agent clients): all endpoints, search with the four join families (`joinMustExist`, `joinMultiple`, `joinGroup`, `joinLeft`), conditions methods, ordering (including dotted notations), pagination, writes with secondaries/deletions, structured 400 error shape. Pair with `GET {prefix}/agent/manifest.md` (when `agentManifest` is enabled) for this deployment's tables/fields/aliases.
66
-
67
- - **[docs/adr/](./docs/adr/README.md)** — Architecture Decision Records. **Read before proposing a "fix" to a deliberate behavior**: open-by-default, non-transactional bulk, always-updatable fields, raw DB errors, insert-pipeline ordering. These are recorded decisions with rationale, not gaps.
68
-
69
- ## Quick Reference
70
-
71
- ### Generated endpoints
72
-
73
- For a table `customer` with `prefix: '/auto'`:
74
-
75
- ```
76
- POST /auto/search/customer — search with filters in body
77
- GET /auto/rest/customer/:id — get single record by PK
78
- POST /auto/rest/customer — insert record (+ secondaries)
79
- PUT /auto/rest/customer — update record (+ secondaries + deletions)
80
- DELETE /auto/rest/customer/:id — delete record by PK
81
- PUT /auto/bulk/customer — bulk upsert (array of items)
82
- POST /auto/bulk/customer/delete — bulk delete (array of PKs)
83
- ```
84
-
85
- ### Minimal setup
86
-
87
- ```typescript
88
- import Fastify from 'fastify';
89
- import fastifyPostgres from '@fastify/postgres';
90
- import { fastifyAutoSqlApi } from 'fastify-auto-sqlapi';
91
- import { dbTables } from './src/db/tables/dbTables.js';
92
-
93
- const app = Fastify();
94
- await app.register(fastifyPostgres, { connectionString: 'postgres://...' });
95
- await app.register(fastifyAutoSqlApi, { DbTables: dbTables, swagger: true });
96
- await app.listen({ port: 3000 });
97
- ```
98
-
99
- ### Minimal table definition
100
-
101
- ```typescript
102
- import { defineTable, exportTableInfo } from 'fastify-auto-sqlapi';
103
- import { SchemaCustomer as Schema } from '../schemas/SchemaCustomer';
104
-
105
- export const TableCustomer = defineTable({
106
- primary: 'id',
107
- excludeFromCreation: ['id'], // MUST include auto-increment PKs
108
- ...exportTableInfo(Schema),
109
- });
110
- ```
111
-
112
- ### Joins at a glance
113
-
114
- | Family | Direction | Cardinality | Output |
115
- |--------|-----------|-------------|--------|
116
- | `joinMustExist` | child → main | 1:N | filters main via EXISTS |
117
- | `joinMultiple` | child → main | 1:N | side query, child rows in `result.joinMultiple.<alias>` |
118
- | `joinGroup` | child → main | 1:N | aggregations in `result.joinGroup.<alias>` |
119
- | `joinLeft` | parent → main | N:1 | real LEFT JOIN (on demand), parent rows in `result.joinLeft.<alias>` |
120
-
121
- Declare relations with `buildRelation(M, mF, J, jF, options?)`. Options are all optional: `alias` defaults to `joinSchema.tableName` (override only when joining the same table twice or to use a friendlier name), `selection` defaults to `'*'`, `unique` defaults to `false`. Set `unique: true` for N:1 (parent) relations to enable `joinLeft`. See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) for the full migration guide.
34
+ The generated Schema maps between the two, whether the DB uses snake_case or camelCase. Clients always send camelCase, including when anchoring an `anyOf` tenant write.