@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.
@@ -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
- await (0, lib_1.createDto)(filePath, entity, { apiType });
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, options) => {
21
+ const createDto = async (apiBaseDir, entity, relationships, // Relationship[]
22
+ options) => {
21
23
  var _a;
22
- const { name: entityName, fields = [], associations } = entity;
23
- const dtoDir = path.join(apiBaseDir, "dto");
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, associations || []);
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.camelCase)(entityName, true)}Dto`;
35
- const updateDtoName = `Update${(0, casing_1.camelCase)(entityName, true)}Dto`;
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,
@@ -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, pluralize_1.default)((0, casing_1.camelCase)(name)),
39
+ pluralizedName: (0, casing_1.camelCase)((0, pluralize_1.default)(name)),
40
40
  columns,
41
41
  associations: [...new Set(relationships)],
42
42
  entitiesToImport,
@@ -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, pluralize_1.default)((0, casing_1.camelCase)(name)),
30
+ pluralizedName: (0, casing_1.camelCase)((0, pluralize_1.default)(name)),
31
31
  columns,
32
32
  associations: relationships,
33
33
  entitiesToImport,
@@ -1,5 +1,5 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column('json', { nullable: <%= Boolean(field.nullable) %> })
3
+ @Column('jsonb', { nullable: <%= Boolean(field.nullable) %> })
4
4
 
5
5
  <%= field.name %>: JSON;
@@ -5,7 +5,7 @@ import { ApiProperty } from '@nestjs/swagger';
5
5
  import * as enums from '../../enums';
6
6
  <% } %>
7
7
 
8
- export class <%= it.name %>Create {
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.name %> {
23
+ export class <%= it.entityName %> {
24
24
  <% if (it.addDefaultPKProperty) { %>
25
25
  @ApiProperty()
26
26
  id: number;
@@ -12,5 +12,5 @@ import {<%= it.ctrlName %>} from "./<%= it.entityName %>.controller";
12
12
  exports: [<%= it.svcName %>],
13
13
  controllers: [<%= it.ctrlName %>],
14
14
  })
15
- export class <%= it.modName %> {}
15
+ export class <%= it.moduleName %> {}
16
16
 
@@ -1,2 +1,3 @@
1
- export declare const camelCase: (str: string, firstUpper?: boolean) => string;
1
+ export declare function camelCase(str: string): string;
2
2
  export declare const snakeCase: (str: string) => string;
3
+ export declare function pascalCase(str: string): string;
@@ -1,16 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.snakeCase = exports.camelCase = void 0;
4
- const camelCase = (str, firstUpper) => {
5
- const firstLetter = firstUpper ? str[0].toUpperCase() : str[0].toLowerCase();
6
- const restOfLetters = str
7
- .slice(1)
8
- .replace(/_[A-Za-z]/g, (underLetter) => underLetter[1].toUpperCase());
9
- return `${firstLetter}${restOfLetters}`;
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;
@@ -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 getJsTypeFromFieldType = (type) => {
6
- switch (type) {
7
- case "array":
8
- return "string[]";
9
- case "boolean":
10
- return "boolean";
11
- case "integer":
12
- return "number";
13
- case "float":
14
- return "number";
15
- case "text":
16
- case "enum":
17
- default:
18
- return "string";
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.camelCase)(entityName, true)}${(0, casing_1.camelCase)(fieldName, true)}Enum`;
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, pluralize_1.default)((0, casing_1.camelCase)(relationship.to));
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 || (relationship.to.toLowerCase() + 's');
115
- const toSideRefName = (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.to_name) || (relationship.from.toLowerCase() + 's');
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.toLowerCase(),
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.toLowerCase(),
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.toLowerCase(),
203
- inverseReferenceName: relationship.to_name || relationship.to.toLowerCase(),
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 || (relationship.to.toLowerCase() + 's');
221
- const toSideRefName = relationship.from.toLowerCase(); // Default singular name for the inverse side
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.toLowerCase(), nullable: relationship.nullable || false, index: relationship.index || false }],
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.toLowerCase(),
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
- // Second pass: Use name-only as a fallback for edge cases
370
- // This helps with ManyToMany cases where both sides exist with different join flags
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)(relationshipName),
399
- pluralizedName: (0, pluralize_1.default)((0, casing_1.camelCase)(relationship.name)),
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, pluralize_1.default)((0, casing_1.camelCase)(referenceName));
426
- const relationshipPath = prefix
427
- ? `${prefix}.${formattedName}`
428
- : formattedName;
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, pluralize_1.default)((0, casing_1.camelCase)(referenceName)));
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: 'entitybs',
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: 'entityas', // Simplified default
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: 'entityas',
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: 'entitybs', // Simplified default
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: 'users', // Simplified default
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: 'users',
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', // Expect preserved camelCase
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: 'tags',
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: 'courses',
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: 'students', // Simplified default
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: 'students',
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: 'courses', // Simplified default
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: 'posts', biDirectional: true })); // Simplified default
206
- (0, globals_1.expect)(result.User).toContainEqual(globals_1.expect.objectContaining({ name: 'Role', type: 'ManyToMany', referenceName: 'roles', biDirectional: true, join: false })); // Simplified default
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({ name: 'User', type: 'OneToOne', referenceName: 'user', biDirectional: true, join: true })); // Simplified default singular
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({ name: 'User', type: 'ManyToOne', referenceName: 'user', biDirectional: true })); // Simplified default singular
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: 'users', biDirectional: true, join: true })); // Simplified default
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('json'" },
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('json'" },
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
- // Current behavior: Only one relationship is kept during name-based deduplication (second phase)
64
- (0, globals_1.expect)(result.length).toBe(1);
65
- // The first one is kept in the Set processing
66
- (0, globals_1.expect)(result[0].relationshipName).toBe("workspaces");
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.4",
2
+ "version": "0.1.6",
3
3
  "commands": {
4
4
  "server:new": {
5
5
  "id": "server:new",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Apso CLI",
5
5
  "author": "Apso by Mavric - @mavric",
6
6
  "bin": {