fastify-auto-sqlapi 0.1.10 → 0.1.11

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 +1 -1
  2. package/AGENTS_BACKEND.md +76 -2
  3. package/AGENTS_FRONTEND.md +69 -545
  4. package/BREAKING_CHANGES.md +63 -0
  5. package/CHANGELOG.md +69 -0
  6. package/README.md +98 -11
  7. package/dist/index.d.ts +5 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +6 -1
  10. package/dist/lib/agent/manifest.d.ts +53 -0
  11. package/dist/lib/agent/manifest.d.ts.map +1 -0
  12. package/dist/lib/agent/manifest.js +153 -0
  13. package/dist/lib/agent/tool-schemas.d.ts +36 -0
  14. package/dist/lib/agent/tool-schemas.d.ts.map +1 -0
  15. package/dist/lib/agent/tool-schemas.js +47 -0
  16. package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
  17. package/dist/lib/cli/schema-codegen.js +9 -1
  18. package/dist/lib/engine/bulk/bulk-delete.d.ts.map +1 -1
  19. package/dist/lib/engine/bulk/bulk-delete.js +5 -1
  20. package/dist/lib/engine/rest/delete.d.ts.map +1 -1
  21. package/dist/lib/engine/rest/delete.js +4 -1
  22. package/dist/lib/engine/rest/get.d.ts.map +1 -1
  23. package/dist/lib/engine/rest/get.js +4 -1
  24. package/dist/lib/nullable.d.ts +17 -0
  25. package/dist/lib/nullable.d.ts.map +1 -0
  26. package/dist/lib/nullable.js +24 -0
  27. package/dist/lib/schema/search.d.ts +11 -9
  28. package/dist/lib/schema/search.d.ts.map +1 -1
  29. package/dist/lib/schema/search.js +27 -10
  30. package/dist/lib/table-helpers.d.ts.map +1 -1
  31. package/dist/lib/table-helpers.js +12 -2
  32. package/dist/routes/auto/agent-manifest.routes.d.ts +12 -0
  33. package/dist/routes/auto/agent-manifest.routes.d.ts.map +1 -0
  34. package/dist/routes/auto/agent-manifest.routes.js +30 -0
  35. package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -1
  36. package/dist/routes/auto/bulk-delete.routes.js +1 -0
  37. package/dist/routes/auto/delete.routes.d.ts.map +1 -1
  38. package/dist/routes/auto/delete.routes.js +1 -0
  39. package/dist/routes/auto/get.routes.d.ts.map +1 -1
  40. package/dist/routes/auto/get.routes.js +1 -0
  41. package/dist/routes/auto/plugin.d.ts.map +1 -1
  42. package/dist/routes/auto/plugin.js +4 -0
  43. package/dist/routes/auto/route-helpers.d.ts +7 -0
  44. package/dist/routes/auto/route-helpers.d.ts.map +1 -1
  45. package/dist/routes/auto/route-helpers.js +11 -0
  46. package/dist/routes/auto/search.routes.js +3 -3
  47. package/dist/types/plugin.d.ts +7 -0
  48. package/dist/types/plugin.d.ts.map +1 -1
  49. package/dist/types/table.d.ts +6 -0
  50. package/dist/types/table.d.ts.map +1 -1
  51. package/dist/types/table.js +8 -0
  52. package/docs/adr/0007-explicit-table-generation.md +39 -0
  53. package/docs/adr/README.md +1 -0
  54. package/package.json +1 -1
@@ -317,3 +317,66 @@ including `joinLeft.filters` / `joinLeft.conditions` and `orderBy`, which previo
317
317
  them with 400. They remain rejected in `selectComputed`, `computeMin/Max/Sum/Avg`,
318
318
  `joinGroup.aggregations.by` and `defaultOrder`, where the expression precedes the `WHERE`
319
319
  values in the parameter order.
