fastify-auto-sqlapi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/dist/bin/generate-schema.d.ts +3 -0
  4. package/dist/bin/generate-schema.d.ts.map +1 -0
  5. package/dist/bin/generate-schema.js +122 -0
  6. package/dist/bin/generate-tables.d.ts +3 -0
  7. package/dist/bin/generate-tables.d.ts.map +1 -0
  8. package/dist/bin/generate-tables.js +135 -0
  9. package/dist/bin/utils.d.ts +18 -0
  10. package/dist/bin/utils.d.ts.map +1 -0
  11. package/dist/bin/utils.js +54 -0
  12. package/dist/index.d.ts +34 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +48 -0
  15. package/dist/lib/adapters/mysql-adapter.d.ts +9 -0
  16. package/dist/lib/adapters/mysql-adapter.d.ts.map +1 -0
  17. package/dist/lib/adapters/mysql-adapter.js +21 -0
  18. package/dist/lib/adapters/pg-adapter.d.ts +9 -0
  19. package/dist/lib/adapters/pg-adapter.d.ts.map +1 -0
  20. package/dist/lib/adapters/pg-adapter.js +11 -0
  21. package/dist/lib/cli/config.d.ts +3 -0
  22. package/dist/lib/cli/config.d.ts.map +1 -0
  23. package/dist/lib/cli/config.js +29 -0
  24. package/dist/lib/cli/mysql-introspect.d.ts +16 -0
  25. package/dist/lib/cli/mysql-introspect.d.ts.map +1 -0
  26. package/dist/lib/cli/mysql-introspect.js +98 -0
  27. package/dist/lib/cli/pg-introspect.d.ts +4 -0
  28. package/dist/lib/cli/pg-introspect.d.ts.map +1 -0
  29. package/dist/lib/cli/pg-introspect.js +36 -0
  30. package/dist/lib/cli/schema-codegen.d.ts +8 -0
  31. package/dist/lib/cli/schema-codegen.d.ts.map +1 -0
  32. package/dist/lib/cli/schema-codegen.js +100 -0
  33. package/dist/lib/cli/tables-codegen.d.ts +18 -0
  34. package/dist/lib/cli/tables-codegen.d.ts.map +1 -0
  35. package/dist/lib/cli/tables-codegen.js +249 -0
  36. package/dist/lib/condition-methods.d.ts +11 -0
  37. package/dist/lib/condition-methods.d.ts.map +1 -0
  38. package/dist/lib/condition-methods.js +20 -0
  39. package/dist/lib/db.d.ts +28 -0
  40. package/dist/lib/db.d.ts.map +1 -0
  41. package/dist/lib/db.js +224 -0
  42. package/dist/lib/dialect.d.ts +17 -0
  43. package/dist/lib/dialect.d.ts.map +1 -0
  44. package/dist/lib/dialect.js +56 -0
  45. package/dist/lib/engine/bulk/bulk-delete.d.ts +3 -0
  46. package/dist/lib/engine/bulk/bulk-delete.d.ts.map +1 -0
  47. package/dist/lib/engine/bulk/bulk-delete.js +30 -0
  48. package/dist/lib/engine/bulk/bulk-upsert.d.ts +3 -0
  49. package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -0
  50. package/dist/lib/engine/bulk/bulk-upsert.js +74 -0
  51. package/dist/lib/engine/rest/delete.d.ts +3 -0
  52. package/dist/lib/engine/rest/delete.d.ts.map +1 -0
  53. package/dist/lib/engine/rest/delete.js +25 -0
  54. package/dist/lib/engine/rest/get.d.ts +3 -0
  55. package/dist/lib/engine/rest/get.d.ts.map +1 -0
  56. package/dist/lib/engine/rest/get.js +32 -0
  57. package/dist/lib/engine/rest/insert.d.ts +3 -0
  58. package/dist/lib/engine/rest/insert.d.ts.map +1 -0
  59. package/dist/lib/engine/rest/insert.js +58 -0
  60. package/dist/lib/engine/rest/update.d.ts +3 -0
  61. package/dist/lib/engine/rest/update.d.ts.map +1 -0
  62. package/dist/lib/engine/rest/update.js +110 -0
  63. package/dist/lib/engine/search/search.d.ts +3 -0
  64. package/dist/lib/engine/search/search.d.ts.map +1 -0
  65. package/dist/lib/engine/search/search.js +300 -0
  66. package/dist/lib/engine/write-helpers.d.ts +8 -0
  67. package/dist/lib/engine/write-helpers.d.ts.map +1 -0
  68. package/dist/lib/engine/write-helpers.js +83 -0
  69. package/dist/lib/naming.d.ts +6 -0
  70. package/dist/lib/naming.d.ts.map +1 -0
  71. package/dist/lib/naming.js +23 -0
  72. package/dist/lib/schema/bulk-delete.d.ts +10 -0
  73. package/dist/lib/schema/bulk-delete.d.ts.map +1 -0
  74. package/dist/lib/schema/bulk-delete.js +16 -0
  75. package/dist/lib/schema/bulk-upsert.d.ts +5 -0
  76. package/dist/lib/schema/bulk-upsert.d.ts.map +1 -0
  77. package/dist/lib/schema/bulk-upsert.js +43 -0
  78. package/dist/lib/schema/helpers.d.ts +14 -0
  79. package/dist/lib/schema/helpers.d.ts.map +1 -0
  80. package/dist/lib/schema/helpers.js +33 -0
  81. package/dist/lib/schema/insert.d.ts +5 -0
  82. package/dist/lib/schema/insert.d.ts.map +1 -0
  83. package/dist/lib/schema/insert.js +43 -0
  84. package/dist/lib/schema/search.d.ts +14 -0
  85. package/dist/lib/schema/search.d.ts.map +1 -0
  86. package/dist/lib/schema/search.js +99 -0
  87. package/dist/lib/schema/update.d.ts +5 -0
  88. package/dist/lib/schema/update.d.ts.map +1 -0
  89. package/dist/lib/schema/update.js +47 -0
  90. package/dist/lib/setup-swagger.d.ts +4 -0
  91. package/dist/lib/setup-swagger.d.ts.map +1 -0
  92. package/dist/lib/setup-swagger.js +27 -0
  93. package/dist/lib/sql-api.d.ts +49 -0
  94. package/dist/lib/sql-api.d.ts.map +1 -0
  95. package/dist/lib/sql-api.js +124 -0
  96. package/dist/lib/table-helpers.d.ts +17 -0
  97. package/dist/lib/table-helpers.d.ts.map +1 -0
  98. package/dist/lib/table-helpers.js +34 -0
  99. package/dist/lib/tenant.d.ts +19 -0
  100. package/dist/lib/tenant.d.ts.map +1 -0
  101. package/dist/lib/tenant.js +110 -0
  102. package/dist/routes/auto/bulk-delete.routes.d.ts +4 -0
  103. package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -0
  104. package/dist/routes/auto/bulk-delete.routes.js +27 -0
  105. package/dist/routes/auto/bulk-upsert.routes.d.ts +4 -0
  106. package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -0
  107. package/dist/routes/auto/bulk-upsert.routes.js +25 -0
  108. package/dist/routes/auto/delete.routes.d.ts +4 -0
  109. package/dist/routes/auto/delete.routes.d.ts.map +1 -0
  110. package/dist/routes/auto/delete.routes.js +29 -0
  111. package/dist/routes/auto/get.routes.d.ts +4 -0
  112. package/dist/routes/auto/get.routes.d.ts.map +1 -0
  113. package/dist/routes/auto/get.routes.js +26 -0
  114. package/dist/routes/auto/insert.routes.d.ts +4 -0
  115. package/dist/routes/auto/insert.routes.d.ts.map +1 -0
  116. package/dist/routes/auto/insert.routes.js +29 -0
  117. package/dist/routes/auto/plugin.d.ts +11 -0
  118. package/dist/routes/auto/plugin.d.ts.map +1 -0
  119. package/dist/routes/auto/plugin.js +81 -0
  120. package/dist/routes/auto/route-helpers.d.ts +7 -0
  121. package/dist/routes/auto/route-helpers.d.ts.map +1 -0
  122. package/dist/routes/auto/route-helpers.js +12 -0
  123. package/dist/routes/auto/search.routes.d.ts +4 -0
  124. package/dist/routes/auto/search.routes.d.ts.map +1 -0
  125. package/dist/routes/auto/search.routes.js +50 -0
  126. package/dist/routes/auto/update.routes.d.ts +4 -0
  127. package/dist/routes/auto/update.routes.d.ts.map +1 -0
  128. package/dist/routes/auto/update.routes.js +30 -0
  129. package/dist/types.d.ts +240 -0
  130. package/dist/types.d.ts.map +1 -0
  131. package/dist/types.js +7 -0
  132. package/package.json +106 -0
