@neohm/nh-cli 1.2.0 → 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.
package/README.md CHANGED
@@ -1,15 +1,37 @@
1
- # @neohm/nh-cli — `nh`
1
+ # @neohm/nh-cli — `nh` and `nhx`
2
2
 
3
3
  A scaffolding CLI that accelerates development on **neohm. NestJS projects**
4
- built on top of the shared [`@neohm/nestend`](../nestend) library.
4
+ built on top of the shared [`@neohm/nestend`](../nestend) library — or its B2C
5
+ sibling [`@neohm/nestendx`](../nestendx).
5
6
 
6
7
  `nh` generates fully wired modules, entities, controllers, services, DTOs, data
7
8
  processors, migrations and seeds — and keeps your import barrels
8
9
  (`es6.classes.ts`) and `app.module.ts` registration in sync, so the project
9
10
  builds without any manual import wiring.
10
11
 
11
- Every generated artifact matches the conventions of the `nestjs-boilerplate`
12
- reference project:
12
+ ## Two flavours, one CLI
13
+
14
+ `nh` writes code against **`@neohm/nestend`**, `nhx` against
15
+ **`@neohm/nestendx`**. Same commands, same conventions, same wiring — they
16
+ differ only where the two packages do:
17
+
18
+ | | `nh` (nestend) | `nhx` (nestendx) |
19
+ | ------------------------ | ------------------------------------------ | ----------------------------------------- |
20
+ | Import specifier | `@neohm/nestend` | `@neohm/nestendx` |
21
+ | What a row belongs to | a tenant (`tenant_id` + `TenantEntity`) | the user who created it (`created_by`) |
22
+ | Access check | `validateAccess(scope, role)` | `validateAccess(scope)` — there are no roles |
23
+ | Generated migration | FK + index on `tenant_id` | no tenant column to key |
24
+
25
+ Everything that does not write package-specific code — `sync`, `check`, `list`,
26
+ `remove` — behaves identically under either name.
27
+
28
+ Running the wrong one is refused rather than half-generated: both read the
29
+ target project's `package.json`, and scaffolding tenanted code into a nestendx
30
+ project (or the reverse) stops with a message naming the command to use instead.
31
+ A project that depends on neither is taken at its word.
32
+
33
+ Every generated artifact matches the conventions of the reference project for
34
+ that flavour — `nestjs-boilerplate` for `nh`, `nestendx-boilerplate` for `nhx`:
13
35
 
14
36
  - **Filenames** are `dot.separated.lowercase` (no hyphens) — e.g.
15
37
  `shape.entity.ts`, and a multi-word `RoundShape` becomes
@@ -22,13 +44,15 @@ reference project:
22
44
  ```bash
23
45
  # from the neohm-cli directory
24
46
  npm install
25
- npm link # exposes `nh` globally
47
+ npm link # exposes both `nh` and `nhx` globally
26
48
 
27
49
  # or run without linking
28
50
  npx nh --help
51
+ npx nhx --help
29
52
  ```
30
53
 
31
- Run `nh` from **inside** a target NestJS project (e.g. `nestjs-boilerplate`).
54
+ Run `nh` from **inside** a target NestJS project (e.g. `nestjs-boilerplate`),
55
+ or `nhx` from inside a nestendx one (e.g. `nestendx-boilerplate`).
32
56
  The project root is auto-detected by walking up from the current directory for a
33
57
  `package.json` next to a `src/` folder.
34
58
 
@@ -46,6 +70,9 @@ The project root is auto-detected by walking up from the current directory for a
46
70
  | `nh remove <name>` | Safely remove a scaffolded module and de-register it (migrations are preserved). |
47
71
  | `nh --help` / `nh -v` | Help and version. |
48
72
 
73
+ Read `nhx` for `nh` throughout the table above when generating against
74
+ `@neohm/nestendx`.
75
+
49
76
  ### `nh gen`
50
77
 
