fastify-auto-sqlapi 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS_BACKEND.md CHANGED
@@ -184,6 +184,8 @@ await app.register(async (instance) => {
184
184
 
185
185
  **Note**: After registering the plugin, `app.sqlApi` is available for custom routes — see [SqlApi](#sqlapi--programmatic-high-level-api).
186
186
 
187
+ **Note**: Granular route plugins create their own `sqlApi` decorator automatically, so they work standalone (no main plugin required). The decorator is scoped to each route plugin: if you also need `app.sqlApi` in your own routes, register the main plugin.
188
+
187
189
  ---
188
190
 
189
191
  ## defineTable() — Complete Reference
@@ -234,8 +236,12 @@ export const TableCustomer = defineTable({
234
236
  ...exportTableInfo(Schema), // Schema + auto-filter builder
235
237
 
236
238
  // OPTIONAL
237
- defaultOrder: 'name', // ORDER BY default (supports multi: 'name ASC, id DESC')
239
+ defaultOrder: 'name', // ORDER BY default. camelCase fields are mapped to DB
240
+ // columns (e.g. 'squadIndex' -> "squad_index"); supports
241
+ // multi ('name ASC, id DESC'), computed fields, and raw
242
+ // SQL fragments (unknown tokens pass through unchanged)
238
243
  excludeFromCreation: ['id'], // Fields omitted from INSERT — MUST include auto-increment PKs
244
+ readExclude: ['passwordHash'], // Fields hidden from ALL reads (writes unaffected)
239
245
  distinctResults: true, // Use SELECT DISTINCT
240
246
 
241
247
  // JOINS — relations to other tables. Alias defaults to joinSchema.tableName —
@@ -757,6 +763,7 @@ Tables without `tenantScope` are unaffected — no filtering regardless of `getT
757
763
  - **camelCase in API, any case in DB**: all request/response fields are camelCase. The plugin converts via `col()` and `colMap`. For snake_case DB columns (default), conversion is automatic. For camelCase DB columns (e.g. betterauth), the CLI generates a `colMap` that preserves the original column names — no conversion needed. Manual schemas without `colMap` fall back to `toUnderscore()`.
758
764
  - **All fields Optional in response**: `RETURNING *` may return any subset. Response schemas use `Type.Partial`.
759
765
  - **`excludeFromCreation`**: **IMPORTANT** — auto-increment PKs (e.g. `id` serial/auto_increment) MUST be listed here, otherwise INSERT will try to send them and fail. The CLI auto-detects this and adds it by default. Also useful for `createdAt`/`updatedAt` columns managed by DB defaults or hooks.
766
+ - **`readExclude`**: fields hidden from every read — not projected by search/get, omitted from read response schemas and from this table's default (`*`) join selection when it is a join target. Referencing one from `filters`, `conditions`, `orderBy`, `joinGroup` aggregations or an explicit join `selection` returns 400: hiding a field from output while allowing it to be filtered would leak its value by bisection. **Writes are unaffected** — insert/update/bulk still accept the field, so a column can be writable but never readable (password hash, access token). Primary keys cannot be excluded. Complementary to trimming the Schema, which removes the column from reads *and* writes.
760
767
  - **`upsertMap`**: when present for a schema, INSERT becomes upsert. PostgreSQL: `ON CONFLICT (...) DO UPDATE`. MySQL/MariaDB: `ON DUPLICATE KEY UPDATE`. Applies to both main and secondary tables.
761
768
  - **`schemaOverrides`**: override auto-generated schema fields with stricter TypeBox types (e.g. `{ email: Type.String({ format: 'email' }) }`). Overrides are merged into the body schema for insert, update, and bulk-upsert. The original Schema file is never modified. In updates, overridden fields are still wrapped in Optional (validates only when present). Overrides appear in Swagger.
762
769
  - **Validation receives camelCase**: `validate` receives the original camelCase record (as sent by the client) and secondaries. Field names match the schema definition, with full TypeScript inference (`main.startDate`, not `main.start_date`). It returns `ValidationError[]` — tuples of `[field, code]` or `[field, code, message]`. If any errors are returned, the request is rejected with 400 before hooks or SQL execute.
package/README.md CHANGED
@@ -143,8 +143,9 @@ const TableCustomer = defineTable({
143
143
  ...exportTableInfo(SchemaCustomer),
144
144
 
145
145
  // Ordering & filtering
146
- defaultOrder: 'name', // default ORDER BY
146
+ defaultOrder: 'name', // default ORDER BY (camelCase fields map to DB columns)
147
147
  excludeFromCreation: ['id'], // omit from INSERT (e.g. auto-increment)
148
+ readExclude: ['passwordHash'], // hide from all reads (writes unaffected)
148
149
  distinctResults: true, // SELECT DISTINCT
149
150
 
150
151
  // Relations — alias defaults to joinSchema.tableName. Override with `{ alias: '...' }`
@@ -320,6 +321,21 @@ only columns present in the generated Schema (as narrowed by `schemaOverrides` /
320
321
  column — the Schema is the write whitelist. Trim the generated Schema (or use `excludeFromCreation`)
321
322
  to keep sensitive columns out of it.
322
323
 
324
+ ### Read visibility
325
+
326
+ `readExclude` hides columns from every read while leaving writes untouched — the case for a
327
+ password hash or an access token: writable, never readable.
328
+
329
+ ```typescript
330
+ readExclude: ['accessToken'],
331
+ ```
332
+
333
+ Excluded fields are not selected by search/get, are omitted from read response schemas and from
334
+ the table's default join selection, and cannot be referenced from `filters`, `conditions`,
335
+ `orderBy`, aggregations or an explicit join `selection` (`400`). That last part is the point:
336
+ letting a hidden field be filtered would leak its value by bisection. Primary keys cannot be
337
+ excluded.
338
+
323
339
  ## API Reference
324
340
 
325
341
  ### POST /search/{table}
@@ -20,7 +20,7 @@ function printHelp() {
20
20
  console.log(' sqlapi-generate-tables --all');
21
21
  console.log('');
22
22
  display('Table*.ts files are only created if they do not already exist.', CONSOLE_COLORS.magenta);
23
- display('dbTables.ts is always regenerated to include all schemas.', CONSOLE_COLORS.magenta);
23
+ display('dbTables.ts is created if missing, indexing the Table*.ts files present on disk.', CONSOLE_COLORS.magenta);
24
24
  }
25
25
  await runCli('fastify-auto-sqlapi: generating tables template', async () => {
26
26
  const cliArgs = parseArgs({
@@ -84,9 +84,11 @@ await runCli('fastify-auto-sqlapi: generating tables template', async () => {
84
84
  }
85
85
  }
86
86
  }
87
+ const tableVarOf = (schema) => 'Table' + schema.schemaName.replace(/^Schema/, '');
87
88
  // Generate individual Table*.ts files (skip if already exists)
89
+ const createdTableVars = [];
88
90
  for (const schema of targetSchemas) {
89
- const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
91
+ const tableVarName = tableVarOf(schema);
90
92
  const tableFile = path.join(tablesDir, `${tableVarName}.ts`);
91
93
  if (fs.existsSync(tableFile)) {
92
94
  displayAsTableRow(`${tableVarName}.ts`, 'skipped (already exists)', 60, CONSOLE_COLORS.gray);
@@ -94,17 +96,29 @@ await runCli('fastify-auto-sqlapi: generating tables template', async () => {
94
96
  else {
95
97
  const content = generateSingleTableFile(schema, allSchemas);
96
98
  fs.writeFileSync(tableFile, content);
99
+ createdTableVars.push(tableVarName);
97
100
  displayAsTableRow(`${tableVarName}.ts`, 'created', 60, CONSOLE_COLORS.green);
98
101
  }
99
102
  }
100
- // Generate dbTables.ts only if it does not exist
103
+ // Generate dbTables.ts only if it does not exist. The index must reference only
104
+ // Table*.ts files actually present on disk, otherwise a subset run would emit
105
+ // imports for files that were never generated and the project would not compile.
101
106
  const dbTablesFile = path.join(tablesDir, 'dbTables.ts');
102
107
  if (fs.existsSync(dbTablesFile)) {
103
108
  displayAsTableRow('dbTables.ts', 'skipped (already exists)', 60, CONSOLE_COLORS.gray);
109
+ const indexContent = fs.readFileSync(dbTablesFile, 'utf-8');
110
+ const missing = createdTableVars.filter((v) => !indexContent.includes(v));
111
+ if (missing.length > 0) {
112
+ display(`Remember to add the new tables to dbTables.ts: ${missing.join(', ')}`, CONSOLE_COLORS.yellow);
113
+ }
104
114
  }
105
115
  else {
106
- const dbTablesContent = generateDbTablesIndex(allSchemas);
107
- fs.writeFileSync(dbTablesFile, dbTablesContent);
116
+ const existingTableVars = new Set(fs
117
+ .readdirSync(tablesDir)
118
+ .filter((f) => f.startsWith('Table') && f.endsWith('.ts'))
119
+ .map((f) => f.slice(0, -'.ts'.length)));
120
+ const indexSchemas = allSchemas.filter((s) => existingTableVars.has(tableVarOf(s)));
121
+ fs.writeFileSync(dbTablesFile, generateDbTablesIndex(indexSchemas));
108
122
  displayAsTableRow('dbTables.ts', 'created', 60, CONSOLE_COLORS.green);
109
123
  }
110
124
  console.log('');
@@ -1 +1 @@
1
- {"version":3,"file":"tables-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/tables-codegen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAuCpE;AAuCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAID,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,CAwHhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CA6FlE"}
1
+ {"version":3,"file":"tables-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/tables-codegen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAuCpE;AAuCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAID,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,CA2HhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CA6FlE"}
@@ -130,10 +130,10 @@ export function generateSingleTableFile(schema, allSchemas) {
130
130
  // Imports
131
131
  lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
132
132
  lines.push(`import type {ValidationError} from 'fastify-auto-sqlapi';`);
133
- lines.push(`import {${schema.schemaName} as Schema} from '../schemas/${schema.schemaName}';`);
133
+ lines.push(`import {${schema.schemaName} as Schema} from '../schemas/${schema.schemaName}.js';`);
134
134
  // Commented imports for related schemas
135
135
  for (const relSchema of relatedSchemas) {
136
- lines.push(`// import {${relSchema}} from '../schemas/${relSchema}';`);
136
+ lines.push(`// import {${relSchema}} from '../schemas/${relSchema}.js';`);
137
137
  }
138
138
  lines.push(``);
139
139
  // Extra filters placeholder
@@ -161,6 +161,8 @@ export function generateSingleTableFile(schema, allSchemas) {
161
161
  else {
162
162
  lines.push(` // excludeFromCreation: [],`);
163
163
  }
164
+ // Hide columns from every read (writes still accept them, e.g. a password hash)
165
+ lines.push(` // readExclude: [],`);
164
166
  if (parentRels.length > 0) {
165
167
  lines.push(` // allowedReadJoins: [`);
166
168
  for (const rel of parentRels) {
@@ -213,7 +215,7 @@ export function generateDbTablesIndex(schemas) {
213
215
  lines.push(``);
214
216
  for (const schema of schemas) {
215
217
  const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
216
- lines.push(`import {${tableVarName}} from './${tableVarName}';`);
218
+ lines.push(`import {${tableVarName}} from './${tableVarName}.js';`);
217
219
  }
218
220
  lines.push(``);
219
221
  lines.push(`export const dbTables: DbTables = {`);
@@ -245,7 +247,7 @@ export function generateTablesFile(schemas) {
245
247
  lines.push(``);
246
248
  // Schema imports
247
249
  for (const schema of schemas) {
248
- lines.push(`import {${schema.schemaName}} from './${schema.schemaName}';`);
250
+ lines.push(`import {${schema.schemaName}} from './${schema.schemaName}.js';`);
249
251
  }
250
252
  // Table definitions
251
253
  for (const schema of schemas) {
@@ -1 +1 @@
1
- {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/get.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAuB,MAAM,mBAAmB,CAAC;AAEnF,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CA6BrE"}
1
+ {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/get.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAuB,MAAM,mBAAmB,CAAC;AAEnF,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CA8BrE"}
@@ -2,6 +2,7 @@ import { ConditionBuilder } from 'node-condition-builder';
2
2
  import { camelcaseObject } from '../../naming.js';
3
3
  import { buildTenantCondition, buildTenantJoin } from '../../tenant.js';
4
4
  import { httpError } from '../../errors.js';
5
+ import { readableSelectColumns } from '../../read-access.js';
5
6
  import { primaryAsString } from '../../../types.js';
6
7
  export async function getEngine(params) {
7
8
  const { db, tableConf, id, tenant } = params;
@@ -19,6 +20,7 @@ export async function getEngine(params) {
19
20
  const values = cb.getValues();
20
21
  const rows = await db.select({
21
22
  tableName: tableConf.Schema.tableName,
23
+ columns: readableSelectColumns(tableConf, tableConf.Schema, db),
22
24
  where,
23
25
  values,
24
26
  limit: '1',
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAcb,MAAM,mBAAmB,CAAC;AA4jC3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CA2IvB"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAcb,MAAM,mBAAmB,CAAC;AA+nC3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CA4IvB"}
@@ -1,14 +1,16 @@
1
1
  import { ConditionBuilder } from 'node-condition-builder';
2
2
  import { camelcaseObject } from '../../naming.js';
3
3
  import { buildTenantCondition, buildTenantJoin } from '../../tenant.js';
4
+ import { assertReadable, assertFiltersReadable, readableSelectColumns } from '../../read-access.js';
4
5
  import { primaryAsString } from '../../../types.js';
5
6
  // ─── Schema field validation ────────────────────────────────
6
- function validateSchemaField(field, schema) {
7
+ function validateSchemaField(field, schema, tableConf) {
7
8
  if (!(field in schema.fields)) {
8
9
  const err = new Error(`Unknown field: ${field}`);
9
10
  err.statusCode = 400;
10
11
  throw err;
11
12
  }
13
+ assertReadable(tableConf, field);
12
14
  return schema.col(field);
13
15
  }
14
16
  function err400(msg) {
@@ -37,6 +39,7 @@ function buildComputedContext(db, schema, alias) {
37
39
  }
38
40
  function resolveFieldRef(field, schema, tableConf, db, alias) {
39
41
  if (field in schema.fields) {
42
+ assertReadable(tableConf, field);
40
43
  const col = db.qi(schema.col(field));
41
44
  const expr = alias ? `${db.qi(alias)}.${col}` : col;
42
45
  return { expr, values: [], computed: false };
@@ -121,11 +124,19 @@ function mapRowsToCamelCase(rows, schema, computedFieldNames) {
121
124
  return camel;
122
125
  });
123
126
  }
124
- /** Build the SELECT columns list for a join fetch: '*' or comma-separated quoted columns. */
125
- function buildSelectionColumns(selection, joinSchema, db) {
126
- if (selection === '*')
127
- return '*';
128
- return selection.split(',').map((c) => db.qi(joinSchema.col(c.trim()))).join(', ');
127
+ /**
128
+ * Build the SELECT columns list for a join fetch: '*' or comma-separated quoted columns.
129
+ * A default '*' selection narrows to the join table's readable columns when it declares
130
+ * `readExclude`; an explicit selection naming an excluded field is rejected with 400.
131
+ */
132
+ function buildSelectionColumns(selection, joinSchema, db, joinTableConf) {
133
+ if (selection === '*') {
134
+ return readableSelectColumns(joinTableConf, joinSchema, db) ?? '*';
135
+ }
136
+ return selection
137
+ .split(',')
138
+ .map((c) => db.qi(validateSchemaField(c.trim(), joinSchema, joinTableConf)))
139
+ .join(', ');
129
140
  }
130
141
  const AGG_FN = {
131
142
  sum: { sql: 'SUM', distinct: false },
@@ -219,7 +230,7 @@ function validateOrderBy(orderBy, tableConf, db, dbTables, joinGroup, startIdx)
219
230
  if (dotted2) {
220
231
  const [, alias, field, dir] = dotted2;
221
232
  const joinDef = requireJoin(tableConf, alias, true);
222
- const col = validateSchemaField(field, joinDef.joinSchema);
233
+ const col = validateSchemaField(field, joinDef.joinSchema, dbTables[joinDef.joinSchema.tableName]);
223
234
  // Reference the LEFT JOIN'd table via its alias (SQL identifier).
224
235
  outParts.push(`${db.qi(alias)}.${db.qi(col)} ${(dir || 'ASC').toUpperCase()}`);
225
236
  leftJoinAliases.add(alias);
@@ -259,6 +270,7 @@ function buildLeftJoinClauses(db, dbTables, tableConf, aliasesNeedingJoin, joinL
259
270
  const cb = new ConditionBuilder('AND', db.cbDialect);
260
271
  const joinTableConf = dbTables[joinSchema.tableName];
261
272
  const computed = joinTableConf?.computedFields;
273
+ assertFiltersReadable(ref.filters, joinTableConf);
262
274
  if (ref.filters) {
263
275
  for (const [field, value] of Object.entries(ref.filters)) {
264
276
  if (value === null || value === undefined)
@@ -279,6 +291,7 @@ function buildLeftJoinClauses(db, dbTables, tableConf, aliasesNeedingJoin, joinL
279
291
  err400(`Invalid condition method: ${c.method}`);
280
292
  }
281
293
  if (c.field in joinSchema.fields) {
294
+ assertReadable(dbTables[joinSchema.tableName], c.field);
282
295
  const col = `${aliasIdent}.${db.qi(joinSchema.col(c.field))}`;
283
296
  dispatchConditionMethod(cb, c.method, col, c.params ?? []);
284
297
  }
@@ -303,19 +316,52 @@ function buildLeftJoinClauses(db, dbTables, tableConf, aliasesNeedingJoin, joinL
303
316
  return { joinClauses, whereExtras, values };
304
317
  }
305
318
  // ─── Main query execution ───────────────────────────────────
319
+ /**
320
+ * Converts a configured order (`defaultOrder` or the primary-key fallback) to SQL.
321
+ * Unlike the request `orderBy` (strictly validated by `validateOrderBy`), this is
322
+ * lenient for backward compatibility: tokens matching a camelCase schema field are
323
+ * mapped to their quoted DB column, computed fields (without bound values) expand
324
+ * to their expression, and anything else passes through unchanged as raw SQL.
325
+ */
326
+ function convertConfiguredOrder(order, tableConf, db) {
327
+ return order
328
+ .split(',')
329
+ .map((part) => {
330
+ const trimmed = part.trim();
331
+ const match = trimmed.match(/^(\w+)(?:\s+(ASC|DESC))?$/i);
332
+ if (match) {
333
+ const [, field, dir] = match;
334
+ const suffix = dir ? ` ${dir.toUpperCase()}` : '';
335
+ if (field in tableConf.Schema.fields) {
336
+ return `${db.qi(tableConf.Schema.col(field))}${suffix}`;
337
+ }
338
+ const computed = tableConf.computedFields?.[field];
339
+ if (computed) {
340
+ const ev = evaluateComputedField(computed, tableConf.Schema, db, undefined, `Computed field '${field}' with bound values cannot be used in defaultOrder`);
341
+ return `${ev.expr}${suffix}`;
342
+ }
343
+ }
344
+ return trimmed;
345
+ })
346
+ .join(', ');
347
+ }
306
348
  async function executeMainQuery(db, tableConf, where, values, orderBy, paginator, extraJoins = [], selectComputed, maxRows) {
307
349
  const tableName = tableConf.Schema.tableName;
308
- const order = orderBy || tableConf.defaultOrder || primaryAsString(tableConf.primary);
350
+ // Request orderBy arrives already validated and mapped by validateOrderBy;
351
+ // configured fallbacks (defaultOrder / primary key) are converted leniently here.
352
+ const order = orderBy || convertConfiguredOrder(tableConf.defaultOrder || primaryAsString(tableConf.primary), tableConf, db);
309
353
  // With a paginator, the page size governs the LIMIT. Without one, apply `maxRows` (if set) so an
310
354
  // unbounded search cannot dump the whole table; a plain integer is a safe, non-injectable LIMIT.
311
355
  const limit = paginator
312
356
  ? `${paginator.itemsPerPage} OFFSET ${(paginator.page - 1) * paginator.itemsPerPage}`
313
357
  : (maxRows != null ? String(maxRows) : null);
358
+ // Base projection: '*' unless the table hides fields via readExclude.
359
+ const readableColumns = readableSelectColumns(tableConf, tableConf.Schema, db);
314
360
  // Optional computed projections — bound values, if any, are NOT yet supported
315
361
  // here (would require placeholder-aware composition with WHERE values).
316
- let columns;
362
+ let columns = readableColumns;
317
363
  if (selectComputed?.length) {
318
- const projections = ['*'];
364
+ const projections = [readableColumns ?? '*'];
319
365
  for (const name of selectComputed) {
320
366
  const fn = tableConf.computedFields?.[name];
321
367
  if (!fn)
@@ -391,7 +437,7 @@ async function executeJoinMultiple(db, dbTables, tableConf, mainResults, joinMul
391
437
  ({ where, values } = appendComputedConditions(where, values, computedClauses, db));
392
438
  }
393
439
  const selection = ref?.selection ?? defaultSelection;
394
- const columns = buildSelectionColumns(selection, joinSchema, db);
440
+ const columns = buildSelectionColumns(selection, joinSchema, db, joinTableConf);
395
441
  const rows = await db.select({
396
442
  tableName: joinSchema.tableName,
397
443
  columns,
@@ -425,7 +471,7 @@ async function executeJoinLeft(db, dbTables, tableConf, mainResults, joinLeft, t
425
471
  const where = cb.build(1, db.ph);
426
472
  const values = cb.getValues();
427
473
  const selection = ref?.selection ?? defaultSelection;
428
- const columns = buildSelectionColumns(selection, joinSchema, db);
474
+ const columns = buildSelectionColumns(selection, joinSchema, db, joinTableConf);
429
475
  const rows = await db.select({
430
476
  tableName: joinSchema.tableName,
431
477
  columns,
@@ -482,7 +528,7 @@ async function executeJoinGroup(db, dbTables, tableConf, mainResults, joinGroup,
482
528
  return;
483
529
  const fn = AGG_FN[kind];
484
530
  for (const f of fields) {
485
- const col = validateSchemaField(f, joinSchema);
531
+ const col = validateSchemaField(f, joinSchema, joinTableConf);
486
532
  selectParts.push(`${aggExpr(fn, db.qi(col))} as "${kind}_${f}"`);
487
533
  }
488
534
  };
@@ -568,6 +614,7 @@ function dispatchConditionMethod(cb, method, colOrExpr, params) {
568
614
  }
569
615
  }
570
616
  function buildJoinRefCondition(joinTableConf, joinSchema, ref, db) {
617
+ assertFiltersReadable(ref.filters, joinTableConf);
571
618
  const cb = (ref.filters && joinTableConf)
572
619
  ? joinTableConf.filters(ref.filters, db.cbDialect)
573
620
  : new ConditionBuilder('AND', db.cbDialect);
@@ -581,7 +628,7 @@ function buildJoinRefCondition(joinTableConf, joinSchema, ref, db) {
581
628
  // for an LHS expression.
582
629
  if (joinTableConf?.computedFields?.[c.field])
583
630
  continue;
584
- const col = db.qi(validateSchemaField(c.field, joinSchema));
631
+ const col = db.qi(validateSchemaField(c.field, joinSchema, joinTableConf));
585
632
  dispatchConditionMethod(cb, c.method, col, c.params ?? []);
586
633
  }
587
634
  }
@@ -778,6 +825,7 @@ export async function searchEngine(dbTables, params) {
778
825
  for (const a of Object.keys(joinLeft))
779
826
  requireJoin(tableConf, a, true);
780
827
  // Build main condition
828
+ assertFiltersReadable(filters, tableConf);
781
829
  const condition = tableConf.filters(filters || {}, db.cbDialect);
782
830
  // Filters that target computed fields (declared on tableConf.computedFields)
783
831
  // are applied via the side-channel pattern further down.
@@ -0,0 +1,31 @@
1
+ import type { QueryClient } from './db.js';
2
+ import type { ITable, SchemaDefinition } from '../types.js';
3
+ /**
4
+ * Read visibility (`ITable.readExclude`).
5
+ *
6
+ * Excluded fields are never projected by any read query and cannot be referenced
7
+ * from filters, conditions, orderBy, aggregations or join selections: allowing a
8
+ * field to be filtered while hiding it from the output would still leak its value
9
+ * by bisection. Write paths (insert/update/upsert) are deliberately unaffected —
10
+ * a field can be writable but never readable (e.g. a password hash).
11
+ */
12
+ export declare function isReadExcluded(tableConf: ITable | undefined, field: string): boolean;
13
+ /** Throws 400 when `field` is read-excluded on `tableConf`. */
14
+ export declare function assertReadable(tableConf: ITable | undefined, field: string): void;
15
+ /**
16
+ * Throws 400 when a filter map targets a read-excluded field. Keys that are not
17
+ * excluded pass through untouched (extraFilters and computed names included).
18
+ */
19
+ export declare function assertFiltersReadable(filters: Record<string, unknown> | undefined, tableConf: ITable | undefined): void;
20
+ /** Schema field names that may be read, in schema declaration order. */
21
+ export declare function readableFieldNames(tableConf: ITable | undefined, schema: SchemaDefinition): string[];
22
+ /**
23
+ * SELECT column list for a table with read exclusions, or `undefined` when the
24
+ * table has none (callers keep their existing `*` projection).
25
+ *
26
+ * Columns are qualified with the table name: read queries may carry a tenant
27
+ * `through` INNER JOIN, where a bare column name shared with the through table
28
+ * would be ambiguous.
29
+ */
30
+ export declare function readableSelectColumns(tableConf: ITable | undefined, schema: SchemaDefinition, db: QueryClient): string | undefined;
31
+ //# sourceMappingURL=read-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-access.d.ts","sourceRoot":"","sources":["../../src/lib/read-access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG5D;;;;;;;;GAQG;AAEH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpF;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIjF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,IAAI,CAKN;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,gBAAgB,GACvB,MAAM,EAAE,CAIV;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,gBAAgB,EACxB,EAAE,EAAE,WAAW,GACd,MAAM,GAAG,SAAS,CAMpB"}
@@ -0,0 +1,53 @@
1
+ import { httpError } from './errors.js';
2
+ /**
3
+ * Read visibility (`ITable.readExclude`).
4
+ *
5
+ * Excluded fields are never projected by any read query and cannot be referenced
6
+ * from filters, conditions, orderBy, aggregations or join selections: allowing a
7
+ * field to be filtered while hiding it from the output would still leak its value
8
+ * by bisection. Write paths (insert/update/upsert) are deliberately unaffected —
9
+ * a field can be writable but never readable (e.g. a password hash).
10
+ */
11
+ export function isReadExcluded(tableConf, field) {
12
+ return tableConf?.readExclude?.includes(field) ?? false;
13
+ }
14
+ /** Throws 400 when `field` is read-excluded on `tableConf`. */
15
+ export function assertReadable(tableConf, field) {
16
+ if (isReadExcluded(tableConf, field)) {
17
+ throw httpError(400, `Field is not readable: ${field}`);
18
+ }
19
+ }
20
+ /**
21
+ * Throws 400 when a filter map targets a read-excluded field. Keys that are not
22
+ * excluded pass through untouched (extraFilters and computed names included).
23
+ */
24
+ export function assertFiltersReadable(filters, tableConf) {
25
+ if (!filters || !tableConf?.readExclude?.length)
26
+ return;
27
+ for (const key of Object.keys(filters)) {
28
+ assertReadable(tableConf, key);
29
+ }
30
+ }
31
+ /** Schema field names that may be read, in schema declaration order. */
32
+ export function readableFieldNames(tableConf, schema) {
33
+ const fields = Object.keys(schema.fields);
34
+ if (!tableConf?.readExclude?.length)
35
+ return fields;
36
+ return fields.filter((f) => !isReadExcluded(tableConf, f));
37
+ }
38
+ /**
39
+ * SELECT column list for a table with read exclusions, or `undefined` when the
40
+ * table has none (callers keep their existing `*` projection).
41
+ *
42
+ * Columns are qualified with the table name: read queries may carry a tenant
43
+ * `through` INNER JOIN, where a bare column name shared with the through table
44
+ * would be ambiguous.
45
+ */
46
+ export function readableSelectColumns(tableConf, schema, db) {
47
+ if (!tableConf?.readExclude?.length)
48
+ return undefined;
49
+ const table = db.qi(schema.tableName);
50
+ return readableFieldNames(tableConf, schema)
51
+ .map((f) => `${table}.${db.qi(schema.col(f))}`)
52
+ .join(', ');
53
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAY/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAuHlF;AAED,eAAO,MAAM,sBAAsB;;;;;;;;EASjC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAmElF"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAGrE,OAAO,KAAK,EAAE,QAAQ,EAA4B,MAAM,gBAAgB,CAAC;AAyBzE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAuHlF;AAED,eAAO,MAAM,sBAAsB;;;;;;;;EASjC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAsElF"}
@@ -1,5 +1,16 @@
1
1
  import { Type } from '@sinclair/typebox';
2
2
  import { ALLOWED_METHODS } from '../condition-methods.js';
3
+ import { readableFieldNames } from '../read-access.js';
4
+ /** Schema fields minus the ones hidden by `readExclude`. */
5
+ function readableFields(schema, tableConf) {
6
+ if (!tableConf?.readExclude?.length)
7
+ return { ...schema.fields };
8
+ const out = {};
9
+ for (const field of readableFieldNames(tableConf, schema)) {
10
+ out[field] = schema.fields[field];
11
+ }
12
+ return out;
13
+ }
3
14
  const JoinGroupResultItem = Type.Object({
4
15
  sum: Type.Optional(Type.Record(Type.String(), Type.Any())),
5
16
  min: Type.Optional(Type.Record(Type.String(), Type.Any())),
@@ -38,7 +49,7 @@ export function SearchTableBodyPost(dbTables, tableName) {
38
49
  }
39
50
  }
40
51
  const filterFields = {
41
- ...schema.fields,
52
+ ...readableFields(schema, tableConf),
42
53
  ...tableConf.extraFilters,
43
54
  ...computedTypes,
44
55
  };
@@ -71,7 +82,7 @@ export function SearchTableBodyPost(dbTables, tableName) {
71
82
  }
72
83
  }
73
84
  const joinFilterFields = joinTableConf
74
- ? { ...joinSchema.fields, ...joinTableConf.extraFilters, ...joinComputedTypes }
85
+ ? { ...readableFields(joinSchema, joinTableConf), ...joinTableConf.extraFilters, ...joinComputedTypes }
75
86
  : { ...joinSchema.fields };
76
87
  const joinRefShape = {
77
88
  filters: Type.Optional(Type.Partial(Type.Object(joinFilterFields))),
@@ -150,16 +161,17 @@ export function SearchTableResponse(dbTables, tableName) {
150
161
  }
151
162
  }
152
163
  }
164
+ const mainReadable = readableFields(tableConf.Schema, tableConf);
153
165
  const mainItem = Object.keys(computedTypes).length > 0
154
- ? Type.Partial(Type.Object({ ...tableConf.Schema.fields, ...computedTypes }))
155
- : Type.Partial(Type.Object(tableConf.Schema.fields));
166
+ ? Type.Partial(Type.Object({ ...mainReadable, ...computedTypes }))
167
+ : Type.Partial(Type.Object(mainReadable));
156
168
  const joinMultipleProps = {};
157
169
  const joinLeftProps = {};
158
170
  const joinGroupProps = {};
159
171
  if (tableConf.allowedReadJoins) {
160
172
  for (const joinDef of tableConf.allowedReadJoins) {
161
173
  const { joinSchema, alias, unique } = joinDef;
162
- const itemArray = Type.Array(Type.Partial(Type.Object(joinSchema.fields)));
174
+ const itemArray = Type.Array(Type.Partial(Type.Object(readableFields(joinSchema, dbTables[joinSchema.tableName]))));
163
175
  if (unique) {
164
176
  joinLeftProps[alias] = itemArray;
165
177
  }
@@ -0,0 +1,18 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type SqlApi } from './sql-api.js';
3
+ import type { SqlApiPluginOptions } from '../types.js';
4
+ declare module 'fastify' {
5
+ interface FastifyInstance {
6
+ sqlApi: SqlApi;
7
+ }
8
+ }
9
+ /**
10
+ * Decorates `fastify.sqlApi` (lazy getter) on the given instance unless the
11
+ * decorator is already present in scope. The main plugin calls this at the
12
+ * fp level (parent scope); granular route plugins call it through
13
+ * `registerForAllTables` so they also work standalone, each within its own
14
+ * encapsulation context. The underlying pool (`fastify.pg` / `fastify.mysql`)
15
+ * is shared either way.
16
+ */
17
+ export declare function ensureSqlApiDecorator(fastify: FastifyInstance, options: SqlApiPluginOptions): void;
18
+ //# sourceMappingURL=sql-api-decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-api-decorator.d.ts","sourceRoot":"","sources":["../../src/lib/sql-api-decorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,eAAe;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,IAAI,CA2BN"}
@@ -0,0 +1,40 @@
1
+ import { createQueryClient } from './db.js';
2
+ import { pgQueryable } from './adapters/pg-adapter.js';
3
+ import { mysqlQueryable } from './adapters/mysql-adapter.js';
4
+ import { createSqlApi } from './sql-api.js';
5
+ /**
6
+ * Decorates `fastify.sqlApi` (lazy getter) on the given instance unless the
7
+ * decorator is already present in scope. The main plugin calls this at the
8
+ * fp level (parent scope); granular route plugins call it through
9
+ * `registerForAllTables` so they also work standalone, each within its own
10
+ * encapsulation context. The underlying pool (`fastify.pg` / `fastify.mysql`)
11
+ * is shared either way.
12
+ */
13
+ export function ensureSqlApiDecorator(fastify, options) {
14
+ if (fastify.hasDecorator('sqlApi'))
15
+ return;
16
+ // Lazy-init QueryClient (internal closure — not decorated on fastify)
17
+ let cachedDb;
18
+ function getDb() {
19
+ if (!cachedDb) {
20
+ const pool = (options.dialect === 'mysql' || options.dialect === 'mariadb')
21
+ ? mysqlQueryable(fastify.mysql)
22
+ : pgQueryable(fastify.pg);
23
+ cachedDb = createQueryClient(pool, options.dialect);
24
+ if (options.debug)
25
+ cachedDb.setDebug(true);
26
+ }
27
+ return cachedDb;
28
+ }
29
+ let cachedSqlApi;
30
+ fastify.decorate('sqlApi', {
31
+ getter() {
32
+ if (!cachedSqlApi) {
33
+ cachedSqlApi = createSqlApi(getDb(), options.DbTables, {
34
+ getTenantId: options.getTenantId,
35
+ });
36
+ }
37
+ return cachedSqlApi;
38
+ },
39
+ });
40
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"table-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/table-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,yBAAyB,EAAiC,MAAM,wBAAwB,CAAC;AACzH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,MAAM,EACP,MAAM,aAAa,CAAC;AAarB,KAAK,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAExH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,EAAE,SAAS,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,EAErE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,YAAY,GAAE,EAAa,EAC3B,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yBAAyB;CAAE,KAC3E,IAAI,GACR;IAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CA0BhG;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAChB,MAAM,CAAC,CAAC,CAAC,CAKX;AA2CD,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,aAAa,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,EAClD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,EAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,cAAc,CAShB;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,GAC5B,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAE9B;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,KAAK,EAAE,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,EAAE,GACvC,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAEjC"}
1
+ {"version":3,"file":"table-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/table-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,yBAAyB,EAAiC,MAAM,wBAAwB,CAAC;AACzH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,MAAM,EACP,MAAM,aAAa,CAAC;AAarB,KAAK,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAExH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,EAAE,SAAS,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,EAErE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,YAAY,GAAE,EAAa,EAC3B,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yBAAyB;CAAE,KAC3E,IAAI,GACR;IAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CA0BhG;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAChB,MAAM,CAAC,CAAC,CAAC,CAMX;AAkED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,aAAa,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,EAClD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,EAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,cAAc,CAShB;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,GAC5B,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAE9B;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,KAAK,EAAE,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,EAAE,GACvC,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAEjC"}
@@ -33,8 +33,26 @@ export function defineTable(config) {
33
33
  validateAliasUniqueness(config.allowedReadJoins, 'allowedReadJoins');
34
34
  validateAliasUniqueness(config.allowedWriteJoins, 'allowedWriteJoins');
35
35
  validateComputedFields(config);
36
+ validateReadExclude(config);
36
37
  return config;
37
38
  }
39
+ function validateReadExclude(config) {
40
+ const excluded = config.readExclude;
41
+ if (!excluded?.length)
42
+ return;
43
+ const schemaFields = Object.keys(config.Schema.fields);
44
+ const pkFields = Array.isArray(config.primary) ? config.primary : [config.primary];
45
+ for (const field of excluded) {
46
+ if (!schemaFields.includes(field)) {
47
+ throw new Error(`defineTable: readExclude field '${field}' is not a schema field on ` +
48
+ `table '${config.Schema.tableName}'.`);
49
+ }
50
+ if (pkFields.includes(field)) {
51
+ throw new Error(`defineTable: readExclude cannot hide the primary key field '${field}' on ` +
52
+ `table '${config.Schema.tableName}' — reads and joins rely on it.`);
53
+ }
54
+ }
55
+ }
38
56
  function validateComputedFields(config) {
39
57
  const computed = config.computedFields;
40
58
  if (!computed)
@@ -1,11 +1,5 @@
1
1
  import type { FastifyInstance } from 'fastify';
2
- import { type SqlApi } from '../../lib/sql-api.js';
3
2
  import type { SqlApiPluginOptions } from '../../types.js';
4
- declare module 'fastify' {
5
- interface FastifyInstance {
6
- sqlApi: SqlApi;
7
- }
8
- }
9
3
  declare const _default: (fastify: FastifyInstance, options: SqlApiPluginOptions) => Promise<void>;
10
4
  export default _default;
11
5
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAO7D,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAC;AASjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,eAAe;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB;CACF;kCAuDU,eAAe,WACf,mBAAmB,KAC3B,OAAO,CAAC,IAAI,CAAC;AAHhB,wBAiFoC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAa7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;kCAuD/C,eAAe,WACf,mBAAmB,KAC3B,OAAO,CAAC,IAAI,CAAC;AAHhB,wBA0DoC"}
@@ -1,10 +1,7 @@
1
1
  import fp from 'fastify-plugin';
2
2
  import { ConditionBuilder } from 'node-condition-builder';
3
3
  import { getDialect } from '../../lib/dialect.js';
4
- import { createQueryClient } from '../../lib/db.js';
5
- import { pgQueryable } from '../../lib/adapters/pg-adapter.js';
6
- import { mysqlQueryable } from '../../lib/adapters/mysql-adapter.js';
7
- import { createSqlApi } from '../../lib/sql-api.js';
4
+ import { ensureSqlApiDecorator } from '../../lib/sql-api-decorator.js';
8
5
  import { setupSwagger } from '../../lib/setup-swagger.js';
9
6
  import searchRoutes from './search.routes.js';
10
7
  import getRoutes from './get.routes.js';
@@ -56,31 +53,8 @@ export default fp(async function fastifyAutoSqlApi(fastify, options) {
56
53
  // Set ConditionBuilder dialect globally
57
54
  const dialect = getDialect(options.dialect || 'postgres');
58
55
  ConditionBuilder.DIALECT = dialect.cbDialect;
59
- // Lazy-init QueryClient (internal closure — not decorated on fastify)
60
- let cachedDb;
61
- function getDb() {
62
- if (!cachedDb) {
63
- const pool = (options.dialect === 'mysql' || options.dialect === 'mariadb')
64
- ? mysqlQueryable(fastify.mysql)
65
- : pgQueryable(fastify.pg);
66
- cachedDb = createQueryClient(pool, options.dialect);
67
- if (options.debug)
68
- cachedDb.setDebug(true);
69
- }
70
- return cachedDb;
71
- }
72
56
  // SqlApi: exposed to parent scope via fp
73
- let cachedSqlApi;
74
- fastify.decorate('sqlApi', {
75
- getter() {
76
- if (!cachedSqlApi) {
77
- cachedSqlApi = createSqlApi(getDb(), options.DbTables, {
78
- getTenantId: options.getTenantId,
79
- });
80
- }
81
- return cachedSqlApi;
82
- },
83
- });
57
+ ensureSqlApiDecorator(fastify, options);
84
58
  // Routes in a child scope — prefix applies here, not at fp level
85
59
  const { prefix, ...routeOptions } = options;
86
60
  await fastify.register(async (instance) => {
@@ -1 +1 @@
1
- {"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/route-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE5F,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC;AAElG,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAE9F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQlG;AAED,UAAU,WAAW;IAEnB,IAAI,CAAC,EAAE,GAAG,CAAC;IAEX,MAAM,CAAC,EAAE,GAAG,CAAC;IAEb,WAAW,CAAC,EAAE,GAAG,CAAC;IAElB,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC1C,gFAAgF;IAChF,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,aAAa,EAAE,GAAG,GAAG,GAAG,CAAC;IACzB,wIAAwI;IACxI,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,WAAW,CAAC;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC;IACpF;2EACuE;IACvE,MAAM,EAAE,CACN,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,KAChB,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,IAAI,CAAC,CAiCf"}
1
+ {"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/route-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE7E,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE5F,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC;AAElG,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAE9F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQlG;AAED,UAAU,WAAW;IAEnB,IAAI,CAAC,EAAE,GAAG,CAAC;IAEX,MAAM,CAAC,EAAE,GAAG,CAAC;IAEb,WAAW,CAAC,EAAE,GAAG,CAAC;IAElB,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC1C,gFAAgF;IAChF,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,aAAa,EAAE,GAAG,GAAG,GAAG,CAAC;IACzB,wIAAwI;IACxI,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,WAAW,CAAC;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC;IACpF;2EACuE;IACvE,MAAM,EAAE,CACN,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,KAChB,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf"}
@@ -1,3 +1,4 @@
1
+ import { ensureSqlApiDecorator } from '../../lib/sql-api-decorator.js';
1
2
  export function mergeOnRequests(options, tableConf) {
2
3
  return [...(options.onRequests || []), ...(tableConf.onRequests || [])];
3
4
  }
@@ -16,6 +17,9 @@ export function buildWriteDescription(action, tableName, tableConf) {
16
17
  * default reply when handler returns a value.
17
18
  */
18
19
  export async function registerForAllTables(fastify, options, spec) {
20
+ // Granular composition support: when a route plugin is registered without the
21
+ // main plugin, no ancestor has decorated `sqlApi` — create it in this scope.
22
+ ensureSqlApiDecorator(fastify, options);
19
23
  const { DbTables } = options;
20
24
  for (const [tableName, tableConf] of Object.entries(DbTables)) {
21
25
  // operations acts as a whitelist; when omitted every operation is exposed.
@@ -102,6 +102,19 @@ export interface ITable<F extends Record<string, TSchema> = Record<string, TSche
102
102
  * affected.
103
103
  */
104
104
  operations?: TableOperation[];
105
+ /**
106
+ * Fields hidden from every read: they are not projected by search/get, not included
107
+ * in the read response schemas, and omitted from this table's default join selection
108
+ * when it is the target of a join.
109
+ *
110
+ * Referencing an excluded field from filters, conditions, orderBy, aggregations or an
111
+ * explicit join selection is rejected with 400 — hiding a field from the output while
112
+ * letting it be filtered would still leak its value by bisection.
113
+ *
114
+ * Write paths are NOT affected: a field can be writable but never readable (e.g. a
115
+ * password hash). Primary-key fields cannot be excluded.
116
+ */
117
+ readExclude?: (string & keyof F)[];
105
118
  defaultOrder?: string;
106
119
  excludeFromCreation?: (string & keyof F)[];
107
120
  distinctResults?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/types/table.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/types/table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,WAAW,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAChI,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEpE,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,KAAK,GACL,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,YAAY,GACZ,YAAY,CAAC;AAEjB,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAAC,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;AAC/F;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,SAAS,KAAK,gBAAgB,CAAC;AAE7F,MAAM,WAAW,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjF,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IACnD,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACjD,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAC;IACpC,iBAAiB,CAAC,EAAE,cAAc,EAAE,CAAC;IACrC,SAAS,CAAC,EAAE,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5C,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAC1B,YAAY,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAClC,YAAY,CAAC,EAAE,CACb,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,IAAI;KAAE,KAC1D,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,CAAC,EAAE,CACb,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,IAAI;KAAE,EAC7D,sBAAsB,CAAC,EAAE,OAAO,KAC7B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,CACZ,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAAE,EACzC,gBAAgB,CAAC,EAAE,OAAO,KACvB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CACZ,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,IAAI;KAAE,EAC7D,gBAAgB,CAAC,EAAE,OAAO,EAC1B,eAAe,CAAC,EAAE,OAAO,KACtB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,CACb,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,MAAM,GAAG,MAAM,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,CACZ,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,MAAM,GAAG,MAAM,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,CACjB,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KACrB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAC5B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IAC3C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC;IAChG,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAGD,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAGnD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CAE7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAEpG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify-auto-sqlapi",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Fastify plugin for automatic CRUD API generation from PostgreSQL, MySQL and MariaDB tables",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",