fastify-auto-sqlapi 0.2.1 → 0.2.3
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.md +50 -0
- package/AGENTS_BACKEND.md +265 -7
- package/AGENTS_FRONTEND.md +3 -3
- package/BREAKING_CHANGES.md +156 -0
- package/CHANGELOG.md +265 -19
- package/README.md +260 -983
- package/dist/bin/generate-schema.js +49 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/agent/manifest.d.ts +4 -1
- package/dist/lib/agent/manifest.d.ts.map +1 -1
- package/dist/lib/agent/manifest.js +15 -3
- package/dist/lib/cli/mysql-introspect.d.ts +28 -0
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +49 -16
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +53 -14
- package/dist/lib/cli/schema-codegen.d.ts +2 -1
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +17 -4
- package/dist/lib/cli/tables-codegen.d.ts +4 -0
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +125 -27
- package/dist/lib/engine/bulk/bulk-upsert.js +2 -2
- package/dist/lib/engine/read-hooks.d.ts +19 -0
- package/dist/lib/engine/read-hooks.d.ts.map +1 -0
- package/dist/lib/engine/read-hooks.js +20 -0
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +5 -2
- 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 +14 -10
- package/dist/lib/engine/search/join-families.d.ts +11 -2
- package/dist/lib/engine/search/join-families.d.ts.map +1 -1
- package/dist/lib/engine/search/join-families.js +11 -13
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +9 -3
- package/dist/lib/engine/validate.d.ts +3 -3
- package/dist/lib/engine/validate.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.d.ts +7 -3
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +27 -28
- package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/schema/bulk-upsert.js +2 -1
- package/dist/lib/schema/get.d.ts +14 -0
- package/dist/lib/schema/get.d.ts.map +1 -0
- package/dist/lib/schema/get.js +18 -0
- package/dist/lib/schema/helpers.d.ts +21 -3
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +37 -5
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +7 -3
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +3 -11
- package/dist/lib/schema/update.d.ts.map +1 -1
- package/dist/lib/schema/update.js +7 -3
- package/dist/lib/sql-api.d.ts +9 -9
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/sql-api.js +2 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +123 -2
- package/dist/lib/tenant.d.ts +2 -2
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +7 -1
- package/dist/lib/write-access.d.ts +36 -0
- package/dist/lib/write-access.d.ts.map +1 -0
- package/dist/lib/write-access.js +58 -0
- package/dist/routes/auto/get.routes.d.ts.map +1 -1
- package/dist/routes/auto/get.routes.js +3 -2
- package/dist/routes/auto/plugin.js +2 -2
- package/dist/routes/auto/route-helpers.js +1 -1
- package/dist/routes/setup-swagger.d.ts.map +1 -0
- package/dist/{lib → routes}/sql-api-decorator.d.ts +1 -1
- package/dist/routes/sql-api-decorator.d.ts.map +1 -0
- package/dist/{lib → routes}/sql-api-decorator.js +4 -4
- package/dist/types/cli.d.ts +27 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/dml.d.ts +6 -4
- package/dist/types/dml.d.ts.map +1 -1
- package/dist/types/plugin.d.ts +24 -3
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/request.d.ts +24 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +1 -0
- package/dist/types/schema.d.ts +15 -0
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/search.d.ts +3 -0
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/table.d.ts +84 -11
- package/dist/types/table.d.ts.map +1 -1
- package/dist/types/validation.d.ts +3 -3
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/docs/adr/0004-updates-always-open.md +2 -1
- package/docs/adr/0014-what-follows-a-write-join.md +84 -0
- package/docs/adr/0015-non-writable-columns.md +86 -0
- package/docs/adr/0016-a-view-is-a-table-config.md +92 -0
- package/docs/adr/0017-the-schema-is-the-contract.md +85 -0
- package/docs/adr/0018-one-seam-to-the-http-framework.md +68 -0
- package/docs/adr/README.md +5 -0
- package/package.json +1 -1
- package/dist/lib/setup-swagger.d.ts.map +0 -1
- package/dist/lib/sql-api-decorator.d.ts.map +0 -1
- /package/dist/{lib → routes}/setup-swagger.d.ts +0 -0
- /package/dist/{lib → routes}/setup-swagger.js +0 -0
|
@@ -37,10 +37,10 @@ function fileStatus(filename, content) {
|
|
|
37
37
|
function writeSchemaFiles(tableMap, schemasDir) {
|
|
38
38
|
const report = { created: false, touched: false, untouched: 0, files: new Set() };
|
|
39
39
|
for (const schemaName of Object.keys(tableMap)) {
|
|
40
|
-
const { name: tableName, fields, colMap, primary } = tableMap[schemaName];
|
|
40
|
+
const { name: tableName, fields, colMap, primary, generated, isView } = tableMap[schemaName];
|
|
41
41
|
const filename = path.join(schemasDir, `${schemaName}.ts`);
|
|
42
42
|
report.files.add(`${schemaName}.ts`);
|
|
43
|
-
const content = generateSchemaFile(schemaName, tableName, fields, colMap, primary);
|
|
43
|
+
const content = generateSchemaFile(schemaName, tableName, fields, colMap, primary, generated, isView);
|
|
44
44
|
const status = fileStatus(filename, content);
|
|
45
45
|
if (status === 'created')
|
|
46
46
|
report.created = true;
|
|
@@ -71,6 +71,51 @@ function removeOrphanSchemas(schemasDir, generated) {
|
|
|
71
71
|
}
|
|
72
72
|
return removed;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Name the columns the database computes, table by table.
|
|
76
|
+
*
|
|
77
|
+
* These are the columns an INSERT or an UPDATE may not mention — both engines reject the
|
|
78
|
+
* statement — so the generator leaves them out of every write body. Saying so here matters
|
|
79
|
+
* more than the comment it also writes into the file: the generated source is not always
|
|
80
|
+
* read, whereas this output is what whoever ran the command actually sees.
|
|
81
|
+
*/
|
|
82
|
+
function reportGeneratedColumns(tableMap) {
|
|
83
|
+
const withGenerated = Object.keys(tableMap)
|
|
84
|
+
.map((schemaName) => tableMap[schemaName])
|
|
85
|
+
.filter((t) => t.generated.length > 0);
|
|
86
|
+
if (withGenerated.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
console.log('');
|
|
89
|
+
display('Database-computed columns found (GENERATED ALWAYS AS ...):', CONSOLE_COLORS.yellow);
|
|
90
|
+
for (const table of withGenerated) {
|
|
91
|
+
display(` ${table.name}: ${table.generated.join(', ')}`, CONSOLE_COLORS.yellow);
|
|
92
|
+
}
|
|
93
|
+
display(' They are readable but rejected by every write, so they are excluded from the insert, ' +
|
|
94
|
+
'update and bulk bodies. Do not send them: they are computed by the database.', CONSOLE_COLORS.gray);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Name the relations that are views.
|
|
98
|
+
*
|
|
99
|
+
* The generated `Table*.ts` says the same thing in a comment, but a generated source file is
|
|
100
|
+
* not always read — this output is what whoever ran the command actually sees, and the two
|
|
101
|
+
* assumptions worth contesting (which column is the key, whether writes make sense) are both
|
|
102
|
+
* assumptions the generator had to make without the database's help.
|
|
103
|
+
*/
|
|
104
|
+
function reportViews(tableMap) {
|
|
105
|
+
const views = Object.keys(tableMap)
|
|
106
|
+
.map((schemaName) => tableMap[schemaName])
|
|
107
|
+
.filter((t) => t.isView);
|
|
108
|
+
if (views.length === 0)
|
|
109
|
+
return;
|
|
110
|
+
console.log('');
|
|
111
|
+
display(`Views found: ${views.map((v) => v.name).join(', ')}`, CONSOLE_COLORS.yellow);
|
|
112
|
+
display(' A view is a table config like any other, but the database reports no PRIMARY KEY for ' +
|
|
113
|
+
'one. The table generator will NOT invent one: it uses "id" when the view has it and says ' +
|
|
114
|
+
'so, and otherwise writes a TODO placeholder. Check the key is really unique in the view ' +
|
|
115
|
+
'before exposing get/update/delete — those address rows by it.', CONSOLE_COLORS.gray);
|
|
116
|
+
display(' Writes are left off by default: only a view simple enough for the engine to make ' +
|
|
117
|
+
'updatable accepts one, and an aggregating view rejects every INSERT and UPDATE.', CONSOLE_COLORS.gray);
|
|
118
|
+
}
|
|
74
119
|
function reportOutcome(report) {
|
|
75
120
|
console.log('');
|
|
76
121
|
if (report.untouched > 0) {
|
|
@@ -117,6 +162,8 @@ await runCli('fastify-auto-sqlapi: generating schemas', async () => {
|
|
|
117
162
|
display(`No matching tables found for: ${tableNames.join(', ')}`, CONSOLE_COLORS.magenta);
|
|
118
163
|
return;
|
|
119
164
|
}
|
|
165
|
+
reportViews(tableMap);
|
|
166
|
+
reportGeneratedColumns(tableMap);
|
|
120
167
|
const report = writeSchemaFiles(tableMap, schemasDir);
|
|
121
168
|
if (!tableNames.length && removeOrphanSchemas(schemasDir, report.files)) {
|
|
122
169
|
report.touched = true;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { resolveTenant } from './lib/tenant.js';
|
|
|
12
12
|
export { SqlApi, createSqlApi } from './lib/sql-api.js';
|
|
13
13
|
export type { SqlApiOptions, SqlApiSearchParams, SqlApiInsertParams, SqlApiUpdateParams } from './lib/sql-api.js';
|
|
14
14
|
export { default as fastifyAutoSqlApi } from './routes/auto/plugin.js';
|
|
15
|
-
export { setupSwagger } from './
|
|
15
|
+
export { setupSwagger } from './routes/setup-swagger.js';
|
|
16
16
|
export { searchEngine } from './lib/engine/search/search.js';
|
|
17
17
|
export { SearchTableBodyPost, SearchTableQuery, SearchTableQueryString, SearchTableResponse } from './lib/schema/search.js';
|
|
18
18
|
export { default as searchRoutes } from './routes/auto/search.routes.js';
|
|
@@ -34,5 +34,5 @@ export { BulkDeleteTableBody, BulkDeleteTableResponse } from './lib/schema/bulk-
|
|
|
34
34
|
export { default as bulkDeleteRoutes } from './routes/auto/bulk-delete.routes.js';
|
|
35
35
|
export { Type, type Static } from '@sinclair/typebox';
|
|
36
36
|
export { ConditionBuilder, Expression } from 'node-condition-builder';
|
|
37
|
-
export type { SqlApiConfig, ColumnInfo, TableMap, Queryable, TransactionQueryable, SqlResult, DbRecord, DbRecordValue, SelectOptions, SchemaDefinition, JoinDefinition, ITable, TableOperation, DbTables, SqlApiPluginOptions, SwaggerOptions, SearchParams, SearchResult, Paginator, PaginationResult, TableFilterFn, ExtendedConditionFn, FilterRecord, AggregationRequest, JoinGroupRequest, ComputedFieldFn, ComputedFieldContext, ComputedFieldExpr, GetParams, GetResult, DeleteParams, DeleteResult, InsertParams, InsertResult, BulkUpsertItem, BulkUpsertParams, BulkUpsertResult, BulkDeleteParams, BulkDeleteResult, ConditionMethod, SearchCondition, UpdateParams, UpdateResult, TenantId, TenantScope, TenantScopeDirect, TenantScopeIndirect, TenantScopeAnyOf, TenantContext, ValidationError, ValidatorFn, BulkValidatorItem, BulkValidatorFn, } from './types.js';
|
|
37
|
+
export type { ApiRequest, SqlApiConfig, ColumnInfo, TableMap, Queryable, TransactionQueryable, SqlResult, DbRecord, DbRecordValue, SelectOptions, SchemaDefinition, JoinDefinition, ITable, TableOperation, DbTables, SqlApiPluginOptions, SwaggerOptions, SearchParams, SearchResult, Paginator, PaginationResult, TableFilterFn, ExtendedConditionFn, FilterRecord, AggregationRequest, JoinGroupRequest, ComputedFieldFn, ComputedFieldContext, ComputedFieldExpr, GetParams, GetResult, DeleteParams, DeleteResult, InsertParams, InsertResult, BulkUpsertItem, BulkUpsertParams, BulkUpsertResult, BulkDeleteParams, BulkDeleteResult, ConditionMethod, SearchCondition, UpdateParams, UpdateResult, TenantId, TenantScope, TenantScopeDirect, TenantScopeIndirect, TenantScopeAnyOf, TenantContext, ValidationError, ValidatorFn, BulkValidatorItem, BulkValidatorFn, } from './types.js';
|
|
38
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1E,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGjF,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACxH,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAGxF,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAIlH,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ export { SqlApi, createSqlApi } from './lib/sql-api.js';
|
|
|
21
21
|
// Plugin
|
|
22
22
|
export { default as fastifyAutoSqlApi } from './routes/auto/plugin.js';
|
|
23
23
|
// Swagger
|
|
24
|
-
export { setupSwagger } from './
|
|
24
|
+
export { setupSwagger } from './routes/setup-swagger.js';
|
|
25
25
|
// Search
|
|
26
26
|
export { searchEngine } from './lib/engine/search/search.js';
|
|
27
27
|
export { SearchTableBodyPost, SearchTableQuery, SearchTableQueryString, SearchTableResponse } from './lib/schema/search.js';
|
|
@@ -7,6 +7,8 @@ interface ManifestField {
|
|
|
7
7
|
nullable?: boolean;
|
|
8
8
|
/** Present (true) when the field is writable but never returned by reads (readExclude). */
|
|
9
9
|
writeOnly?: boolean;
|
|
10
|
+
/** Present (true) when the field is returned by reads but refused by every write. */
|
|
11
|
+
readOnly?: boolean;
|
|
10
12
|
}
|
|
11
13
|
interface ManifestJoin {
|
|
12
14
|
alias: string;
|
|
@@ -47,7 +49,8 @@ export declare function tableOperations(tableConf: ITable): TableOperation[];
|
|
|
47
49
|
export declare function buildAgentManifest(dbTables: DbTables): AgentManifest;
|
|
48
50
|
/**
|
|
49
51
|
* Compact markdown rendering of the manifest, designed for an LLM system prompt.
|
|
50
|
-
* Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads
|
|
52
|
+
* Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads,
|
|
53
|
+
* `(readOnly)` returned by reads and refused by every write.
|
|
51
54
|
*/
|
|
52
55
|
export declare function renderAgentManifestMd(manifest: AgentManifest): string;
|
|
53
56
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/lib/agent/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/lib/agent/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQvF,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,0FAA0F;IAC1F,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtC,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACvC;AAgCD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAInE;AA4DD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAmBrE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isCompositePrimary } from '../../types.js';
|
|
2
|
+
import { writeExcludedFields } from '../write-access.js';
|
|
2
3
|
const ALL_OPERATIONS = [
|
|
3
4
|
'search', 'get', 'insert', 'update', 'delete', 'bulkUpsert', 'bulkDelete',
|
|
4
5
|
];
|
|
@@ -45,6 +46,7 @@ function manifestFields(tableConf) {
|
|
|
45
46
|
const schema = tableConf.Schema;
|
|
46
47
|
const required = new Set((schema.validation.required) ?? []);
|
|
47
48
|
const readExcluded = new Set(tableConf.readExclude ?? []);
|
|
49
|
+
const writeExcluded = writeExcludedFields(tableConf, schema);
|
|
48
50
|
const fields = {};
|
|
49
51
|
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
50
52
|
const f = { type: shortType(fieldSchema) };
|
|
@@ -54,6 +56,8 @@ function manifestFields(tableConf) {
|
|
|
54
56
|
f.nullable = true;
|
|
55
57
|
if (readExcluded.has(name))
|
|
56
58
|
f.writeOnly = true;
|
|
59
|
+
if (writeExcluded.has(name))
|
|
60
|
+
f.readOnly = true;
|
|
57
61
|
fields[name] = f;
|
|
58
62
|
}
|
|
59
63
|
return fields;
|
|
@@ -115,13 +119,15 @@ export function buildAgentManifest(dbTables) {
|
|
|
115
119
|
}
|
|
116
120
|
/**
|
|
117
121
|
* Compact markdown rendering of the manifest, designed for an LLM system prompt.
|
|
118
|
-
* Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads
|
|
122
|
+
* Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads,
|
|
123
|
+
* `(readOnly)` returned by reads and refused by every write.
|
|
119
124
|
*/
|
|
120
125
|
export function renderAgentManifestMd(manifest) {
|
|
121
126
|
const lines = [
|
|
122
127
|
'# Tables',
|
|
123
128
|
'',
|
|
124
|
-
'Notation: `!`=required on insert, `?`=nullable, `(writeOnly)`=writable, never in reads
|
|
129
|
+
'Notation: `!`=required on insert, `?`=nullable, `(writeOnly)`=writable, never in reads, ' +
|
|
130
|
+
'`(readOnly)`=returned by reads, refused by every write (omit it from write payloads).',
|
|
125
131
|
'serverGenerated fields: omit on insert (values from client are ignored).',
|
|
126
132
|
'readJoins aliases: `1:N` → joinMustExist/joinMultiple/joinGroup; `N:1` → joinLeft.',
|
|
127
133
|
'writeJoins aliases: keys for `secondaries`/`deletions` in writes.',
|
|
@@ -138,7 +144,11 @@ export function renderAgentManifestMd(manifest) {
|
|
|
138
144
|
function namedTypes(map) {
|
|
139
145
|
return Object.entries(map).map(([n, ty]) => `${n}:${ty}`).join(', ');
|
|
140
146
|
}
|
|
141
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* A field's type with its notation suffixes: `!` required, `?` nullable, `(writeOnly)`,
|
|
149
|
+
* `(readOnly)`. The two exclusion suffixes are mutually exclusive — `defineTable` rejects a
|
|
150
|
+
* field that is both — so at most one of them is ever appended.
|
|
151
|
+
*/
|
|
142
152
|
function fieldNotation(f) {
|
|
143
153
|
let s = f.type;
|
|
144
154
|
if (f.required)
|
|
@@ -147,6 +157,8 @@ function fieldNotation(f) {
|
|
|
147
157
|
s += '?';
|
|
148
158
|
if (f.writeOnly)
|
|
149
159
|
s += '(writeOnly)';
|
|
160
|
+
if (f.readOnly)
|
|
161
|
+
s += '(readOnly)';
|
|
150
162
|
return s;
|
|
151
163
|
}
|
|
152
164
|
function renderTableMd(t) {
|
|
@@ -6,6 +6,34 @@ export interface MysqlConnectionConfig {
|
|
|
6
6
|
password: string;
|
|
7
7
|
database: string;
|
|
8
8
|
}
|
|
9
|
+
/** information_schema.columns row. MySQL and MariaDB differ on column-name case. */
|
|
10
|
+
interface MysqlColumnRow {
|
|
11
|
+
TABLE_NAME?: string;
|
|
12
|
+
table_name?: string;
|
|
13
|
+
COLUMN_NAME?: string;
|
|
14
|
+
column_name?: string;
|
|
15
|
+
DATA_TYPE?: string;
|
|
16
|
+
data_type?: string;
|
|
17
|
+
COLUMN_DEFAULT?: string | null;
|
|
18
|
+
column_default?: string | null;
|
|
19
|
+
IS_NULLABLE?: string;
|
|
20
|
+
is_nullable?: string;
|
|
21
|
+
COLUMN_KEY?: string;
|
|
22
|
+
column_key?: string;
|
|
23
|
+
EXTRA?: string;
|
|
24
|
+
extra?: string;
|
|
25
|
+
GENERATION_EXPRESSION?: string | null;
|
|
26
|
+
generation_expression?: string | null;
|
|
27
|
+
TABLE_TYPE?: string;
|
|
28
|
+
table_type?: string;
|
|
29
|
+
}
|
|
9
30
|
export declare function buildMysqlConnectionConfig(): MysqlConnectionConfig;
|
|
31
|
+
/**
|
|
32
|
+
* One `information_schema.columns` row as the generator sees it.
|
|
33
|
+
*
|
|
34
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
35
|
+
*/
|
|
36
|
+
export declare function mapMysqlColumnRow(row: MysqlColumnRow): ColumnInfo;
|
|
10
37
|
export declare function introspectMysqlTables(connectionConfig: MysqlConnectionConfig, schema: string): Promise<ColumnInfo[]>;
|
|
38
|
+
export {};
|
|
11
39
|
//# 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,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;
|
|
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;AAkBD,oFAAoF;AACpF,UAAU,cAAc;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,0BAA0B,IAAI,qBAAqB,CAmBlE;AAyED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,UAAU,CAejE;AAED,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE,qBAAqB,EACvC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAoBvB"}
|
|
@@ -65,26 +65,59 @@ function mapMysqlType(dataType) {
|
|
|
65
65
|
return 'varchar';
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
/** `EXTRA`, lowercased — MySQL and MariaDB disagree on the case of both key and value. */
|
|
69
|
+
function extra(row) {
|
|
70
|
+
return String(row.EXTRA || row.extra || '').toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
/** The two `EXTRA` markers that mean a real `GENERATED ALWAYS AS (...)` column. */
|
|
73
|
+
const GENERATED_EXTRA = /\b(?:virtual|stored|persistent) generated\b/;
|
|
74
|
+
/**
|
|
75
|
+
* True only for a column the database computes from an expression — never writable.
|
|
76
|
+
*
|
|
77
|
+
* `EXTRA` cannot be matched on the word 'generated': MySQL writes `DEFAULT_GENERATED` there
|
|
78
|
+
* for any column with a default *expression*, `DEFAULT CURRENT_TIMESTAMP` included, so a
|
|
79
|
+
* substring match takes every such timestamp column out of the write bodies. Only
|
|
80
|
+
* `GENERATION_EXPRESSION` separates the two, and it is non-empty for generated columns alone.
|
|
81
|
+
* `EXTRA` stays as a fallback, matched on the exact markers, for a driver or an older server
|
|
82
|
+
* that does not return the expression.
|
|
83
|
+
*/
|
|
84
|
+
function isGenerated(row) {
|
|
85
|
+
const expression = String(row.GENERATION_EXPRESSION ?? row.generation_expression ?? '').trim();
|
|
86
|
+
return expression !== '' || GENERATED_EXTRA.test(extra(row));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* One `information_schema.columns` row as the generator sees it.
|
|
90
|
+
*
|
|
91
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
92
|
+
*/
|
|
93
|
+
export function mapMysqlColumnRow(row) {
|
|
94
|
+
// `||` is kept as-is from the untyped version: this change types the driver, it does
|
|
95
|
+
// not alter behaviour. The trailing fallbacks only satisfy ColumnInfo, which cannot
|
|
96
|
+
// hold `undefined`.
|
|
97
|
+
return {
|
|
98
|
+
table_name: row.TABLE_NAME || row.table_name || '',
|
|
99
|
+
column_name: row.COLUMN_NAME || row.column_name || '',
|
|
100
|
+
udt_name: mapMysqlType(row.DATA_TYPE || row.data_type || ''),
|
|
101
|
+
column_default: row.COLUMN_DEFAULT || row.column_default || null,
|
|
102
|
+
is_nullable: row.IS_NULLABLE || row.is_nullable || '',
|
|
103
|
+
is_primary: (row.COLUMN_KEY || row.column_key) === 'PRI',
|
|
104
|
+
is_auto_increment: extra(row).includes('auto_increment'),
|
|
105
|
+
is_generated: isGenerated(row),
|
|
106
|
+
is_view: (row.TABLE_TYPE || row.table_type) === 'VIEW',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
68
109
|
export async function introspectMysqlTables(connectionConfig, schema) {
|
|
69
110
|
const mysql2 = loadOptionalDependency('mysql2/promise', 'npm install mysql2');
|
|
70
111
|
const connection = await mysql2.createConnection(connectionConfig);
|
|
71
112
|
try {
|
|
72
|
-
const [rows] = await connection.query(`SELECT table_name, column_name, data_type, column_default, is_nullable,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return rows.map(
|
|
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 || '',
|
|
85
|
-
is_primary: (row.COLUMN_KEY || row.column_key) === 'PRI',
|
|
86
|
-
is_auto_increment: String(row.EXTRA || row.extra || '').includes('auto_increment'),
|
|
87
|
-
}));
|
|
113
|
+
const [rows] = await connection.query(`SELECT c.table_name, c.column_name, c.data_type, c.column_default, c.is_nullable,
|
|
114
|
+
c.column_key, c.extra, c.generation_expression, t.table_type
|
|
115
|
+
FROM information_schema.columns c
|
|
116
|
+
JOIN information_schema.tables t
|
|
117
|
+
ON t.table_schema = c.table_schema AND t.table_name = c.table_name
|
|
118
|
+
WHERE c.table_schema = ?
|
|
119
|
+
ORDER BY c.table_name, c.ordinal_position`, [schema]);
|
|
120
|
+
return rows.map(mapMysqlColumnRow);
|
|
88
121
|
}
|
|
89
122
|
finally {
|
|
90
123
|
await connection.end();
|
|
@@ -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;AAmBjD,wBAAgB,qBAAqB,IAAI,MAAM,CAY9C;
|
|
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;AAcD,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CAmEvB"}
|
|
@@ -15,20 +15,59 @@ export async function introspectTables(connectionString, schema) {
|
|
|
15
15
|
const client = new pg.Client({ connectionString });
|
|
16
16
|
try {
|
|
17
17
|
await client.connect();
|
|
18
|
-
const result = await client.query(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
const result = await client.query(
|
|
19
|
+
// The UNION picks up materialized views, which `information_schema` does not describe at
|
|
20
|
+
// all: they live only in `pg_catalog`, and without this half they were silently absent
|
|
21
|
+
// from every generation run. Their columns are cast to the same shapes the standard view
|
|
22
|
+
// returns, so the two halves stay union-compatible.
|
|
23
|
+
`SELECT * FROM (
|
|
24
|
+
SELECT c.table_name::text, c.column_name::text, c.udt_name::text,
|
|
25
|
+
c.column_default::text, c.is_nullable::text,
|
|
26
|
+
c.is_identity::text, c.is_generated::text,
|
|
27
|
+
(pk.column_name IS NOT NULL) AS is_primary,
|
|
28
|
+
(t.table_type = 'VIEW') AS is_view,
|
|
29
|
+
c.ordinal_position::int AS ordinal_position
|
|
30
|
+
FROM information_schema.columns c
|
|
31
|
+
JOIN information_schema.tables t
|
|
32
|
+
ON t.table_schema = c.table_schema AND t.table_name = c.table_name
|
|
33
|
+
LEFT JOIN (
|
|
34
|
+
SELECT kcu.table_name, kcu.column_name
|
|
35
|
+
FROM information_schema.table_constraints tc
|
|
36
|
+
JOIN information_schema.key_column_usage kcu
|
|
37
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
38
|
+
AND tc.table_schema = kcu.table_schema
|
|
39
|
+
WHERE tc.constraint_type = 'PRIMARY KEY' AND tc.table_schema = $1
|
|
40
|
+
) pk ON pk.table_name = c.table_name AND pk.column_name = c.column_name
|
|
41
|
+
WHERE c.table_schema = $1
|
|
42
|
+
UNION ALL
|
|
43
|
+
SELECT mc.relname::text, ma.attname::text, mt.typname::text,
|
|
44
|
+
NULL::text, CASE WHEN ma.attnotnull THEN 'NO' ELSE 'YES' END,
|
|
45
|
+
'NO'::text, 'NEVER'::text,
|
|
46
|
+
false, true, ma.attnum::int
|
|
47
|
+
FROM pg_class mc
|
|
48
|
+
JOIN pg_namespace mn ON mn.oid = mc.relnamespace
|
|
49
|
+
JOIN pg_attribute ma
|
|
50
|
+
ON ma.attrelid = mc.oid AND ma.attnum > 0 AND NOT ma.attisdropped
|
|
51
|
+
JOIN pg_type mt ON mt.oid = ma.atttypid
|
|
52
|
+
WHERE mc.relkind = 'm' AND mn.nspname = $1
|
|
53
|
+
) cols
|
|
54
|
+
ORDER BY table_name, ordinal_position`, [schema]);
|
|
55
|
+
// The query's own columns are spelled out rather than spread: `is_identity` and
|
|
56
|
+
// `is_generated` arrive as the standard's string enums and `ordinal_position` only exists
|
|
57
|
+
// to order the union, so none of the three belongs in the `ColumnInfo` handed back.
|
|
58
|
+
return result.rows.map((row) => ({
|
|
59
|
+
table_name: row.table_name,
|
|
60
|
+
column_name: row.column_name,
|
|
61
|
+
udt_name: row.udt_name,
|
|
62
|
+
column_default: row.column_default,
|
|
63
|
+
is_nullable: row.is_nullable,
|
|
64
|
+
is_primary: row.is_primary,
|
|
65
|
+
is_view: row.is_view,
|
|
66
|
+
// An identity column has no `column_default`, so without this flag it is emitted as a
|
|
67
|
+
// mandatory field and the table cannot be inserted into at all.
|
|
68
|
+
...(row.is_identity === 'YES' ? { is_auto_increment: true } : {}),
|
|
69
|
+
...(row.is_generated === 'ALWAYS' ? { is_generated: true } : {}),
|
|
70
|
+
}));
|
|
32
71
|
}
|
|
33
72
|
finally {
|
|
34
73
|
await client.end();
|
|
@@ -6,6 +6,7 @@ export declare function convertColType(udtName: string, columnInfo: {
|
|
|
6
6
|
column_default: string | null;
|
|
7
7
|
is_nullable: string;
|
|
8
8
|
is_auto_increment?: boolean;
|
|
9
|
+
is_generated?: boolean;
|
|
9
10
|
}): string;
|
|
10
11
|
export declare function buildTableMap(rows: ColumnInfo[]): TableMap;
|
|
11
12
|
/**
|
|
@@ -15,5 +16,5 @@ export declare function buildTableMap(rows: ColumnInfo[]): TableMap;
|
|
|
15
16
|
* Returns the names of the excluded tables, in map order.
|
|
16
17
|
*/
|
|
17
18
|
export declare function applyExcludeTables(tableMap: TableMap, excludeTables?: string[]): string[];
|
|
18
|
-
export declare function generateSchemaFile(schemaName: string, tableName: string, fields: Record<string, string>, colMap: Record<string, string>, primary?: string[]): string;
|
|
19
|
+
export declare function generateSchemaFile(schemaName: string, tableName: string, fields: Record<string, string>, colMap: Record<string, string>, primary?: string[], generated?: string[], isView?: boolean): string;
|
|
19
20
|
//# sourceMappingURL=schema-codegen.d.ts.map
|
|
@@ -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;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;
|
|
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;IACV,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GACA,MAAM,CA6DR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,CA0C1D;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,EAClB,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,MAAM,CAAC,EAAE,OAAO,GACf,MAAM,CAoDR"}
|
|
@@ -3,8 +3,10 @@ import { toCamelCase, toSchemaName } from '../naming.js';
|
|
|
3
3
|
* @testonly Exported only so unit tests can exercise it directly.
|
|
4
4
|
*/
|
|
5
5
|
export function convertColType(udtName, columnInfo) {
|
|
6
|
-
// AUTO_INCREMENT (mysql)
|
|
7
|
-
|
|
6
|
+
// AUTO_INCREMENT (mysql) and IDENTITY (postgres) behave like a default: the DB generates
|
|
7
|
+
// the value. A generated column goes further — it refuses one — so it can never be a
|
|
8
|
+
// mandatory field either, whatever its nullability says.
|
|
9
|
+
const hasDefault = columnInfo.column_default || columnInfo.is_auto_increment || columnInfo.is_generated;
|
|
8
10
|
const isNullable = columnInfo.is_nullable === 'YES';
|
|
9
11
|
const wrap = (t) => {
|
|
10
12
|
// Nullable columns need the type-array form (`Nullable(T)` → type: [..., 'null']):
|
|
@@ -69,6 +71,8 @@ export function buildTableMap(rows) {
|
|
|
69
71
|
fields: {},
|
|
70
72
|
colMap: {},
|
|
71
73
|
primary: [],
|
|
74
|
+
generated: [],
|
|
75
|
+
isView: row.is_view === true,
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
78
|
const fieldName = toCamelCase(row.column_name);
|
|
@@ -85,6 +89,9 @@ export function buildTableMap(rows) {
|
|
|
85
89
|
if (row.is_primary) {
|
|
86
90
|
acc[schemaName].primary.push(fieldName);
|
|
87
91
|
}
|
|
92
|
+
if (row.is_generated) {
|
|
93
|
+
acc[schemaName].generated.push(fieldName);
|
|
94
|
+
}
|
|
88
95
|
return acc;
|
|
89
96
|
}, {});
|
|
90
97
|
}
|
|
@@ -113,10 +120,16 @@ export function applyExcludeTables(tableMap, excludeTables) {
|
|
|
113
120
|
}
|
|
114
121
|
return excluded;
|
|
115
122
|
}
|
|
116
|
-
export function generateSchemaFile(schemaName, tableName, fields, colMap, primary) {
|
|
123
|
+
export function generateSchemaFile(schemaName, tableName, fields, colMap, primary, generated, isView) {
|
|
117
124
|
const fieldsStr = JSON.stringify(fields, null, 2).replace(/"(.*?)"\s*:\s*"Type(.*?)"/g, '$1: Type$2');
|
|
118
125
|
const colMapStr = JSON.stringify(colMap, null, 2);
|
|
119
126
|
const primaryKeyLine = primary?.length ? `\n primaryKey: ${JSON.stringify(primary)},` : '';
|
|
127
|
+
// Published so the table generator — and the runtime — know which columns the database
|
|
128
|
+
// computes: those are readable but reject every write.
|
|
129
|
+
const generatedLine = generated?.length ? `\n generatedFields: ${JSON.stringify(generated)},` : '';
|
|
130
|
+
// A view has no PRIMARY KEY constraint to introspect, so the table generator has to be told
|
|
131
|
+
// it may not read the absence of one as "no key exists on this data".
|
|
132
|
+
const isViewLine = isView ? `\n isView: true,` : '';
|
|
120
133
|
const usesNullable = Object.values(fields).some((t) => t.includes('Nullable('));
|
|
121
134
|
const valueImports = usesNullable ? 'Type, Nullable' : 'Type';
|
|
122
135
|
return `// Auto-generated by fastify-auto-sqlapi. Do not edit.
|
|
@@ -144,7 +157,7 @@ export const Schema = {
|
|
|
144
157
|
fields: _Schema,
|
|
145
158
|
validation,
|
|
146
159
|
tableName: "${tableName}",
|
|
147
|
-
partialValidation,${primaryKeyLine}
|
|
160
|
+
partialValidation,${primaryKeyLine}${generatedLine}${isViewLine}
|
|
148
161
|
};
|
|
149
162
|
export const ${schemaName} = Schema;
|
|
150
163
|
export type TypeSchema = Static<typeof Schema.validation>;
|
|
@@ -5,6 +5,10 @@ export interface ParsedSchema {
|
|
|
5
5
|
fieldTypes: Record<string, string>;
|
|
6
6
|
/** PRIMARY KEY fields declared in the schema file (`primaryKey: [...]`), when present. */
|
|
7
7
|
primary?: string[];
|
|
8
|
+
/** Database-computed fields declared in the schema file (`generatedFields: [...]`). */
|
|
9
|
+
generated?: string[];
|
|
10
|
+
/** True when the schema file declares `isView: true` — the relation is a view. */
|
|
11
|
+
isView?: boolean;
|
|
8
12
|
}
|
|
9
13
|
interface DetectedRelation {
|
|
10
14
|
parentSchemaName: string;
|
|
@@ -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;
|
|
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;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;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,CAoCpE;AA4GD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAgID,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,CAwFhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AAqDD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAoClE"}
|