@api-client/core 0.18.6 → 0.18.8
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/build/src/modeling/importers/CsvImporter.d.ts +40 -6
- package/build/src/modeling/importers/CsvImporter.d.ts.map +1 -1
- package/build/src/modeling/importers/CsvImporter.js +75 -2
- package/build/src/modeling/importers/CsvImporter.js.map +1 -1
- package/build/src/modeling/importers/JsonSchemaImporter.d.ts +4 -0
- package/build/src/modeling/importers/JsonSchemaImporter.d.ts.map +1 -1
- package/build/src/modeling/importers/JsonSchemaImporter.js +89 -16
- package/build/src/modeling/importers/JsonSchemaImporter.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +21 -21
- package/package.json +1 -1
- package/src/modeling/importers/CsvImporter.ts +115 -5
- package/src/modeling/importers/JsonSchemaImporter.ts +111 -20
- package/tests/fixtures/schemas/arrays.json +35 -0
- package/tests/fixtures/schemas/complex.json +41 -0
- package/tests/fixtures/schemas/enumerated_values.json +11 -0
- package/tests/fixtures/schemas/person.json +21 -0
- package/tests/fixtures/schemas/regexp.json +12 -0
- package/tests/unit/modeling/importers/csv_importer.spec.ts +351 -0
- package/tests/unit/modeling/importers/json_schema_importer.spec.ts +137 -0
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
import { type CSVOptions, type ParseResult } from '@pawel-up/csv';
|
|
1
|
+
import { type CSVOptions, type ParseResult as ParseResult_ } from '@pawel-up/csv';
|
|
2
2
|
import type { DataDomain } from '../DataDomain.js';
|
|
3
3
|
import type { DomainEntity } from '../DomainEntity.js';
|
|
4
4
|
import type { DomainModel } from '../DomainModel.js';
|
|
5
|
-
|
|
5
|
+
interface ParseResult extends ParseResult_ {
|
|
6
|
+
/**
|
|
7
|
+
* The file name of the CSV that was parsed.
|
|
8
|
+
* If the CSV is provided as a string, it will be named `inline_csv_0`, `inline_csv_1`, etc.
|
|
9
|
+
*/
|
|
10
|
+
file: string;
|
|
11
|
+
}
|
|
12
|
+
interface ImportResult {
|
|
13
|
+
/**
|
|
14
|
+
* The DomainEntity created from the CSV data.
|
|
15
|
+
*/
|
|
16
|
+
entity: DomainEntity;
|
|
17
|
+
/**
|
|
18
|
+
* The DomainModel that contains the imported entity.
|
|
19
|
+
*/
|
|
20
|
+
model: DomainModel;
|
|
21
|
+
}
|
|
22
|
+
interface ImportManyResult {
|
|
23
|
+
/**
|
|
24
|
+
* An array of DomainEntity instances created from the CSV data.
|
|
25
|
+
*/
|
|
26
|
+
entities: DomainEntity[];
|
|
27
|
+
/**
|
|
28
|
+
* The DomainModel that contains all imported entities.
|
|
29
|
+
*/
|
|
30
|
+
model: DomainModel;
|
|
31
|
+
}
|
|
32
|
+
export type { CSVOptions, ParseResult, ImportResult, ImportManyResult };
|
|
6
33
|
/**
|
|
7
34
|
* Imports CSV data into a DataDomain.
|
|
8
35
|
*
|
|
@@ -25,6 +52,7 @@ export declare class CsvImporter {
|
|
|
25
52
|
* @returns A promise that resolves with the parsed result.
|
|
26
53
|
*/
|
|
27
54
|
parse(csv: File | string): Promise<ParseResult>;
|
|
55
|
+
private removeExtension;
|
|
28
56
|
/**
|
|
29
57
|
* Imports the parsed CSV data structure into the domain as a new model and entity.
|
|
30
58
|
* It creates a `DomainEntity` where each column from the CSV is represented as a `DomainProperty`.
|
|
@@ -33,9 +61,15 @@ export declare class CsvImporter {
|
|
|
33
61
|
* @param modelName The name to be used for the created `DomainModel` and `DomainEntity`.
|
|
34
62
|
* @returns A promise that resolves when the import is complete.
|
|
35
63
|
*/
|
|
36
|
-
import(data: ParseResult, modelName: string): Promise<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
import(data: ParseResult, modelName: string): Promise<ImportResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Imports multiple parsed CSV data structures into the same data model.
|
|
67
|
+
* Creates a single `DomainModel` with multiple `DomainEntity` instances, one for each CSV data structure.
|
|
68
|
+
* Each entity represents the structure of its corresponding CSV file.
|
|
69
|
+
* @param data Array of objects containing parsed CSV data and entity names.
|
|
70
|
+
* @param modelName The name to be used for the created `DomainModel`.
|
|
71
|
+
* @returns A promise that resolves with the created model and all entities.
|
|
72
|
+
*/
|
|
73
|
+
importMany(data: ParseResult[], modelName: string): Promise<ImportManyResult>;
|
|
40
74
|
}
|
|
41
75
|
//# sourceMappingURL=CsvImporter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CsvImporter.d.ts","sourceRoot":"","sources":["../../../../src/modeling/importers/CsvImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAa,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"CsvImporter.d.ts","sourceRoot":"","sources":["../../../../src/modeling/importers/CsvImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAa,KAAK,WAAW,IAAI,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAGlD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,UAAU,WAAY,SAAQ,YAAY;IACxC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;CACb;AAID,UAAU,YAAY;IACpB;;OAEG;IACH,MAAM,EAAE,YAAY,CAAA;IACpB;;OAEG;IACH,KAAK,EAAE,WAAW,CAAA;CACnB;AAED,UAAU,gBAAgB;IACxB;;OAEG;IACH,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB;;OAEG;IACH,KAAK,EAAE,WAAW,CAAA;CACnB;AAED,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAA;AAEvE;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,MAAM,CAAY;IAE1B;;;;OAIG;gBACS,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,UAAU;IAKpD;;;;;OAKG;IACU,KAAK,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ5D,OAAO,CAAC,eAAe;IAOvB;;;;;;;OAOG;IACU,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA6ChF;;;;;;;OAOG;IACU,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAyD3F"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CSVParser } from '@pawel-up/csv';
|
|
2
2
|
import { sanitizeInput, toDatabaseColumnName, toDatabaseTableName } from '../helpers/database.js';
|
|
3
|
+
let increment = 0;
|
|
3
4
|
/**
|
|
4
5
|
* Imports CSV data into a DataDomain.
|
|
5
6
|
*
|
|
@@ -24,8 +25,18 @@ export class CsvImporter {
|
|
|
24
25
|
* @param csv The CSV content to parse, as a File object or a string.
|
|
25
26
|
* @returns A promise that resolves with the parsed result.
|
|
26
27
|
*/
|
|
27
|
-
parse(csv) {
|
|
28
|
-
|
|
28
|
+
async parse(csv) {
|
|
29
|
+
const result = await this.parser.parse(csv);
|
|
30
|
+
return {
|
|
31
|
+
...result,
|
|
32
|
+
file: typeof csv === 'string' ? 'inline_csv_' + increment++ : csv.name,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
removeExtension(name) {
|
|
36
|
+
if (name.endsWith('.csv')) {
|
|
37
|
+
name = name.slice(0, -4); // Remove .csv extension if present
|
|
38
|
+
}
|
|
39
|
+
return name;
|
|
29
40
|
}
|
|
30
41
|
/**
|
|
31
42
|
* Imports the parsed CSV data structure into the domain as a new model and entity.
|
|
@@ -36,6 +47,7 @@ export class CsvImporter {
|
|
|
36
47
|
* @returns A promise that resolves when the import is complete.
|
|
37
48
|
*/
|
|
38
49
|
async import(data, modelName) {
|
|
50
|
+
modelName = this.removeExtension(modelName);
|
|
39
51
|
const name = toDatabaseTableName(modelName, 'imported_cvs_data');
|
|
40
52
|
const model = this.domain.addModel({ info: { name } });
|
|
41
53
|
const entity = model.addEntity({ info: { name } });
|
|
@@ -78,5 +90,66 @@ export class CsvImporter {
|
|
|
78
90
|
}
|
|
79
91
|
return { entity, model };
|
|
80
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Imports multiple parsed CSV data structures into the same data model.
|
|
95
|
+
* Creates a single `DomainModel` with multiple `DomainEntity` instances, one for each CSV data structure.
|
|
96
|
+
* Each entity represents the structure of its corresponding CSV file.
|
|
97
|
+
* @param data Array of objects containing parsed CSV data and entity names.
|
|
98
|
+
* @param modelName The name to be used for the created `DomainModel`.
|
|
99
|
+
* @returns A promise that resolves with the created model and all entities.
|
|
100
|
+
*/
|
|
101
|
+
async importMany(data, modelName) {
|
|
102
|
+
modelName = this.removeExtension(modelName);
|
|
103
|
+
const name = toDatabaseTableName(modelName, 'imported_cvs_data');
|
|
104
|
+
const model = this.domain.addModel({ info: { name } });
|
|
105
|
+
const sanitizedModelName = sanitizeInput(modelName);
|
|
106
|
+
if (name !== sanitizedModelName) {
|
|
107
|
+
model.info.displayName = sanitizedModelName;
|
|
108
|
+
}
|
|
109
|
+
const entities = [];
|
|
110
|
+
for (const { format, values, file } of data) {
|
|
111
|
+
const cleanFile = this.removeExtension(file);
|
|
112
|
+
const entityDbName = toDatabaseTableName(cleanFile, `entity_${entities.length}`);
|
|
113
|
+
const entity = model.addEntity({ info: { name: entityDbName } });
|
|
114
|
+
const sanitizedEntityName = sanitizeInput(cleanFile);
|
|
115
|
+
if (entityDbName !== sanitizedEntityName) {
|
|
116
|
+
entity.info.displayName = sanitizedEntityName;
|
|
117
|
+
}
|
|
118
|
+
const exampleRow = values[0];
|
|
119
|
+
for (const row of format) {
|
|
120
|
+
const { index, name, type, format } = row;
|
|
121
|
+
const columnName = toDatabaseColumnName(name, `column_${index}`);
|
|
122
|
+
const schema = {
|
|
123
|
+
info: { name: columnName },
|
|
124
|
+
type,
|
|
125
|
+
};
|
|
126
|
+
if (format) {
|
|
127
|
+
schema.bindings = [
|
|
128
|
+
{
|
|
129
|
+
type: 'web',
|
|
130
|
+
schema: {
|
|
131
|
+
format: format === 'integer' ? 'int64' : 'double',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(exampleRow) && exampleRow[index]) {
|
|
137
|
+
const value = exampleRow[index];
|
|
138
|
+
if (value !== undefined && value !== null) {
|
|
139
|
+
schema.schema = {
|
|
140
|
+
examples: [sanitizeInput(String(value))],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const prop = entity.addProperty(schema);
|
|
145
|
+
const sn = sanitizeInput(name);
|
|
146
|
+
if (sn !== columnName) {
|
|
147
|
+
prop.info.displayName = sn;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
entities.push(entity);
|
|
151
|
+
}
|
|
152
|
+
return { entities, model };
|
|
153
|
+
}
|
|
81
154
|
}
|
|
82
155
|
//# sourceMappingURL=CsvImporter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CsvImporter.js","sourceRoot":"","sources":["../../../../src/modeling/importers/CsvImporter.ts"],"names":[],"mappings":"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","sourcesContent":["import { type CSVOptions, CSVParser, type ParseResult } from '@pawel-up/csv'\nimport type { DataDomain } from '../DataDomain.js'\nimport { sanitizeInput, toDatabaseColumnName, toDatabaseTableName } from '../helpers/database.js'\nimport type { DomainPropertySchema } from '../DomainProperty.js'\nimport type { DomainEntity } from '../DomainEntity.js'\nimport type { DomainModel } from '../DomainModel.js'\n\nexport type { CSVOptions, ParseResult }\n\n/**\n * Imports CSV data into a DataDomain.\n *\n * This class parses CSV files and translates the column definitions into DomainProperty instances\n * within a specified DomainModel and DomainEntity.\n */\nexport class CsvImporter {\n private parser: CSVParser\n private domain: DataDomain\n\n /**\n * Creates an instance of CsvImporter.\n * @param domain The DataDomain to which the imported data will be added.\n * @param options Optional CSV parsing options.\n */\n constructor(domain: DataDomain, options?: CSVOptions) {\n this.parser = new CSVParser(options)\n this.domain = domain\n }\n\n /**\n * Parses a CSV file or string to extract its structure and data.\n * This is a wrapper around the underlying CSV parser.\n * @param csv The CSV content to parse, as a File object or a string.\n * @returns A promise that resolves with the parsed result.\n */\n public parse(csv: File | string): Promise<ParseResult> {\n return this.parser.parse(csv)\n }\n\n /**\n * Imports the parsed CSV data structure into the domain as a new model and entity.\n * It creates a `DomainEntity` where each column from the CSV is represented as a `DomainProperty`.\n * The first row of data is used to provide an example value for each property.\n * @param data The result from the `parse` method, containing the CSV structure and values.\n * @param modelName The name to be used for the created `DomainModel` and `DomainEntity`.\n * @returns A promise that resolves when the import is complete.\n */\n public async import(data: ParseResult, modelName: string): Promise<{ entity: DomainEntity; model: DomainModel }> {\n const name = toDatabaseTableName(modelName, 'imported_cvs_data')\n const model = this.domain.addModel({ info: { name } })\n const entity = model.addEntity({ info: { name } })\n const sanitizedInputName = sanitizeInput(modelName)\n if (name !== sanitizedInputName) {\n model.info.displayName = sanitizedInputName\n entity.info.displayName = sanitizedInputName\n }\n for (const row of data.format) {\n const { index, name, type, format } = row\n const columnName = toDatabaseColumnName(name, `column_${index}`)\n const schema: Partial<DomainPropertySchema> = {\n info: { name: columnName },\n type,\n }\n if (format) {\n schema.bindings = [\n {\n type: 'web',\n schema: {\n format: format === 'integer' ? 'int64' : 'double',\n },\n },\n ]\n }\n const exampleRow = data.values[0]\n if (Array.isArray(exampleRow) && exampleRow[index]) {\n const value = exampleRow[index]\n if (value !== undefined && value !== null) {\n schema.schema = {\n examples: [sanitizeInput(String(value))],\n }\n }\n }\n const prop = entity.addProperty(schema)\n const sn = sanitizeInput(name)\n if (sn !== columnName) {\n prop.info.displayName = sn\n }\n }\n return { entity, model }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"CsvImporter.js","sourceRoot":"","sources":["../../../../src/modeling/importers/CsvImporter.ts"],"names":[],"mappings":"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","sourcesContent":["import { type CSVOptions, CSVParser, type ParseResult as ParseResult_ } from '@pawel-up/csv'\nimport type { DataDomain } from '../DataDomain.js'\nimport { sanitizeInput, toDatabaseColumnName, toDatabaseTableName } from '../helpers/database.js'\nimport type { DomainPropertySchema } from '../DomainProperty.js'\nimport type { DomainEntity } from '../DomainEntity.js'\nimport type { DomainModel } from '../DomainModel.js'\n\ninterface ParseResult extends ParseResult_ {\n /**\n * The file name of the CSV that was parsed.\n * If the CSV is provided as a string, it will be named `inline_csv_0`, `inline_csv_1`, etc.\n */\n file: string\n}\n\nlet increment = 0\n\ninterface ImportResult {\n /**\n * The DomainEntity created from the CSV data.\n */\n entity: DomainEntity\n /**\n * The DomainModel that contains the imported entity.\n */\n model: DomainModel\n}\n\ninterface ImportManyResult {\n /**\n * An array of DomainEntity instances created from the CSV data.\n */\n entities: DomainEntity[]\n /**\n * The DomainModel that contains all imported entities.\n */\n model: DomainModel\n}\n\nexport type { CSVOptions, ParseResult, ImportResult, ImportManyResult }\n\n/**\n * Imports CSV data into a DataDomain.\n *\n * This class parses CSV files and translates the column definitions into DomainProperty instances\n * within a specified DomainModel and DomainEntity.\n */\nexport class CsvImporter {\n private parser: CSVParser\n private domain: DataDomain\n\n /**\n * Creates an instance of CsvImporter.\n * @param domain The DataDomain to which the imported data will be added.\n * @param options Optional CSV parsing options.\n */\n constructor(domain: DataDomain, options?: CSVOptions) {\n this.parser = new CSVParser(options)\n this.domain = domain\n }\n\n /**\n * Parses a CSV file or string to extract its structure and data.\n * This is a wrapper around the underlying CSV parser.\n * @param csv The CSV content to parse, as a File object or a string.\n * @returns A promise that resolves with the parsed result.\n */\n public async parse(csv: File | string): Promise<ParseResult> {\n const result = await this.parser.parse(csv)\n return {\n ...result,\n file: typeof csv === 'string' ? 'inline_csv_' + increment++ : csv.name,\n }\n }\n\n private removeExtension(name: string): string {\n if (name.endsWith('.csv')) {\n name = name.slice(0, -4) // Remove .csv extension if present\n }\n return name\n }\n\n /**\n * Imports the parsed CSV data structure into the domain as a new model and entity.\n * It creates a `DomainEntity` where each column from the CSV is represented as a `DomainProperty`.\n * The first row of data is used to provide an example value for each property.\n * @param data The result from the `parse` method, containing the CSV structure and values.\n * @param modelName The name to be used for the created `DomainModel` and `DomainEntity`.\n * @returns A promise that resolves when the import is complete.\n */\n public async import(data: ParseResult, modelName: string): Promise<ImportResult> {\n modelName = this.removeExtension(modelName)\n const name = toDatabaseTableName(modelName, 'imported_cvs_data')\n const model = this.domain.addModel({ info: { name } })\n const entity = model.addEntity({ info: { name } })\n const sanitizedInputName = sanitizeInput(modelName)\n if (name !== sanitizedInputName) {\n model.info.displayName = sanitizedInputName\n entity.info.displayName = sanitizedInputName\n }\n for (const row of data.format) {\n const { index, name, type, format } = row\n const columnName = toDatabaseColumnName(name, `column_${index}`)\n const schema: Partial<DomainPropertySchema> = {\n info: { name: columnName },\n type,\n }\n if (format) {\n schema.bindings = [\n {\n type: 'web',\n schema: {\n format: format === 'integer' ? 'int64' : 'double',\n },\n },\n ]\n }\n const exampleRow = data.values[0]\n if (Array.isArray(exampleRow) && exampleRow[index]) {\n const value = exampleRow[index]\n if (value !== undefined && value !== null) {\n schema.schema = {\n examples: [sanitizeInput(String(value))],\n }\n }\n }\n const prop = entity.addProperty(schema)\n const sn = sanitizeInput(name)\n if (sn !== columnName) {\n prop.info.displayName = sn\n }\n }\n return { entity, model }\n }\n\n /**\n * Imports multiple parsed CSV data structures into the same data model.\n * Creates a single `DomainModel` with multiple `DomainEntity` instances, one for each CSV data structure.\n * Each entity represents the structure of its corresponding CSV file.\n * @param data Array of objects containing parsed CSV data and entity names.\n * @param modelName The name to be used for the created `DomainModel`.\n * @returns A promise that resolves with the created model and all entities.\n */\n public async importMany(data: ParseResult[], modelName: string): Promise<ImportManyResult> {\n modelName = this.removeExtension(modelName)\n const name = toDatabaseTableName(modelName, 'imported_cvs_data')\n const model = this.domain.addModel({ info: { name } })\n const sanitizedModelName = sanitizeInput(modelName)\n if (name !== sanitizedModelName) {\n model.info.displayName = sanitizedModelName\n }\n\n const entities: DomainEntity[] = []\n\n for (const { format, values, file } of data) {\n const cleanFile = this.removeExtension(file)\n const entityDbName = toDatabaseTableName(cleanFile, `entity_${entities.length}`)\n const entity = model.addEntity({ info: { name: entityDbName } })\n const sanitizedEntityName = sanitizeInput(cleanFile)\n if (entityDbName !== sanitizedEntityName) {\n entity.info.displayName = sanitizedEntityName\n }\n\n const exampleRow = values[0]\n for (const row of format) {\n const { index, name, type, format } = row\n const columnName = toDatabaseColumnName(name, `column_${index}`)\n const schema: Partial<DomainPropertySchema> = {\n info: { name: columnName },\n type,\n }\n if (format) {\n schema.bindings = [\n {\n type: 'web',\n schema: {\n format: format === 'integer' ? 'int64' : 'double',\n },\n },\n ]\n }\n if (Array.isArray(exampleRow) && exampleRow[index]) {\n const value = exampleRow[index]\n if (value !== undefined && value !== null) {\n schema.schema = {\n examples: [sanitizeInput(String(value))],\n }\n }\n }\n const prop = entity.addProperty(schema)\n const sn = sanitizeInput(name)\n if (sn !== columnName) {\n prop.info.displayName = sn\n }\n }\n entities.push(entity)\n }\n\n return { entities, model }\n }\n}\n"]}
|
|
@@ -68,6 +68,10 @@ export declare class JsonSchemaImporter {
|
|
|
68
68
|
* Helper for entity creation, can be extended for custom logic.
|
|
69
69
|
*/
|
|
70
70
|
private createEntity;
|
|
71
|
+
/**
|
|
72
|
+
* Generates a meaningful name for inline entities based on their parent context.
|
|
73
|
+
*/
|
|
74
|
+
private generateInlineEntityName;
|
|
71
75
|
/**
|
|
72
76
|
* Populates a DomainEntity with its properties, associations, and parent relationships.
|
|
73
77
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsonSchemaImporter.d.ts","sourceRoot":"","sources":["../../../../src/modeling/importers/JsonSchemaImporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA8C,MAAM,aAAa,CAAA;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"JsonSchemaImporter.d.ts","sourceRoot":"","sources":["../../../../src/modeling/importers/JsonSchemaImporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA8C,MAAM,aAAa,CAAA;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAS/C;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,GAAG,MAAM,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB;;OAEG;IACH,KAAK,EAAE,WAAW,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,kDAAkD;IAClD,QAAQ,EAAE,WAAW,CAAA;CACtB;AAED;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,KAAK,CAAiC;gBAElC,MAAM,EAAE,UAAU;IAI9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,OAAO,CAAC,MAAM;IAId;;OAEG;IACU,MAAM,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4F5F,OAAO,CAAC,MAAM;IAYd;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAuBxB;;OAEG;IACH,OAAO,CAAC,YAAY;IA6BpB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAehC;;OAEG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAgDpB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAsF3B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAyEnC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA0D5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAuE/B,OAAO,CAAC,kBAAkB;CAgG3B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DomainProperty } from '../DomainProperty.js';
|
|
2
2
|
import { sanitizeInput, toDatabaseColumnName, toDatabaseTableName } from '../helpers/database.js';
|
|
3
|
+
import { nanoid } from '../../nanoid.js';
|
|
3
4
|
/**
|
|
4
5
|
* Imports JSON Schema definitions into a DataDomain.
|
|
5
6
|
*
|
|
@@ -38,31 +39,47 @@ export class JsonSchemaImporter {
|
|
|
38
39
|
// Build a flat map of all definitions and $id-indexed schemas
|
|
39
40
|
const definitionMap = new Map();
|
|
40
41
|
const idMap = new Map();
|
|
42
|
+
const processedSchemas = new Set(); // Track processed schema objects to avoid duplicates
|
|
41
43
|
for (const schema of schemas) {
|
|
42
44
|
// Add $id mapping if present
|
|
43
45
|
if (schema.contents.$id) {
|
|
44
46
|
idMap.set(schema.contents.$id, schema.contents);
|
|
47
|
+
// Add the entire schema to the definitions maps as well as they can be referenced by other schemas.
|
|
48
|
+
definitionMap.set(schema.contents.$id, schema.contents);
|
|
45
49
|
}
|
|
46
50
|
// Add the schema itself by its path
|
|
47
51
|
definitionMap.set(schema.path, schema.contents);
|
|
48
52
|
// Add all definitions inside this schema
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
for (const [defName, defSchema] of Object.entries(
|
|
53
|
+
const definitions = schema.contents.definitions;
|
|
54
|
+
const defs = schema.contents.$defs;
|
|
55
|
+
if (definitions) {
|
|
56
|
+
for (const [defName, defSchema] of Object.entries(definitions)) {
|
|
53
57
|
if (typeof defSchema === 'object') {
|
|
54
|
-
// Use a JSON pointer for the key
|
|
58
|
+
// Use a JSON pointer for the key - include both full path and short form
|
|
55
59
|
definitionMap.set(`${schema.path}#/definitions/${defName}`, defSchema);
|
|
56
|
-
// Also allow lookup by just #/definitions/defName for single-file schemas
|
|
57
60
|
definitionMap.set(`#/definitions/${defName}`, defSchema);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
if (defs) {
|
|
65
|
+
for (const [defName, defSchema] of Object.entries(defs)) {
|
|
66
|
+
if (typeof defSchema === 'object') {
|
|
67
|
+
// Use a JSON pointer for the key - include both full path and short form
|
|
68
|
+
definitionMap.set(`${schema.path}#/$defs/${defName}`, defSchema);
|
|
69
|
+
definitionMap.set(`#/$defs/${defName}`, defSchema);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
61
73
|
}
|
|
62
74
|
// == Pass 1: Create all entities ==
|
|
63
75
|
for (const [key, schema] of definitionMap.entries()) {
|
|
76
|
+
// Skip if this schema object has already been processed
|
|
77
|
+
if (processedSchemas.has(schema)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
64
80
|
if (typeof schema === 'object' && schema.type === 'object') {
|
|
65
81
|
this.createEntity(key, schema, key);
|
|
82
|
+
processedSchemas.add(schema);
|
|
66
83
|
}
|
|
67
84
|
else if (this.isEnum(schema)) {
|
|
68
85
|
this.enums.set(key, schema);
|
|
@@ -73,10 +90,15 @@ export class JsonSchemaImporter {
|
|
|
73
90
|
}
|
|
74
91
|
// Also create entities for all $id-indexed schemas if not already present
|
|
75
92
|
for (const [id, schema] of idMap.entries()) {
|
|
93
|
+
// Skip if this schema object has already been processed
|
|
94
|
+
if (processedSchemas.has(schema)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
76
97
|
if (!this.refMap.has(id) && typeof schema === 'object' && schema.type === 'object') {
|
|
77
98
|
this.createEntity(id, schema, id);
|
|
99
|
+
processedSchemas.add(schema);
|
|
78
100
|
}
|
|
79
|
-
else if (this.isEnum(schema)) {
|
|
101
|
+
else if (this.isEnum(schema) && !this.enums.has(id)) {
|
|
80
102
|
this.enums.set(id, schema);
|
|
81
103
|
}
|
|
82
104
|
else {
|
|
@@ -120,10 +142,10 @@ export class JsonSchemaImporter {
|
|
|
120
142
|
return definitionMap.get(refPath);
|
|
121
143
|
if (idMap.has(refPath))
|
|
122
144
|
return idMap.get(refPath);
|
|
123
|
-
// Try to resolve #/definitions/Name from any schema
|
|
124
|
-
if (refPath.startsWith('#/definitions/')) {
|
|
145
|
+
// Try to resolve #/definitions/Name or #/$defs/Name from any schema
|
|
146
|
+
if (refPath.startsWith('#/definitions/') || refPath.startsWith('#/$defs/')) {
|
|
125
147
|
for (const [key, schema] of definitionMap.entries()) {
|
|
126
|
-
if (key
|
|
148
|
+
if (key === refPath)
|
|
127
149
|
return schema;
|
|
128
150
|
}
|
|
129
151
|
}
|
|
@@ -140,7 +162,16 @@ export class JsonSchemaImporter {
|
|
|
140
162
|
* Helper for entity creation, can be extended for custom logic.
|
|
141
163
|
*/
|
|
142
164
|
createEntity(name, schema, refPath) {
|
|
143
|
-
|
|
165
|
+
let fixedName = schema.title || name;
|
|
166
|
+
// Extract just the definition name from JSON pointers (both short and full paths)
|
|
167
|
+
if (name.includes('#/definitions/')) {
|
|
168
|
+
const parts = name.split('#/definitions/');
|
|
169
|
+
fixedName = schema.title || parts[parts.length - 1];
|
|
170
|
+
}
|
|
171
|
+
else if (name.includes('#/$defs/')) {
|
|
172
|
+
const parts = name.split('#/$defs/');
|
|
173
|
+
fixedName = schema.title || parts[parts.length - 1];
|
|
174
|
+
}
|
|
144
175
|
const cleanName = toDatabaseTableName(fixedName, 'untitled_entity');
|
|
145
176
|
const entity = this.model.addEntity({
|
|
146
177
|
info: {
|
|
@@ -157,6 +188,21 @@ export class JsonSchemaImporter {
|
|
|
157
188
|
this.refMap.set(refPath, entity.key);
|
|
158
189
|
return entity;
|
|
159
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Generates a meaningful name for inline entities based on their parent context.
|
|
193
|
+
*/
|
|
194
|
+
generateInlineEntityName(parentEntityKey, propName, schema) {
|
|
195
|
+
// Try to use the schema title first
|
|
196
|
+
if (schema.title) {
|
|
197
|
+
return toDatabaseTableName(schema.title, 'untitled_entity');
|
|
198
|
+
}
|
|
199
|
+
// Find the parent entity to create a meaningful name
|
|
200
|
+
const parentEntity = this.domain.findEntity(parentEntityKey);
|
|
201
|
+
const parentName = parentEntity?.info.name || 'unknown';
|
|
202
|
+
// Create a name like "User_Address" or "Order_ShippingInfo"
|
|
203
|
+
const combinedName = `${parentName}_${propName}`;
|
|
204
|
+
return toDatabaseTableName(combinedName, 'untitled_entity');
|
|
205
|
+
}
|
|
160
206
|
/**
|
|
161
207
|
* Populates a DomainEntity with its properties, associations, and parent relationships.
|
|
162
208
|
*/
|
|
@@ -223,7 +269,7 @@ export class JsonSchemaImporter {
|
|
|
223
269
|
* Recursively analyzes a schema definition to find all contained primitive types and `$ref`s.
|
|
224
270
|
* It's used to understand the nature of a property (is it a primitive, an association, or a mix).
|
|
225
271
|
*/
|
|
226
|
-
collectTypesAndRefs(schema) {
|
|
272
|
+
collectTypesAndRefs(schema, parentEntityKey, propName) {
|
|
227
273
|
const collected = { refs: new Set(), types: new Set(), isArray: false };
|
|
228
274
|
if (typeof schema !== 'object') {
|
|
229
275
|
return collected;
|
|
@@ -239,11 +285,22 @@ export class JsonSchemaImporter {
|
|
|
239
285
|
collected.types.add(t);
|
|
240
286
|
}
|
|
241
287
|
}
|
|
288
|
+
// Handle inline object types by creating entities immediately
|
|
289
|
+
if (schema.type === 'object' && schema.properties && parentEntityKey && propName) {
|
|
290
|
+
// Create an entity for this inline object immediately
|
|
291
|
+
const inlineEntityName = this.generateInlineEntityName(parentEntityKey, propName, schema);
|
|
292
|
+
const inlineEntity = this.createEntity(inlineEntityName, schema, inlineEntityName);
|
|
293
|
+
// Populate it immediately since we have the schema
|
|
294
|
+
this.populateEntity(inlineEntity, schema);
|
|
295
|
+
// Add a reference to it so it gets treated as an association
|
|
296
|
+
collected.refs.add(inlineEntityName);
|
|
297
|
+
return collected;
|
|
298
|
+
}
|
|
242
299
|
if (schema.type === 'array') {
|
|
243
300
|
collected.isArray = true;
|
|
244
301
|
if (typeof schema.items === 'object') {
|
|
245
|
-
// Recurse into items
|
|
246
|
-
const itemInfo = this.collectTypesAndRefs(Array.isArray(schema.items) ? schema.items[0] : schema.items);
|
|
302
|
+
// Recurse into items - pass along parent context for inline objects in arrays
|
|
303
|
+
const itemInfo = this.collectTypesAndRefs(Array.isArray(schema.items) ? schema.items[0] : schema.items, parentEntityKey, propName ? `${propName}_item` : undefined);
|
|
247
304
|
itemInfo.refs.forEach((r) => collected.refs.add(r));
|
|
248
305
|
itemInfo.types.forEach((t) => collected.types.add(t));
|
|
249
306
|
}
|
|
@@ -252,7 +309,7 @@ export class JsonSchemaImporter {
|
|
|
252
309
|
const choices = schema.anyOf || schema.oneOf;
|
|
253
310
|
if (choices) {
|
|
254
311
|
for (const choice of choices) {
|
|
255
|
-
const choiceInfo = this.collectTypesAndRefs(choice);
|
|
312
|
+
const choiceInfo = this.collectTypesAndRefs(choice, parentEntityKey);
|
|
256
313
|
choiceInfo.refs.forEach((r) => collected.refs.add(r));
|
|
257
314
|
choiceInfo.types.forEach((t) => collected.types.add(t));
|
|
258
315
|
if (choiceInfo.isArray) {
|
|
@@ -260,6 +317,19 @@ export class JsonSchemaImporter {
|
|
|
260
317
|
}
|
|
261
318
|
}
|
|
262
319
|
}
|
|
320
|
+
if (schema.enum) {
|
|
321
|
+
// If the schema has an enum, we treat it as a string type with specific values.
|
|
322
|
+
collected.types.add('string');
|
|
323
|
+
// consider the following schema: `"enum": ["one", "two", "three"]`
|
|
324
|
+
const id = nanoid(8);
|
|
325
|
+
this.enums.set(id, {
|
|
326
|
+
type: 'string',
|
|
327
|
+
oneOf: schema.enum.map((value) => ({
|
|
328
|
+
const: String(value), // Ensure enum values are strings
|
|
329
|
+
})),
|
|
330
|
+
});
|
|
331
|
+
collected.refs.add(id); // Add the enum reference
|
|
332
|
+
}
|
|
263
333
|
return collected;
|
|
264
334
|
}
|
|
265
335
|
/**
|
|
@@ -267,7 +337,9 @@ export class JsonSchemaImporter {
|
|
|
267
337
|
*/
|
|
268
338
|
createPropertyOrAssociation(entity, propName, propSchema, isRequired) {
|
|
269
339
|
// Analyze the property schema to understand its composition (primitives, refs, arrays).
|
|
270
|
-
|
|
340
|
+
// Pass entity context to collectTypesAndRefs for inline object handling
|
|
341
|
+
const analysis = this.collectTypesAndRefs(propSchema, entity.key, propName);
|
|
342
|
+
// console.log(`Analyzing property '${propName}' in entity '${entity.info.name}':`, analysis, propSchema)
|
|
271
343
|
// Case 1: If the property has `$ref` to an enum, we treat it as a DomainProperty.
|
|
272
344
|
if (analysis.refs.size === 1 && this.enums.has([...analysis.refs][0])) {
|
|
273
345
|
const prop = this.createEnumProperty(propName, propSchema, this.enums.get([...analysis.refs][0]), entity.info.name);
|
|
@@ -517,6 +589,7 @@ export class JsonSchemaImporter {
|
|
|
517
589
|
}
|
|
518
590
|
}
|
|
519
591
|
if (Object.keys(schema).length > 0) {
|
|
592
|
+
// console.log(`Creating enum property '${propName}' with schema:`, schema)
|
|
520
593
|
prop.schema = schema;
|
|
521
594
|
}
|
|
522
595
|
return prop;
|