@neohm/nh-cli 1.1.5 → 1.3.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.
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
+ const { pkg, current } = require('./flavour');
3
4
  const {
5
+ toWords,
4
6
  toDotCase,
5
7
  toPascalCase,
6
8
  toKebabCase,
@@ -10,12 +12,30 @@ const {
10
12
 
11
13
  /**
12
14
  * Code templates. All generated code imports shared base classes from the
13
- * published library `@neohm/nestend` (verified exports: CommonEntity,
14
- * CommonDataDto, CommonDataProcessor, MigrationUtility, SeederUtility, etc.)
15
- * and matches the dot.separated filename / PascalCase class conventions used
16
- * across nestjs-boilerplate.
15
+ * package the active flavour names — `@neohm/nestend` under `nh`,
16
+ * `@neohm/nestendx` under `nhx` (verified exports: CommonEntity, CommonDataDto,
17
+ * CommonDataProcessor, MigrationUtility, SeederUtility, etc.) — and matches the
18
+ * dot.separated filename / PascalCase class conventions used across the
19
+ * boilerplates.
20
+ *
21
+ * The two differ in what a record belongs to, and that difference is read from
22
+ * `current().tenantScoped` rather than branched on a package name: tenanted
23
+ * code scopes rows by `tenant_id` and checks a scope with a role beside it,
24
+ * while the B2C model scopes by the user who created the row and checks the
25
+ * scope alone.
17
26
  */
18
27
 
28
+ /**
29
+ * A named import, wrapped the way prettier's 80-column printer would: one line
30
+ * while it fits, one member per line once it doesn't. Generated files are
31
+ * committed as-is, so they have to come out already formatted.
32
+ */
33
+ function namedImport(members, from) {
34
+ const oneLine = `import { ${members.join(', ')} } from '${from}';`;
35
+ if (oneLine.length <= 80) return oneLine;
36
+ return `import {\n${members.map((m) => ` ${m},`).join('\n')}\n} from '${from}';`;
37
+ }
38
+
19
39
  /** Build the canonical names for a given entity-ish base (e.g. "Shape"). */
20
40
  function names(base) {
21
41
  const dot = toDotCase(base);
@@ -32,6 +52,8 @@ function names(base) {
32
52
  attributesDtoFile: `${dot}.attributes.dto.ts`,
33
53
  controllerClass: `${pascal}Controller`,
34
54
  controllerFile: `${dot}.controller.ts`,
55
+ serviceClass: `${pascal}Service`,
56
+ serviceFile: `${dot}.service.ts`,
35
57
  dtoClass: `Add${pascal}DataDto`,
36
58
  dtoFile: `add.${dot}.data.dto.ts`,
37
59
  processorClass: `${pascal}DataProcessor`,
@@ -42,6 +64,17 @@ function names(base) {
42
64
  subscriberFile: `${dot}.subscriber.ts`,
43
65
  jobClass: `${pascal}Job`,
44
66
  jobFile: `${dot}.job.ts`,
67
+ // External (API-key) surface. Mirrors the internal names with an
68
+ // `external.` filename prefix and an `External` class prefix, matching
69
+ // external/controllers/external.test.controller.ts in the boilerplate.
70
+ externalControllerClass: `External${pascal}Controller`,
71
+ externalControllerFile: `external.${dot}.controller.ts`,
72
+ externalServiceClass: `External${pascal}Service`,
73
+ externalServiceFile: `external.${dot}.service.ts`,
74
+ externalProcessorClass: `External${pascal}DataProcessor`,
75
+ externalProcessorFile: `external.${dot}.data.processor.ts`,
76
+ externalListProcessorClass: `External${pascal}ListProcessor`,
77
+ externalListProcessorFile: `external.${dot}.list.processor.ts`,
45
78
  // Fed through naming.migrationFileName/migrationClassName so the migration
46
79
  // `nh gen` writes is named exactly like one from `nh migration`.
47
80
  migrationName: `Add${pascal}Table`,
@@ -50,14 +83,46 @@ function names(base) {
50
83
 
51
84
  function entity(base, tableName) {
52
85
  const n = names(base);
53
- return `import { Column, Entity } from 'typeorm';
54
- import { CommonEntity } from '@neohm/nestend';
86
+
87
+ // Without tenants there is nothing extra to declare: CommonEntity already
88
+ // carries `created_by` and its relation, which is what a row belongs to.
89
+ if (!current().tenantScoped) {
90
+ return `import { Column, Entity } from 'typeorm';
91
+ import { CommonEntity } from '${pkg()}';
92
+ import { ${n.attributesDtoClass} } from '../dtos/${n.dot}.attributes.dto';
93
+
94
+ @Entity({ name: '${tableName}' })
95
+ export class ${n.entityClass} extends CommonEntity {
96
+ // TODO: declare the columns ${n.pascal} owns.
97
+
98
+ @Column({ type: 'jsonb' })
99
+ attributes: ${n.attributesDtoClass};
100
+
101
+ // Relations and methods
102
+ }
103
+ `;
104
+ }
105
+
106
+ return `import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
107
+ import { CommonEntity, TenantEntity } from '${pkg()}';
55
108
  import { ${n.attributesDtoClass} } from '../dtos/${n.dot}.attributes.dto';
56
109
 
57
110
  @Entity({ name: '${tableName}' })
58
111
  export class ${n.entityClass} extends CommonEntity {
112
+ // Tenant scope — the service and the list processor both filter on it.
113
+ @Column()
114
+ tenant_id: string;
115
+
116
+ // TODO: declare the columns ${n.pascal} owns.
117
+
59
118
  @Column({ type: 'jsonb' })
60
119
  attributes: ${n.attributesDtoClass};
120
+
121
+ // Relations and methods
122
+
123
+ @ManyToOne(() => TenantEntity)
124
+ @JoinColumn({ name: 'tenant_id' })
125
+ tenant: TenantEntity;
61
126
  }
62
127
  `;
63
128
  }
@@ -70,46 +135,270 @@ function attributesDto(base) {
70
135
  `;
71
136
  }
72
137
 
73
- function controller(base, { listProcessor = false } = {}) {
138
+ /**
139
+ * Controllers hold no business logic. They authenticate the caller, then
140
+ * delegate to the module's service — so the HTTP surface stays readable and
141
+ * the same operation is reusable from a job, a subscriber or another service.
142
+ */
143
+ function controller(base, { processor = false, listProcessor = false } = {}) {
74
144
  const n = names(base);
75
- if (!listProcessor) {
76
- return `import { Controller } from '@nestjs/common';
77
- import { ApiTags } from '@nestjs/swagger';
145
+ const svcProp = `${n.camel}Service`;
146
+ const hasPost = processor || listProcessor;
147
+
148
+ // @nestjs/common members, alphabetised the way prettier leaves them.
149
+ const nestCommon = ['Controller', 'Get', 'Param'];
150
+ if (hasPost) nestCommon.push('Body', 'Post');
151
+ nestCommon.sort();
152
+
153
+ const nestendImports = ['AuthService'];
154
+ if (listProcessor) nestendImports.push('CommonListFilterDto');
155
+ nestendImports.push('CommonParamsDto');
156
+
157
+ const imports = [
158
+ namedImport(nestCommon, '@nestjs/common'),
159
+ // @ApiBearerAuth() attaches the requirement to these operations. The
160
+ // DocumentBuilder call only registers the scheme (under its default name,
161
+ // 'bearer') and draws the Authorize button — without the decorator
162
+ // swagger-ui shows no padlock and never sends the Authorization header.
163
+ `import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';`,
164
+ namedImport(nestendImports, pkg()),
165
+ `import { ${n.serviceClass} } from '../services/${n.dot}.service';`,
166
+ ];
167
+ if (processor) {
168
+ imports.push(
169
+ `import { ${n.dtoClass} } from '../dtos/add.${n.dot}.data.dto';`,
170
+ );
171
+ }
78
172
 
79
- @ApiTags('${n.kebab}')
80
- @Controller('api/v1/${n.kebab}')
81
- export class ${n.controllerClass} {
82
- constructor() {}
83
- }
84
- `;
173
+ // Tenanted access takes a scope and a role; the B2C model has no roles at
174
+ // all, so the same call carries the scope alone.
175
+ const tenanted = current().tenantScoped;
176
+ const gate = (indent) =>
177
+ tenanted
178
+ ? `await this.authService.validateAccess(\n${indent} 'add-scope',\n${indent} 'add-role',\n${indent})`
179
+ : `await this.authService.validateAccess('add-scope')`;
180
+
181
+ const methods = [
182
+ ` @Get(':id')
183
+ async get${n.pascal}(@Param() params: CommonParamsDto) {
184
+ const { user } = ${gate(' ')};
185
+
186
+ return this.${svcProp}.get(user, params.id);
187
+ }`,
188
+ ];
189
+
190
+ if (processor) {
191
+ methods.push(` @Post()
192
+ async upsert${n.pascal}(@Body() body: ${n.dtoClass}) {
193
+ ${tenanted ? `await this.authService.validateAccess('add-scope', 'add-role');` : `await this.authService.validateAccess('add-scope');`}
194
+
195
+ return this.${svcProp}.upsert(body);
196
+ }`);
85
197
  }
86
- return `import { Body, Controller, Post } from '@nestjs/common';
87
- import { ApiTags } from '@nestjs/swagger';
88
- import {
89
- AuthService,
90
- CommonListFilterDto,
91
- ListFilterService,
92
- } from '@neohm/nestend';
93
- import { ${n.listProcessorClass} } from '../libraries/${n.dot}.list.processor';
94
198
 
199
+ if (listProcessor) {
200
+ methods.push(` @Post('list')
201
+ async list(@Body() body: CommonListFilterDto) {
202
+ const { user } = ${gate(' ')};
203
+
204
+ return this.${svcProp}.list(user, body);
205
+ }`);
206
+ }
207
+
208
+ const gateTodo = tenanted
209
+ ? `// TODO: replace 'add-scope' / 'add-role' with this module's scope and role.`
210
+ : `// TODO: replace 'add-scope' with this module's scope — a granted parent\n// scope reaches its children, so name the one the feature is filed under.`;
211
+
212
+ return `${imports.join('\n')}
213
+
214
+ ${gateTodo}
95
215
  @ApiTags('${n.kebab}')
216
+ @ApiBearerAuth()
96
217
  @Controller('api/v1/${n.kebab}')
97
218
  export class ${n.controllerClass} {
98
219
  constructor(
99
- private readonly listFilterService: ListFilterService,
220
+ private readonly ${svcProp}: ${n.serviceClass},
100
221
  private readonly authService: AuthService,
101
222
  ) {}
102
223
 
103
- @Post('list')
104
- async list(@Body() body: CommonListFilterDto) {
105
- // Open by default: authenticate explicitly. validateAccess() gates the
106
- // feature on the caller's organization, and guarantees the
107
- // organization_id the processor scopes on — a tenantless user is
108
- // refused here rather than reaching the query with a null scope.
109
- // TODO: replace with the sys_scopes identifier this endpoint sits behind.
110
- const { user } = await this.authService.validateAccess('add-scope', 'add-role');
111
-
112
- return new ${n.listProcessorClass}(this.listFilterService, user).process(body);
224
+ ${methods.join('\n\n')}
225
+ }
226
+ `;
227
+ }
228
+
229
+ /**
230
+ * The module's service layer, always generated: single-record reads live here,
231
+ * and write / list work is handed to the processors. Injected into the
232
+ * controller, so nothing downstream has to know how a record is fetched.
233
+ *
234
+ * `get()` is unconditional — it is the one operation that needs no processor.
235
+ * `upsert()` appears with a data processor, `list()` with a listing processor.
236
+ */
237
+ function service(base, { processor = false, listProcessor = false } = {}) {
238
+ const n = names(base);
239
+ const label = toWords(base).join(' ');
240
+
241
+ const nestendImports = [];
242
+ if (listProcessor) {
243
+ nestendImports.push('CommonListFilterDto', 'ListFilterService');
244
+ }
245
+ nestendImports.push('NotFoundException', 'UserEntity');
246
+
247
+ const imports = [
248
+ namedImport(nestendImports, pkg()),
249
+ `import { Injectable } from '@nestjs/common';`,
250
+ ];
251
+ if (listProcessor) {
252
+ imports.push(
253
+ `import { ${n.listProcessorClass} } from '../libraries/${n.dot}.list.processor';`,
254
+ );
255
+ }
256
+ if (processor) {
257
+ imports.push(
258
+ `import { ${n.dtoClass} } from '../dtos/add.${n.dot}.data.dto';`,
259
+ `import { ${n.processorClass} } from '../libraries/${n.dot}.data.processor';`,
260
+ );
261
+ }
262
+ imports.push(
263
+ `import { ${n.entityClass} } from '../entities/${n.dot}.entity';`,
264
+ );
265
+
266
+ const members = [];
267
+ if (listProcessor) {
268
+ members.push(
269
+ ` constructor(protected readonly listFilterService: ListFilterService) {}`,
270
+ );
271
+ }
272
+
273
+ // Ownership is part of the lookup, not a check after it: a row belonging to
274
+ // someone else must read as "not found", never as "forbidden". Tenanted rows
275
+ // carry the organization; without tenants it is the user who created it.
276
+ const owner = current().tenantScoped
277
+ ? 'tenant_id: user.organization_id'
278
+ : 'created_by: user.id';
279
+ members.push(` async get(user: UserEntity, id: string) {
280
+ const ${n.camel} = await ${n.entityClass}.findOne({
281
+ where: { id, ${owner} },
282
+ });
283
+ if (!${n.camel}) throw new NotFoundException('Invalid ${label} id');
284
+
285
+ return ${n.camel};
286
+ }`);
287
+
288
+ if (processor) {
289
+ // Insert or update: CommonDataDto carries an optional `id`, and the data
290
+ // processor decides which of the two the payload asks for.
291
+ members.push(` async upsert(data: ${n.dtoClass}) {
292
+ return new ${n.processorClass}().process(data);
293
+ }`);
294
+ }
295
+
296
+ if (listProcessor) {
297
+ // Emit the call already wrapped the way prettier's 80-column printer
298
+ // would, so a long entity name doesn't leave the generated file dirty.
299
+ // Prettier's three tiers: keep the fluent one-liner; else break the
300
+ // .process() argument; else break the constructor arguments too.
301
+ const call = `new ${n.listProcessorClass}(this.listFilterService, user)`;
302
+ const oneLine = ` return ${call}.process(data);`;
303
+ const brokenArg = ` return ${call}.process(`;
304
+ let body;
305
+ if (oneLine.length <= 80) {
306
+ body = oneLine;
307
+ } else if (brokenArg.length <= 80) {
308
+ body = `${brokenArg}\n data,\n );`;
309
+ } else {
310
+ body = ` return new ${n.listProcessorClass}(\n this.listFilterService,\n user,\n ).process(data);`;
311
+ }
312
+ members.push(` async list(user: UserEntity, data: CommonListFilterDto) {
313
+ ${body}
314
+ }`);
315
+ }
316
+
317
+ return `${imports.join('\n')}
318
+
319
+ @Injectable()
320
+ export class ${n.serviceClass} {
321
+ ${members.join('\n\n')}
322
+ }
323
+ `;
324
+ }
325
+
326
+ /**
327
+ * Bare controller for `nh gen -c`: swagger tag, the caller-supplied route
328
+ * under api/v1, AuthService on hand, and one empty `check()` to build on.
329
+ */
330
+ function bareController(base, routePath) {
331
+ const n = names(base);
332
+ return `import { Controller, Get } from '@nestjs/common';
333
+ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
334
+ import { AuthService } from '${pkg()}';
335
+
336
+ @ApiTags('${routePath}')
337
+ @ApiBearerAuth()
338
+ @Controller('api/v1/${routePath}')
339
+ export class ${n.controllerClass} {
340
+ constructor(private readonly authService: AuthService) {}
341
+
342
+ @Get('check')
343
+ async check() {}
344
+ }
345
+ `;
346
+ }
347
+
348
+ /**
349
+ * Bare external controller for `nh gen -x -c`. The API-key surface differs
350
+ * from the internal one in three ways, all of them load-bearing: the route is
351
+ * api/e/v1, the key is declared to swagger with @ApiSecurity('api-key'), and
352
+ * access is checked through ApiKeyService rather than AuthService.
353
+ */
354
+ function bareExternalController(base, routePath) {
355
+ const n = names(base);
356
+ return `import { Controller, Get } from '@nestjs/common';
357
+ import { ApiSecurity, ApiTags } from '@nestjs/swagger';
358
+ import { ApiKeyService } from '${pkg()}';
359
+
360
+ @ApiTags('external-${routePath} (Api key)')
361
+ @ApiSecurity('api-key')
362
+ @Controller('api/e/v1/${routePath}')
363
+ export class ${n.externalControllerClass} {
364
+ constructor(private readonly apiKeyService: ApiKeyService) {}
365
+
366
+ @Get('check')
367
+ async check() {}
368
+ }
369
+ `;
370
+ }
371
+
372
+ /** Bare injectable service for `nh gen -s` / `nh gen -x -s`. */
373
+ function bareService(base, { external = false } = {}) {
374
+ const n = names(base);
375
+ const cls = external ? n.externalServiceClass : n.serviceClass;
376
+ return `import { Injectable } from '@nestjs/common';
377
+
378
+ @Injectable()
379
+ export class ${cls} {}
380
+ `;
381
+ }
382
+
383
+ /**
384
+ * Standalone job for `nh gen -j`. Unlike the entity job the wizard scaffolds,
385
+ * this one is not driven by a subscriber, so it takes no DatabaseEventDto and
386
+ * references no entity — whatever `dispatch()` was handed arrives as `data`.
387
+ */
388
+ function additionalJob(base) {
389
+ const n = names(base);
390
+ return `import { CommonJob, QueueService } from '${pkg()}';
391
+ import { Injectable } from '@nestjs/common';
392
+ import { JobConstants } from '../../constants/job.constants';
393
+
394
+ @Injectable()
395
+ export class ${n.jobClass} extends CommonJob {
396
+ constructor(protected readonly queueService: QueueService) {
397
+ super(JobConstants.${n.camel}Job);
398
+ }
399
+
400
+ async handle(data: unknown): Promise<void> {
401
+ return;
113
402
  }
114
403
  }
115
404
  `;
@@ -117,7 +406,7 @@ export class ${n.controllerClass} {
117
406
 
118
407
  function dto(base) {
119
408
  const n = names(base);
120
- return `import { CommonDataDto } from '@neohm/nestend';
409
+ return `import { CommonDataDto } from '${pkg()}';
121
410
 
122
411
  export class ${n.dtoClass} extends CommonDataDto {
123
412
  // TODO: declare validated payload fields for ${n.pascal}.
@@ -125,12 +414,16 @@ export class ${n.dtoClass} extends CommonDataDto {
125
414
  `;
126
415
  }
127
416
 
128
- function processor(base) {
417
+ function processor(base, { external = false } = {}) {
129
418
  const n = names(base);
130
- return `import { CommonDataProcessor } from '@neohm/nestend';
131
- import { ${n.dtoClass} } from '../dtos/add.${n.dot}.data.dto';
132
-
133
- export class ${n.processorClass} extends CommonDataProcessor {
419
+ // An external processor sits one level deeper (external/libraries/), so its
420
+ // reach back into the module's dtos/ gains a segment.
421
+ const up = external ? '../..' : '..';
422
+ const cls = external ? n.externalProcessorClass : n.processorClass;
423
+ return `import { CommonDataProcessor } from '${pkg()}';
424
+ import { ${n.dtoClass} } from '${up}/dtos/add.${n.dot}.data.dto';
425
+
426
+ export class ${cls} extends CommonDataProcessor {
134
427
  protected payload: ${n.dtoClass};
135
428
 
136
429
  async process(data: ${n.dtoClass}) {
@@ -153,8 +446,9 @@ export class ${n.processorClass} extends CommonDataProcessor {
153
446
  `;
154
447
  }
155
448
 
156
- function listProcessor(base, tableName) {
449
+ function listProcessor(base, tableName, { external = false } = {}) {
157
450
  const n = names(base);
451
+ const cls = external ? n.externalListProcessorClass : n.listProcessorClass;
158
452
  return `import {
159
453
  CommonListFilterConfig,
160
454
  CommonListFilterDto,
@@ -163,7 +457,7 @@ function listProcessor(base, tableName) {
163
457
  ListFilterService,
164
458
  SortDirection,
165
459
  UserEntity,
166
- } from '@neohm/nestend';
460
+ } from '${pkg()}';
167
461
 
168
462
  /**
169
463
  * Lists / filters ${n.pascal}.
@@ -183,7 +477,7 @@ function listProcessor(base, tableName) {
183
477
  * \`columns\`. Standing literal clauses go in \`predicates\`; anything
184
478
  * carrying a value goes in the method, where add() binds it.
185
479
  */
186
- export class ${n.listProcessorClass} extends CommonListProcessor {
480
+ export class ${cls} extends CommonListProcessor {
187
481
  protected config: CommonListFilterConfig = {
188
482
  query: '${tableName} a',
189
483
  columns: [
@@ -219,11 +513,13 @@ export class ${n.listProcessorClass} extends CommonListProcessor {
219
513
  }
220
514
 
221
515
  protected applyCustomPredicates() {
222
- // Filter for specific user's tenant/organization
223
- this.predicates.add(
224
- 'a.organization_id = :organization_id',
225
- this.user.organization_id,
226
- );
516
+ ${
517
+ current().tenantScoped
518
+ ? ` // Filter for the caller's tenant/organization
519
+ this.predicates.add('a.tenant_id = :tenant_id', this.user.organization_id);`
520
+ : ` // Filter for the caller: rows belong to whoever created them.
521
+ this.predicates.add('a.created_by = :created_by', this.user.id);`
522
+ }
227
523
  }
228
524
  }
229
525
  `;
@@ -231,7 +527,7 @@ export class ${n.listProcessorClass} extends CommonListProcessor {
231
527
 
232
528
  function subscriber(base) {
233
529
  const n = names(base);
234
- return `import { CommonSubscriber } from '@neohm/nestend';
530
+ return `import { CommonSubscriber } from '${pkg()}';
235
531
  import { EventSubscriber } from 'typeorm';
236
532
  import { ${n.entityClass} } from '../entities/${n.dot}.entity';
237
533
  import { DataSource } from 'typeorm';
@@ -256,7 +552,7 @@ export class ${n.subscriberClass} extends CommonSubscriber<${n.entityClass}> {
256
552
 
257
553
  function job(base) {
258
554
  const n = names(base);
259
- return `import { CommonJob, QueueService, DatabaseEventDto } from '@neohm/nestend';
555
+ return `import { CommonJob, QueueService, DatabaseEventDto } from '${pkg()}';
260
556
  import { Injectable } from '@nestjs/common';
261
557
  import { JobConstants } from '../../constants/job.constants';
262
558
  import { ${n.entityClass} } from '../entities/${n.dot}.entity';
@@ -282,7 +578,7 @@ export class ${n.jobClass} extends CommonJob {
282
578
  function genMigration(base, tableName, timestamp) {
283
579
  const n = names(base);
284
580
  const className = migrationClassName(n.migrationName, timestamp);
285
- return `import { MigrationUtility } from '@neohm/nestend';
581
+ return `import { MigrationUtility } from '${pkg()}';
286
582
 
287
583
  export class ${className} extends MigrationUtility {
288
584
  constructor() {
@@ -292,10 +588,21 @@ export class ${className} extends MigrationUtility {
292
588
 
293
589
  process() {
294
590
  this.rootPrimary();
295
- this.foreign({ name: 'created_by', foreignTable: 'sys_users' });
296
- // TODO: add columns matching ${n.entityClass} attributes.
591
+
592
+ ${
593
+ current().tenantScoped
594
+ ? ` this.foreign({ name: 'tenant_id', foreignTable: 'sys_tenants' });\n`
595
+ : ''
596
+ } this.foreign({ name: 'created_by', foreignTable: 'sys_users' });
597
+ this.foreign({ name: 'updated_by', foreignTable: 'sys_users' });
598
+
599
+ // TODO: add columns matching ${n.entityClass}.
600
+
297
601
  this.json('attributes');
298
602
  this.timestamps();
603
+
604
+ ${current().tenantScoped ? ` this.index('tenant_id');\n` : ''} this.index('created_by');
605
+ this.index('updated_by');
299
606
  }
300
607
  }
301
608
  `;
@@ -304,7 +611,7 @@ export class ${className} extends MigrationUtility {
304
611
  /** Standalone migration (`nh migration`). */
305
612
  function migration(migrationName, tableName, timestamp) {
306
613
  const className = migrationClassName(migrationName, timestamp);
307
- return `import { MigrationUtility } from '@neohm/nestend';
614
+ return `import { MigrationUtility } from '${pkg()}';
308
615
 
309
616
  export class ${className} extends MigrationUtility {
310
617
  constructor() {
@@ -324,7 +631,7 @@ export class ${className} extends MigrationUtility {
324
631
  /** Seed file (`nh seed`). */
325
632
  function seed(seedName, tableName, timestamp) {
326
633
  const className = `${toPascalCase(seedName)}${timestamp}`;
327
- return `import { SeederUtility } from '@neohm/nestend';
634
+ return `import { SeederUtility } from '${pkg()}';
328
635
 
329
636
  export class ${className} extends SeederUtility {
330
637
  constructor() {
@@ -343,7 +650,7 @@ export class ${className} extends SeederUtility {
343
650
  /** Fresh src/constants/job.constants.ts (first entry included). */
344
651
  function jobConstantsFile(base) {
345
652
  const n = names(base);
346
- return `import { baseJobConstants } from '@neohm/nestend';
653
+ return `import { baseJobConstants } from '${pkg()}';
347
654
 
348
655
  export const JobConstants = {
349
656
  ...baseJobConstants,
@@ -355,7 +662,7 @@ export const JobConstants = {
355
662
  /** Fresh src/constants/entity.constants.ts (first entry included). */
356
663
  function entityConstantsFile(base) {
357
664
  const n = names(base);
358
- return `import { baseEntityConstants } from '@neohm/nestend';
665
+ return `import { baseEntityConstants } from '${pkg()}';
359
666
 
360
667
  export const EntityConstants = {
361
668
  ...baseEntityConstants,
@@ -367,7 +674,7 @@ export const EntityConstants = {
367
674
  /** Fresh src/constants/job.maps.ts (first entry included). */
368
675
  function jobMapsFile(base, moduleDot, constants) {
369
676
  const n = names(base);
370
- return `import { baseJobMaps } from '@neohm/nestend';
677
+ return `import { baseJobMaps } from '${pkg()}';
371
678
  import { ${n.jobClass} } from '../${moduleDot}/jobs/${n.dot}.job';
372
679
  import { ${constants.exportName} } from '${constants.importPath}';
373
680
 
@@ -381,7 +688,7 @@ export const JobMaps = {
381
688
  /** Fresh src/constants/entity.maps.ts (first entry included). */
382
689
  function entityMapsFile(base, moduleDot, constants) {
383
690
  const n = names(base);
384
- return `import { baseEntityMaps } from '@neohm/nestend';
691
+ return `import { baseEntityMaps } from '${pkg()}';
385
692
  import { ${n.entityClass} } from '../${moduleDot}/entities/${n.dot}.entity';
386
693
  import { ${constants.exportName} } from '${constants.importPath}';
387
694
 
@@ -413,7 +720,7 @@ import {
413
720
  QueueModule,
414
721
  AuthModule,
415
722
  SystemPropertyModule,
416
- } from '@neohm/nestend';
723
+ } from '${pkg()}';
417
724
  import { es6Classes } from './es6.classes';
418
725
 
419
726
  @Module({
@@ -435,6 +742,11 @@ module.exports = {
435
742
  entity,
436
743
  attributesDto,
437
744
  controller,
745
+ service,
746
+ bareController,
747
+ bareExternalController,
748
+ bareService,
749
+ additionalJob,
438
750
  dto,
439
751
  processor,
440
752
  listProcessor,