51
78
  ```text
package/bin/nh.js CHANGED
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ // Generates @neohm/nestend code — the tenanted model. `nhx` is the same CLI
5
+ // against @neohm/nestendx; the flavour is set before anything else loads,
6
+ // because the templates read it while they are being defined.
7
+ require('../src/lib/flavour').setFlavour('nestend');
8
+
4
9
  const { program } = require('../src/index');
5
10
 
6
11
  program.parseAsync(process.argv);
package/bin/nhx.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // The nestendx flavour of `nh`: same commands, same conventions, code written
5
+ // against @neohm/nestendx — users hold scopes rather than roles, and a row
6
+ // belongs to whoever created it rather than to a tenant.
7
+ require('../src/lib/flavour').setFlavour('nestendx');
8
+
9
+ const { program } = require('../src/index');
10
+
11
+ program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@neohm/nh-cli",
3
- "version": "1.2.0",
4
- "description": "nh — Command Line Interface for neohm. NestJS projects",
3
+ "version": "1.3.0",
4
+ "description": "nh / nhx \u2014 Command Line Interface for neohm. NestJS projects",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "nh": "bin/nh.js"
7
+ "nh": "bin/nh.js",
8
+ "nhx": "bin/nhx.js"
8
9
  },
9
10
  "scripts": {
10
11
  "start": "node bin/nh.js",
11
- "prepublishOnly": "find bin src -name '*.js' | xargs node --check"
12
+ "prepublishOnly": "find bin src -name '*.js' | xargs node --check",
13
+ "start:x": "node bin/nhx.js"
12
14
  },
13
15
  "packageManager": "yarn@1.22.22",
14
16
  "files": [
@@ -20,7 +22,8 @@
20
22
  "cli",
21
23
  "scaffold",
22
24
  "neohm",
23
- "nestend"
25
+ "nestend",
26
+ "nestendx"
24
27
  ],
25
28
  "author": "Ashim Baral",
26
29
  "license": "MIT",
@@ -2,6 +2,7 @@
2
2
 
3
3
  const path = require('path');
4
4
  const chalk = require('chalk');
5
+ const { cli } = require('../lib/flavour');
5
6
 
6
7
  const { projectPaths } = require('../lib/paths');
7
8
  const engine = require('../lib/sync.engine');
@@ -47,9 +48,13 @@ async function check() {
47
48
  }
48
49
 
49
50
  if (changes) {
50
- console.log(chalk.yellow(`\n${changes} file(s) would change. Run \`nh sync\` to apply.`));
51
+ console.log(
52
+ chalk.yellow(
53
+ `\n${changes} file(s) would change. Run \`${cli()} sync\` to apply.`,
54
+ ),
55
+ );
51
56
  } else {
52
- log.done('In sync — `nh sync` would make no changes.');
57
+ log.done(`In sync — \`${cli()} sync\` would make no changes.`);
53
58
  }
54
59
  }
55
60
 
@@ -6,6 +6,7 @@ const inquirer = require('inquirer');
6
6
  const chalk = require('chalk');
7
7
 
8
8
  const { projectPaths, MODULE_SUBFOLDERS } = require('../lib/paths');
9
+ const { assertFlavourMatchesProject, cli } = require('../lib/flavour');
9
10
  const naming = require('../lib/naming');
10
11
  const tpl = require('../lib/templates');