320
+
321
+ # Breaking Change — by-single-id operations disabled for composite primary keys
322
+
323
+ Since **0.1.11**, tables whose `primary` is an array of more than one field no longer expose
324
+ the operations that address a record by a single PK value:
325
+
326
+ - `GET /rest/:table/:id` and `DELETE /rest/:table/:id` are **not registered** (404);
327
+ - `POST /bulk/:table/delete` is **not registered** (404);
328
+ - explicitly listing `get`, `delete` or `bulkDelete` in `ITable.operations` for such a table
329
+ now **throws at startup** with a descriptive error;
330
+ - the programmatic `sqlApi.get/delete/bulkDelete` reject with `400`.
331
+
332
+ Search, insert, update and bulk upsert are unaffected — they already matched every PK column.
333
+
334
+ ## Why this changed
335
+
336
+ These operations matched on the **first** PK column alone. On `primary: ['agentId', 'teamId']`:
337
+
338
+ - `DELETE /rest/t/1` deleted **every** row with `agent_id = 1` — not one record;
339
+ - `GET /rest/t/1` returned an arbitrary row among those sharing `agent_id = 1`;
340
+ - bulk delete removed every row matching each listed first-column value.
341
+
342
+ A single `:id` cannot address a composite key; silently matching a prefix of the key is data
343
+ loss waiting to happen, so the operations are refused loudly instead.
344
+
345
+ ## Who is affected
346
+
347
+ Only consumers with composite-PK tables **and** clients calling get/delete/bulk-delete on
348
+ them. If your composite table's first PK column happens to be unique on its own, these
349
+ endpoints previously behaved correctly for you — they are now gone and you must migrate.
350
+
351
+ ## Migration
352
+
353
+ - **Read one record** → `search` with every PK field in the filters:
354
+
355
+ ```typescript
356
+ await fetch('/auto/search/agent_team', {
357
+ method: 'POST',
358
+ body: JSON.stringify({ filters: { agentId: 1, teamId: 2 } }),
359
+ });
360
+ ```
361
+
362
+ - **Delete one record** → a custom route using the low-level `QueryClient`, matching the
363
+ full key:
364
+
365
+ ```typescript
366
+ import { createQueryClient, pgQueryable } from 'fastify-auto-sqlapi';
367
+
368
+ const db = createQueryClient(pgQueryable(app.pg), 'postgres');
369
+
370
+ app.delete('/agent-team/:agentId/:teamId', async (req) => {
371
+ const { agentId, teamId } = req.params;
372
+ await db.delete('agent_team', { agent_id: agentId, team_id: teamId });
373
+ return { main: { agentId, teamId } };
374
+ });
375
+ ```
376
+
377
+ - **Row removal as part of an edit flow** → `update` on the parent with `deletions` (write
378
+ joins), or bulk upsert — both match every PK column.
379
+
380
+ - **First PK column is truly unique on its own?** Then the composite declaration was
381
+ redundant: declare `primary: 'id'` (single) and keep the second column as a plain field —
382
+ every by-id operation comes back.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,75 @@ All notable changes to this project are documented here. This project follows
7
7
 
8
8
  Migration instructions for breaking changes live in **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)**.
9
9
 
10
+ ## [0.1.11]
11
+
12
+ ### Breaking
13
+
14
+ - **⚠️ By-single-id operations are disabled for composite-PK tables.** `GET /rest/:id`,
15
+ `DELETE /rest/:id` and `POST /bulk/:table/delete` matched on the **first** PK column alone:
16
+ on a table with `primary: ['agentId', 'teamId']`, `DELETE /rest/t/1` deleted **every** row
17
+ with `agentId = 1`, not one record. These routes are no longer registered for composite-PK
18
+ tables (404); explicitly listing one of them in `operations` now throws at startup, and the
19
+ programmatic `sqlApi.get/delete/bulkDelete` reject with 400. Search, insert, update and
20
+ bulk upsert are unaffected — they already handled every PK column. If your composite table's
21
+ first PK column happens to be unique, these endpoints previously worked for you and are now
22
+ gone — see
23
+ [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#breaking-change--by-single-id-operations-disabled-for-composite-primary-keys)
24
+ for the migration paths.
25
+
26
+ ### Fixed
27
+
28
+ - **⚠️ NULL values were serialized as `0` / `""` in read responses.** The generator mapped
29
+ nullable columns to `Type.Optional(T)`, which means "key may be absent" — not "value may
30
+ be null" — so fast-json-stringify coerced every NULL to the type's zero value (a NULL FK
31
+ was served as `0`). Nullable columns are now generated as `Type.Optional(Nullable(T))`,
32
+ where the new exported `Nullable()` helper emits the JSON-Schema type-array form
33
+ (`type: ['integer', 'null']`). **Regenerate your schemas** (`sqlapi-generate-schema`) to
34
+ pick up the fix. Note: the type-array form is deliberate — a `Type.Union([T, Type.Null()])`
35
+ would NOT fix it, because Fastify's default Ajv `coerceTypes` corrupts values through
36
+ union branches (`null` → `0` with the Null branch last, `0`/`""` → `null` with it first);
37
+ with a type array no coercion happens. As a side effect, writes now accept an explicit
38
+ `null` to set a column to NULL.
39
+ - **`maxItemsPerPage` below 500 broke every request without an explicit `itemsPerPage`.**
40
+ The search querystring schema declared a fixed `default: 500`; Ajv injects defaults before
41
+ the cap check runs, so any lower cap rejected the injected default with 400. The schema is
42
+ now parameterized: `default = min(500, maxItemsPerPage)` and the cap doubles as the schema
43
+ `maximum` (structured 400 instead of a plain error). The `SearchTableQuery(cap)` builder is
44
+ exported; `SearchTableQueryString` remains as the default-cap variant.
45
+
46
+ ### Changed
47
+
48
+ - **`filters` with an explicit `null` now filter by `IS NULL`.** Previously a null filter
49
+ was silently dropped — a request asking "field is null" returned unfiltered results. An
50
+ explicit `null` on a schema field now builds `WHERE col IS NULL` (undefined/absent keys
51
+ are still ignored). `conditions` + `isNull` keeps working as before.
52
+
53
+ ### Added
54
+
55
+ - **LLM / agent client support.** The plugin doubles as the enforcement layer for a chat
56
+ agent operating on your data (tenant scoping, `readExclude`, `operations`, caps apply to
57
+ whatever the model invents). New pieces:
58
+ - `AGENTS_FRONTEND.md` rewritten as a compact LLM-oriented request grammar (search with
59
+ all join families, ordering, pagination, writes, error shapes) — usable both by coding
60
+ agents and as the system prompt of a runtime agent client. Same filename and package
61
+ path as before, so existing references keep working; the verbose tutorial-style
62
+ content it replaces is fully covered by the grammar plus Swagger.
63
+ - `agentManifest: true` plugin option → `GET {prefix}/agent/manifest` (JSON) and
64
+ `/agent/manifest.md` (markdown for the system prompt): per-table fields with
65
+ type/required/nullable, enabled operations, join aliases with direction, computed
66
+ fields, extra filters — always in sync with the running config, behind the same
67
+ global `onRequests`. Also exported programmatically (`buildAgentManifest`,
68
+ `renderAgentManifestMd`) and as a granular route plugin (`agentManifestRoutes`).
69
+ - `agentToolSchemas(dbTables, table)` — the exact JSON Schemas the routes validate
70
+ with, per enabled operation, for strict provider-side tool definitions.
71
+ - README "LLM / agent clients" section and AGENTS_BACKEND.md pattern: read-only agent
72
+ surface via granular composition, loose-tool + structured-400 retry loop vs strict
73
+ tools.
74
+ - `Nullable(schema)` export: marks a TypeBox schema nullable via the JSON-Schema type-array
75
+ form — the only representation safe under Fastify's default Ajv/fast-json-stringify on
76
+ both input validation and response serialization.
77
+ - ADR 0007: table generation is explicit — `--all` is a migration helper, not the default.
78
+
10
79
  ## [0.1.10]
