jax-hono 1.1.0 → 1.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.
- package/build/generate-registry.ts +95 -14
- package/core/controller.ts +30 -2
- package/package.json +1 -1
|
@@ -39,6 +39,12 @@ type PluginFile = EnabledPlugin & {
|
|
|
39
39
|
filePath: string
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
type ControllerExportInfo = {
|
|
43
|
+
defaultClass: boolean
|
|
44
|
+
namedClasses: string[]
|
|
45
|
+
namedValues: string[]
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
const controllerGroups: ControllerGroup[] = [
|
|
43
49
|
{ name: 'api', dirs: ['api', 'controllers/api', 'controller/api', 'modules'], suffix: '.api' },
|
|
44
50
|
{ name: 'backend', dirs: ['backend', 'controllers/backend', 'controller/backend', 'modules'], suffix: '.backend' },
|
|
@@ -211,6 +217,11 @@ function getFilesFromDirs(dirs: string[]) {
|
|
|
211
217
|
return uniqueSorted(dirs.flatMap((dirName) => walkFiles(join(srcDir, dirName))))
|
|
212
218
|
}
|
|
213
219
|
|
|
220
|
+
function isRootDirIndex(filePath: string, dirs: string[]) {
|
|
221
|
+
return basename(filePath) === 'index.ts'
|
|
222
|
+
&& dirs.some((dirName) => dirname(filePath) === join(srcDir, dirName))
|
|
223
|
+
}
|
|
224
|
+
|
|
214
225
|
function getEnabledPluginFiles(suffix: string) {
|
|
215
226
|
return getEnabledPlugins().flatMap((plugin) => {
|
|
216
227
|
return walkFiles(plugin.rootDir)
|
|
@@ -240,8 +251,47 @@ function isModuleFeatureFile(filePath: string, suffix: string) {
|
|
|
240
251
|
return !isFileInDir(filePath, 'modules') || isSuffixedModuleFile(filePath, suffix)
|
|
241
252
|
}
|
|
242
253
|
|
|
243
|
-
function
|
|
244
|
-
|
|
254
|
+
function getControllerExportInfo(filePath: string): ControllerExportInfo {
|
|
255
|
+
const source = readFileSync(filePath, 'utf8')
|
|
256
|
+
const classNames = new Set(
|
|
257
|
+
[...source.matchAll(/\b(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g)]
|
|
258
|
+
.map((match) => match[1]),
|
|
259
|
+
)
|
|
260
|
+
const defaultIdentifier = source.match(/\bexport\s+default\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\b/)?.[1]
|
|
261
|
+
const defaultClass = /\bexport\s+default\s+(?:abstract\s+)?class\b/.test(source)
|
|
262
|
+
|| Boolean(defaultIdentifier && classNames.has(defaultIdentifier))
|
|
263
|
+
const namedClasses = [...source.matchAll(/\bexport\s+(?:abstract\s+)?class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g)]
|
|
264
|
+
.map((match) => match[1])
|
|
265
|
+
.sort((left, right) => left.localeCompare(right))
|
|
266
|
+
const namedValues = [
|
|
267
|
+
...source.matchAll(/\bexport\s+(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g),
|
|
268
|
+
...source.matchAll(/\bexport\s+(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g),
|
|
269
|
+
]
|
|
270
|
+
.map((match) => match[1])
|
|
271
|
+
.filter((name) => !namedClasses.includes(name))
|
|
272
|
+
.sort((left, right) => left.localeCompare(right))
|
|
273
|
+
|
|
274
|
+
return { defaultClass, namedClasses, namedValues }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function hasControllerExport(info: ControllerExportInfo) {
|
|
278
|
+
return info.defaultClass || info.namedClasses.length > 0 || info.namedValues.length > 0
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getControllerClassExport(info: ControllerExportInfo) {
|
|
282
|
+
if (info.defaultClass) {
|
|
283
|
+
return 'default'
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return info.namedClasses.find((name) => name.endsWith('Controller')) ?? info.namedClasses[0]
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function toControllerNamedExportsObject(namespaceName: string, names: string[]) {
|
|
290
|
+
if (names.length === 0) {
|
|
291
|
+
return '{}'
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return `{\n${indentObject(names.map((name) => `${toObjectKey(name)}: ${namespaceName}.${name},`).join('\n'), 2)}\n}`
|
|
245
295
|
}
|
|
246
296
|
|
|
247
297
|
function indentObject(content: string, spaces = 2) {
|
|
@@ -291,32 +341,37 @@ function generateControllersRegistry() {
|
|
|
291
341
|
const imports: string[] = []
|
|
292
342
|
const controllerExports: string[] = []
|
|
293
343
|
const groupLines: string[] = []
|
|
344
|
+
let usesCreateController = false
|
|
294
345
|
|
|
295
346
|
for (const group of controllerGroups) {
|
|
296
347
|
const projectEntries = getFilesFromDirs(group.dirs)
|
|
297
348
|
.filter((filePath) => basename(filePath) !== 'index.ts')
|
|
298
349
|
.filter((filePath) => !group.suffix || isModuleFeatureFile(filePath, group.suffix))
|
|
299
|
-
.
|
|
300
|
-
.
|
|
350
|
+
.map((filePath) => ({ filePath, exportInfo: getControllerExportInfo(filePath) }))
|
|
351
|
+
.filter(({ exportInfo }) => hasControllerExport(exportInfo))
|
|
352
|
+
.map(({ filePath, exportInfo }, index) => {
|
|
301
353
|
const baseDir = group.dirs.find((dirName) => {
|
|
302
354
|
return isFileInDir(filePath, dirName)
|
|
303
355
|
}) ?? group.dirs[0]
|
|
304
356
|
const modulePath = toModulePath(baseDir, filePath, group.suffix ? [group.suffix] : [])
|
|
305
357
|
const importName = toIdentifier([group.name, modulePath, String(index), 'Controller'])
|
|
358
|
+
const namespaceName = toIdentifier([group.name, modulePath, String(index), 'ControllerExports'])
|
|
306
359
|
const exportName = toLowerCamelIdentifier(group.name === 'controller' ? [modulePath] : [group.name, modulePath], 'Controller')
|
|
307
360
|
|
|
308
|
-
return { modulePath, importName, exportName, filePath }
|
|
361
|
+
return { modulePath, importName, namespaceName, exportName, filePath, exportInfo }
|
|
309
362
|
})
|
|
310
363
|
const pluginEntries = group.name === 'controller'
|
|
311
364
|
? getEnabledPluginFiles('.controller')
|
|
312
365
|
.filter(({ filePath }) => basename(filePath) !== 'index.ts')
|
|
313
|
-
.
|
|
314
|
-
.
|
|
366
|
+
.map((item) => ({ ...item, exportInfo: getControllerExportInfo(item.filePath) }))
|
|
367
|
+
.filter(({ exportInfo }) => hasControllerExport(exportInfo))
|
|
368
|
+
.map(({ name, rootDir, filePath, exportInfo }, index) => {
|
|
315
369
|
const modulePath = toPluginModulePath(name, rootDir, filePath, ['.controller'])
|
|
316
370
|
const importName = toIdentifier([group.name, 'plugin', modulePath, String(index), 'Controller'])
|
|
371
|
+
const namespaceName = toIdentifier([group.name, 'plugin', modulePath, String(index), 'ControllerExports'])
|
|
317
372
|
const exportName = toLowerCamelIdentifier([modulePath], 'Controller')
|
|
318
373
|
|
|
319
|
-
return { modulePath, importName, exportName, filePath }
|
|
374
|
+
return { modulePath, importName, namespaceName, exportName, filePath, exportInfo }
|
|
320
375
|
})
|
|
321
376
|
: []
|
|
322
377
|
const entries = [...projectEntries, ...pluginEntries]
|
|
@@ -341,8 +396,33 @@ function generateControllersRegistry() {
|
|
|
341
396
|
return true
|
|
342
397
|
})
|
|
343
398
|
|
|
344
|
-
imports.push(...entries.
|
|
345
|
-
|
|
399
|
+
imports.push(...entries.flatMap((entry) => {
|
|
400
|
+
const importPath = toImportPath(entry.filePath)
|
|
401
|
+
const classExport = getControllerClassExport(entry.exportInfo)
|
|
402
|
+
const defaultImport = classExport === 'default'
|
|
403
|
+
? [`import ${entry.importName} from '${importPath}'`]
|
|
404
|
+
: []
|
|
405
|
+
|
|
406
|
+
return [
|
|
407
|
+
...defaultImport,
|
|
408
|
+
`import * as ${entry.namespaceName} from '${importPath}'`,
|
|
409
|
+
]
|
|
410
|
+
}))
|
|
411
|
+
controllerExports.push(...entries.map((entry) => {
|
|
412
|
+
const classExport = getControllerClassExport(entry.exportInfo)
|
|
413
|
+
const namedExports = toControllerNamedExportsObject(entry.namespaceName, entry.exportInfo.namedValues)
|
|
414
|
+
|
|
415
|
+
if (!classExport) {
|
|
416
|
+
return `export const ${entry.exportName} = ${namedExports}`
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
usesCreateController = true
|
|
420
|
+
const controllerExport = classExport === 'default'
|
|
421
|
+
? entry.importName
|
|
422
|
+
: `${entry.namespaceName}.${classExport}`
|
|
423
|
+
|
|
424
|
+
return `export const ${entry.exportName} = createController(${controllerExport}, ${namedExports})`
|
|
425
|
+
}))
|
|
346
426
|
|
|
347
427
|
if (entries.length === 0) {
|
|
348
428
|
continue
|
|
@@ -360,7 +440,7 @@ function generateControllersRegistry() {
|
|
|
360
440
|
}
|
|
361
441
|
}
|
|
362
442
|
|
|
363
|
-
const helperImport =
|
|
443
|
+
const helperImport = usesCreateController
|
|
364
444
|
? `import { createController } from 'jax-hono/core/controller'\n`
|
|
365
445
|
: ''
|
|
366
446
|
const content = `${generatedHeader}
|
|
@@ -442,9 +522,10 @@ ${entries.join('\n')}
|
|
|
442
522
|
}
|
|
443
523
|
|
|
444
524
|
function generateServicesRegistry() {
|
|
525
|
+
const serviceDirs = ['services', 'service']
|
|
445
526
|
const files = [
|
|
446
|
-
...getFilesFromDirs(
|
|
447
|
-
.filter((filePath) =>
|
|
527
|
+
...getFilesFromDirs(serviceDirs)
|
|
528
|
+
.filter((filePath) => !isRootDirIndex(filePath, serviceDirs))
|
|
448
529
|
.map((filePath) => ({ filePath })),
|
|
449
530
|
]
|
|
450
531
|
const imports: string[] = []
|
|
@@ -454,7 +535,7 @@ function generateServicesRegistry() {
|
|
|
454
535
|
files.forEach((item) => {
|
|
455
536
|
const { filePath } = item
|
|
456
537
|
const source = readFileSync(filePath, 'utf8')
|
|
457
|
-
const namedServiceObjects = [...source.matchAll(/\bexport\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*Service)\b/g)]
|
|
538
|
+
const namedServiceObjects = [...source.matchAll(/\bexport\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*(?:Service|Adapters))\b/g)]
|
|
458
539
|
.map((match) => match[1])
|
|
459
540
|
.sort((left, right) => left.localeCompare(right))
|
|
460
541
|
|
package/core/controller.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type { GeneratedPlugins, GeneratedServices } from './generated';
|
|
|
5
5
|
const controllerContextStorage = new AsyncLocalStorage<Context>();
|
|
6
6
|
type ControllerConstructor<T extends Controller> = new (...args: any[]) => T;
|
|
7
7
|
type ControllerExport<T extends Controller> = T | ControllerConstructor<T>;
|
|
8
|
+
type ControllerNamedExports = Record<string, unknown>;
|
|
9
|
+
type ControllerResult<T extends Controller, TNamedExports extends ControllerNamedExports> =
|
|
10
|
+
T & Omit<TNamedExports, 'default'>;
|
|
8
11
|
|
|
9
12
|
const bodyCache = new WeakMap<Context, unknown>();
|
|
10
13
|
|
|
@@ -22,8 +25,33 @@ export function getJaxContext() {
|
|
|
22
25
|
return context;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
export function createController<T extends Controller>(
|
|
26
|
-
|
|
28
|
+
export function createController<T extends Controller>(
|
|
29
|
+
controller: ControllerExport<T>,
|
|
30
|
+
): T;
|
|
31
|
+
export function createController<T extends Controller, TNamedExports extends ControllerNamedExports>(
|
|
32
|
+
controller: ControllerExport<T>,
|
|
33
|
+
namedExports: TNamedExports,
|
|
34
|
+
): ControllerResult<T, TNamedExports>;
|
|
35
|
+
export function createController<T extends Controller>(
|
|
36
|
+
controller: ControllerExport<T>,
|
|
37
|
+
namedExports: ControllerNamedExports = {},
|
|
38
|
+
): T & ControllerNamedExports {
|
|
39
|
+
const instance = (typeof controller === 'function' ? new controller() : controller) as T & ControllerNamedExports;
|
|
40
|
+
|
|
41
|
+
for (const [name, value] of Object.entries(namedExports)) {
|
|
42
|
+
if (name === 'default') {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Object.defineProperty(instance, name, {
|
|
47
|
+
configurable: true,
|
|
48
|
+
enumerable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return instance;
|
|
27
55
|
}
|
|
28
56
|
|
|
29
57
|
export abstract class Controller {
|