@@ -0,0 +1,300 @@
1
+ import { ConditionBuilder } from 'node-condition-builder';
2
+ import { camelcaseObject } from '../../naming.js';
3
+ import { buildTenantCondition, buildTenantJoin } from '../../tenant.js';
4
+ import { primaryAsString } from '../../../types.js';
5
+ function validateSchemaField(field, schema) {
6
+ if (!(field in schema.fields)) {
7
+ const err = new Error(`Unknown field: ${field}`);
8
+ err.statusCode = 400;
9
+ throw err;
10
+ }
11
+ return schema.col(field);
12
+ }
13
+ function validateOrderBy(orderBy, tableConf, db) {
14
+ return orderBy.split(',').map((part) => {
15
+ const trimmed = part.trim();
16
+ const match = trimmed.match(/^(\w+)(?:\s+(ASC|DESC))?$/i);
17
+ if (!match) {
18
+ const err = new Error(`Invalid orderBy: ${trimmed}`);
19
+ err.statusCode = 400;
20
+ throw err;
21
+ }
22
+ const [, field, dir] = match;
23
+ const col = validateSchemaField(field, tableConf.Schema);
24
+ return `${db.qi(col)} ${(dir || 'ASC').toUpperCase()}`;
25
+ }).join(', ');
26
+ }
27
+ async function executeMainQuery(db, tableConf, where, values, orderBy, paginator, extraJoins = []) {
28
+ const tableName = tableConf.Schema.tableName;
29
+ const order = orderBy || tableConf.defaultOrder || primaryAsString(tableConf.primary);
30
+ const limit = paginator
31
+ ? `${paginator.itemsPerPage} OFFSET ${(paginator.page - 1) * paginator.itemsPerPage}`
32
+ : null;
33
+ const rows = await db.select({
34
+ tableName,
35
+ where,
36
+ values,
37
+ orderBy: order,
38
+ limit,
39
+ distinct: tableConf.distinctResults,
40
+ joins: extraJoins.length > 0 ? extraJoins : undefined,
41
+ });
42
+ return rows.map((r) => camelcaseObject(r));
43
+ }
44
+ async function buildPagination(db, tableConf, where, values, paginator, extraJoins = [], computeMin, computeMax, computeSum, computeAvg) {
45
+ const tableName = tableConf.Schema.tableName;
46
+ const joinClause = extraJoins.length > 0 ? ' ' + extraJoins.join(' ') : '';
47
+ const countResult = await db.query(`SELECT COUNT(*) as total FROM ${db.qi(tableName)}${joinClause} WHERE ${where}`, values);
48
+ const total = parseInt(countResult.rows[0].total, 10);
49
+ const computed = {};
50
+ const computations = [
51
+ { key: 'min', field: computeMin, fn: 'MIN' },
52
+ { key: 'max', field: computeMax, fn: 'MAX' },
53
+ { key: 'sum', field: computeSum, fn: 'SUM' },
54
+ { key: 'avg', field: computeAvg, fn: 'AVG' },
55
+ ];
56
+ for (const { key, field, fn } of computations) {
57
+ if (field) {
58
+ const col = validateSchemaField(field, tableConf.Schema);
59
+ const result = await db.query(`SELECT ${fn}(${db.qi(col)}) as value FROM ${db.qi(tableName)}${joinClause} WHERE ${where}`, values);
60
+ computed[key] = { [field]: result.rows[0].value };
61
+ }
62
+ }
63
+ return {
64
+ total,
65
+ pages: Math.ceil(total / paginator.itemsPerPage),
66
+ ...(Object.keys(computed).length > 0 ? { computed } : {}),
67
+ paginator,
68
+ };
69
+ }
70
+ function findJoinDefinition(tableConf, joinTableName) {
71
+ return tableConf.allowedReadJoins?.find(([joinSchema]) => joinSchema.tableName === joinTableName);
72
+ }
73
+ async function executeVirtualJoins(db, dbTables, tableConf, mainResults, joins) {
74
+ const result = {};
75
+ for (const [joinTableName, joinOpts] of Object.entries(joins)) {
76
+ const joinDef = findJoinDefinition(tableConf, joinTableName);
77
+ if (!joinDef)
78
+ continue;
79
+ const [joinSchema, joinField, mainField, selection] = joinDef;
80
+ const joinTableConf = dbTables[joinTableName];
81
+ // Collect IDs from main results
82
+ const ids = collectIds(mainResults, mainField);
83
+ if (ids.length === 0) {
84
+ result[joinTableName] = [];
85
+ continue;
86
+ }
87
+ // Build join filters
88
+ const cb = joinTableConf
89
+ ? joinTableConf.filters(joinOpts?.filters || {})
90
+ : new ConditionBuilder('AND');
91
+ const fkCol = joinSchema.col(joinField);
92
+ cb.isIn(db.qi(fkCol), ids);
93
+ const where = cb.build(1, db.ph);
94
+ const values = cb.getValues();
95
+ const columns = selection === '*' ? '*' : selection;
96
+ const rows = await db.select({
97
+ tableName: joinSchema.tableName,
98
+ columns,
99
+ where,
100
+ values,
101
+ });
102
+ result[joinTableName] = rows.map((r) => camelcaseObject(r));
103
+ }
104
+ return result;
105
+ }
106
+ async function executeJoinGroups(db, dbTables, tableConf, mainResults, joinGroups) {
107
+ const result = {};
108
+ for (const [joinTableName, groupReq] of Object.entries(joinGroups)) {
109
+ const joinDef = findJoinDefinition(tableConf, joinTableName);
110
+ if (!joinDef)
111
+ continue;
112
+ const [joinSchema, joinField, mainField] = joinDef;
113
+ const ids = collectIds(mainResults, mainField);
114
+ if (ids.length === 0) {
115
+ result[joinTableName] = {};
116
+ continue;
117
+ }
118
+ const { aggregations, filters: groupFilters } = groupReq;
119
+ const selectParts = [];
120
+ const groupByParts = [];
121
+ if (aggregations.by) {
122
+ const byCol = validateSchemaField(aggregations.by, joinSchema);
123
+ selectParts.push(`${db.qi(byCol)} as "by"`);
124
+ groupByParts.push(db.qi(byCol));
125
+ }
126
+ if (aggregations.distinctCount) {
127
+ for (const f of aggregations.distinctCount) {
128
+ const col = validateSchemaField(f, joinSchema);
129
+ selectParts.push(`COUNT(DISTINCT ${db.qi(col)}) as "distinctCount_${f}"`);
130
+ }
131
+ }
132
+ if (aggregations.min) {
133
+ for (const f of aggregations.min) {
134
+ const col = validateSchemaField(f, joinSchema);
135
+ selectParts.push(`MIN(${db.qi(col)}) as "min_${f}"`);
136
+ }
137
+ }
138
+ if (aggregations.max) {
139
+ for (const f of aggregations.max) {
140
+ const col = validateSchemaField(f, joinSchema);
141
+ selectParts.push(`MAX(${db.qi(col)}) as "max_${f}"`);
142
+ }
143
+ }
144
+ if (aggregations.sum) {
145
+ for (const f of aggregations.sum) {
146
+ const col = validateSchemaField(f, joinSchema);
147
+ selectParts.push(`SUM(${db.qi(col)}) as "sum_${f}"`);
148
+ }
149
+ }
150
+ if (selectParts.length === 0) {
151
+ result[joinTableName] = {};
152
+ continue;
153
+ }
154
+ // Build WHERE clause
155
+ const joinTableConf = dbTables[joinTableName];
156
+ const cb = joinTableConf && groupFilters
157
+ ? joinTableConf.filters(groupFilters)
158
+ : new ConditionBuilder('AND');
159
+ const fkCol = joinSchema.col(joinField);
160
+ cb.isIn(db.qi(fkCol), ids);
161
+ const where = cb.build(1, db.ph);
162
+ const values = cb.getValues();
163
+ const groupBy = groupByParts.length > 0 ? `GROUP BY ${groupByParts.join(', ')}` : '';
164
+ const sql = `SELECT ${selectParts.join(', ')} FROM ${db.qi(joinSchema.tableName)} WHERE ${where} ${groupBy}`;
165
+ const queryResult = await db.query(sql, values);
166
+ const rows = queryResult.rows;
167
+ // Format: { distinctCount: {field: value}, min: {field: value}, ... }
168
+ const formatted = {};
169
+ if (rows.length > 0) {
170
+ const row = rows.length === 1 && !aggregations.by ? rows[0] : rows;
171
+ if (Array.isArray(row)) {
172
+ formatted.rows = row;
173
+ }
174
+ else {
175
+ for (const [key, value] of Object.entries(row)) {
176
+ if (key === 'by')
177
+ continue;
178
+ const [fn, field] = key.split('_');
179
+ if (!formatted[fn])
180
+ formatted[fn] = {};
181
+ formatted[fn][field] = value;
182
+ }
183
+ }
184
+ }
185
+ result[joinTableName] = formatted;
186
+ }
187
+ return result;
188
+ }
189
+ function collectIds(mainResults, mainField) {
190
+ if (Array.isArray(mainField)) {
191
+ // Composite key: collect tuples
192
+ const seen = new Set();
193
+ const ids = [];
194
+ for (const r of mainResults) {
195
+ const key = mainField.map((f) => r[f]).join('|');
196
+ if (!seen.has(key) && mainField.every((f) => r[f] != null)) {
197
+ seen.add(key);
198
+ ids.push(...mainField.map((f) => r[f]));
199
+ }
200
+ }
201
+ return ids;
202
+ }
203
+ const unique = [...new Set(mainResults.map((r) => r[mainField]).filter((v) => v != null))];
204
+ return unique;
205
+ }
206
+ // ─── Conditions (advanced filters) ──────────────────────────
207
+ import { ALLOWED_SET, SINGLE_VALUE_SET, BETWEEN_SET, IN_SET, NULL_SET, } from '../../condition-methods.js';
208
+ function applyConditions(condition, conditions, schema, db) {
209
+ for (const c of conditions) {
210
+ // Validate method — whitelist only, blocks prototype poisoning
211
+ if (!ALLOWED_SET.has(c.method)) {
212
+ const err = new Error(`Invalid condition method: ${c.method}`);
213
+ err.statusCode = 400;
214
+ throw err;
215
+ }
216
+ const col = db.qi(validateSchemaField(c.field, schema));
217
+ if (SINGLE_VALUE_SET.has(c.method)) {
218
+ condition[c.method](col, c.params[0]);
219
+ }
220
+ else if (BETWEEN_SET.has(c.method)) {
221
+ condition[c.method](col, c.params[0], c.params[1]);
222
+ }
223
+ else if (IN_SET.has(c.method)) {
224
+ condition[c.method](col, c.params[0]);
225
+ }
226
+ else if (NULL_SET.has(c.method)) {
227
+ condition[c.method](col, true);
228
+ }
229
+ }
230
+ }
231
+ // ─── Join filters (EXISTS subquery) ─────────────────────────
232
+ function buildJoinFiltersExists(db, dbTables, tableConf, joinFilters, currentWhere, currentValues) {
233
+ let where = currentWhere;
234
+ const values = [...currentValues];
235
+ for (const [joinTableName, filterValues] of Object.entries(joinFilters)) {
236
+ const joinDef = findJoinDefinition(tableConf, joinTableName);
237
+ if (!joinDef)
238
+ continue;
239
+ const [joinSchema, joinField, mainField] = joinDef;
240
+ const joinTableConf = dbTables[joinTableName];
241
+ if (!joinTableConf)
242
+ continue;
243
+ const filterCondition = joinTableConf.filters(filterValues);
244
+ const startIdx = values.length + 1;
245
+ const filterWhere = filterCondition.build(startIdx, db.ph);
246
+ const filterVals = filterCondition.getValues();
247
+ const fkCol = db.qi(joinSchema.col(joinField));
248
+ const mainColName = Array.isArray(mainField) ? mainField[0] : mainField;
249
+ const mainCol = db.qi(tableConf.Schema.col(mainColName));
250
+ const mainTable = db.qi(tableConf.Schema.tableName);
251
+ where += ` AND EXISTS (SELECT 1 FROM ${db.qi(joinSchema.tableName)} WHERE ${fkCol} = ${mainTable}.${mainCol} AND ${filterWhere})`;
252
+ values.push(...filterVals);
253
+ }
254
+ return { where, values };
255
+ }
256
+ export async function searchEngine(dbTables, params) {
257
+ const { db, tableConf, filters, conditions, joinFilters, joins, joinGroups, orderBy, paginator, computeMin, computeMax, computeSum, computeAvg, tenant } = params;
258
+ // Build main condition
259
+ const condition = tableConf.filters(filters || {});
260
+ // Advanced conditions
261
+ if (conditions?.length) {
262
+ applyConditions(condition, conditions, tableConf.Schema, db);
263
+ }
264
+ // Tenant filtering
265
+ const tenantJoins = [];
266
+ if (tenant) {
267
+ condition.append(buildTenantCondition(db, tenant.scope, tenant.ids));
268
+ if ('through' in tenant.scope) {
269
+ tenantJoins.push(buildTenantJoin(db, tenant.scope, tableConf.Schema.tableName));
270
+ }
271
+ }
272
+ let where = condition.build(1, db.ph);
273
+ let values = [...condition.getValues()];
274
+ // Join filters: add EXISTS subqueries
275
+ if (joinFilters && Object.keys(joinFilters).length > 0) {
276
+ ({ where, values } = buildJoinFiltersExists(db, dbTables, tableConf, joinFilters, where, values));
277
+ }
278
+ // Validate and sanitize orderBy (user input -> SQL identifier)
279
+ const safeOrderBy = orderBy ? validateOrderBy(orderBy, tableConf, db) : undefined;
280
+ // Main query
281
+ const main = await executeMainQuery(db, tableConf, where, values, safeOrderBy, paginator, tenantJoins);
282
+ // Pagination
283
+ let pagination;
284
+ if (paginator) {
285
+ pagination = await buildPagination(db, tableConf, where, values, paginator, tenantJoins, computeMin, computeMax, computeSum, computeAvg);
286
+ }
287
+ // Virtual joins (only if requested)
288
+ const result = { main };
289
+ if (joins && Object.keys(joins).length > 0) {
290
+ result.joins = await executeVirtualJoins(db, dbTables, tableConf, main, joins);
291
+ }
292
+ // Join groups (only if requested)
293
+ if (joinGroups && Object.keys(joinGroups).length > 0) {
294
+ result.joinGroups = await executeJoinGroups(db, dbTables, tableConf, main, joinGroups);
295
+ }
296
+ if (pagination) {
297
+ result.pagination = pagination;
298
+ }
299
+ return result;
300
+ }
@@ -0,0 +1,8 @@
1
+ import type { QueryClient } from '../db.js';
2
+ import type { ITable, DbTables, JoinDefinition } from '../../types.js';
3
+ export declare function findWriteJoin(tableConf: ITable, joinTableName: string): JoinDefinition | undefined;
4
+ export declare function removeExcludedFields(record: Record<string, unknown>, tableConf: ITable): Record<string, unknown>;
5
+ export declare function findSecondaryTableConf(dbTables: DbTables, joinTableName: string): ITable | undefined;
6
+ export declare function processSecondaries(db: QueryClient, tableConf: ITable, dbTables: DbTables, mainRecord: Record<string, unknown>, secondaries: Record<string, Record<string, unknown>[]>): Promise<Record<string, Record<string, unknown>[]>>;
7
+ export declare function processDeletions(db: QueryClient, tableConf: ITable, deletions: Record<string, Record<string, unknown>[]>): Promise<Record<string, Record<string, unknown>[]>>;
8
+ //# sourceMappingURL=write-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/engine/write-helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EACV,MAAM,EACN,QAAQ,EACR,cAAc,EAEf,MAAM,gBAAgB,CAAC;AAExB,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,cAAc,GAAG,SAAS,CAI5B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,MAAM,GACpB,MAAM,GAAG,SAAS,CAKpB;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,WAAW,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GACrD,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CA2DpD;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,WAAW,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GACnD,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAwBpD"}
@@ -0,0 +1,83 @@
1
+ import { camelcaseObject, snakecaseRecord } from '../naming.js';
2
+ export function findWriteJoin(tableConf, joinTableName) {
3
+ return tableConf.allowedWriteJoins?.find(([joinSchema]) => joinSchema.tableName === joinTableName);
4
+ }
5
+ export function removeExcludedFields(record, tableConf) {
6
+ if (!tableConf.excludeFromCreation?.length)
7
+ return record;
8
+ const result = { ...record };
9
+ for (const field of tableConf.excludeFromCreation) {
10
+ const col = tableConf.Schema.col(field);
11
+ delete result[col];
12
+ }
13
+ return result;
14
+ }
15
+ export function findSecondaryTableConf(dbTables, joinTableName) {
16
+ for (const [, conf] of Object.entries(dbTables)) {
17
+ if (conf.Schema.tableName === joinTableName)
18
+ return conf;
19
+ }
20
+ return undefined;
21
+ }
22
+ export async function processSecondaries(db, tableConf, dbTables, mainRecord, secondaries) {
23
+ const results = {};
24
+ for (const [joinTableName, records] of Object.entries(secondaries)) {
25
+ if (!records?.length)
26
+ continue;
27
+ const joinDef = findWriteJoin(tableConf, joinTableName);
28
+ if (!joinDef)
29
+ continue;
30
+ const [joinSchema, joinField, mainField] = joinDef;
31
+ const joinCol = joinSchema.col(joinField);
32
+ const secondaryTableConf = findSecondaryTableConf(dbTables, joinTableName);
33
+ const secondaryPk = secondaryTableConf?.primary || joinField;
34
+ const secondaryPkCol = Array.isArray(secondaryPk)
35
+ ? secondaryPk.map((f) => joinSchema.col(f))
36
+ : joinSchema.col(secondaryPk);
37
+ const preparedRecords = records.map((rec) => {
38
+ let prepared = snakecaseRecord(rec);
39
+ // Auto-fill FK from main record
40
+ const mainValue = Array.isArray(mainField)
41
+ ? mainRecord[mainField[0]]
42
+ : mainRecord[mainField];
43
+ prepared[joinCol] = mainValue;
44
+ // Remove excluded fields
45
+ if (secondaryTableConf) {
46
+ prepared = removeExcludedFields(prepared, secondaryTableConf);
47
+ }
48
+ return prepared;
49
+ });
50
+ const secondaryUpsertKeys = tableConf.upsertMap?.get(joinSchema);
51
+ let pkRows;
52
+ if (secondaryUpsertKeys) {
53
+ const conflictCols = secondaryUpsertKeys.map((k) => joinSchema.col(k));
54
+ pkRows = await db.bulkInsertOrUpdate(joinSchema.tableName, preparedRecords, conflictCols, secondaryPkCol);
55
+ }
56
+ else {
57
+ pkRows = await db.bulkInsert(joinSchema.tableName, preparedRecords, secondaryPkCol);
58
+ }
59
+ results[joinTableName] = pkRows.map((r) => camelcaseObject(r));
60
+ }
61
+ return results;
62
+ }
63
+ export async function processDeletions(db, tableConf, deletions) {
64
+ const results = {};
65
+ for (const [joinTableName, records] of Object.entries(deletions)) {
66
+ if (!records?.length)
67
+ continue;
68
+ const joinDef = findWriteJoin(tableConf, joinTableName);
69
+ if (!joinDef)
70
+ continue;
71
+ const [joinSchema] = joinDef;
72
+ const deletedRows = [];
73
+ for (const rec of records) {
74
+ const snaked = snakecaseRecord(rec);
75
+ const affectedRows = await db.delete(joinSchema.tableName, snaked);
76
+ if (affectedRows > 0) {
77
+ deletedRows.push(camelcaseObject(snaked));
78
+ }
79
+ }
80
+ results[joinTableName] = deletedRows;
81
+ }
82
+ return results;
83
+ }
@@ -0,0 +1,6 @@
1
+ export declare function toCamelCase(str: string): string;
2
+ export declare function toUnderscore(str: string): string;
3
+ export declare function camelcaseObject<T extends Record<string, unknown>>(obj: T): Record<string, unknown>;
4
+ export declare function snakecaseRecord(obj: Record<string, unknown>): Record<string, unknown>;
5
+ export declare function toSchemaName(tableName: string): string;
6
+ //# sourceMappingURL=naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/lib/naming.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,GAAG,EAAE,CAAC,GACL,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzB;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzB;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQtD"}
@@ -0,0 +1,23 @@
1
+ export function toCamelCase(str) {
2
+ return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', ''));
3
+ }
4
+ export function toUnderscore(str) {
5
+ return str
6
+ .replace(/([A-Z])/g, '|$1')
7
+ .split('|')
8
+ .map((a, index) => (index === 0 ? a : a[0].toLowerCase() + a.substring(1)))
9
+ .join('_');
10
+ }
11
+ export function camelcaseObject(obj) {
12
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [toCamelCase(k), v]));
13
+ }
14
+ export function snakecaseRecord(obj) {
15
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [toUnderscore(k), v]));
16
+ }
17
+ export function toSchemaName(tableName) {
18
+ return ('Schema' +
19
+ tableName
20
+ .split('_')
21
+ .map((w) => w.substring(0, 1).toUpperCase() + w.substring(1))
22
+ .join(''));
23
+ }
@@ -0,0 +1,10 @@
1
+ import type { DbTables } from '../../types.js';
2
+ export declare function BulkDeleteTableBody(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
3
+ [x: string]: any;
4
+ }>>;
5
+ export declare function BulkDeleteTableResponse(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
6
+ main: import("@sinclair/typebox").TObject<{
7
+ [x: string]: any;
8
+ }>;
9
+ }>>;
10
+ //# sourceMappingURL=bulk-delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-delete.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/bulk-delete.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;;IAMxE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;;;;IAU5E"}
@@ -0,0 +1,16 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { primaryAsString } from '../../types.js';
3
+ export function BulkDeleteTableBody(dbTables, tableName) {
4
+ const tableConf = dbTables[tableName];
5
+ const pkField = primaryAsString(tableConf.primary);
6
+ const pkType = tableConf.Schema.fields[pkField];
7
+ return Type.Array(Type.Object({ [pkField]: pkType }));
8
+ }
9
+ export function BulkDeleteTableResponse(dbTables, tableName) {
10
+ const tableConf = dbTables[tableName];
11
+ const pkField = primaryAsString(tableConf.primary);
12
+ const pkType = tableConf.Schema.fields[pkField];
13
+ return Type.Array(Type.Object({
14
+ main: Type.Object({ [pkField]: pkType }),
15
+ }));
16
+ }
@@ -0,0 +1,5 @@
1
+ import { type TSchema } from '@sinclair/typebox';
2
+ import type { DbTables } from '../../types.js';
3
+ export declare function BulkUpsertTableBody(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<Record<string, TSchema>>>;
4
+ export declare function BulkUpsertTableResponse(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<Record<string, TSchema>>>;
5
+ //# sourceMappingURL=bulk-upsert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/bulk-upsert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oGAiCxE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oGA8B5E"}
@@ -0,0 +1,43 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { primaryAsString } from '../../types.js';
3
+ import { findSecondaryTableConf } from '../engine/write-helpers.js';
4
+ import { pkSchema, buildSecondaryFields } from './helpers.js';
5
+ export function BulkUpsertTableBody(dbTables, tableName) {
6
+ const tableConf = dbTables[tableName];
7
+ const schema = tableConf.Schema;
8
+ const mainSchema = Type.Partial(Type.Object(schema.fields));
9
+ const itemProperties = {
10
+ main: mainSchema,
11
+ };
12
+ if (tableConf.allowedWriteJoins?.length) {
13
+ const secondaryProperties = {};
14
+ const deletionProperties = {};
15
+ for (const [joinSchema, joinField] of tableConf.allowedWriteJoins) {
16
+ const secondaryTableConf = findSecondaryTableConf(dbTables, joinSchema.tableName);
17
+ const joinFields = buildSecondaryFields(joinSchema, joinField, secondaryTableConf);
18
+ secondaryProperties[joinSchema.tableName] = Type.Array(Type.Object(joinFields));
19
+ deletionProperties[joinSchema.tableName] = Type.Array(Type.Partial(Type.Object(joinSchema.fields)));
20
+ }
21
+ itemProperties.secondaries = Type.Optional(Type.Partial(Type.Object(secondaryProperties)));
22
+ itemProperties.deletions = Type.Optional(Type.Partial(Type.Object(deletionProperties)));
23
+ }
24
+ return Type.Array(Type.Object(itemProperties));
25
+ }
26
+ export function BulkUpsertTableResponse(dbTables, tableName) {
27
+ const tableConf = dbTables[tableName];
28
+ const responseProperties = {
29
+ main: Type.Object(pkSchema(tableConf, tableConf.Schema, primaryAsString(tableConf.primary))),
30
+ };
31
+ if (tableConf.allowedWriteJoins?.length) {
32
+ const secondaryProperties = {};
33
+ const deletionProperties = {};
34
+ for (const [joinSchema, joinField] of tableConf.allowedWriteJoins) {
35
+ const secondaryTableConf = findSecondaryTableConf(dbTables, joinSchema.tableName);
36
+ secondaryProperties[joinSchema.tableName] = Type.Array(Type.Object(pkSchema(secondaryTableConf, joinSchema, joinField)));
37
+ deletionProperties[joinSchema.tableName] = Type.Array(Type.Partial(Type.Object(joinSchema.fields)));
38
+ }
39
+ responseProperties.secondaries = Type.Optional(Type.Partial(Type.Object(secondaryProperties)));
40
+ responseProperties.deletions = Type.Optional(Type.Partial(Type.Object(deletionProperties)));
41
+ }
42
+ return Type.Array(Type.Object(responseProperties));
43
+ }
@@ -0,0 +1,14 @@
1
+ import { type TSchema } from '@sinclair/typebox';
2
+ import type { ITable, SchemaDefinition } from '../../types.js';
3
+ /**
4
+ * Build a PK-only schema for response shapes (single or composite PK).
5
+ * Falls back to `fallback` field if tableConf is undefined.
6
+ */
7
+ export declare function pkSchema(tableConf: ITable | undefined, schema: SchemaDefinition, fallback: string): Record<string, TSchema>;
8
+ /**
9
+ * Build secondary table fields for body schemas:
10
+ * - FK field becomes Optional (auto-filled from main)
11
+ * - excludeFromCreation fields become Optional
12
+ */
13
+ export declare function buildSecondaryFields(joinSchema: SchemaDefinition, joinField: string, secondaryTableConf: ITable | undefined): Record<string, TSchema>;
14
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ3H;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,gBAAgB,EAC5B,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzB"}
@@ -0,0 +1,33 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ /**
3
+ * Build a PK-only schema for response shapes (single or composite PK).
4
+ * Falls back to `fallback` field if tableConf is undefined.
5
+ */
6
+ export function pkSchema(tableConf, schema, fallback) {
7
+ const pk = tableConf?.primary || fallback;
8
+ const fields = Array.isArray(pk) ? pk : [pk];
9
+ const result = {};
10
+ for (const f of fields) {
11
+ result[f] = schema.fields[f] || Type.Any();
12
+ }
13
+ return result;
14
+ }
15
+ /**
16
+ * Build secondary table fields for body schemas:
17
+ * - FK field becomes Optional (auto-filled from main)
18
+ * - excludeFromCreation fields become Optional
19
+ */
20
+ export function buildSecondaryFields(joinSchema, joinField, secondaryTableConf) {
21
+ const fields = { ...joinSchema.fields };
22
+ if (joinField in fields) {
23
+ fields[joinField] = Type.Optional(fields[joinField]);
24
+ }
25
+ if (secondaryTableConf?.excludeFromCreation) {
26
+ for (const field of secondaryTableConf.excludeFromCreation) {
27
+ if (field in fields) {
28
+ fields[field] = Type.Optional(fields[field]);
29
+ }
30
+ }
31
+ }
32
+ return fields;
33
+ }
@@ -0,0 +1,5 @@
1
+ import { type TObject } from '@sinclair/typebox';
2
+ import type { DbTables } from '../../types.js';
3
+ export declare function InsertTableBody(dbTables: DbTables, tableName: string): TObject;
4
+ export declare function InsertTableResponse(dbTables: DbTables, tableName: string): TObject;
5
+ //# sourceMappingURL=insert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/insert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CA+B9E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAuBlF"}
@@ -0,0 +1,43 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { primaryAsString } from '../../types.js';
3
+ import { findSecondaryTableConf } from '../engine/write-helpers.js';
4
+ import { pkSchema, buildSecondaryFields } from './helpers.js';
5
+ export function InsertTableBody(dbTables, tableName) {
6
+ const tableConf = dbTables[tableName];
7
+ const schema = tableConf.Schema;
8
+ // Main: full validation, but excludeFromCreation fields become Optional
9
+ const mainFields = { ...schema.fields };
10
+ if (tableConf.excludeFromCreation) {
11
+ for (const field of tableConf.excludeFromCreation) {
12
+ mainFields[field] = Type.Optional(mainFields[field]);
13
+ }
14
+ }
15
+ const bodyProperties = {
16
+ main: Type.Object(mainFields),
17
+ };
18
+ if (tableConf.allowedWriteJoins?.length) {
19
+ const secondaryProperties = {};
20
+ for (const [joinSchema, joinField] of tableConf.allowedWriteJoins) {
21
+ const secondaryTableConf = findSecondaryTableConf(dbTables, joinSchema.tableName);
22
+ const joinFields = buildSecondaryFields(joinSchema, joinField, secondaryTableConf);
23
+ secondaryProperties[joinSchema.tableName] = Type.Array(Type.Object(joinFields));
24
+ }
25
+ bodyProperties.secondaries = Type.Optional(Type.Partial(Type.Object(secondaryProperties)));
26
+ }
27
+ return Type.Object(bodyProperties);
28
+ }
29
+ export function InsertTableResponse(dbTables, tableName) {
30
+ const tableConf = dbTables[tableName];
31
+ const responseProperties = {
32
+ main: Type.Object(pkSchema(tableConf, tableConf.Schema, primaryAsString(tableConf.primary))),
33
+ };
34
+ if (tableConf.allowedWriteJoins?.length) {
35
+ const secondaryProperties = {};
36
+ for (const [joinSchema, joinField] of tableConf.allowedWriteJoins) {
37
+ const secondaryTableConf = findSecondaryTableConf(dbTables, joinSchema.tableName);
38
+ secondaryProperties[joinSchema.tableName] = Type.Array(Type.Object(pkSchema(secondaryTableConf, joinSchema, joinField)));
39
+ }
40
+ responseProperties.secondaries = Type.Optional(Type.Partial(Type.Object(secondaryProperties)));
41
+ }
42
+ return Type.Object(responseProperties);
43
+ }
@@ -0,0 +1,14 @@
1
+ import { type TObject } from '@sinclair/typebox';
2
+ import type { DbTables } from '../../types.js';
3
+ export declare function SearchTableBodyPost(dbTables: DbTables, tableName: string): TObject;
4
+ export declare const SearchTableQueryString: TObject<{
5
+ orderBy: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
6
+ page: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
7
+ itemsPerPage: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
8
+ computeMin: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
+ computeMax: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
10
+ computeSum: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
11
+ computeAvg: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
12
+ }>;
13
+ export declare function SearchTableResponse(dbTables: DbTables, tableName: string): TObject;
14
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +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;AAU/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAgElF;AAED,eAAO,MAAM,sBAAsB;;;;;;;;EAQjC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAiClF"}