11
80
 
12
81
  ### Added
package/README.md CHANGED
@@ -4,18 +4,9 @@ Automatic CRUD API generation from PostgreSQL, MySQL, and MariaDB tables for [Fa
4
4
 
5
5
  Point it at your database, and get a full REST API with search, advanced conditions, pagination, joins, aggregations, bulk operations, multi-tenant isolation, validation, and hooks.
6
6
 
7
- ## What LLMs Are Saying
8
-
9
- > API: 5 new read-only tables — and this is where your plugin really shined.
10
-
11
- > Yes, it genuinely saved me a lot of work, and the numbers make that obvious: I deleted about 350 lines of hand-written API repository and route code, replacing them with roughly 150 lines of declarative configuration. And it wasn't a one-for-one trade. In return, I gained features I simply didn't have before: complete Swagger documentation, the conditions DSL (previously I only had sorting and fixed filters), pagination with total counts, structured 400 errors, and consistent camelCase conversion.
12
-
13
- > The moment I thought, "Okay, this is well designed," was when I used computedFields: the case of "attributes stored inside a jsonb column that still need to be sortable and filterable" is exactly the kind of problem that usually forces you to step outside the framework, yet it fit in cleanly. It worked end-to-end with the role-based CASE expression, remained sortable, could be projected with selectComputed, and required zero SQL in the routes.
14
-
15
- > The AGENTS files deserve a mention: I configured everything without ever opening the source code—CLI schema, defineTable, buildRelation, computed, extraFilters, and the frontend request/response shapes. As "documentation for agents," they genuinely work, and that's more than I can say for most libraries.
16
-
17
7
  ## Features
18
8
 
9
+ - **Agent-ready** — `AGENTS.md` and the ADRs ship *inside* the npm package, so a coding agent can configure the library without reading the source or guessing intent
19
10
  - **Zero boilerplate** — define your tables, get 7 endpoints each
20
11
  - **No ORM** — raw SQL via `pg` + parameterized queries
21
12
  - **TypeBox validation** — request/response schemas auto-generated from your DB
@@ -33,6 +24,38 @@ Point it at your database, and get a full REST API with search, advanced conditi
33
24
  > **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** for migration guides. If you use `computedFields`
34
25
  > with bound values, read the 0.1.6 entry — earlier versions could return wrong rows.
35
26
 
27
+
28
+ ## Working with coding agents
29
+
30
+ The library is documented for two readers. Humans get this README; agents get task-oriented
31
+ files that ship in the published package, so they are already on disk after `npm install`:
32
+
33
+ | File | Contents |
34
+ | -------------------------------------- | ----------------------------------------------------------------- |
35
+ | `AGENTS.md` | Entry point: what the library does, which file to read for what |
36
+ | `AGENTS_BACKEND.md` | `defineTable` reference, hooks, tenancy, computed fields, CLI |
37
+ | `AGENTS_FRONTEND.md` | Request/response shapes for every endpoint |
38
+ | `docs/adr/` | Why the non-obvious behaviours are what they are |
39
+
40
+ Point your agent at them once:
41
+
42
+ > Read `node_modules/fastify-auto-sqlapi/AGENTS.md` before touching anything under `src/db/`.
43
+
44
+ Three design choices make the generated code predictable enough for an agent to write it
45
+ unsupervised:
46
+
47
+ - **Configuration is declarative and local.** A table is one `defineTable()` call — nothing to
48
+ wire across files, so a diff is reviewable at a glance.
49
+ - **One naming convention.** camelCase in requests, responses, hooks and validators; the
50
+ mapping to DB columns is automatic. There is no second convention to remember, and no
51
+ layer where the agent has to guess which one applies.
52
+ - **Errors are machine-readable.** A 400 carries `fields: [{ path, code, message }]`, so a
53
+ failing request tells the agent exactly what to fix instead of requiring a guess.
54
+
55
+ The ADRs matter more than they look: they answer the "why is it like this?" questions
56
+ (open-by-default, non-transactional bulk, always-updatable fields) that an agent would
57
+ otherwise resolve by inventing a workaround — or by proposing to change intentional behaviour.
58
+
36
59
  ## Quick Start
37
60
 
38
61
  ### 1. Install
@@ -513,6 +536,13 @@ Search with filters, advanced conditions, pagination, ordering, joins, and aggre
513
536
 
514
537
  Returns `{ main: { ... } }` or 404.
515
538
 
539
+ > **Composite primary keys:** `GET /rest/:id`, `DELETE /rest/:id` and `POST /bulk/:table/delete`
540
+ > address a record by a single PK value, so they are **not registered** for tables whose
541
+ > primary key spans multiple columns (a match on the first column alone could hit many rows —
542
+ > for the deletes, destructively). Explicitly listing one of these operations in `operations`
543
+ > for such a table throws at startup. Use `search` with all PK fields in the filters, `update`
544
+ > (which matches every PK column), or a custom route.
545
+
516
546
  ### POST /rest/{table}
517
547
 
518
548
  **Body:**
@@ -540,7 +570,7 @@ Returns `{ main: { ... } }` or 404.
540
570
  > ),
