@cosider.construction/eapp 1.0.4 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosider.construction/eapp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Project-local CLI for Electron + Vue ERP projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,16 +1,16 @@
1
1
  import path from 'path';
2
2
  import chalk from 'chalk';
3
3
  import { parseEntityPath, ipcRoute, toUpper, toLower, toPascal } from '../utils/naming.js';
4
- import { writeFile, writeJson, exists, assertProjectRoot, loadEntityRegistry } from '../utils/fs.js';
4
+ import { writeFile, writeJson, exists, assertProjectRoot } from '../utils/fs.js';
5
5
  import { log } from '../utils/log.js';
6
- import { textInput, optionsLine, fieldsTable, radioLine } from '../tui/engine.js';
6
+ import { textInput, optionsLine, fieldsTable } from '../tui/engine.js';
7
7
 
8
8
  // ─── Default fields ───────────────────────────────────────────────────────────
9
9
 
10
10
  function defaultFillableRows(entityName) {
11
11
  return [
12
- { field: `${entityName}_id`, type: 'int', values: '', pk: true, fk: false, group: '' },
13
- { field: `${entityName}_lib`, type: 'varchar', values: '', pk: false, fk: false, group: '' },
12
+ { field: `${entityName}_id`, type: 'int', values: '', pk: true, fk: false, group: '' },
13
+ { field: `${entityName}_lib`, type: 'varchar', values: '', pk: false, fk: false, group: '' },
14
14
  ];
15
15
  }
16
16
 
@@ -29,11 +29,14 @@ const AUDIT_WIDTHS = { field: 20, type: 10 };
29
29
  // ─── Code generators ──────────────────────────────────────────────────────────
30
30
 
