aegisnode 0.0.5 → 0.1.1

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.
@@ -5,7 +5,7 @@ import https from 'https';
5
5
  import { AsyncLocalStorage } from 'async_hooks';
6
6
  import crypto from 'crypto';
7
7
  import path from 'path';
8
- import { fileURLToPath, pathToFileURL } from 'url';
8
+ import { fileURLToPath } from 'url';
9
9
  import express from 'express';
10
10
  import ejs from 'ejs';
11
11
  import helmet from 'helmet';
@@ -23,6 +23,13 @@ import { runLoaders } from './loaders.js';
23
23
  import { createRuntimeHelpers } from './helpers.js';
24
24
  import { createUploadManager, isMultipartRequestContentType, normalizeUploadsConfig } from './upload.js';
25
25
  import { createMailManager, normalizeMailConfig } from './mail.js';
26
+ import { importProjectModule } from './typescript.js';
27
+ import {
28
+ hasNamedSourceSuffix,
29
+ isSourceFileName,
30
+ resolveSourceFile,
31
+ resolveSourceIndexFile,
32
+ } from '../utils/source-files.js';
26
33
 
27
34
  const ROUTE_DEFINITION = 'aegis:routes';
28
35
  const PROJECT_ROUTE_DEFINITION = 'aegis:project-routes';
@@ -33,7 +40,7 @@ const EMPTY_ROUTE_CONTEXT = Object.freeze({});
33
40
  const REQUEST_I18N_CONTEXT = new AsyncLocalStorage();
34
41
 
35
42
  function exists(filePath) {
36
- return fs.existsSync(filePath);
43
+ return typeof filePath === 'string' && fs.existsSync(filePath);
37
44
  }
38
45
 
