fastify-auto-sqlapi 0.1.14 → 0.2.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.
- package/AGENTS_BACKEND.md +52 -7
- package/BREAKING_CHANGES.md +86 -5
- package/CHANGELOG.md +57 -0
- package/README.md +110 -3
- package/dist/bin/generate-schema.js +89 -77
- package/dist/bin/generate-tables.js +72 -62
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +47 -33
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/agent/manifest.d.ts +3 -2
- package/dist/lib/agent/manifest.d.ts.map +1 -1
- package/dist/lib/agent/manifest.js +102 -80
- package/dist/lib/cli/mysql-introspect.d.ts +4 -9
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +8 -7
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +0 -1
- package/dist/lib/cli/schema-codegen.d.ts +3 -0
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +3 -0
- package/dist/lib/cli/tables-codegen.d.ts +8 -1
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +100 -118
- package/dist/lib/condition-methods.d.ts +14 -4
- package/dist/lib/condition-methods.d.ts.map +1 -1
- package/dist/lib/condition-methods.js +18 -4
- package/dist/lib/db.d.ts +7 -2
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +24 -6
- package/dist/lib/dialect.d.ts +2 -1
- package/dist/lib/dialect.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +44 -36
- package/dist/lib/engine/rest/delete.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +5 -4
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +1 -1
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +26 -19
- package/dist/lib/engine/search/aggregations.d.ts +33 -0
- package/dist/lib/engine/search/aggregations.d.ts.map +1 -0
- package/dist/lib/engine/search/aggregations.js +109 -0
- package/dist/lib/engine/search/conditions.d.ts +31 -0
- package/dist/lib/engine/search/conditions.d.ts.map +1 -0
- package/dist/lib/engine/search/conditions.js +131 -0
- package/dist/lib/engine/search/fields.d.ts +49 -0
- package/dist/lib/engine/search/fields.d.ts.map +1 -0
- package/dist/lib/engine/search/fields.js +142 -0
- package/dist/lib/engine/search/join-families.d.ts +35 -0
- package/dist/lib/engine/search/join-families.d.ts.map +1 -0
- package/dist/lib/engine/search/join-families.js +345 -0
- package/dist/lib/engine/search/joins.d.ts +55 -0
- package/dist/lib/engine/search/joins.d.ts.map +1 -0
- package/dist/lib/engine/search/joins.js +81 -0
- package/dist/lib/engine/search/order-by.d.ts +35 -0
- package/dist/lib/engine/search/order-by.d.ts.map +1 -0
- package/dist/lib/engine/search/order-by.js +104 -0
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +106 -791
- package/dist/lib/engine/validate.d.ts +0 -5
- package/dist/lib/engine/validate.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.d.ts +25 -17
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +86 -44
- package/dist/lib/errors.d.ts +24 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +59 -0
- package/dist/lib/read-access.d.ts +12 -1
- package/dist/lib/read-access.d.ts.map +1 -1
- package/dist/lib/read-access.js +20 -4
- package/dist/lib/schema/helpers.d.ts +0 -15
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +7 -4
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +107 -128
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +48 -0
- package/dist/lib/tenant.d.ts +66 -5
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +209 -48
- package/dist/routes/auto/plugin.d.ts.map +1 -1
- package/dist/routes/auto/plugin.js +6 -0
- package/dist/routes/auto/route-helpers.d.ts +0 -7
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +58 -33
- package/dist/routes/auto/search.routes.js +0 -1
- package/dist/types/plugin.d.ts +19 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/table.d.ts +1 -1
- package/dist/types/tenant.d.ts +26 -2
- package/dist/types/tenant.d.ts.map +1 -1
- package/docs/GLOSSARY.md +30 -0
- package/docs/adr/0006-raw-db-errors.md +2 -1
- package/docs/adr/0010-joins-do-not-run-route-guards.md +4 -1
- package/docs/adr/0012-anti-duplication-tooling.md +59 -0
- package/docs/adr/0013-sanitized-db-errors.md +114 -0
- package/docs/adr/README.md +4 -1
- package/package.json +29 -2
|
@@ -40,6 +40,65 @@ export function tableOperations(tableConf) {
|
|
|
40
40
|
return [...requested];
|
|
41
41
|
return requested.filter((op) => !SINGLE_PK_OPERATIONS.has(op));
|
|
42
42
|
}
|
|
43
|
+
/** Field descriptors: type, plus the flags an agent needs to build a valid payload. */
|
|
44
|
+
function manifestFields(tableConf) {
|
|
45
|
+
const schema = tableConf.Schema;
|
|
46
|
+
const required = new Set((schema.validation.required) ?? []);
|
|
47
|
+
const readExcluded = new Set(tableConf.readExclude ?? []);
|
|
48
|
+
const fields = {};
|
|
49
|
+
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
50
|
+
const f = { type: shortType(fieldSchema) };
|
|
51
|
+
if (required.has(name))
|
|
52
|
+
f.required = true;
|
|
53
|
+
if (isNullable(fieldSchema))
|
|
54
|
+
f.nullable = true;
|
|
55
|
+
if (readExcluded.has(name))
|
|
56
|
+
f.writeOnly = true;
|
|
57
|
+
fields[name] = f;
|
|
58
|
+
}
|
|
59
|
+
return fields;
|
|
60
|
+
}
|
|
61
|
+
function manifestJoins(joins) {
|
|
62
|
+
return (joins ?? []).map((j) => ({
|
|
63
|
+
alias: j.alias,
|
|
64
|
+
table: j.joinSchema.tableName,
|
|
65
|
+
kind: j.unique ? 'N:1' : '1:N',
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
function manifestExtraFilters(tableConf) {
|
|
69
|
+
const entries = Object.entries(tableConf.extraFilters ?? {});
|
|
70
|
+
if (entries.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
return Object.fromEntries(entries.map(([n, s]) => [n, shortType(s)]));
|
|
73
|
+
}
|
|
74
|
+
/** One table's entry. Optional keys are omitted entirely when empty, to keep the prompt small. */
|
|
75
|
+
function manifestTable(tableConf) {
|
|
76
|
+
const entry = {
|
|
77
|
+
table: tableConf.Schema.tableName,
|
|
78
|
+
primary: tableConf.primary,
|
|
79
|
+
operations: tableOperations(tableConf),
|
|
80
|
+
fields: manifestFields(tableConf),
|
|
81
|
+
};
|
|
82
|
+
const computed = computedTypes(tableConf);
|
|
83
|
+
const extraFilters = manifestExtraFilters(tableConf);
|
|
84
|
+
const readJoins = manifestJoins(tableConf.allowedReadJoins);
|
|
85
|
+
const writeJoins = manifestJoins(tableConf.allowedWriteJoins);
|
|
86
|
+
if (tableConf.excludeFromCreation?.length)
|
|
87
|
+
entry.serverGenerated = [...tableConf.excludeFromCreation];
|
|
88
|
+
if (computed)
|
|
89
|
+
entry.computed = computed;
|
|
90
|
+
if (extraFilters)
|
|
91
|
+
entry.extraFilters = extraFilters;
|
|
92
|
+
if (readJoins.length)
|
|
93
|
+
entry.readJoins = readJoins;
|
|
94
|
+
if (writeJoins.length)
|
|
95
|
+
entry.writeJoins = writeJoins;
|
|
96
|
+
if (tableConf.tenantScope)
|
|
97
|
+
entry.tenantScoped = true;
|
|
98
|
+
if (tableConf.defaultOrder)
|
|
99
|
+
entry.defaultOrder = tableConf.defaultOrder;
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
43
102
|
/**
|
|
44
103
|
* Build the machine-readable description of every table exposed by this deployment:
|
|
45
104
|
* fields (type/required/nullable), enabled operations, join aliases, computed fields,
|
|
@@ -50,55 +109,7 @@ export function tableOperations(tableConf) {
|
|
|
50
109
|
export function buildAgentManifest(dbTables) {
|
|
51
110
|
const tables = {};
|
|
52
111
|
for (const [key, tableConf] of Object.entries(dbTables)) {
|
|
53
|
-
|
|
54
|
-
const required = new Set((schema.validation.required) ?? []);
|
|
55
|
-
const readExcluded = new Set(tableConf.readExclude ?? []);
|
|
56
|
-
const fields = {};
|
|
57
|
-
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
58
|
-
const f = { type: shortType(fieldSchema) };
|
|
59
|
-
if (required.has(name))
|
|
60
|
-
f.required = true;
|
|
61
|
-
if (isNullable(fieldSchema))
|
|
62
|
-
f.nullable = true;
|
|
63
|
-
if (readExcluded.has(name))
|
|
64
|
-
f.writeOnly = true;
|
|
65
|
-
fields[name] = f;
|
|
66
|
-
}
|
|
67
|
-
const readJoins = (tableConf.allowedReadJoins ?? []).map((j) => ({
|
|
68
|
-
alias: j.alias,
|
|
69
|
-
table: j.joinSchema.tableName,
|
|
70
|
-
kind: j.unique ? 'N:1' : '1:N',
|
|
71
|
-
}));
|
|
72
|
-
const writeJoins = (tableConf.allowedWriteJoins ?? []).map((j) => ({
|
|
73
|
-
alias: j.alias,
|
|
74
|
-
table: j.joinSchema.tableName,
|
|
75
|
-
kind: j.unique ? 'N:1' : '1:N',
|
|
76
|
-
}));
|
|
77
|
-
const extraFilters = Object.keys(tableConf.extraFilters ?? {}).length
|
|
78
|
-
? Object.fromEntries(Object.entries(tableConf.extraFilters).map(([n, s]) => [n, shortType(s)]))
|
|
79
|
-
: undefined;
|
|
80
|
-
const entry = {
|
|
81
|
-
table: schema.tableName,
|
|
82
|
-
primary: tableConf.primary,
|
|
83
|
-
operations: tableOperations(tableConf),
|
|
84
|
-
fields,
|
|
85
|
-
};
|
|
86
|
-
if (tableConf.excludeFromCreation?.length)
|
|
87
|
-
entry.serverGenerated = [...tableConf.excludeFromCreation];
|
|
88
|
-
const computed = computedTypes(tableConf);
|
|
89
|
-
if (computed)
|
|
90
|
-
entry.computed = computed;
|
|
91
|
-
if (extraFilters)
|
|
92
|
-
entry.extraFilters = extraFilters;
|
|
93
|
-
if (readJoins.length)
|
|
94
|
-
entry.readJoins = readJoins;
|
|
95
|
-
if (writeJoins.length)
|
|
96
|
-
entry.writeJoins = writeJoins;
|
|
97
|
-
if (tableConf.tenantScope)
|
|
98
|
-
entry.tenantScoped = true;
|
|
99
|
-
if (tableConf.defaultOrder)
|
|
100
|
-
entry.defaultOrder = tableConf.defaultOrder;
|
|
101
|
-
tables[key] = entry;
|
|
112
|
+
tables[key] = manifestTable(tableConf);
|
|
102
113
|
}
|
|
103
114
|
return { tables };
|
|
104
115
|
}
|
|
@@ -119,37 +130,48 @@ export function renderAgentManifestMd(manifest) {
|
|
|
119
130
|
'',
|
|
120
131
|
];
|
|
121
132
|
for (const t of Object.values(manifest.tables)) {
|
|
122
|
-
|
|
123
|
-
const flags = t.tenantScoped ? ' [tenant-scoped]' : '';
|
|
124
|
-
lines.push(`## ${t.table} PK:${pk} ops:${t.operations.join(',')}${flags}`);
|
|
125
|
-
const fieldParts = Object.entries(t.fields).map(([name, f]) => {
|
|
126
|
-
let s = `${name}:${f.type}`;
|
|
127
|
-
if (f.required)
|
|
128
|
-
s += '!';
|
|
129
|
-
if (f.nullable)
|
|
130
|
-
s += '?';
|
|
131
|
-
if (f.writeOnly)
|
|
132
|
-
s += '(writeOnly)';
|
|
133
|
-
return s;
|
|
134
|
-
});
|
|
135
|
-
lines.push(`fields: ${fieldParts.join(', ')}`);
|
|
136
|
-
if (t.serverGenerated)
|
|
137
|
-
lines.push(`serverGenerated: ${t.serverGenerated.join(', ')}`);
|
|
138
|
-
if (t.computed) {
|
|
139
|
-
lines.push(`computed: ${Object.entries(t.computed).map(([n, ty]) => `${n}:${ty}`).join(', ')}`);
|
|
140
|
-
}
|
|
141
|
-
if (t.extraFilters) {
|
|
142
|
-
lines.push(`extraFilters: ${Object.entries(t.extraFilters).map(([n, ty]) => `${n}:${ty}`).join(', ')}`);
|
|
143
|
-
}
|
|
144
|
-
if (t.readJoins) {
|
|
145
|
-
lines.push(`readJoins: ${t.readJoins.map((j) => `${j.alias}→${j.table}(${j.kind})`).join(', ')}`);
|
|
146
|
-
}
|
|
147
|
-
if (t.writeJoins) {
|
|
148
|
-
lines.push(`writeJoins: ${t.writeJoins.map((j) => `${j.alias}→${j.table}`).join(', ')}`);
|
|
149
|
-
}
|
|
150
|
-
if (t.defaultOrder)
|
|
151
|
-
lines.push(`defaultOrder: ${t.defaultOrder}`);
|
|
152
|
-
lines.push('');
|
|
133
|
+
lines.push(...renderTableMd(t));
|
|
153
134
|
}
|
|
154
135
|
return lines.join('\n');
|
|
155
136
|
}
|
|
137
|
+
/** `name:type` pairs — the compact form used throughout the manifest. */
|
|
138
|
+
function namedTypes(map) {
|
|
139
|
+
return Object.entries(map).map(([n, ty]) => `${n}:${ty}`).join(', ');
|
|
140
|
+
}
|
|
141
|
+
/** A field's type with its notation suffixes: `!` required, `?` nullable, `(writeOnly)`. */
|
|
142
|
+
function fieldNotation(f) {
|
|
143
|
+
let s = f.type;
|
|
144
|
+
if (f.required)
|
|
145
|
+
s += '!';
|
|
146
|
+
if (f.nullable)
|
|
147
|
+
s += '?';
|
|
148
|
+
if (f.writeOnly)
|
|
149
|
+
s += '(writeOnly)';
|
|
150
|
+
return s;
|
|
151
|
+
}
|
|
152
|
+
function renderTableMd(t) {
|
|
153
|
+
const pk = Array.isArray(t.primary) ? t.primary.join('+') : t.primary;
|
|
154
|
+
const flags = t.tenantScoped ? ' [tenant-scoped]' : '';
|
|
155
|
+
const ops = t.operations.join(',');
|
|
156
|
+
const lines = [`## ${t.table} PK:${pk} ops:${ops}${flags}`];
|
|
157
|
+
const fieldParts = Object.entries(t.fields).map(([name, f]) => `${name}:${fieldNotation(f)}`);
|
|
158
|
+
lines.push(`fields: ${fieldParts.join(', ')}`);
|
|
159
|
+
if (t.serverGenerated)
|
|
160
|
+
lines.push(`serverGenerated: ${t.serverGenerated.join(', ')}`);
|
|
161
|
+
if (t.computed)
|
|
162
|
+
lines.push(`computed: ${namedTypes(t.computed)}`);
|
|
163
|
+
if (t.extraFilters)
|
|
164
|
+
lines.push(`extraFilters: ${namedTypes(t.extraFilters)}`);
|
|
165
|
+
if (t.readJoins) {
|
|
166
|
+
const readJoins = t.readJoins.map((j) => `${j.alias}→${j.table}(${j.kind})`);
|
|
167
|
+
lines.push(`readJoins: ${readJoins.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
if (t.writeJoins) {
|
|
170
|
+
const writeJoins = t.writeJoins.map((j) => `${j.alias}→${j.table}`);
|
|
171
|
+
lines.push(`writeJoins: ${writeJoins.join(', ')}`);
|
|
172
|
+
}
|
|
173
|
+
if (t.defaultOrder)
|
|
174
|
+
lines.push(`defaultOrder: ${t.defaultOrder}`);
|
|
175
|
+
lines.push('');
|
|
176
|
+
return lines;
|
|
177
|
+
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import type { ColumnInfo } from '../../types.js';
|
|
2
|
-
export
|
|
2
|
+
export interface MysqlConnectionConfig {
|
|
3
3
|
host: string;
|
|
4
4
|
port: number;
|
|
5
5
|
user: string;
|
|
6
6
|
password: string;
|
|
7
7
|
database: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function
|
|
10
|
-
|
|
11
|
-
port: number;
|
|
12
|
-
user: string;
|
|
13
|
-
password: string;
|
|
14
|
-
database: string;
|
|
15
|
-
}, schema: string): Promise<ColumnInfo[]>;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildMysqlConnectionConfig(): MysqlConnectionConfig;
|
|
10
|
+
export declare function introspectMysqlTables(connectionConfig: MysqlConnectionConfig, schema: string): Promise<ColumnInfo[]>;
|
|
16
11
|
//# sourceMappingURL=mysql-introspect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mysql-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/mysql-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,
|
|
1
|
+
{"version":3,"file":"mysql-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/mysql-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoCD,wBAAgB,0BAA0B,IAAI,qBAAqB,CAmBlE;AAkDD,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE,qBAAqB,EACvC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CA4BvB"}
|
|
@@ -66,7 +66,6 @@ function mapMysqlType(dataType) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
export async function introspectMysqlTables(connectionConfig, schema) {
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
69
|
const mysql2 = loadOptionalDependency('mysql2/promise', 'npm install mysql2');
|
|
71
70
|
const connection = await mysql2.createConnection(connectionConfig);
|
|
72
71
|
try {
|
|
@@ -74,13 +73,15 @@ export async function introspectMysqlTables(connectionConfig, schema) {
|
|
|
74
73
|
FROM information_schema.columns
|
|
75
74
|
WHERE table_schema = ?
|
|
76
75
|
ORDER BY table_name, ordinal_position`, [schema]);
|
|
77
|
-
//
|
|
76
|
+
// `||` is kept as-is from the untyped version: this change types the driver, it does
|
|
77
|
+
// not alter behaviour. The trailing fallbacks only satisfy ColumnInfo, which cannot
|
|
78
|
+
// hold `undefined`.
|
|
78
79
|
return rows.map((row) => ({
|
|
79
|
-
table_name: row.TABLE_NAME || row.table_name,
|
|
80
|
-
column_name: row.COLUMN_NAME || row.column_name,
|
|
81
|
-
udt_name: mapMysqlType(row.DATA_TYPE || row.data_type),
|
|
82
|
-
column_default: row.COLUMN_DEFAULT || row.column_default,
|
|
83
|
-
is_nullable: row.IS_NULLABLE || row.is_nullable,
|
|
80
|
+
table_name: row.TABLE_NAME || row.table_name || '',
|
|
81
|
+
column_name: row.COLUMN_NAME || row.column_name || '',
|
|
82
|
+
udt_name: mapMysqlType(row.DATA_TYPE || row.data_type || ''),
|
|
83
|
+
column_default: row.COLUMN_DEFAULT || row.column_default || null,
|
|
84
|
+
is_nullable: row.IS_NULLABLE || row.is_nullable || '',
|
|
84
85
|
is_primary: (row.COLUMN_KEY || row.column_key) === 'PRI',
|
|
85
86
|
is_auto_increment: String(row.EXTRA || row.extra || '').includes('auto_increment'),
|
|
86
87
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/pg-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"pg-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/pg-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAmBjD,wBAAgB,qBAAqB,IAAI,MAAM,CAY9C;AAED,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB"}
|
|
@@ -11,7 +11,6 @@ export function buildConnectionString() {
|
|
|
11
11
|
return `postgres://${user}:${password}@${host}:${port}/${db}`;
|
|
12
12
|
}
|
|
13
13
|
export async function introspectTables(connectionString, schema) {
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
14
|
const pg = loadOptionalDependency('pg', 'npm install pg');
|
|
16
15
|
const client = new pg.Client({ connectionString });
|
|
17
16
|
try {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ColumnInfo, TableMap } from '../../types.js';
|
|
2
|
+
/** Maps a DB column type to its TypeBox schema expression.
|
|
3
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
4
|
+
*/
|
|
2
5
|
export declare function convertColType(udtName: string, columnInfo: {
|
|
3
6
|
column_default: string | null;
|
|
4
7
|
is_nullable: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/schema-codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9F,MAAM,CA0DR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,CAqC1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAoBzF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,CA8CR"}
|
|
1
|
+
{"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/schema-codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3D;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9F,MAAM,CA0DR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,CAqC1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAoBzF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,CA8CR"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { toCamelCase, toSchemaName } from '../naming.js';
|
|
2
|
+
/** Maps a DB column type to its TypeBox schema expression.
|
|
3
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
4
|
+
*/
|
|
2
5
|
export function convertColType(udtName, columnInfo) {
|
|
3
6
|
// AUTO_INCREMENT (mysql) behaves like a default: the DB generates the value.
|
|
4
7
|
const hasDefault = columnInfo.column_default || columnInfo.is_auto_increment;
|
|
@@ -6,15 +6,22 @@ export interface ParsedSchema {
|
|
|
6
6
|
/** PRIMARY KEY fields declared in the schema file (`primaryKey: [...]`), when present. */
|
|
7
7
|
primary?: string[];
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
interface DetectedRelation {
|
|
10
10
|
parentSchemaName: string;
|
|
11
11
|
parentField: string;
|
|
12
12
|
childSchemaName: string;
|
|
13
13
|
childField: string;
|
|
14
14
|
}
|
|
15
15
|
export declare function parseSchemaFile(content: string): ParsedSchema | null;
|
|
16
|
+
/** Infers FK relations between parsed schemas.
|
|
17
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
18
|
+
*/
|
|
16
19
|
export declare function detectRelations(schemas: ParsedSchema[]): DetectedRelation[];
|
|
17
20
|
export declare function generateSingleTableFile(schema: ParsedSchema, allSchemas: ParsedSchema[]): string;
|
|
18
21
|
export declare function generateDbTablesIndex(schemas: ParsedSchema[]): string;
|
|
22
|
+
/** Legacy single-file DbTables generator.
|
|
23
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
24
|
+
*/
|
|
19
25
|
export declare function generateTablesFile(schemas: ParsedSchema[]): string;
|
|
26
|
+
export {};
|
|
20
27
|
//# sourceMappingURL=tables-codegen.d.ts.map
|
|
@@ -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,
|
|
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,UAAU,gBAAgB;IACxB,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;AAyCD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAyED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,CAgFhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AA8CD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAoClE"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { toUnderscore } from '../naming.js';
|
|
2
2
|
// ─── Parsing ─────────────────────────────────────────────────
|
|
3
3
|
export function parseSchemaFile(content) {
|
|
4
|
-
const tableNameMatch =
|
|
4
|
+
const tableNameMatch = /tableName:\s*"([^"]+)"/.exec(content);
|
|
5
5
|
if (!tableNameMatch)
|
|
6
6
|
return null;
|
|
7
|
-
const exportMatch =
|
|
7
|
+
const exportMatch = /export const (Schema\w+)\s*=\s*Schema/.exec(content);
|
|
8
8
|
if (!exportMatch)
|
|
9
9
|
return null;
|
|
10
|
-
const schemaBlockMatch =
|
|
10
|
+
const schemaBlockMatch = /const _Schema\s*=\s*\{([\s\S]*?)};/.exec(content);
|
|
11
11
|
if (!schemaBlockMatch)
|
|
12
12
|
return null;
|
|
13
13
|
const fields = [];
|
|
@@ -22,7 +22,7 @@ export function parseSchemaFile(content) {
|
|
|
22
22
|
return null;
|
|
23
23
|
// primaryKey: ["code"] — emitted by generate-schema from DB introspection
|
|
24
24
|
let primary;
|
|
25
|
-
const pkMatch =
|
|
25
|
+
const pkMatch = /primaryKey:\s*\[([^\]]*)\]/.exec(content);
|
|
26
26
|
if (pkMatch) {
|
|
27
27
|
primary = pkMatch[1]
|
|
28
28
|
.split(',')
|
|
@@ -64,12 +64,18 @@ function pkFirst(pk) {
|
|
|
64
64
|
}
|
|
65
65
|
/** Render the `primary:` value for the defineTable template. */
|
|
66
66
|
function formatPrimary(pk) {
|
|
67
|
-
|
|
67
|
+
if (!Array.isArray(pk))
|
|
68
|
+
return `'${pk}'`;
|
|
69
|
+
const quoted = pk.map((p) => `'${p}'`);
|
|
70
|
+
return `[${quoted.join(', ')}]`;
|
|
68
71
|
}
|
|
69
72
|
/** All PK fields as an array, for "non-PK field" lookups in example snippets. */
|
|
70
73
|
function pkFields(pk) {
|
|
71
74
|
return Array.isArray(pk) ? pk : [pk];
|
|
72
75
|
}
|
|
76
|
+
/** Infers FK relations between parsed schemas.
|
|
77
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
78
|
+
*/
|
|
73
79
|
export function detectRelations(schemas) {
|
|
74
80
|
const tableBySnakeName = new Map();
|
|
75
81
|
for (const schema of schemas) {
|
|
@@ -96,36 +102,68 @@ export function detectRelations(schemas) {
|
|
|
96
102
|
return relations;
|
|
97
103
|
}
|
|
98
104
|
// ─── Single Table File Generation ────────────────────────────
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const
|
|
105
|
+
/** Relations grouped by the schema they hang off, in both directions. */
|
|
106
|
+
function indexRelations(relations) {
|
|
107
|
+
const byParent = new Map();
|
|
108
|
+
const byChild = new Map();
|
|
103
109
|
for (const rel of relations) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const childArr =
|
|
110
|
+
const parentArr = byParent.get(rel.parentSchemaName) ?? [];
|
|
111
|
+
parentArr.push(rel);
|
|
112
|
+
byParent.set(rel.parentSchemaName, parentArr);
|
|
113
|
+
const childArr = byChild.get(rel.childSchemaName) ?? [];
|
|
108
114
|
childArr.push(rel);
|
|
109
|
-
|
|
115
|
+
byChild.set(rel.childSchemaName, childArr);
|
|
110
116
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
return { byParent, byChild };
|
|
118
|
+
}
|
|
119
|
+
/** Schemas on the other end of a relation — emitted as commented-out imports. */
|
|
120
|
+
function collectRelatedSchemas(ownName, ...relationGroups) {
|
|
121
|
+
const related = new Set();
|
|
122
|
+
for (const rels of relationGroups) {
|
|
123
|
+
for (const rel of rels) {
|
|
124
|
+
if (rel.childSchemaName !== ownName)
|
|
125
|
+
related.add(rel.childSchemaName);
|
|
126
|
+
if (rel.parentSchemaName !== ownName)
|
|
127
|
+
related.add(rel.parentSchemaName);
|
|
128
|
+
}
|
|
122
129
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
return related;
|
|
131
|
+
}
|
|
132
|
+
/** A plausible `schemaOverrides` example: an email format if there is one, else a minLength. */
|
|
133
|
+
function schemaOverridesLine(schema, pk) {
|
|
134
|
+
const emailField = schema.fields.find((f) => f.toLowerCase().includes('email'));
|
|
135
|
+
if (emailField) {
|
|
136
|
+
return ` // schemaOverrides: { ${emailField}: Type.String({ format: 'email' }) },`;
|
|
137
|
+
}
|
|
138
|
+
const stringField = schema.fields.find((f) => !pkFields(pk).includes(f) && schema.fieldTypes[f]?.includes('String'));
|
|
139
|
+
return stringField
|
|
140
|
+
? ` // schemaOverrides: { ${stringField}: Type.String({ minLength: 1 }) },`
|
|
141
|
+
: ` // schemaOverrides: {},`;
|
|
142
|
+
}
|
|
143
|
+
/** Indirect scope when the table hangs off a parent, direct scope otherwise. */
|
|
144
|
+
function tenantScopeLine(childRels) {
|
|
145
|
+
const rel = childRels[0];
|
|
146
|
+
if (!rel)
|
|
147
|
+
return ` // tenantScope: { column: 'tenant_col' },`;
|
|
148
|
+
return ` // tenantScope: { column: 'tenant_col', through: { schema: ${rel.parentSchemaName}, localField: '${rel.childField}', foreignField: '${rel.parentField}' } },`;
|
|
149
|
+
}
|
|
150
|
+
function allowedReadJoinsLines(parentRels) {
|
|
151
|
+
if (parentRels.length === 0)
|
|
152
|
+
return [` // allowedReadJoins: [],`];
|
|
153
|
+
const lines = [` // allowedReadJoins: [`];
|
|
154
|
+
for (const rel of parentRels) {
|
|
155
|
+
lines.push(` // buildRelation(${rel.parentSchemaName}, '${rel.parentField}', ${rel.childSchemaName}, '${rel.childField}'),`);
|
|
128
156
|
}
|
|
157
|
+
lines.push(` // ],`);
|
|
158
|
+
return lines;
|
|
159
|
+
}
|
|
160
|
+
export function generateSingleTableFile(schema, allSchemas) {
|
|
161
|
+
const { byParent, byChild } = indexRelations(detectRelations(allSchemas));
|
|
162
|
+
const { pk, autoIncrement } = detectPrimaryKey(schema);
|
|
163
|
+
const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
|
|
164
|
+
const parentRels = byParent.get(schema.schemaName) ?? [];
|
|
165
|
+
const childRels = byChild.get(schema.schemaName) ?? [];
|
|
166
|
+
const relatedSchemas = collectRelatedSchemas(schema.schemaName, parentRels, childRels);
|
|
129
167
|
const lines = [];
|
|
130
168
|
// Imports
|
|
131
169
|
lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
|
|
@@ -165,31 +203,10 @@ export function generateSingleTableFile(schema, allSchemas) {
|
|
|
165
203
|
}
|
|
166
204
|
// Hide columns from every read (writes still accept them, e.g. a password hash)
|
|
167
205
|
lines.push(` // readExclude: [],`);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
lines.push(` // ],`);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
lines.push(` // allowedReadJoins: [],`);
|
|
177
|
-
}
|
|
178
|
-
lines.push(` // upsertMap: buildUpsertRules(buildUpsertRule(Schema, [${pkFields(pk).map((p) => `'${p}'`).join(', ')}])),`);
|
|
179
|
-
// schemaOverrides: suggest email format if email field exists, otherwise minLength on first string field
|
|
180
|
-
const emailField = schema.fields.find(f => f.toLowerCase().includes('email'));
|
|
181
|
-
if (emailField) {
|
|
182
|
-
lines.push(` // schemaOverrides: { ${emailField}: Type.String({ format: 'email' }) },`);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const stringField = schema.fields.find(f => !pkFields(pk).includes(f) && schema.fieldTypes[f]?.includes('String'));
|
|
186
|
-
if (stringField) {
|
|
187
|
-
lines.push(` // schemaOverrides: { ${stringField}: Type.String({ minLength: 1 }) },`);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
lines.push(` // schemaOverrides: {},`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
206
|
+
lines.push(...allowedReadJoinsLines(parentRels));
|
|
207
|
+
const upsertPk = pkFields(pk).map((p) => `'${p}'`).join(', ');
|
|
208
|
+
lines.push(` // upsertMap: buildUpsertRules(buildUpsertRule(Schema, [${upsertPk}])),`);
|
|
209
|
+
lines.push(schemaOverridesLine(schema, pk));
|
|
193
210
|
// validate: use first non-PK field for example
|
|
194
211
|
const exampleField = schema.fields.find(f => !pkFields(pk).includes(f)) || schema.fields[0];
|
|
195
212
|
lines.push(` validate: async (db, req, main, secondaries) => {`);
|
|
@@ -199,13 +216,7 @@ export function generateSingleTableFile(schema, allSchemas) {
|
|
|
199
216
|
lines.push(` },`);
|
|
200
217
|
lines.push(` // beforeInsert: async (db, req, record) => {},`);
|
|
201
218
|
lines.push(` // beforeUpdate: async (db, req, fields) => {},`);
|
|
202
|
-
|
|
203
|
-
const rel = childRels[0];
|
|
204
|
-
lines.push(` // tenantScope: { column: 'tenant_col', through: { schema: ${rel.parentSchemaName}, localField: '${rel.childField}', foreignField: '${rel.parentField}' } },`);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
lines.push(` // tenantScope: { column: 'tenant_col' },`);
|
|
208
|
-
}
|
|
219
|
+
lines.push(tenantScopeLine(childRels));
|
|
209
220
|
lines.push(`});`);
|
|
210
221
|
lines.push(``);
|
|
211
222
|
return lines.join('\n');
|
|
@@ -230,18 +241,33 @@ export function generateDbTablesIndex(schemas) {
|
|
|
230
241
|
return lines.join('\n');
|
|
231
242
|
}
|
|
232
243
|
// ─── Code Generation (legacy) ───────────────────────────────
|
|
244
|
+
/** One `defineTable({...})` block for the legacy single-file generator. */
|
|
245
|
+
function legacyTableBlock(schema, parentRels, childRels) {
|
|
246
|
+
const { pk, autoIncrement } = detectPrimaryKey(schema);
|
|
247
|
+
const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
|
|
248
|
+
const upsertPk = pkFields(pk).map((p) => `'${p}'`).join(', ');
|
|
249
|
+
const exampleField = schema.fields.find((f) => !pkFields(pk).includes(f)) || schema.fields[0];
|
|
250
|
+
const lines = [
|
|
251
|
+
``,
|
|
252
|
+
`// ─── ${schema.tableName} ──────────────────────────────`,
|
|
253
|
+
`// Fields: ${schema.fields.join(', ')}`,
|
|
254
|
+
`const ${tableVarName} = defineTable({`,
|
|
255
|
+
` primary: ${formatPrimary(pk)},`,
|
|
256
|
+
` ...exportTableInfo(${schema.schemaName}),`,
|
|
257
|
+
` defaultOrder: '${pkFirst(pk)}',`,
|
|
258
|
+
autoIncrement
|
|
259
|
+
? ` excludeFromCreation: ['${pkFirst(pk)}'],`
|
|
260
|
+
: ` // excludeFromCreation: [],`,
|
|
261
|
+
];
|
|
262
|
+
lines.push(...allowedReadJoinsLines(parentRels));
|
|
263
|
+
lines.push(` // upsertMap: buildUpsertRules(buildUpsertRule(${schema.schemaName}, [${upsertPk}])),`, ` // schemaOverrides: {},`, ` validate: async (db, req, main, secondaries) => {`, ` const errors: ValidationError[] = [];`, ` // if (!main.${exampleField}) errors.push(['${exampleField}', 'required']);`, ` return errors;`, ` },`, ` // beforeInsert: async (db, req, record) => {},`, ` // beforeUpdate: async (db, req, fields) => {},`, tenantScopeLine(childRels), `});`);
|
|
264
|
+
return lines;
|
|
265
|
+
}
|
|
266
|
+
/** Legacy single-file DbTables generator.
|
|
267
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
268
|
+
*/
|
|
233
269
|
export function generateTablesFile(schemas) {
|
|
234
|
-
const
|
|
235
|
-
const relsByParent = new Map();
|
|
236
|
-
const relsByChild = new Map();
|
|
237
|
-
for (const rel of relations) {
|
|
238
|
-
const arr = relsByParent.get(rel.parentSchemaName) || [];
|
|
239
|
-
arr.push(rel);
|
|
240
|
-
relsByParent.set(rel.parentSchemaName, arr);
|
|
241
|
-
const childArr = relsByChild.get(rel.childSchemaName) || [];
|
|
242
|
-
childArr.push(rel);
|
|
243
|
-
relsByChild.set(rel.childSchemaName, childArr);
|
|
244
|
-
}
|
|
270
|
+
const { byParent, byChild } = indexRelations(detectRelations(schemas));
|
|
245
271
|
const lines = [];
|
|
246
272
|
// Imports
|
|
247
273
|
lines.push(`import {defineTable, exportTableInfo, Type} from 'fastify-auto-sqlapi';`);
|
|
@@ -253,51 +279,7 @@ export function generateTablesFile(schemas) {
|
|
|
253
279
|
}
|
|
254
280
|
// Table definitions
|
|
255
281
|
for (const schema of schemas) {
|
|
256
|
-
|
|
257
|
-
const tableVarName = 'Table' + schema.schemaName.replace(/^Schema/, '');
|
|
258
|
-
const parentRels = relsByParent.get(schema.schemaName) || [];
|
|
259
|
-
lines.push(``);
|
|
260
|
-
lines.push(`// ─── ${schema.tableName} ──────────────────────────────`);
|
|
261
|
-
lines.push(`// Fields: ${schema.fields.join(', ')}`);
|
|
262
|
-
lines.push(`const ${tableVarName} = defineTable({`);
|
|
263
|
-
lines.push(` primary: ${formatPrimary(pk)},`);
|
|
264
|
-
lines.push(` ...exportTableInfo(${schema.schemaName}),`);
|
|
265
|
-
lines.push(` defaultOrder: '${pkFirst(pk)}',`);
|
|
266
|
-
if (autoIncrement) {
|
|
267
|
-
lines.push(` excludeFromCreation: ['${pkFirst(pk)}'],`);
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
lines.push(` // excludeFromCreation: [],`);
|
|
271
|
-
}
|
|
272
|
-
if (parentRels.length > 0) {
|
|
273
|
-
lines.push(` // allowedReadJoins: [`);
|
|
274
|
-
for (const rel of parentRels) {
|
|
275
|
-
lines.push(` // buildRelation(${rel.parentSchemaName}, '${rel.parentField}', ${rel.childSchemaName}, '${rel.childField}'),`);
|
|
276
|
-
}
|
|
277
|
-
lines.push(` // ],`);
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
lines.push(` // allowedReadJoins: [],`);
|
|
281
|
-
}
|
|
282
|
-
lines.push(` // upsertMap: buildUpsertRules(buildUpsertRule(${schema.schemaName}, [${pkFields(pk).map((p) => `'${p}'`).join(', ')}])),`);
|
|
283
|
-
lines.push(` // schemaOverrides: {},`);
|
|
284
|
-
const exampleFieldLegacy = schema.fields.find(f => !pkFields(pk).includes(f)) || schema.fields[0];
|
|
285
|
-
lines.push(` validate: async (db, req, main, secondaries) => {`);
|
|
286
|
-
lines.push(` const errors: ValidationError[] = [];`);
|
|
287
|
-
lines.push(` // if (!main.${exampleFieldLegacy}) errors.push(['${exampleFieldLegacy}', 'required']);`);
|
|
288
|
-
lines.push(` return errors;`);
|
|
289
|
-
lines.push(` },`);
|
|
290
|
-
lines.push(` // beforeInsert: async (db, req, record) => {},`);
|
|
291
|
-
lines.push(` // beforeUpdate: async (db, req, fields) => {},`);
|
|
292
|
-
const childRels = relsByChild.get(schema.schemaName) || [];
|
|
293
|
-
if (childRels.length > 0) {
|
|
294
|
-
const rel = childRels[0];
|
|
295
|
-
lines.push(` // tenantScope: { column: 'tenant_col', through: { schema: ${rel.parentSchemaName}, localField: '${rel.childField}', foreignField: '${rel.parentField}' } },`);
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
lines.push(` // tenantScope: { column: 'tenant_col' },`);
|
|
299
|
-
}
|
|
300
|
-
lines.push(`});`);
|
|
282
|
+
lines.push(...legacyTableBlock(schema, byParent.get(schema.schemaName) ?? [], byChild.get(schema.schemaName) ?? []));
|
|
301
283
|
}
|
|
302
284
|
// DbTables export
|
|
303
285
|
lines.push(``);
|
|
@@ -1,11 +1,21 @@
|
|
|
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
1
|
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
2
|
export declare const SINGLE_VALUE_SET: Set<string>;
|
|
7
3
|
export declare const BETWEEN_SET: Set<string>;
|
|
8
4
|
export declare const IN_SET: Set<string>;
|
|
9
5
|
export declare const NULL_SET: Set<string>;
|
|
10
6
|
export declare const ALLOWED_SET: Set<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Caps on how much work one search request may ask for.
|
|
9
|
+
*
|
|
10
|
+
* Every dotted condition and every 3-part `orderBy` token becomes its own correlated subquery,
|
|
11
|
+
* so an uncapped list turns a single request into arbitrarily many — `maxItemsPerPage` bounds
|
|
12
|
+
* the rows returned, not the work done to find them. They live here, in the data module both
|
|
13
|
+
* the engine and the TypeBox schema builders already share, so the request schema advertises
|
|
14
|
+
* exactly the limit the engine enforces.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_CONDITIONS = 100;
|
|
17
|
+
/** Token cap the engine applies to `orderBy`, counted over its comma-separated parts. */
|
|
18
|
+
export declare const MAX_ORDER_BY_PARTS = 20;
|
|
19
|
+
/** Character cap the request schema applies to `orderBy` — generous for MAX_ORDER_BY_PARTS. */
|
|
20
|
+
export declare const MAX_ORDER_BY_LENGTH = 1024;
|
|
11
21
|
//# sourceMappingURL=condition-methods.d.ts.map
|