fastify-auto-sqlapi 0.0.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/dist/bin/generate-schema.d.ts +3 -0
  4. package/dist/bin/generate-schema.d.ts.map +1 -0
  5. package/dist/bin/generate-schema.js +122 -0
  6. package/dist/bin/generate-tables.d.ts +3 -0
  7. package/dist/bin/generate-tables.d.ts.map +1 -0
  8. package/dist/bin/generate-tables.js +135 -0
  9. package/dist/bin/utils.d.ts +18 -0
  10. package/dist/bin/utils.d.ts.map +1 -0
  11. package/dist/bin/utils.js +54 -0
  12. package/dist/index.d.ts +34 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +48 -0
  15. package/dist/lib/adapters/mysql-adapter.d.ts +9 -0
  16. package/dist/lib/adapters/mysql-adapter.d.ts.map +1 -0
  17. package/dist/lib/adapters/mysql-adapter.js +21 -0
  18. package/dist/lib/adapters/pg-adapter.d.ts +9 -0
  19. package/dist/lib/adapters/pg-adapter.d.ts.map +1 -0
  20. package/dist/lib/adapters/pg-adapter.js +11 -0
  21. package/dist/lib/cli/config.d.ts +3 -0
  22. package/dist/lib/cli/config.d.ts.map +1 -0
  23. package/dist/lib/cli/config.js +29 -0
  24. package/dist/lib/cli/mysql-introspect.d.ts +16 -0
  25. package/dist/lib/cli/mysql-introspect.d.ts.map +1 -0
  26. package/dist/lib/cli/mysql-introspect.js +98 -0
  27. package/dist/lib/cli/pg-introspect.d.ts +4 -0
  28. package/dist/lib/cli/pg-introspect.d.ts.map +1 -0
  29. package/dist/lib/cli/pg-introspect.js +36 -0
  30. package/dist/lib/cli/schema-codegen.d.ts +8 -0
  31. package/dist/lib/cli/schema-codegen.d.ts.map +1 -0
  32. package/dist/lib/cli/schema-codegen.js +100 -0
  33. package/dist/lib/cli/tables-codegen.d.ts +18 -0
  34. package/dist/lib/cli/tables-codegen.d.ts.map +1 -0
  35. package/dist/lib/cli/tables-codegen.js +249 -0
  36. package/dist/lib/condition-methods.d.ts +11 -0
  37. package/dist/lib/condition-methods.d.ts.map +1 -0
  38. package/dist/lib/condition-methods.js +20 -0
  39. package/dist/lib/db.d.ts +28 -0
  40. package/dist/lib/db.d.ts.map +1 -0
  41. package/dist/lib/db.js +224 -0
  42. package/dist/lib/dialect.d.ts +17 -0
  43. package/dist/lib/dialect.d.ts.map +1 -0
  44. package/dist/lib/dialect.js +56 -0
  45. package/dist/lib/engine/bulk/bulk-delete.d.ts +3 -0
  46. package/dist/lib/engine/bulk/bulk-delete.d.ts.map +1 -0
  47. package/dist/lib/engine/bulk/bulk-delete.js +30 -0
  48. package/dist/lib/engine/bulk/bulk-upsert.d.ts +3 -0
  49. package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -0
  50. package/dist/lib/engine/bulk/bulk-upsert.js +74 -0
  51. package/dist/lib/engine/rest/delete.d.ts +3 -0
  52. package/dist/lib/engine/rest/delete.d.ts.map +1 -0
  53. package/dist/lib/engine/rest/delete.js +25 -0
  54. package/dist/lib/engine/rest/get.d.ts +3 -0
  55. package/dist/lib/engine/rest/get.d.ts.map +1 -0
  56. package/dist/lib/engine/rest/get.js +32 -0
  57. package/dist/lib/engine/rest/insert.d.ts +3 -0
  58. package/dist/lib/engine/rest/insert.d.ts.map +1 -0
  59. package/dist/lib/engine/rest/insert.js +58 -0
  60. package/dist/lib/engine/rest/update.d.ts +3 -0
  61. package/dist/lib/engine/rest/update.d.ts.map +1 -0
  62. package/dist/lib/engine/rest/update.js +110 -0
  63. package/dist/lib/engine/search/search.d.ts +3 -0
  64. package/dist/lib/engine/search/search.d.ts.map +1 -0
  65. package/dist/lib/engine/search/search.js +300 -0
  66. package/dist/lib/engine/write-helpers.d.ts +8 -0
  67. package/dist/lib/engine/write-helpers.d.ts.map +1 -0
  68. package/dist/lib/engine/write-helpers.js +83 -0
  69. package/dist/lib/naming.d.ts +6 -0
  70. package/dist/lib/naming.d.ts.map +1 -0
  71. package/dist/lib/naming.js +23 -0
  72. package/dist/lib/schema/bulk-delete.d.ts +10 -0
  73. package/dist/lib/schema/bulk-delete.d.ts.map +1 -0
  74. package/dist/lib/schema/bulk-delete.js +16 -0
  75. package/dist/lib/schema/bulk-upsert.d.ts +5 -0
  76. package/dist/lib/schema/bulk-upsert.d.ts.map +1 -0
  77. package/dist/lib/schema/bulk-upsert.js +43 -0
  78. package/dist/lib/schema/helpers.d.ts +14 -0
  79. package/dist/lib/schema/helpers.d.ts.map +1 -0
  80. package/dist/lib/schema/helpers.js +33 -0
  81. package/dist/lib/schema/insert.d.ts +5 -0
  82. package/dist/lib/schema/insert.d.ts.map +1 -0
  83. package/dist/lib/schema/insert.js +43 -0
  84. package/dist/lib/schema/search.d.ts +14 -0
  85. package/dist/lib/schema/search.d.ts.map +1 -0
  86. package/dist/lib/schema/search.js +99 -0
  87. package/dist/lib/schema/update.d.ts +5 -0
  88. package/dist/lib/schema/update.d.ts.map +1 -0
  89. package/dist/lib/schema/update.js +47 -0
  90. package/dist/lib/setup-swagger.d.ts +4 -0
  91. package/dist/lib/setup-swagger.d.ts.map +1 -0
  92. package/dist/lib/setup-swagger.js +27 -0
  93. package/dist/lib/sql-api.d.ts +49 -0
  94. package/dist/lib/sql-api.d.ts.map +1 -0
  95. package/dist/lib/sql-api.js +124 -0
  96. package/dist/lib/table-helpers.d.ts +17 -0
  97. package/dist/lib/table-helpers.d.ts.map +1 -0
  98. package/dist/lib/table-helpers.js +34 -0
  99. package/dist/lib/tenant.d.ts +19 -0
  100. package/dist/lib/tenant.d.ts.map +1 -0
  101. package/dist/lib/tenant.js +110 -0
  102. package/dist/routes/auto/bulk-delete.routes.d.ts +4 -0
  103. package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -0
  104. package/dist/routes/auto/bulk-delete.routes.js +27 -0
  105. package/dist/routes/auto/bulk-upsert.routes.d.ts +4 -0
  106. package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -0
  107. package/dist/routes/auto/bulk-upsert.routes.js +25 -0
  108. package/dist/routes/auto/delete.routes.d.ts +4 -0
  109. package/dist/routes/auto/delete.routes.d.ts.map +1 -0
  110. package/dist/routes/auto/delete.routes.js +29 -0
  111. package/dist/routes/auto/get.routes.d.ts +4 -0
  112. package/dist/routes/auto/get.routes.d.ts.map +1 -0
  113. package/dist/routes/auto/get.routes.js +26 -0
  114. package/dist/routes/auto/insert.routes.d.ts +4 -0
  115. package/dist/routes/auto/insert.routes.d.ts.map +1 -0
  116. package/dist/routes/auto/insert.routes.js +29 -0
  117. package/dist/routes/auto/plugin.d.ts +11 -0
  118. package/dist/routes/auto/plugin.d.ts.map +1 -0
  119. package/dist/routes/auto/plugin.js +81 -0
  120. package/dist/routes/auto/route-helpers.d.ts +7 -0
  121. package/dist/routes/auto/route-helpers.d.ts.map +1 -0
  122. package/dist/routes/auto/route-helpers.js +12 -0
  123. package/dist/routes/auto/search.routes.d.ts +4 -0
  124. package/dist/routes/auto/search.routes.d.ts.map +1 -0
  125. package/dist/routes/auto/search.routes.js +50 -0
  126. package/dist/routes/auto/update.routes.d.ts +4 -0
  127. package/dist/routes/auto/update.routes.d.ts.map +1 -0
  128. package/dist/routes/auto/update.routes.js +30 -0
  129. package/dist/types.d.ts +240 -0
  130. package/dist/types.d.ts.map +1 -0
  131. package/dist/types.js +7 -0
  132. package/package.json +106 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luca Rainone (luca.rainone@gmail.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,422 @@
1
+ # fastify-auto-sqlapi
2
+
3
+ Automatic CRUD API generation from PostgreSQL tables for [Fastify](https://fastify.dev/). No ORM, no magic — just raw SQL via `pg`, with TypeBox validation and Swagger docs out of the box.
4
+
5
+ Point it at your database, and get a full REST API with search, pagination, joins, bulk operations, multi-tenant isolation, and hooks.
6
+
7
+ ## Features
8
+
9
+ - **Zero boilerplate** — define your tables, get 7 endpoints each
10
+ - **No ORM** — raw SQL via `pg` + parameterized queries
11
+ - **TypeBox validation** — request/response schemas auto-generated from your DB
12
+ - **Virtual joins** — fetch related records without complex SQL
13
+ - **Bulk operations** — batch insert/upsert/delete in single queries
14
+ - **Multi-tenant** — automatic row-level isolation, zero code in handlers
15
+ - **Hooks** — `beforeInsert`, `beforeUpdate`, `afterInsert` for custom logic
16
+ - **Swagger UI** — optional, auto-configured from your schemas
17
+ - **Composable** — register all routes or pick only what you need
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Install
22
+
23
+ ```bash
24
+ npm install fastify-auto-sqlapi fastify @fastify/postgres
25
+ ```
26
+
27
+ ### 2. Create the config file
28
+
29
+ Create `sqlapi.config.ts` in your project root. This is used only by the CLI generators, not at runtime.
30
+
31
+ ```typescript
32
+ export default {
33
+ outputDir: './src/schemas', // where to generate Schema files
34
+ schema: 'public', // PostgreSQL schema to introspect
35
+ };
36
+ ```
37
+
38
+ The DB connection is read from environment variables:
39
+
40
+ ```bash
41
+ # Either a full connection string:
42
+ DATABASE_URL=postgres://user:pass@localhost:5432/mydb
43
+
44
+ # Or individual vars:
45
+ POSTGRES_HOST=127.0.0.1
46
+ POSTGRES_PORT=5432
47
+ POSTGRES_USER=myuser
48
+ POSTGRES_PASSWORD=mypassword
49
+ POSTGRES_DB=mydb
50
+ ```
51
+
52
+ ### 3. Generate schemas from your database
53
+
54
+ ```bash
55
+ npx sqlapi-generate-schema
56
+ ```
57
+
58
+ This introspects your tables and generates one TypeBox schema file per table (e.g. `SchemaCustomer.ts`, `SchemaOrder.ts`). These files are auto-generated — don't edit them.
59
+
60
+ ### 4. Generate the tables template
61
+
62
+ ```bash
63
+ npx sqlapi-generate-tables
64
+ ```
65
+
66
+ This creates a `tables.ts` file with a `defineTable()` call for each table. It auto-detects primary keys, foreign key relations, and lays out all available options as commented code. **This file is yours to customize.**
67
+
68
+ ### 5. Register the plugin
69
+
70
+ ```typescript
71
+ import Fastify from 'fastify';
72
+ import fastifyPostgres from '@fastify/postgres';
73
+ import { fastifyAutoSqlApi } from 'fastify-auto-sqlapi';
74
+ import { dbTables } from './src/schemas/tables.js';
75
+
76
+ const app = Fastify();
77
+
78
+ await app.register(fastifyPostgres, {
79
+ connectionString: 'postgres://user:pass@localhost:5432/mydb',
80
+ });
81
+
82
+ await app.register(fastifyAutoSqlApi, {
83
+ DbTables: dbTables,
84
+ swagger: true,
85
+ prefix: '/api',
86
+ });
87
+
88
+ await app.listen({ port: 3000 });
89
+ ```
90
+
91
+ That's it. For a table called `customer`, you now have:
92
+
93
+ | Method | URL | Description |
94
+ |--------|-----|-------------|
95
+ | `POST` | `/api/search/customer` | Search with filters, pagination, joins |
96
+ | `GET` | `/api/rest/customer/:id` | Get a single record by primary key |
97
+ | `POST` | `/api/rest/customer` | Insert a new record |
98
+ | `PUT` | `/api/rest/customer` | Update a record |
99
+ | `DELETE` | `/api/rest/customer/:id` | Delete a record |
100
+ | `PUT` | `/api/bulk/customer` | Bulk upsert (array of records) |
101
+ | `POST` | `/api/bulk/customer/delete` | Bulk delete (array of PKs) |
102
+
103
+ > **Note:** Search uses `POST` because filters are passed as JSON in the request body.
104
+
105
+ ## Table Configuration
106
+
107
+ Tables are configured with `defineTable()`. The only required fields are `primary` and the output of `exportTableInfo()`:
108
+
109
+ ```typescript
110
+ import { defineTable, exportTableInfo } from 'fastify-auto-sqlapi';
111
+ import { SchemaCustomer } from './SchemaCustomer.js';
112
+
113
+ const TableCustomer = defineTable({
114
+ primary: 'id',
115
+ ...exportTableInfo(SchemaCustomer),
116
+ });
117
+ ```
118
+
119
+ `exportTableInfo()` provides the schema, a filter builder (auto-generates WHERE clauses from request fields), and extra filter definitions.
120
+
121
+ Export all your tables as a single record:
122
+
123
+ ```typescript
124
+ export const dbTables = {
125
+ customer: TableCustomer,
126
+ order: TableOrder,
127
+ };
128
+ ```
129
+
130
+ The keys in this record (`customer`, `order`) become the table names in the URL paths.
131
+
132
+ ### All available options
133
+
134
+ ```typescript
135
+ const TableCustomer = defineTable({
136
+ // Required
137
+ primary: 'id',
138
+ ...exportTableInfo(SchemaCustomer),
139
+
140
+ // Ordering & filtering
141
+ defaultOrder: 'name', // default ORDER BY
142
+ excludeFromCreation: ['id'], // omit from INSERT (e.g. auto-increment)
143
+ distinctResults: true, // SELECT DISTINCT
144
+
145
+ // Relations
146
+ allowedReadJoins: [
147
+ buildRelation(SchemaCustomer, 'id', SchemaOrder, 'customerId'),
148
+ ],
149
+ allowedWriteJoins: [
150
+ buildRelation(SchemaCustomer, 'id', SchemaOrder, 'customerId'),
151
+ ],
152
+
153
+ // Upsert (ON CONFLICT)
154
+ upsertMap: buildUpsertRules(
155
+ buildUpsertRule(SchemaCustomer, ['id']),
156
+ ),
157
+
158
+ // Multi-tenant isolation
159
+ tenantScope: { column: 'organization_id' },
160
+
161
+ // Hooks
162
+ beforeInsert: async (db, req, record) => { /* record is snake_case */ },
163
+ beforeUpdate: async (db, req, fields) => { /* fields is snake_case */ },
164
+ afterInsert: async (db, req, record, secondaryRecords) => { /* record is camelCase */ },
165
+
166
+ // Auth (per-table)
167
+ onRequests: [
168
+ async (request, reply) => {
169
+ if (!request.user) return reply.status(401).send({ error: 'Unauthorized' });
170
+ },
171
+ ],
172
+ });
173
+ ```
174
+
175
+ ### Custom filters (extraFilters)
176
+
177
+ For filters that don't map to real columns (e.g. a search `q` field):
178
+
179
+ ```typescript
180
+ import { Type, ConditionBuilder } from 'fastify-auto-sqlapi';
181
+
182
+ const TableCustomer = defineTable({
183
+ primary: 'id',
184
+ ...exportTableInfo(
185
+ SchemaCustomer,
186
+ { q: Type.Optional(Type.String()) }, // extra filter definition
187
+ (condition, filters) => { // custom condition builder
188
+ if (filters.q) {
189
+ const or = new ConditionBuilder('OR');
190
+ or.isILike('name', `%${filters.q}%`);
191
+ or.isILike('email', `%${filters.q}%`);
192
+ condition.append(or);
193
+ }
194
+ }
195
+ ),
196
+ });
197
+ ```
198
+
199
+ ## Plugin Options
200
+
201
+ ```typescript
202
+ await app.register(fastifyAutoSqlApi, {
203
+ DbTables: dbTables, // Required — your table definitions
204
+ prefix: '/api', // Optional — URL prefix for all routes
205
+ swagger: true, // Optional — enable Swagger UI (or pass SwaggerOptions)
206
+ onRequests: [authMiddleware], // Optional — global hooks applied to every route
207
+ getTenantId: (req) => id, // Optional — multi-tenant function
208
+ });
209
+ ```
210
+
211
+ | Option | Type | Required | Description |
212
+ |--------|------|----------|-------------|
213
+ | `DbTables` | `Record<string, ITable>` | Yes | Table configurations |
214
+ | `prefix` | `string` | No | URL prefix (e.g. `/api`) |
215
+ | `swagger` | `boolean \| SwaggerOptions` | No | Enable Swagger UI |
216
+ | `onRequests` | `Function[]` | No | Global auth/middleware hooks |
217
+ | `getTenantId` | `(req) => id \| null` | No | Tenant resolver for multi-tenant |
218
+
219
+ ## API Reference
220
+
221
+ ### POST /search/{table}
222
+
223
+ Search with filters, pagination, ordering, joins, and aggregations.
224
+
225
+ **Request body** (all fields optional):
226
+
227
+ ```json
228
+ {
229
+ "filters": { "name": "Mario" },
230
+ "joins": { "order": {} },
231
+ "paginator": { "page": 1, "itemsPerPage": 20 }
232
+ }
233
+ ```
234
+
235
+ **Querystring** (optional): `orderBy`, `page`, `itemsPerPage`, `computeMin`, `computeMax`, `computeSum`, `computeAvg`
236
+
237
+ **Response:**
238
+
239
+ ```json
240
+ {
241
+ "table": "customer",
242
+ "main": [{ "id": 1, "name": "Mario", "email": "m@test.it" }],
243
+ "joins": { "order": [{ "id": 10, "customerId": 1, "total": 50 }] },
244
+ "pagination": { "total": 25, "pages": 3, "paginator": { "page": 1, "itemsPerPage": 20 } }
245
+ }
246
+ ```
247
+
248
+ `joins`, `joinGroups`, and `pagination` only appear when requested. A simple `{}` body returns `{ table, main }`.
249
+
250
+ ### GET /rest/{table}/:id
251
+
252
+ Returns `{ main: { ... } }` or 404.
253
+
254
+ ### POST /rest/{table}
255
+
256
+ **Body:**
257
+
258
+ ```json
259
+ {
260
+ "main": { "name": "Mario", "email": "m@test.it" },
261
+ "secondaries": {
262
+ "order": [{ "total": 50, "status": "pending" }]
263
+ }
264
+ }
265
+ ```
266
+
267
+ `secondaries` is optional. FK fields are auto-filled from the inserted main record.
268
+
269
+ **Response (201):** `{ main: { ... }, secondaries: { ... } }`
270
+
271
+ ### PUT /rest/{table}
272
+
273
+ **Body:**
274
+
275
+ ```json
276
+ {
277
+ "main": { "id": 1, "name": "Updated Name" },
278
+ "secondaries": { "order": [{ "total": 75 }] },
279
+ "deletions": { "order": [{ "id": 10 }] }
280
+ }
281
+ ```
282
+
283
+ `main` must include the primary key. `secondaries` and `deletions` are optional.
284
+
285
+ ### DELETE /rest/{table}/:id
286
+
287
+ Returns the deleted record or 404.
288
+
289
+ ### PUT /bulk/{table}
290
+
291
+ **Body:** Array of `{ main, secondaries?, deletions? }`. All main records are inserted/upserted in a single SQL query.
292
+
293
+ ### POST /bulk/{table}/delete
294
+
295
+ **Body:** Array of objects with the PK field, e.g. `[{ "id": 1 }, { "id": 2 }]`. Executes as a single `DELETE WHERE pk IN (...)`.
296
+
297
+ ## Multi-Tenant
298
+
299
+ Automatic row-level isolation on all CRUD operations. Configure once, no code in handlers.
300
+
301
+ ```typescript
302
+ await app.register(fastifyAutoSqlApi, {
303
+ DbTables: dbTables,
304
+ getTenantId: (request) => request.user?.organizationId ?? null,
305
+ });
306
+ ```
307
+
308
+ When `getTenantId` returns `null`, no filtering is applied (admin mode).
309
+
310
+ ### Direct tenant (column on the table)
311
+
312
+ ```typescript
313
+ defineTable({
314
+ primary: 'id',
315
+ ...exportTableInfo(SchemaCustomer),
316
+ tenantScope: { column: 'organization_id' },
317
+ });
318
+ ```
319
+
320
+ ### Indirect tenant (via parent table)
321
+
322
+ ```typescript
323
+ defineTable({
324
+ primary: 'id',
325
+ ...exportTableInfo(SchemaOrder),
326
+ tenantScope: {
327
+ column: 'organization_id',
328
+ through: { schema: SchemaCustomer, localField: 'customer_id', foreignField: 'id' },
329
+ },
330
+ });
331
+ ```
332
+
333
+ Tables without `tenantScope` are unaffected.
334
+
335
+ ## Swagger
336
+
337
+ Enabled by passing `swagger: true` (or a config object) to the plugin options. Requires `@fastify/swagger` and `@fastify/swagger-ui` as peer dependencies.
338
+
339
+ ```bash
340
+ npm install @fastify/swagger @fastify/swagger-ui
341
+ ```
342
+
343
+ ```typescript
344
+ await app.register(fastifyAutoSqlApi, {
345
+ DbTables: dbTables,
346
+ swagger: {
347
+ title: 'My API',
348
+ description: 'Auto-generated CRUD API',
349
+ version: '1.0.0',
350
+ routePrefix: '/docs',
351
+ },
352
+ });
353
+ ```
354
+
355
+ If the swagger packages are not installed, the plugin logs a warning and continues without Swagger.
356
+
357
+ ## Granular Composition
358
+
359
+ Instead of registering the all-in-one plugin, you can register individual route plugins for more control:
360
+
361
+ ```typescript
362
+ import {
363
+ searchRoutes, getRoutes, insertRoutes, updateRoutes,
364
+ deleteRoutes, bulkUpsertRoutes, bulkDeleteRoutes, setupSwagger,
365
+ } from 'fastify-auto-sqlapi';
366
+
367
+ // Read-only API
368
+ await app.register(async (instance) => {
369
+ await setupSwagger(instance, { swagger: true });
370
+ const opts = { DbTables: dbTables };
371
+ await instance.register(searchRoutes, opts);
372
+ await instance.register(getRoutes, opts);
373
+ }, { prefix: '/public' });
374
+
375
+ // Full CRUD with auth
376
+ await app.register(async (instance) => {
377
+ const opts = { DbTables: dbTables, onRequests: [authMiddleware] };
378
+ await instance.register(searchRoutes, opts);
379
+ await instance.register(getRoutes, opts);
380
+ await instance.register(insertRoutes, opts);
381
+ await instance.register(updateRoutes, opts);
382
+ await instance.register(deleteRoutes, opts);
383
+ await instance.register(bulkUpsertRoutes, opts);
384
+ await instance.register(bulkDeleteRoutes, opts);
385
+ }, { prefix: '/admin' });
386
+ ```
387
+
388
+ ## Conventions
389
+
390
+ - **camelCase in API, snake_case in DB** — the plugin converts automatically
391
+ - **Joins are virtual** — they execute separate `SELECT ... WHERE fk IN (...)` queries, not SQL JOINs
392
+ - **Hooks receive snake_case** — `beforeInsert` and `beforeUpdate` get snake_case records; `afterInsert` gets camelCase
393
+ - **All response fields are Optional** — response schemas use `Type.Partial` since `RETURNING *` may return any subset
394
+
395
+ ## Re-exports
396
+
397
+ The package re-exports commonly needed utilities so you don't need to install them separately:
398
+
399
+ ```typescript
400
+ import {
401
+ Type, // from @sinclair/typebox
402
+ ConditionBuilder, // from node-condition-builder
403
+ Expression, // from node-condition-builder
404
+ QueryClient, // raw SQL query helper
405
+ defineTable,
406
+ exportTableInfo,
407
+ buildRelation,
408
+ buildUpsertRule,
409
+ buildUpsertRules,
410
+ } from 'fastify-auto-sqlapi';
411
+ ```
412
+
413
+ ## Requirements
414
+
415
+ - Node.js >= 18
416
+ - PostgreSQL
417
+ - Fastify >= 4
418
+ - `@fastify/postgres` >= 5
419
+
420
+ ## License
421
+
422
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=generate-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-schema.d.ts","sourceRoot":"","sources":["../../src/bin/generate-schema.ts"],"names":[],"mappings":""}
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { loadConfig } from '../lib/cli/config.js';
5
+ import { buildConnectionString, introspectTables } from '../lib/cli/pg-introspect.js';
6
+ import { buildMysqlConnectionConfig, introspectMysqlTables } from '../lib/cli/mysql-introspect.js';
7
+ import { buildTableMap, generateSchemaFile } from '../lib/cli/schema-codegen.js';
8
+ import { loadEnvFile, CONSOLE_COLORS, display, displayAsTableRow, error } from './utils.js';
9
+ loadEnvFile();
10
+ function parseCliArgs() {
11
+ const args = process.argv.slice(2);
12
+ const result = {};
13
+ for (let i = 0; i < args.length; i++) {
14
+ if (args[i] === '--output' && i + 1 < args.length) {
15
+ result.output = args[++i];
16
+ }
17
+ else if (args[i] === '--tables' && i + 1 < args.length) {
18
+ result.tables = args[++i].split(',').map((t) => t.trim()).filter(Boolean);
19
+ }
20
+ else if (args[i] === '--dialect' && i + 1 < args.length) {
21
+ result.dialect = args[++i];
22
+ }
23
+ }
24
+ return result;
25
+ }
26
+ async function main() {
27
+ display('++++++ fastify-auto-sqlapi: generating schemas ++++++', CONSOLE_COLORS.yellow);
28
+ const cliArgs = parseCliArgs();
29
+ const config = await loadConfig();
30
+ const dialect = (cliArgs.dialect || config.dialect || 'postgres');
31
+ const schema = config.schema || (dialect === 'postgres' ? 'public' : config.schema || 'public');
32
+ const outputDir = path.resolve(process.cwd(), cliArgs.output || config.outputDir);
33
+ const schemasDir = path.join(outputDir, 'schemas');
34
+ if (!fs.existsSync(schemasDir)) {
35
+ fs.mkdirSync(schemasDir, { recursive: true });
36
+ display(`Created directory: ${schemasDir}`, CONSOLE_COLORS.green);
37
+ }
38
+ let rows;
39
+ if (dialect === 'mysql' || dialect === 'mariadb') {
40
+ const connConfig = buildMysqlConnectionConfig();
41
+ rows = await introspectMysqlTables(connConfig, connConfig.database);
42
+ }
43
+ else {
44
+ const connectionString = buildConnectionString();
45
+ rows = await introspectTables(connectionString, schema);
46
+ }
47
+ if (rows.length === 0) {
48
+ display(`No tables found in schema "${schema}".`, CONSOLE_COLORS.magenta);
49
+ return;
50
+ }
51
+ const tableMap = buildTableMap(rows);
52
+ // Filter tables if --tables flag is provided
53
+ const tableNames = cliArgs.tables;
54
+ if (tableNames) {
55
+ for (const schemaName of Object.keys(tableMap)) {
56
+ if (!tableNames.includes(tableMap[schemaName].name)) {
57
+ delete tableMap[schemaName];
58
+ }
59
+ }
60
+ if (Object.keys(tableMap).length === 0) {
61
+ display(`No matching tables found for: ${tableNames.join(', ')}`, CONSOLE_COLORS.magenta);
62
+ return;
63
+ }
64
+ }
65
+ let somethingCreated = false;
66
+ let somethingTouched = false;
67
+ let untouched = 0;
68
+ const generatedFiles = new Set();
69
+ for (const schemaName of Object.keys(tableMap)) {
70
+ const { name: tableName, fields } = tableMap[schemaName];
71
+ const filename = path.join(schemasDir, `${schemaName}.ts`);
72
+ generatedFiles.add(`${schemaName}.ts`);
73
+ const content = generateSchemaFile(schemaName, tableName, fields);
74
+ let status = 'created';
75
+ let color = CONSOLE_COLORS.green;
76
+ if (fs.existsSync(filename)) {
77
+ status = 'untouched';
78
+ color = CONSOLE_COLORS.gray;
79
+ if (fs.readFileSync(filename, 'utf-8') !== content) {
80
+ status = 'updated';
81
+ color = CONSOLE_COLORS.cyan;
82
+ }
83
+ }
84
+ else {
85
+ somethingCreated = true;
86
+ }
87
+ if (status !== 'untouched') {
88
+ fs.writeFileSync(filename, content);
89
+ displayAsTableRow(filename, status, 90, color);
90
+ somethingTouched = true;
91
+ }
92
+ else {
93
+ untouched++;
94
+ }
95
+ }
96
+ // Remove orphan Schema*.ts files (only when generating all tables)
97
+ if (!tableNames) {
98
+ const existingFiles = fs.readdirSync(schemasDir);
99
+ for (const file of existingFiles) {
100
+ if (file.startsWith('Schema') && file.endsWith('.ts') && !generatedFiles.has(file)) {
101
+ const filePath = path.join(schemasDir, file);
102
+ fs.unlinkSync(filePath);
103
+ displayAsTableRow(filePath, 'removed', 90, CONSOLE_COLORS.red);
104
+ somethingTouched = true;
105
+ }
106
+ }
107
+ }
108
+ console.log('');
109
+ if (untouched > 0) {
110
+ display(`${untouched} schema(s) untouched`, CONSOLE_COLORS.gray);
111
+ }
112
+ if (somethingCreated) {
113
+ display("Don't forget to git add your new Schema files.", CONSOLE_COLORS.magenta);
114
+ }
115
+ if (!somethingTouched) {
116
+ display('All schemas are already up to date.', CONSOLE_COLORS.magenta);
117
+ }
118
+ }
119
+ main().catch((e) => {
120
+ error(`Error: ${e.message}`);
121
+ process.exit(1);
122
+ });
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=generate-tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-tables.d.ts","sourceRoot":"","sources":["../../src/bin/generate-tables.ts"],"names":[],"mappings":""}