11
12
  const {
@@ -170,6 +171,7 @@ async function genFlag(target, { external = false } = {}) {
170
171
  }
171
172
 
172
173
  const paths = projectPaths();
174
+ assertFlavourMatchesProject(paths.root);
173
175
  log.info(`Project: ${paths.root}\n`);
174
176
 
175
177
  const touched = [];
@@ -346,7 +348,9 @@ async function genFlag(target, { external = false } = {}) {
346
348
  const made = artifacts.map((a) => a.className).join(', ');
347
349
  log.done(`${made} generated in "${moduleDot}".`);
348
350
  console.log(
349
- chalk.dim('Run `nh sync` anytime to re-wire imports after manual edits.'),
351
+ chalk.dim(
352
+ `Run \`${cli()} sync\` anytime to re-wire imports after manual edits.`,
353
+ ),
350
354
  );
351
355
  }
352
356
 
@@ -6,6 +6,7 @@ const inquirer = require("inquirer");
6
6
  const chalk = require("chalk");
7
7
 
8
8
  const { projectPaths, MODULE_SUBFOLDERS } = require("../lib/paths");
9
+ const { assertFlavourMatchesProject, cli } = require("../lib/flavour");
9
10
  const naming = require("../lib/naming");
10
11
  const tpl = require("../lib/templates");
11
12
  const { writeFile, report: reportFile, log, ensureDir } = require("../lib/fs.utils");
@@ -15,6 +16,7 @@ const { registerJob, registerEntity } = require("../lib/registry");
15
16
 
16
17
  async function gen() {
17
18
  const paths = projectPaths();
19
+ assertFlavourMatchesProject(paths.root);
18
20
  log.info(`Project: ${paths.root}\n`);
19
21
 
20
22
  // Every file this run writes or rewrites, formatted in one pass at the end.
@@ -215,7 +217,9 @@ async function gen() {
215
217
 
216
218
  log.done(`Module "${moduleDot}" scaffolded.`);
217
219
  console.log(
218
- chalk.dim("Run `nh sync` anytime to re-wire imports after manual edits."),
220
+ chalk.dim(
221
+ `Run \`${cli()} sync\` anytime to re-wire imports after manual edits.`,
222
+ ),
219
223
  );
220
224
  }
221
225
 
@@ -4,12 +4,14 @@ const path = require("path");
4
4
  const inquirer = require("inquirer");
5
5
 
6
6
  const { projectPaths } = require("../lib/paths");
7
+ const { assertFlavourMatchesProject } = require("../lib/flavour");
7
8
  const naming = require("../lib/naming");
8
9
  const tpl = require("../lib/templates");
9
10
  const { writeFile, report, log } = require("../lib/fs.utils");
10
11
 
11
12
  async function migration() {
12
13
  const paths = projectPaths();
14
+ assertFlavourMatchesProject(paths.root);
13
15
 
14
16
  const { tableName, migrationName } = await inquirer.prompt([
15
17
  {
@@ -4,12 +4,14 @@ const path = require("path");
4
4
  const inquirer = require("inquirer");
5
5
 
6
6
  const { projectPaths } = require("../lib/paths");
7
+ const { assertFlavourMatchesProject } = require("../lib/flavour");
7
8
  const naming = require("../lib/naming");
8
9
  const tpl = require("../lib/templates");
9
10
  const { writeFile, report, log } = require("../lib/fs.utils");
10
11
 
11
12
  async function seed() {
12
13
  const paths = projectPaths();
14
+ assertFlavourMatchesProject(paths.root);
13
15
 
14
16
  const { tableName, seedName } = await inquirer.prompt([
15
17
  {
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { Command } = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const pkg = require('../package.json');
6
+ const flavour = require('./lib/flavour');
6
7
 
7
8
  const program = new Command();
8
9
 
@@ -17,14 +18,21 @@ function run(action) {
17
18
  };
18
19
  }
19
20
 
21
+ // `nh` and `nhx` are the same program against different packages: the bin
22
+ // entrypoint picks the flavour before this module is required, and everything
23
+ // that generates code reads it from there.
24
+ const { cli, package: packageName } = flavour.current();
25
+
20
26
  program
21
- .name('nh')
22
- .description('nh — scaffolding CLI for neohm. NestJS projects (built on @neohm/nestend)')
27
+ .name(cli)
28
+ .description(
29
+ `${cli} — scaffolding CLI for neohm. NestJS projects (built on ${packageName})`,
30
+ )
23
31
  .version(pkg.version, '-v, --version', 'output the current version');
24
32
 
25
- // `nh gen` with no target flag runs the full wizard; a target flag generates
26
- // just that one artifact. -x mirrors a target onto the module's external
27
- // (API-key) surface and applies to -c, -s, -dp and -lp only.
33
+ // `gen` with no target flag runs the full wizard; a target flag generates just
34
+ // that one artifact. -x mirrors a target onto the module's external (API-key)
35
+ // surface and applies to -c, -s, -dp and -lp only.
28
36
  const GEN_TARGETS = [
29
37
  ['controller', 'controller'],
30
38
  ['service', 'service'],
@@ -50,15 +58,15 @@ program
50
58
  'after',
51
59
  `
52
60
  Examples:
53
- nh gen full wizard: module, entity, controller, service, ...
54
- nh gen -c controller only (asks module, name, route path)
55
- nh gen -s bare injectable service
56
- nh gen -dp data processor (+ its Add<Name>DataDto if missing)
57
- nh gen -lp listing processor
58
- nh gen -e entity + migration, registered in the constants/maps
59
- nh gen -j job, registered in job.constants/job.maps
60
- nh gen -x -c external controller under <module>/external/controllers
61
- nh gen -x -dp external data processor under <module>/external/libraries`,
61
+ ${cli} gen full wizard: module, entity, controller, service, ...
62
+ ${cli} gen -c controller only (asks module, name, route path)
63
+ ${cli} gen -s bare injectable service
64
+ ${cli} gen -dp data processor (+ its Add<Name>DataDto if missing)
65
+ ${cli} gen -lp listing processor
66
+ ${cli} gen -e entity + migration, registered in the constants/maps
67
+ ${cli} gen -j job, registered in job.constants/job.maps
68
+ ${cli} gen -x -c external controller under <module>/external/controllers
69
+ ${cli} gen -x -dp external data processor under <module>/external/libraries`,
62
70
  )
63
71
  .action(
64
72
  run((opts) => {
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Which package the generated code is written against.
8
+ *
9
+ * The two share every convention this CLI encodes — dot.separated filenames,
10
+ * the processor lifecycle, es6.classes barrels, the migration utility — and
11
+ * differ in what a record belongs to. `nestend` is the tenanted model: rows
12
+ * carry a `tenant_id`, access is a scope *and* a role. `nestendx` is the B2C
13
+ * one: users register themselves and hold scopes alone, so a row belongs to the
14
+ * user who created it and access is a scope.
15
+ *
16
+ * `nh` generates the first, `nhx` the second. Everything that does not generate
17
+ * code — sync, check, list, remove — is the same command either way.
18
+ */
19
+ const FLAVOURS = {
20
+ nestend: {
21
+ key: 'nestend',
22
+ cli: 'nh',
23
+ package: '@neohm/nestend',
24
+ /** Rows belong to a tenant; the scope check carries a role beside it. */
25
+ tenantScoped: true,
26
+ },
27
+ nestendx: {
28
+ key: 'nestendx',
29
+ cli: 'nhx',
30
+ package: '@neohm/nestendx',
31
+ tenantScoped: false,
32
+ },
33
+ };
34
+
35
+ let active = FLAVOURS.nestend;
36
+
37
+ /** Set by the bin entrypoint before anything else is required. */
38
+ function setFlavour(key) {
39
+ const flavour = FLAVOURS[key];
40
+ if (!flavour) throw new Error(`Unknown flavour "${key}".`);
41
+ active = flavour;
42
+ return active;
43
+ }
44
+
45
+ function current() {
46
+ return active;
47
+ }
48
+
49
+ /** The package specifier generated imports are written against. */
50
+ function pkg() {
51
+ return active.package;
52
+ }
53
+
54
+ /** The command the person actually typed, for help text and hints. */
55
+ function cli() {
56
+ return active.cli;
57
+ }
58
+
59
+ /**
60
+ * Which package a project depends on, read from its package.json. Returns null
61
+ * when it depends on neither — a project that has not installed one yet is not
62
+ * an error, it is simply not evidence either way.
63
+ */
64
+ function detectFlavour(projectRoot) {
65
+ const manifest = path.join(projectRoot, 'package.json');
66
+ let deps;
67
+ try {
68
+ const parsed = JSON.parse(fs.readFileSync(manifest, 'utf8'));
69
+ deps = { ...parsed.dependencies, ...parsed.devDependencies };
70
+ } catch {
71
+ return null;
72
+ }
73
+
74
+ // nestendx first: "@neohm/nestend" is a prefix of it, and a project can only
75
+ // sensibly depend on one.
76
+ if (deps[FLAVOURS.nestendx.package]) return FLAVOURS.nestendx.key;
77
+ if (deps[FLAVOURS.nestend.package]) return FLAVOURS.nestend.key;
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Refuse to scaffold against the wrong package. Generating tenanted code into a
83
+ * nestendx project produces files that import a package that is not there and
84
+ * columns no table has — a failure the person would meet later, in a compiler
85
+ * error, with nothing pointing back here.
86
+ */
87
+ function assertFlavourMatchesProject(projectRoot) {
88
+ const detected = detectFlavour(projectRoot);
89
+ if (!detected || detected === active.key) return;
90
+
91
+ const wanted = FLAVOURS[detected];
92
+ throw new Error(
93
+ `This project depends on ${wanted.package}, but you ran ${active.cli}, ` +
94
+ `which generates ${active.package} code.\n Use \`${wanted.cli}\` here instead.`,
95
+ );
96
+ }
97
+
98
+ module.exports = {
99
+ FLAVOURS,
100
+ setFlavour,
101
+ current,
102
+ pkg,
103
+ cli,
104
+ detectFlavour,
105
+ assertFlavourMatchesProject,
106
+ };
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ const { pkg, current } = require('./flavour');
3
4
  const {
4
5
  toWords,
5
6
  toDotCase,
@@ -11,10 +12,17 @@ const {
11
12
 
12
13
  /**
13
14
  * Code templates. All generated code imports shared base classes from the
14
- * published library `@neohm/nestend` (verified exports: CommonEntity,
15
- * CommonDataDto, CommonDataProcessor, MigrationUtility, SeederUtility, etc.)
16
- * and matches the dot.separated filename / PascalCase class conventions used
17
- * 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.
18
26
  */
19
27
 
20
28
  /**
@@ -75,8 +83,28 @@ function names(base) {
75
83
 
76
84
  function entity(base, tableName) {
77
85
  const n = names(base);
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
+
78
106
  return `import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
79
- import { CommonEntity, TenantEntity } from '@neohm/nestend';
107
+ import { CommonEntity, TenantEntity } from '${pkg()}';
80
108
  import { ${n.attributesDtoClass} } from '../dtos/${n.dot}.attributes.dto';
81
109
 
82
110
  @Entity({ name: '${tableName}' })
@@ -133,7 +161,7 @@ function controller(base, { processor = false, listProcessor = false } = {}) {
133
161
  // 'bearer') and draws the Authorize button — without the decorator
134
162
  // swagger-ui shows no padlock and never sends the Authorization header.
135
163
  `import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';`,
136
- namedImport(nestendImports, '@neohm/nestend'),
164
+ namedImport(nestendImports, pkg()),
137
165
  `import { ${n.serviceClass} } from '../services/${n.dot}.service';`,
138
166
  ];
139
167
  if (processor) {
@@ -142,13 +170,18 @@ function controller(base, { processor = false, listProcessor = false } = {}) {
142
170
  );
143
171
  }
144
172
 
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
+
145
181
  const methods = [
146
182
  ` @Get(':id')
147
183
  async get${n.pascal}(@Param() params: CommonParamsDto) {
148
- const { user } = await this.authService.validateAccess(
149
- 'add-scope',
150
- 'add-role',
151
- );
184
+ const { user } = ${gate(' ')};
152
185
 
153
186
  return this.${svcProp}.get(user, params.id);
154
187
  }`,
@@ -157,7 +190,7 @@ function controller(base, { processor = false, listProcessor = false } = {}) {
157
190
  if (processor) {
158
191
  methods.push(` @Post()
159
192
  async upsert${n.pascal}(@Body() body: ${n.dtoClass}) {
160
- await this.authService.validateAccess('add-scope', 'add-role');
193
+ ${tenanted ? `await this.authService.validateAccess('add-scope', 'add-role');` : `await this.authService.validateAccess('add-scope');`}
161
194
 
162
195
  return this.${svcProp}.upsert(body);
163
196
  }`);
@@ -166,17 +199,19 @@ function controller(base, { processor = false, listProcessor = false } = {}) {
166
199
  if (listProcessor) {
167
200
  methods.push(` @Post('list')
168
201
  async list(@Body() body: CommonListFilterDto) {
169
- const { user } = await this.authService.validateAccess(
170
- 'add-scope',
171
- 'add-role',
172
- );
202
+ const { user } = ${gate(' ')};
203
+
173
204
  return this.${svcProp}.list(user, body);
174
205
  }`);
175
206
  }
176
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
+
177
212
  return `${imports.join('\n')}
178
213
 
179
- // TODO: replace 'add-scope' / 'add-role' with this module's scope and role.
214
+ ${gateTodo}
180
215
  @ApiTags('${n.kebab}')
181
216
  @ApiBearerAuth()
182
217
  @Controller('api/v1/${n.kebab}')
@@ -210,7 +245,7 @@ function service(base, { processor = false, listProcessor = false } = {}) {
210
245
  nestendImports.push('NotFoundException', 'UserEntity');
211
246
 
212
247
  const imports = [
213
- namedImport(nestendImports, '@neohm/nestend'),
248
+ namedImport(nestendImports, pkg()),
214
249
  `import { Injectable } from '@nestjs/common';`,
215
250
  ];
216
251
  if (listProcessor) {
@@ -235,11 +270,15 @@ function service(base, { processor = false, listProcessor = false } = {}) {
235
270
  );
236
271
  }
237
272
 
238
- // The tenant scope is part of the lookup, not a check after it: a row
239
- // belonging to another tenant must read as "not found", never as "forbidden".
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';
240
279
  members.push(` async get(user: UserEntity, id: string) {
241
280
  const ${n.camel} = await ${n.entityClass}.findOne({
242
- where: { id, tenant_id: user.organization_id },
281
+ where: { id, ${owner} },
243
282
  });
244
283
  if (!${n.camel}) throw new NotFoundException('Invalid ${label} id');
245
284
 
@@ -292,7 +331,7 @@ function bareController(base, routePath) {
292
331
  const n = names(base);
293
332
  return `import { Controller, Get } from '@nestjs/common';
294
333
  import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
295
- import { AuthService } from '@neohm/nestend';
334
+ import { AuthService } from '${pkg()}';
296
335
 
297
336
  @ApiTags('${routePath}')
298
337
  @ApiBearerAuth()
@@ -316,7 +355,7 @@ function bareExternalController(base, routePath) {
316
355
  const n = names(base);
317
356
  return `import { Controller, Get } from '@nestjs/common';
318
357
  import { ApiSecurity, ApiTags } from '@nestjs/swagger';
319
- import { ApiKeyService } from '@neohm/nestend';
358
+ import { ApiKeyService } from '${pkg()}';
320
359
 
321
360
  @ApiTags('external-${routePath} (Api key)')
322
361
  @ApiSecurity('api-key')
@@ -348,7 +387,7 @@ export class ${cls} {}
348
387
  */
349
388
  function additionalJob(base) {
350
389
  const n = names(base);
351
- return `import { CommonJob, QueueService } from '@neohm/nestend';
390
+ return `import { CommonJob, QueueService } from '${pkg()}';
352
391
  import { Injectable } from '@nestjs/common';
353
392
  import { JobConstants } from '../../constants/job.constants';
354
393
 
@@ -367,7 +406,7 @@ export class ${n.jobClass} extends CommonJob {
367
406
 
368
407
  function dto(base) {
369
408
  const n = names(base);
370
- return `import { CommonDataDto } from '@neohm/nestend';
409
+ return `import { CommonDataDto } from '${pkg()}';
371
410
 
372
411
  export class ${n.dtoClass} extends CommonDataDto {
373
412
  // TODO: declare validated payload fields for ${n.pascal}.
@@ -381,7 +420,7 @@ function processor(base, { external = false } = {}) {
381
420
  // reach back into the module's dtos/ gains a segment.
382
421
  const up = external ? '../..' : '..';
383
422
  const cls = external ? n.externalProcessorClass : n.processorClass;
384
- return `import { CommonDataProcessor } from '@neohm/nestend';
423
+ return `import { CommonDataProcessor } from '${pkg()}';
385
424
  import { ${n.dtoClass} } from '${up}/dtos/add.${n.dot}.data.dto';
386
425
 
387
426
  export class ${cls} extends CommonDataProcessor {
@@ -418,7 +457,7 @@ function listProcessor(base, tableName, { external = false } = {}) {
418
457
  ListFilterService,
419
458
  SortDirection,
420
459
  UserEntity,
421
- } from '@neohm/nestend';
460
+ } from '${pkg()}';
422
461
 
423
462
  /**
424
463
  * Lists / filters ${n.pascal}.
@@ -474,8 +513,13 @@ export class ${cls} extends CommonListProcessor {
474
513
  }
475
514
 
476
515
  protected applyCustomPredicates() {
477
- // Filter for the caller's tenant/organization
478
- this.predicates.add('a.tenant_id = :tenant_id', this.user.organization_id);
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
+ }
479
523
  }
480
524
  }
481
525
  `;
@@ -483,7 +527,7 @@ export class ${cls} extends CommonListProcessor {
483
527
 
484
528
  function subscriber(base) {
485
529
  const n = names(base);
486
- return `import { CommonSubscriber } from '@neohm/nestend';
530
+ return `import { CommonSubscriber } from '${pkg()}';
487
531
  import { EventSubscriber } from 'typeorm';
488
532
  import { ${n.entityClass} } from '../entities/${n.dot}.entity';
489
533
  import { DataSource } from 'typeorm';
@@ -508,7 +552,7 @@ export class ${n.subscriberClass} extends CommonSubscriber<${n.entityClass}> {
508
552
 
509
553
  function job(base) {
510
554
  const n = names(base);
511
- return `import { CommonJob, QueueService, DatabaseEventDto } from '@neohm/nestend';
555
+ return `import { CommonJob, QueueService, DatabaseEventDto } from '${pkg()}';
512
556
  import { Injectable } from '@nestjs/common';
513
557
  import { JobConstants } from '../../constants/job.constants';
514
558
  import { ${n.entityClass} } from '../entities/${n.dot}.entity';
@@ -534,7 +578,7 @@ export class ${n.jobClass} extends CommonJob {
534
578
  function genMigration(base, tableName, timestamp) {
535
579
  const n = names(base);
536
580
  const className = migrationClassName(n.migrationName, timestamp);
537
- return `import { MigrationUtility } from '@neohm/nestend';
581
+ return `import { MigrationUtility } from '${pkg()}';
538
582
 
539
583
  export class ${className} extends MigrationUtility {
540
584
  constructor() {
@@ -545,8 +589,11 @@ export class ${className} extends MigrationUtility {
545
589
  process() {
546
590
  this.rootPrimary();
547
591
 
548
- this.foreign({ name: 'tenant_id', foreignTable: 'sys_tenants' });
549
- this.foreign({ name: 'created_by', foreignTable: 'sys_users' });
592
+ ${
593
+ current().tenantScoped
594
+ ? ` this.foreign({ name: 'tenant_id', foreignTable: 'sys_tenants' });\n`
595
+ : ''
596
+ } this.foreign({ name: 'created_by', foreignTable: 'sys_users' });
550
597
  this.foreign({ name: 'updated_by', foreignTable: 'sys_users' });
551
598
 
552
599
  // TODO: add columns matching ${n.entityClass}.
@@ -554,8 +601,7 @@ export class ${className} extends MigrationUtility {
554
601
  this.json('attributes');
555
602
  this.timestamps();
556
603
 
557
- this.index('tenant_id');
558
- this.index('created_by');
604
+ ${current().tenantScoped ? ` this.index('tenant_id');\n` : ''} this.index('created_by');
559
605
  this.index('updated_by');
560
606
  }
561
607
  }
@@ -565,7 +611,7 @@ export class ${className} extends MigrationUtility {
565
611
  /** Standalone migration (`nh migration`). */
566
612
  function migration(migrationName, tableName, timestamp) {
567
613
  const className = migrationClassName(migrationName, timestamp);
568
- return `import { MigrationUtility } from '@neohm/nestend';
614
+ return `import { MigrationUtility } from '${pkg()}';
569
615
 
570
616
  export class ${className} extends MigrationUtility {
571
617
  constructor() {
@@ -585,7 +631,7 @@ export class ${className} extends MigrationUtility {
585
631
  /** Seed file (`nh seed`). */
586
632
  function seed(seedName, tableName, timestamp) {
587
633
  const className = `${toPascalCase(seedName)}${timestamp}`;
588
- return `import { SeederUtility } from '@neohm/nestend';
634
+ return `import { SeederUtility } from '${pkg()}';
589
635
 
590
636
  export class ${className} extends SeederUtility {
591
637
  constructor() {
@@ -604,7 +650,7 @@ export class ${className} extends SeederUtility {
604
650
  /** Fresh src/constants/job.constants.ts (first entry included). */
605
651
  function jobConstantsFile(base) {
606
652
  const n = names(base);
607
- return `import { baseJobConstants } from '@neohm/nestend';
653
+ return `import { baseJobConstants } from '${pkg()}';
608
654
 
609
655
  export const JobConstants = {
610
656
  ...baseJobConstants,
@@ -616,7 +662,7 @@ export const JobConstants = {
616
662
  /** Fresh src/constants/entity.constants.ts (first entry included). */
617
663
  function entityConstantsFile(base) {
618
664
  const n = names(base);
619
- return `import { baseEntityConstants } from '@neohm/nestend';
665
+ return `import { baseEntityConstants } from '${pkg()}';
620
666
 
621
667
  export const EntityConstants = {
622
668
  ...baseEntityConstants,
@@ -628,7 +674,7 @@ export const EntityConstants = {
628
674
  /** Fresh src/constants/job.maps.ts (first entry included). */
629
675
  function jobMapsFile(base, moduleDot, constants) {
630
676
  const n = names(base);
631
- return `import { baseJobMaps } from '@neohm/nestend';
677
+ return `import { baseJobMaps } from '${pkg()}';
632
678
  import { ${n.jobClass} } from '../${moduleDot}/jobs/${n.dot}.job';
633
679
  import { ${constants.exportName} } from '${constants.importPath}';
634
680
 
@@ -642,7 +688,7 @@ export const JobMaps = {
642
688
  /** Fresh src/constants/entity.maps.ts (first entry included). */
643
689
  function entityMapsFile(base, moduleDot, constants) {
644
690
  const n = names(base);
645
- return `import { baseEntityMaps } from '@neohm/nestend';
691
+ return `import { baseEntityMaps } from '${pkg()}';
646
692
  import { ${n.entityClass} } from '../${moduleDot}/entities/${n.dot}.entity';
647
693
  import { ${constants.exportName} } from '${constants.importPath}';
648
694
 
@@ -674,7 +720,7 @@ import {
674
720
  QueueModule,
675
721
  AuthModule,
676
722
  SystemPropertyModule,
677
- } from '@neohm/nestend';
723
+ } from '${pkg()}';
678
724
  import { es6Classes } from './es6.classes';
679
725
 
680
726
  @Module({