39
46
  function isRouterInstance(value) {
@@ -653,7 +660,7 @@ const DB_LIBRARY_PATTERN = /\b(querymesh|mongoose|pg|postgres|postgresql|mysql|m
653
660
  const IMPORT_FROM_PATTERN = /import\s+[\s\S]*?\sfrom\s+['"]([^'"]+)['"]/g;
654
661
  const IMPORT_SIDE_EFFECT_PATTERN = /import\s+['"]([^'"]+)['"]/g;
655
662
  const REQUIRE_PATTERN = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
656
- const MODEL_IMPORT_PATH_PATTERN = /(?:^|\/)(models(?:\.js)?|[^/]+\.model(?:\.js)?)$/i;
663
+ const MODEL_IMPORT_PATH_PATTERN = /(?:^|\/)(models(?:\.(?:js|ts))?|[^/]+\.model(?:\.(?:js|ts))?)$/i;
657
664
 
658
665
  function extractImportSpecifiers(source) {
659
666
  const imports = [];
@@ -693,26 +700,26 @@ async function collectStrictLayerFiles(appRoot) {
693
700
  const routeFiles = [];
694
701
  const serviceFiles = [];
695
702
 
696
- const routesFile = path.join(appRoot, 'routes.js');
703
+ const routesFile = resolveSourceFile(path.join(appRoot, 'routes'));
697
704
  if (exists(routesFile)) {
698
705
  routeFiles.push(routesFile);
699
706
  }
700
707
 
701
708
  const routesDir = path.join(appRoot, 'routes');
702
709
  for (const fileName of await loadDirectoryFiles(routesDir)) {
703
- if (!fileName.endsWith('.js')) {
710
+ if (!isSourceFileName(fileName)) {
704
711
  continue;
705
712
  }
706
713
  routeFiles.push(path.join(routesDir, fileName));
707
714
  }
708
715
 
709
- const servicesFile = path.join(appRoot, 'services.js');
716
+ const servicesFile = resolveSourceFile(path.join(appRoot, 'services'));
710
717
  if (exists(servicesFile)) {
711
718
  serviceFiles.push(servicesFile);
712
719
  }
713
720
 
714
721
  for (const fileName of await loadDirectoryFiles(appRoot)) {
715
- if (!fileName.endsWith('.service.js')) {
722
+ if (!hasNamedSourceSuffix(fileName, '.service')) {
716
723
  continue;
717
724
  }
718
725
  serviceFiles.push(path.join(appRoot, fileName));
@@ -720,7 +727,7 @@ async function collectStrictLayerFiles(appRoot) {
720
727
 
721
728
  const servicesDir = path.join(appRoot, 'services');
722
729
  for (const fileName of await loadDirectoryFiles(servicesDir)) {
723
- if (!fileName.endsWith('.js')) {
730
+ if (!isSourceFileName(fileName)) {
724
731
  continue;
725
732
  }
726
733
  serviceFiles.push(path.join(servicesDir, fileName));
@@ -1829,8 +1836,7 @@ function extractCsrfToken(req, csrfConfig) {
1829
1836
  }
1830
1837
 
1831
1838
  async function importModule(filePath) {
1832
- const moduleUrl = `${pathToFileURL(filePath).href}?t=${Date.now()}`;
1833
- return import(moduleUrl);
1839
+ return importProjectModule(filePath);
1834
1840
  }
1835
1841
 
1836
1842
  export function defineRoutes(register) {
@@ -2357,7 +2363,7 @@ async function registerControllers({ appName, appRoot, container, logger }) {
2357
2363
  const files = await loadDirectoryFiles(directory);
2358
2364
 
2359
2365
  for (const fileName of files) {
2360
- if (!fileName.endsWith('.js')) {
2366
+ if (!isSourceFileName(fileName)) {
2361
2367
  continue;
2362
2368
  }
2363
2369
 
@@ -2370,23 +2376,23 @@ async function registerControllers({ appName, appRoot, container, logger }) {
2370
2376
  }
2371
2377
  }
2372
2378
 
2373
- const singleFiles = ['controllers.js', 'views.js'];
2374
- for (const fileName of singleFiles) {
2375
- const filePath = path.join(appRoot, fileName);
2379
+ const singleFiles = ['controllers', 'views'];
2380
+ for (const baseName of singleFiles) {
2381
+ const filePath = resolveSourceFile(path.join(appRoot, baseName));
2376
2382
  if (!exists(filePath)) {
2377
2383
  continue;
2378
2384
  }
2379
2385
 
2380
2386
  const loaded = await importModule(filePath);
2381
2387
  const controller = loaded.default ?? loaded;
2382
- const controllerName = normalizeControllerName(fileName);
2388
+ const controllerName = normalizeControllerName(path.basename(filePath));
2383
2389
  container.set(`controller:${appName}.${controllerName}`, controller);
2384
2390
  logger.debug('Controller registered: controller:%s.%s', appName, controllerName);
2385
2391
  }
2386
2392
  }
2387
2393
 
2388
2394
  async function registerModels({ appName, appRoot, container, logger }) {
2389
- const modelsFile = path.join(appRoot, 'models.js');
2395
+ const modelsFile = resolveSourceFile(path.join(appRoot, 'models'));
2390
2396
  if (exists(modelsFile)) {
2391
2397
  const loaded = await importModule(modelsFile);
2392
2398
  const exported = loaded.default ?? loaded;
@@ -2403,7 +2409,7 @@ async function registerModels({ appName, appRoot, container, logger }) {
2403
2409
 
2404
2410
  const rootFiles = await loadDirectoryFiles(appRoot);
2405
2411
  for (const fileName of rootFiles) {
2406
- if (!fileName.endsWith('.model.js')) {
2412
+ if (!hasNamedSourceSuffix(fileName, '.model')) {
2407
2413
  continue;
2408
2414
  }
2409
2415
 
@@ -2419,7 +2425,7 @@ async function registerModels({ appName, appRoot, container, logger }) {
2419
2425
  const files = await loadDirectoryFiles(modelsDir);
2420
2426
 
2421
2427
  for (const fileName of files) {
2422
- if (!fileName.endsWith('.js')) {
2428
+ if (!isSourceFileName(fileName)) {
2423
2429
  continue;
2424
2430
  }
2425
2431
 
@@ -2433,7 +2439,7 @@ async function registerModels({ appName, appRoot, container, logger }) {
2433
2439
  }
2434
2440
 
2435
2441
  async function registerValidators({ appName, appRoot, container, logger }) {
2436
- const validatorsFile = path.join(appRoot, 'validators.js');
2442
+ const validatorsFile = resolveSourceFile(path.join(appRoot, 'validators'));
2437
2443
  if (exists(validatorsFile)) {
2438
2444
  const loaded = await importModule(validatorsFile);
2439
2445
  const exported = loaded.default ?? loaded;
@@ -2450,7 +2456,7 @@ async function registerValidators({ appName, appRoot, container, logger }) {
2450
2456
 
2451
2457
  const rootFiles = await loadDirectoryFiles(appRoot);
2452
2458
  for (const fileName of rootFiles) {
2453
- if (!fileName.endsWith('.validator.js')) {
2459
+ if (!hasNamedSourceSuffix(fileName, '.validator')) {
2454
2460
  continue;
2455
2461
  }
2456
2462
 
@@ -2466,7 +2472,7 @@ async function registerValidators({ appName, appRoot, container, logger }) {
2466
2472
  const files = await loadDirectoryFiles(validatorsDir);
2467
2473
 
2468
2474
  for (const fileName of files) {
2469
- if (!fileName.endsWith('.js')) {
2475
+ if (!isSourceFileName(fileName)) {
2470
2476
  continue;
2471
2477
  }
2472
2478
 
@@ -2480,7 +2486,7 @@ async function registerValidators({ appName, appRoot, container, logger }) {
2480
2486
  }
2481
2487
 
2482
2488
  async function registerServices({ appName, appRoot, container, logger }) {
2483
- const servicesFile = path.join(appRoot, 'services.js');
2489
+ const servicesFile = resolveSourceFile(path.join(appRoot, 'services'));
2484
2490
  if (exists(servicesFile)) {
2485
2491
  const loaded = await importModule(servicesFile);
2486
2492
  const exported = loaded.default ?? loaded;
@@ -2495,11 +2501,25 @@ async function registerServices({ appName, appRoot, container, logger }) {
2495
2501
  }
2496
2502
  }
2497
2503
 
2504
+ const rootFiles = await loadDirectoryFiles(appRoot);
2505
+ for (const fileName of rootFiles) {
2506
+ if (!hasNamedSourceSuffix(fileName, '.service')) {
2507
+ continue;
2508
+ }
2509
+
2510
+ const filePath = path.join(appRoot, fileName);
2511
+ const loaded = await importModule(filePath);
2512
+ const service = loaded.default ?? loaded;
2513
+ const serviceName = normalizeServiceName(fileName);
2514
+ container.set(`service:${appName}.${serviceName}`, service);
2515
+ logger.debug('Service registered: service:%s.%s', appName, serviceName);
2516
+ }
2517
+
2498
2518
  const servicesDir = path.join(appRoot, 'services');
2499
2519
  const files = await loadDirectoryFiles(servicesDir);
2500
2520
 
2501
2521
  for (const fileName of files) {
2502
- if (!fileName.endsWith('.js')) {
2522
+ if (!isSourceFileName(fileName)) {
2503
2523
  continue;
2504
2524
  }
2505
2525
 
@@ -2513,9 +2533,8 @@ async function registerServices({ appName, appRoot, container, logger }) {
2513
2533
  }
2514
2534
 
2515
2535
  async function registerSubscribers({ appName, appRoot, context, logger }) {
2516
- const subscribersFile = exists(path.join(appRoot, 'subscribers.js'))
2517
- ? path.join(appRoot, 'subscribers.js')
2518
- : path.join(appRoot, 'subscribers', 'index.js');
2536
+ const subscribersFile = resolveSourceFile(path.join(appRoot, 'subscribers'))
2537
+ || resolveSourceIndexFile(path.join(appRoot, 'subscribers'));
2519
2538
 
2520
2539
  if (!exists(subscribersFile)) {
2521
2540
  return;
@@ -2531,9 +2550,8 @@ async function registerSubscribers({ appName, appRoot, context, logger }) {
2531
2550
  }
2532
2551
 
2533
2552
  async function mountAppRoutes({ appDefinition, appRoot, context, expressApp, routeContext = null }) {
2534
- const routesFile = exists(path.join(appRoot, 'routes.js'))
2535
- ? path.join(appRoot, 'routes.js')
2536
- : path.join(appRoot, 'routes', 'index.js');
2553
+ const routesFile = resolveSourceFile(path.join(appRoot, 'routes'))
2554
+ || resolveSourceIndexFile(path.join(appRoot, 'routes'));
2537
2555
  if (!exists(routesFile)) {
2538
2556
  return;
2539
2557
  }
@@ -2599,9 +2617,8 @@ async function mountAppRoutes({ appDefinition, appRoot, context, expressApp, rou
2599
2617
  }
2600
2618
 
2601
2619
  async function loadProjectRoutes(rootDir) {
2602
- const routesFile = exists(path.join(rootDir, 'routes.js'))
2603
- ? path.join(rootDir, 'routes.js')
2604
- : path.join(rootDir, 'routes', 'index.js');
2620
+ const routesFile = resolveSourceFile(path.join(rootDir, 'routes'))
2621
+ || resolveSourceIndexFile(path.join(rootDir, 'routes'));
2605
2622
 
2606
2623
  if (!exists(routesFile)) {
2607
2624
  return null;
@@ -0,0 +1,21 @@
1
+ import { pathToFileURL } from 'url';
2
+ import { isTypeScriptFile } from '../utils/source-files.js';
3
+
4
+ let registrationPromise = null;
5
+
6
+ export async function registerTypeScriptRuntime() {
7
+ if (!registrationPromise) {
8
+ registrationPromise = import('tsx/esm/api').then(({ register }) => register());
9
+ }
10
+
11
+ return registrationPromise;
12
+ }
13
+
14
+ export async function importProjectModule(filePath) {
15
+ if (isTypeScriptFile(filePath)) {
16
+ await registerTypeScriptRuntime();
17
+ }
18
+
19
+ const moduleUrl = `${pathToFileURL(filePath).href}?t=${Date.now()}`;
20
+ return import(moduleUrl);
21
+ }
@@ -0,0 +1,78 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export const SOURCE_EXTENSIONS = Object.freeze(['.js', '.ts']);
5
+ export const DEFAULT_SOURCE_EXTENSION = '.js';
6
+ export const TYPESCRIPT_SOURCE_EXTENSION = '.ts';
7
+
8
+ const SOURCE_FILE_PATTERN = /\.(?:js|ts)$/i;
9
+
10
+ function escapeRegExp(value) {
11
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
12
+ }
13
+
14
+ export function isSourceFileName(fileName) {
15
+ return SOURCE_FILE_PATTERN.test(String(fileName || ''));
16
+ }
17
+
18
+ export function isTypeScriptFile(filePath) {
19
+ return String(filePath || '').toLowerCase().endsWith(TYPESCRIPT_SOURCE_EXTENSION);
20
+ }
21
+
22
+ export function stripSourceExtension(fileName) {
23
+ return String(fileName || '').replace(/\.(?:js|ts)$/i, '');
24
+ }
25
+
26
+ export function hasNamedSourceSuffix(fileName, suffix) {
27
+ return new RegExp(`${escapeRegExp(suffix)}\\.(?:js|ts)$`, 'i').test(String(fileName || ''));
28
+ }
29
+
30
+ export function resolveSourceFile(basePath, extensions = SOURCE_EXTENSIONS) {
31
+ for (const extension of extensions) {
32
+ const candidate = `${basePath}${extension}`;
33
+ if (fs.existsSync(candidate)) {
34
+ return candidate;
35
+ }
36
+ }
37
+
38
+ return null;
39
+ }
40
+
41
+ export function resolveSourceIndexFile(directoryPath, extensions = SOURCE_EXTENSIONS) {
42
+ for (const extension of extensions) {
43
+ const candidate = path.join(directoryPath, `index${extension}`);
44
+ if (fs.existsSync(candidate)) {
45
+ return candidate;
46
+ }
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ export function detectProjectSourceExtension(rootDir) {
53
+ const tsSignals = [
54
+ resolveSourceFile(path.join(rootDir, 'settings'), [TYPESCRIPT_SOURCE_EXTENSION]),
55
+ resolveSourceFile(path.join(rootDir, 'app'), [TYPESCRIPT_SOURCE_EXTENSION]),
56
+ resolveSourceFile(path.join(rootDir, 'routes'), [TYPESCRIPT_SOURCE_EXTENSION]),
57
+ resolveSourceIndexFile(path.join(rootDir, 'settings'), [TYPESCRIPT_SOURCE_EXTENSION]),
58
+ ];
59
+ if (tsSignals.some(Boolean)) {
60
+ return TYPESCRIPT_SOURCE_EXTENSION;
61
+ }
62
+
63
+ const jsSignals = [
64
+ resolveSourceFile(path.join(rootDir, 'settings'), [DEFAULT_SOURCE_EXTENSION]),
65
+ resolveSourceFile(path.join(rootDir, 'app'), [DEFAULT_SOURCE_EXTENSION]),
66
+ resolveSourceFile(path.join(rootDir, 'routes'), [DEFAULT_SOURCE_EXTENSION]),
67
+ resolveSourceIndexFile(path.join(rootDir, 'settings'), [DEFAULT_SOURCE_EXTENSION]),
68
+ ];
69
+ if (jsSignals.some(Boolean)) {
70
+ return DEFAULT_SOURCE_EXTENSION;
71
+ }
72
+
73
+ if (fs.existsSync(path.join(rootDir, 'tsconfig.json'))) {
74
+ return TYPESCRIPT_SOURCE_EXTENSION;
75
+ }
76
+
77
+ return DEFAULT_SOURCE_EXTENSION;
78
+ }