541
571
  > // → PUT /rest/product { "main": {...}, "secondaries": { "translations": [{ "lang": "en", "name": "Bike" }] } }
542
572
  > ```
543
- > Expose a composite-PK table as a standalone CRUD table only when it stands on its own (M:N link tables, natural keys) — composite PKs are fully supported there too.
573
+ > Expose a composite-PK table as a standalone CRUD table only when it stands on its own (M:N link tables, natural keys) — search, insert, update and bulk upsert fully support composite PKs; the by-single-id routes (get, delete, bulkDelete) are skipped for them (see the note under GET).
544
574
 
545
575
  **Response (201):** `{ main: { ... }, secondaries: { ... } }`
546
576
 
@@ -691,6 +721,60 @@ await app.register(async (instance) => {
691
721
  }, { prefix: '/admin' });
692
722
  ```
693
723
 
724
+ ## LLM / agent clients
725
+
726
+ The plugin is a natural enforcement layer for an agent that operates on your data through
727
+ chat: whatever requests the LLM invents, they pass through the same tenant scoping,
728
+ `readExclude`, `operations` whitelist, validation and request caps as any HTTP client —
729
+ direct-DB firepower, backoffice constraints. Three pieces make this practical:
730
+
731
+ **1. The grammar** — [`AGENTS_FRONTEND.md`](./AGENTS_FRONTEND.md) (ships in the npm package)
732
+ is a compact, LLM-oriented reference of how to call the API: search with all four join
733
+ families, ordering, pagination, writes, error shapes. Put it in the system prompt.
734
+
735
+ **2. The vocabulary** — enable the manifest endpoint to describe *this deployment's* tables:
736
+
737
+ ```typescript
738
+ await app.register(fastifyAutoSqlApi, {
739
+ DbTables: dbTables,
740
+ agentManifest: true, // GET {prefix}/agent/manifest (JSON) + /agent/manifest.md
741
+ });
742
+ ```
743
+
744
+ `GET /agent/manifest.md` returns a compact markdown block per table — fields with
745
+ type/required/nullable, enabled operations, join aliases (with direction), computed fields,
746
+ extra filters — always in sync with the running config, behind the same `onRequests` auth.
747
+ Fetch it at session start: grammar + vocabulary is everything the model needs. The same data
748
+ is available programmatically via `buildAgentManifest(dbTables)` /
749
+ `renderAgentManifestMd(manifest)`.
750
+
751
+ **3. The guardrails** — writes are often too dangerous to hand to a model. Register a
752
+ read-only surface with granular composition and give the agent only that:
753
+
754
+ ```typescript
755
+ import { searchRoutes, getRoutes, agentManifestRoutes } from 'fastify-auto-sqlapi';
756
+
757
+ await app.register(async (instance) => {
758
+ const opts = { DbTables: dbTables, onRequests: [agentAuth] };
759
+ await instance.register(searchRoutes, opts);
760
+ await instance.register(getRoutes, opts);
761
+ await instance.register(agentManifestRoutes, opts);
762
+ }, { prefix: '/agent' }); // reads only: no insert/update/delete routes exist here
763
+ ```
764
+
765
+ **Validation strategy** — two options, per table or per deployment:
766
+
767
+ - *Loose tool + retry loop* (recommended default): expose a generic tool
768
+ (`table` as enum from the manifest, `body` as free object) and let the plugin validate.
769
+ A failed request returns a structured 400 with `fields: [{path, code, message}]` — feed
770
+ it back to the model and it self-corrects in one round trip. Cheap prompts, no schema
771
+ duplication.
772
+ - *Strict tools*: `agentToolSchemas(dbTables, 'customer')` returns the exact JSON Schemas
773
+ the routes validate with (body + querystring for search, bodies for writes, only for the
774
+ operations enabled on that table) — plug them into provider-side tool definitions when
775
+ you want invalid calls rejected before they leave the model. Costs prompt size; best for
776
+ a few hot tables.
777
+
694
778
  ## Design Decisions