31
31
  function buildModelJs({ module, entity, table, schema, fields, auditFields, pkField }) {
32
- const TABLE = toUpper(table || entity);
33
- const dbAlias = schema === 'dbo' ? 'dbo' : schema;
34
- const importPath = module === 'shared' ? '../../../engine/db.js' : '../../engine/db.js';
32
+ const TABLE = toUpper(table || entity);
33
+ const dbAlias = schema === 'dbo' ? 'dbo' : schema;
34
+ // import path: shared entities are 3 levels deep (shared/ENTITY/model.js engine/db.js)
35
+ // module entities are also 3 levels deep (modules/MOD/ENTITY/model.js → engine/db.js)
36
+ const importPath = module === 'shared'
37
+ ? '../../../engine/db.js'
38
+ : '../../../engine/db.js';
35
39
 
36
- const allFields = [...fields, ...auditFields];
37
40
  const fillable = fields.filter(f => !f.pk);
38
41
  const insertCols = fillable.map(f => f.field).join(', ');
39
42
  const insertVals = fillable.map(f => `@${f.field}`).join(', ');
@@ -44,6 +47,7 @@ function buildModelJs({ module, entity, table, schema, fields, auditFields, pkFi
44
47
  // Table : ${TABLE}
45
48
  // Schema : ${schema}
46
49
  // PK : ${pkField}
50
+ // The scoped DB instance automatically prepends [database].[${schema}] to table names.
47
51
 
48
52
  export async function getAll() {
49
53
  return db.query('SELECT * FROM ${TABLE}');
@@ -56,20 +60,20 @@ export async function getById(${pkField}) {
56
60
  export async function add(data) {
57
61
  const { ${fillable.map(f => f.field).join(', ') || '/* fields */'} } = data;
58
62
  return db.execute(
59
- 'INSERT INTO ${TABLE} (${insertCols || '/* cols */'}) VALUES (${insertVals || '/* vals */'})',
63
+ \`INSERT INTO ${TABLE} (${insertCols || '/* cols */'}) VALUES (${insertVals || '/* vals */'})\`,
60
64
  data
61
65
  );
62
66
  }
63
67
 
64
68
  export async function update(${pkField}, data) {
65
69
  return db.execute(
66
- 'UPDATE ${TABLE} SET ${updateSet || '/* set */'} WHERE ${pkField} = @${pkField}',
70
+ \`UPDATE ${TABLE} SET ${updateSet || '/* set clause */'} WHERE ${pkField} = @${pkField}\`,
67
71
  { ...data, ${pkField} }
68
72
  );
69
73
  }
70
74
 
71
75
  export async function remove(${pkField}) {
72
- return db.execute('DELETE FROM ${TABLE} WHERE ${pkField} = @${pkField}', { ${pkField} });
76
+ return db.execute(\`DELETE FROM ${TABLE} WHERE ${pkField} = @${pkField}\`, { ${pkField} });
73
77
  }
74
78
  `;
75
79
  }
@@ -77,15 +81,15 @@ export async function remove(${pkField}) {
77
81
  function buildControllerJs({ pkField }) {
78
82
  return `import * as model from './model.js';
79
83
 
80
- export async function getAll() { return model.getAll(); }
81
- export async function getById(${pkField}) { return model.getById(${pkField}); }
82
- export async function add(data) { return model.add(data); }
83
- export async function update(${pkField}, data) { return model.update(${pkField}, data); }
84
- export async function remove(${pkField}) { return model.remove(${pkField}); }
84
+ export async function getAll() { return model.getAll(); }
85
+ export async function getById(${pkField}) { return model.getById(${pkField}); }
86
+ export async function add(data) { return model.add(data); }
87
+ export async function update(${pkField}, data) { return model.update(${pkField}, data); }
88
+ export async function remove(${pkField}) { return model.remove(${pkField}); }
85
89
  `;
86
90
  }
87
91
 
88
- function buildIpcJson({ module, entity, table, schema }) {
92
+ function buildIpcJson({ module, entity, schema }) {
89
93
  const route = ipcRoute(module, entity);
90
94
  const mod = module === 'shared' ? 'shared' : module;
91
95
  const ent = toLower(entity);
@@ -115,51 +119,69 @@ export default new ${ClassName}();
115
119
  `;
116
120
  }
117
121
 
118
- function buildMetaJson({ module, entity, schema, fields, auditFields, pkField }) {
119
- return JSON.stringify({ module, entity, schema, fields, auditFields, pkField }, null, 2);
122
+ function buildMetaJson({ module, entity, table, schema, fields, auditFields, pkField }) {
123
+ return JSON.stringify({ module, entity, table, schema, fields, auditFields, pkField }, null, 2);
120
124
  }
121
125
 
122
126
  // ─── Write entity files ───────────────────────────────────────────────────────
123
127
 
124
128
  async function writeEntity({ module, entity, table, schema, fields, auditFields, pkField, opts, dryRun }) {
125
129
  const cwd = process.cwd();
126
- // If user typed POLE.dbo it means entity=pole, schema=dbo — show confirmation
127
- const displayPath = `${module}/${entity}` + (parsedTable !== entity ? `:${parsedTable}` : '') + `.${schema}`;
130
+
131
+ // Display path
132
+ const displayPath = `${module}/${entity}` +
133
+ (table !== entity ? `:${table}` : '') +
134
+ `.${schema}`;
128
135
  console.log('');
129
136
  console.log(chalk.bold.white(' Entity ') + chalk.cyan(displayPath));
130
- if (parsedTable !== entity) {
131
- console.log(chalk.dim(` table name in DB will be: ${toUpper(parsedTable)}`));
137
+ if (table !== entity) {
138
+ console.log(chalk.dim(` DB table name: ${toUpper(table)}`));
132
139
  }
133
140
 
134
- const backendBase = module === 'shared'
141
+ const backendBase = module === 'shared'
135
142
  ? path.join(cwd, 'src', 'backend', 'shared', toUpper(entity))
136
143
  : path.join(cwd, 'src', 'backend', 'modules', toUpper(module), toUpper(entity));
137
144
 
138
145
  const o = opts || { model: true, controller: true, crud: true, api: true, ipc: true };
139
146
 
140
147
  if (o.model) {
141
- await writeFile(path.join(backendBase, 'model.js'),
142
- buildModelJs({ module, entity, table: table || entity, schema, fields, auditFields, pkField }), { dryRun });
148
+ await writeFile(
149
+ path.join(backendBase, 'model.js'),
150
+ buildModelJs({ module, entity, table, schema, fields, auditFields, pkField }),
151
+ { dryRun }
152
+ );
143
153
  }
144
154
  if (o.controller) {
145
- await writeFile(path.join(backendBase, 'controller.js'),
146
- buildControllerJs({ pkField }), { dryRun });
155
+ await writeFile(
156
+ path.join(backendBase, 'controller.js'),
157
+ buildControllerJs({ pkField }),
158
+ { dryRun }
159
+ );
147
160
  }
148
161
  if (o.ipc) {
149
- await writeFile(path.join(backendBase, 'ipc.json'),
150
- buildIpcJson({ module, entity, table: table || entity, schema }), { dryRun });
162
+ await writeFile(
163
+ path.join(backendBase, 'ipc.json'),
164
+ buildIpcJson({ module, entity, schema }),
165
+ { dryRun }
166
+ );
151
167
  }
152
- // Always write meta so registry can read it
153
- await writeJson(path.join(backendBase, 'entity.meta.json'),
154
- JSON.parse(buildMetaJson({ module, entity, schema, fields, auditFields, pkField })), { dryRun });
168
+
169
+ // Always write meta — entity registry reads this
170
+ await writeJson(
171
+ path.join(backendBase, 'entity.meta.json'),
172
+ { module, entity, table, schema, fields, auditFields, pkField },
173
+ { dryRun }
174
+ );
155
175
 
156
176
  if (o.api && o.ipc) {
157
- const apiPath = path.join(cwd, 'src', 'frontend', 'api', 'modules',
158
- `${toLower(module)}.${toLower(entity)}.api.js`);
177
+ const apiPath = path.join(
178
+ cwd, 'src', 'frontend', 'api', 'modules',
179
+ `${toLower(module)}.${toLower(entity)}.api.js`
180
+ );
159
181
  await writeFile(apiPath, buildApiJs({ module, entity }), { dryRun });
160
182
  }
161
183
 
162
- log.success(`Entity ${chalk.cyan(module + '/' + entity + '.' + schema)} generated.`);
184
+ log.success(`Entity ${chalk.cyan(displayPath)} generated.`);
163
185
  }
164
186
 
165
187
  // ─── Main generator ───────────────────────────────────────────────────────────
@@ -175,10 +197,12 @@ export async function generateEntity(args, rawArgs) {
175
197
  console.log('');
176
198
  console.log(chalk.bold.cyan(' eapp entity') + chalk.dim(' <module/entity.schema>'));
177
199
  console.log('');
178
- console.log(chalk.dim(' /employee → shared/employee.dbo'));
200
+ console.log(chalk.dim(' /employee → shared, employee, dbo'));
179
201
  console.log(chalk.dim(' rh/employee → rh module, rh schema'));
180
202
  console.log(chalk.dim(' rh/employee.dbo → rh module, dbo schema'));
181
- console.log(chalk.dim(' rh/employee.hr → rh module, custom schema hr'));
203
+ console.log(chalk.dim(' rh/employee.hr → rh module, custom schema'));
204
+ console.log(chalk.dim(' POLE.dbo → shared, pole entity, dbo schema'));
205
+ console.log(chalk.dim(' rh/agent:AGENTX → rh module, entity=agent, table=AGENTX'));
182
206
  console.log('');
183
207
  }
184
208
 
@@ -194,38 +218,27 @@ export async function generateEntity(args, rawArgs) {
194
218
  catch (e) { log.error(e.message); process.exit(1); }
195
219
  }
196
220
 
197
- const { module, entity, table: parsedTable, schema } = parsed;
198
- const pkField = `${entity}_id`;
199
- let tableOverride = parsedTable !== entity ? parsedTable : null;
221
+ const { module, entity, table, schema } = parsed;
222
+ const defaultPk = `${entity}_id`;
200
223
 
201
224
  // ── Check if exists ───────────────────────────────────────────────────────
202
225
  const cwd = process.cwd();
203
- // If user typed POLE.dbo it means entity=pole, schema=dbo show confirmation
204
- const displayPath = `${module}/${entity}` + (parsedTable !== entity ? `:${parsedTable}` : '') + `.${schema}`;
205
- console.log('');
206
- console.log(chalk.bold.white(' Entity ') + chalk.cyan(displayPath));
207
- if (parsedTable !== entity) {
208
- console.log(chalk.dim(` table name in DB will be: ${toUpper(parsedTable)}`));
209
- }
210
-
211
- const backendBase = module === 'shared'
226
+ const backendBase = module === 'shared'
212
227
  ? path.join(cwd, 'src', 'backend', 'shared', toUpper(entity))
213
228
  : path.join(cwd, 'src', 'backend', 'modules', toUpper(module), toUpper(entity));
214
229
 
215
- const entityExists = await exists(backendBase);
216
- if (entityExists) {
217
- log.info(`Entity ${chalk.cyan(module + '/' + entity)} already exists.`);
218
- log.dim(' Regenerating files — existing logic will be preserved in model/controller.');
230
+ if (await exists(backendBase)) {
231
+ log.info(`Entity ${chalk.cyan(module + '/' + entity)} already exists — regenerating.`);
219
232
  }
220
233
 
221
- // ── --default: skip all prompts ───────────────────────────────────────────
234
+ // ── --default: instant generation ────────────────────────────────────────
222
235
  if (useDefault) {
223
236
  await writeEntity({
224
- module, entity, table: parsedTable || entity, schema,
225
- fields: defaultFillableRows(entity),
237
+ module, entity, table, schema,
238
+ fields: defaultFillableRows(entity),
226
239
  auditFields: DEFAULT_AUDIT_ROWS,
227
- pkField,
228
- opts: { model: true, controller: true, crud: true, api: true, ipc: true },
240
+ pkField: defaultPk,
241
+ opts: { model: true, controller: true, crud: true, api: true, ipc: true },
229
242
  dryRun,
230
243
  });
231
244
  return;
@@ -242,7 +255,7 @@ export async function generateEntity(args, rawArgs) {
242
255
  { key: 'ipc', label: 'IPC', default: true },
243
256
  ]);
244
257
 
245
- // ── Fillable fields ───────────────────────────────────────────────────────
258
+ // ── Fillable fields table ─────────────────────────────────────────────────
246
259
  const fillableRows = await fieldsTable(
247
260
  defaultFillableRows(entity),
248
261
  FILLABLE_COLS,
@@ -250,23 +263,23 @@ export async function generateEntity(args, rawArgs) {
250
263
  'Fillable Fields (+ add - remove ← → cols ↑ ↓ rows space pk/fk enter done)'
251
264
  );
252
265
 
253
- // ── Audit fields ──────────────────────────────────────────────────────────
266
+ // ── Audit fields table ────────────────────────────────────────────────────
254
267
  const auditRows = await fieldsTable(
255
268
  [...DEFAULT_AUDIT_ROWS],
256
269
  AUDIT_COLS,
257
270
  AUDIT_WIDTHS,
258
- 'Audit Fields (non-fillable) (- to remove unwanted)'
271
+ 'Audit Fields (non-fillable) (- to remove unwanted enter done)'
259
272
  );
260
273
 
261
- // Find PK
262
- const pkRow = fillableRows.find(r => r.pk);
263
- const resolvedPk = pkRow ? pkRow.field : pkField;
274
+ // Resolve PK from fields
275
+ const pkRow = fillableRows.find(r => r.pk);
276
+ const resolvedPk = pkRow ? pkRow.field : defaultPk;
264
277
 
265
278
  await writeEntity({
266
- module, entity, table: tableOverride || entity, schema,
267
- fields: fillableRows,
279
+ module, entity, table, schema,
280
+ fields: fillableRows,
268
281
  auditFields: auditRows,
269
- pkField: resolvedPk,
282
+ pkField: resolvedPk,
270
283
  opts,
271
284
  dryRun,
272
285
  });
@@ -67,8 +67,8 @@ async function editViewMenu(menuFile, existing) {
67
67
  console.log('');
68
68
  const rows = await fieldsTable(
69
69
  existing_items,
70
- ['label', 'route', 'icon', 'parent', 'permission'],
71
- { label: 16, route: 20, icon: 6, parent: 14, permission: 20 },
70
+ ['label', 'route', 'icon', 'path', 'dot', 'nbr'],
71
+ { label: 16, route: 20, icon: 5, path: 14, dot: 5, nbr: 6 },
72
72
  'hMenu items (parent = dropdown group label, empty = top level)'
73
73
  );
74
74
  const menu = { route: existing?.route || '', hMenu: { style: 'classic', items: nestItems(rows) } };
@@ -15,93 +15,89 @@ export const isValidName = (s) => /^[a-zA-Z0-9_-]+$/.test(s);
15
15
  /**
16
16
  * Parse "module/entity.schema" format
17
17
  *
18
- * /employee { module: 'shared', entity: 'employee', schema: 'dbo' }
19
- * rh/employee { module: 'rh', entity: 'employee', schema: 'rh' }
20
- * rh/employee.dbo { module: 'rh', entity: 'employee', schema: 'dbo' }
21
- * rh/employee.hr { module: 'rh', entity: 'employee', schema: 'hr' }
22
- * rh/employee. { module: 'rh', entity: 'employee', schema: 'rh' } (dot alone = module)
23
- * employee { module: 'shared', entity: 'employee', schema: 'dbo' } (no slash = shared)
18
+ * /employee → shared, employee, dbo
19
+ * rh/employee → rh, employee, rh
20
+ * rh/employee.dbo → rh, employee, dbo
21
+ * rh/employee.hr → rh, employee, hr
22
+ * rh/employee. → rh, employee, rh (dot alone = module schema)
23
+ * employee → shared, employee, dbo
24
+ * POLE.dbo → shared, pole, dbo (uppercase entity with schema)
25
+ * rh/agent:AGENTX.dbo → rh, agent, dbo, table=agentx
26
+ *
27
+ * OLD compat dot notation: rh.employee → rh/employee
28
+ * BUT: POLE.dbo must NOT rewrite — left has uppercase so it's not a module name
24
29
  */
25
30
  export function parseEntityPath(input) {
26
31
  if (!input) throw new Error('No entity path provided.');
27
32
 
28
33
  let raw = input.trim();
29
34
 
30
- // Handle dot notation ONLY when the part before the dot could be a module name
31
- // and the part after is NOT a schema keyword (dbo, etc.) — i.e. "rh.employee" → "rh/employee"
32
- // BUT "POLE.dbo" must NOT be rewritten — POLE is the entity, dbo is the schema.
33
- // Rule: rewrite "x.y" only when x contains no uppercase and y is not a known schema.
34
- const SCHEMA_NAMES = new Set(['dbo', 'dbo2', 'hr', 'rh', 'stk', 'fin', 'crm', 'adm', 'sec', 'shared']);
35
+ // ── Old "module.entity" compat (only if left is strictly lowercase) ────────
35
36
  if (!raw.includes('/') && raw.includes('.') && raw.split('.').length === 2) {
36
37
  const [left, right] = raw.split('.');
37
- const looksLikeSchema = SCHEMA_NAMES.has(right.toLowerCase()) || /^[a-z]+$/.test(right);
38
- const looksLikeModule = /^[a-z][a-z0-9_-]*$/.test(left);
39
- // Only rewrite if left looks like a lowercase module AND right does NOT look like a schema
40
- if (looksLikeModule && !looksLikeSchema) {
38
+ const leftIsLowercaseModule = /^[a-z][a-z0-9_-]*$/.test(left);
39
+ const rightIsSchemaKeyword = /^(dbo\d*|[a-z]{1,6})$/.test(right.toLowerCase());
40
+ if (leftIsLowercaseModule && !rightIsSchemaKeyword) {
41
41
  raw = `${left}/${right}`;
42
42
  }
43
+ // Otherwise treat as entity.schema without module (e.g. POLE.dbo, employee.dbo)
43
44
  }
44
45
 
45
- // No slash at all shared
46
+ // ── No slash shared module ───────────────────────────────────────────────
46
47
  if (!raw.includes('/')) {
47
- const _e = toLower(raw);
48
- return { module: 'shared', entity: _e, table: _e, schema: 'dbo' };
48
+ const dotPos = raw.indexOf('.');
49
+ let ent, sch;
50
+ if (dotPos !== -1) {
51
+ ent = toLower(raw.slice(0, dotPos));
52
+ sch = raw.slice(dotPos + 1).toLowerCase() || 'dbo';
53
+ } else {
54
+ ent = toLower(raw);
55
+ sch = 'dbo';
56
+ }
57
+ if (!isValidName(ent)) throw new Error(`Invalid entity name "${ent}".`);
58
+ return { module: 'shared', entity: ent, table: ent, schema: sch };
49
59
  }
50
60
 
51
- // Split on /
52
- const slashIdx = raw.indexOf('/');
53
- const modulePart = raw.slice(0, slashIdx); // may be empty → shared
54
- const rest = raw.slice(slashIdx + 1); // entity.schema or entity
55
-
56
- const module = modulePart === '' ? 'shared' : toLower(modulePart);
57
-
58
- // Split entity and schema on .
59
- const dotIdx = rest.indexOf('.');
60
- let entity, schema;
61
+ // ── Has slash ─────────────────────────────────────────────────────────────
62
+ const slashIdx = raw.indexOf('/');
63
+ const modPart = raw.slice(0, slashIdx);
64
+ const rest = raw.slice(slashIdx + 1);
65
+ const module = modPart === '' ? 'shared' : toLower(modPart);
61
66
 
62
- // Also parse optional table alias: entity:tableName
63
- // e.g. rh/agent:agentx.dbo → entity=agent, table=agentx, schema=dbo
67
+ // ── Optional table alias: entity:TABLE.schema ──────────────────────────────
64
68
  let tableAlias = null;
65
- if (rest.includes(':')) {
66
- const colonIdx = rest.indexOf(':');
67
- tableAlias = rest.slice(colonIdx + 1).split('.')[0]; // strip schema part
68
- // rewrite rest without the alias for further parsing
69
- // keep the .schema part after the alias if any
70
- const afterColon = rest.slice(colonIdx + 1);
71
- const schemaInAlias = afterColon.includes('.') ? afterColon.slice(afterColon.indexOf('.')) : '';
72
- // rest becomes "entity.schema"
73
- // rest = rest.slice(0, colonIdx) + schemaInAlias; — handled below
69
+ let cleanRest = rest;
70
+ const colonPos = rest.indexOf(':');
71
+ if (colonPos !== -1) {
72
+ const beforeColon = rest.slice(0, colonPos);
73
+ const afterColon = rest.slice(colonPos + 1);
74
+ const aliasDot = afterColon.indexOf('.');
75
+ tableAlias = toLower(aliasDot !== -1 ? afterColon.slice(0, aliasDot) : afterColon);
76
+ const schemaRest = aliasDot !== -1 ? afterColon.slice(aliasDot) : '';
77
+ cleanRest = beforeColon + schemaRest;
74
78
  }
75
- const cleanRest = rest.replace(/:([^.]+)/, ''); // strip :alias for entity/schema parsing
76
79
 
77
- const dotIdx2 = cleanRest.indexOf('.');
80
+ // ── Entity and schema ──────────────────────────────────────────────────────
81
+ const dotPos = cleanRest.indexOf('.');
78
82
  let entity, schema;
79
- if (dotIdx2 === -1) {
83
+
84
+ if (dotPos === -1) {
80
85
  entity = toLower(cleanRest);
81
86
  schema = module === 'shared' ? 'dbo' : module;
82
87
  } else {
83
- entity = toLower(cleanRest.slice(0, dotIdx2));
84
- const schemaPart = cleanRest.slice(dotIdx2 + 1);
85
- schema = schemaPart === '' ? (module === 'shared' ? 'dbo' : module) : toLower(schemaPart);
88
+ entity = toLower(cleanRest.slice(0, dotPos));
89
+ const schemaPart = cleanRest.slice(dotPos + 1);
90
+ schema = schemaPart === '' ? (module === 'shared' ? 'dbo' : module) : toLower(schemaPart);
86
91
  }
87
- const table = tableAlias ? toLower(tableAlias) : entity;
88
- // (keep legacy variable for code below that uses dotIdx)
89
- const dotIdx = dotIdx2;
90
92
 
91
- if (!isValidName(entity)) {
92
- throw new Error(`Invalid entity name "${entity}". Use alphanumeric, hyphens, underscores only.`);
93
- }
94
- if (module !== 'shared' && !isValidName(module)) {
95
- throw new Error(`Invalid module name "${module}".`);
96
- }
93
+ if (!isValidName(entity)) throw new Error(`Invalid entity name "${entity}".`);
94
+ if (module !== 'shared' && !isValidName(module)) throw new Error(`Invalid module name "${module}".`);
97
95
 
98
- return { module, entity, table: table || entity, schema };
96
+ return { module, entity, table: tableAlias || entity, schema };
99
97
  }
100
98
 
101
99
  /**
102
- * Derive IPC route
103
- * shared + client → shared.CLIENT
104
- * rh + employee → modules.RH.EMPLOYEE
100
+ * IPC route: shared.CLIENT or modules.RH.EMPLOYEE
105
101
  */
106
102
  export function ipcRoute(module, entity) {
107
103
  if (module === 'shared') return `shared.${toUpper(entity)}`;
@@ -109,11 +105,9 @@ export function ipcRoute(module, entity) {
109
105
  }
110
106
 
111
107
  /**
112
- * Derive the backend directory path for an entity
108
+ * Backend directory for an entity
113
109
  */
114
110
  export function backendEntityDir(cwd, module, entity) {
115
- if (module === 'shared') {
116
- return `${cwd}/src/backend/shared/${toUpper(entity)}`;
117
- }
111
+ if (module === 'shared') return `${cwd}/src/backend/shared/${toUpper(entity)}`;
118
112
  return `${cwd}/src/backend/modules/${toUpper(module)}/${toUpper(entity)}`;
119
113
  }