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.
- package/AGENTS.md +24 -111
- package/AGENTS_BACKEND.md +282 -1332
- package/AGENTS_FRONTEND.md +70 -47
- package/BREAKING_CHANGES.md +131 -0
- package/CHANGELOG.md +139 -0
- package/README.md +1 -1
- package/dist/bin/generate-schema.js +2 -2
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +24 -1
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +21 -2
- package/dist/lib/cli/schema-codegen.d.ts +1 -1
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +31 -3
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +187 -60
- package/dist/lib/dialect.d.ts +15 -10
- package/dist/lib/dialect.d.ts.map +1 -1
- package/dist/lib/dialect.js +12 -31
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +2 -1
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +2 -1
- package/dist/lib/engine/write-helpers.d.ts +13 -1
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +35 -0
- package/dist/lib/schema/helpers.d.ts +7 -0
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +18 -0
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +3 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +41 -0
- package/dist/lib/tenant.d.ts +6 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +16 -0
- package/dist/routes/auto/bulk-delete.routes.js +1 -1
- package/dist/routes/auto/bulk-upsert.routes.js +1 -1
- package/dist/routes/auto/delete.routes.js +1 -1
- package/dist/routes/auto/get.routes.js +1 -1
- package/dist/routes/auto/insert.routes.js +1 -1
- package/dist/routes/auto/route-helpers.d.ts +5 -2
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +1 -1
- package/dist/routes/auto/search.routes.js +3 -3
- package/dist/routes/auto/update.routes.js +1 -1
- package/dist/types/cli.d.ts +11 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/schema.d.ts +7 -0
- package/dist/types/schema.d.ts.map +1 -1
- package/docs/adr/0020-no-conditional-or-mass-update-route.md +68 -0
- package/docs/adr/0021-upsert-targets-a-verified-unique-key.md +153 -0
- package/docs/adr/README.md +2 -0
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -1,121 +1,34 @@
|
|
|
1
|
-
# fastify-auto-sqlapi —
|
|
1
|
+
# fastify-auto-sqlapi — agent instructions
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
5
|
+
## Which file to read
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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.
|