@zintrust/core 0.4.9 → 0.4.11

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": "@zintrust/core",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -1,4 +1,4 @@
1
- import type { IRouter } from '../../routes/Router';
1
+ import { type IRouter } from '../../routes/Router';
2
2
  export declare const isCompiledJsModule: () => boolean;
3
3
  export declare const tryImportOptional: <T>(modulePath: string) => Promise<T | undefined>;
4
4
  export declare const tryImportOptionalR: <T>(modulePath: string) => Promise<T | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"registerRoute.d.ts","sourceRoot":"","sources":["../../../../src/boot/registry/registerRoute.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAUnD,eAAO,MAAM,kBAAkB,QAAO,OAKrC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,CAAC,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAMpF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,CAAC,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAOrF,CAAC;AAuFF,eAAO,MAAM,oBAAoB,GAC/B,kBAAkB,MAAM,EACxB,QAAQ,OAAO,KACd,OAAO,CAAC,IAAI,CAoBd,CAAC"}
1
+ {"version":3,"file":"registerRoute.d.ts","sourceRoot":"","sources":["../../../../src/boot/registry/registerRoute.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAW3D,eAAO,MAAM,kBAAkB,QAAO,OAKrC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,CAAC,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAMpF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,CAAC,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAOrF,CAAC;AA0FF,eAAO,MAAM,oBAAoB,GAC/B,kBAAkB,MAAM,EACxB,QAAQ,OAAO,KACd,OAAO,CAAC,IAAI,CAoBd,CAAC"}
@@ -1,6 +1,8 @@
1
1
  import { appConfig } from '../../config/index.js';
2
2
  import Logger from '../../config/logger.js';
3
+ import { Router } from '../../routes/Router.js';
3
4
  import { isObject } from '../../helper/index.js';
5
+ import { getServicePrefix } from '../../microservices/ServiceManifest.js';
4
6
  import * as path from '../../node-singletons/path.js';
5
7
  import { pathToFileURL } from '../../node-singletons/url.js';
6
8
  import { detectRuntime } from '../../runtime/detectRuntime.js';
@@ -72,8 +74,11 @@ const registerManifestRoutes = async (router) => {
72
74
  try {
73
75
  // eslint-disable-next-line no-await-in-loop
74
76
  const mod = await entry.loadRoutes();
75
- if (isObject(mod) && typeof mod.registerRoutes === 'function') {
76
- mod.registerRoutes(router);
77
+ const registerRoutes = isObject(mod) ? mod.registerRoutes : undefined;
78
+ if (typeof registerRoutes === 'function') {
79
+ Router.group(router, getServicePrefix(entry), (scopedRouter) => {
80
+ registerRoutes(scopedRouter);
81
+ });
77
82
  }
78
83
  }
79
84
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"RoutesCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/RoutesCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAsSvF,eAAO,MAAM,aAAa;cACd,YAAY;EAatB,CAAC;AAEH,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"RoutesCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/RoutesCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA2VvF,eAAO,MAAM,aAAa;cACd,YAAY;EAatB,CAAC;AAEH,eAAe,aAAa,CAAC"}
@@ -6,6 +6,9 @@ import { BaseCommand } from '../BaseCommand.js';
6
6
  import { Env } from '../../config/env.js';
7
7
  import { Router } from '../../routes/Router.js';
8
8
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
9
+ import { isObject } from '../../helper/index.js';
10
+ import { getServicePrefix } from '../../microservices/ServiceManifest.js';
11
+ import { ProjectRuntime } from '../../runtime/ProjectRuntime.js';
9
12
  const parseGroupBy = (value) => {
10
13
  const raw = typeof value === 'string' ? value.trim().toLowerCase() : '';
11
14
  if (raw === '' || raw === 'group')
@@ -157,6 +160,40 @@ const renderTable = (rows) => {
157
160
  console.log(bottom);
158
161
  /* eslint-enable no-console */
159
162
  };
163
+ const annotateManifestRoutes = (router, beforeCount, serviceId) => {
164
+ const routes = Router.getRoutes(router);
165
+ for (const route of routes.slice(beforeCount)) {
166
+ route.__zintrustServiceId = serviceId;
167
+ }
168
+ };
169
+ const getRouteServiceId = (route) => {
170
+ return route.__zintrustServiceId ?? '';
171
+ };
172
+ const registerManifestRoutes = async (router) => {
173
+ await ProjectRuntime.tryLoadNodeRuntime();
174
+ const serviceManifest = ProjectRuntime.getServiceManifest();
175
+ if (serviceManifest.length === 0)
176
+ return;
177
+ for (const entry of serviceManifest) {
178
+ if (entry.monolithEnabled === false || typeof entry.loadRoutes !== 'function')
179
+ continue;
180
+ try {
181
+ const beforeCount = Router.getRoutes(router).length;
182
+ // eslint-disable-next-line no-await-in-loop
183
+ const mod = await entry.loadRoutes();
184
+ const registerRoutes = isObject(mod) ? mod.registerRoutes : undefined;
185
+ if (typeof registerRoutes === 'function') {
186
+ Router.group(router, getServicePrefix(entry), (scopedRouter) => {
187
+ registerRoutes(scopedRouter);
188
+ });
189
+ annotateManifestRoutes(router, beforeCount, entry.id);
190
+ }
191
+ }
192
+ catch {
193
+ // Ignore broken optional service routes and keep listing what is available.
194
+ }
195
+ }
196
+ };
160
197
  const buildRows = async (options) => {
161
198
  const groupBy = parseGroupBy(options.groupBy);
162
199
  const filterText = typeof options.filter === 'string' ? options.filter.trim().toLowerCase() : '';
@@ -174,12 +211,14 @@ const buildRows = async (options) => {
174
211
  catch {
175
212
  // routes/api.ts not found, continue with just core routes
176
213
  }
214
+ await registerManifestRoutes(router);
177
215
  const routes = Router.getRoutes(router);
178
216
  const rows = routes.map((route) => {
179
217
  const path = normalizePath(route.path);
218
+ const serviceId = getRouteServiceId(route);
180
219
  let group = '';
181
220
  if (groupBy === 'service') {
182
- group = deriveService(path);
221
+ group = serviceId === '' ? deriveService(path) : serviceId;
183
222
  }
184
223
  else if (groupBy === 'group') {
185
224
  group = deriveGroup(path);
@@ -1 +1 @@
1
- {"version":3,"file":"StartCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/StartCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAspBvF,eAAO,MAAM,YAAY;cACb,YAAY;EA6BtB,CAAC"}
1
+ {"version":3,"file":"StartCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/StartCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAgtBvF,eAAO,MAAM,YAAY;cACb,YAAY;EA6BtB,CAAC"}
@@ -168,8 +168,19 @@ const resolveCacheEnabledPreference = (options) => {
168
168
  return options.cache;
169
169
  return undefined;
170
170
  };
171
- const readPackageJson = (cwd) => {
172
- const packagePath = path.join(cwd, 'package.json');
171
+ const findNearestPackageJsonDir = (cwd) => {
172
+ let current = path.resolve(cwd);
173
+ while (true) {
174
+ if (existsSync(path.join(current, 'package.json')))
175
+ return current;
176
+ const parent = path.dirname(current);
177
+ if (parent === current)
178
+ return undefined;
179
+ current = parent;
180
+ }
181
+ };
182
+ const readPackageJsonFromDir = (dir) => {
183
+ const packagePath = path.join(dir, 'package.json');
173
184
  if (!existsSync(packagePath)) {
174
185
  throw ErrorFactory.createCliError("Error: No ZinTrust app found. Run 'zin new <project>' or ensure package.json exists.");
175
186
  }
@@ -181,6 +192,28 @@ const readPackageJson = (cwd) => {
181
192
  throw ErrorFactory.createTryCatchError('Failed to read package.json', error);
182
193
  }
183
194
  };
195
+ const resolveStartContext = (cwd) => {
196
+ const projectRoot = findNearestPackageJsonDir(cwd) ?? cwd;
197
+ const packageDir = findNearestPackageJsonDir(cwd);
198
+ return {
199
+ cwd,
200
+ projectRoot,
201
+ ...(packageDir === undefined ? {} : { packageJson: readPackageJsonFromDir(packageDir) }),
202
+ };
203
+ };
204
+ const requirePackageJson = (context) => {
205
+ if (context.packageJson !== undefined)
206
+ return context.packageJson;
207
+ throw ErrorFactory.createCliError("Error: No ZinTrust app found. Run 'zin new <project>' or ensure package.json exists.");
208
+ };
209
+ const buildStartEnv = (projectRoot) => ({
210
+ ...process.env,
211
+ ZINTRUST_PROJECT_ROOT: projectRoot,
212
+ });
213
+ const ensureStartEnvLoaded = (context) => {
214
+ const extraCwds = context.cwd === context.projectRoot ? [] : [context.cwd];
215
+ EnvFileLoader.ensureLoaded({ cwd: context.projectRoot, extraCwds });
216
+ };
184
217
  const isFrameworkRepo = (packageJson) => packageJson.name === '@zintrust/core';
185
218
  const hasDevScript = (packageJson) => {
186
219
  const scripts = packageJson.scripts;
@@ -263,14 +296,14 @@ const resolveNodeProdCommand = (cwd) => {
263
296
  }
264
297
  return { command: 'node', args: [compiled] };
265
298
  };
266
- const executeWranglerStart = async (cmd, cwd, port, runtime, envName, wranglerConfig) => {
299
+ const executeWranglerStart = async (cmd, context, port, runtime, envName, wranglerConfig) => {
267
300
  if (runtime !== undefined) {
268
301
  throw ErrorFactory.createCliError('Error: --runtime is not supported with --wrangler (Wrangler controls Workers runtime).');
269
302
  }
270
303
  const normalizedConfig = typeof wranglerConfig === 'string' ? wranglerConfig.trim() : '';
271
- const explicitConfigFullPath = normalizedConfig.length > 0 ? path.join(cwd, normalizedConfig) : undefined;
272
- const configPath = explicitConfigFullPath ?? findWranglerConfig(cwd);
273
- const entry = resolveWranglerEntry(cwd);
304
+ const explicitConfigFullPath = normalizedConfig.length > 0 ? path.join(context.cwd, normalizedConfig) : undefined;
305
+ const configPath = explicitConfigFullPath ?? findWranglerConfig(context.cwd);
306
+ const entry = resolveWranglerEntry(context.cwd);
274
307
  if (explicitConfigFullPath !== undefined) {
275
308
  if (existsSync(explicitConfigFullPath)) {
276
309
  // ok
@@ -300,7 +333,7 @@ const executeWranglerStart = async (cmd, cwd, port, runtime, envName, wranglerCo
300
333
  const exitCode = await SpawnUtil.spawnAndWait({
301
334
  command: 'wrangler',
302
335
  args: wranglerArgs,
303
- env: process.env,
336
+ env: buildStartEnv(context.projectRoot),
304
337
  });
305
338
  process.exit(exitCode);
306
339
  };
@@ -321,92 +354,98 @@ const ensureTmpRunnerFile = (cwd, filename, content) => {
321
354
  }
322
355
  return fullPath;
323
356
  };
324
- const executeDenoStart = async (cmd, cwd, mode, watchEnabled, _port, runtime) => {
357
+ const executeDenoStart = async (cmd, context, mode, watchEnabled, _port, runtime) => {
325
358
  if (runtime !== undefined) {
326
359
  throw ErrorFactory.createCliError('Error: --runtime cannot be used with --deno.');
327
360
  }
328
361
  if (mode === 'testing') {
329
362
  throw ErrorFactory.createCliError('Error: Cannot start server in testing mode. Use development or production.');
330
363
  }
331
- const startModuleSpecifier = resolveRuntimeStartModuleSpecifier(cwd);
332
- const denoRunner = ensureTmpRunnerFile(cwd, 'zin-start-deno.ts', createDenoRunnerSource(startModuleSpecifier));
364
+ const startModuleSpecifier = resolveRuntimeStartModuleSpecifier(context.cwd);
365
+ const denoRunner = ensureTmpRunnerFile(context.cwd, 'zin-start-deno.ts', createDenoRunnerSource(startModuleSpecifier));
333
366
  const args = [];
334
367
  if (mode === 'development' && watchEnabled)
335
368
  args.push('watch');
336
369
  args.push(denoRunner);
337
370
  cmd.info('Starting in Deno adapter mode...');
338
- const exitCode = await SpawnUtil.spawnAndWait({ command: 'tsx', args, env: process.env });
371
+ const exitCode = await SpawnUtil.spawnAndWait({
372
+ command: 'tsx',
373
+ args,
374
+ env: buildStartEnv(context.projectRoot),
375
+ });
339
376
  process.exit(exitCode);
340
377
  };
341
- const executeLambdaStart = async (cmd, cwd, mode, watchEnabled, _port, runtime) => {
378
+ const executeLambdaStart = async (cmd, context, mode, watchEnabled, _port, runtime) => {
342
379
  if (runtime !== undefined) {
343
380
  throw ErrorFactory.createCliError('Error: --runtime cannot be used with --lambda.');
344
381
  }
345
382
  if (mode === 'testing') {
346
383
  throw ErrorFactory.createCliError('Error: Cannot start server in testing mode. Use development or production.');
347
384
  }
348
- const startModuleSpecifier = resolveRuntimeStartModuleSpecifier(cwd);
349
- const lambdaRunner = ensureTmpRunnerFile(cwd, 'zin-start-lambda.ts', createLambdaRunnerSource(startModuleSpecifier));
385
+ const startModuleSpecifier = resolveRuntimeStartModuleSpecifier(context.cwd);
386
+ const lambdaRunner = ensureTmpRunnerFile(context.cwd, 'zin-start-lambda.ts', createLambdaRunnerSource(startModuleSpecifier));
350
387
  const args = [];
351
388
  if (mode === 'development' && watchEnabled)
352
389
  args.push('watch');
353
390
  args.push(lambdaRunner);
354
391
  cmd.info('Starting in Lambda adapter mode...');
355
- const exitCode = await SpawnUtil.spawnAndWait({ command: 'tsx', args, env: process.env });
392
+ const exitCode = await SpawnUtil.spawnAndWait({
393
+ command: 'tsx',
394
+ args,
395
+ env: buildStartEnv(context.projectRoot),
396
+ });
356
397
  process.exit(exitCode);
357
398
  };
358
- const executeNodeStart = async (cmd, cwd, mode, watchEnabled, _port) => {
399
+ const executeNodeStart = async (cmd, context, mode, watchEnabled, _port) => {
359
400
  if (mode === 'testing') {
360
401
  throw ErrorFactory.createCliError('Error: Cannot start server in testing mode. Use --force to override (not supported).');
361
402
  }
362
403
  if (mode === 'development') {
363
404
  if (!watchEnabled) {
364
405
  cmd.warn('Watch mode disabled; starting once.');
365
- const bootstrap = resolveBootstrapEntryTs(cwd);
406
+ const bootstrap = resolveBootstrapEntryTs(context.cwd);
366
407
  const args = bootstrap === undefined ? ['src/index.ts'] : [bootstrap];
367
408
  const exitCode = await SpawnUtil.spawnAndWait({
368
409
  command: 'tsx',
369
410
  args,
370
411
  forwardSignals: false,
371
- env: process.env,
412
+ env: buildStartEnv(context.projectRoot),
372
413
  });
373
414
  process.exit(exitCode);
374
415
  }
375
- const packageJson = readPackageJson(cwd);
376
- const dev = resolveNodeDevCommand(cwd, packageJson);
416
+ const dev = resolveNodeDevCommand(context.cwd, requirePackageJson(context));
377
417
  cmd.info('Starting in development mode (watch enabled)...');
378
418
  const exitCode = await SpawnUtil.spawnAndWait({
379
419
  command: dev.command,
380
420
  args: dev.args,
381
421
  forwardSignals: false,
382
- env: process.env,
422
+ env: buildStartEnv(context.projectRoot),
383
423
  });
384
424
  process.exit(exitCode);
385
425
  }
386
- const prod = resolveNodeProdCommand(cwd);
426
+ const prod = resolveNodeProdCommand(context.cwd);
387
427
  cmd.info('Starting in production mode...');
388
428
  const exitCode = await SpawnUtil.spawnAndWait({
389
429
  command: prod.command,
390
430
  args: prod.args,
391
431
  forwardSignals: false,
392
- env: process.env,
432
+ env: buildStartEnv(context.projectRoot),
393
433
  });
394
434
  process.exit(exitCode);
395
435
  };
396
- const executeSplitStart = async (cmd, cwd, _options) => {
436
+ const executeSplitStart = async (cmd, context, _options) => {
397
437
  cmd.info('🚀 Starting in split mode (Producer + Consumer)...');
398
- const packageJson = readPackageJson(cwd);
399
- const webDev = resolveNodeDevCommand(cwd, packageJson);
438
+ const webDev = resolveNodeDevCommand(context.cwd, requirePackageJson(context));
400
439
  // Producer Environment
401
440
  const producerEnv = {
402
- ...process.env,
441
+ ...buildStartEnv(context.projectRoot),
403
442
  WORKER_ENABLED: 'false',
404
443
  QUEUE_ENABLED: 'true',
405
444
  RUNTIME_MODE: 'node-server',
406
445
  };
407
446
  // Consumer Environment
408
447
  const consumerEnv = {
409
- ...process.env,
448
+ ...buildStartEnv(context.projectRoot),
410
449
  WORKER_ENABLED: 'true',
411
450
  QUEUE_ENABLED: 'true',
412
451
  RUNTIME_MODE: 'containers',
@@ -417,10 +456,10 @@ const executeSplitStart = async (cmd, cwd, _options) => {
417
456
  };
418
457
  // Resolve Consumer Command (zintrust worker:start-all)
419
458
  // We try to use tsx against the source bin if possible
420
- const workerArgs = existsSync(path.join(cwd, 'bin/zin.ts'))
459
+ const workerArgs = existsSync(path.join(context.projectRoot, 'bin/zin.ts'))
421
460
  ? ['bin/zin.ts', 'worker:start-all']
422
461
  : ['dist/bin/zin.js', 'worker:start-all'];
423
- const workerCommand = existsSync(path.join(cwd, 'bin/zin.ts')) ? 'tsx' : 'node';
462
+ const workerCommand = existsSync(path.join(context.projectRoot, 'bin/zin.ts')) ? 'tsx' : 'node';
424
463
  cmd.info('-------------------------------------------');
425
464
  cmd.info('🔹 [Producer] Web Server starting...');
426
465
  cmd.info('🔸 [Consumer] Worker Process starting...');
@@ -428,12 +467,14 @@ const executeSplitStart = async (cmd, cwd, _options) => {
428
467
  const pProducer = SpawnUtil.spawnAndWait({
429
468
  command: webDev.command,
430
469
  args: webDev.args,
470
+ cwd: context.cwd,
431
471
  env: producerEnv,
432
472
  forwardSignals: true,
433
473
  });
434
474
  const pConsumer = SpawnUtil.spawnAndWait({
435
475
  command: workerCommand,
436
476
  args: workerArgs,
477
+ cwd: context.projectRoot,
437
478
  env: consumerEnv,
438
479
  forwardSignals: true,
439
480
  });
@@ -441,7 +482,8 @@ const executeSplitStart = async (cmd, cwd, _options) => {
441
482
  };
442
483
  const executeStart = async (options, cmd) => {
443
484
  const cwd = process.cwd();
444
- EnvFileLoader.ensureLoaded();
485
+ const context = resolveStartContext(cwd);
486
+ ensureStartEnvLoaded(context);
445
487
  const mode = resolveMode(options);
446
488
  const port = resolvePort(options);
447
489
  const runtime = resolveRuntime(options);
@@ -454,7 +496,7 @@ const executeStart = async (options, cmd) => {
454
496
  if (variant === 'lambda')
455
497
  effectiveRuntime = 'lambda';
456
498
  if (mode === 'split') {
457
- await executeSplitStart(cmd, cwd, options);
499
+ await executeSplitStart(cmd, context, options);
458
500
  return;
459
501
  }
460
502
  assertCompatibleStartVariant(variant, configuredRuntime);
@@ -469,7 +511,7 @@ const executeStart = async (options, cmd) => {
469
511
  const wranglerConfig = typeof options.wranglerConfig === 'string' && options.wranglerConfig.trim() !== ''
470
512
  ? options.wranglerConfig.trim()
471
513
  : undefined;
472
- await executeWranglerStart(cmd, cwd, port, runtime, envName === '' ? undefined : envName, wranglerConfig);
514
+ await executeWranglerStart(cmd, context, port, runtime, envName === '' ? undefined : envName, wranglerConfig);
473
515
  return;
474
516
  }
475
517
  if (envName !== '') {
@@ -477,14 +519,14 @@ const executeStart = async (options, cmd) => {
477
519
  }
478
520
  const watchEnabled = resolveWatchPreference(options, mode);
479
521
  if (variant === 'deno') {
480
- await executeDenoStart(cmd, cwd, mode, watchEnabled, port, runtime);
522
+ await executeDenoStart(cmd, context, mode, watchEnabled, port, runtime);
481
523
  return;
482
524
  }
483
525
  if (variant === 'lambda') {
484
- await executeLambdaStart(cmd, cwd, mode, watchEnabled, port, runtime);
526
+ await executeLambdaStart(cmd, context, mode, watchEnabled, port, runtime);
485
527
  return;
486
528
  }
487
- await executeNodeStart(cmd, cwd, mode, watchEnabled, port);
529
+ await executeNodeStart(cmd, context, mode, watchEnabled, port);
488
530
  };
489
531
  export const StartCommand = Object.freeze({
490
532
  create() {
@@ -1 +1 @@
1
- {"version":3,"file":"ServiceScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ServiceScaffolder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC7C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AAEH;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAuB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CAGnF;AAED;;GAEG;AAEH,wBAAgB,QAAQ,CACtB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAiDhC;AAobD,eAAO,MAAM,iBAAiB;;;;EAI5B,CAAC"}
1
+ {"version":3,"file":"ServiceScaffolder.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/ServiceScaffolder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC7C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD;;GAEG;AAEH;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAuB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CAGnF;AAED;;GAEG;AAEH,wBAAgB,QAAQ,CACtB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,qBAAqB,CAAC,CAiDhC;AAgcD,eAAO,MAAM,iBAAiB;;;;EAI5B,CAAC"}
@@ -5,6 +5,10 @@
5
5
  import { FileGenerator } from '../scaffolding/FileGenerator.js';
6
6
  import { Logger } from '../../config/logger.js';
7
7
  import * as path from '../../node-singletons/path.js';
8
+ const coreModuleSpecifier = ['@zintrust', 'core'].join('/');
9
+ const coreStartModuleSpecifier = `${coreModuleSpecifier}/start`;
10
+ const serviceManifestImportExpression = "import('./bootstrap/service-manifest.ts').catch(() => import('./bootstrap/service-manifest.js'))";
11
+ const buildRouteImportExpression = (domain, serviceName) => `import('../services/${domain}/${serviceName}/routes/api.ts').catch(() => import('../services/${domain}/${serviceName}/routes/api.js'))`;
8
12
  /**
9
13
  * ServiceScaffolder generates microservices with all necessary files
10
14
  */
@@ -91,20 +95,22 @@ export function scaffold(projectRoot, options) {
91
95
  function ensureProjectRuntimeFiles(projectRoot, options) {
92
96
  const domain = options.domain ?? 'default';
93
97
  const serviceId = `${domain}/${options.name}`;
98
+ const routeImportExpression = buildRouteImportExpression(domain, options.name);
94
99
  const bootstrapDir = path.join(projectRoot, 'src', 'bootstrap');
95
100
  FileGenerator.createDirectory(bootstrapDir);
96
101
  const manifestPath = path.join(bootstrapDir, 'service-manifest.ts');
97
102
  if (!FileGenerator.fileExists(manifestPath)) {
98
- const initialManifest = `import type { ServiceManifestEntry } from '../../index.js';
103
+ const initialManifest = `import type { ServiceManifestEntry } from '${coreModuleSpecifier}';
99
104
 
100
105
  export const serviceManifest: ReadonlyArray<ServiceManifestEntry> = [
101
106
  {
102
107
  id: '${serviceId}',
103
108
  domain: '${domain}',
104
109
  name: '${options.name}',
110
+ prefix: '${serviceId}',
105
111
  port: ${options.port ?? 3001},
106
112
  monolithEnabled: true,
107
- loadRoutes: async () => import('../services/${domain}/${options.name}/routes/api'),
113
+ loadRoutes: async () => ${routeImportExpression},
108
114
  },
109
115
  ];
110
116
 
@@ -112,7 +118,9 @@ export default serviceManifest;
112
118
  `;
113
119
  FileGenerator.writeFile(manifestPath, initialManifest);
114
120
  }
115
- const runtimeModule = `import serviceManifest from './bootstrap/service-manifest';
121
+ const runtimeModule = `const serviceManifestModule = await ${serviceManifestImportExpression};
122
+
123
+ const serviceManifest = serviceManifestModule.default ?? serviceManifestModule.serviceManifest ?? [];
116
124
 
117
125
  export { serviceManifest };
118
126
 
@@ -128,6 +136,7 @@ export default Object.freeze({ serviceManifest });
128
136
  function updateServiceManifest(projectRoot, options) {
129
137
  const domain = options.domain ?? 'default';
130
138
  const serviceId = `${domain}/${options.name}`;
139
+ const routeImportExpression = buildRouteImportExpression(domain, options.name);
131
140
  const manifestPath = path.join(projectRoot, 'src', 'bootstrap', 'service-manifest.ts');
132
141
  if (!FileGenerator.fileExists(manifestPath))
133
142
  return;
@@ -139,9 +148,10 @@ function updateServiceManifest(projectRoot, options) {
139
148
  id: '${serviceId}',
140
149
  domain: '${domain}',
141
150
  name: '${options.name}',
151
+ prefix: '${serviceId}',
142
152
  port: ${options.port ?? 3001},
143
153
  monolithEnabled: true,
144
- loadRoutes: async () => import('../services/${domain}/${options.name}/routes/api'),
154
+ loadRoutes: async () => ${routeImportExpression},
145
155
  },
146
156
  `;
147
157
  const marker = '];';
@@ -234,7 +244,7 @@ function generateServiceIndex(options) {
234
244
  * Auth: ${options.auth ?? 'api-key'}
235
245
  */
236
246
 
237
- import { bootStandaloneService } from '../../start.js';
247
+ import { bootStandaloneService } from '${coreStartModuleSpecifier}';
238
248
 
239
249
  await bootStandaloneService(import.meta.url, {
240
250
  id: '${serviceId}',
@@ -244,7 +254,7 @@ await bootStandaloneService(import.meta.url, {
244
254
  });
245
255
 
246
256
  // Cloudflare Workers entry.
247
- export { default } from '../../start.js';
257
+ export { default } from '${coreStartModuleSpecifier}';
248
258
  `;
249
259
  }
250
260
  /**
@@ -255,7 +265,7 @@ function generateServiceRoutes(options) {
255
265
  * ${options.name} Service Routes
256
266
  */
257
267
 
258
- import { Router, type IRequest, type IResponse, type IRouter } from '../../index.js';
268
+ import { Router, type IRequest, type IResponse, type IRouter } from '${coreModuleSpecifier}';
259
269
 
260
270
  export function registerRoutes(router: IRouter): void {
261
271
  // Example route
@@ -326,8 +336,7 @@ function generateExampleController(options) {
326
336
  * Example Controller for ${options.name} Service
327
337
  */
328
338
 
329
- import { type IRequest, type IResponse, Controller } from '../../index.js';
330
-
339
+ import { type IRequest, type IResponse, Controller } from '${coreModuleSpecifier}';
331
340
  const controller = Object.freeze({
332
341
  ...Controller,
333
342
 
@@ -389,7 +398,7 @@ function generateExampleModel(options) {
389
398
  * Example Model for ${options.name} Service
390
399
  */
391
400
 
392
- import { Model } from '../../index.js';
401
+ import { Model } from '${coreModuleSpecifier}';
393
402
 
394
403
  export const Example = Model.define({
395
404
  table: '${options.name}',
@@ -447,6 +456,8 @@ function getServiceConfig(options) {
447
456
  */
448
457
  function generateServiceReadme(options) {
449
458
  const config = getServiceConfig(options);
459
+ const serviceDir = `src/services/${config.domain}/${options.name}`;
460
+ const serviceId = `${config.domain}/${options.name}`;
450
461
  return `# ${options.name} Service
451
462
 
452
463
  Microservice for ${config.domain} domain.
@@ -460,14 +471,19 @@ Microservice for ${config.domain} domain.
460
471
  ## Getting Started
461
472
 
462
473
  \`\`\`bash
463
- # Start service
464
- npm start
474
+ # Start this service from its service directory (Node)
475
+ cd ${serviceDir}
476
+ zin s
477
+
478
+ # Start this service with Cloudflare Workers dev
479
+ cd ${serviceDir}
480
+ zin s --wg
481
+
482
+ # List this service routes from the project root
483
+ MICROSERVICES=true SERVICES=${serviceId} zin routes
465
484
 
466
485
  # Run tests
467
486
  npm test
468
-
469
- # Run migrations
470
- npm run migrate
471
487
  \`\`\`
472
488
 
473
489
  ## Environment Variables
@@ -1,6 +1,7 @@
1
1
  type node_env = 'development' | 'production' | 'testing';
2
2
  type LoadOptions = {
3
3
  cwd?: string;
4
+ extraCwds?: string[];
4
5
  overrideExisting?: boolean;
5
6
  };
6
7
  type LoadState = {
@@ -15,7 +16,7 @@ type CliOverrides = {
15
16
  };
16
17
  export declare const EnvFileLoader: Readonly<{
17
18
  load: (options?: LoadOptions) => LoadState;
18
- ensureLoaded: () => LoadState;
19
+ ensureLoaded: (options?: Omit<LoadOptions, "overrideExisting">) => LoadState;
19
20
  applyCliOverrides: (overrides: CliOverrides) => void;
20
21
  getState: () => LoadState;
21
22
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"EnvFileLoader.d.ts","sourceRoot":"","sources":["../../../../src/cli/utils/EnvFileLoader.ts"],"names":[],"mappings":"AAQA,KAAK,QAAQ,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,CAAC;AAmIzD,KAAK,WAAW,GAAG;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AA+FF,eAAO,MAAM,aAAa;qBAtEH,WAAW,KAAQ,SAAS;wBAmC1B,SAAS;mCAEI,YAAY,KAAG,IAAI;oBA+BpC,SAAS;EAO5B,CAAC"}
1
+ {"version":3,"file":"EnvFileLoader.d.ts","sourceRoot":"","sources":["../../../../src/cli/utils/EnvFileLoader.ts"],"names":[],"mappings":"AAQA,KAAK,QAAQ,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,CAAC;AAmIzD,KAAK,WAAW,GAAG;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAkHF,eAAO,MAAM,aAAa;qBA7DH,WAAW,KAAQ,SAAS;6BAyBpB,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,KAAQ,SAAS;mCAG/C,YAAY,KAAG,IAAI;oBA+BpC,SAAS;EAO5B,CAAC"}
@@ -139,11 +139,7 @@ const filesLoader = (cwd, mode) => {
139
139
  return files;
140
140
  };
141
141
  let cached;
142
- const load = (options = {}) => {
143
- if (cached !== undefined)
144
- return cached;
145
- const cwd = typeof options.cwd === 'string' && options.cwd !== '' ? options.cwd : process.cwd();
146
- const overrideExisting = options.overrideExisting ?? true;
142
+ const loadFromCwd = (cwd, overrideExisting) => {
147
143
  const mode = resolveAppMode(cwd);
148
144
  const files = filesLoader(cwd, mode);
149
145
  let baseApplied = false;
@@ -163,10 +159,30 @@ const load = (options = {}) => {
163
159
  if (mode !== undefined) {
164
160
  safeEnvSet('NODE_ENV', mode);
165
161
  }
166
- cached = { loadedFiles: files, mode };
162
+ return { loadedFiles: files, mode };
163
+ };
164
+ const load = (options = {}) => {
165
+ if (cached !== undefined)
166
+ return cached;
167
+ const cwd = typeof options.cwd === 'string' && options.cwd !== '' ? options.cwd : process.cwd();
168
+ const extraCwds = Array.isArray(options.extraCwds)
169
+ ? options.extraCwds.filter((value) => typeof value === 'string' && value.trim() !== '')
170
+ : [];
171
+ const overrideExisting = options.overrideExisting ?? true;
172
+ const roots = [cwd, ...extraCwds].filter((value, index, items) => items.indexOf(value) === index);
173
+ let mergedMode;
174
+ const loadedFiles = [];
175
+ for (let index = 0; index < roots.length; index += 1) {
176
+ const root = roots[index];
177
+ const state = loadFromCwd(root, index === 0 ? overrideExisting : true);
178
+ if (mergedMode === undefined && state.mode !== undefined)
179
+ mergedMode = state.mode;
180
+ loadedFiles.push(...state.loadedFiles);
181
+ }
182
+ cached = { loadedFiles, mode: mergedMode };
167
183
  return cached;
168
184
  };
169
- const ensureLoaded = () => load({ overrideExisting: false });
185
+ const ensureLoaded = (options = {}) => load({ ...options, overrideExisting: false });
170
186
  const applyCliOverrides = (overrides) => {
171
187
  // Ensure base env is loaded first.
172
188
  ensureLoaded();
@@ -1 +1 @@
1
- {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../../src/cli/utils/spawn.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA8BD,eAAO,MAAM,SAAS;wBACM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;EA+E7D,CAAC"}
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../../src/cli/utils/spawn.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA0CD,eAAO,MAAM,SAAS;wBACM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;EA+E7D,CAAC"}
@@ -29,6 +29,16 @@ const resolveLocalBin = (command, cwd) => {
29
29
  }
30
30
  return command;
31
31
  };
32
+ const buildCommandNotFoundMessage = (command) => {
33
+ if (command === 'tsx') {
34
+ return [
35
+ "Error: 'tsx' not found on PATH.",
36
+ 'Install it in the project with "npm install -D tsx".',
37
+ 'If you want a machine-wide fallback, install it globally with "npm install -g tsx".',
38
+ ].join(' ');
39
+ }
40
+ return `Error: '${command}' not found on PATH.`;
41
+ };
32
42
  export const SpawnUtil = Object.freeze({
33
43
  async spawnAndWait(input) {
34
44
  const cwd = input.cwd ?? process.cwd();
@@ -87,7 +97,7 @@ export const SpawnUtil = Object.freeze({
87
97
  catch (error) {
88
98
  const code = error?.code;
89
99
  if (code === 'ENOENT') {
90
- throw ErrorFactory.createCliError(`Error: '${input.command}' not found on PATH.`);
100
+ throw ErrorFactory.createCliError(buildCommandNotFoundMessage(input.command));
91
101
  }
92
102
  throw ErrorFactory.createTryCatchError('Failed to spawn child process', error);
93
103
  }
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @zintrust/core v0.4.9
2
+ * @zintrust/core v0.4.11
3
3
  *
4
4
  * ZinTrust Framework - Production-Grade TypeScript Backend
5
5
  * Built for performance, type safety, and exceptional developer experience
6
6
  *
7
7
  * Build Information:
8
- * Built: 2026-03-23T11:19:34.232Z
8
+ * Built: 2026-03-23T13:29:47.237Z
9
9
  * Node: >=20.0.0
10
10
  * License: MIT
11
11
  *
@@ -21,7 +21,7 @@
21
21
  * Available at runtime for debugging and health checks
22
22
  */
23
23
  export const ZINTRUST_VERSION = '0.1.41';
24
- export const ZINTRUST_BUILD_DATE = '2026-03-23T11:19:34.198Z'; // Replaced during build
24
+ export const ZINTRUST_BUILD_DATE = '2026-03-23T13:29:47.153Z'; // Replaced during build
25
25
  export { Application } from './boot/Application.js';
26
26
  export { AwsSigV4 } from './common/index.js';
27
27
  export { SignedRequest } from './security/SignedRequest.js';
@@ -3,6 +3,7 @@ export interface ServiceManifestEntry {
3
3
  id: string;
4
4
  domain: string;
5
5
  name: string;
6
+ prefix?: string;
6
7
  version?: string;
7
8
  description?: string;
8
9
  port?: number;
@@ -21,6 +22,12 @@ export interface ProjectRuntimeModule {
21
22
  activeService?: ActiveServiceRuntime;
22
23
  }
23
24
  export declare const getServiceId: (domain: string, name: string) => string;
25
+ export declare const getDefaultServicePrefix: (domain: string, name: string) => string;
26
+ export declare const getServicePrefix: (args: {
27
+ prefix?: unknown;
28
+ domain?: unknown;
29
+ name?: unknown;
30
+ }) => string;
24
31
  export declare const isCanonicalServiceId: (value: unknown) => value is string;
25
32
  export declare const toCanonicalServiceId: (args: {
26
33
  id?: unknown;
@@ -34,6 +41,12 @@ export declare const normalizeProjectRuntimeModule: (value: unknown) => ProjectR
34
41
  export declare const serviceMatchesAllowList: (serviceId: string, serviceName: string, allowList: ReadonlyArray<string>) => boolean;
35
42
  declare const _default: Readonly<{
36
43
  getServiceId: (domain: string, name: string) => string;
44
+ getDefaultServicePrefix: (domain: string, name: string) => string;
45
+ getServicePrefix: (args: {
46
+ prefix?: unknown;
47
+ domain?: unknown;
48
+ name?: unknown;
49
+ }) => string;
37
50
  isCanonicalServiceId: (value: unknown) => value is string;
38
51
  toCanonicalServiceId: (args: {
39
52
  id?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"ServiceManifest.d.ts","sourceRoot":"","sources":["../../../src/microservices/ServiceManifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IACtD,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,MAA6B,CAAC;AAE1F,eAAO,MAAM,oBAAoB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAI9D,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,MAAM;IACzC,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,KAAG,MAKH,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,oBAYhE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,aAAa,CAAC,oBAAoB,CAQ3F,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,oBAAoB,GAAG,SAarF,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,OAAO,OAAO,KAAG,oBAa9D,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,WAAW,aAAa,CAAC,MAAM,CAAC,KAC/B,OAGF,CAAC;;2BAhFmC,MAAM,QAAQ,MAAM,KAAG,MAAM;kCAEtB,OAAO,KAAG,KAAK,IAAI,MAAM;iCAM1B;QACzC,EAAE,CAAC,EAAE,OAAO,CAAC;QACb,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,KAAG,MAAM;oCAOoC,OAAO,KAAG,KAAK,IAAI,oBAAoB;sCAcrC,OAAO,KAAG,aAAa,CAAC,oBAAoB,CAAC;2CAUxC,OAAO,KAAG,oBAAoB,GAAG,SAAS;2CAe1C,OAAO,KAAG,oBAAoB;yCAgBtE,MAAM,eACJ,MAAM,aACR,aAAa,CAAC,MAAM,CAAC,KAC/B,OAAO;;AAKV,wBASG"}
1
+ {"version":3,"file":"ServiceManifest.d.ts","sourceRoot":"","sources":["../../../src/microservices/ServiceManifest.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,25 @@
1
1
  import { isArray, isFunction, isNonEmptyString, isObject } from '../helper/index.js';
2
2
  export const getServiceId = (domain, name) => `${domain}/${name}`;
3
+ export const getDefaultServicePrefix = (domain, name) => `/${getServiceId(domain, name)}`;
4
+ const normalizeServicePrefix = (value) => {
5
+ const trimmed = value.trim();
6
+ if (trimmed === '' || trimmed === '/')
7
+ return '/';
8
+ const segments = trimmed
9
+ .split('/')
10
+ .map((segment) => segment.trim())
11
+ .filter((segment) => segment.length > 0);
12
+ if (segments.length === 0)
13
+ return '/';
14
+ return `/${segments.join('/')}`;
15
+ };
16
+ export const getServicePrefix = (args) => {
17
+ if (typeof args.prefix === 'string')
18
+ return normalizeServicePrefix(args.prefix);
19
+ const domain = isNonEmptyString(args.domain) ? args.domain : 'default';
20
+ const name = isNonEmptyString(args.name) ? args.name : 'unknown';
21
+ return getDefaultServicePrefix(domain, name);
22
+ };
3
23
  export const isCanonicalServiceId = (value) => {
4
24
  if (!isNonEmptyString(value))
5
25
  return false;
@@ -22,6 +42,10 @@ export const isServiceManifestEntry = (value) => {
22
42
  return false;
23
43
  if (!isNonEmptyString(value['name']))
24
44
  return false;
45
+ const prefix = value['prefix'];
46
+ if (prefix !== undefined && typeof prefix !== 'string') {
47
+ return false;
48
+ }
25
49
  const loadRoutes = value['loadRoutes'];
26
50
  if (loadRoutes !== undefined && !isFunction(loadRoutes)) {
27
51
  return false;
@@ -34,6 +58,7 @@ export const normalizeServiceManifest = (value) => {
34
58
  return value.filter(isServiceManifestEntry).map((entry) => ({
35
59
  ...entry,
36
60
  id: toCanonicalServiceId(entry),
61
+ prefix: getServicePrefix(entry),
37
62
  monolithEnabled: entry.monolithEnabled !== false,
38
63
  }));
39
64
  };
@@ -72,6 +97,8 @@ export const serviceMatchesAllowList = (serviceId, serviceName, allowList) => {
72
97
  };
73
98
  export default Object.freeze({
74
99
  getServiceId,
100
+ getDefaultServicePrefix,
101
+ getServicePrefix,
75
102
  isCanonicalServiceId,
76
103
  toCanonicalServiceId,
77
104
  isServiceManifestEntry,
@@ -1 +1 @@
1
- {"version":3,"file":"OverrideValueMerge.d.ts","sourceRoot":"","sources":["../../../src/runtime/OverrideValueMerge.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,GAAI,MAAM,OAAO,EAAE,UAAU,OAAO,KAAG,OAYtE,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"OverrideValueMerge.d.ts","sourceRoot":"","sources":["../../../src/runtime/OverrideValueMerge.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,GAAI,MAAM,OAAO,EAAE,UAAU,OAAO,KAAG,OAatE,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -6,7 +6,8 @@ export const mergeOverrideValues = (base, override) => {
6
6
  const merged = { ...base };
7
7
  for (const [key, value] of Object.entries(override)) {
8
8
  const current = merged[key];
9
- merged[key] = isObject(current) && isObject(value) ? mergeOverrideValues(current, value) : value;
9
+ merged[key] =
10
+ isObject(current) && isObject(value) ? mergeOverrideValues(current, value) : value;
10
11
  }
11
12
  return merged;
12
13
  };
@@ -1,4 +1,8 @@
1
- import serviceManifest from './bootstrap/service-manifest';
1
+ const serviceManifestModule = await import('./bootstrap/service-manifest.ts').catch(() =>
2
+ import('./bootstrap/service-manifest.js')
3
+ );
4
+
5
+ const serviceManifest = serviceManifestModule.default ?? serviceManifestModule.serviceManifest ?? [];
2
6
 
3
7
  export { serviceManifest };
4
8
 
@@ -1,4 +1,8 @@
1
- import serviceManifest from './bootstrap/service-manifest';
1
+ const serviceManifestModule = await import('./bootstrap/service-manifest.ts').catch(() =>
2
+ import('./bootstrap/service-manifest.js')
3
+ );
4
+
5
+ const serviceManifest = serviceManifestModule.default ?? serviceManifestModule.serviceManifest ?? [];
2
6
 
3
7
  export { serviceManifest };
4
8