fastify-auto-sqlapi 0.0.4 → 0.1.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 (127) hide show
  1. package/AGENTS.md +15 -2
  2. package/AGENTS_BACKEND.md +160 -32
  3. package/AGENTS_FRONTEND.md +220 -49
  4. package/README.md +147 -27
  5. package/dist/bin/generate-schema.js +12 -29
  6. package/dist/bin/generate-tables.js +10 -33
  7. package/dist/bin/utils.d.ts +29 -1
  8. package/dist/bin/utils.d.ts.map +1 -1
  9. package/dist/bin/utils.js +71 -2
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/lib/cli/load-dependency.d.ts +8 -0
  13. package/dist/lib/cli/load-dependency.d.ts.map +1 -0
  14. package/dist/lib/cli/load-dependency.js +21 -0
  15. package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
  16. package/dist/lib/cli/mysql-introspect.js +2 -11
  17. package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
  18. package/dist/lib/cli/pg-introspect.js +2 -17
  19. package/dist/lib/cli/schema-codegen.d.ts +1 -1
  20. package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
  21. package/dist/lib/cli/schema-codegen.js +11 -4
  22. package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
  23. package/dist/lib/cli/tables-codegen.js +4 -3
  24. package/dist/lib/db.d.ts +1 -0
  25. package/dist/lib/db.d.ts.map +1 -1
  26. package/dist/lib/db.js +33 -49
  27. package/dist/lib/dialect.d.ts +17 -0
  28. package/dist/lib/dialect.d.ts.map +1 -1
  29. package/dist/lib/dialect.js +32 -0
  30. package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
  31. package/dist/lib/engine/bulk/bulk-upsert.js +26 -50
  32. package/dist/lib/engine/rest/delete.d.ts.map +1 -1
  33. package/dist/lib/engine/rest/delete.js +5 -10
  34. package/dist/lib/engine/rest/get.d.ts.map +1 -1
  35. package/dist/lib/engine/rest/get.js +4 -6
  36. package/dist/lib/engine/rest/insert.d.ts.map +1 -1
  37. package/dist/lib/engine/rest/insert.js +20 -39
  38. package/dist/lib/engine/rest/update.d.ts.map +1 -1
  39. package/dist/lib/engine/rest/update.js +35 -67
  40. package/dist/lib/engine/search/search.d.ts.map +1 -1
  41. package/dist/lib/engine/search/search.js +650 -130
  42. package/dist/lib/engine/write-helpers.d.ts +39 -2
  43. package/dist/lib/engine/write-helpers.d.ts.map +1 -1
  44. package/dist/lib/engine/write-helpers.js +58 -15
  45. package/dist/lib/errors.d.ts +11 -0
  46. package/dist/lib/errors.d.ts.map +1 -0
  47. package/dist/lib/errors.js +12 -0
  48. package/dist/lib/naming.d.ts +11 -2
  49. package/dist/lib/naming.d.ts.map +1 -1
  50. package/dist/lib/naming.js +33 -5
  51. package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
  52. package/dist/lib/schema/bulk-upsert.js +9 -25
  53. package/dist/lib/schema/helpers.d.ts +28 -1
  54. package/dist/lib/schema/helpers.d.ts.map +1 -1
  55. package/dist/lib/schema/helpers.js +38 -0
  56. package/dist/lib/schema/insert.d.ts.map +1 -1
  57. package/dist/lib/schema/insert.js +9 -19
  58. package/dist/lib/schema/search.d.ts.map +1 -1
  59. package/dist/lib/schema/search.js +147 -53
  60. package/dist/lib/schema/update.d.ts.map +1 -1
  61. package/dist/lib/schema/update.js +9 -25
  62. package/dist/lib/sql-api.d.ts +6 -6
  63. package/dist/lib/sql-api.d.ts.map +1 -1
  64. package/dist/lib/sql-api.js +8 -8
  65. package/dist/lib/table-helpers.d.ts +6 -1
  66. package/dist/lib/table-helpers.d.ts.map +1 -1
  67. package/dist/lib/table-helpers.js +51 -3
  68. package/dist/lib/tenant.d.ts +23 -0
  69. package/dist/lib/tenant.d.ts.map +1 -1
  70. package/dist/lib/tenant.js +57 -9
  71. package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -1
  72. package/dist/routes/auto/bulk-delete.routes.js +17 -23
  73. package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -1
  74. package/dist/routes/auto/bulk-upsert.routes.js +16 -22
  75. package/dist/routes/auto/delete.routes.d.ts.map +1 -1
  76. package/dist/routes/auto/delete.routes.js +18 -24
  77. package/dist/routes/auto/get.routes.d.ts.map +1 -1
  78. package/dist/routes/auto/get.routes.js +16 -23
  79. package/dist/routes/auto/insert.routes.d.ts.map +1 -1
  80. package/dist/routes/auto/insert.routes.js +19 -26
  81. package/dist/routes/auto/route-helpers.d.ts +27 -2
  82. package/dist/routes/auto/route-helpers.d.ts.map +1 -1
  83. package/dist/routes/auto/route-helpers.js +39 -1
  84. package/dist/routes/auto/search.routes.d.ts.map +1 -1
  85. package/dist/routes/auto/search.routes.js +47 -47
  86. package/dist/routes/auto/update.routes.d.ts.map +1 -1
  87. package/dist/routes/auto/update.routes.js +20 -27
  88. package/dist/types/cli.d.ts +22 -0
  89. package/dist/types/cli.d.ts.map +1 -0
  90. package/dist/types/cli.js +1 -0
  91. package/dist/types/computed.d.ts +29 -0
  92. package/dist/types/computed.d.ts.map +1 -0
  93. package/dist/types/computed.js +1 -0
  94. package/dist/types/conditions.d.ts +16 -0
  95. package/dist/types/conditions.d.ts.map +1 -0
  96. package/dist/types/conditions.js +1 -0
  97. package/dist/types/db.d.ts +22 -0
  98. package/dist/types/db.d.ts.map +1 -0
  99. package/dist/types/db.js +1 -0
  100. package/dist/types/dml.d.ts +64 -0
  101. package/dist/types/dml.d.ts.map +1 -0
  102. package/dist/types/dml.js +1 -0
  103. package/dist/types/join.d.ts +44 -0
  104. package/dist/types/join.d.ts.map +1 -0
  105. package/dist/types/join.js +1 -0
  106. package/dist/types/plugin.d.ts +20 -0
  107. package/dist/types/plugin.d.ts.map +1 -0
  108. package/dist/types/plugin.js +1 -0
  109. package/dist/types/schema.d.ts +10 -0
  110. package/dist/types/schema.d.ts.map +1 -0
  111. package/dist/types/schema.js +1 -0
  112. package/dist/types/search.d.ts +45 -0
  113. package/dist/types/search.d.ts.map +1 -0
  114. package/dist/types/search.js +1 -0
  115. package/dist/types/table.d.ts +52 -0
  116. package/dist/types/table.d.ts.map +1 -0
  117. package/dist/types/table.js +7 -0
  118. package/dist/types/tenant.d.ts +19 -0
  119. package/dist/types/tenant.d.ts.map +1 -0
  120. package/dist/types/tenant.js +1 -0
  121. package/dist/types/validation.d.ts +21 -0
  122. package/dist/types/validation.d.ts.map +1 -0
  123. package/dist/types/validation.js +1 -0
  124. package/dist/types.d.ts +12 -258
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js +12 -7
  127. package/package.json +8 -5
