@apso/cli 0.3.1 → 0.4.0

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.
@@ -7,7 +7,13 @@ export default class Scaffold extends BaseCommand {
7
7
  help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
8
8
  };
9
9
  static args: {};
10
- scaffoldServer(dir: string, entity: Entity, relationshipMap: RelationshipMap, apiType: string): Promise<void>;
10
+ scaffoldServer(options: {
11
+ dir: string;
12
+ entity: Entity;
13
+ relationshipMap: RelationshipMap;
14
+ apiType: string;
15
+ allEntities: Entity[];
16
+ }): Promise<void>;
11
17
  setupRestFiles(dir: string, entity: Entity, relationshipMap: RelationshipMap, apiType: string): Promise<void>;
12
18
  setupGraphqlFiles(dir: string, entity: Entity, relationshipMap: RelationshipMap): Promise<void>;
13
19
  run(): Promise<void>;
@@ -13,13 +13,20 @@ Eta.configure({
13
13
  cache: false
14
14
  });
15
15
  class Scaffold extends base_command_1.default {
16
- async scaffoldServer(dir, entity, relationshipMap, apiType) {
16
+ async scaffoldServer(options) {
17
+ const { dir, entity, relationshipMap, apiType, allEntities } = options;
17
18
  const entityBuildStart = perf_hooks_1.performance.now();
18
19
  this.log(`Building... ${entity.name}`);
19
20
  const entityName = entity.name;
20
21
  const filePath = path.join(dir, entityName);
21
22
  const entityRelationships = relationshipMap[entity.name] || [];
22
- await (0, lib_1.createEntity)(filePath, entity, entityRelationships, apiType);
23
+ await (0, lib_1.createEntity)({
24
+ apiBaseDir: filePath,
25
+ entity,
26
+ relationships: entityRelationships,
27
+ apiType,
28
+ allEntities
29
+ });
23
30
  switch (apiType) {
24
31
  case apsorc_parser_1.ApiType.Graphql:
25
32
  await this.setupGraphqlFiles(dir, entity, relationshipMap);
@@ -71,7 +78,13 @@ class Scaffold extends base_command_1.default {
71
78
  await (0, lib_1.createEnums)(autogenPath, entities, lowerCaseApiType);
72
79
  await Promise.all(entities.map(async (entity) => {
73
80
  const scaffoldModel = this.scaffoldServer.bind(this);
74
- await scaffoldModel(autogenPath, entity, relationshipMap, lowerCaseApiType);
81
+ await scaffoldModel({
82
+ dir: autogenPath,
83
+ entity,
84
+ relationshipMap,
85
+ apiType: lowerCaseApiType,
86
+ allEntities: entities
87
+ });
75
88
  if (process.env.DEBUG) {
76
89
  const used = process.memoryUsage();
77
90
  console.log(`[mem] heapUsed after ${entity.name}: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`);
@@ -9,10 +9,11 @@ class BaseCommand extends core_1.Command {
9
9
  return new Promise((resolve, reject) => {
10
10
  const isWindows = os_1.default.platform() === "win32";
11
11
  const command = isWindows ? "npm.cmd" : "npm";
12
+ const stdio = silent ? "ignore" : "inherit";
12
13
  const cmdStr = `${command} ${args.join(" ")}`;
13
14
  this.log(`Running: ${cmdStr}`);
14
15
  const child = (0, child_process_1.spawn)(command, args, {
15
- stdio: "inherit",
16
+ stdio,
16
17
  shell: isWindows,
17
18
  });
18
19
  child.on("close", (code) => {
@@ -2,10 +2,18 @@ import { Entity, Relationship } from "./types";
2
2
  /**
3
3
  * Generates a TypeORM entity file based on the entity definition and relationships.
4
4
  *
5
- * @param apiBaseDir The base directory for the generated API module (e.g., 'src/autogen/users').
6
- * @param entity The entity definition object from the parsed .apsorc.
7
- * @param relationshipsNew An array of processed Relationship objects for this entity.
8
- * @param apiType The type of API being generated (e.g., "rest", "graphql").
5
+ * @param options Configuration options for entity generation
6
+ * @param options.apiBaseDir The base directory for the generated API module (e.g., 'src/autogen/users').
7
+ * @param options.entity The entity definition object from the parsed .apsorc.
8
+ * @param options.relationships An array of processed Relationship objects for this entity.
9
+ * @param options.apiType The type of API being generated (e.g., "rest", "graphql").
10
+ * @param options.allEntities Optional array of all entities, used to determine foreign key types.
9
11
  * @returns {Promise<void>} A promise that resolves when the entity file is created.
10
12
  */
11
- export declare const createEntity: (apiBaseDir: string, entity: Entity, relationshipsNew: Relationship[], apiType: string) => Promise<void>;
13
+ export declare const createEntity: (options: {
14
+ apiBaseDir: string;
15
+ entity: Entity;
16
+ relationships: Relationship[];
17
+ apiType: string;
18
+ allEntities?: Entity[];
19
+ }) => Promise<void>;
@@ -12,13 +12,16 @@ const Eta = tslib_1.__importStar(require("eta"));
12
12
  /**
13
13
  * Generates a TypeORM entity file based on the entity definition and relationships.
14
14
  *
15
- * @param apiBaseDir The base directory for the generated API module (e.g., 'src/autogen/users').
16
- * @param entity The entity definition object from the parsed .apsorc.
17
- * @param relationshipsNew An array of processed Relationship objects for this entity.
18
- * @param apiType The type of API being generated (e.g., "rest", "graphql").
15
+ * @param options Configuration options for entity generation
16
+ * @param options.apiBaseDir The base directory for the generated API module (e.g., 'src/autogen/users').
17
+ * @param options.entity The entity definition object from the parsed .apsorc.
18
+ * @param options.relationships An array of processed Relationship objects for this entity.
19
+ * @param options.apiType The type of API being generated (e.g., "rest", "graphql").
20
+ * @param options.allEntities Optional array of all entities, used to determine foreign key types.
19
21
  * @returns {Promise<void>} A promise that resolves when the entity file is created.
20
22
  */
21
- const createEntity = async (apiBaseDir, entity, relationshipsNew, apiType) => {
23
+ const createEntity = async (options) => {
24
+ const { apiBaseDir, entity, relationships: relationshipsNew, apiType, allEntities } = options;
22
25
  const { name, fields = [], indexes, uniques } = entity;
23
26
  const File = path.join(apiBaseDir, `${name}.entity.ts`);
24
27
  const columns = (0, field_1.getFieldForTemplate)(fields, name);
@@ -26,13 +29,15 @@ const createEntity = async (apiBaseDir, entity, relationshipsNew, apiType) => {
26
29
  .length === 0;
27
30
  const createdAt = entity.created_at;
28
31
  const updatedAt = entity.updated_at;
29
- const relationships = (0, relationships_1.getRelationshipForTemplate)(name, relationshipsNew);
32
+ const primaryKeyType = entity.primaryKeyType || 'serial'; // Default to 'serial' if not specified
33
+ const relationships = (0, relationships_1.getRelationshipForTemplate)(name, relationshipsNew, allEntities);
30
34
  const entitiesToImport = (0, relationships_1.getRelationshipsForImport)(name, relationships);
31
35
  const data = {
32
36
  name,
33
37
  indexes: indexes || [],
34
38
  uniques: uniques || [],
35
39
  createPrimaryKey,
40
+ primaryKeyType,
36
41
  snakeCasedName: (0, casing_1.snakeCase)(name),
37
42
  createdAt,
38
43
  updatedAt,
@@ -52,8 +52,14 @@ const { CREATE, UPDATE } = CrudValidationGroups;
52
52
  export class <%= it.name %> {
53
53
  <% if (it.createPrimaryKey) { %>
54
54
  // Fields
55
+ <% if (it.primaryKeyType === 'uuid') { %>
56
+ @Column({ type: 'uuid' })
57
+ @PrimaryColumn()
58
+ id: string;
59
+ <% } else { %>
55
60
  @PrimaryGeneratedColumn()
56
61
  id!: number;
62
+ <% } %>
57
63
  <% } %>
58
64
 
59
65
  <% if (it.createdAt) { %>
@@ -95,8 +101,13 @@ export class <%= it.name %> {
95
101
  @JoinColumn({ name: '<%= assoc.camelCasedId %>' })
96
102
  <%= assoc.relationshipName %>: <%= assoc.name %>;
97
103
 
104
+ <% if (assoc.referencedEntityPrimaryKeyType === 'uuid') { %>
105
+ @Column({ type: "uuid", <% if (assoc.nullable) { %> nullable: true <% } %>})
106
+ <%= assoc.camelCasedId %>: string;
107
+ <% } else { %>
98
108
  @Column({ type: "integer", <% if (assoc.nullable) { %> nullable: true <% } %>})
99
109
  <%= assoc.camelCasedId %>: number;
110
+ <% } %>
100
111
 
101
112
  <% } else if (assoc.type === 'ManyToMany') { %>
102
113
 
@@ -6,6 +6,7 @@ export interface Entity {
6
6
  name: string;
7
7
  created_at?: boolean;
8
8
  updated_at?: boolean;
9
+ primaryKeyType?: 'serial' | 'uuid';
9
10
  fields?: Field[];
10
11
  indexes?: Index[];
11
12
  uniques?: Unique[];
@@ -36,6 +36,7 @@ export interface RelationshipForTemplate extends Relationship {
36
36
  joinTable: boolean;
37
37
  biDirectional: boolean;
38
38
  inversePropertyName?: string;
39
+ referencedEntityPrimaryKeyType?: 'serial' | 'uuid';
39
40
  }
40
41
  export type RelationshipMap = {
41
42
  [key: string]: Relationship[];
@@ -49,9 +49,10 @@ export declare const getRelationshipIdField: (relationship: Relationship) => str
49
49
  * Calculates additional helper properties like pluralized names and inverse property names.
50
50
  * @param entityName The name of the entity for which to generate template data.
51
51
  * @param relationships An array of Relationship objects for the given entity.
52
+ * @param entities Optional array of all entities, used to determine referenced entity's primary key type.
52
53
  * @returns An array of RelationshipForTemplate objects with extra properties for the template.
53
54
  */
54
- export declare const getRelationshipForTemplate: (entityName: string, relationships: Relationship[]) => RelationshipForTemplate[];
55
+ export declare const getRelationshipForTemplate: (entityName: string, relationships: Relationship[], entities?: any[]) => RelationshipForTemplate[];
55
56
  /**
56
57
  * Generates all valid nested join paths for an entity, avoiding cycles and redundant paths.
57
58
  *
@@ -331,12 +331,20 @@ const calculateInversePropertyName = (relationship, entityName, relationshipName
331
331
  * Calculates additional helper properties like pluralized names and inverse property names.
332
332
  * @param entityName The name of the entity for which to generate template data.
333
333
  * @param relationships An array of Relationship objects for the given entity.
334
+ * @param entities Optional array of all entities, used to determine referenced entity's primary key type.
334
335
  * @returns An array of RelationshipForTemplate objects with extra properties for the template.
335
336
  */
336
- const getRelationshipForTemplate = (entityName, relationships) => {
337
+ const getRelationshipForTemplate = (entityName, relationships, entities) => {
337
338
  if (!relationships) {
338
339
  return [];
339
340
  }
341
+ // Create entity map for looking up primary key types
342
+ const entityMap = new Map();
343
+ if (entities) {
344
+ entities.forEach(entity => {
345
+ entityMap.set(entity.name, entity);
346
+ });
347
+ }
340
348
  // First pass: Group by name and referenceName (more specific)
341
349
  const processedRefs = new Map();
342
350
  relationships.forEach((relationship) => {
@@ -369,6 +377,9 @@ const getRelationshipForTemplate = (entityName, relationships) => {
369
377
  (relationship.type === 'ManyToMany' || relationship.type === 'OneToMany' || relationship.type === 'OneToOne')) {
370
378
  console.info(`[APSO INFO] Using default property name '${inverseSidePropertyName}' for ${relationship.name}->${entityName}. To customize, add 'to_name' in your relationship definition.`);
371
379
  }
380
+ // Look up the referenced entity's primary key type
381
+ const referencedEntity = entityMap.get(relationship.name);
382
+ const referencedEntityPrimaryKeyType = (referencedEntity === null || referencedEntity === void 0 ? void 0 : referencedEntity.primaryKeyType) || 'serial'; // Default to 'serial' if not specified
372
383
  return {
373
384
  ...relationship,
374
385
  relationshipName,
@@ -381,6 +392,7 @@ const getRelationshipForTemplate = (entityName, relationships) => {
381
392
  biDirectional: relationship.biDirectional || false,
382
393
  referenceName: thisReferenceName || undefined,
383
394
  inverseSidePropertyName,
395
+ referencedEntityPrimaryKeyType,
384
396
  };
385
397
  });
386
398
  };
@@ -227,3 +227,149 @@ const parse_1 = require("./parse");
227
227
  });
228
228
  });
229
229
  });
230
+ (0, globals_1.describe)('getRelationshipForTemplate', () => {
231
+ // Mock console.warn to prevent test output clutter
232
+ (0, globals_1.beforeEach)(() => {
233
+ globals_1.jest.spyOn(console, 'warn').mockImplementation(() => { });
234
+ });
235
+ (0, globals_1.afterEach)(() => {
236
+ globals_1.jest.restoreAllMocks();
237
+ });
238
+ (0, globals_1.describe)('UUID Primary Key Support', () => {
239
+ (0, globals_1.it)('should populate referencedEntityPrimaryKeyType as uuid when referenced entity has uuid primary key', () => {
240
+ const entities = [
241
+ { name: 'User', primaryKeyType: 'uuid' },
242
+ { name: 'Account', primaryKeyType: 'serial' },
243
+ ];
244
+ const relationships = [
245
+ {
246
+ name: 'User',
247
+ type: 'ManyToOne',
248
+ referenceName: null,
249
+ nullable: false,
250
+ index: false,
251
+ },
252
+ ];
253
+ const result = (0, parse_1.getRelationshipForTemplate)('Account', relationships, entities);
254
+ (0, globals_1.expect)(result).toHaveLength(1);
255
+ (0, globals_1.expect)(result[0]).toMatchObject({
256
+ name: 'User',
257
+ type: 'ManyToOne',
258
+ referencedEntityPrimaryKeyType: 'uuid',
259
+ });
260
+ });
261
+ (0, globals_1.it)('should populate referencedEntityPrimaryKeyType as serial when referenced entity has serial primary key', () => {
262
+ const entities = [
263
+ { name: 'User', primaryKeyType: 'serial' },
264
+ { name: 'Post', primaryKeyType: 'serial' },
265
+ ];
266
+ const relationships = [
267
+ {
268
+ name: 'User',
269
+ type: 'ManyToOne',
270
+ referenceName: null,
271
+ nullable: false,
272
+ index: false,
273
+ },
274
+ ];
275
+ const result = (0, parse_1.getRelationshipForTemplate)('Post', relationships, entities);
276
+ (0, globals_1.expect)(result).toHaveLength(1);
277
+ (0, globals_1.expect)(result[0]).toMatchObject({
278
+ name: 'User',
279
+ type: 'ManyToOne',
280
+ referencedEntityPrimaryKeyType: 'serial',
281
+ });
282
+ });
283
+ (0, globals_1.it)('should default referencedEntityPrimaryKeyType to serial when entity does not specify primaryKeyType', () => {
284
+ const entities = [
285
+ { name: 'User' },
286
+ { name: 'Post', primaryKeyType: 'serial' },
287
+ ];
288
+ const relationships = [
289
+ {
290
+ name: 'User',
291
+ type: 'ManyToOne',
292
+ referenceName: null,
293
+ nullable: false,
294
+ index: false,
295
+ },
296
+ ];
297
+ const result = (0, parse_1.getRelationshipForTemplate)('Post', relationships, entities);
298
+ (0, globals_1.expect)(result).toHaveLength(1);
299
+ (0, globals_1.expect)(result[0]).toMatchObject({
300
+ name: 'User',
301
+ type: 'ManyToOne',
302
+ referencedEntityPrimaryKeyType: 'serial',
303
+ });
304
+ });
305
+ (0, globals_1.it)('should default referencedEntityPrimaryKeyType to serial when entities array is not provided', () => {
306
+ const relationships = [
307
+ {
308
+ name: 'User',
309
+ type: 'ManyToOne',
310
+ referenceName: null,
311
+ nullable: false,
312
+ index: false,
313
+ },
314
+ ];
315
+ const result = (0, parse_1.getRelationshipForTemplate)('Post', relationships);
316
+ (0, globals_1.expect)(result).toHaveLength(1);
317
+ (0, globals_1.expect)(result[0]).toMatchObject({
318
+ name: 'User',
319
+ type: 'ManyToOne',
320
+ referencedEntityPrimaryKeyType: 'serial',
321
+ });
322
+ });
323
+ (0, globals_1.it)('should handle multiple relationships with mixed primary key types', () => {
324
+ const entities = [
325
+ { name: 'User', primaryKeyType: 'uuid' },
326
+ { name: 'Organization', primaryKeyType: 'serial' },
327
+ { name: 'Team', primaryKeyType: 'uuid' },
328
+ { name: 'Post', primaryKeyType: 'serial' },
329
+ ];
330
+ const relationships = [
331
+ {
332
+ name: 'User',
333
+ type: 'ManyToOne',
334
+ referenceName: null,
335
+ nullable: false,
336
+ index: true,
337
+ },
338
+ {
339
+ name: 'Organization',
340
+ type: 'ManyToOne',
341
+ referenceName: null,
342
+ nullable: false,
343
+ index: false,
344
+ },
345
+ {
346
+ name: 'Team',
347
+ type: 'ManyToOne',
348
+ referenceName: null,
349
+ nullable: true,
350
+ index: false,
351
+ },
352
+ ];
353
+ const result = (0, parse_1.getRelationshipForTemplate)('Post', relationships, entities);
354
+ (0, globals_1.expect)(result).toHaveLength(3);
355
+ const userRel = result.find(r => r.name === 'User');
356
+ (0, globals_1.expect)(userRel).toMatchObject({
357
+ name: 'User',
358
+ type: 'ManyToOne',
359
+ referencedEntityPrimaryKeyType: 'uuid',
360
+ });
361
+ const orgRel = result.find(r => r.name === 'Organization');
362
+ (0, globals_1.expect)(orgRel).toMatchObject({
363
+ name: 'Organization',
364
+ type: 'ManyToOne',
365
+ referencedEntityPrimaryKeyType: 'serial',
366
+ });
367
+ const teamRel = result.find(r => r.name === 'Team');
368
+ (0, globals_1.expect)(teamRel).toMatchObject({
369
+ name: 'Team',
370
+ type: 'ManyToOne',
371
+ referencedEntityPrimaryKeyType: 'uuid',
372
+ });
373
+ });
374
+ });
375
+ });
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const Eta = tslib_1.__importStar(require("eta"));
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const globals_1 = require("@jest/globals");
7
+ (0, globals_1.describe)('UUID Entity Template Tests', () => {
8
+ (0, globals_1.beforeAll)(() => {
9
+ Eta.configure({
10
+ views: path.join(__dirname, '../../../lib/templates'),
11
+ cache: false
12
+ });
13
+ });
14
+ (0, globals_1.describe)('UUID Primary Key', () => {
15
+ (0, globals_1.it)('should render entity with UUID primary key', async () => {
16
+ const data = {
17
+ name: 'User',
18
+ snakeCasedName: 'user',
19
+ createPrimaryKey: true,
20
+ primaryKeyType: 'uuid',
21
+ createdAt: true,
22
+ updatedAt: true,
23
+ columns: [],
24
+ associations: [],
25
+ indexes: [],
26
+ uniques: [],
27
+ entitiesToImport: [],
28
+ apiType: 'rest',
29
+ importEnums: false
30
+ };
31
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
32
+ // Should use @Column with uuid type
33
+ (0, globals_1.expect)(rendered).toContain("@Column({ type: 'uuid' })");
34
+ // Should use @PrimaryColumn instead of @PrimaryGeneratedColumn
35
+ (0, globals_1.expect)(rendered).toContain('@PrimaryColumn()');
36
+ (0, globals_1.expect)(rendered).not.toContain('@PrimaryGeneratedColumn()');
37
+ // Should declare id as string
38
+ (0, globals_1.expect)(rendered).toContain('id: string;');
39
+ (0, globals_1.expect)(rendered).not.toContain('id!: number;');
40
+ });
41
+ (0, globals_1.it)('should render entity with serial (auto-increment) primary key', async () => {
42
+ const data = {
43
+ name: 'Post',
44
+ snakeCasedName: 'post',
45
+ createPrimaryKey: true,
46
+ primaryKeyType: 'serial',
47
+ createdAt: true,
48
+ updatedAt: true,
49
+ columns: [],
50
+ associations: [],
51
+ indexes: [],
52
+ uniques: [],
53
+ entitiesToImport: [],
54
+ apiType: 'rest',
55
+ importEnums: false
56
+ };
57
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
58
+ // Should use @PrimaryGeneratedColumn
59
+ (0, globals_1.expect)(rendered).toContain('@PrimaryGeneratedColumn()');
60
+ (0, globals_1.expect)(rendered).not.toContain("@Column({ type: 'uuid' })");
61
+ (0, globals_1.expect)(rendered).not.toContain('@PrimaryColumn()');
62
+ // Should declare id as number
63
+ (0, globals_1.expect)(rendered).toContain('id!: number;');
64
+ (0, globals_1.expect)(rendered).not.toContain('id: string;');
65
+ });
66
+ (0, globals_1.it)('should default to serial primary key when primaryKeyType is not specified', async () => {
67
+ const data = {
68
+ name: 'Comment',
69
+ snakeCasedName: 'comment',
70
+ createPrimaryKey: true,
71
+ // primaryKeyType not specified
72
+ createdAt: true,
73
+ updatedAt: true,
74
+ columns: [],
75
+ associations: [],
76
+ indexes: [],
77
+ uniques: [],
78
+ entitiesToImport: [],
79
+ apiType: 'rest',
80
+ importEnums: false
81
+ };
82
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
83
+ // Should default to serial (auto-increment)
84
+ (0, globals_1.expect)(rendered).toContain('@PrimaryGeneratedColumn()');
85
+ (0, globals_1.expect)(rendered).toContain('id!: number;');
86
+ });
87
+ });
88
+ (0, globals_1.describe)('UUID Foreign Keys', () => {
89
+ (0, globals_1.it)('should render ManyToOne relationship with UUID foreign key when referenced entity has UUID primary key', async () => {
90
+ const data = {
91
+ name: 'Account',
92
+ snakeCasedName: 'account',
93
+ createPrimaryKey: true,
94
+ primaryKeyType: 'serial',
95
+ createdAt: true,
96
+ updatedAt: true,
97
+ columns: [],
98
+ associations: [
99
+ {
100
+ name: 'User',
101
+ type: 'ManyToOne',
102
+ relationshipName: 'user',
103
+ pluralizedRelationshipName: 'users',
104
+ pluralizedName: 'users',
105
+ camelCasedName: 'user',
106
+ camelCasedId: 'userId',
107
+ entityName: 'account',
108
+ joinTable: false,
109
+ biDirectional: true,
110
+ inverseSidePropertyName: 'accounts',
111
+ referencedEntityPrimaryKeyType: 'uuid',
112
+ nullable: false,
113
+ index: false
114
+ }
115
+ ],
116
+ indexes: [],
117
+ uniques: [],
118
+ entitiesToImport: ['User'],
119
+ apiType: 'rest',
120
+ importEnums: false
121
+ };
122
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
123
+ // Should have ManyToOne decorator
124
+ (0, globals_1.expect)(rendered).toContain('@ManyToOne(() => User');
125
+ // Should have JoinColumn
126
+ (0, globals_1.expect)(rendered).toContain("@JoinColumn({ name: 'userId' })");
127
+ // Should declare relationship property
128
+ (0, globals_1.expect)(rendered).toContain('user: User;');
129
+ // Should have UUID foreign key column
130
+ (0, globals_1.expect)(rendered).toContain('@Column({ type: "uuid"');
131
+ (0, globals_1.expect)(rendered).toContain('userId: string;');
132
+ // Should NOT have integer foreign key
133
+ (0, globals_1.expect)(rendered).not.toContain('userId: number;');
134
+ });
135
+ (0, globals_1.it)('should render ManyToOne relationship with integer foreign key when referenced entity has serial primary key', async () => {
136
+ const data = {
137
+ name: 'Post',
138
+ snakeCasedName: 'post',
139
+ createPrimaryKey: true,
140
+ primaryKeyType: 'serial',
141
+ createdAt: true,
142
+ updatedAt: true,
143
+ columns: [],
144
+ associations: [
145
+ {
146
+ name: 'User',
147
+ type: 'ManyToOne',
148
+ relationshipName: 'user',
149
+ pluralizedRelationshipName: 'users',
150
+ pluralizedName: 'users',
151
+ camelCasedName: 'user',
152
+ camelCasedId: 'userId',
153
+ entityName: 'post',
154
+ joinTable: false,
155
+ biDirectional: true,
156
+ inverseSidePropertyName: 'posts',
157
+ referencedEntityPrimaryKeyType: 'serial',
158
+ nullable: false,
159
+ index: true
160
+ }
161
+ ],
162
+ indexes: [],
163
+ uniques: [],
164
+ entitiesToImport: ['User'],
165
+ apiType: 'rest',
166
+ importEnums: false
167
+ };
168
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
169
+ // Should have ManyToOne decorator
170
+ (0, globals_1.expect)(rendered).toContain('@ManyToOne(() => User');
171
+ // Should have index since index: true
172
+ (0, globals_1.expect)(rendered).toContain('@Index()');
173
+ // Should have integer foreign key column
174
+ (0, globals_1.expect)(rendered).toContain('@Column({ type: "integer"');
175
+ (0, globals_1.expect)(rendered).toContain('userId: number;');
176
+ // Should NOT have UUID foreign key
177
+ (0, globals_1.expect)(rendered).not.toContain('userId: string;');
178
+ });
179
+ (0, globals_1.it)('should handle nullable UUID foreign keys', async () => {
180
+ const data = {
181
+ name: 'Profile',
182
+ snakeCasedName: 'profile',
183
+ createPrimaryKey: true,
184
+ primaryKeyType: 'uuid',
185
+ createdAt: true,
186
+ updatedAt: true,
187
+ columns: [],
188
+ associations: [
189
+ {
190
+ name: 'Organization',
191
+ type: 'ManyToOne',
192
+ relationshipName: 'organization',
193
+ pluralizedRelationshipName: 'organizations',
194
+ pluralizedName: 'organizations',
195
+ camelCasedName: 'organization',
196
+ camelCasedId: 'organizationId',
197
+ entityName: 'profile',
198
+ joinTable: false,
199
+ biDirectional: false,
200
+ referencedEntityPrimaryKeyType: 'uuid',
201
+ nullable: true,
202
+ index: false
203
+ }
204
+ ],
205
+ indexes: [],
206
+ uniques: [],
207
+ entitiesToImport: ['Organization'],
208
+ apiType: 'rest',
209
+ importEnums: false
210
+ };
211
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
212
+ // Should have nullable UUID foreign key
213
+ (0, globals_1.expect)(rendered).toContain('@Column({ type: "uuid", nullable: true');
214
+ (0, globals_1.expect)(rendered).toContain('organizationId: string;');
215
+ });
216
+ (0, globals_1.it)('should handle multiple relationships with mixed UUID and serial foreign keys', async () => {
217
+ const data = {
218
+ name: 'Comment',
219
+ snakeCasedName: 'comment',
220
+ createPrimaryKey: true,
221
+ primaryKeyType: 'serial',
222
+ createdAt: true,
223
+ updatedAt: true,
224
+ columns: [],
225
+ associations: [
226
+ {
227
+ name: 'User',
228
+ type: 'ManyToOne',
229
+ relationshipName: 'user',
230
+ pluralizedRelationshipName: 'users',
231
+ pluralizedName: 'users',
232
+ camelCasedName: 'user',
233
+ camelCasedId: 'userId',
234
+ entityName: 'comment',
235
+ joinTable: false,
236
+ biDirectional: true,
237
+ inverseSidePropertyName: 'comments',
238
+ referencedEntityPrimaryKeyType: 'uuid',
239
+ nullable: false,
240
+ index: true
241
+ },
242
+ {
243
+ name: 'Post',
244
+ type: 'ManyToOne',
245
+ relationshipName: 'post',
246
+ pluralizedRelationshipName: 'posts',
247
+ pluralizedName: 'posts',
248
+ camelCasedName: 'post',
249
+ camelCasedId: 'postId',
250
+ entityName: 'comment',
251
+ joinTable: false,
252
+ biDirectional: true,
253
+ inverseSidePropertyName: 'comments',
254
+ referencedEntityPrimaryKeyType: 'serial',
255
+ nullable: false,
256
+ index: true
257
+ }
258
+ ],
259
+ indexes: [],
260
+ uniques: [],
261
+ entitiesToImport: ['User', 'Post'],
262
+ apiType: 'rest',
263
+ importEnums: false
264
+ };
265
+ const rendered = await Eta.renderFileAsync('./entities/entity', data);
266
+ // Should have UUID foreign key for User
267
+ (0, globals_1.expect)(rendered).toContain('@Column({ type: "uuid"');
268
+ (0, globals_1.expect)(rendered).toContain('userId: string;');
269
+ // Should have integer foreign key for Post
270
+ (0, globals_1.expect)(rendered).toContain('@Column({ type: "integer"');
271
+ (0, globals_1.expect)(rendered).toContain('postId: number;');
272
+ // Should have both relationships
273
+ (0, globals_1.expect)(rendered).toContain('user: User;');
274
+ (0, globals_1.expect)(rendered).toContain('post: Post;');
275
+ });
276
+ });
277
+ });
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.1",
2
+ "version": "0.4.0",
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.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Apso CLI",
5
5
  "author": "Apso by Mavric - @mavric",
6
6
  "bin": {