695
779
 
696
780
  Deliberate, non-obvious choices — open-by-default, non-transactional bulk operations,
@@ -707,6 +791,8 @@ proposes changing one of these behaviors.
707
791
  - **`joinMustExist` / `joinMultiple` / `joinGroup`** are 1:N (child→main) and use side queries / EXISTS / correlated subqueries — no row duplication
708
792
  - **`joinLeft`** is N:1 (parent→main) and adds a real `LEFT JOIN` on demand (only when filtering/ordering by parent)
709
793
  - **All response fields are Optional** — response schemas use `Type.Partial` since `RETURNING *` may return any subset
794
+ - **Nullable columns use `Nullable(T)`** — the generator emits the JSON-Schema type-array form (`type: ['integer', 'null']`) via the exported `Nullable()` helper. This is deliberate: a bare `Type.Optional(T)` serializes NULL as `0`/`""`, and a `Type.Union([T, Type.Null()])` gets corrupted by Ajv's default type coercion (`null` ↔ `0`/`""` through the branches). In `filters`, an explicit `null` filters by `IS NULL`
795
+ - **The `pg` driver returns `numeric`/`int8` as strings** and parses timestamps into local-timezone `Date`s — driver defaults the plugin deliberately does not override (global state). See "PostgreSQL driver type parsers" in AGENTS_BACKEND.md for the recommended one-time setup
710
796
 
711
797
  ## Re-exports
712
798
 