package/README.md CHANGED
@@ -1,15 +1,16 @@
1
1
  # fastify-auto-sqlapi
2
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.
3
+ Automatic CRUD API generation from PostgreSQL, MySQL, and MariaDB tables for [Fastify](https://fastify.dev/). No ORM, no magic — just raw SQL, with TypeBox validation and Swagger docs out of the box.
4
4
 
5
- Point it at your database, and get a full REST API with search, pagination, joins, bulk operations, multi-tenant isolation, and hooks.
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
7
  ## Features
8
8
 
9
9
  - **Zero boilerplate** — define your tables, get 7 endpoints each
10
10
  - **No ORM** — raw SQL via `pg` + parameterized queries
11
11
  - **TypeBox validation** — request/response schemas auto-generated from your DB
12
- - **Virtual joins** — fetch related records without complex SQL
12
+ - **Joins** — four explicit families (`joinMustExist`, `joinMultiple`, `joinGroup`, `joinLeft`) with alias support
13
+ - **Computed fields** — declare virtual fields as SQL expressions in `defineTable`; usable like schema fields in filters/orderBy/conditions, and opt-in projected in the response via `selectComputed`. Covers JSON extraction, derived columns, dialect-aware date bucketing
13
14
  - **Bulk operations** — batch insert/upsert/delete in single queries
14
15
  - **Multi-tenant** — automatic row-level isolation, zero code in handlers
15
16
  - **Validation** — structured field-level validation with cross-entity support
@@ -17,6 +18,8 @@ Point it at your database, and get a full REST API with search, pagination, join
17
18
  - **Swagger UI** — optional, auto-configured from your schemas
18
19
  - **Composable** — register all routes or pick only what you need
19
20
 
21
+ > ⚠️ **Migrating from a previous version?** The join API was redesigned. See **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** for the full migration guide.
22
+
20
23
  ## Quick Start
21
24
 
22
25
  ### 1. Install
@@ -143,12 +146,16 @@ const TableCustomer = defineTable({
143
146
  excludeFromCreation: ['id'], // omit from INSERT (e.g. auto-increment)
144
147
  distinctResults: true, // SELECT DISTINCT
145
148
 
146
- // Relations
149
+ // Relations — alias defaults to joinSchema.tableName. Override with `{ alias: '...' }`
150
+ // when you join the same table twice (e.g. `createdBy`/`updatedBy`) or want a friendlier
151
+ // name. Set `unique: true` for N:1 (parent) relations to enable `joinLeft`.
147
152
  allowedReadJoins: [
148
- buildRelation(SchemaCustomer, 'id', SchemaOrder, 'customerId'),
153
+ buildRelation(SchemaCustomer, 'id', SchemaOrder, 'customerId'), // alias = 'order'
154
+ buildRelation(SchemaSession, 'userId', SchemaUser, 'id', { unique: true }), // alias = 'user', N:1
155
+ buildRelation(SchemaSession, 'updatedBy', SchemaUser, 'id', { alias: 'updater', unique: true }),
149
156
  ],
150
157
  allowedWriteJoins: [
151
- buildRelation(SchemaCustomer, 'id', SchemaOrder, 'customerId'),
158
+ buildRelation(SchemaCustomer, 'id', SchemaOrder, 'customerId'), // alias = 'order'
152
159
  ],
153
160
 
154
161
  // Upsert (ON CONFLICT)
@@ -176,10 +183,10 @@ const TableCustomer = defineTable({
176
183
  return [];
177
184
  },
178
185
 
179
- // Hooks (runs after validation)
180
- beforeInsert: async (db, req, record) => { /* record is snake_case */ },
181
- beforeUpdate: async (db, req, fields) => { /* fields is snake_case */ },
182
- afterInsert: async (db, req, record, secondaryRecords) => { /* record is camelCase */ },
186
+ // Hooks (runs after validation) — all receive camelCase records (schema field names)
187
+ beforeInsert: async (db, req, record) => { /* camelCase; mutations propagate to INSERT */ },
188
+ beforeUpdate: async (db, req, fields) => { /* camelCase; PK included for reference, excluded from UPDATE SET */ },
189
+ afterInsert: async (db, req, record, secondaryRecords) => { /* camelCase; input merged with generated PK */ },
183
190
 
184
191
  // Auth (per-table)
185
192
  onRequests: [
@@ -219,37 +226,89 @@ const TableCustomer = defineTable({
219
226
  ```typescript
220
227
  await app.register(fastifyAutoSqlApi, {
221
228
  DbTables: dbTables, // Required — your table definitions
229
+ dialect: 'postgres', // Optional — 'postgres' | 'mysql' | 'mariadb' (default: 'postgres')
222
230
  prefix: '/api', // Optional — URL prefix for all routes
223
231
  swagger: true, // Optional — enable Swagger UI (or pass SwaggerOptions)
224
232
  onRequests: [authMiddleware], // Optional — global hooks applied to every route
225
233
  getTenantId: (req) => id, // Optional — multi-tenant function
234
+ debug: true, // Optional — log all SQL queries and params
226
235
  });
227
236
  ```
228
237
 
229
238
  | Option | Type | Required | Description |
230
239
  |--------|------|----------|-------------|
231
240
  | `DbTables` | `Record<string, ITable>` | Yes | Table configurations |
241
+ | `dialect` | `'postgres' \| 'mysql' \| 'mariadb'` | No | DB dialect (default: `'postgres'`) |
232
242
  | `prefix` | `string` | No | URL prefix (e.g. `/api`) |
233
243
  | `swagger` | `boolean \| SwaggerOptions` | No | Enable Swagger UI |
234
244
  | `onRequests` | `Function[]` | No | Global auth/middleware hooks |
235
245
  | `getTenantId` | `(req) => id \| null` | No | Tenant resolver for multi-tenant |
246
+ | `debug` | `boolean` | No | Log all SQL queries to console |
247
+
248
+ For MySQL/MariaDB, register `mysql2/promise` pool instead of `@fastify/postgres` and pass `dialect: 'mysql'` or `'mariadb'`.
236
249
 
237
250
  ## API Reference
238
251
 
239
252
  ### POST /search/{table}
240
253
 
241
- Search with filters, pagination, ordering, joins, and aggregations.
254
+ Search with filters, advanced conditions, pagination, ordering, joins, and aggregations.
242
255
 
243
256
  **Request body** (all fields optional):
244
257
 
245
258
  ```json
246
259
  {
247
260
  "filters": { "name": "Mario" },
248
- "joins": { "order": {} },
249
- "paginator": { "page": 1, "itemsPerPage": 20 }
261
+ "conditions": [
262
+ { "field": "total", "method": "isGreater", "params": [100] },
263
+ { "field": "createdAt", "method": "isBetween", "params": ["2024-01-01", "2024-12-31"] }
264
+ ],
265
+ "joinMustExist": {
266
+ "orders": {
267
+ "filters": { "status": "completed" },
268
+ "conditions": [{ "field": "total", "method": "isGreater", "params": [50] }]
269
+ }
270
+ },
271
+ "joinMultiple": {
272
+ "orders": {
273
+ "filters": { "status": "completed" },
274
+ "selection": "id,total,status"
275
+ }
276
+ },
277
+ "joinGroup": {
278
+ "orders": {
279
+ "aggregations": {
280
+ "by": "status",
281
+ "sum": ["total"],
282
+ "min": ["total"],
283
+ "max": ["total"],
284
+ "avg": ["total"],
285
+ "count": ["id"],
286
+ "distinctCount": ["status"]
287
+ },
288
+ "filters": { "status": "completed" }
289
+ }
290
+ },
291
+ "joinLeft": {
292
+ "creator": { "selection": "id,name,email" }
293
+ }
250
294
  }
251
295
  ```
252
296
 
297
+ - **`filters`** — equality-based, flat key/value. Supports schema fields + `extraFilters`.
298
+ - **`conditions`** — array of `{ field, method, params }`. Methods: `isEqual`, `isNotEqual`, `isGreater`, `isGreaterOrEqual`, `isLess`, `isLessOrEqual`, `isLike`, `isILike`, `isIn`, `isNotIn`, `isBetween`, `isNotBetween`, `isNull`, `isNotNull`.
299
+ - **`joinMustExist`** — EXISTS-based filter: "main rows where at least one related row matches". Accepts `{ filters, conditions }` (both optional). Aliases must come from `allowedReadJoins` declarations with `unique: false`.
300
+ - **`joinMultiple`** — fetches related child rows in a side query. Accepts `{ filters, conditions, selection }`. Same `unique: false` aliases.
301
+ - **`joinGroup`** — aggregations on the related table. Supports `sum`, `min`, `max`, `avg`, `count`, `distinctCount`, and optional `by` for GROUP BY (a schema field name or a computed-field name declared on the join table — e.g. for date bucketing declare a computed using `db.dateTrunc('month', qiCol('orderDate'))`). Accepts `{ filters, conditions }`. Same `unique: false` aliases.
302
+ - **`joinLeft`** — embeds an N:1 parent. Real `LEFT JOIN` is added on demand (only when the request has `filters`/`conditions` on the parent or uses 2-parti `orderBy` on this alias). Aliases must be declared with `unique: true`. Accepts `{ filters, conditions, selection }`.
303
+
304
+ **Dot-notation in `orderBy` and `conditions`**:
305
+
306
+ | Form | Source | Example |
307
+ |------|--------|---------|
308
+ | `<field>` | main schema | `orderBy=name ASC` |
309
+ | `<alias>.<field>` | `joinLeft` aliases (`unique: true`) | `orderBy=creator.name ASC` |
310
+ | `<alias>.<fn>.<field>` | `joinGroup` aliases declared in the same body | `orderBy=orders.sum.total DESC`, or `conditions: [{ field: 'orders.count.id', method: 'isGreaterOrEqual', params: [4] }]` |
311
+
253
312
  **Querystring** (optional): `orderBy`, `page`, `itemsPerPage`, `computeMin`, `computeMax`, `computeSum`, `computeAvg`
254
313
 
255
314
  **Response:**
@@ -258,12 +317,25 @@ Search with filters, pagination, ordering, joins, and aggregations.
258
317
  {
259
318
  "table": "customer",
260
319
  "main": [{ "id": 1, "name": "Mario", "email": "m@test.it" }],
261
- "joins": { "order": [{ "id": 10, "customerId": 1, "total": 50 }] },
262
- "pagination": { "total": 25, "pages": 3, "paginator": { "page": 1, "itemsPerPage": 20 } }
320
+ "joinLeft": { "creator": [{ "id": 7, "name": "Alice", "email": "a@x.it" }] },
321
+ "joinMultiple": { "orders": [{ "id": 10, "customerId": 1, "total": 50 }] },
322
+ "joinGroup": {
323
+ "orders": {
324
+ "sum": { "total": 300 },
325
+ "count": { "id": 2 },
326
+ "rows": [{ "by": "completed", "sum_total": 300, "count_id": 2 }]
327
+ }
328
+ },
329
+ "pagination": {
330
+ "total": 25,
331
+ "pages": 3,
332
+ "computed": { "min": { "id": 1 }, "max": { "id": 100 } },
333
+ "paginator": { "page": 1, "itemsPerPage": 20 }
334
+ }
263
335
  }
264
336
  ```
265
337
 
266
- `joins`, `joinGroups`, and `pagination` only appear when requested. A simple `{}` body returns `{ table, main }`.
338
+ `joinLeft`, `joinMultiple`, `joinGroup`, and `pagination` appear only when requested. A simple `{}` body returns `{ table, main }`. `pagination.computed` appears only if `computeMin`/`computeMax`/`computeSum`/`computeAvg` are used.
267
339
 
268
340
  ### GET /rest/{table}/:id
269
341
 
@@ -277,12 +349,12 @@ Returns `{ main: { ... } }` or 404.
277
349
  {
278
350
  "main": { "name": "Mario", "email": "m@test.it" },
279
351
  "secondaries": {
280
- "order": [{ "total": 50, "status": "pending" }]
352
+ "orders": [{ "total": 50, "status": "pending" }]
281
353
  }
282
354
  }
283
355
  ```
284
356
 
285
- `secondaries` is optional. FK fields are auto-filled from the inserted main record.
357
+ `secondaries` keys are the **alias** declared in `allowedWriteJoins`. FK fields are auto-filled from the inserted main record.
286
358
 
287
359
  **Response (201):** `{ main: { ... }, secondaries: { ... } }`
288
360
 
@@ -293,8 +365,8 @@ Returns `{ main: { ... } }` or 404.
293
365
  ```json
294
366
  {
295
367
  "main": { "id": 1, "name": "Updated Name" },
296
- "secondaries": { "order": [{ "total": 75 }] },
297
- "deletions": { "order": [{ "id": 10 }] }
368
+ "secondaries": { "orders": [{ "total": 75 }] },
369
+ "deletions": { "orders": [{ "id": 10 }] }
298
370
  }
299
371
  ```
300
372
 
@@ -312,6 +384,24 @@ Returns the deleted record or 404.
312
384
 
313
385
  **Body:** Array of objects with the PK field, e.g. `[{ "id": 1 }, { "id": 2 }]`. Executes as a single `DELETE WHERE pk IN (...)`.
314
386
 
387
+ ### Validation errors (400)
388
+
389
+ Both schema-level (TypeBox/Ajv) and custom (`validate` / `validateBulk`) errors use the same response shape:
390
+
391
+ ```json
392
+ {
393
+ "statusCode": 400,
394
+ "error": "Bad Request",
395
+ "message": "Validation failed",
396
+ "fields": [
397
+ { "path": "main.email", "code": "format", "message": "must match format \"email\"" },
398
+ { "path": "name", "code": "required", "message": "is required" }
399
+ ]
400
+ }
401
+ ```
402
+
403
+ Custom validators return tuples `[field, code]` or `[field, code, message]` — `message` defaults to `code` if omitted. `validateBulk` replaces per-item `validate` in bulk-upsert requests.
404
+
315
405
  ## Multi-Tenant
316
406
 
317
407
  Automatic row-level isolation on all CRUD operations. Configure once, no code in handlers.
@@ -405,9 +495,11 @@ await app.register(async (instance) => {
405
495
 
406
496
  ## Conventions
407
497
 
408
- - **camelCase in API, snake_case in DB** — the plugin converts automatically
409
- - **Joins are virtual** — they execute separate `SELECT ... WHERE fk IN (...)` queries, not SQL JOINs
410
- - **Hooks receive snake_case** — `beforeInsert` and `beforeUpdate` get snake_case records; `afterInsert` gets camelCase
498
+ - **camelCase everywhere in the API** — requests, responses, `validate`, and all hooks (`beforeInsert`, `beforeUpdate`, `afterInsert`) use schema field names
499
+ - **Conversion to DB column format is automatic** via `colMap` — supports both snake_case and camelCase DB columns (e.g. betterauth-style)
500
+ - **Aliases identify joins** — declared in `buildRelation({ alias })`, used as keys in request/response/`secondaries`/dotted notation
501
+ - **`joinMustExist` / `joinMultiple` / `joinGroup`** are 1:N (child→main) and use side queries / EXISTS / correlated subqueries — no row duplication
502
+ - **`joinLeft`** is N:1 (parent→main) and adds a real `LEFT JOIN` on demand (only when filtering/ordering by parent)
411
503
  - **All response fields are Optional** — response schemas use `Type.Partial` since `RETURNING *` may return any subset
412
504
 
413
505
  ## Re-exports
@@ -417,23 +509,51 @@ The package re-exports commonly needed utilities so you don't need to install th
417
509
  ```typescript
418
510
  import {
419
511
  Type, // from @sinclair/typebox
512
+ type Static, // from @sinclair/typebox
420
513
  ConditionBuilder, // from node-condition-builder
421
514
  Expression, // from node-condition-builder
422
- QueryClient, // raw SQL query helper
515
+
516
+ // Table configuration
423
517
  defineTable,
424
518
  exportTableInfo,
425
519
  buildRelation,
426
520
  buildUpsertRule,
427
521
  buildUpsertRules,
522
+
523
+ // DB layer
524
+ QueryClient, // raw SQL query helper
525
+ createQueryClient, // factory with dialect string
526
+ pgQueryable, // pg pool adapter
527
+ mysqlQueryable, // mysql2 pool adapter
528
+
529
+ // Programmatic high-level API
530
+ createSqlApi, // standalone SqlApi (for scripts/tests)
531
+ setupSwagger, // manual Swagger registration
532
+ } from 'fastify-auto-sqlapi';
533
+
534
+ import type {
535
+ ValidationError, // [field, code] | [field, code, message]
536
+ ValidatorFn,
537
+ BulkValidatorFn,
538
+ JoinDefinition,
539
+ JoinRefFilter,
540
+ JoinFetchRequest,
541
+ JoinGroupRequest,
542
+ SearchCondition,
543
+ ConditionMethod,
428
544
  } from 'fastify-auto-sqlapi';
429
545
  ```
430
546
 
547
+ After registering the plugin, `app.sqlApi` is decorated on the Fastify instance and exposes `search`, `get`, `insert`, `update`, `delete`, `bulkUpsert`, `bulkDelete` for custom routes — same code path as the auto-generated endpoints.
548
+
431
549
  ## Requirements
432
550
 
433
551
  - Node.js >= 18
434
- - PostgreSQL
435
- - Fastify >= 4
436
- - `@fastify/postgres` >= 5
552
+ - Fastify >= 4 (peer dependency)
553
+ - One of:
554
+ - **PostgreSQL** + `pg` + `@fastify/postgres`
555
+ - **MySQL** / **MariaDB** + `mysql2`
556
+ - Optional: `@fastify/swagger` + `@fastify/swagger-ui` for Swagger UI
437
557
 
438
558
  ## License
439
559
 
@@ -5,28 +5,15 @@ import { loadConfig } from '../lib/cli/config.js';
5
5
  import { buildConnectionString, introspectTables } from '../lib/cli/pg-introspect.js';
6
6
  import { buildMysqlConnectionConfig, introspectMysqlTables } from '../lib/cli/mysql-introspect.js';
7
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();
8
+ import { loadEnvFile, CONSOLE_COLORS, display, displayAsTableRow, parseArgs, runCli } from './utils.js';
9
+ await runCli('fastify-auto-sqlapi: generating schemas', async () => {
10
+ const cliArgs = parseArgs({
11
+ output: { type: 'value' },
12
+ tables: { type: 'list' },
13
+ dialect: { type: 'value' },
14
+ });
29
15
  const config = await loadConfig();
16
+ loadEnvFile(config.envFile);
30
17
  const dialect = (cliArgs.dialect || config.dialect || 'postgres');
31
18
  const schema = config.schema || (dialect === 'postgres' ? 'public' : config.schema || 'public');
32
19
  const outputDir = path.resolve(process.cwd(), cliArgs.output || config.outputDir);
@@ -51,7 +38,7 @@ async function main() {
51
38
  const tableMap = buildTableMap(rows);
52
39
  // Filter tables if --tables flag is provided
53
40
  const tableNames = cliArgs.tables;
54
- if (tableNames) {
41
+ if (tableNames.length) {
55
42
  for (const schemaName of Object.keys(tableMap)) {
56
43
  if (!tableNames.includes(tableMap[schemaName].name)) {
57
44
  delete tableMap[schemaName];
@@ -67,10 +54,10 @@ async function main() {
67
54
  let untouched = 0;
68
55
  const generatedFiles = new Set();
69
56
  for (const schemaName of Object.keys(tableMap)) {
70
- const { name: tableName, fields } = tableMap[schemaName];
57
+ const { name: tableName, fields, colMap } = tableMap[schemaName];
71
58
  const filename = path.join(schemasDir, `${schemaName}.ts`);
72
59
  generatedFiles.add(`${schemaName}.ts`);
73
- const content = generateSchemaFile(schemaName, tableName, fields);
60
+ const content = generateSchemaFile(schemaName, tableName, fields, colMap);
74
61
  let status = 'created';
75
62
  let color = CONSOLE_COLORS.green;
76
63
  if (fs.existsSync(filename)) {
@@ -94,7 +81,7 @@ async function main() {
94
81
  }
95
82
  }
96
83
  // Remove orphan Schema*.ts files (only when generating all tables)
97
- if (!tableNames) {
84
+ if (!tableNames.length) {
98
85
  const existingFiles = fs.readdirSync(schemasDir);
99
86
  for (const file of existingFiles) {
100
87
  if (file.startsWith('Schema') && file.endsWith('.ts') && !generatedFiles.has(file)) {
@@ -115,8 +102,4 @@ async function main() {
115
102
  if (!somethingTouched) {
116
103
  display('All schemas are already up to date.', CONSOLE_COLORS.magenta);
117
104
  }
118
- }
119
- main().catch((e) => {
120
- error(`Error: ${e.message}`);
121
- process.exit(1);
122
105
  });
@@ -3,8 +3,7 @@ import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { loadConfig } from '../lib/cli/config.js';
5
5
  import { parseSchemaFile, generateSingleTableFile, generateDbTablesIndex } from '../lib/cli/tables-codegen.js';
6
- import { loadEnvFile, CONSOLE_COLORS, display, displayAsTableRow, error } from './utils.js';
7
- loadEnvFile();
6
+ import { loadEnvFile, CONSOLE_COLORS, display, displayAsTableRow, error, parseArgs, runCli } from './utils.js';
8
7
  function printHelp() {
9
8
  console.log('');
10
9
  display('Usage:', CONSOLE_COLORS.yellow);
@@ -23,39 +22,21 @@ function printHelp() {
23
22
  display('Table*.ts files are only created if they do not already exist.', CONSOLE_COLORS.magenta);
24
23
  display('dbTables.ts is always regenerated to include all schemas.', CONSOLE_COLORS.magenta);
25
24
  }
26
- function parseCliArgs() {
27
- const args = process.argv.slice(2);
28
- const result = { all: false, tables: [] };
29
- for (let i = 0; i < args.length; i++) {
30
- if (args[i] === '--output' && i + 1 < args.length) {
31
- result.output = args[++i];
32
- }
33
- else if (args[i] === '--all') {
34
- result.all = true;
35
- }
36
- else if (!args[i].startsWith('--')) {
37
- // Split by comma to support both "a b" and "a,b" and "a, b"
38
- for (const part of args[i].split(',')) {
39
- const trimmed = part.trim();
40
- if (trimmed)
41
- result.tables.push(trimmed);
42
- }
43
- }
44
- }
45
- return result;
46
- }
47
- async function main() {
48
- const cliArgs = parseCliArgs();
25
+ await runCli('fastify-auto-sqlapi: generating tables template', async () => {
26
+ const cliArgs = parseArgs({
27
+ output: { type: 'value' },
28
+ all: { type: 'flag' },
29
+ tables: { type: 'list' },
30
+ });
49
31
  if (!cliArgs.all && cliArgs.tables.length === 0) {
50
- display('++++++ fastify-auto-sqlapi: generate tables template ++++++', CONSOLE_COLORS.yellow);
51
32
  printHelp();
52
33
  process.exit(1);
53
34
  }
54
- display('++++++ fastify-auto-sqlapi: generating tables template ++++++', CONSOLE_COLORS.yellow);
55
35
  const config = await loadConfig();
36
+ loadEnvFile(config.envFile);
56
37
  const outputDir = path.resolve(process.cwd(), cliArgs.output || config.outputDir);
57
- const schemasDir = outputDir;
58
- const tablesDir = outputDir;
38
+ const schemasDir = path.join(outputDir, 'schemas');
39
+ const tablesDir = path.join(outputDir, 'tables');
59
40
  if (!fs.existsSync(schemasDir)) {
60
41
  error(`Schemas directory not found: ${schemasDir}`);
61
42
  display('Run sqlapi-generate-schema first.', CONSOLE_COLORS.magenta);
@@ -128,8 +109,4 @@ async function main() {
128
109
  }
129
110
  console.log('');
130
111
  display('Edit the generated Table*.ts files to customize your table configuration.', CONSOLE_COLORS.magenta);
131
- }
132
- main().catch((e) => {
133
- error(`Error: ${e.message}`);
134
- process.exit(1);
135
112
  });
@@ -2,7 +2,7 @@
2
2
  * Load .env file from cwd if it exists.
3
3
  * Does not override already-set environment variables.
4
4
  */
5
- export declare function loadEnvFile(): void;
5
+ export declare function loadEnvFile(envFile?: string): void;
6
6
  export declare const CONSOLE_COLORS: {
7
7
  readonly black: 30;
8
8
  readonly gray: 90;
@@ -15,4 +15,32 @@ export declare const CONSOLE_COLORS: {
15
15
  export declare function displayAsTableRow(pref: string, value: string, distance: number, color?: number): void;
16
16
  export declare function display(text: string, color?: number): void;
17
17
  export declare function error(text: string): void;
18
+ type ArgSpec = {
19
+ type: 'value';
20
+ } | {
21
+ type: 'flag';
22
+ } | {
23
+ type: 'list';
24
+ };
25
+ type ArgValue<S extends ArgSpec> = S['type'] extends 'flag' ? boolean : S['type'] extends 'list' ? string[] : string | undefined;
26
+ /**
27
+ * Tiny declarative argv parser for the bundled CLIs.
28
+ * - `value` → recognized as `--name X`, last value wins
29
+ * - `flag` → recognized as `--name`, default false
30
+ * - `list` → comma-separated `--name a,b,c`; can also accept positional comma-/space- separated tokens
31
+ *
32
+ * Positional tokens (non-`--`) are accumulated into `positional`. List specs auto-merge those tokens
33
+ * (split on ',') into the list as well, supporting both "a b" and "a,b" CLI shapes.
34
+ */
35
+ export declare function parseArgs<S extends Record<string, ArgSpec>>(spec: S, argv?: string[]): {
36
+ [K in keyof S]: ArgValue<S[K]>;
37
+ } & {
38
+ positional: string[];
39
+ };
40
+ /**
41
+ * CLI entry-point wrapper: prints a header, awaits `fn`, and turns thrown errors into a red message
42
+ * with a non-zero exit code (without aborting the process synchronously, so unfinished stdout flushes).
43
+ */
44
+ export declare function runCli(name: string, fn: () => Promise<void>): Promise<void>;
45
+ export {};
18
46
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/bin/utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAqBlC;AAED,eAAO,MAAM,cAAc;;;;;;;;CAQjB,CAAC;AAEX,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAU,GAChB,IAAI,CAQN;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,IAAI,CAE7D;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/bin/utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAqBlD;AAED,eAAO,MAAM,cAAc;;;;;;;;CAQjB,CAAC;AAEX,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAU,GAChB,IAAI,CAQN;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,IAAI,CAE7D;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExC;AAED,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,KAAK,QAAQ,CAAC,CAAC,SAAS,OAAO,IAC7B,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,GAAG,OAAO,GAClC,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,GACnC,MAAM,GAAG,SAAS,CAAC;AAErB;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,IAAI,EAAE,CAAC,EACP,IAAI,WAAwB,GAC3B;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG;IAAE,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC/D;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF"}
package/dist/bin/utils.js CHANGED
@@ -4,8 +4,8 @@ import { resolve } from 'node:path';
4
4
  * Load .env file from cwd if it exists.
5
5
  * Does not override already-set environment variables.
6
6
  */
7
- export function loadEnvFile() {
8
- const envPath = resolve(process.cwd(), '.env');
7
+ export function loadEnvFile(envFile) {
8
+ const envPath = resolve(process.cwd(), envFile || '.env');
9
9
  if (!existsSync(envPath))
10
10
  return;
11
11
  const content = readFileSync(envPath, 'utf-8');
@@ -52,3 +52,72 @@ export function display(text, color = 0) {
52
52
  export function error(text) {
53
53
  display(text, CONSOLE_COLORS.red);
54
54
  }
55
+ /**
56
+ * Tiny declarative argv parser for the bundled CLIs.
57
+ * - `value` → recognized as `--name X`, last value wins
58
+ * - `flag` → recognized as `--name`, default false
59
+ * - `list` → comma-separated `--name a,b,c`; can also accept positional comma-/space- separated tokens
60
+ *
61
+ * Positional tokens (non-`--`) are accumulated into `positional`. List specs auto-merge those tokens
62
+ * (split on ',') into the list as well, supporting both "a b" and "a,b" CLI shapes.
63
+ */
64
+ export function parseArgs(spec, argv = process.argv.slice(2)) {
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ const out = { positional: [] };
67
+ for (const [name, def] of Object.entries(spec)) {
68
+ out[name] = def.type === 'flag' ? false : def.type === 'list' ? [] : undefined;
69
+ }
70
+ for (let i = 0; i < argv.length; i++) {
71
+ const token = argv[i];
72
+ if (token.startsWith('--')) {
73
+ const name = token.slice(2);
74
+ const def = spec[name];
75
+ if (!def)
76
+ continue;
77
+ if (def.type === 'flag') {
78
+ out[name] = true;
79
+ }
80
+ else if (def.type === 'value' && i + 1 < argv.length) {
81
+ out[name] = argv[++i];
82
+ }
83
+ else if (def.type === 'list' && i + 1 < argv.length) {
84
+ const next = argv[++i];
85
+ for (const part of next.split(',')) {
86
+ const t = part.trim();
87
+ if (t)
88
+ out[name].push(t);
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ out.positional.push(token);
94
+ // Auto-distribute positional values into the (single) list spec, if any
95
+ for (const [n, def] of Object.entries(spec)) {
96
+ if (def.type === 'list') {
97
+ for (const part of token.split(',')) {
98
+ const t = part.trim();
99
+ if (t)
100
+ out[n].push(t);
101
+ }
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ return out;
109
+ }
110
+ /**
111
+ * CLI entry-point wrapper: prints a header, awaits `fn`, and turns thrown errors into a red message
112
+ * with a non-zero exit code (without aborting the process synchronously, so unfinished stdout flushes).
113
+ */
114
+ export async function runCli(name, fn) {
115
+ display(`++++++ ${name} ++++++`, CONSOLE_COLORS.yellow);
116
+ try {
117
+ await fn();
118
+ }
119
+ catch (e) {
120
+ error(`Error: ${e.message ?? String(e)}`);
121
+ process.exitCode = 1;
122
+ }
123
+ }
package/dist/index.d.ts CHANGED
@@ -30,5 +30,5 @@ export { BulkDeleteTableBody, BulkDeleteTableResponse } from './lib/schema/bulk-
30
30
  export { default as bulkDeleteRoutes } from './routes/auto/bulk-delete.routes.js';
31
31
  export { Type, type Static } from '@sinclair/typebox';
32
32
  export { ConditionBuilder, Expression } from 'node-condition-builder';
33
- export type { SqlApiConfig, ColumnInfo, TableMap, Queryable, SqlResult, DbRecord, DbRecordValue, SelectOptions, SchemaDefinition, JoinDefinition, ITable, DbTables, SqlApiPluginOptions, SwaggerOptions, SearchParams, SearchResult, Paginator, PaginationResult, TableFilterFn, ExtendedConditionFn, FilterRecord, AggregationRequest, JoinGroupRequest, GetParams, GetResult, DeleteParams, DeleteResult, InsertParams, InsertResult, BulkUpsertItem, BulkUpsertParams, BulkUpsertResult, BulkDeleteParams, BulkDeleteResult, ConditionMethod, SearchCondition, UpdateParams, UpdateResult, TenantId, TenantScope, TenantScopeDirect, TenantScopeIndirect, TenantContext, ValidationError, ValidatorFn, BulkValidatorItem, BulkValidatorFn, } from './types.js';
33
+ export type { SqlApiConfig, ColumnInfo, TableMap, Queryable, SqlResult, DbRecord, DbRecordValue, SelectOptions, SchemaDefinition, JoinDefinition, ITable, DbTables, SqlApiPluginOptions, SwaggerOptions, SearchParams, SearchResult, Paginator, PaginationResult, TableFilterFn, ExtendedConditionFn, FilterRecord, AggregationRequest, JoinGroupRequest, ComputedFieldFn, ComputedFieldContext, ComputedFieldExpr, GetParams, GetResult, DeleteParams, DeleteResult, InsertParams, InsertResult, BulkUpsertItem, BulkUpsertParams, BulkUpsertResult, BulkDeleteParams, BulkDeleteResult, ConditionMethod, SearchCondition, UpdateParams, UpdateResult, TenantId, TenantScope, TenantScopeDirect, TenantScopeIndirect, TenantContext, ValidationError, ValidatorFn, BulkValidatorItem, BulkValidatorFn, } from './types.js';
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -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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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Load an optional peer dependency by trying the consumer's CWD first
3
+ * (so it works when installed via `npm install`/`npm link`), then falling
4
+ * back to the plugin's own `node_modules` (so it works in the plugin's own tests).
5
+ * Throws a friendly error if neither succeeds.
6
+ */
7
+ export declare function loadOptionalDependency<T = unknown>(modulePath: string, installHint: string): T;
8
+ //# sourceMappingURL=load-dependency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-dependency.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/load-dependency.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,CAAC,CAW9F"}
@@ -0,0 +1,21 @@
1
+ import { createRequire } from 'node:module';
2
+ /**
3
+ * Load an optional peer dependency by trying the consumer's CWD first
4
+ * (so it works when installed via `npm install`/`npm link`), then falling
5
+ * back to the plugin's own `node_modules` (so it works in the plugin's own tests).
6
+ * Throws a friendly error if neither succeeds.
7
+ */
8
+ export function loadOptionalDependency(modulePath, installHint) {
9
+ try {
10
+ return createRequire(process.cwd() + '/noop.js')(modulePath);
11
+ }
12
+ catch {
13
+ /* fall through to local require */
14
+ }
15
+ try {
16
+ return createRequire(import.meta.url)(modulePath);
17
+ }
18
+ catch {
19
+ throw new Error(`${modulePath} is required. Install it with: ${installHint}`);
20
+ }
21
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"mysql-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/mysql-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAgB,0BAA0B,IAAI;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAmBA;AAkDD,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAClG,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAiCvB"}
1
+ {"version":3,"file":"mysql-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/mysql-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAgB,0BAA0B,IAAI;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAmBA;AAkDD,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAClG,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAyBvB"}