@tsofist/schema-forge 2.11.0 → 3.0.0-next.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/lib/artefacts-policy.d.ts +3 -0
- package/lib/artefacts-policy.d.ts.map +1 -0
- package/lib/artefacts-policy.js +7 -0
- package/lib/dbml-generator/converter.d.ts +5 -0
- package/lib/dbml-generator/converter.d.ts.map +1 -0
- package/lib/dbml-generator/converter.js +15 -0
- package/lib/dbml-generator/generator.d.ts +4 -0
- package/lib/dbml-generator/generator.d.ts.map +1 -0
- package/lib/dbml-generator/generator.js +423 -0
- package/lib/dbml-generator/generator.spec.d.ts +2 -0
- package/lib/dbml-generator/generator.spec.d.ts.map +1 -0
- package/lib/dbml-generator/generator.spec.js +94 -0
- package/lib/dbml-generator/types.d.ts +144 -0
- package/lib/dbml-generator/types.d.ts.map +1 -0
- package/lib/dbml-generator/types.js +53 -0
- package/lib/definition-info/api-signature.d.ts +18 -0
- package/lib/definition-info/api-signature.d.ts.map +1 -0
- package/lib/definition-info/api-signature.js +31 -0
- package/lib/definition-info/parser.d.ts +3 -0
- package/lib/definition-info/parser.d.ts.map +1 -0
- package/lib/definition-info/parser.js +44 -0
- package/lib/definition-info/ref.d.ts +3 -0
- package/lib/definition-info/ref.d.ts.map +1 -0
- package/lib/definition-info/ref.js +6 -0
- package/lib/definition-info/types.d.ts +48 -0
- package/lib/definition-info/types.d.ts.map +1 -0
- package/lib/definition-info/types.js +20 -0
- package/lib/efc.d.ts +27 -0
- package/lib/efc.d.ts.map +1 -0
- package/lib/efc.js +9 -0
- package/lib/fake-generator/generator-host.d.ts +4 -0
- package/lib/fake-generator/generator-host.d.ts.map +1 -0
- package/lib/fake-generator/generator-host.js +57 -0
- package/lib/fake-generator/generator.d.ts +6 -0
- package/lib/fake-generator/generator.d.ts.map +1 -0
- package/lib/fake-generator/generator.js +64 -0
- package/lib/fake-generator/generator.spec.d.ts +2 -0
- package/lib/fake-generator/generator.spec.d.ts.map +1 -0
- package/lib/{fake-generator.spec.js → fake-generator/generator.spec.js} +21 -19
- package/lib/fake-generator/modules.d.ts +3 -0
- package/lib/fake-generator/modules.d.ts.map +1 -0
- package/lib/fake-generator/modules.js +44 -0
- package/lib/fake-generator/types.d.ts +18 -0
- package/lib/fake-generator/types.d.ts.map +1 -0
- package/lib/fake-generator/types.js +2 -0
- package/lib/schema-generator/extended-annotations-reader.d.ts +2 -0
- package/lib/schema-generator/extended-annotations-reader.d.ts.map +1 -0
- package/lib/{util/patch.extended-annotations-reader.js → schema-generator/extended-annotations-reader.js} +2 -2
- package/lib/schema-generator/forge.d.ts +3 -0
- package/lib/schema-generator/forge.d.ts.map +1 -0
- package/lib/{generator.js → schema-generator/forge.js} +13 -33
- package/lib/schema-generator/forge.spec.d.ts +2 -0
- package/lib/schema-generator/forge.spec.d.ts.map +1 -0
- package/lib/{generator.spec.js → schema-generator/forge.spec.js} +116 -107
- package/lib/schema-generator/format-error.d.ts +2 -0
- package/lib/schema-generator/format-error.d.ts.map +1 -0
- package/lib/{util/format.error.js → schema-generator/format-error.js} +3 -3
- package/lib/schema-generator/generate-drafts.d.ts +17 -0
- package/lib/schema-generator/generate-drafts.d.ts.map +1 -0
- package/lib/{generator/types-generator.js → schema-generator/generate-drafts.js} +140 -120
- package/lib/schema-generator/generate-schema.d.ts +15 -0
- package/lib/schema-generator/generate-schema.d.ts.map +1 -0
- package/lib/{generator/schema-generator.js → schema-generator/generate-schema.js} +26 -19
- package/lib/{util/tsc.d.ts → schema-generator/helpers-tsc.d.ts} +2 -1
- package/lib/schema-generator/helpers-tsc.d.ts.map +1 -0
- package/lib/schema-generator/shrink-definition-name.d.ts +7 -0
- package/lib/schema-generator/shrink-definition-name.d.ts.map +1 -0
- package/lib/schema-generator/shrink-definition-name.js +21 -0
- package/lib/schema-generator/sort-properties.d.ts +3 -0
- package/lib/schema-generator/sort-properties.d.ts.map +1 -0
- package/lib/{util → schema-generator}/sort-properties.js +2 -2
- package/lib/schema-generator/types.d.ts +18 -0
- package/lib/schema-generator/types.d.ts.map +1 -0
- package/lib/{generator → schema-generator}/types.js +20 -8
- package/lib/schema-registry/kw-api.d.ts +3 -0
- package/lib/schema-registry/kw-api.d.ts.map +1 -0
- package/lib/schema-registry/kw-api.js +25 -0
- package/lib/schema-registry/kw-common.d.ts +3 -0
- package/lib/schema-registry/kw-common.d.ts.map +1 -0
- package/lib/schema-registry/kw-common.js +47 -0
- package/lib/schema-registry/kw-dbml.d.ts +3 -0
- package/lib/schema-registry/kw-dbml.d.ts.map +1 -0
- package/lib/schema-registry/kw-dbml.js +79 -0
- package/lib/schema-registry/loader.d.ts +10 -0
- package/lib/schema-registry/loader.d.ts.map +1 -0
- package/lib/schema-registry/loader.js +24 -0
- package/lib/schema-registry/registry.d.ts +25 -0
- package/lib/schema-registry/registry.d.ts.map +1 -0
- package/lib/schema-registry/registry.js +230 -0
- package/lib/schema-registry/types.d.ts +78 -0
- package/lib/schema-registry/types.d.ts.map +1 -0
- package/lib/schema-registry/types.js +2 -0
- package/lib/types.d.ts +53 -79
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +0 -15
- package/package.json +7 -4
- package/lib/fake-generator.d.ts +0 -21
- package/lib/fake-generator.js +0 -158
- package/lib/fake-generator.spec.d.ts +0 -1
- package/lib/generator/schema-generator.d.ts +0 -20
- package/lib/generator/types-generator.d.ts +0 -13
- package/lib/generator/types.d.ts +0 -40
- package/lib/generator.d.ts +0 -5
- package/lib/generator.spec.d.ts +0 -1
- package/lib/index.d.ts +0 -7
- package/lib/index.js +0 -64
- package/lib/types/db.types.d.ts +0 -96
- package/lib/types/db.types.js +0 -17
- package/lib/util/format.error.d.ts +0 -1
- package/lib/util/patch.extended-annotations-reader.d.ts +0 -1
- package/lib/util/sort-properties.d.ts +0 -2
- package/lib/validator.d.ts +0 -47
- package/lib/validator.js +0 -396
- /package/lib/{util/tsc.js → schema-generator/helpers-tsc.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artefacts-policy.d.ts","sourceRoot":"","sources":["../src/artefacts-policy.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,mBAAmB,SAAkC,CAAC;AACnE,eAAO,MAAM,kBAAkB,SAAuC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KEEP_GEN_ARTEFACTS = exports.KEEP_SPEC_ARTEFACTS = void 0;
|
|
4
|
+
const node_process_1 = require("node:process");
|
|
5
|
+
const raw = node_process_1.env['SF_ARTEFACTS_POLICY'];
|
|
6
|
+
exports.KEEP_SPEC_ARTEFACTS = raw === 'all' || raw === 'spec';
|
|
7
|
+
exports.KEEP_GEN_ARTEFACTS = raw === 'all' || raw === 'generator';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExportFormatOption } from '@dbml/core/types/export/ModelExporter';
|
|
2
|
+
export declare function convertDBMLToDatabaseModel(source: string): import("@dbml/core/types/model_structure/database").default;
|
|
3
|
+
export declare function convertDBMLToJSON(source: string): import("@dbml/core/types/model_structure/database").RawDatabase;
|
|
4
|
+
export declare function convertDBMLToSQL(source: string, format?: ExportFormatOption): string;
|
|
5
|
+
//# sourceMappingURL=converter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../src/dbml-generator/converter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,+DAExD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,mEAE/C;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,kBAA+B,UAEvF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertDBMLToDatabaseModel = convertDBMLToDatabaseModel;
|
|
4
|
+
exports.convertDBMLToJSON = convertDBMLToJSON;
|
|
5
|
+
exports.convertDBMLToSQL = convertDBMLToSQL;
|
|
6
|
+
const core_1 = require("@dbml/core");
|
|
7
|
+
function convertDBMLToDatabaseModel(source) {
|
|
8
|
+
return core_1.Parser.parse(source, 'json');
|
|
9
|
+
}
|
|
10
|
+
function convertDBMLToJSON(source) {
|
|
11
|
+
return core_1.Parser.parseDBMLToJSONv2(source);
|
|
12
|
+
}
|
|
13
|
+
function convertDBMLToSQL(source, format = 'postgres') {
|
|
14
|
+
return core_1.exporter.export(source, format);
|
|
15
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SchemaForgeRegistry } from '../schema-registry/types';
|
|
2
|
+
import { DBMLGeneratorOptions, DBMLProjectScope } from './types';
|
|
3
|
+
export declare function generateDBMLSpec(schemaRegistry: SchemaForgeRegistry, scopes: DBMLProjectScope[], options?: DBMLGeneratorOptions): string;
|
|
4
|
+
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/dbml-generator/generator.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,OAAO,EAIH,oBAAoB,EAGpB,gBAAgB,EACnB,MAAM,SAAS,CAAC;AAEjB,wBAAgB,gBAAgB,CAC5B,cAAc,EAAE,mBAAmB,EACnC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,OAAO,GAAE,oBAAyB,UAqFrC"}
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateDBMLSpec = generateDBMLSpec;
|
|
4
|
+
const as_array_1 = require("@tsofist/stem/lib/as-array");
|
|
5
|
+
const equal_keys_1 = require("@tsofist/stem/lib/equal-keys");
|
|
6
|
+
const error_1 = require("@tsofist/stem/lib/error");
|
|
7
|
+
const entries_1 = require("@tsofist/stem/lib/object/entries");
|
|
8
|
+
const snake_1 = require("@tsofist/stem/lib/string/case/snake");
|
|
9
|
+
const compare_1 = require("@tsofist/stem/lib/string/compare");
|
|
10
|
+
const substr_1 = require("@tsofist/stem/lib/string/substr");
|
|
11
|
+
const text_builder_1 = require("@tsofist/stem/lib/string/text-builder");
|
|
12
|
+
function generateDBMLSpec(schemaRegistry, scopes, options = {}) {
|
|
13
|
+
const text = new text_builder_1.TextBuilder();
|
|
14
|
+
const tables = new Map();
|
|
15
|
+
const groups = new Map();
|
|
16
|
+
const sources = new Map();
|
|
17
|
+
if (options.meta?.comment) {
|
|
18
|
+
text.push(`// ${options.meta?.comment}`);
|
|
19
|
+
}
|
|
20
|
+
text.push(`Project ${options.meta?.name ?? 'Scratch'} {`);
|
|
21
|
+
text.push(`database_type: 'PostgreSQL'`, 1);
|
|
22
|
+
if (options.meta?.note) {
|
|
23
|
+
text.push(buildNote(options.meta.note), 1);
|
|
24
|
+
}
|
|
25
|
+
text.push('}');
|
|
26
|
+
text.push(``);
|
|
27
|
+
for (const scope of scopes.sort((a, b) => {
|
|
28
|
+
return (0, compare_1.compareStringsAsc)(a.scopeName ?? DefaultScopeName, b.scopeName ?? DefaultScopeName);
|
|
29
|
+
})) {
|
|
30
|
+
if (sources.has(scope.scopeName ?? DefaultScopeName)) {
|
|
31
|
+
(0, error_1.raise)(`Duplicate scope: ${scope.scopeName ?? '[ unnamed ]'}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
sources.set(scope.scopeName ?? DefaultScopeName, scope);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const { definitions, scopeName = DefaultScopeName } of sources.values()) {
|
|
38
|
+
for (const definition of definitions) {
|
|
39
|
+
const tableSpec = generateTable(definition, schemaRegistry, options);
|
|
40
|
+
if (tableSpec) {
|
|
41
|
+
if (tables.has(tableSpec.name)) {
|
|
42
|
+
(0, error_1.raise)(`Definitions contain duplicate table name: ${tableSpec.name}`);
|
|
43
|
+
}
|
|
44
|
+
tables.set(tableSpec.name, tableSpec);
|
|
45
|
+
let groupTables = groups.get(scopeName);
|
|
46
|
+
if (!groupTables) {
|
|
47
|
+
groupTables = [];
|
|
48
|
+
groups.set(scopeName, groupTables);
|
|
49
|
+
}
|
|
50
|
+
groupTables.push(tableSpec.name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const defaultGroupOnly = groups.size === 1 && groups.has(DefaultScopeName);
|
|
55
|
+
for (const [groupName, groupTableNames] of groups.entries()) {
|
|
56
|
+
for (const tableName of groupTableNames.sort(compare_1.compareStringsAsc)) {
|
|
57
|
+
const table = tables.get(tableName);
|
|
58
|
+
text.push(table.value);
|
|
59
|
+
text.push(``);
|
|
60
|
+
}
|
|
61
|
+
if (!defaultGroupOnly) {
|
|
62
|
+
const scope = sources.get(groupName);
|
|
63
|
+
if (scope) {
|
|
64
|
+
const comment = scope.comment;
|
|
65
|
+
if (comment) {
|
|
66
|
+
text.push(`// ${comment}`);
|
|
67
|
+
}
|
|
68
|
+
text.push(`TableGroup ${groupName} {`);
|
|
69
|
+
for (const tableName of groupTableNames.sort(compare_1.compareStringsAsc)) {
|
|
70
|
+
text.push(tableName, 1);
|
|
71
|
+
}
|
|
72
|
+
text.push(`}`);
|
|
73
|
+
text.push(``);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return text.stringify();
|
|
78
|
+
}
|
|
79
|
+
function generateTable(info, registry, options) {
|
|
80
|
+
const includeNotes = options?.includeNotes ?? false;
|
|
81
|
+
const entityTypeName = info.type;
|
|
82
|
+
const entitySchema = registry.getSchema(`${info.schemaId}#/definitions/${entityTypeName}`);
|
|
83
|
+
if (!entitySchema)
|
|
84
|
+
return;
|
|
85
|
+
const tableName = readDBEntityName(entitySchema);
|
|
86
|
+
if (!tableName)
|
|
87
|
+
return;
|
|
88
|
+
const { properties, required } = listProperties(entitySchema);
|
|
89
|
+
const columns = generateColumns(properties, required, includeNotes, registry, info.schemaId);
|
|
90
|
+
const indexes = generateIndexes(tableName, properties, readDBEntityIndexes(entitySchema), includeNotes, registry, info.schemaId);
|
|
91
|
+
const text = new text_builder_1.TextBuilder();
|
|
92
|
+
if (entitySchema.$comment) {
|
|
93
|
+
text.push(stringifyComment(entitySchema.$comment));
|
|
94
|
+
}
|
|
95
|
+
text.push(`Table ${tableName} {`);
|
|
96
|
+
text.push(columns, 1);
|
|
97
|
+
if (indexes.size) {
|
|
98
|
+
text.push('');
|
|
99
|
+
text.push('indexes {', 1);
|
|
100
|
+
text.push(indexes, 2);
|
|
101
|
+
text.push('}', 1);
|
|
102
|
+
}
|
|
103
|
+
if (entitySchema.description) {
|
|
104
|
+
text.push('');
|
|
105
|
+
text.push(buildNote(entitySchema.description), 1);
|
|
106
|
+
}
|
|
107
|
+
text.push('}');
|
|
108
|
+
return {
|
|
109
|
+
name: tableName,
|
|
110
|
+
value: text.stringify(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function generateColumns(properties, requiredFields, notes, registry, schemaId) {
|
|
114
|
+
const text = new text_builder_1.TextBuilder();
|
|
115
|
+
let count = 0;
|
|
116
|
+
for (const [key, property] of (0, entries_1.entries)(properties).sort(([keyA], [keyB]) => {
|
|
117
|
+
const indexA = SortableFields.indexOf(keyA);
|
|
118
|
+
const indexB = SortableFields.indexOf(keyB);
|
|
119
|
+
if (indexA === -1 && indexB === -1)
|
|
120
|
+
return 0;
|
|
121
|
+
else if (indexA === -1)
|
|
122
|
+
return 1;
|
|
123
|
+
else if (indexB === -1)
|
|
124
|
+
return -1;
|
|
125
|
+
return indexA - indexB;
|
|
126
|
+
})) {
|
|
127
|
+
if (property === undefined)
|
|
128
|
+
continue;
|
|
129
|
+
const isRequired = requiredFields.includes(key);
|
|
130
|
+
const isColumnNullable = isNullable(property);
|
|
131
|
+
const columnType = getType(property, schemaId, registry);
|
|
132
|
+
const attributes = generateColumnAttributes(key, property, isRequired, isColumnNullable, notes);
|
|
133
|
+
if (property.$comment)
|
|
134
|
+
text.push(stringifyComment(property.$comment));
|
|
135
|
+
text.push(`${(0, snake_1.snakeCase)(key)} ${columnType} [${attributes.stringify(', ')}]`);
|
|
136
|
+
count++;
|
|
137
|
+
}
|
|
138
|
+
if (count === 0) {
|
|
139
|
+
// todo!
|
|
140
|
+
// raise(`Table ${schemaId} has no columns`);
|
|
141
|
+
}
|
|
142
|
+
return text;
|
|
143
|
+
}
|
|
144
|
+
function generateColumnAttributes(_key, property, isRequired, isColumnNullable, notes) {
|
|
145
|
+
const result = new text_builder_1.TextBuilder();
|
|
146
|
+
const isPK = property.dbColumn?.pk ?? false;
|
|
147
|
+
if (isPK) {
|
|
148
|
+
result.push('pk');
|
|
149
|
+
}
|
|
150
|
+
const defaultValue = property.default;
|
|
151
|
+
const hasDefaultValue = defaultValue !== undefined;
|
|
152
|
+
if (isColumnNullable && isRequired) {
|
|
153
|
+
if (!hasDefaultValue)
|
|
154
|
+
result.push('default: null');
|
|
155
|
+
// result.push('null'); <- by default
|
|
156
|
+
}
|
|
157
|
+
else if (isColumnNullable || !isRequired) {
|
|
158
|
+
result.push('null');
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
result.push('not null');
|
|
162
|
+
}
|
|
163
|
+
if (hasDefaultValue) {
|
|
164
|
+
const v = typeof defaultValue === 'string' &&
|
|
165
|
+
defaultValue[0] !== '`' &&
|
|
166
|
+
defaultValue[defaultValue.length - 1] !== '`'
|
|
167
|
+
? `"${defaultValue}"`
|
|
168
|
+
: defaultValue;
|
|
169
|
+
result.push(`default: ${String(v)}`);
|
|
170
|
+
}
|
|
171
|
+
if (notes && property.description) {
|
|
172
|
+
result.push(buildNote(property.description).stringify());
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
function generateIndexes(tableName, schemaProperties, entityIndexes, includeNotes, registry, schemaId) {
|
|
177
|
+
const text = new text_builder_1.TextBuilder();
|
|
178
|
+
const idx = {};
|
|
179
|
+
const normalizedTableName = tableName.replace(/\./g, '_');
|
|
180
|
+
const dropIndexes = new Set();
|
|
181
|
+
function processIndex(source, column, key, rawKey) {
|
|
182
|
+
if (!key)
|
|
183
|
+
key = column;
|
|
184
|
+
if (!rawKey)
|
|
185
|
+
rawKey = key;
|
|
186
|
+
const list = (0, as_array_1.asArray)(source);
|
|
187
|
+
for (const item of list) {
|
|
188
|
+
let indexName;
|
|
189
|
+
if (typeof item === 'string') {
|
|
190
|
+
indexName = item;
|
|
191
|
+
}
|
|
192
|
+
else if (item === true) {
|
|
193
|
+
indexName = `ix_${normalizedTableName}_${key}`;
|
|
194
|
+
}
|
|
195
|
+
else if (item != null && typeof item === 'object') {
|
|
196
|
+
if (item.name) {
|
|
197
|
+
indexName = item.name;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const k = list.length === 1 ? key : `${key}_${item.type ?? 'btree'}`;
|
|
201
|
+
indexName = `ix_${normalizedTableName}_${k}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (indexName) {
|
|
205
|
+
if (item === false) {
|
|
206
|
+
dropIndexes.add(indexName);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
if (!schemaProperties[column])
|
|
210
|
+
(0, error_1.raise)('`Column ${column} not found in schema`');
|
|
211
|
+
const field = (0, substr_1.substr)(rawKey, '.');
|
|
212
|
+
const index = (idx[indexName] ||= {
|
|
213
|
+
...(typeof item === 'object' ? item : {}),
|
|
214
|
+
columnType: getType(schemaProperties[column], schemaId, registry),
|
|
215
|
+
columns: [],
|
|
216
|
+
fields: [],
|
|
217
|
+
});
|
|
218
|
+
if (field)
|
|
219
|
+
index.fields.push(field);
|
|
220
|
+
index.columns.push(column);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (entityIndexes) {
|
|
226
|
+
for (const [key, def] of Object.entries(entityIndexes)) {
|
|
227
|
+
const prefix = (0, substr_1.substr)(key, 0, '.');
|
|
228
|
+
const column = prefix ? prefix : key;
|
|
229
|
+
processIndex(
|
|
230
|
+
//
|
|
231
|
+
def, column, prefix ? key.replace(/\./g, '_') : key, key);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
for (const [column, property] of Object.entries(schemaProperties)) {
|
|
235
|
+
if (property?.dbIndex !== undefined) {
|
|
236
|
+
processIndex(property.dbIndex, column);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const [indexName, index] of (0, entries_1.entries)(idx)) {
|
|
240
|
+
if (!index)
|
|
241
|
+
continue;
|
|
242
|
+
const { columns, unique, pk, comment, note } = index;
|
|
243
|
+
if (columns.length !== new Set(columns).size) {
|
|
244
|
+
console.error(`Duplicate columns in index ${indexName} for table ${tableName}`);
|
|
245
|
+
}
|
|
246
|
+
const options = [`name: "${indexName}"`];
|
|
247
|
+
const type = index.type || index.columnType === 'jsonb' || index.columnType.endsWith('[]')
|
|
248
|
+
? 'gin'
|
|
249
|
+
: undefined;
|
|
250
|
+
if (type)
|
|
251
|
+
options.push(`type: ${type}`);
|
|
252
|
+
if (pk)
|
|
253
|
+
options.push(`pk`);
|
|
254
|
+
if (!pk && unique)
|
|
255
|
+
options.push('unique');
|
|
256
|
+
// todo
|
|
257
|
+
// if (includeNotes && fields.length) {
|
|
258
|
+
// options.push(`note: 'Fields: ${fields.join(', ')};'`);
|
|
259
|
+
// }
|
|
260
|
+
if (includeNotes && note)
|
|
261
|
+
options.push(buildNote(note).stringify());
|
|
262
|
+
if (comment)
|
|
263
|
+
text.push(`// ${comment}`);
|
|
264
|
+
text.push(`(${columns.map(snake_1.snakeCase).join(', ')}) [${options.join(', ')}]`);
|
|
265
|
+
}
|
|
266
|
+
return text;
|
|
267
|
+
}
|
|
268
|
+
function readDBEntityName({ dbEntity }) {
|
|
269
|
+
if (dbEntity) {
|
|
270
|
+
if (typeof dbEntity === 'string')
|
|
271
|
+
return dbEntity;
|
|
272
|
+
if (dbEntity.name)
|
|
273
|
+
return dbEntity.name;
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
function readDBEntityIndexes({ dbEntity }) {
|
|
278
|
+
if (dbEntity != null && typeof dbEntity === 'object') {
|
|
279
|
+
return dbEntity.indexes;
|
|
280
|
+
}
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
function stringifyComment(value) {
|
|
284
|
+
if (!value)
|
|
285
|
+
return undefined;
|
|
286
|
+
return value.split('\n').map((item) => `// ${item.trim()}`);
|
|
287
|
+
}
|
|
288
|
+
function buildNote(value) {
|
|
289
|
+
const result = new text_builder_1.TextBuilder();
|
|
290
|
+
if (value) {
|
|
291
|
+
if (value.includes('\n')) {
|
|
292
|
+
result.push(`note:`);
|
|
293
|
+
result.push([`'''`, ...value.split('\n'), `'''`], 1);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
result.push(`note: "${value}"`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
function isNullable(property) {
|
|
302
|
+
return (property.anyOf?.some((item) => typeof item !== 'boolean' && item?.type !== 'null') ?? false);
|
|
303
|
+
}
|
|
304
|
+
function getType(property, schemaId, registry) {
|
|
305
|
+
if (property.dbColumn?.type)
|
|
306
|
+
return property.dbColumn.type;
|
|
307
|
+
let type = property.type;
|
|
308
|
+
if (property.$ref) {
|
|
309
|
+
const schema = registry.getSchema(`${schemaId}${property.$ref}`);
|
|
310
|
+
if (schema && schema.type)
|
|
311
|
+
type = schema.type;
|
|
312
|
+
const ref = schema?.$ref || property.$ref;
|
|
313
|
+
if (typeof ref !== 'string')
|
|
314
|
+
(0, error_1.raise)(`Invalid schema reference type ${typeof ref} (expected string)`);
|
|
315
|
+
const refType = ref.split('/').pop();
|
|
316
|
+
if (refType && refType in {}) {
|
|
317
|
+
// @1ts-expect-error It's OK
|
|
318
|
+
// return TypeMappings[refType];
|
|
319
|
+
return '-'; // TODO!
|
|
320
|
+
}
|
|
321
|
+
else if (schema) {
|
|
322
|
+
property = schema;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (typeof type !== 'string')
|
|
326
|
+
(0, error_1.raise)(`Invalid schema type ${typeof type} (expected string)`);
|
|
327
|
+
const variants = property.anyOf || property.oneOf;
|
|
328
|
+
if (variants) {
|
|
329
|
+
const nonNullType = variants.find((item) => typeof item !== 'boolean' && item?.type !== 'null');
|
|
330
|
+
if (nonNullType) {
|
|
331
|
+
return typeof nonNullType === 'object'
|
|
332
|
+
? getType(nonNullType, schemaId, registry)
|
|
333
|
+
: nonNullType.toString();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (type === 'string') {
|
|
337
|
+
if (property.format === 'date-time') {
|
|
338
|
+
return 'timestamptz';
|
|
339
|
+
}
|
|
340
|
+
return property.format === 'uuid' ? 'uuid' : 'text';
|
|
341
|
+
}
|
|
342
|
+
if (type === 'object' || type === 'array') {
|
|
343
|
+
return 'jsonb';
|
|
344
|
+
}
|
|
345
|
+
return type ?? 'jsonb';
|
|
346
|
+
}
|
|
347
|
+
function listProperties(schema) {
|
|
348
|
+
if (schema.properties) {
|
|
349
|
+
return {
|
|
350
|
+
type: 'object',
|
|
351
|
+
properties: schema.properties,
|
|
352
|
+
required: schema.required || [],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
// todo oneOf
|
|
356
|
+
if (!schema.anyOf) {
|
|
357
|
+
return {
|
|
358
|
+
type: 'object',
|
|
359
|
+
properties: {},
|
|
360
|
+
required: [],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const propertiesMap = {};
|
|
364
|
+
const requiredSet = new Set();
|
|
365
|
+
const initialized = false;
|
|
366
|
+
for (const item of schema.anyOf) {
|
|
367
|
+
if (typeof item !== 'object')
|
|
368
|
+
continue;
|
|
369
|
+
if (item.type !== 'object')
|
|
370
|
+
continue;
|
|
371
|
+
if (typeof item.properties !== 'object')
|
|
372
|
+
continue;
|
|
373
|
+
if (!initialized) {
|
|
374
|
+
for (const name of Object.keys(item.properties)) {
|
|
375
|
+
propertiesMap[name] = item.properties[name];
|
|
376
|
+
}
|
|
377
|
+
for (const name of item.required || []) {
|
|
378
|
+
requiredSet.add(name);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
if (!(0, equal_keys_1.isEqualKeys)(propertiesMap, item.properties)) {
|
|
383
|
+
console.error('Properties are not identical across all members of anyOf');
|
|
384
|
+
}
|
|
385
|
+
for (const name of item.required || []) {
|
|
386
|
+
if (!requiredSet.has(name)) {
|
|
387
|
+
console.error('Required fields are not identical across all members of anyOf');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
type: 'object',
|
|
394
|
+
properties: propertiesMap,
|
|
395
|
+
required: Array.from(requiredSet),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const DefaultScopeName = 'Default';
|
|
399
|
+
// todo REMOVE THIS
|
|
400
|
+
const SortableFields = [
|
|
401
|
+
'uid',
|
|
402
|
+
'slug',
|
|
403
|
+
'system',
|
|
404
|
+
'created',
|
|
405
|
+
'updated',
|
|
406
|
+
'deleted',
|
|
407
|
+
'position',
|
|
408
|
+
'priority',
|
|
409
|
+
'creator',
|
|
410
|
+
'active',
|
|
411
|
+
'name',
|
|
412
|
+
'kind',
|
|
413
|
+
'validity',
|
|
414
|
+
'location',
|
|
415
|
+
'visual',
|
|
416
|
+
'integration',
|
|
417
|
+
'refs',
|
|
418
|
+
'seo',
|
|
419
|
+
'contacts',
|
|
420
|
+
'content',
|
|
421
|
+
];
|
|
422
|
+
// todo escape notes!
|
|
423
|
+
// todo escape comments!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.spec.d.ts","sourceRoot":"","sources":["../../src/dbml-generator/generator.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const promises_1 = require("node:fs/promises");
|
|
4
|
+
const noop_1 = require("@tsofist/stem/lib/noop");
|
|
5
|
+
const artefacts_policy_1 = require("../artefacts-policy");
|
|
6
|
+
const types_1 = require("../definition-info/types");
|
|
7
|
+
const forge_1 = require("../schema-generator/forge");
|
|
8
|
+
const loader_1 = require("../schema-registry/loader");
|
|
9
|
+
const registry_1 = require("../schema-registry/registry");
|
|
10
|
+
const generator_1 = require("./generator");
|
|
11
|
+
describe('DBML Generator', () => {
|
|
12
|
+
const outputSchemaFile = './dbml.c1.generated.schema.tmp.json';
|
|
13
|
+
const outputSchemaMetadataFile = './dbml.c1.generated.definitions.tmp.json';
|
|
14
|
+
const schemaId = 'test-dbml';
|
|
15
|
+
let forgeSchemaResult;
|
|
16
|
+
let registry;
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
forgeSchemaResult = await (0, forge_1.forgeSchema)({
|
|
19
|
+
allowUseFallbackDescription: false,
|
|
20
|
+
explicitPublic: true,
|
|
21
|
+
schemaId,
|
|
22
|
+
tsconfigFrom: './tsconfig.build-test.json',
|
|
23
|
+
sourcesDirectoryPattern: 'test-sources/dbml/c1',
|
|
24
|
+
sourcesFilesPattern: ['service.api.ts', '*.api.ts', 'types.ts'],
|
|
25
|
+
outputSchemaFile,
|
|
26
|
+
outputSchemaMetadataFile,
|
|
27
|
+
});
|
|
28
|
+
registry = (0, registry_1.createSchemaForgeRegistry)();
|
|
29
|
+
const schema = await (0, loader_1.loadJSONSchema)([outputSchemaFile]);
|
|
30
|
+
registry.addSchema(schema);
|
|
31
|
+
}, 10_000);
|
|
32
|
+
afterAll(async () => {
|
|
33
|
+
if (!artefacts_policy_1.KEEP_SPEC_ARTEFACTS) {
|
|
34
|
+
await (0, promises_1.unlink)(outputSchemaFile).catch(noop_1.noop);
|
|
35
|
+
await (0, promises_1.unlink)(outputSchemaMetadataFile).catch(noop_1.noop);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
it('generated schema should be valid', () => {
|
|
39
|
+
expect(forgeSchemaResult).toBeTruthy();
|
|
40
|
+
expect(forgeSchemaResult.schema.$id).toStrictEqual(schemaId);
|
|
41
|
+
expect(forgeSchemaResult.generatedTemporaryFiles.length).toStrictEqual(1);
|
|
42
|
+
expect(forgeSchemaResult.refs.length).toStrictEqual(13);
|
|
43
|
+
});
|
|
44
|
+
it('basic cases', () => {
|
|
45
|
+
const definitions = registry.listDefinitions((info, keywords) => {
|
|
46
|
+
return (info.name.endsWith('Basic') &&
|
|
47
|
+
info.kind === types_1.SchemaDefinitionInfoKind.Type &&
|
|
48
|
+
keywords.has('dbEntity'));
|
|
49
|
+
});
|
|
50
|
+
const dbml = (0, generator_1.generateDBMLSpec)(registry, [
|
|
51
|
+
{
|
|
52
|
+
scopeName: 'TestScope',
|
|
53
|
+
comment: 'Test comment for TestScope',
|
|
54
|
+
schemaId,
|
|
55
|
+
definitions,
|
|
56
|
+
},
|
|
57
|
+
], {
|
|
58
|
+
meta: {
|
|
59
|
+
name: 'TestProject',
|
|
60
|
+
comment: 'Test comment!',
|
|
61
|
+
note: [
|
|
62
|
+
//
|
|
63
|
+
'Multi-line note',
|
|
64
|
+
'',
|
|
65
|
+
'> Powered by [SchemaForge]',
|
|
66
|
+
].join('\n'),
|
|
67
|
+
},
|
|
68
|
+
includeNotes: true,
|
|
69
|
+
});
|
|
70
|
+
expect(dbml).toMatchSnapshot();
|
|
71
|
+
});
|
|
72
|
+
it('table meta cases', () => {
|
|
73
|
+
const definitions = registry.listDefinitions((info, keywords) => {
|
|
74
|
+
return (info.name.endsWith('TableMeta') &&
|
|
75
|
+
info.kind === types_1.SchemaDefinitionInfoKind.Type &&
|
|
76
|
+
keywords.has('dbEntity'));
|
|
77
|
+
});
|
|
78
|
+
const dbml = (0, generator_1.generateDBMLSpec)(
|
|
79
|
+
//
|
|
80
|
+
registry, [{ schemaId, definitions }], { includeNotes: true });
|
|
81
|
+
expect(dbml).toMatchSnapshot();
|
|
82
|
+
});
|
|
83
|
+
it('index meta cases', () => {
|
|
84
|
+
const definitions = registry.listDefinitions((info, keywords) => {
|
|
85
|
+
return (info.name.endsWith('IndexMeta') &&
|
|
86
|
+
info.kind === types_1.SchemaDefinitionInfoKind.Type &&
|
|
87
|
+
keywords.has('dbEntity'));
|
|
88
|
+
});
|
|
89
|
+
const dbml = (0, generator_1.generateDBMLSpec)(
|
|
90
|
+
//
|
|
91
|
+
registry, [{ schemaId, definitions }], { includeNotes: true });
|
|
92
|
+
expect(dbml).toMatchSnapshot();
|
|
93
|
+
});
|
|
94
|
+
});
|