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,100 @@
1
+ import { toCamelCase, toSchemaName } from '../naming.js';
2
+ export function convertColType(udtName, columnInfo) {
3
+ const hasDefault = columnInfo.column_default;
4
+ const isNullable = columnInfo.is_nullable === 'YES';
5
+ const wrap = (t) => {
6
+ return hasDefault || isNullable ? `Type.Optional(${t})` : t;
7
+ };
8
+ switch (udtName) {
9
+ case 'uuid':
10
+ return wrap("Type.String({format: 'uuid'})");
11
+ case 'bool':
12
+ return wrap('Type.Boolean()');
13
+ case 'float4':
14
+ case 'float8':
15
+ case 'numeric':
16
+ return wrap('Type.Number()');
17
+ case 'int2':
18
+ case 'int4':
19
+ case 'int8':
20
+ return wrap('Type.Integer()');
21
+ case 'varchar':
22
+ case 'text':
23
+ case 'char':
24
+ case 'bpchar':
25
+ return wrap('Type.String()');
26
+ case 'timestamptz':
27
+ case 'timestamp':
28
+ return wrap('Type.String()');
29
+ case 'date':
30
+ return wrap("Type.String({format: 'date'})");
31
+ case 'time':
32
+ return wrap('Type.String()');
33
+ case 'jsonb':
34
+ case 'json':
35
+ return wrap('Type.Any()');
36
+ case '_varchar':
37
+ case '_text':
38
+ return wrap('Type.Array(Type.String())');
39
+ case '_int2':
40
+ case '_int4':
41
+ case '_int8':
42
+ return wrap('Type.Array(Type.Integer())');
43
+ case '_float4':
44
+ case '_float8':
45
+ return wrap('Type.Array(Type.Number())');
46
+ default:
47
+ if (udtName.substring(0, 3) === 'int') {
48
+ return wrap('Type.Integer()');
49
+ }
50
+ return wrap('Type.String()');
51
+ }
52
+ }
53
+ export function buildTableMap(rows) {
54
+ return rows.reduce((acc, row) => {
55
+ const schemaName = toSchemaName(row.table_name);
56
+ if (!acc[schemaName]) {
57
+ acc[schemaName] = {
58
+ name: row.table_name,
59
+ fields: {},
60
+ };
61
+ }
62
+ acc[schemaName].fields[toCamelCase(row.column_name)] = convertColType(row.udt_name, row);
63
+ return acc;
64
+ }, {});
65
+ }
66
+ export function generateSchemaFile(schemaName, tableName, fields) {
67
+ const fieldsStr = JSON.stringify(fields, null, 2).replace(/"(.*?)"\s*:\s*"Type(.*?)"/g, '$1: Type$2');
68
+ return `// Auto-generated by fastify-auto-sqlapi. Do not edit.
69
+ import { Type, toUnderscore } from "fastify-auto-sqlapi";
70
+ import type { Static } from "fastify-auto-sqlapi";
71
+
72
+ const _Schema = ${fieldsStr};
73
+
74
+ const validation = Type.Object(_Schema, { $id: "${schemaName}" });
75
+ const partialValidation = Type.Object(
76
+ Object.fromEntries(
77
+ Object.entries(_Schema).map(([k, v]) => [
78
+ k,
79
+ (validation.required as string[] | undefined)?.includes(k) ? Type.Optional(v) : v,
80
+ ])
81
+ ),
82
+ { additionalProperties: false, $id: "Partial${schemaName}" }
83
+ );
84
+
85
+ export const Schema = {
86
+ col: (f: keyof typeof _Schema) => toUnderscore(f),
87
+ fields: _Schema,
88
+ validation,
89
+ tableName: "${tableName}",
90
+ partialValidation,
91
+ };
92
+ export const ${schemaName} = Schema;
93
+ export type TypeSchema = Static<typeof Schema.validation>;
94
+ export type Type${schemaName} = TypeSchema;
95
+ export default async function (fastify: any) {
96
+ fastify.addSchema(partialValidation);
97
+ fastify.addSchema(validation);
98
+ }
99
+ `;
100
+ }
@@ -0,0 +1,18 @@
1
+ export interface ParsedSchema {
2
+ schemaName: string;
3
+ tableName: string;
4
+ fields: string[];
5
+ fieldTypes: Record<string, string>;
6
+ }
7
+ export interface DetectedRelation {
8
+ parentSchemaName: string;
9
+ parentField: string;
10
+ childSchemaName: string;
11
+ childField: string;
12
+ }
13
+ export declare function parseSchemaFile(content: string): ParsedSchema | null;
14
+ export declare function detectRelations(schemas: ParsedSchema[]): DetectedRelation[];
15
+ export declare function generateSingleTableFile(schema: ParsedSchema, allSchemas: ParsedSchema[]): string;
16
+ export declare function generateDbTablesIndex(schemas: ParsedSchema[]): string;
17
+ export declare function generateTablesFile(schemas: ParsedSchema[]): string;
18
+ //# sourceMappingURL=tables-codegen.d.ts.map
@@ -0,0 +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;CACpC;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,CA2BpE;AAgBD,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,CAiGhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAoFlE"}
@@ -0,0 +1,249 @@
1
+ import { toUnderscore } from '../naming.js';
2
+ // ─── Parsing ─────────────────────────────────────────────────
3
+ export function parseSchemaFile(content) {
4
+ const tableNameMatch = content.match(/tableName:\s*"([^"]+)"/);
5
+ if (!tableNameMatch)
6
+ return null;
7
+ const exportMatch = content.match(/export const (Schema\w+)\s*=\s*Schema/);
8
+ if (!exportMatch)
9
+ return null;
10
+ const schemaBlockMatch = content.match(/const _Schema\s*=\s*\{([\s\S]*?)};/);
11
+ if (!schemaBlockMatch)
12
+ return null;
13
+ const fields = [];
14
+ const fieldTypes = {};
15
+ const fieldRegex = /^\s+(\w+):\s+(Type\..+)$/gm;
16
+ let match;
17
+ while ((match = fieldRegex.exec(schemaBlockMatch[1])) !== null) {
18
+ fields.push(match[1]);
19
+ fieldTypes[match[1]] = match[2].replace(/,\s*$/, '');
20
+ }
21
+ if (fields.length === 0)
22
+ return null;
23
+ return {
24
+ schemaName: exportMatch[1],
25
+ tableName: tableNameMatch[1],
26
+ fields,
27
+ fieldTypes,
28
+ };
29
+ }
30
+ // ─── Detection ───────────────────────────────────────────────
31
+ function detectPrimaryKey(schema) {
32
+ if (schema.fields.includes('id')) {
33
+ return { pk: 'id', autoIncrement: schema.fieldTypes['id']?.includes('Optional') ?? false };
34
+ }
35
+ for (const field of schema.fields) {
36
+ if (schema.fieldTypes[field]?.includes('Integer')) {
37
+ return { pk: field, autoIncrement: schema.fieldTypes[field].includes('Optional') };
38
+ }
39
+ }
40
+ return { pk: schema.fields[0], autoIncrement: false };
41
+ }
42
+ export function detectRelations(schemas) {
43
+ const tableBySnakeName = new Map();
44
+ for (const schema of schemas) {
45
+ tableBySnakeName.set(schema.tableName, schema);
46
+ }
47
+ const relations = [];
48
+ for (const schema of schemas) {
49
+ for (const field of schema.fields) {
50
+ if (!field.endsWith('Id') || field === 'id')
51
+ continue;
52
+ const parentTableName = toUnderscore(field.slice(0, -2));
53
+ const parent = tableBySnakeName.get(parentTableName);
54
+ if (parent) {
55
+ const { pk } = detectPrimaryKey(parent);
56
+ relations.push({
57
+ parentSchemaName: parent.schemaName,
58
+ parentField: pk,
59
+ childSchemaName: schema.schemaName,
60
+ childField: field,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ return relations;
66
+ }
67
+ // ─── Single Table File Generation ────────────────────────────
68
+ export function generateSingleTableFile(schema, allSchemas) {
69
+ const relations = detectRelations(allSchemas);
70
+ const relsByParent = new Map();
71
+ const relsByChild = new Map();
72
+ for (const rel of relations) {
73
+ const arr = relsByParent.get(rel.parentSchemaName) || [];
74
+ arr.push(rel);
75
+ relsByParent.set(rel.parentSchemaName, arr);
76
+ const childArr = relsByChild.get(rel.childSchemaName) || [];
77
+ childArr.push(rel);
78
+ relsByChild.set(rel.childSchemaName, childArr);
79
+ }
80
+ const { pk, autoIncrement } = detectPrimaryKey(schema);
81
+ const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
82
+ const parentRels = relsByParent.get(schema.schemaName) || [];
83
+ const childRels = relsByChild.get(schema.schemaName) || [];
84
+ // Collect related schema names for commented imports
85
+ const relatedSchemas = new Set();
86
+ for (const rel of parentRels) {
87
+ if (rel.childSchemaName !== schema.schemaName)
88
+ relatedSchemas.add(rel.childSchemaName);
89
+ if (rel.parentSchemaName !== schema.schemaName)
90
+ relatedSchemas.add(rel.parentSchemaName);
91
+ }
92
+ for (const rel of childRels) {
93
+ if (rel.parentSchemaName !== schema.schemaName)
94
+ relatedSchemas.add(rel.parentSchemaName);
95
+ if (rel.childSchemaName !== schema.schemaName)
96
+ relatedSchemas.add(rel.childSchemaName);
97
+ }
98
+ const lines = [];
99
+ // Imports
100
+ lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
101
+ lines.push(`import {${schema.schemaName} as Schema} from '../schemas/${schema.schemaName}';`);
102
+ // Commented imports for related schemas
103
+ for (const relSchema of relatedSchemas) {
104
+ lines.push(`// import {${relSchema}} from '../schemas/${relSchema}';`);
105
+ }
106
+ lines.push(``);
107
+ // Extra filters placeholder
108
+ lines.push(`// const extraFiltersValidation = Type.Object({`);
109
+ lines.push(`// q: Type.String(),`);
110
+ lines.push(`// });`);
111
+ // Table definition
112
+ lines.push(``);
113
+ lines.push(`// Fields: ${schema.fields.join(', ')}`);
114
+ lines.push(`export const ${tableVarName} = defineTable({`);
115
+ lines.push(` primary: '${pk}',`);
116
+ lines.push(` ...exportTableInfo(Schema),`);
117
+ // Commented example with extraFilters + extendedCondition
118
+ lines.push(` // ...exportTableInfo(`);
119
+ lines.push(` // Schema,`);
120
+ lines.push(` // extraFiltersValidation,`);
121
+ lines.push(` // (condition, filters) => {`);
122
+ lines.push(` // if (filters.q) condition.isILike(Schema.col('${schema.fields.includes('name') ? 'name' : schema.fields[0]}'), \`%\${filters.q}%\`);`);
123
+ lines.push(` // }`);
124
+ lines.push(` // ),`);
125
+ lines.push(` defaultOrder: '${pk}',`);
126
+ if (autoIncrement) {
127
+ lines.push(` excludeFromCreation: ['${pk}'],`);
128
+ }
129
+ else {
130
+ lines.push(` // excludeFromCreation: [],`);
131
+ }
132
+ if (parentRels.length > 0) {
133
+ lines.push(` // allowedReadJoins: [`);
134
+ for (const rel of parentRels) {
135
+ lines.push(` // buildRelation(${rel.parentSchemaName}, '${rel.parentField}', ${rel.childSchemaName}, '${rel.childField}'),`);
136
+ }
137
+ lines.push(` // ],`);
138
+ }
139
+ else {
140
+ lines.push(` // allowedReadJoins: [],`);
141
+ }
142
+ lines.push(` // upsertMap: buildUpsertRules(buildUpsertRule(Schema, ['${pk}'])),`);
143
+ lines.push(` // beforeInsert: async (db, req, record) => {},`);
144
+ lines.push(` // beforeUpdate: async (db, req, fields) => {},`);
145
+ if (childRels.length > 0) {
146
+ const rel = childRels[0];
147
+ lines.push(` // tenantScope: { column: 'tenant_col', through: { schema: ${rel.parentSchemaName}, localField: '${rel.childField}', foreignField: '${rel.parentField}' } },`);
148
+ }
149
+ else {
150
+ lines.push(` // tenantScope: { column: 'tenant_col' },`);
151
+ }
152
+ lines.push(`});`);
153
+ lines.push(``);
154
+ return lines.join('\n');
155
+ }
156
+ // ─── DbTables Index Generation ──────────────────────────────
157
+ export function generateDbTablesIndex(schemas) {
158
+ const lines = [];
159
+ lines.push(`import type { DbTables } from 'fastify-auto-sqlapi';`);
160
+ lines.push(``);
161
+ for (const schema of schemas) {
162
+ const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
163
+ lines.push(`import {${tableVarName}} from './${tableVarName}';`);
164
+ }
165
+ lines.push(``);
166
+ lines.push(`export const dbTables: DbTables = {`);
167
+ for (const schema of schemas) {
168
+ const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
169
+ lines.push(` ${schema.tableName}: ${tableVarName},`);
170
+ }
171
+ lines.push(`};`);
172
+ lines.push(``);
173
+ return lines.join('\n');
174
+ }
175
+ // ─── Code Generation (legacy) ───────────────────────────────
176
+ export function generateTablesFile(schemas) {
177
+ const relations = detectRelations(schemas);
178
+ const relsByParent = new Map();
179
+ const relsByChild = new Map();
180
+ for (const rel of relations) {
181
+ const arr = relsByParent.get(rel.parentSchemaName) || [];
182
+ arr.push(rel);
183
+ relsByParent.set(rel.parentSchemaName, arr);
184
+ const childArr = relsByChild.get(rel.childSchemaName) || [];
185
+ childArr.push(rel);
186
+ relsByChild.set(rel.childSchemaName, childArr);
187
+ }
188
+ const lines = [];
189
+ // Imports
190
+ lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
191
+ lines.push(`import type { DbTables } from 'fastify-auto-sqlapi';`);
192
+ lines.push(``);
193
+ // Schema imports
194
+ for (const schema of schemas) {
195
+ lines.push(`import {${schema.schemaName}} from './${schema.schemaName}';`);
196
+ }
197
+ // Table definitions
198
+ for (const schema of schemas) {
199
+ const { pk, autoIncrement } = detectPrimaryKey(schema);
200
+ const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
201
+ const parentRels = relsByParent.get(schema.schemaName) || [];
202
+ lines.push(``);
203
+ lines.push(`// ─── ${schema.tableName} ──────────────────────────────`);
204
+ lines.push(`// Fields: ${schema.fields.join(', ')}`);
205
+ lines.push(`const ${tableVarName} = defineTable({`);
206
+ lines.push(` primary: '${pk}',`);
207
+ lines.push(` ...exportTableInfo(${schema.schemaName}),`);
208
+ lines.push(` defaultOrder: '${pk}',`);
209
+ if (autoIncrement) {
210
+ lines.push(` excludeFromCreation: ['${pk}'],`);
211
+ }
212
+ else {
213
+ lines.push(` // excludeFromCreation: [],`);
214
+ }
215
+ if (parentRels.length > 0) {
216
+ lines.push(` // allowedReadJoins: [`);
217
+ for (const rel of parentRels) {
218
+ lines.push(` // buildRelation(${rel.parentSchemaName}, '${rel.parentField}', ${rel.childSchemaName}, '${rel.childField}'),`);
219
+ }
220
+ lines.push(` // ],`);
221
+ }
222
+ else {
223
+ lines.push(` // allowedReadJoins: [],`);
224
+ }
225
+ lines.push(` // upsertMap: buildUpsertRules(buildUpsertRule(${schema.schemaName}, ['${pk}'])),`);
226
+ lines.push(` // beforeInsert: async (db, req, record) => {},`);
227
+ lines.push(` // beforeUpdate: async (db, req, fields) => {},`);
228
+ const childRels = relsByChild.get(schema.schemaName) || [];
229
+ if (childRels.length > 0) {
230
+ const rel = childRels[0];
231
+ lines.push(` // tenantScope: { column: 'tenant_col', through: { schema: ${rel.parentSchemaName}, localField: '${rel.childField}', foreignField: '${rel.parentField}' } },`);
232
+ }
233
+ else {
234
+ lines.push(` // tenantScope: { column: 'tenant_col' },`);
235
+ }
236
+ lines.push(`});`);
237
+ }
238
+ // DbTables export
239
+ lines.push(``);
240
+ lines.push(`// ─── DbTables ──────────────────────────────────────`);
241
+ lines.push(`export const dbTables: DbTables = {`);
242
+ for (const schema of schemas) {
243
+ const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
244
+ lines.push(` ${schema.tableName}: ${tableVarName},`);
245
+ }
246
+ lines.push(`};`);
247
+ lines.push(``);
248
+ return lines.join('\n');
249
+ }
@@ -0,0 +1,11 @@
1
+ export declare const SINGLE_VALUE_METHODS: readonly ["isEqual", "isNotEqual", "isGreater", "isNotGreater", "isGreaterOrEqual", "isNotGreaterOrEqual", "isLess", "isNotLess", "isLessOrEqual", "isNotLessOrEqual", "isLike", "isNotLike", "isILike", "isNotILike"];
2
+ export declare const BETWEEN_METHODS: readonly ["isBetween", "isNotBetween"];
3
+ export declare const IN_METHODS: readonly ["isIn", "isNotIn"];
4
+ export declare const NULL_METHODS: readonly ["isNull", "isNotNull"];
5
+ export declare const ALLOWED_METHODS: readonly ["isEqual", "isNotEqual", "isGreater", "isNotGreater", "isGreaterOrEqual", "isNotGreaterOrEqual", "isLess", "isNotLess", "isLessOrEqual", "isNotLessOrEqual", "isLike", "isNotLike", "isILike", "isNotILike", "isBetween", "isNotBetween", "isIn", "isNotIn", "isNull", "isNotNull"];
6
+ export declare const SINGLE_VALUE_SET: Set<string>;
7
+ export declare const BETWEEN_SET: Set<string>;
8
+ export declare const IN_SET: Set<string>;
9
+ export declare const NULL_SET: Set<string>;
10
+ export declare const ALLOWED_SET: Set<string>;
11
+ //# sourceMappingURL=condition-methods.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition-methods.d.ts","sourceRoot":"","sources":["../../src/lib/condition-methods.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB,wNAKvB,CAAC;AAEX,eAAO,MAAM,eAAe,wCAAyC,CAAC;AACtE,eAAO,MAAM,UAAU,8BAA+B,CAAC;AACvD,eAAO,MAAM,YAAY,kCAAmC,CAAC;AAE7D,eAAO,MAAM,eAAe,+RAElB,CAAC;AAGX,eAAO,MAAM,gBAAgB,aAAwC,CAAC;AACtE,eAAO,MAAM,WAAW,aAAmC,CAAC;AAC5D,eAAO,MAAM,MAAM,aAA8B,CAAC;AAClD,eAAO,MAAM,QAAQ,aAAgC,CAAC;AACtD,eAAO,MAAM,WAAW,aAAmC,CAAC"}
@@ -0,0 +1,20 @@
1
+ // Allowed ConditionBuilder methods for the `conditions` search API.
2
+ // Single source of truth for both schema validation (Swagger) and runtime whitelist.
3
+ export const SINGLE_VALUE_METHODS = [
4
+ 'isEqual', 'isNotEqual',
5
+ 'isGreater', 'isNotGreater', 'isGreaterOrEqual', 'isNotGreaterOrEqual',
6
+ 'isLess', 'isNotLess', 'isLessOrEqual', 'isNotLessOrEqual',
7
+ 'isLike', 'isNotLike', 'isILike', 'isNotILike',
8
+ ];
9
+ export const BETWEEN_METHODS = ['isBetween', 'isNotBetween'];
10
+ export const IN_METHODS = ['isIn', 'isNotIn'];
11
+ export const NULL_METHODS = ['isNull', 'isNotNull'];
12
+ export const ALLOWED_METHODS = [
13
+ ...SINGLE_VALUE_METHODS, ...BETWEEN_METHODS, ...IN_METHODS, ...NULL_METHODS,
14
+ ];
15
+ // Runtime Sets for fast lookup
16
+ export const SINGLE_VALUE_SET = new Set(SINGLE_VALUE_METHODS);
17
+ export const BETWEEN_SET = new Set(BETWEEN_METHODS);
18
+ export const IN_SET = new Set(IN_METHODS);
19
+ export const NULL_SET = new Set(NULL_METHODS);
20
+ export const ALLOWED_SET = new Set(ALLOWED_METHODS);
@@ -0,0 +1,28 @@
1
+ import { Expression } from 'node-condition-builder';
2
+ import type { ConditionBuilder } from 'node-condition-builder';
3
+ import type { Queryable, SqlResult, DbRecord, SelectOptions } from '../types.js';
4
+ import type { SqlDialect } from './dialect.js';
5
+ /** @deprecated Use db.qi() instead */
6
+ export declare const escapeIdent: (f: string) => string;
7
+ export declare class QueryClient {
8
+ #private;
9
+ private client;
10
+ private dialect;
11
+ private debug;
12
+ constructor(client: Queryable, dialect?: SqlDialect);
13
+ get qi(): (id: string) => string;
14
+ get ph(): (i: number) => string;
15
+ get dialectName(): string;
16
+ setDebug(mode: boolean): void;
17
+ expression(value: string): Expression;
18
+ query<T = Record<string, unknown>>(text: string, values?: unknown[]): Promise<SqlResult<T>>;
19
+ insert(table: string, record: DbRecord, pkCol: string | string[]): Promise<Record<string, unknown>>;
20
+ insertOrUpdate(table: string, record: DbRecord, conflictKeys: string[], pkCol: string | string[]): Promise<Record<string, unknown>>;
21
+ bulkInsert(table: string, records: DbRecord[], pkCol: string | string[], chunkSize?: number): Promise<Record<string, unknown>[]>;
22
+ bulkInsertOrUpdate(table: string, records: DbRecord[], conflictKeys: string[], pkCol: string | string[], chunkSize?: number): Promise<Record<string, unknown>[]>;
23
+ update(table: string, record: DbRecord, where: DbRecord, extraCondition?: ConditionBuilder): Promise<number>;
24
+ delete(table: string, where: DbRecord): Promise<number>;
25
+ select<T = Record<string, unknown>>({ tableName, columns, where, values, limit, orderBy, joins, distinct, }: SelectOptions): Promise<T[]>;
26
+ }
27
+ export declare function createQueryClient(pool: Queryable, dialectName?: string): QueryClient;
28
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK/C,sCAAsC;AACtC,eAAO,MAAM,WAAW,GAAI,GAAG,MAAM,WAA0B,CAAC;AAEhE,qBAAa,WAAW;;IACtB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,UAAU;IAKnD,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAE/B;IAED,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAE9B;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI7B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAIrC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IA6FlB,MAAM,CACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAoB7B,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,EAChB,YAAY,EAAE,MAAM,EAAE,EACtB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAuB7B,UAAU,CACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,QAAQ,EAAE,EACnB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,SAAS,SAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IA2B/B,kBAAkB,CACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,QAAQ,EAAE,EACnB,YAAY,EAAE,MAAM,EAAE,EACtB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,SAAS,SAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IA+B/B,MAAM,CACV,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,EAChB,KAAK,EAAE,QAAQ,EACf,cAAc,CAAC,EAAE,gBAAgB,GAChC,OAAO,CAAC,MAAM,CAAC;IAuBZ,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,QAAQ,GACd,OAAO,CAAC,MAAM,CAAC;IAYZ,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACxC,SAAS,EACT,OAAa,EACb,KAAK,EACL,MAAM,EACN,KAAY,EACZ,OAAY,EACZ,KAAU,EACV,QAAgB,GACjB,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;CAchC;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,WAAW,CAGb"}
package/dist/lib/db.js ADDED
@@ -0,0 +1,224 @@
1
+ import { Expression } from 'node-condition-builder';
2
+ import { getDialect } from './dialect.js';
3
+ const DEFAULT_CHUNK_SIZE = 500;
4
+ /** @deprecated Use db.qi() instead */
5
+ export const escapeIdent = (f) => f.replace(/"/g, '""');
6
+ export class QueryClient {
7
+ client;
8
+ dialect;
9
+ debug = false;
10
+ constructor(client, dialect) {
11
+ this.client = client;
12
+ this.dialect = dialect ?? getDialect('postgres');
13
+ }
14
+ get qi() {
15
+ return this.dialect.qi;
16
+ }
17
+ get ph() {
18
+ return this.dialect.ph;
19
+ }
20
+ get dialectName() {
21
+ return this.dialect.name;
22
+ }
23
+ setDebug(mode) {
24
+ this.debug = mode;
25
+ }
26
+ expression(value) {
27
+ return new Expression(value);
28
+ }
29
+ query(text, values) {
30
+ if (this.debug) {
31
+ console.log('SQL:', text.replace(/\s+/g, ' ').trim());
32
+ console.log('PARAMS:', values || []);
33
+ }
34
+ return this.client.query(text, values);
35
+ }
36
+ // Quote a list of fields
37
+ #q(fields) {
38
+ return fields.map((f) => this.dialect.qi(f)).join(', ');
39
+ }
40
+ // Appends record fields/values to a shared values array, auto-indexing placeholders
41
+ #params(record, values) {
42
+ const fields = [];
43
+ const placeholders = [];
44
+ for (const [field, val] of Object.entries(record)) {
45
+ fields.push(field);
46
+ if (val instanceof Expression) {
47
+ placeholders.push(val.value);
48
+ }
49
+ else {
50
+ values.push(val);
51
+ placeholders.push(this.dialect.ph(values.length));
52
+ }
53
+ }
54
+ return { fields, placeholders };
55
+ }
56
+ // Builds multi-row VALUES string for bulk operations
57
+ #bulkValues(fields, records) {
58
+ const values = [];
59
+ const rowPlaceholders = [];
60
+ for (const record of records) {
61
+ const cols = [];
62
+ for (const field of fields) {
63
+ const val = record[field];
64
+ if (val instanceof Expression) {
65
+ cols.push(val.value);
66
+ }
67
+ else {
68
+ values.push(val);
69
+ cols.push(this.dialect.ph(values.length));
70
+ }
71
+ }
72
+ rowPlaceholders.push(`(${cols.join(', ')})`);
73
+ }
74
+ return { values, rows: rowPlaceholders.join(', ') };
75
+ }
76
+ // Build the update SET for upsert: dialect-specific
77
+ #upsertUpdateSet(fields, conflictKeys) {
78
+ const updateCols = fields.filter((f) => !conflictKeys.includes(f));
79
+ if (!updateCols.length)
80
+ return '';
81
+ if (this.dialect.name === 'postgres') {
82
+ return updateCols
83
+ .map((f) => `${this.dialect.qi(f)} = EXCLUDED.${this.dialect.qi(f)}`)
84
+ .join(', ');
85
+ }
86
+ // MySQL/MariaDB: VALUES(col) syntax
87
+ return updateCols
88
+ .map((f) => `${this.dialect.qi(f)} = VALUES(${this.dialect.qi(f)})`)
89
+ .join(', ');
90
+ }
91
+ #quotedPkCols(pkCol) {
92
+ const cols = Array.isArray(pkCol) ? pkCol : [pkCol];
93
+ return cols.map((c) => this.dialect.qi(c)).join(', ');
94
+ }
95
+ #mysqlPkResult(pkCol, record, insertId) {
96
+ const cols = Array.isArray(pkCol) ? pkCol : [pkCol];
97
+ const result = {};
98
+ for (const col of cols) {
99
+ if (col in record && record[col] != null) {
100
+ result[col] = record[col];
101
+ }
102
+ }
103
+ if (Object.keys(result).length > 0)
104
+ return result;
105
+ if (!Array.isArray(pkCol))
106
+ return { [pkCol]: insertId };
107
+ return result;
108
+ }
109
+ async insert(table, record, pkCol) {
110
+ const values = [];
111
+ const { fields, placeholders } = this.#params(record, values);
112
+ if (!fields.length)
113
+ throw new Error('Cannot execute empty insert');
114
+ const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
115
+ const result = await this.query(`INSERT INTO ${this.dialect.qi(table)} (${this.#q(fields)})
116
+ VALUES (${placeholders.join(', ')})${returning}`, values);
117
+ if (this.dialect.supportsReturning) {
118
+ return result.rows[0];
119
+ }
120
+ return this.#mysqlPkResult(pkCol, record, result.insertId);
121
+ }
122
+ async insertOrUpdate(table, record, conflictKeys, pkCol) {
123
+ const values = [];
124
+ const { fields, placeholders } = this.#params(record, values);
125
+ if (!fields.length)
126
+ throw new Error('Cannot execute empty insert');
127
+ const updateSet = this.#upsertUpdateSet(fields, conflictKeys);
128
+ const upsertClause = this.dialect.upsertSql(this.#q(conflictKeys), updateSet);
129
+ const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
130
+ const result = await this.query(`INSERT INTO ${this.dialect.qi(table)} (${this.#q(fields)})
131
+ VALUES (${placeholders.join(', ')})
132
+ ${upsertClause}${returning}`, values);
133
+ if (this.dialect.supportsReturning) {
134
+ return result.rows[0];
135
+ }
136
+ return this.#mysqlPkResult(pkCol, record, result.insertId);
137
+ }
138
+ async bulkInsert(table, records, pkCol, chunkSize = DEFAULT_CHUNK_SIZE) {
139
+ if (!records.length)
140
+ return [];
141
+ const fields = Object.keys(records[0]);
142
+ const results = [];
143
+ const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
144
+ for (let i = 0; i < records.length; i += chunkSize) {
145
+ const chunk = records.slice(i, i + chunkSize);
146
+ const { values, rows } = this.#bulkValues(fields, chunk);
147
+ const result = await this.query(`INSERT INTO ${this.dialect.qi(table)} (${this.#q(fields)}) VALUES ${rows}${returning}`, values);
148
+ if (this.dialect.supportsReturning) {
149
+ results.push(...result.rows);
150
+ }
151
+ else {
152
+ for (const rec of chunk) {
153
+ results.push(this.#mysqlPkResult(pkCol, rec, result.insertId));
154
+ }
155
+ }
156
+ }
157
+ return results;
158
+ }
159
+ async bulkInsertOrUpdate(table, records, conflictKeys, pkCol, chunkSize = DEFAULT_CHUNK_SIZE) {
160
+ if (!records.length)
161
+ return [];
162
+ const fields = Object.keys(records[0]);
163
+ const updateSet = this.#upsertUpdateSet(fields, conflictKeys);
164
+ const upsertClause = this.dialect.upsertSql(this.#q(conflictKeys), updateSet);
165
+ const returning = this.dialect.returningPk(this.#quotedPkCols(pkCol));
166
+ const results = [];
167
+ for (let i = 0; i < records.length; i += chunkSize) {
168
+ const chunk = records.slice(i, i + chunkSize);
169
+ const { values, rows } = this.#bulkValues(fields, chunk);
170
+ const result = await this.query(`INSERT INTO ${this.dialect.qi(table)} (${this.#q(fields)})
171
+ VALUES ${rows}
172
+ ${upsertClause}${returning}`, values);
173
+ if (this.dialect.supportsReturning) {
174
+ results.push(...result.rows);
175
+ }
176
+ else {
177
+ for (const rec of chunk) {
178
+ results.push(this.#mysqlPkResult(pkCol, rec, result.insertId));
179
+ }
180
+ }
181
+ }
182
+ return results;
183
+ }
184
+ async update(table, record, where, extraCondition) {
185
+ const values = [];
186
+ const { fields: setF, placeholders: setP } = this.#params(record, values);
187
+ const { fields: whereF, placeholders: whereP } = this.#params(where, values);
188
+ let extraWhere = '';
189
+ if (extraCondition) {
190
+ extraWhere = ` AND ${extraCondition.build(values.length + 1, this.dialect.ph)}`;
191
+ values.push(...extraCondition.getValues());
192
+ }
193
+ const result = await this.query(`UPDATE ${this.dialect.qi(table)}
194
+ SET ${setF.map((f, i) => `${this.dialect.qi(f)} = ${setP[i]}`).join(', ')}
195
+ WHERE ${whereF.map((f, i) => `${this.dialect.qi(f)} = ${whereP[i]}`).join(' AND ')}${extraWhere}`, values);
196
+ return result.affectedRows;
197
+ }
198
+ async delete(table, where) {
199
+ const values = [];
200
+ const { fields, placeholders } = this.#params(where, values);
201
+ const result = await this.query(`DELETE FROM ${this.dialect.qi(table)}
202
+ WHERE ${fields.map((f, i) => `${this.dialect.qi(f)} = ${placeholders[i]}`).join(' AND ')}`, values);
203
+ return result.affectedRows;
204
+ }
205
+ async select({ tableName, columns = '*', where, values, limit = null, orderBy = '', joins = [], distinct = false, }) {
206
+ const cols = columns === '*' ? `${this.dialect.qi(tableName)}.*` : columns;
207
+ const parts = [
208
+ `SELECT${distinct ? ' DISTINCT' : ''} ${cols}`,
209
+ `FROM ${this.dialect.qi(tableName)}`,
210
+ ...joins,
211
+ `WHERE ${where}`,
212
+ ];
213
+ if (orderBy)
214
+ parts.push(`ORDER BY ${orderBy}`);
215
+ if (limit !== null)
216
+ parts.push(`LIMIT ${limit}`);
217
+ const result = await this.query(parts.join('\n'), values);
218
+ return result.rows;
219
+ }
220
+ }
221
+ export function createQueryClient(pool, dialectName) {
222
+ const dialect = getDialect((dialectName || 'postgres'));
223
+ return new QueryClient(pool, dialect);
224
+ }