@@ -725,6 +811,7 @@ import {
725
811
  buildRelation,
726
812
  buildUpsertRule,
727
813
  buildUpsertRules,
814
+ Nullable, // type-array nullable schema (see Conventions)
728
815
 
729
816
  // DB layer
730
817
  QueryClient, // raw SQL query helper
package/dist/index.d.ts CHANGED
@@ -4,13 +4,17 @@ export { getDialect, type SqlDialect, type DialectName } from './lib/dialect.js'
4
4
  export { pgQueryable } from './lib/adapters/pg-adapter.js';
5
5
  export { mysqlQueryable } from './lib/adapters/mysql-adapter.js';
6
6
  export { exportTableInfo, defineTable, buildRelation, buildUpsertRule, buildUpsertRules } from './lib/table-helpers.js';
7
+ export { Nullable } from './lib/nullable.js';
8
+ export { buildAgentManifest, renderAgentManifestMd, type AgentManifest, type ManifestTable, } from './lib/agent/manifest.js';
9
+ export { agentToolSchemas, type AgentToolSchemas } from './lib/agent/tool-schemas.js';
10
+ export { default as agentManifestRoutes } from './routes/auto/agent-manifest.routes.js';
7
11
  export { resolveTenant } from './lib/tenant.js';
8
12
  export { SqlApi, createSqlApi } from './lib/sql-api.js';
9
13
  export type { SqlApiOptions, SqlApiSearchParams, SqlApiInsertParams, SqlApiUpdateParams } from './lib/sql-api.js';
10
14
  export { default as fastifyAutoSqlApi } from './routes/auto/plugin.js';
11
15
  export { setupSwagger } from './lib/setup-swagger.js';
12
16
  export { searchEngine } from './lib/engine/search/search.js';
13
- export { SearchTableBodyPost, SearchTableQueryString, SearchTableResponse } from './lib/schema/search.js';
17
+ export { SearchTableBodyPost, SearchTableQuery, SearchTableQueryString, SearchTableResponse } from './lib/schema/search.js';
14
18
  export { default as searchRoutes } from './routes/auto/search.routes.js';
15
19
  export { insertEngine } from './lib/engine/rest/insert.js';
16
20
  export { InsertTableBody, InsertTableResponse } from './lib/schema/insert.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -9,6 +9,11 @@ export { pgQueryable } from './lib/adapters/pg-adapter.js';
9
9
  export { mysqlQueryable } from './lib/adapters/mysql-adapter.js';
10
10
  // Table helpers
11
11
  export { exportTableInfo, defineTable, buildRelation, buildUpsertRule, buildUpsertRules } from './lib/table-helpers.js';
12
+ export { Nullable } from './lib/nullable.js';
13
+ // Agent / LLM client helpers
14
+ export { buildAgentManifest, renderAgentManifestMd, } from './lib/agent/manifest.js';
15
+ export { agentToolSchemas } from './lib/agent/tool-schemas.js';
16
+ export { default as agentManifestRoutes } from './routes/auto/agent-manifest.routes.js';
12
17
  // Tenant
13
18
  export { resolveTenant } from './lib/tenant.js';
14
19
  // SqlApi — programmatic high-level API
@@ -19,7 +24,7 @@ export { default as fastifyAutoSqlApi } from './routes/auto/plugin.js';
19
24
  export { setupSwagger } from './lib/setup-swagger.js';
20
25
  // Search
21
26
  export { searchEngine } from './lib/engine/search/search.js';
22
- export { SearchTableBodyPost, SearchTableQueryString, SearchTableResponse } from './lib/schema/search.js';
27
+ export { SearchTableBodyPost, SearchTableQuery, SearchTableQueryString, SearchTableResponse } from './lib/schema/search.js';
23
28
  export { default as searchRoutes } from './routes/auto/search.routes.js';
24
29
  // Insert
25
30
  export { insertEngine } from './lib/engine/rest/insert.js';
@@ -0,0 +1,53 @@
1
+ import type { DbTables, ITable, TableOperation } from '../../types.js';
2
+ export interface ManifestField {
3
+ type: string;
4
+ /** Present (true) when the field must be sent on insert. */
5
+ required?: boolean;
6
+ /** Present (true) when the column accepts null. */
7
+ nullable?: boolean;
8
+ /** Present (true) when the field is writable but never returned by reads (readExclude). */
9
+ writeOnly?: boolean;
10
+ }
11
+ export interface ManifestJoin {
12
+ alias: string;
13
+ table: string;
14
+ /** '1:N' → usable in joinMustExist/joinMultiple/joinGroup; 'N:1' → usable in joinLeft. */
15
+ kind: '1:N' | 'N:1';
16
+ }
17
+ export interface ManifestTable {
18
+ /** URL segment for this table's routes. */
19
+ table: string;
20
+ primary: string | string[];
21
+ operations: TableOperation[];
22
+ fields: Record<string, ManifestField>;
23
+ /** Fields stripped from client input on insert (server-generated: PKs, timestamps). */
24
+ serverGenerated?: string[];
25
+ /** Computed (virtual) fields: usable in filters/conditions/orderBy/selectComputed. */
26
+ computed?: Record<string, string>;
27
+ /** Extra filter names accepted in `filters` beyond schema fields. */
28
+ extraFilters?: Record<string, string>;
29
+ readJoins?: ManifestJoin[];
30
+ /** Aliases accepted as `secondaries`/`deletions` keys in writes. */
31
+ writeJoins?: ManifestJoin[];
32
+ /** Present (true) when rows are automatically scoped to the caller's tenant. */
33
+ tenantScoped?: boolean;
34
+ defaultOrder?: string;
35
+ }
36
+ export interface AgentManifest {
37
+ tables: Record<string, ManifestTable>;
38
+ }
39
+ export declare function tableOperations(tableConf: ITable): TableOperation[];
40
+ /**
41
+ * Build the machine-readable description of every table exposed by this deployment:
42
+ * fields (type/required/nullable), enabled operations, join aliases, computed fields,
43
+ * extra filters, tenant scoping. Derived entirely from `DbTables`, so it is always in
44
+ * sync with the running configuration. Pairs with AGENTS_FRONTEND.md (the request grammar)
45
+ * to give an LLM client everything it needs to drive the API.
46
+ */
47
+ export declare function buildAgentManifest(dbTables: DbTables): AgentManifest;
48
+ /**
49
+ * Compact markdown rendering of the manifest, designed for an LLM system prompt.
50
+ * Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads.
51
+ */
52
+ export declare function renderAgentManifestMd(manifest: AgentManifest): string;
53
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/lib/agent/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAOvE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,0FAA0F;IAC1F,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtC,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACvC;AAgCD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAInE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CAuDpE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CA2CrE"}
@@ -0,0 +1,153 @@
1
+ import { isCompositePrimary } from '../../types.js';
2
+ const ALL_OPERATIONS = [
3
+ 'search', 'get', 'insert', 'update', 'delete', 'bulkUpsert', 'bulkDelete',
4
+ ];
5
+ const SINGLE_PK_OPERATIONS = new Set(['get', 'delete', 'bulkDelete']);
6
+ function shortType(schema) {
7
+ if (!schema)
8
+ return 'any';
9
+ const t = schema.type;
10
+ if (!t)
11
+ return 'any';
12
+ const types = (Array.isArray(t) ? t : [t]).filter((x) => x !== 'null');
13
+ return types[0] ?? 'any';
14
+ }
15
+ function isNullable(schema) {
16
+ const t = schema?.type;
17
+ return Array.isArray(t) && t.includes('null');
18
+ }
19
+ function computedTypes(tableConf) {
20
+ if (!tableConf.computedFields)
21
+ return undefined;
22
+ const out = {};
23
+ for (const [name, fn] of Object.entries(tableConf.computedFields)) {
24
+ try {
25
+ const stub = fn({
26
+ db: { qi: (s) => s, dialectName: 'postgres' },
27
+ qiCol: () => '""',
28
+ });
29
+ out[name] = shortType(stub.type);
30
+ }
31
+ catch {
32
+ out[name] = 'any';
33
+ }
34
+ }
35
+ return Object.keys(out).length ? out : undefined;
36
+ }
37
+ export function tableOperations(tableConf) {
38
+ const requested = tableConf.operations ?? ALL_OPERATIONS;
39
+ if (!isCompositePrimary(tableConf.primary))
40
+ return [...requested];
41
+ return requested.filter((op) => !SINGLE_PK_OPERATIONS.has(op));
42
+ }
43
+ /**
44
+ * Build the machine-readable description of every table exposed by this deployment:
45
+ * fields (type/required/nullable), enabled operations, join aliases, computed fields,
46
+ * extra filters, tenant scoping. Derived entirely from `DbTables`, so it is always in
47
+ * sync with the running configuration. Pairs with AGENTS_FRONTEND.md (the request grammar)
48
+ * to give an LLM client everything it needs to drive the API.
49
+ */
50
+ export function buildAgentManifest(dbTables) {
51
+ const tables = {};
52
+ for (const [key, tableConf] of Object.entries(dbTables)) {
53
+ const schema = tableConf.Schema;
54
+ const required = new Set((schema.validation.required) ?? []);
55
+ const readExcluded = new Set(tableConf.readExclude ?? []);
56
+ const fields = {};
57
+ for (const [name, fieldSchema] of Object.entries(schema.fields)) {
58
+ const f = { type: shortType(fieldSchema) };
59
+ if (required.has(name))
60
+ f.required = true;
61
+ if (isNullable(fieldSchema))
62
+ f.nullable = true;
63
+ if (readExcluded.has(name))
64
+ f.writeOnly = true;
65
+ fields[name] = f;
66
+ }
67
+ const readJoins = (tableConf.allowedReadJoins ?? []).map((j) => ({
68
+ alias: j.alias,
69
+ table: j.joinSchema.tableName,
70
+ kind: j.unique ? 'N:1' : '1:N',
71
+ }));
72
+ const writeJoins = (tableConf.allowedWriteJoins ?? []).map((j) => ({
73
+ alias: j.alias,
74
+ table: j.joinSchema.tableName,
75
+ kind: j.unique ? 'N:1' : '1:N',
76
+ }));
77
+ const extraFilters = Object.keys(tableConf.extraFilters ?? {}).length
78
+ ? Object.fromEntries(Object.entries(tableConf.extraFilters).map(([n, s]) => [n, shortType(s)]))
79
+ : undefined;
80
+ const entry = {
81
+ table: schema.tableName,
82
+ primary: tableConf.primary,
83
+ operations: tableOperations(tableConf),
84
+ fields,
85
+ };
86
+ if (tableConf.excludeFromCreation?.length)
87
+ entry.serverGenerated = [...tableConf.excludeFromCreation];
88
+ const computed = computedTypes(tableConf);
89
+ if (computed)
90
+ entry.computed = computed;
91
+ if (extraFilters)
92
+ entry.extraFilters = extraFilters;
93
+ if (readJoins.length)
94
+ entry.readJoins = readJoins;
95
+ if (writeJoins.length)
96
+ entry.writeJoins = writeJoins;
97
+ if (tableConf.tenantScope)
98
+ entry.tenantScoped = true;
99
+ if (tableConf.defaultOrder)
100
+ entry.defaultOrder = tableConf.defaultOrder;
101
+ tables[key] = entry;
102
+ }
103
+ return { tables };
104
+ }
105
+ /**
106
+ * Compact markdown rendering of the manifest, designed for an LLM system prompt.
107
+ * Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads.
108
+ */
109
+ export function renderAgentManifestMd(manifest) {
110
+ const lines = [
111
+ '# Tables',
112
+ '',
113
+ 'Notation: `!`=required on insert, `?`=nullable, `(writeOnly)`=writable, never in reads.',
114
+ 'serverGenerated fields: omit on insert (values from client are ignored).',
115
+ 'readJoins aliases: `1:N` → joinMustExist/joinMultiple/joinGroup; `N:1` → joinLeft.',
116
+ 'writeJoins aliases: keys for `secondaries`/`deletions` in writes.',
117
+ '',
118
+ ];
119
+ for (const t of Object.values(manifest.tables)) {
120
+ const pk = Array.isArray(t.primary) ? t.primary.join('+') : t.primary;
121
+ const flags = t.tenantScoped ? ' [tenant-scoped]' : '';
122
+ lines.push(`## ${t.table} PK:${pk} ops:${t.operations.join(',')}${flags}`);
123
+ const fieldParts = Object.entries(t.fields).map(([name, f]) => {
124
+ let s = `${name}:${f.type}`;
125
+ if (f.required)
126
+ s += '!';
127
+ if (f.nullable)
128
+ s += '?';
129
+ if (f.writeOnly)
130
+ s += '(writeOnly)';
131
+ return s;
132
+ });
133
+ lines.push(`fields: ${fieldParts.join(', ')}`);
134
+ if (t.serverGenerated)
135
+ lines.push(`serverGenerated: ${t.serverGenerated.join(', ')}`);
136
+ if (t.computed) {
137
+ lines.push(`computed: ${Object.entries(t.computed).map(([n, ty]) => `${n}:${ty}`).join(', ')}`);
138
+ }
139
+ if (t.extraFilters) {
140
+ lines.push(`extraFilters: ${Object.entries(t.extraFilters).map(([n, ty]) => `${n}:${ty}`).join(', ')}`);
141
+ }
142
+ if (t.readJoins) {
143
+ lines.push(`readJoins: ${t.readJoins.map((j) => `${j.alias}→${j.table}(${j.kind})`).join(', ')}`);
144
+ }
145
+ if (t.writeJoins) {
146
+ lines.push(`writeJoins: ${t.writeJoins.map((j) => `${j.alias}→${j.table}`).join(', ')}`);
147
+ }
148
+ if (t.defaultOrder)
149
+ lines.push(`defaultOrder: ${t.defaultOrder}`);
150
+ lines.push('');
151
+ }
152
+ return lines.join('\n');
153
+ }
@@ -0,0 +1,36 @@
1
+ import type { DbTables } from '../../types.js';
2
+ export interface AgentToolSchemas {
3
+ /** POST /search/{table}: `body` + `querystring` (orderBy/page/itemsPerPage/compute*). */
4
+ search?: {
5
+ body: object;
6
+ querystring: object;
7
+ };
8
+ insert?: {
9
+ body: object;
10
+ };
11
+ update?: {
12
+ body: object;
13
+ };
14
+ bulkUpsert?: {
15
+ body: object;
16
+ };
17
+ bulkDelete?: {
18
+ body: object;
19
+ };
20
+ }
21
+ /**
22
+ * Ready-to-use JSON Schemas for exposing a table's operations as strict LLM tools —
23
+ * the same TypeBox schemas the HTTP routes validate with (TypeBox objects ARE plain
24
+ * JSON Schema). Only the operations actually enabled for the table are included
25
+ * (`operations` whitelist, composite-PK rules). `get` and `delete` take a single PK
26
+ * path param and need no schema.
27
+ *
28
+ * Strict tools trade prompt size for provider-side validation; the lighter
29
+ * alternative is a loose tool plus the plugin's structured 400 as the retry signal
30
+ * (see "LLM / agent clients" in the README).
31
+ */
32
+ export declare function agentToolSchemas(dbTables: DbTables, tableName: string, opts?: {
33
+ maxItemsPerPage?: number;
34
+ maxBulkItems?: number;
35
+ }): AgentToolSchemas;
36
+ //# sourceMappingURL=tool-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-schemas.d.ts","sourceRoot":"","sources":["../../../src/lib/agent/tool-schemas.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,yFAAyF;IACzF,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACzD,gBAAgB,CA6BlB"}