@apso/cli 0.1.4 → 0.1.6
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/dist/commands/server/scaffold.js +2 -1
- package/dist/lib/dto.d.ts +2 -1
- package/dist/lib/dto.js +34 -6
- package/dist/lib/entity.js +1 -1
- package/dist/lib/gql-dto.js +1 -1
- package/dist/lib/templates/entities/entity-col-json.eta +1 -1
- package/dist/lib/templates/rest/dto-rest.eta +2 -2
- package/dist/lib/templates/rest/module-rest.eta +1 -1
- package/dist/lib/utils/casing.d.ts +2 -1
- package/dist/lib/utils/casing.js +18 -8
- package/dist/lib/utils/field.d.ts +1 -1
- package/dist/lib/utils/field.js +37 -16
- package/dist/lib/utils/relationships/parse.js +34 -56
- package/dist/lib/utils/relationships/parse.spec.js +28 -17
- package/dist/test-nested-relationships.d.ts +1 -0
- package/dist/test-nested-relationships.js +112 -0
- package/dist/tests/templates/entities/types/json-template.spec.js +2 -2
- package/dist/tests/utils/relationship-deduplication.spec.js +5 -4
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -27,7 +27,8 @@ class Scaffold extends base_command_1.default {
|
|
|
27
27
|
}
|
|
28
28
|
async setupRestFiles(dir, entity, relationshipMap, apiType) {
|
|
29
29
|
const filePath = path.join(dir, entity.name);
|
|
30
|
-
|
|
30
|
+
const entityRelationships = relationshipMap[entity.name] || [];
|
|
31
|
+
await (0, lib_1.createDto)(filePath, entity, entityRelationships, { apiType });
|
|
31
32
|
await (0, lib_1.createService)(filePath, entity);
|
|
32
33
|
await (0, lib_1.createController)(filePath, entity, relationshipMap);
|
|
33
34
|
}
|
package/dist/lib/dto.d.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { Entity } from "./types/entity";
|
|
|
4
4
|
*
|
|
5
5
|
* @param apiBaseDir The base directory for the generated API module (e.g., 'src/autogen/users').
|
|
6
6
|
* @param entity The entity definition object from the parsed .apsorc.
|
|
7
|
+
* @param relationships The relationships array from the parsed .apsorc.
|
|
7
8
|
* @param options Optional configuration flags.
|
|
8
9
|
* @param options.apiType The type of API being generated (default: "rest").
|
|
9
10
|
* @returns {Promise<void>} A promise that resolves when the DTO files are created.
|
|
10
11
|
*/
|
|
11
|
-
export declare const createDto: (apiBaseDir: string, entity: Entity, options?: {
|
|
12
|
+
export declare const createDto: (apiBaseDir: string, entity: Entity, relationships: any[], options?: {
|
|
12
13
|
apiType?: string;
|
|
13
14
|
}) => Promise<void>;
|
package/dist/lib/dto.js
CHANGED
|
@@ -13,17 +13,45 @@ const casing_1 = require("./utils/casing");
|
|
|
13
13
|
*
|
|
14
14
|
* @param apiBaseDir The base directory for the generated API module (e.g., 'src/autogen/users').
|
|
15
15
|
* @param entity The entity definition object from the parsed .apsorc.
|
|
16
|
+
* @param relationships The relationships array from the parsed .apsorc.
|
|
16
17
|
* @param options Optional configuration flags.
|
|
17
18
|
* @param options.apiType The type of API being generated (default: "rest").
|
|
18
19
|
* @returns {Promise<void>} A promise that resolves when the DTO files are created.
|
|
19
20
|
*/
|
|
20
|
-
const createDto = async (apiBaseDir, entity,
|
|
21
|
+
const createDto = async (apiBaseDir, entity, relationships, // Relationship[]
|
|
22
|
+
options) => {
|
|
21
23
|
var _a;
|
|
22
|
-
const { name: entityName, fields = []
|
|
23
|
-
const dtoDir = path.join(apiBaseDir, "
|
|
24
|
+
const { name: entityName, fields = [] } = entity;
|
|
25
|
+
const dtoDir = path.join(apiBaseDir, "dtos");
|
|
24
26
|
const dtoFile = path.join(dtoDir, `${entityName}.dto.ts`);
|
|
25
27
|
const columns = (0, field_1.getFieldForTemplate)(fields, entityName);
|
|
26
|
-
const relationshipsTemplate = (0, relationships_1.getRelationshipForTemplate)(entityName,
|
|
28
|
+
const relationshipsTemplate = (0, relationships_1.getRelationshipForTemplate)(entityName, relationships);
|
|
29
|
+
if (process.env.DEBUG) {
|
|
30
|
+
console.log('[DTO DEBUG] Generating DTO for:', entityName);
|
|
31
|
+
console.log('[DTO DEBUG] relationshipsTemplate:', relationshipsTemplate);
|
|
32
|
+
}
|
|
33
|
+
// Add foreign key columns for ManyToOne relationships if not present
|
|
34
|
+
relationshipsTemplate.forEach(rel => {
|
|
35
|
+
if (rel.type === 'ManyToOne') {
|
|
36
|
+
const fkName = (0, casing_1.camelCase)(rel.referenceName || rel.name) + 'Id';
|
|
37
|
+
if (process.env.DEBUG) {
|
|
38
|
+
console.log('[DTO DEBUG]', {
|
|
39
|
+
relType: rel.type,
|
|
40
|
+
relName: rel.name,
|
|
41
|
+
referenceName: rel.referenceName,
|
|
42
|
+
fkName,
|
|
43
|
+
columns: columns.map(c => c.name)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!columns.some(col => col.name === fkName)) {
|
|
47
|
+
columns.push({
|
|
48
|
+
name: fkName,
|
|
49
|
+
dataType: 'number',
|
|
50
|
+
type: 'integer',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
27
55
|
const primaryKeyColumns = columns.filter((column) => column.primary === true);
|
|
28
56
|
const addDefaultPKProperty = primaryKeyColumns.length === 0;
|
|
29
57
|
const createdAt = entity.created_at;
|
|
@@ -31,8 +59,8 @@ const createDto = async (apiBaseDir, entity, options) => {
|
|
|
31
59
|
// Filter columns: remove id, createdAt, updatedAt
|
|
32
60
|
const dtoColumns = columns.filter((col) => !["id", "createdAt", "updatedAt"].includes(col.name));
|
|
33
61
|
// Class names
|
|
34
|
-
const createDtoName = `Create${(0, casing_1.
|
|
35
|
-
const updateDtoName = `Update${(0, casing_1.
|
|
62
|
+
const createDtoName = `Create${(0, casing_1.pascalCase)(entityName)}Dto`;
|
|
63
|
+
const updateDtoName = `Update${(0, casing_1.pascalCase)(entityName)}Dto`;
|
|
36
64
|
const data = {
|
|
37
65
|
entityName,
|
|
38
66
|
addDefaultPKProperty,
|
package/dist/lib/entity.js
CHANGED
|
@@ -36,7 +36,7 @@ const createEntity = async (apiBaseDir, entity, relationshipsNew, apiType) => {
|
|
|
36
36
|
snakeCasedName: (0, casing_1.snakeCase)(name),
|
|
37
37
|
createdAt,
|
|
38
38
|
updatedAt,
|
|
39
|
-
pluralizedName: (0,
|
|
39
|
+
pluralizedName: (0, casing_1.camelCase)((0, pluralize_1.default)(name)),
|
|
40
40
|
columns,
|
|
41
41
|
associations: [...new Set(relationships)],
|
|
42
42
|
entitiesToImport,
|
package/dist/lib/gql-dto.js
CHANGED
|
@@ -27,7 +27,7 @@ const createGqlDTO = async (apiBaseDir, entity, relationshipsNew) => {
|
|
|
27
27
|
snakeCasedName: (0, casing_1.snakeCase)(name),
|
|
28
28
|
createdAt,
|
|
29
29
|
updatedAt,
|
|
30
|
-
pluralizedName: (0,
|
|
30
|
+
pluralizedName: (0, casing_1.camelCase)((0, pluralize_1.default)(name)),
|
|
31
31
|
columns,
|
|
32
32
|
associations: relationships,
|
|
33
33
|
entitiesToImport,
|
|
@@ -5,7 +5,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
|
|
5
5
|
import * as enums from '../../enums';
|
|
6
6
|
<% } %>
|
|
7
7
|
|
|
8
|
-
export class <%= it.
|
|
8
|
+
export class <%= it.entityName %>Create {
|
|
9
9
|
<% it.columns.forEach((field) => { %>
|
|
10
10
|
@ApiProperty()
|
|
11
11
|
<%if (field.dataType.includes("Enum")) {%>
|
|
@@ -20,7 +20,7 @@ export class <%= it.name %>Create {
|
|
|
20
20
|
<% }) %>
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export class <%= it.
|
|
23
|
+
export class <%= it.entityName %> {
|
|
24
24
|
<% if (it.addDefaultPKProperty) { %>
|
|
25
25
|
@ApiProperty()
|
|
26
26
|
id: number;
|
package/dist/lib/utils/casing.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.snakeCase = exports.camelCase = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
exports.pascalCase = exports.snakeCase = exports.camelCase = void 0;
|
|
4
|
+
function camelCase(str) {
|
|
5
|
+
return str
|
|
6
|
+
// Insert a space before all caps that are followed by lower case (for PascalCase)
|
|
7
|
+
.replace(/([\da-z])([A-Z])/g, '$1 $2')
|
|
8
|
+
// Replace all separators with spaces
|
|
9
|
+
.replace(/[ _-]+/g, ' ')
|
|
10
|
+
// Lowercase the string, then uppercase the first letter of each word (except the first)
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/ (\w)/g, (_, c) => c ? c.toUpperCase() : '')
|
|
13
|
+
// Remove spaces
|
|
14
|
+
.replace(/ /g, '');
|
|
15
|
+
}
|
|
11
16
|
exports.camelCase = camelCase;
|
|
12
17
|
const snakeCase = (str) => str[0].toLowerCase() +
|
|
13
18
|
str
|
|
14
19
|
.slice(1, str.length)
|
|
15
20
|
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
16
21
|
exports.snakeCase = snakeCase;
|
|
22
|
+
function pascalCase(str) {
|
|
23
|
+
const c = camelCase(str);
|
|
24
|
+
return c.charAt(0).toUpperCase() + c.slice(1);
|
|
25
|
+
}
|
|
26
|
+
exports.pascalCase = pascalCase;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FieldType, Field, ComputedField, Entity } from "../types";
|
|
2
|
-
export declare const getJsTypeFromFieldType: (type: FieldType) => string;
|
|
2
|
+
export declare const getJsTypeFromFieldType: (type: FieldType | string) => string;
|
|
3
3
|
export declare const getDefaultValueForField: (field: Field) => string | null;
|
|
4
4
|
export declare const getFieldForTemplate: (fields: Field[], entityName: string) => ComputedField[];
|
|
5
5
|
export declare const fieldToEnumType: (fieldName: string, entityName: string) => string;
|
package/dist/lib/utils/field.js
CHANGED
|
@@ -2,22 +2,43 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.typeExistsInEntity = exports.fieldToEnumType = exports.getFieldForTemplate = exports.getDefaultValueForField = exports.getJsTypeFromFieldType = void 0;
|
|
4
4
|
const casing_1 = require("./casing");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
const typeToJsType = {
|
|
6
|
+
array: "string[]",
|
|
7
|
+
boolean: "boolean",
|
|
8
|
+
integer: "number",
|
|
9
|
+
float: "number",
|
|
10
|
+
real: "number",
|
|
11
|
+
double: "number",
|
|
12
|
+
decimal: "number",
|
|
13
|
+
numeric: "number",
|
|
14
|
+
smallint: "number",
|
|
15
|
+
serial: "number",
|
|
16
|
+
bigserial: "number",
|
|
17
|
+
smallserial: "number",
|
|
18
|
+
bigint: "string",
|
|
19
|
+
text: "string",
|
|
20
|
+
varchar: "string",
|
|
21
|
+
char: "string",
|
|
22
|
+
uuid: "string",
|
|
23
|
+
xml: "string",
|
|
24
|
+
inet: "string",
|
|
25
|
+
interval: "string",
|
|
26
|
+
money: "string",
|
|
27
|
+
date: "string",
|
|
28
|
+
timestamp: "string",
|
|
29
|
+
timestamptz: "string",
|
|
30
|
+
timetz: "string",
|
|
31
|
+
tsvector: "string",
|
|
32
|
+
string: "string",
|
|
33
|
+
json: "any",
|
|
34
|
+
jsonb: "any",
|
|
35
|
+
"json-plain": "any",
|
|
36
|
+
bytea: "Buffer",
|
|
37
|
+
point: "{ x: number, y: number }",
|
|
38
|
+
int4range: "{ lower: number, upper: number, lowerInclusive: boolean, upperInclusive: boolean }",
|
|
39
|
+
enum: "string", // handled elsewhere for custom enums
|
|
20
40
|
};
|
|
41
|
+
const getJsTypeFromFieldType = (type) => { var _a; return (_a = typeToJsType[type]) !== null && _a !== void 0 ? _a : "string"; };
|
|
21
42
|
exports.getJsTypeFromFieldType = getJsTypeFromFieldType;
|
|
22
43
|
const getDefaultValueForField = (field) => {
|
|
23
44
|
var _a, _b;
|
|
@@ -57,7 +78,7 @@ const getFieldForTemplate = (fields, entityName) => fields.map((field) => ({
|
|
|
57
78
|
: (0, exports.getJsTypeFromFieldType)(field.type),
|
|
58
79
|
}));
|
|
59
80
|
exports.getFieldForTemplate = getFieldForTemplate;
|
|
60
|
-
const fieldToEnumType = (fieldName, entityName) => `${(0, casing_1.
|
|
81
|
+
const fieldToEnumType = (fieldName, entityName) => `${(0, casing_1.pascalCase)(entityName)}${(0, casing_1.pascalCase)(fieldName)}Enum`;
|
|
61
82
|
exports.fieldToEnumType = fieldToEnumType;
|
|
62
83
|
const typeExistsInEntity = (entity, type) => (entity === null || entity === void 0 ? void 0 : entity.fields)
|
|
63
84
|
? entity === null || entity === void 0 ? void 0 : entity.fields.findIndex((field) => field.type === type)
|
|
@@ -13,23 +13,8 @@ const casing_1 = require("../../utils/casing");
|
|
|
13
13
|
*/
|
|
14
14
|
const parseOneToMany = (relationship) => {
|
|
15
15
|
return {
|
|
16
|
-
[relationship.from]: [
|
|
17
|
-
|
|
18
|
-
name: relationship.to,
|
|
19
|
-
type: "OneToMany",
|
|
20
|
-
biDirectional: true
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
[relationship.to]: [
|
|
24
|
-
{
|
|
25
|
-
name: relationship.from,
|
|
26
|
-
type: "ManyToOne",
|
|
27
|
-
nullable: relationship.nullable || false,
|
|
28
|
-
biDirectional: true,
|
|
29
|
-
index: relationship.index || false,
|
|
30
|
-
cascadeDelete: relationship.cascadeDelete || false
|
|
31
|
-
},
|
|
32
|
-
],
|
|
16
|
+
[relationship.from]: [{ name: relationship.to, type: "OneToMany", biDirectional: true, referenceName: relationship.to_name || relationship.to, inverseReferenceName: relationship.from }],
|
|
17
|
+
[relationship.to]: [{ name: relationship.from, type: "ManyToOne", nullable: relationship.nullable || false, biDirectional: true, index: relationship.index || false, cascadeDelete: relationship.cascadeDelete || false, referenceName: relationship.from, inverseReferenceName: relationship.to_name || relationship.to }],
|
|
33
18
|
};
|
|
34
19
|
};
|
|
35
20
|
exports.parseOneToMany = parseOneToMany;
|
|
@@ -95,7 +80,7 @@ const parseManyToMany = (relationship, allRelationships) => {
|
|
|
95
80
|
isOwningSide = true;
|
|
96
81
|
}
|
|
97
82
|
// Default currentPropertyName if to_name is not provided
|
|
98
|
-
const currentPropertyName = relationship.to_name || (0,
|
|
83
|
+
const currentPropertyName = relationship.to_name || (0, casing_1.camelCase)((0, pluralize_1.default)(relationship.to));
|
|
99
84
|
let inversePropertyName = inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.to_name;
|
|
100
85
|
// For self-referencing pairs, store the other to_name as inverseReferenceName
|
|
101
86
|
if (isSelfReferencing && selfRefInverse) {
|
|
@@ -111,8 +96,8 @@ const parseManyToMany = (relationship, allRelationships) => {
|
|
|
111
96
|
// Default inverse property name if missing (using calculateInversePropertyName logic later)
|
|
112
97
|
}
|
|
113
98
|
// Calculate reference names, applying defaults if needed
|
|
114
|
-
const fromSideRefName = relationship.to_name ||
|
|
115
|
-
const toSideRefName = (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.to_name) ||
|
|
99
|
+
const fromSideRefName = relationship.to_name || relationship.to;
|
|
100
|
+
const toSideRefName = (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.to_name) || relationship.from;
|
|
116
101
|
let joinTableName = relationship.joinTableName;
|
|
117
102
|
// For self-referencing, ensure both sides get join=true and the same custom names
|
|
118
103
|
if (isSelfReferencing && relationship.bi_directional) {
|
|
@@ -179,9 +164,9 @@ const createFromOneToOneRelationship = (relationship, inverseDefinition) => {
|
|
|
179
164
|
type: "OneToOne",
|
|
180
165
|
join: isOwner,
|
|
181
166
|
nullable: relationship.nullable || false,
|
|
182
|
-
referenceName: relationship.to_name || relationship.to
|
|
167
|
+
referenceName: relationship.to_name || relationship.to,
|
|
183
168
|
biDirectional: relationship.bi_directional || false,
|
|
184
|
-
inverseReferenceName: (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.to_name) || relationship.from
|
|
169
|
+
inverseReferenceName: (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.to_name) || relationship.from,
|
|
185
170
|
};
|
|
186
171
|
};
|
|
187
172
|
/**
|
|
@@ -199,8 +184,8 @@ isOwner // Is the 'from' side the owner?
|
|
|
199
184
|
type: "OneToOne",
|
|
200
185
|
biDirectional: true,
|
|
201
186
|
join: !isOwner,
|
|
202
|
-
referenceName: inverseDefinition.to_name || relationship.from
|
|
203
|
-
inverseReferenceName: relationship.to_name || relationship.to
|
|
187
|
+
referenceName: inverseDefinition.to_name || relationship.from,
|
|
188
|
+
inverseReferenceName: relationship.to_name || relationship.to,
|
|
204
189
|
};
|
|
205
190
|
};
|
|
206
191
|
/**
|
|
@@ -217,8 +202,8 @@ const parseRelationship = (relationship, allRelationships) => {
|
|
|
217
202
|
}
|
|
218
203
|
switch (relationship.type) {
|
|
219
204
|
case "OneToMany": {
|
|
220
|
-
const fromSideRefName = relationship.to_name ||
|
|
221
|
-
const toSideRefName = relationship.from
|
|
205
|
+
const fromSideRefName = relationship.to_name || relationship.to;
|
|
206
|
+
const toSideRefName = relationship.from;
|
|
222
207
|
return {
|
|
223
208
|
[relationship.from]: [{ name: relationship.to, type: "OneToMany", biDirectional: true, referenceName: fromSideRefName, inverseReferenceName: toSideRefName }],
|
|
224
209
|
[relationship.to]: [{ name: relationship.from, type: "ManyToOne", nullable: relationship.nullable || false, biDirectional: true, index: relationship.index || false, cascadeDelete: relationship.cascadeDelete || false, referenceName: toSideRefName, inverseReferenceName: fromSideRefName }],
|
|
@@ -226,7 +211,7 @@ const parseRelationship = (relationship, allRelationships) => {
|
|
|
226
211
|
}
|
|
227
212
|
case "ManyToOne":
|
|
228
213
|
return {
|
|
229
|
-
[relationship.from]: [{ name: relationship.to, type: "ManyToOne", referenceName: relationship.to_name || relationship.to
|
|
214
|
+
[relationship.from]: [{ name: relationship.to, type: "ManyToOne", referenceName: relationship.to_name || relationship.to, nullable: relationship.nullable || false, index: relationship.index || false }],
|
|
230
215
|
};
|
|
231
216
|
case "OneToOne": {
|
|
232
217
|
const inverseOnetoOneDef = allRelationships.find((def) => def.from === relationship.to &&
|
|
@@ -257,7 +242,7 @@ const parseRelationship = (relationship, allRelationships) => {
|
|
|
257
242
|
type: "OneToOne",
|
|
258
243
|
biDirectional: true,
|
|
259
244
|
join: !(fromRel.join || false),
|
|
260
|
-
referenceName: relationship.from
|
|
245
|
+
referenceName: relationship.from,
|
|
261
246
|
inverseReferenceName: fromRel.referenceName || undefined // Reference the 'from' side's name, default to undefined if null
|
|
262
247
|
};
|
|
263
248
|
responseMap[relationship.to] = [
|
|
@@ -352,10 +337,8 @@ const getRelationshipForTemplate = (entityName, relationships) => {
|
|
|
352
337
|
if (!relationships) {
|
|
353
338
|
return [];
|
|
354
339
|
}
|
|
355
|
-
// More comprehensive deduplication to handle all duplicate cases
|
|
356
|
-
const dedupedRelationships = [];
|
|
357
|
-
const processedRefs = new Map();
|
|
358
340
|
// First pass: Group by name and referenceName (more specific)
|
|
341
|
+
const processedRefs = new Map();
|
|
359
342
|
relationships.forEach((relationship) => {
|
|
360
343
|
var _a;
|
|
361
344
|
const name = relationship.name;
|
|
@@ -366,27 +349,15 @@ const getRelationshipForTemplate = (entityName, relationships) => {
|
|
|
366
349
|
processedRefs.set(key, relationship);
|
|
367
350
|
}
|
|
368
351
|
});
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
const nameOnlyKeys = new Set();
|
|
372
|
-
processedRefs.forEach((rel) => {
|
|
373
|
-
if (!nameOnlyKeys.has(rel.name)) {
|
|
374
|
-
nameOnlyKeys.add(rel.name);
|
|
375
|
-
dedupedRelationships.push(rel);
|
|
376
|
-
}
|
|
377
|
-
else if (rel.join === true) {
|
|
378
|
-
// If we already have this name but the current one is the owner (join=true),
|
|
379
|
-
// replace the existing one
|
|
380
|
-
const existingIdx = dedupedRelationships.findIndex(r => r.name === rel.name);
|
|
381
|
-
if (existingIdx !== -1 && !dedupedRelationships[existingIdx].join) {
|
|
382
|
-
dedupedRelationships[existingIdx] = rel;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
return dedupedRelationships.map((relationship) => {
|
|
352
|
+
// Only deduplicate by name+referenceName, not just name
|
|
353
|
+
return [...processedRefs.values()].map((relationship) => {
|
|
387
354
|
const thisReferenceName = relationship.referenceName;
|
|
388
355
|
const relationshipName = (0, casing_1.camelCase)(thisReferenceName || relationship.name);
|
|
389
356
|
const needsPluralInverse = relationship.type === 'ManyToOne' || relationship.type === 'ManyToMany';
|
|
357
|
+
// DEBUG LOGGING
|
|
358
|
+
if (process.env.DEBUG) {
|
|
359
|
+
console.log('[DEBUG] relationship.name:', relationship.name, '| relationship.referenceName:', relationship.referenceName, '| thisReferenceName:', thisReferenceName, '| pluralizedRelationshipName:', (0, casing_1.camelCase)((0, pluralize_1.default)(thisReferenceName || relationship.name)), '| pluralizedName:', (0, casing_1.camelCase)((0, pluralize_1.default)(relationship.name)));
|
|
360
|
+
}
|
|
390
361
|
const inverseSidePropertyName = calculateInversePropertyName(relationship, entityName, relationshipName, needsPluralInverse);
|
|
391
362
|
if (relationship.biDirectional && !relationship.inverseReferenceName &&
|
|
392
363
|
(relationship.type === 'ManyToMany' || relationship.type === 'OneToMany' || relationship.type === 'OneToOne')) {
|
|
@@ -395,8 +366,8 @@ const getRelationshipForTemplate = (entityName, relationships) => {
|
|
|
395
366
|
return {
|
|
396
367
|
...relationship,
|
|
397
368
|
relationshipName,
|
|
398
|
-
pluralizedRelationshipName: (0, pluralize_1.default)(
|
|
399
|
-
pluralizedName: (0,
|
|
369
|
+
pluralizedRelationshipName: (0, casing_1.camelCase)((0, pluralize_1.default)(thisReferenceName || relationship.name)),
|
|
370
|
+
pluralizedName: (0, casing_1.camelCase)((0, pluralize_1.default)(relationship.name)),
|
|
400
371
|
camelCasedName: (0, casing_1.camelCase)(relationship.name),
|
|
401
372
|
camelCasedId: (0, exports.getRelationshipIdField)(relationship),
|
|
402
373
|
entityName: (0, casing_1.camelCase)(entityName),
|
|
@@ -422,15 +393,22 @@ const getNestedRelationships = (entityName, relationshipMap, prefix = "", visite
|
|
|
422
393
|
const referenceName = getRelationshipName(relationship);
|
|
423
394
|
const formattedName = type === "ManyToOne" || type === "OneToOne"
|
|
424
395
|
? (0, casing_1.camelCase)(referenceName)
|
|
425
|
-
: (0,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
396
|
+
: (0, casing_1.camelCase)((0, pluralize_1.default)(referenceName));
|
|
397
|
+
// Create the relationship path while preserving camelCase
|
|
398
|
+
let relationshipPath;
|
|
399
|
+
if (prefix) {
|
|
400
|
+
// Split the path by dots and preserve each segment's casing
|
|
401
|
+
const segments = prefix.split('.');
|
|
402
|
+
relationshipPath = `${segments.join('.')}.${formattedName}`;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
relationshipPath = formattedName;
|
|
406
|
+
}
|
|
429
407
|
const hasChild = relationshipPath.includes(".");
|
|
430
408
|
const exists = prefix
|
|
431
409
|
.split(".")
|
|
432
410
|
.some((a) => a === (0, casing_1.camelCase)(referenceName) ||
|
|
433
|
-
a === (0,
|
|
411
|
+
a === (0, casing_1.camelCase)((0, pluralize_1.default)(referenceName)));
|
|
434
412
|
const deepEnough = prefix.split(".").length >= 4;
|
|
435
413
|
const canAdd = hasChild && !exists && !deepEnough;
|
|
436
414
|
if (canAdd) {
|
|
@@ -26,26 +26,26 @@ const parse_1 = require("./parse");
|
|
|
26
26
|
{
|
|
27
27
|
name: 'EntityB',
|
|
28
28
|
type: 'ManyToMany',
|
|
29
|
-
referenceName: '
|
|
29
|
+
referenceName: 'EntityB',
|
|
30
30
|
join: true,
|
|
31
31
|
biDirectional: true,
|
|
32
32
|
joinTableName: undefined,
|
|
33
33
|
joinColumnName: undefined,
|
|
34
34
|
inverseJoinColumnName: undefined,
|
|
35
|
-
inverseReferenceName: '
|
|
35
|
+
inverseReferenceName: 'EntityA', // PascalCase entity name
|
|
36
36
|
},
|
|
37
37
|
],
|
|
38
38
|
EntityB: [
|
|
39
39
|
{
|
|
40
40
|
name: 'EntityA',
|
|
41
41
|
type: 'ManyToMany',
|
|
42
|
-
referenceName: '
|
|
42
|
+
referenceName: 'EntityA',
|
|
43
43
|
join: false,
|
|
44
44
|
biDirectional: true,
|
|
45
45
|
joinTableName: undefined,
|
|
46
46
|
joinColumnName: undefined,
|
|
47
47
|
inverseJoinColumnName: undefined,
|
|
48
|
-
inverseReferenceName: '
|
|
48
|
+
inverseReferenceName: 'EntityB', // PascalCase entity name
|
|
49
49
|
},
|
|
50
50
|
],
|
|
51
51
|
};
|
|
@@ -75,20 +75,20 @@ const parse_1 = require("./parse");
|
|
|
75
75
|
joinTableName: undefined,
|
|
76
76
|
joinColumnName: undefined,
|
|
77
77
|
inverseJoinColumnName: undefined,
|
|
78
|
-
inverseReferenceName: '
|
|
78
|
+
inverseReferenceName: 'User', // PascalCase entity name
|
|
79
79
|
},
|
|
80
80
|
],
|
|
81
81
|
Group: [
|
|
82
82
|
{
|
|
83
83
|
name: 'User',
|
|
84
84
|
type: 'ManyToMany',
|
|
85
|
-
referenceName: '
|
|
85
|
+
referenceName: 'User',
|
|
86
86
|
join: false,
|
|
87
87
|
biDirectional: true,
|
|
88
88
|
joinTableName: undefined,
|
|
89
89
|
joinColumnName: undefined,
|
|
90
90
|
inverseJoinColumnName: undefined,
|
|
91
|
-
inverseReferenceName: 'userGroups', //
|
|
91
|
+
inverseReferenceName: 'userGroups', // Preserved camelCase from to_name
|
|
92
92
|
},
|
|
93
93
|
],
|
|
94
94
|
};
|
|
@@ -109,7 +109,7 @@ const parse_1 = require("./parse");
|
|
|
109
109
|
{
|
|
110
110
|
name: 'Tag',
|
|
111
111
|
type: 'ManyToMany',
|
|
112
|
-
referenceName: '
|
|
112
|
+
referenceName: 'Tag',
|
|
113
113
|
join: true,
|
|
114
114
|
biDirectional: false,
|
|
115
115
|
joinTableName: undefined,
|
|
@@ -144,26 +144,26 @@ const parse_1 = require("./parse");
|
|
|
144
144
|
{
|
|
145
145
|
name: 'Course',
|
|
146
146
|
type: 'ManyToMany',
|
|
147
|
-
referenceName: '
|
|
147
|
+
referenceName: 'Course',
|
|
148
148
|
join: true,
|
|
149
149
|
biDirectional: true,
|
|
150
150
|
joinTableName: 'enrollments',
|
|
151
151
|
joinColumnName: 'student_id',
|
|
152
152
|
inverseJoinColumnName: 'course_id',
|
|
153
|
-
inverseReferenceName: '
|
|
153
|
+
inverseReferenceName: 'Student', // PascalCase entity name
|
|
154
154
|
},
|
|
155
155
|
],
|
|
156
156
|
Course: [
|
|
157
157
|
{
|
|
158
158
|
name: 'Student',
|
|
159
159
|
type: 'ManyToMany',
|
|
160
|
-
referenceName: '
|
|
160
|
+
referenceName: 'Student',
|
|
161
161
|
join: false,
|
|
162
162
|
biDirectional: true,
|
|
163
163
|
joinTableName: undefined,
|
|
164
164
|
joinColumnName: undefined,
|
|
165
165
|
inverseJoinColumnName: undefined,
|
|
166
|
-
inverseReferenceName: '
|
|
166
|
+
inverseReferenceName: 'Course', // PascalCase entity name
|
|
167
167
|
},
|
|
168
168
|
],
|
|
169
169
|
};
|
|
@@ -202,17 +202,28 @@ const parse_1 = require("./parse");
|
|
|
202
202
|
// Check User relationships
|
|
203
203
|
(0, globals_1.expect)(result.User).toHaveLength(3);
|
|
204
204
|
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Profile', type: 'OneToOne', referenceName: 'userProfile', biDirectional: true, join: false })); // Expect preserved camelCase
|
|
205
|
-
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Post', type: 'OneToMany', referenceName: '
|
|
206
|
-
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Role', type: 'ManyToMany', referenceName: '
|
|
205
|
+
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Post', type: 'OneToMany', referenceName: 'Post', biDirectional: true })); // PascalCase entity name
|
|
206
|
+
(0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Role', type: 'ManyToMany', referenceName: 'Role', biDirectional: true, join: false })); // PascalCase entity name
|
|
207
207
|
// Check Profile relationship
|
|
208
208
|
(0, globals_1.expect)(result.Profile).toHaveLength(1);
|
|
209
|
-
(0, globals_1.expect)(result.Profile).toContainEqual(globals_1.expect.objectContaining({
|
|
209
|
+
(0, globals_1.expect)(result.Profile).toContainEqual(globals_1.expect.objectContaining({
|
|
210
|
+
name: 'User',
|
|
211
|
+
type: 'OneToOne',
|
|
212
|
+
referenceName: 'User',
|
|
213
|
+
biDirectional: true,
|
|
214
|
+
join: true
|
|
215
|
+
}));
|
|
210
216
|
// Check Post relationship
|
|
211
217
|
(0, globals_1.expect)(result.Post).toHaveLength(1);
|
|
212
|
-
(0, globals_1.expect)(result.Post).toContainEqual(globals_1.expect.objectContaining({
|
|
218
|
+
(0, globals_1.expect)(result.Post).toContainEqual(globals_1.expect.objectContaining({
|
|
219
|
+
name: 'User',
|
|
220
|
+
type: 'ManyToOne',
|
|
221
|
+
referenceName: 'User',
|
|
222
|
+
biDirectional: true
|
|
223
|
+
}));
|
|
213
224
|
// Check Role relationship
|
|
214
225
|
(0, globals_1.expect)(result.Role).toHaveLength(1);
|
|
215
|
-
(0, globals_1.expect)(result.Role).toContainEqual(globals_1.expect.objectContaining({ name: 'User', type: 'ManyToMany', referenceName: '
|
|
226
|
+
(0, globals_1.expect)(result.Role).toContainEqual(globals_1.expect.objectContaining({ name: 'User', type: 'ManyToMany', referenceName: 'User', biDirectional: true, join: true })); // PascalCase entity name
|
|
216
227
|
});
|
|
217
228
|
});
|
|
218
229
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const relationships_1 = require("./lib/utils/relationships");
|
|
4
|
+
// Test case based on the diff showing camelCase issues
|
|
5
|
+
const relationshipMap = {
|
|
6
|
+
User: [
|
|
7
|
+
{
|
|
8
|
+
type: "ManyToOne",
|
|
9
|
+
name: "Workspace",
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
Workspace: [
|
|
13
|
+
{
|
|
14
|
+
type: "OneToMany",
|
|
15
|
+
name: "User",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "OneToMany",
|
|
19
|
+
name: "WorkspaceUser",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
WorkspaceUser: [
|
|
23
|
+
{
|
|
24
|
+
type: "ManyToOne",
|
|
25
|
+
name: "Workspace",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "ManyToOne",
|
|
29
|
+
name: "User",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "OneToMany",
|
|
33
|
+
name: "WorkspaceServiceUser",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
WorkspaceServiceUser: [
|
|
37
|
+
{
|
|
38
|
+
type: "ManyToOne",
|
|
39
|
+
name: "WorkspaceUser",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: "ManyToOne",
|
|
43
|
+
name: "WorkspaceService",
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
WorkspaceService: [
|
|
47
|
+
{
|
|
48
|
+
type: "OneToMany",
|
|
49
|
+
name: "WorkspaceServiceUser",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "OneToMany",
|
|
53
|
+
name: "WorkspaceServiceApiKey",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
WorkspaceServiceApiKey: [
|
|
57
|
+
{
|
|
58
|
+
type: "ManyToOne",
|
|
59
|
+
name: "WorkspaceService",
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
// Run the test for User entity
|
|
64
|
+
console.log("Nested relationships for User:");
|
|
65
|
+
const userNestedRelationships = (0, relationships_1.getNestedRelationships)("User", relationshipMap);
|
|
66
|
+
userNestedRelationships.forEach(rel => console.log(` - ${rel}`));
|
|
67
|
+
// Run test for CustomerCard entity (similar to what's shown in the diff)
|
|
68
|
+
// Create a CustomerCard example with the pattern seen in the diff
|
|
69
|
+
const customerCardRelationshipMap = {
|
|
70
|
+
CustomerCard: [
|
|
71
|
+
{
|
|
72
|
+
type: "ManyToOne",
|
|
73
|
+
name: "User",
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
User: [
|
|
77
|
+
{
|
|
78
|
+
type: "OneToMany",
|
|
79
|
+
name: "Workspace",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
Workspace: [
|
|
83
|
+
{
|
|
84
|
+
type: "OneToMany",
|
|
85
|
+
name: "WorkspaceUser",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "OneToMany",
|
|
89
|
+
name: "WorkspaceService",
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
WorkspaceService: [
|
|
93
|
+
{
|
|
94
|
+
type: "OneToMany",
|
|
95
|
+
name: "WorkspaceServiceApiKey",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
WorkspaceServiceApiKey: [
|
|
99
|
+
{
|
|
100
|
+
type: "ManyToOne",
|
|
101
|
+
name: "WorkspaceService",
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
console.log("\nNested relationships for CustomerCard:");
|
|
106
|
+
const customerCardNestedRelationships = (0, relationships_1.getNestedRelationships)("CustomerCard", customerCardRelationshipMap);
|
|
107
|
+
customerCardNestedRelationships.forEach(rel => console.log(` - ${rel}`));
|
|
108
|
+
// Expected to see properly camelCased names like:
|
|
109
|
+
// - workspace.workspaceUsers
|
|
110
|
+
// - workspace.workspaceUsers.workspaceServiceUsers
|
|
111
|
+
// - workspace.workspaceUsers.workspaceServiceUsers.workspaceService
|
|
112
|
+
// - workspace.workspaceUsers.workspaceServiceUsers.workspaceService.workspaceServiceApiKeys
|
|
@@ -17,7 +17,7 @@ const globals_1 = require("@jest/globals");
|
|
|
17
17
|
nullable: false
|
|
18
18
|
},
|
|
19
19
|
assertions: [
|
|
20
|
-
{ type: 'contains', text: "@Column('
|
|
20
|
+
{ type: 'contains', text: "@Column('jsonb'" },
|
|
21
21
|
{ type: 'contains', text: 'nullable: false' },
|
|
22
22
|
{ type: 'contains', text: 'jsonData: JSON;' }
|
|
23
23
|
]
|
|
@@ -30,7 +30,7 @@ const globals_1 = require("@jest/globals");
|
|
|
30
30
|
nullable: true
|
|
31
31
|
},
|
|
32
32
|
assertions: [
|
|
33
|
-
{ type: 'contains', text: "@Column('
|
|
33
|
+
{ type: 'contains', text: "@Column('jsonb'" },
|
|
34
34
|
{ type: 'contains', text: 'nullable: true' },
|
|
35
35
|
{ type: 'contains', text: 'optionalJsonData: JSON;' }
|
|
36
36
|
]
|
|
@@ -60,10 +60,11 @@ const parse_1 = require("../../lib/utils/relationships/parse");
|
|
|
60
60
|
// Run the function
|
|
61
61
|
const result = (0, parse_1.getRelationshipForTemplate)(entityName, mockRelationships);
|
|
62
62
|
// Assertions
|
|
63
|
-
//
|
|
64
|
-
(0, globals_1.expect)(result.length).toBe(
|
|
65
|
-
|
|
66
|
-
(0, globals_1.expect)(
|
|
63
|
+
// New behavior: Both relationships with different referenceName should be kept
|
|
64
|
+
(0, globals_1.expect)(result.length).toBe(2);
|
|
65
|
+
const referenceNames = result.map(r => r.relationshipName || r.referenceName);
|
|
66
|
+
(0, globals_1.expect)(referenceNames).toContain("workspaces");
|
|
67
|
+
(0, globals_1.expect)(referenceNames).toContain("ownedWorkspaces");
|
|
67
68
|
});
|
|
68
69
|
(0, globals_1.test)("should prioritize owner side (join=true) when deduplicating", () => {
|
|
69
70
|
// Scenario based on real issue: two ManyToMany relationships to same entity
|
package/oclif.manifest.json
CHANGED