@xeno-js/cli 0.1.2 → 0.1.4
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/LICENSE +18 -12
- package/README.md +461 -236
- package/dist/index.js +1018 -688
- package/dist/index.js.map +1 -1
- package/package.json +91 -66
- package/.github/.copilot-instruction.md +0 -73
- package/.github/agents/git-operator.agent.md +0 -86
- package/.github/agents/unit-tester.agent.md +0 -68
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.husky/pre-push +0 -1
- package/.prettierignore +0 -7
- package/.prettierrc.json +0 -37
- package/commitlint.config.cjs +0 -39
- package/eslint.config.mjs +0 -306
- package/lint-staged.config.mjs +0 -5
- package/logo/logo.png +0 -0
- package/src/domain/contracts/icommand-cli.contracts.ts +0 -13
- package/src/domain/contracts/icommand-runner.contracts.ts +0 -3
- package/src/domain/contracts/idispatch.contracts.ts +0 -3
- package/src/domain/contracts/ifile-service.contracts.ts +0 -3
- package/src/domain/contracts/igenerator.contracts.ts +0 -5
- package/src/domain/contracts/index.ts +0 -6
- package/src/domain/contracts/iscaffold-strategy.contracts.ts +0 -7
- package/src/domain/index.ts +0 -1
- package/src/index.ts +0 -30
- package/src/infrastructure/bootstrap/bootstrapper.ts +0 -30
- package/src/infrastructure/bootstrap/index.ts +0 -1
- package/src/infrastructure/builder/builder.test.ts +0 -36
- package/src/infrastructure/builder/builder.ts +0 -15
- package/src/infrastructure/builder/index.ts +0 -1
- package/src/infrastructure/command_launcher/command.utils.ts +0 -31
- package/src/infrastructure/command_launcher/index.ts +0 -0
- package/src/infrastructure/commands/command-runner.service.ts +0 -36
- package/src/infrastructure/commands/generate.command.ts +0 -55
- package/src/infrastructure/commands/help.command.ts +0 -63
- package/src/infrastructure/commands/index.ts +0 -4
- package/src/infrastructure/commands/new-project.command.ts +0 -47
- package/src/infrastructure/dispatcher/dispatcher.ts +0 -45
- package/src/infrastructure/dispatcher/index.ts +0 -1
- package/src/infrastructure/file/file.service.ts +0 -14
- package/src/infrastructure/file/index.ts +0 -1
- package/src/infrastructure/generators/core/bootstrap.generator.ts +0 -118
- package/src/infrastructure/generators/core/command/cqrs.generator.ts +0 -63
- package/src/infrastructure/generators/core/command/generate-command.generator.ts +0 -191
- package/src/infrastructure/generators/core/command/generate-query.generator.ts +0 -150
- package/src/infrastructure/generators/core/drizzle-sql-lite.generator.ts +0 -76
- package/src/infrastructure/generators/core/drizzle.generator.ts +0 -86
- package/src/infrastructure/generators/core/env.generator.ts +0 -69
- package/src/infrastructure/generators/core/index.ts +0 -9
- package/src/infrastructure/generators/core/main.generator.ts +0 -41
- package/src/infrastructure/generators/core/packagejson.generator.ts +0 -70
- package/src/infrastructure/generators/core/readme.generator.ts +0 -427
- package/src/infrastructure/generators/core/registry.generator.ts +0 -40
- package/src/infrastructure/generators/core/tsconfig.generator.ts +0 -39
- package/src/infrastructure/generators/gitignore.generator.ts +0 -60
- package/src/infrastructure/generators/vue/app-vue.generator.ts +0 -21
- package/src/infrastructure/generators/vue/bootstrap-ts.generator.ts +0 -60
- package/src/infrastructure/generators/vue/command_query/cqrs.generator.ts +0 -36
- package/src/infrastructure/generators/vue/command_query/generate-command-vue.generator.ts +0 -153
- package/src/infrastructure/generators/vue/command_query/generate-query-vue.generator.ts +0 -161
- package/src/infrastructure/generators/vue/env.generator.ts +0 -39
- package/src/infrastructure/generators/vue/index-html.generator.ts +0 -24
- package/src/infrastructure/generators/vue/index.ts +0 -12
- package/src/infrastructure/generators/vue/main.generator.ts +0 -41
- package/src/infrastructure/generators/vue/packagejson.generator.ts +0 -50
- package/src/infrastructure/generators/vue/readme.generator.ts +0 -389
- package/src/infrastructure/generators/vue/registry-ts.generator.ts +0 -26
- package/src/infrastructure/generators/vue/router.generator.ts +0 -35
- package/src/infrastructure/generators/vue/tailwind.generator.ts +0 -17
- package/src/infrastructure/generators/vue/tsconfig.generator.ts +0 -38
- package/src/infrastructure/generators/vue/use-app.generator.ts +0 -38
- package/src/infrastructure/generators/vue/vite-config.generator.ts +0 -35
- package/src/infrastructure/index.ts +0 -5
- package/src/infrastructure/strategies/core-scaffold.strategy.ts +0 -100
- package/src/infrastructure/strategies/index.ts +0 -2
- package/src/infrastructure/strategies/vue-scaffold.strategy.ts +0 -79
- package/src/shared/constants/command.constants.ts +0 -18
- package/src/shared/constants/core.constants.ts +0 -7
- package/src/shared/constants/index.ts +0 -2
- package/src/shared/index.ts +0 -3
- package/src/shared/types/common.types.ts +0 -68
- package/src/shared/types/index.ts +0 -1
- package/src/shared/utils/guards.utils.ts +0 -334
- package/src/shared/utils/index.ts +0 -2
- package/src/shared/utils/string.utils.ts +0 -60
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -13
package/dist/index.js
CHANGED
|
@@ -474,20 +474,27 @@ var init_generate_command_generator = __esm({
|
|
|
474
474
|
}
|
|
475
475
|
_fileService;
|
|
476
476
|
async generate(pascalName, lowerName, tokenPrefix, componentDir, hasZod, hasDrizzle) {
|
|
477
|
+
const appDir = import_node_path.default.join(componentDir, "application");
|
|
478
|
+
const infraDir = import_node_path.default.join(componentDir, "infrastructure");
|
|
479
|
+
const domainDir = import_node_path.default.join(componentDir, "domain");
|
|
480
|
+
const presDir = import_node_path.default.join(componentDir, "presentation");
|
|
477
481
|
const commandContent = `import { Command } from '@xeno-js/core';
|
|
482
|
+
export interface ${pascalName}Payload {
|
|
483
|
+
// TODO: Define your command payload properties here
|
|
484
|
+
}
|
|
478
485
|
|
|
479
486
|
/*
|
|
480
487
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
481
|
-
* Please replace <
|
|
488
|
+
* Please replace <void> with your specific payload and response types.
|
|
482
489
|
*/
|
|
483
|
-
export class ${pascalName}Command extends Command<
|
|
484
|
-
constructor(public readonly payload:
|
|
490
|
+
export class ${pascalName}Command extends Command<void> {
|
|
491
|
+
constructor(public readonly payload: ${pascalName}Payload) {
|
|
485
492
|
super('${tokenPrefix}_COMMAND_HANDLER');
|
|
486
493
|
}
|
|
487
494
|
}
|
|
488
495
|
`;
|
|
489
496
|
await this._fileService.writeFileRecursive(
|
|
490
|
-
import_node_path.default.join(
|
|
497
|
+
import_node_path.default.join(appDir, `${lowerName}.command.ts`),
|
|
491
498
|
commandContent
|
|
492
499
|
);
|
|
493
500
|
const handlerContent = `import type { IFactory, ResultType, UserContext } from '@xeno-js/core';
|
|
@@ -496,16 +503,16 @@ import { ${pascalName}Command } from './${lowerName}.command';
|
|
|
496
503
|
|
|
497
504
|
/*
|
|
498
505
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
499
|
-
* Please replace <
|
|
506
|
+
* Please replace <void> with your specific response type.
|
|
500
507
|
*/
|
|
501
|
-
export class ${pascalName}Handler extends BaseHandler<${pascalName}Command,
|
|
508
|
+
export class ${pascalName}Handler extends BaseHandler<${pascalName}Command, void> {
|
|
502
509
|
constructor(
|
|
503
510
|
identityFactory: IFactory<void, UserContext>
|
|
504
511
|
) {
|
|
505
512
|
super(identityFactory);
|
|
506
513
|
}
|
|
507
514
|
|
|
508
|
-
|
|
515
|
+
protected async executeAsync(request: ${pascalName}Command, singal: AbortSignal): Promise<ResultType<void>> {
|
|
509
516
|
// TODO: Implement your business logic here
|
|
510
517
|
|
|
511
518
|
return Result.ok();
|
|
@@ -513,18 +520,18 @@ export class ${pascalName}Handler extends BaseHandler<${pascalName}Command, any>
|
|
|
513
520
|
}
|
|
514
521
|
`;
|
|
515
522
|
await this._fileService.writeFileRecursive(
|
|
516
|
-
import_node_path.default.join(
|
|
523
|
+
import_node_path.default.join(appDir, `${lowerName}.handler.ts`),
|
|
517
524
|
handlerContent
|
|
518
525
|
);
|
|
519
526
|
const controllerContent = `import type { IContextAccessor, IMediator, RequestContext, ResponseDto } from '@xeno-js/core';
|
|
520
527
|
import { BaseController } from '@xeno-js/core';
|
|
521
|
-
import { ${pascalName}Command } from '
|
|
528
|
+
import { ${pascalName}Command, ${pascalName}Payload } from '../application/${lowerName}.command';
|
|
522
529
|
|
|
523
530
|
/*
|
|
524
531
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
525
|
-
* Please replace <
|
|
532
|
+
* Please replace <{pascalName}Payload, void> with your specific request and response types.
|
|
526
533
|
*/
|
|
527
|
-
export class ${pascalName}Controller extends BaseController
|
|
534
|
+
export class ${pascalName}Controller extends BaseController<${pascalName}Payload, void> {
|
|
528
535
|
constructor(
|
|
529
536
|
requestContext: IContextAccessor<RequestContext>,
|
|
530
537
|
mediator: IMediator,
|
|
@@ -532,7 +539,7 @@ export class ${pascalName}Controller extends BaseController<any, any> {
|
|
|
532
539
|
super(requestContext, mediator);
|
|
533
540
|
}
|
|
534
541
|
|
|
535
|
-
public async handle(request:
|
|
542
|
+
public async handle(request: {pascalName}Payload): Promise<ResponseDto<void>> {
|
|
536
543
|
const cmd = new ${pascalName}Command(request);
|
|
537
544
|
const result = await this._send(cmd);
|
|
538
545
|
|
|
@@ -545,7 +552,7 @@ export class ${pascalName}Controller extends BaseController<any, any> {
|
|
|
545
552
|
}
|
|
546
553
|
`;
|
|
547
554
|
await this._fileService.writeFileRecursive(
|
|
548
|
-
import_node_path.default.join(
|
|
555
|
+
import_node_path.default.join(presDir, `${lowerName}.controller.ts`),
|
|
549
556
|
controllerContent
|
|
550
557
|
);
|
|
551
558
|
const entityContent = `import { Entity } from '@xeno-js/core';
|
|
@@ -569,23 +576,27 @@ export class ${pascalName} extends Entity<${pascalName}Props> {
|
|
|
569
576
|
}
|
|
570
577
|
`;
|
|
571
578
|
await this._fileService.writeFileRecursive(
|
|
572
|
-
import_node_path.default.join(
|
|
579
|
+
import_node_path.default.join(domainDir, `${lowerName}.entity.ts`),
|
|
573
580
|
entityContent
|
|
574
581
|
);
|
|
575
582
|
if (hasZod) {
|
|
576
583
|
const zodContent = `import { z } from 'zod';
|
|
577
584
|
import { ZodUtils } from '@xeno-js/core';
|
|
578
585
|
|
|
579
|
-
|
|
586
|
+
/**
|
|
580
587
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
581
588
|
* Define the actual Zod validation schema for your command payload.
|
|
582
589
|
*/
|
|
583
590
|
export const ${pascalName}Schema = ZodUtils.createCommandSchema({
|
|
584
|
-
payload: z.
|
|
591
|
+
payload: z.object({
|
|
592
|
+
// TODO: Define strict validation rules here, e.g.:
|
|
593
|
+
// email: z.string().email(),
|
|
594
|
+
// name: z.string().min(1),
|
|
595
|
+
}),
|
|
585
596
|
});
|
|
586
597
|
`;
|
|
587
598
|
await this._fileService.writeFileRecursive(
|
|
588
|
-
import_node_path.default.join(
|
|
599
|
+
import_node_path.default.join(infraDir, `${lowerName}.schema-zod.ts`),
|
|
589
600
|
zodContent
|
|
590
601
|
);
|
|
591
602
|
}
|
|
@@ -607,14 +618,14 @@ export type ${pascalName}Dto = typeof ${lowerName}s.$inferSelect;
|
|
|
607
618
|
export type New${pascalName}Dto = typeof ${lowerName}s.$inferInsert;
|
|
608
619
|
`;
|
|
609
620
|
await this._fileService.writeFileRecursive(
|
|
610
|
-
import_node_path.default.join(
|
|
621
|
+
import_node_path.default.join(infraDir, `${lowerName}.schema-db.ts`),
|
|
611
622
|
dbContent
|
|
612
623
|
);
|
|
613
624
|
}
|
|
614
625
|
const moduleContent = `import type { IServiceContainer, IConfigurationService } from '@xeno-js/core';
|
|
615
626
|
import { TOKENS } from '@xeno-js/core';
|
|
616
|
-
import { ${pascalName}Controller } from '
|
|
617
|
-
import { ${pascalName}Handler } from '
|
|
627
|
+
import { ${pascalName}Controller } from './presentation/${lowerName}.controller';
|
|
628
|
+
import { ${pascalName}Handler } from './application/${lowerName}.handler';
|
|
618
629
|
|
|
619
630
|
export const ${pascalName}Module = Object.freeze({
|
|
620
631
|
register(opts: IServiceContainer<any>, _config: IConfigurationService): void {
|
|
@@ -656,14 +667,21 @@ var init_generate_query_generator = __esm({
|
|
|
656
667
|
}
|
|
657
668
|
_fileService;
|
|
658
669
|
async generate(pascalName, lowerName, tokenPrefix, componentDir, hasZod) {
|
|
670
|
+
const appDir = import_node_path2.default.join(componentDir, "application");
|
|
671
|
+
const infraDir = import_node_path2.default.join(componentDir, "infrastructure");
|
|
672
|
+
const domainDir = import_node_path2.default.join(componentDir, "domain");
|
|
673
|
+
const presDir = import_node_path2.default.join(componentDir, "presentation");
|
|
659
674
|
const queryContent = `import { BaseQuery } from '@xeno-js/core';
|
|
675
|
+
export interface ${pascalName}Payload {
|
|
676
|
+
// TODO: Define your command payload properties here
|
|
677
|
+
}
|
|
660
678
|
|
|
661
679
|
/*
|
|
662
680
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
663
|
-
* Please replace <
|
|
681
|
+
* Please replace <void> with your specific payload and response types.
|
|
664
682
|
*/
|
|
665
|
-
export class ${pascalName}Query extends BaseQuery<
|
|
666
|
-
constructor(public readonly payload:
|
|
683
|
+
export class ${pascalName}Query extends BaseQuery<void> {
|
|
684
|
+
constructor(public readonly payload: ${pascalName}Payload) {
|
|
667
685
|
super(
|
|
668
686
|
'${tokenPrefix}_QUERY_HANDLER',
|
|
669
687
|
// Default cache options:
|
|
@@ -679,7 +697,7 @@ export class ${pascalName}Query extends BaseQuery<any> {
|
|
|
679
697
|
}
|
|
680
698
|
`;
|
|
681
699
|
await this._fileService.writeFileRecursive(
|
|
682
|
-
import_node_path2.default.join(
|
|
700
|
+
import_node_path2.default.join(appDir, `${lowerName}.query.ts`),
|
|
683
701
|
queryContent
|
|
684
702
|
);
|
|
685
703
|
const handlerContent = `import type { IFactory, ResultType, UserContext } from '@xeno-js/core';
|
|
@@ -688,9 +706,9 @@ import { ${pascalName}Query } from './${lowerName}.query';
|
|
|
688
706
|
|
|
689
707
|
/*
|
|
690
708
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
691
|
-
* Please replace <
|
|
709
|
+
* Please replace <void> with your specific response type.
|
|
692
710
|
*/
|
|
693
|
-
export class ${pascalName}Handler extends BaseHandler<${pascalName}Query,
|
|
711
|
+
export class ${pascalName}Handler extends BaseHandler<${pascalName}Query, void> {
|
|
694
712
|
constructor(
|
|
695
713
|
identityFactory: IFactory<void, UserContext>
|
|
696
714
|
// TODO: Inject your ReadDao or DataSource here
|
|
@@ -698,7 +716,7 @@ export class ${pascalName}Handler extends BaseHandler<${pascalName}Query, any> {
|
|
|
698
716
|
super(identityFactory);
|
|
699
717
|
}
|
|
700
718
|
|
|
701
|
-
|
|
719
|
+
protected async executeAsync(request: ${pascalName}Query, singal: AbortSignal): Promise<ResultType<void>> {
|
|
702
720
|
// TODO: Implement your query logic here
|
|
703
721
|
|
|
704
722
|
return Result.ok();
|
|
@@ -706,18 +724,18 @@ export class ${pascalName}Handler extends BaseHandler<${pascalName}Query, any> {
|
|
|
706
724
|
}
|
|
707
725
|
`;
|
|
708
726
|
await this._fileService.writeFileRecursive(
|
|
709
|
-
import_node_path2.default.join(
|
|
727
|
+
import_node_path2.default.join(appDir, `${lowerName}.handler.ts`),
|
|
710
728
|
handlerContent
|
|
711
729
|
);
|
|
712
730
|
const controllerContent = `import type { IContextAccessor, IMediator, RequestContext, ResponseDto } from '@xeno-js/core';
|
|
713
731
|
import { BaseController } from '@xeno-js/core';
|
|
714
|
-
import { ${pascalName}Query } from '
|
|
732
|
+
import { ${pascalName}Query, ${pascalName}Payload } from '../presentation/${lowerName}.query';
|
|
715
733
|
|
|
716
734
|
/*
|
|
717
735
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
718
|
-
* Please replace
|
|
736
|
+
* Please replace <${pascalName}Payload, void> with your specific request and response types.
|
|
719
737
|
*/
|
|
720
|
-
export class ${pascalName}Controller extends BaseController
|
|
738
|
+
export class ${pascalName}Controller extends BaseController<${pascalName}Payload, void> {
|
|
721
739
|
constructor(
|
|
722
740
|
requestContext: IContextAccessor<RequestContext>,
|
|
723
741
|
mediator: IMediator,
|
|
@@ -725,7 +743,7 @@ export class ${pascalName}Controller extends BaseController<any, any> {
|
|
|
725
743
|
super(requestContext, mediator);
|
|
726
744
|
}
|
|
727
745
|
|
|
728
|
-
public async handle(request:
|
|
746
|
+
public async handle(request: ${pascalName}Payload): Promise<ResponseDto<void>> {
|
|
729
747
|
const query = new ${pascalName}Query(request);
|
|
730
748
|
const result = await this._query(query);
|
|
731
749
|
|
|
@@ -738,23 +756,51 @@ export class ${pascalName}Controller extends BaseController<any, any> {
|
|
|
738
756
|
}
|
|
739
757
|
`;
|
|
740
758
|
await this._fileService.writeFileRecursive(
|
|
741
|
-
import_node_path2.default.join(
|
|
759
|
+
import_node_path2.default.join(presDir, `${lowerName}.controller.ts`),
|
|
742
760
|
controllerContent
|
|
743
761
|
);
|
|
762
|
+
const entityContent = `import { Entity } from '@xeno-js/core';
|
|
763
|
+
|
|
764
|
+
export interface ${pascalName}Props {
|
|
765
|
+
// TODO: Define your entity properties
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/*
|
|
769
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
770
|
+
* Adjust the properties and types according to your domain logic.
|
|
771
|
+
*/
|
|
772
|
+
export class ${pascalName} extends Entity<${pascalName}Props> {
|
|
773
|
+
private constructor(props: ${pascalName}Props, id?: string) {
|
|
774
|
+
super(props, id);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
static create(props: ${pascalName}Props, id?: string): ${pascalName} {
|
|
778
|
+
return new ${pascalName}(props, id);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
`;
|
|
782
|
+
await this._fileService.writeFileRecursive(
|
|
783
|
+
import_node_path2.default.join(domainDir, `${lowerName}.entity.ts`),
|
|
784
|
+
entityContent
|
|
785
|
+
);
|
|
744
786
|
if (hasZod) {
|
|
745
787
|
const zodContent = `import { z } from 'zod';
|
|
746
788
|
import { ZodUtils } from '@xeno-js/core';
|
|
747
789
|
|
|
748
|
-
|
|
790
|
+
/**
|
|
749
791
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
750
|
-
* Define the actual Zod validation schema for your
|
|
792
|
+
* Define the actual Zod validation schema for your command payload.
|
|
751
793
|
*/
|
|
752
|
-
export const ${pascalName}Schema = ZodUtils.
|
|
753
|
-
payload: z.
|
|
794
|
+
export const ${pascalName}Schema = ZodUtils.createCommandSchema({
|
|
795
|
+
payload: z.object({
|
|
796
|
+
// TODO: Define strict validation rules here, e.g.:
|
|
797
|
+
// email: z.string().email(),
|
|
798
|
+
// name: z.string().min(1),
|
|
799
|
+
}),
|
|
754
800
|
});
|
|
755
801
|
`;
|
|
756
802
|
await this._fileService.writeFileRecursive(
|
|
757
|
-
import_node_path2.default.join(
|
|
803
|
+
import_node_path2.default.join(infraDir, `${lowerName}.schema-zod.ts`),
|
|
758
804
|
zodContent
|
|
759
805
|
);
|
|
760
806
|
}
|
|
@@ -772,8 +818,8 @@ export const ${pascalName}Module = Object.freeze({
|
|
|
772
818
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
773
819
|
* Please copy and paste the following tokens to your MyRegistry interface (usually in src/registry.ts):
|
|
774
820
|
*
|
|
775
|
-
* ${tokenPrefix}_QUERY_CONTROLLER: IController
|
|
776
|
-
* ${tokenPrefix}_QUERY_HANDLER: IHandler<${pascalName}Query,
|
|
821
|
+
* ${tokenPrefix}_QUERY_CONTROLLER: IController<${pascalName}Query, void>;
|
|
822
|
+
* ${tokenPrefix}_QUERY_HANDLER: IHandler<${pascalName}Query, void>;
|
|
777
823
|
*/
|
|
778
824
|
}
|
|
779
825
|
} as const);
|
|
@@ -866,6 +912,9 @@ var init_generate_command_vue_generator = __esm({
|
|
|
866
912
|
}
|
|
867
913
|
_fileService;
|
|
868
914
|
async generate(pascalName, lowerName, tokenPrefix, componentDir) {
|
|
915
|
+
const appDir = import_node_path4.default.join(componentDir, "application");
|
|
916
|
+
const domainDir = import_node_path4.default.join(componentDir, "domain");
|
|
917
|
+
const presDir = import_node_path4.default.join(componentDir, "presentation");
|
|
869
918
|
const commandContent = `import { Command } from '@xeno-js/vue';
|
|
870
919
|
|
|
871
920
|
export class ${pascalName}Command extends Command {
|
|
@@ -875,13 +924,13 @@ export class ${pascalName}Command extends Command {
|
|
|
875
924
|
}
|
|
876
925
|
`;
|
|
877
926
|
await this._fileService.writeFileRecursive(
|
|
878
|
-
import_node_path4.default.join(
|
|
927
|
+
import_node_path4.default.join(appDir, `${lowerName}.command.ts`),
|
|
879
928
|
commandContent
|
|
880
929
|
);
|
|
881
|
-
const handlerContent = `import type {
|
|
882
|
-
import { AppError, Result } from '@xeno-js/vue';
|
|
930
|
+
const handlerContent = `import type { ResultType } from '@xeno-js/vue';
|
|
931
|
+
import { AppError, BaseHandler, Result } from '@xeno-js/vue';
|
|
883
932
|
import type { ${pascalName}Command } from './${lowerName}.command';
|
|
884
|
-
import type { ${pascalName}Response } from '
|
|
933
|
+
import type { ${pascalName}Response } from '../entity/${lowerName}.model';
|
|
885
934
|
|
|
886
935
|
/*
|
|
887
936
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
@@ -896,28 +945,26 @@ import type { ${pascalName}Response } from './${lowerName}.model';
|
|
|
896
945
|
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
897
946
|
* });
|
|
898
947
|
*/
|
|
899
|
-
export class ${pascalName}Handler
|
|
948
|
+
export class ${pascalName}Handler extends BaseHandler<${pascalName}Command, ${pascalName}Response> {
|
|
900
949
|
constructor(
|
|
901
950
|
// TODO: Inject your remote DataSources or other services here
|
|
902
951
|
) {}
|
|
903
952
|
|
|
904
|
-
|
|
905
|
-
AppError.throwIfAborted(signal, this.constructor.name);
|
|
906
|
-
|
|
953
|
+
protected async executeAsync(command: ${pascalName}Command, signal: AbortSignal): Promise<ResultType<${pascalName}Response>> {
|
|
907
954
|
// TODO: Implement your frontend business logic or API calls here
|
|
908
955
|
return Result.ok();
|
|
909
956
|
}
|
|
910
957
|
}
|
|
911
958
|
`;
|
|
912
959
|
await this._fileService.writeFileRecursive(
|
|
913
|
-
import_node_path4.default.join(
|
|
960
|
+
import_node_path4.default.join(appDir, `${lowerName}.handler.ts`),
|
|
914
961
|
handlerContent
|
|
915
962
|
);
|
|
916
963
|
const composableContent = `import { ref } from 'vue';
|
|
917
964
|
import { AppError, Result, type ApiResponseDto, type ResultType } from '@xeno-js/vue';
|
|
918
965
|
import { ServicesUtils } from '@/use-app';
|
|
919
|
-
import { ${pascalName}Command } from '
|
|
920
|
-
import type { ${pascalName}Request, ${pascalName}Response } from '
|
|
966
|
+
import { ${pascalName}Command } from '../application/${lowerName}.command';
|
|
967
|
+
import type { ${pascalName}Request, ${pascalName}Response } from '../domain/${lowerName}.model';
|
|
921
968
|
|
|
922
969
|
/*
|
|
923
970
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
@@ -984,7 +1031,7 @@ export function use${pascalName}() {
|
|
|
984
1031
|
}
|
|
985
1032
|
`;
|
|
986
1033
|
await this._fileService.writeFileRecursive(
|
|
987
|
-
import_node_path4.default.join(
|
|
1034
|
+
import_node_path4.default.join(presDir, `use-${lowerName}.composable.ts`),
|
|
988
1035
|
composableContent
|
|
989
1036
|
);
|
|
990
1037
|
const modelContent = `export interface ${pascalName}Request {
|
|
@@ -996,7 +1043,7 @@ export interface ${pascalName}Response {
|
|
|
996
1043
|
}
|
|
997
1044
|
`;
|
|
998
1045
|
await this._fileService.writeFileRecursive(
|
|
999
|
-
import_node_path4.default.join(
|
|
1046
|
+
import_node_path4.default.join(domainDir, `${lowerName}.model.ts`),
|
|
1000
1047
|
modelContent
|
|
1001
1048
|
);
|
|
1002
1049
|
}
|
|
@@ -1020,6 +1067,9 @@ var init_generate_query_vue_generator = __esm({
|
|
|
1020
1067
|
}
|
|
1021
1068
|
_fileService;
|
|
1022
1069
|
async generate(pascalName, lowerName, tokenPrefix, componentDir) {
|
|
1070
|
+
const appDir = import_node_path5.default.join(componentDir, "application");
|
|
1071
|
+
const domainDir = import_node_path5.default.join(componentDir, "domain");
|
|
1072
|
+
const presDir = import_node_path5.default.join(componentDir, "presentation");
|
|
1023
1073
|
const queryContent = `import { Query } from '@xeno-js/vue';
|
|
1024
1074
|
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
1025
1075
|
|
|
@@ -1036,13 +1086,13 @@ export class ${pascalName}Query extends Query<${pascalName}Response> {
|
|
|
1036
1086
|
}
|
|
1037
1087
|
`;
|
|
1038
1088
|
await this._fileService.writeFileRecursive(
|
|
1039
|
-
import_node_path5.default.join(
|
|
1089
|
+
import_node_path5.default.join(appDir, `${lowerName}.query.ts`),
|
|
1040
1090
|
queryContent
|
|
1041
1091
|
);
|
|
1042
|
-
const handlerContent = `import type {
|
|
1043
|
-
import { AppError, Result } from '@xeno-js/vue';
|
|
1092
|
+
const handlerContent = `import type { ResultType } from '@xeno-js/vue';
|
|
1093
|
+
import { AppError, BaseHandler, Result } from '@xeno-js/vue';
|
|
1044
1094
|
import type { ${pascalName}Query } from './${lowerName}.query';
|
|
1045
|
-
import type { ${pascalName}Response } from '
|
|
1095
|
+
import type { ${pascalName}Response } from '../domain/${lowerName}.model';
|
|
1046
1096
|
|
|
1047
1097
|
/*
|
|
1048
1098
|
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
@@ -1057,28 +1107,26 @@ import type { ${pascalName}Response } from './${lowerName}.model';
|
|
|
1057
1107
|
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
1058
1108
|
* });
|
|
1059
1109
|
*/
|
|
1060
|
-
export class ${pascalName}Handler
|
|
1110
|
+
export class ${pascalName}Handler extends BaseHandler<${pascalName}Query, ${pascalName}Response> {
|
|
1061
1111
|
constructor(
|
|
1062
1112
|
// TODO: Inject your remote DataSources or other services here
|
|
1063
1113
|
) {}
|
|
1064
1114
|
|
|
1065
|
-
|
|
1066
|
-
AppError.throwIfAborted(signal, this.constructor.name);
|
|
1067
|
-
|
|
1115
|
+
protected async executeAsync(query: ${pascalName}Query, signal: AbortSignal): Promise<ResultType<${pascalName}Response>> {
|
|
1068
1116
|
// TODO: Implement your frontend business logic or API calls here
|
|
1069
1117
|
return Result.ok();
|
|
1070
1118
|
}
|
|
1071
1119
|
}
|
|
1072
1120
|
`;
|
|
1073
1121
|
await this._fileService.writeFileRecursive(
|
|
1074
|
-
import_node_path5.default.join(
|
|
1122
|
+
import_node_path5.default.join(appDir, `${lowerName}.handler.ts`),
|
|
1075
1123
|
handlerContent
|
|
1076
1124
|
);
|
|
1077
1125
|
const composableContent = `import { ref } from 'vue';
|
|
1078
1126
|
import { AppError, Result, type ApiResponseDto, type ResultType } from '@xeno-js/vue';
|
|
1079
1127
|
import { ServicesUtils } from '@/use-app';
|
|
1080
|
-
import { ${pascalName}Query } from '
|
|
1081
|
-
import type { ${pascalName}Response } from '
|
|
1128
|
+
import { ${pascalName}Query } from '../application/${lowerName}.query';
|
|
1129
|
+
import type { ${pascalName}Response } from '../domain/${lowerName}.model';
|
|
1082
1130
|
|
|
1083
1131
|
export function use${pascalName}() {
|
|
1084
1132
|
const loading = ref(false);
|
|
@@ -1149,7 +1197,7 @@ const fetch = async (): Promise<ResultType<${pascalName}Response>> => {
|
|
|
1149
1197
|
}
|
|
1150
1198
|
`;
|
|
1151
1199
|
await this._fileService.writeFileRecursive(
|
|
1152
|
-
import_node_path5.default.join(
|
|
1200
|
+
import_node_path5.default.join(presDir, `use-${lowerName}.composable.ts`),
|
|
1153
1201
|
composableContent
|
|
1154
1202
|
);
|
|
1155
1203
|
const modelContent = `export interface ${pascalName}Response {
|
|
@@ -1157,7 +1205,7 @@ const fetch = async (): Promise<ResultType<${pascalName}Response>> => {
|
|
|
1157
1205
|
}
|
|
1158
1206
|
`;
|
|
1159
1207
|
await this._fileService.writeFileRecursive(
|
|
1160
|
-
import_node_path5.default.join(
|
|
1208
|
+
import_node_path5.default.join(domainDir, `${lowerName}.model.ts`),
|
|
1161
1209
|
modelContent
|
|
1162
1210
|
);
|
|
1163
1211
|
}
|
|
@@ -1230,28 +1278,27 @@ var init_packagejson_generator = __esm({
|
|
|
1230
1278
|
scripts: {
|
|
1231
1279
|
dev: "vite",
|
|
1232
1280
|
build: "vue-tsc -b && vite build",
|
|
1233
|
-
preview: "vite preview"
|
|
1234
|
-
g: "xeno-js generate"
|
|
1281
|
+
preview: "vite preview"
|
|
1235
1282
|
},
|
|
1236
1283
|
dependencies: {
|
|
1237
1284
|
"@xeno-js/vue": "latest",
|
|
1238
|
-
"vue": "^3.5.
|
|
1285
|
+
"vue": "^3.5.43"
|
|
1239
1286
|
},
|
|
1240
1287
|
devDependencies: {
|
|
1241
1288
|
"@types/node": "^20.0.0",
|
|
1242
1289
|
"@vitejs/plugin-vue": "^5.1.0",
|
|
1243
|
-
"typescript": "^5.
|
|
1290
|
+
"typescript": "^5.8.0",
|
|
1244
1291
|
"vite": "^5.4.0",
|
|
1245
|
-
"vue-tsc": "^
|
|
1292
|
+
"vue-tsc": "^3.3.11"
|
|
1246
1293
|
}
|
|
1247
1294
|
};
|
|
1248
|
-
if (options.axios) packageJson.dependencies["axios"] = "^1.
|
|
1295
|
+
if (options.axios) packageJson.dependencies["axios"] = "^1.20.0";
|
|
1249
1296
|
if (options.cockatiel) packageJson.dependencies["cockatiel"] = "^4.0.0";
|
|
1250
|
-
if (options.supabase) packageJson.dependencies["@supabase/supabase-js"] = "^2.
|
|
1251
|
-
if (options.sentry) packageJson.dependencies["@sentry/vue"] = "^
|
|
1297
|
+
if (options.supabase) packageJson.dependencies["@supabase/supabase-js"] = "^2.117.1";
|
|
1298
|
+
if (options.sentry) packageJson.dependencies["@sentry/vue"] = "^10.75.2";
|
|
1252
1299
|
if (options.pinia) packageJson.dependencies["pinia"] = "^2.2.0";
|
|
1253
|
-
if (options.router) packageJson.dependencies["vue-router"] = "^4.4
|
|
1254
|
-
if (options.zod) packageJson.dependencies["zod"] = "^4.
|
|
1300
|
+
if (options.router) packageJson.dependencies["vue-router"] = "^4.6.4";
|
|
1301
|
+
if (options.zod) packageJson.dependencies["zod"] = "^4.6.5";
|
|
1255
1302
|
if (options.tailwind) {
|
|
1256
1303
|
packageJson.devDependencies["tailwindcss"] = "^4.0.0";
|
|
1257
1304
|
packageJson.devDependencies["@tailwindcss/vite"] = "^4.0.0";
|
|
@@ -1416,28 +1463,30 @@ var init_readme_generator = __esm({
|
|
|
1416
1463
|
this._fileService = _fileService;
|
|
1417
1464
|
}
|
|
1418
1465
|
_fileService;
|
|
1419
|
-
async generate(projectPath,
|
|
1420
|
-
const content =
|
|
1421
|
-
|
|
1466
|
+
async generate(projectPath, _options) {
|
|
1467
|
+
const content = `
|
|
1422
1468
|
<div align="center">
|
|
1423
|
-
<img src="logo/logo.png" alt="Xeno Logo" width="140" />
|
|
1469
|
+
<img src="logo/logo.png" alt="Xeno Vue Logo" width="140" />
|
|
1424
1470
|
|
|
1425
1471
|
<h1>Xeno Vue</h1>
|
|
1426
1472
|
|
|
1427
|
-
<p><
|
|
1473
|
+
<p><strong>Your UI framework handles the UI. Xeno handles the application.</strong></p>
|
|
1474
|
+
|
|
1475
|
+
<p>
|
|
1476
|
+
Application architecture for Vue with explicit dependency injection,
|
|
1477
|
+
CQRS, composable pipelines, and clear boundaries between presentation,
|
|
1478
|
+
application behavior, and infrastructure.
|
|
1479
|
+
</p>
|
|
1428
1480
|
|
|
1429
1481
|
<p>
|
|
1430
|
-
<a href="https://github.com/xeno-js/xeno-js">
|
|
1431
|
-
<img src="https://img.shields.io/badge/Powered%20by-Xeno-blueviolet?style=flat-square" alt="Powered by Xeno" />
|
|
1432
|
-
</a>
|
|
1433
|
-
<a href="https://github.com/xeno-js/xeno-fe/blob/main/LICENSE">
|
|
1434
|
-
<img src="https://img.shields.io/npm/l/@xeno-js/vue?style=flat-square" alt="License: ISC" />
|
|
1435
|
-
</a>
|
|
1436
1482
|
<a href="https://www.npmjs.com/package/@xeno-js/vue">
|
|
1437
|
-
<img src="https://img.shields.io/npm/v/@xeno-js/vue?style=flat-square" alt="
|
|
1483
|
+
<img src="https://img.shields.io/npm/v/@xeno-js/vue?style=flat-square" alt="npm version" />
|
|
1438
1484
|
</a>
|
|
1439
|
-
<a href="https://
|
|
1440
|
-
<img src="https://img.shields.io/
|
|
1485
|
+
<a href="https://github.com/xeno-js/xeno-fe">
|
|
1486
|
+
<img src="https://img.shields.io/github/stars/xeno-js/xeno-fe?style=flat-square" alt="GitHub stars" />
|
|
1487
|
+
</a>
|
|
1488
|
+
<a href="https://img.shields.io/npm/l/@xeno-js/vue?style=flat-square">
|
|
1489
|
+
<img src="https://img.shields.io/npm/l/@xeno-js/vue?style=flat-square" alt="License: MIT" />
|
|
1441
1490
|
</a>
|
|
1442
1491
|
</p>
|
|
1443
1492
|
</div>
|
|
@@ -1446,352 +1495,585 @@ var init_readme_generator = __esm({
|
|
|
1446
1495
|
|
|
1447
1496
|
## What is Xeno Vue?
|
|
1448
1497
|
|
|
1449
|
-
**Xeno Vue** (\`@xeno-js/vue\`)
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1498
|
+
**Xeno Vue** (\`@xeno-js / vue\`) brings Xeno's application architecture to Vue applications.
|
|
1499
|
+
|
|
1500
|
+
It provides the composition root and application building blocks needed to keep
|
|
1501
|
+
application behavior explicit instead of putting all of it inside Vue
|
|
1502
|
+
components.
|
|
1503
|
+
|
|
1504
|
+
The package is built around:
|
|
1505
|
+
|
|
1506
|
+
* explicit dependency injection;
|
|
1507
|
+
* a typed application registry;
|
|
1508
|
+
* commands and queries through a client-side mediator;
|
|
1509
|
+
* composable application pipelines;
|
|
1510
|
+
* remote data source boundaries;
|
|
1511
|
+
* browser request and identity context;
|
|
1512
|
+
* optional authentication, logging, validation, and caching integrations.
|
|
1453
1513
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
It treats the browser as a complex distributed client, not just a document
|
|
1458
|
-
viewer.
|
|
1514
|
+
The goal is simple:
|
|
1515
|
+
|
|
1516
|
+
> **Keep Vue responsible for presentation. Keep application behavior explicit.**
|
|
1459
1517
|
|
|
1460
1518
|
---
|
|
1461
1519
|
|
|
1462
|
-
##
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
Xeno
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
executed or an API call fires, using the native \`Mediator\` pipeline.
|
|
1520
|
+
## The boundary
|
|
1521
|
+
|
|
1522
|
+
A Vue application does not need to put every concern into components, stores, or router handlers.
|
|
1523
|
+
|
|
1524
|
+
Xeno gives the application layer a distinct place to live:
|
|
1525
|
+
|
|
1526
|
+
\`\`\`text
|
|
1527
|
+
Vue UI
|
|
1528
|
+
\u2193
|
|
1529
|
+
Application
|
|
1530
|
+
\u2193
|
|
1531
|
+
Domain / Shared
|
|
1532
|
+
\u2193
|
|
1533
|
+
Infrastructure
|
|
1534
|
+
\u2193
|
|
1535
|
+
HTTP / external systems
|
|
1536
|
+
\`\`\`
|
|
1537
|
+
|
|
1538
|
+
Vue remains your presentation layer.
|
|
1539
|
+
|
|
1540
|
+
Xeno provides the application composition and execution model around it.
|
|
1484
1541
|
|
|
1485
1542
|
---
|
|
1486
1543
|
|
|
1487
|
-
##
|
|
1544
|
+
## Why Xeno Vue?
|
|
1545
|
+
|
|
1546
|
+
As an application grows, API calls, validation, logging, caching, authentication,
|
|
1547
|
+
and application decisions can end up spread across components.
|
|
1548
|
+
|
|
1549
|
+
Xeno makes those responsibilities explicit.
|
|
1550
|
+
|
|
1551
|
+
### Explicit dependency injection
|
|
1552
|
+
|
|
1553
|
+
\`XenoAppBuilder\` is the composition root.
|
|
1554
|
+
|
|
1555
|
+
Dependencies are registered in code instead of being discovered through decorators,
|
|
1556
|
+
runtime scanning, or hidden framework conventions.
|
|
1488
1557
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
documentation hub:
|
|
1558
|
+
\`\`\`ts
|
|
1559
|
+
import { XenoAppBuilder } from '@xeno-js/vue'
|
|
1492
1560
|
|
|
1493
|
-
|
|
1561
|
+
const builder = XenoAppBuilder.create()
|
|
1562
|
+
\`\`\`
|
|
1494
1563
|
|
|
1495
|
-
|
|
1496
|
-
IoC building (\`XenoAppBuilder\`), Vue \`Provide/Inject\` boundaries, and CQRS
|
|
1497
|
-
composables.
|
|
1564
|
+
You configure the services your application needs through the builder.
|
|
1498
1565
|
|
|
1499
1566
|
---
|
|
1500
1567
|
|
|
1501
|
-
|
|
1568
|
+
### CQRS in the browser
|
|
1569
|
+
|
|
1570
|
+
The client mediator exposes two execution paths:
|
|
1571
|
+
|
|
1572
|
+
\`\`\`text
|
|
1573
|
+
Command
|
|
1574
|
+
\u2193
|
|
1575
|
+
Command pipeline
|
|
1576
|
+
\u2193
|
|
1577
|
+
Application action
|
|
1578
|
+
|
|
1579
|
+
Query
|
|
1580
|
+
\u2193
|
|
1581
|
+
Query pipeline
|
|
1582
|
+
\u2193
|
|
1583
|
+
Application action
|
|
1584
|
+
\`\`\`
|
|
1585
|
+
|
|
1586
|
+
Commands are sent with:
|
|
1587
|
+
|
|
1588
|
+
\`\`\`ts
|
|
1589
|
+
await services.mediator.send(command, action)
|
|
1590
|
+
\`\`\`
|
|
1502
1591
|
|
|
1503
|
-
|
|
1504
|
-
showcasing end-to-end command/query segregation, resilient HTTP fetching, and
|
|
1505
|
-
pure Composition API integration.
|
|
1592
|
+
Queries are executed with:
|
|
1506
1593
|
|
|
1507
|
-
|
|
1594
|
+
\`\`\`ts
|
|
1595
|
+
await services.mediator.query(query, action)
|
|
1596
|
+
\`\`\`
|
|
1597
|
+
|
|
1598
|
+
The application action is supplied by your code, so the transport remains behind
|
|
1599
|
+
an infrastructure boundary.
|
|
1508
1600
|
|
|
1509
1601
|
---
|
|
1510
1602
|
|
|
1511
|
-
##
|
|
1603
|
+
## Pipelines
|
|
1512
1604
|
|
|
1513
|
-
|
|
1605
|
+
Cross-cutting behavior belongs in the application execution pipeline.
|
|
1514
1606
|
|
|
1515
|
-
|
|
1516
|
-
npm install @xeno-js/vue
|
|
1607
|
+
The current implementation provides pipeline building blocks for:
|
|
1517
1608
|
|
|
1518
|
-
|
|
1609
|
+
* exception handling;
|
|
1610
|
+
* logging;
|
|
1611
|
+
* performance thresholds;
|
|
1612
|
+
* validation with Zod schemas;
|
|
1613
|
+
* query caching.
|
|
1519
1614
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1615
|
+
Pipelines are composed by the application module and executed around commands
|
|
1616
|
+
and queries.
|
|
1522
1617
|
|
|
1523
|
-
|
|
1524
|
-
# Example: Install tools only if you enable them in the builder
|
|
1525
|
-
npm install axios cockatiel zod @supabase/supabase-js @sentry/vue
|
|
1618
|
+
For example:
|
|
1526
1619
|
|
|
1527
|
-
\`\`\`
|
|
1620
|
+
\`\`\`ts
|
|
1621
|
+
builder.addPipeline((config) => {
|
|
1622
|
+
config.queryCaching = true
|
|
1623
|
+
config.threshold = 500
|
|
1624
|
+
})
|
|
1625
|
+
\`\`\`
|
|
1626
|
+
|
|
1627
|
+
Query caching uses the configured cache and is intended for query requests.
|
|
1528
1628
|
|
|
1529
1629
|
---
|
|
1530
1630
|
|
|
1531
|
-
##
|
|
1631
|
+
## Remote data sources
|
|
1532
1632
|
|
|
1533
|
-
|
|
1534
|
-
inject it into the Vue application, and consume it using Composition API
|
|
1535
|
-
Composables.
|
|
1633
|
+
HTTP concerns can be isolated behind a remote data source.
|
|
1536
1634
|
|
|
1537
|
-
|
|
1635
|
+
\`\`\`ts
|
|
1636
|
+
import { RemoteDataSource } from '@xeno-js/vue'
|
|
1538
1637
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
}>
|
|
1548
|
-
|
|
1549
|
-
// Create the root IoC container context for the browser
|
|
1550
|
-
const builder = XenoAppBuilder.create<MyRegistry>()
|
|
1551
|
-
.addContext((opts) => {
|
|
1552
|
-
opts.contextAccessor = useContextStore()
|
|
1553
|
-
})
|
|
1554
|
-
.addLogger((opts, config) => {
|
|
1555
|
-
opts.console = config.get('VITE_APP_ENV') === 'development'
|
|
1556
|
-
opts.level = LOG_LEVEL.DEBUG
|
|
1557
|
-
})
|
|
1558
|
-
.addAuth((opts, config) => {
|
|
1559
|
-
opts.url = config.getOrThrow('VITE_SUPABASE_URL')
|
|
1560
|
-
opts.key = config.getOrThrow('VITE_SUPABASE_KEY')
|
|
1561
|
-
})
|
|
1562
|
-
.addHttpCore('BFF_REMOTE_DS', (opts, config) => {
|
|
1563
|
-
opts.client = {
|
|
1564
|
-
baseURL: config.getOrThrow('VITE_API_BASE_URL'),
|
|
1565
|
-
timeoutMs: 10000,
|
|
1638
|
+
export class UsersRemoteDataSource extends RemoteDataSource {
|
|
1639
|
+
public getById(id: string) {
|
|
1640
|
+
return this.get<User>(\`/users/\${id}\`)
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
public create(payload: CreateUserPayload) {
|
|
1644
|
+
return this.post<User, CreateUserPayload>('/users', payload)
|
|
1645
|
+
}
|
|
1566
1646
|
}
|
|
1567
|
-
|
|
1568
|
-
opts.factory = (http, resilience) =>
|
|
1569
|
-
new BffRemoteDataSource(http, resilience)
|
|
1570
|
-
})
|
|
1571
|
-
.addPipeline((config) => {
|
|
1572
|
-
config.queryCaching = true // Enable in-memory caching for Queries
|
|
1573
|
-
})
|
|
1647
|
+
\`\`\`
|
|
1574
1648
|
|
|
1575
|
-
|
|
1576
|
-
return await builder.build()
|
|
1577
|
-
}
|
|
1578
|
-
\`\`\`
|
|
1649
|
+
The data source depends on the framework-neutral HTTP client abstraction.
|
|
1579
1650
|
|
|
1580
|
-
|
|
1651
|
+
\`XenoAppBuilder.addHttpCore()\` then provides the concrete HTTP client and registers
|
|
1652
|
+
the resulting data source in the application registry.
|
|
1581
1653
|
|
|
1582
|
-
\`\`\`
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
import { XENO_SERVICES_KEY, ServicesUtils } from '@xeno-js/vue'
|
|
1654
|
+
\`\`\`ts
|
|
1655
|
+
type AppRegistry = XenoVueRegistry<{
|
|
1656
|
+
users: UsersRemoteDataSource
|
|
1657
|
+
}>
|
|
1587
1658
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1659
|
+
const builder = XenoAppBuilder.create<AppRegistry>()
|
|
1660
|
+
.addHttpCore('users', (config) => {
|
|
1661
|
+
config.client.baseURL = '/api'
|
|
1662
|
+
config.factory = (http) => new UsersRemoteDataSource(http)
|
|
1663
|
+
})
|
|
1664
|
+
\`\`\`
|
|
1592
1665
|
|
|
1593
|
-
|
|
1594
|
-
app.provide(XENO_SERVICES_KEY, container)
|
|
1666
|
+
Axios is used by the built-in HTTP adapter when \`addHttpCore()\` is configured.
|
|
1595
1667
|
|
|
1596
|
-
|
|
1597
|
-
ServicesUtils.setGlobalServices(container)
|
|
1668
|
+
---
|
|
1598
1669
|
|
|
1599
|
-
|
|
1600
|
-
} catch (error) {
|
|
1601
|
-
console.error('Critical error during frontend bootstrap:', error)
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1670
|
+
## Application context
|
|
1604
1671
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1672
|
+
Browser applications still need contextual information around the current
|
|
1673
|
+
execution.
|
|
1607
1674
|
|
|
1608
|
-
|
|
1675
|
+
Xeno provides a browser context accessor containing information such as:
|
|
1609
1676
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1677
|
+
* request ID;
|
|
1678
|
+
* correlation ID;
|
|
1679
|
+
* user identity;
|
|
1680
|
+
* user agent;
|
|
1681
|
+
* path;
|
|
1682
|
+
* origin;
|
|
1683
|
+
* transport metadata.
|
|
1615
1684
|
|
|
1616
|
-
|
|
1617
|
-
const loading = ref(false)
|
|
1618
|
-
const error = ref<string | null>(null)
|
|
1685
|
+
You can use the default accessor or provide your own implementation.
|
|
1619
1686
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1687
|
+
\`\`\`ts
|
|
1688
|
+
builder.addContext((config) => {
|
|
1689
|
+
// Optional custom context accessor configuration.
|
|
1690
|
+
})
|
|
1691
|
+
\`\`\`
|
|
1624
1692
|
|
|
1625
|
-
|
|
1626
|
-
// Safely resolve the Mediator from the Xeno Container
|
|
1627
|
-
const { mediator, BFF_REMOTE_DS } = ServicesUtils.useApp()
|
|
1628
|
-
const command = new CreateUserCommand()
|
|
1693
|
+
---
|
|
1629
1694
|
|
|
1630
|
-
|
|
1631
|
-
const apiResult = await bffRemoteDs.post('/v1/users', payload)
|
|
1695
|
+
## Logging
|
|
1632
1696
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1697
|
+
Logging is configured independently from the application code.
|
|
1698
|
+
|
|
1699
|
+
The package currently supports:
|
|
1700
|
+
|
|
1701
|
+
* console logging;
|
|
1702
|
+
* Sentry logging;
|
|
1703
|
+
* custom logger clients.
|
|
1704
|
+
|
|
1705
|
+
Example:
|
|
1706
|
+
|
|
1707
|
+
\`\`\`ts
|
|
1708
|
+
builder.addLogger((config) => {
|
|
1709
|
+
config.console = true
|
|
1710
|
+
})
|
|
1711
|
+
\`\`\`
|
|
1712
|
+
|
|
1713
|
+
Optional integrations are loaded when they are configured, keeping them outside
|
|
1714
|
+
the default bootstrap path.
|
|
1715
|
+
|
|
1716
|
+
---
|
|
1717
|
+
|
|
1718
|
+
## Authentication
|
|
1719
|
+
|
|
1720
|
+
Supabase authentication is available as an infrastructure integration.
|
|
1721
|
+
|
|
1722
|
+
\`\`\`ts
|
|
1723
|
+
builder.addAuth((config, env) => {
|
|
1724
|
+
config.url = env.getOrThrow('SUPABASE_URL')
|
|
1725
|
+
config.key = env.getOrThrow('SUPABASE_KEY')
|
|
1726
|
+
})
|
|
1727
|
+
\`\`\`
|
|
1728
|
+
|
|
1729
|
+
The default configuration service resolves \`VITE_\` environment variables in
|
|
1730
|
+
Vite applications.
|
|
1731
|
+
|
|
1732
|
+
For example:
|
|
1733
|
+
|
|
1734
|
+
\`\`\`text
|
|
1735
|
+
VITE_SUPABASE_URL
|
|
1736
|
+
VITE_SUPABASE_KEY
|
|
1737
|
+
\`\`\`
|
|
1636
1738
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1739
|
+
Authentication is an integration, not part of the application's business rules.
|
|
1740
|
+
|
|
1741
|
+
---
|
|
1742
|
+
|
|
1743
|
+
## Validation
|
|
1744
|
+
|
|
1745
|
+
Application request validation can be configured with Zod schemas.
|
|
1746
|
+
|
|
1747
|
+
\`\`\`ts
|
|
1748
|
+
builder.addPipeline((config) => {
|
|
1749
|
+
config.schemas = {
|
|
1750
|
+
CreateUser: createUserSchema,
|
|
1751
|
+
UpdateUser: updateUserSchema,
|
|
1639
1752
|
}
|
|
1753
|
+
})
|
|
1754
|
+
\`\`\`
|
|
1755
|
+
|
|
1756
|
+
Validation is executed as part of the application pipeline.
|
|
1757
|
+
|
|
1758
|
+
Zod remains optional until validation schemas are actually configured.
|
|
1759
|
+
|
|
1760
|
+
---
|
|
1761
|
+
|
|
1762
|
+
## Query caching
|
|
1763
|
+
|
|
1764
|
+
The package includes an in-memory cache path for query requests.
|
|
1765
|
+
|
|
1766
|
+
Enable it through the pipeline configuration:
|
|
1767
|
+
|
|
1768
|
+
\`\`\`ts
|
|
1769
|
+
builder.addPipeline((config) => {
|
|
1770
|
+
config.queryCaching = true
|
|
1771
|
+
})
|
|
1772
|
+
\`\`\`
|
|
1773
|
+
|
|
1774
|
+
Cache keys can be contextual or user-scoped through the shared cache key builder.
|
|
1775
|
+
|
|
1776
|
+
---
|
|
1777
|
+
|
|
1778
|
+
## Bootstrap
|
|
1779
|
+
|
|
1780
|
+
A complete browser composition root can look like this:
|
|
1781
|
+
|
|
1782
|
+
\`\`\`ts
|
|
1783
|
+
import { XenoAppBuilder } from '@xeno-js/vue'
|
|
1784
|
+
|
|
1785
|
+
const builder = XenoAppBuilder.create()
|
|
1786
|
+
.addContext(() => { })
|
|
1787
|
+
.addLogger((config) => {
|
|
1788
|
+
config.console = true
|
|
1789
|
+
})
|
|
1790
|
+
.addAuth((config, env) => {
|
|
1791
|
+
config.url = env.getOrThrow('SUPABASE_URL')
|
|
1792
|
+
config.key = env.getOrThrow('SUPABASE_KEY')
|
|
1793
|
+
})
|
|
1794
|
+
.addPipeline((config) => {
|
|
1795
|
+
config.queryCaching = true
|
|
1796
|
+
config.threshold = 500
|
|
1797
|
+
})
|
|
1640
1798
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
loading.value = false
|
|
1799
|
+
export async function bootstrap() {
|
|
1800
|
+
return builder.build()
|
|
1644
1801
|
}
|
|
1645
|
-
|
|
1802
|
+
\`\`\`
|
|
1646
1803
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1804
|
+
The builder executes the registered tasks and returns a frozen application
|
|
1805
|
+
registry.
|
|
1806
|
+
|
|
1807
|
+
The composition root is application code: there is no requirement to hide it
|
|
1808
|
+
behind Vue plugins or decorators.
|
|
1650
1809
|
|
|
1651
1810
|
---
|
|
1652
1811
|
|
|
1653
|
-
##
|
|
1812
|
+
## Providing services to Vue
|
|
1654
1813
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
instantly scaffolding the CQRS files, composables, and dependency injection
|
|
1658
|
-
boundaries.
|
|
1814
|
+
The package exports \`XENO_SERVICES_KEY\` so the application registry can be made
|
|
1815
|
+
available through Vue's \`provide / inject\` mechanism.
|
|
1659
1816
|
|
|
1660
|
-
\`\`\`
|
|
1661
|
-
|
|
1662
|
-
|
|
1817
|
+
\`\`\`ts
|
|
1818
|
+
import { createApp } from 'vue'
|
|
1819
|
+
import { XENO_SERVICES_KEY } from '@xeno-js/vue'
|
|
1663
1820
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1821
|
+
import App from './App.vue'
|
|
1822
|
+
import { bootstrap } from './bootstrap'
|
|
1666
1823
|
|
|
1667
|
-
|
|
1824
|
+
async function mountApp() {
|
|
1825
|
+
const app = createApp(App)
|
|
1826
|
+
const services = await bootstrap()
|
|
1827
|
+
|
|
1828
|
+
app.provide(XENO_SERVICES_KEY, services)
|
|
1829
|
+
app.mount('#app')
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
mountApp()
|
|
1833
|
+
\`\`\`
|
|
1834
|
+
|
|
1835
|
+
From there, your application code can resolve the registry through Vue's normal
|
|
1836
|
+
dependency injection mechanism.
|
|
1837
|
+
|
|
1838
|
+
The package does not require a custom state-management abstraction.
|
|
1839
|
+
|
|
1840
|
+
---
|
|
1841
|
+
|
|
1842
|
+
## Keeping application logic outside components
|
|
1843
|
+
|
|
1844
|
+
A component can remain focused on presentation while application behavior lives
|
|
1845
|
+
in a composable or application service written by your project.
|
|
1846
|
+
|
|
1847
|
+
For example:
|
|
1848
|
+
|
|
1849
|
+
\`\`\`ts
|
|
1850
|
+
import { inject } from 'vue'
|
|
1851
|
+
import { XENO_SERVICES_KEY } from '@xeno-js/vue'
|
|
1852
|
+
|
|
1853
|
+
export function useUsersApplication() {
|
|
1854
|
+
const services = inject(XENO_SERVICES_KEY)
|
|
1855
|
+
|
|
1856
|
+
if (!services) {
|
|
1857
|
+
throw new Error('Xeno services are not available')
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
return services
|
|
1861
|
+
}
|
|
1862
|
+
\`\`\`
|
|
1668
1863
|
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1864
|
+
The composable belongs to your application.
|
|
1865
|
+
|
|
1866
|
+
Xeno provides the application infrastructure it uses.
|
|
1672
1867
|
|
|
1673
1868
|
---
|
|
1674
1869
|
|
|
1675
|
-
##
|
|
1870
|
+
## Public API
|
|
1871
|
+
|
|
1872
|
+
The package root currently exposes:
|
|
1873
|
+
|
|
1874
|
+
\`\`\`text
|
|
1875
|
+
XenoAppBuilder
|
|
1876
|
+
RemoteDataSource
|
|
1877
|
+
XENO_SERVICES_KEY
|
|
1878
|
+
\`\`\`
|
|
1676
1879
|
|
|
1677
|
-
|
|
1678
|
-
stability of the core framework, **direct pushes to the \`main\` and \`develop\`
|
|
1679
|
-
branches are strictly prohibited.** Please follow this Git Flow to contribute:
|
|
1880
|
+
It also re-exports the public contracts and primitives from \`@xeno-js / shared\`.
|
|
1680
1881
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1882
|
+
The concrete internal pipeline implementations are assembled by the builder and
|
|
1883
|
+
are not intended to be the primary public API of the package.
|
|
1884
|
+
|
|
1885
|
+
---
|
|
1886
|
+
|
|
1887
|
+
## Installation
|
|
1888
|
+
|
|
1889
|
+
Install Vue, Shared, and Xeno Vue:
|
|
1683
1890
|
|
|
1684
1891
|
\`\`\`bash
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
git checkout -b feat/your-awesome-feature
|
|
1892
|
+
npm install @xeno-js / vue @xeno-js / shared vue
|
|
1893
|
+
\`\`\`
|
|
1688
1894
|
|
|
1689
|
-
|
|
1895
|
+
Install the integrations you actually use.
|
|
1690
1896
|
|
|
1691
|
-
|
|
1692
|
-
(linting, types, and tests).
|
|
1897
|
+
For HTTP data sources:
|
|
1693
1898
|
|
|
1694
1899
|
\`\`\`bash
|
|
1695
|
-
npm
|
|
1900
|
+
npm install axios
|
|
1901
|
+
\`\`\`
|
|
1696
1902
|
|
|
1697
|
-
|
|
1903
|
+
For validation:
|
|
1904
|
+
|
|
1905
|
+
\`\`\`bash
|
|
1906
|
+
npm install zod
|
|
1907
|
+
\`\`\`
|
|
1908
|
+
|
|
1909
|
+
For Supabase authentication:
|
|
1910
|
+
|
|
1911
|
+
\`\`\`bash
|
|
1912
|
+
npm install @supabase/supabase-js
|
|
1913
|
+
\`\`\`
|
|
1914
|
+
|
|
1915
|
+
For Sentry:
|
|
1916
|
+
|
|
1917
|
+
\`\`\`bash
|
|
1918
|
+
npm install @sentry/vue
|
|
1919
|
+
\`\`\`
|
|
1698
1920
|
|
|
1699
|
-
|
|
1700
|
-
[Conventional Commits](https://www.conventionalcommits.org/?utm_source=gemini).
|
|
1701
|
-
Husky will verify your commit message format.
|
|
1702
|
-
4. **Commit Format:**
|
|
1921
|
+
For Vue Router integration:
|
|
1703
1922
|
|
|
1704
1923
|
\`\`\`bash
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1924
|
+
npm install vue - router
|
|
1925
|
+
\`\`\`
|
|
1926
|
+
|
|
1927
|
+
---
|
|
1928
|
+
|
|
1929
|
+
## CLI
|
|
1930
|
+
|
|
1931
|
+
The Xeno ecosystem includes an official CLI for creating Vue application
|
|
1932
|
+
structures:
|
|
1933
|
+
|
|
1934
|
+
\`\`\`bash
|
|
1935
|
+
npx @xeno-js / cli new my - app--vue
|
|
1936
|
+
\`\`\`
|
|
1937
|
+
|
|
1938
|
+
You can also generate application components inside an existing project:
|
|
1939
|
+
|
|
1940
|
+
\`\`\`bash
|
|
1941
|
+
npx @xeno-js / cli g command CreateUser--vue
|
|
1942
|
+
\`\`\`
|
|
1943
|
+
|
|
1944
|
+
CLI:
|
|
1708
1945
|
|
|
1946
|
+
https://github.com/xeno-js/xeno-cli
|
|
1947
|
+
|
|
1948
|
+
---
|
|
1949
|
+
|
|
1950
|
+
## Xeno ecosystem
|
|
1951
|
+
|
|
1952
|
+
\`\`\`text
|
|
1953
|
+
@xeno-js / shared
|
|
1954
|
+
Define the application.
|
|
1955
|
+
|
|
1956
|
+
@xeno-js / core
|
|
1957
|
+
Execute the backend application.
|
|
1958
|
+
|
|
1959
|
+
@xeno-js / vue
|
|
1960
|
+
Bring the application architecture to Vue.
|
|
1961
|
+
|
|
1962
|
+
@xeno-js / cli
|
|
1963
|
+
Get started with the structure.
|
|
1709
1964
|
\`\`\`
|
|
1710
1965
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1966
|
+
A useful mental model is:
|
|
1967
|
+
|
|
1968
|
+
\`\`\`text
|
|
1969
|
+
Transport
|
|
1970
|
+
hosts the application
|
|
1971
|
+
|
|
1972
|
+
Application
|
|
1973
|
+
executes use cases
|
|
1974
|
+
|
|
1975
|
+
Domain
|
|
1976
|
+
defines business rules
|
|
1977
|
+
|
|
1978
|
+
Infrastructure
|
|
1979
|
+
connects external systems
|
|
1980
|
+
\`\`\`
|
|
1715
1981
|
|
|
1716
|
-
|
|
1717
|
-
flows from feature branches \u27A1\uFE0F \`develop\` \u27A1\uFE0F \`main\`._
|
|
1982
|
+
---
|
|
1718
1983
|
|
|
1719
|
-
|
|
1984
|
+
## Keep your stack
|
|
1720
1985
|
|
|
1721
|
-
|
|
1722
|
-
| ----------------------- | ---------------------------------------------- |
|
|
1723
|
-
| \`npm run build\` | Builds the TypeScript source code into \`dist/\` |
|
|
1724
|
-
| \`npm run typecheck\` | Checks types without emitting files |
|
|
1725
|
-
| \`npm run lint\` | Runs ESLint |
|
|
1726
|
-
| \`npm run format\` | Formats code with Prettier |
|
|
1727
|
-
| \`npm run test\` | Runs the Vitest test suite |
|
|
1728
|
-
| \`npm run test:coverage\` | Runs tests and generates a coverage report |
|
|
1986
|
+
Xeno Vue does not replace Vue.
|
|
1729
1987
|
|
|
1730
|
-
|
|
1988
|
+
It does not replace your router.
|
|
1731
1989
|
|
|
1732
|
-
|
|
1733
|
-
repository:
|
|
1990
|
+
It does not replace your state management solution.
|
|
1734
1991
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
Conventional Commits).
|
|
1738
|
-
- **\`pre-push\`**: Runs type checking, linting, and testing before code leaves
|
|
1739
|
-
your machine.
|
|
1992
|
+
It provides a place for application behavior and infrastructure composition to
|
|
1993
|
+
live alongside the tools you already use.
|
|
1740
1994
|
|
|
1741
1995
|
---
|
|
1742
1996
|
|
|
1743
|
-
##
|
|
1997
|
+
## Browser environment
|
|
1998
|
+
|
|
1999
|
+
The default configuration service is designed for Vite/browser applications.
|
|
2000
|
+
|
|
2001
|
+
The package provides browser-oriented request context data and integrates with
|
|
2002
|
+
Vue's dependency injection system.
|
|
2003
|
+
|
|
2004
|
+
Node.js \`20 + \` is required by the package tooling.
|
|
2005
|
+
|
|
2006
|
+
---
|
|
1744
2007
|
|
|
1745
|
-
|
|
1746
|
-
open-source framework requires a massive amount of continuous dedication and
|
|
1747
|
-
architectural engineering.
|
|
2008
|
+
## Development
|
|
1748
2009
|
|
|
1749
|
-
|
|
1750
|
-
core business logic, or simplified your system infrastructure layout, consider
|
|
1751
|
-
supporting its open-source lifecycle. Your backing directly accelerates our
|
|
1752
|
-
strategic roadmap for new out-of-the-box transport integrations and keeps the
|
|
1753
|
-
documentation pristine.
|
|
2010
|
+
Clone the repository:
|
|
1754
2011
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
2012
|
+
\`\`\`bash
|
|
2013
|
+
git clone https://github.com/xeno-js/xeno-fe.git
|
|
2014
|
+
cd xeno - fe
|
|
2015
|
+
npm install
|
|
2016
|
+
\`\`\`
|
|
2017
|
+
|
|
2018
|
+
Run the checks:
|
|
2019
|
+
|
|
2020
|
+
\`\`\`bash
|
|
2021
|
+
npm run check
|
|
2022
|
+
\`\`\`
|
|
1759
2023
|
|
|
1760
|
-
|
|
1761
|
-
**[Read our support guidelines and find out how to help](https://www.xeno-js.it/support-us)**
|
|
2024
|
+
Useful commands:
|
|
1762
2025
|
|
|
1763
|
-
|
|
2026
|
+
\`\`\`bash
|
|
2027
|
+
npm run build
|
|
2028
|
+
npm run typecheck
|
|
2029
|
+
npm run lint
|
|
2030
|
+
npm run test
|
|
2031
|
+
npm run test: coverage
|
|
2032
|
+
npm run format
|
|
2033
|
+
\`\`\`
|
|
1764
2034
|
|
|
1765
|
-
|
|
1766
|
-
</amp-bounce>
|
|
1767
|
-
<a href="https://www.buymeacoffee.com/xenojs" target="_blank">
|
|
1768
|
-
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="42" style="height: 42px !important;" />
|
|
1769
|
-
</a>
|
|
2035
|
+
Development happens from feature branches targeting \`develop\`.
|
|
1770
2036
|
|
|
1771
2037
|
---
|
|
1772
2038
|
|
|
1773
|
-
##
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
\`\`\`
|
|
2039
|
+
## Contributing
|
|
2040
|
+
|
|
2041
|
+
Contributions are welcome.
|
|
2042
|
+
|
|
2043
|
+
Create a feature branch from \`develop\`:
|
|
2044
|
+
|
|
2045
|
+
\`\`\`bash
|
|
2046
|
+
git checkout develop
|
|
2047
|
+
git pull origin develop
|
|
2048
|
+
git checkout - b feat / your - feature
|
|
2049
|
+
\`\`\`
|
|
2050
|
+
|
|
2051
|
+
Run the project checks before opening a pull request:
|
|
2052
|
+
|
|
2053
|
+
\`\`\`bash
|
|
2054
|
+
npm run check
|
|
2055
|
+
\`\`\`
|
|
2056
|
+
|
|
2057
|
+
We use Conventional Commits:
|
|
2058
|
+
|
|
2059
|
+
\`\`\`text
|
|
2060
|
+
feat(vue): add application service
|
|
2061
|
+
fix(datasource): correct request handling
|
|
2062
|
+
refactor(builder): simplify bootstrap
|
|
2063
|
+
docs(readme): clarify architecture
|
|
2064
|
+
\`\`\`
|
|
1790
2065
|
|
|
1791
|
-
|
|
2066
|
+
Open pull requests against:
|
|
2067
|
+
|
|
2068
|
+
\`\`\`text
|
|
2069
|
+
develop
|
|
2070
|
+
\`\`\`
|
|
2071
|
+
|
|
2072
|
+
---
|
|
1792
2073
|
|
|
1793
|
-
|
|
1794
|
-
|
|
2074
|
+
## License
|
|
2075
|
+
|
|
2076
|
+
MIT License. See \`LICENSE\`.
|
|
1795
2077
|
`;
|
|
1796
2078
|
await this._fileService.writeFileRecursive(import_node_path12.default.join(projectPath, "README.md"), content);
|
|
1797
2079
|
}
|
|
@@ -1925,7 +2207,7 @@ var init_bootstrap_ts_generator = __esm({
|
|
|
1925
2207
|
if (options.sentry) {
|
|
1926
2208
|
builderNodes += `
|
|
1927
2209
|
.addLogger((opts, config) => {
|
|
1928
|
-
const env = config.get(
|
|
2210
|
+
const env = config.get('APP_ENV') ?? 'development'
|
|
1929
2211
|
const isDev = env.toLowerCase() === 'development'
|
|
1930
2212
|
opts.console = isDev
|
|
1931
2213
|
if (!isDev) {
|
|
@@ -2320,8 +2602,7 @@ var init_packagejson_generator2 = __esm({
|
|
|
2320
2602
|
types: "./dist/index.d.ts",
|
|
2321
2603
|
scripts: {
|
|
2322
2604
|
start: "tsx src/main.ts",
|
|
2323
|
-
build: "tsc --project tsconfig.json"
|
|
2324
|
-
g: "xeno-js generate"
|
|
2605
|
+
build: "tsc --project tsconfig.json"
|
|
2325
2606
|
},
|
|
2326
2607
|
dependencies: {
|
|
2327
2608
|
"@xeno-js/core": "latest",
|
|
@@ -2330,35 +2611,35 @@ var init_packagejson_generator2 = __esm({
|
|
|
2330
2611
|
devDependencies: {
|
|
2331
2612
|
"@types/node": "^20.0.0",
|
|
2332
2613
|
"tsx": "^4.7.0",
|
|
2333
|
-
"typescript": "^5.
|
|
2614
|
+
"typescript": "^5.8.0"
|
|
2334
2615
|
}
|
|
2335
2616
|
};
|
|
2336
2617
|
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
packageJson.dependencies["@libsql/client"] = "^0.14.0";
|
|
2347
|
-
packageJson.devDependencies["drizzle-kit"] = "^0.31.10";
|
|
2348
|
-
}
|
|
2618
|
+
packageJson.dependencies["drizzle-orm"] = "^0.45.3";
|
|
2619
|
+
packageJson.dependencies["pg"] = "^8.23.0";
|
|
2620
|
+
packageJson.dependencies["postgres"] = "^3.4.9";
|
|
2621
|
+
packageJson.devDependencies["@types/pg"] = "^8.23.1";
|
|
2622
|
+
packageJson.devDependencies["drizzle-kit"] = "^0.31.11";
|
|
2623
|
+
packageJson.scripts["db:migrate"] = "drizzle-kit migrate";
|
|
2624
|
+
packageJson.dependencies["drizzle-orm"] = "^0.45.3";
|
|
2625
|
+
packageJson.devDependencies["drizzle-kit"] = "^0.31.11";
|
|
2626
|
+
packageJson.dependencies["@libsql/client"] = "^0.14.0";
|
|
2349
2627
|
packageJson.scripts["db:generate"] = "drizzle-kit generate";
|
|
2350
2628
|
packageJson.scripts["db:push"] = "drizzle-kit push";
|
|
2351
2629
|
}
|
|
2352
|
-
if (options.axios) packageJson.dependencies["axios"] = "^1.
|
|
2630
|
+
if (options.axios) packageJson.dependencies["axios"] = "^1.20.0";
|
|
2353
2631
|
if (options.cockatiel) packageJson.dependencies["cockatiel"] = "^4.0.0";
|
|
2354
2632
|
if (options.pino) {
|
|
2355
2633
|
packageJson.dependencies["pino"] = "^10.3.1";
|
|
2356
2634
|
packageJson.devDependencies["pino-pretty"] = "^11.2.2";
|
|
2357
2635
|
}
|
|
2358
|
-
if (options.sentry) packageJson.dependencies["@sentry/node"] = "^
|
|
2359
|
-
if (options.redis) packageJson.dependencies["ioredis"] = "^
|
|
2360
|
-
if (options.supabase)
|
|
2361
|
-
|
|
2636
|
+
if (options.sentry) packageJson.dependencies["@sentry/node"] = "^11.0.0";
|
|
2637
|
+
if (options.redis) packageJson.dependencies["ioredis"] = "^6.0.0";
|
|
2638
|
+
if (options.supabase) {
|
|
2639
|
+
packageJson.dependencies["@supabase/supabase-js"] = "^2.117.1";
|
|
2640
|
+
packageJson.dependencies["@supabase/ssr"] = "^0.12.7";
|
|
2641
|
+
}
|
|
2642
|
+
if (options.zod) packageJson.dependencies["zod"] = "^4.6.5";
|
|
2362
2643
|
const filePath = import_node_path22.default.join(projectPath, "package.json");
|
|
2363
2644
|
await this._fileService.writeFileRecursive(filePath, JSON.stringify(packageJson, null, 2));
|
|
2364
2645
|
}
|
|
@@ -2572,23 +2853,17 @@ var init_readme_generator2 = __esm({
|
|
|
2572
2853
|
_fileService;
|
|
2573
2854
|
async generate(projectPath, _options) {
|
|
2574
2855
|
const content = `
|
|
2575
|
-
|
|
2856
|
+
<div align="center">
|
|
2576
2857
|
<img src="logo/logo.png" alt="Xeno Logo" width="140" />
|
|
2577
2858
|
|
|
2578
|
-
<h1>Xeno</h1>
|
|
2579
|
-
|
|
2580
|
-
<p
|
|
2859
|
+
<h1>Xeno.JS</h1>
|
|
2860
|
+
<p><strong>The application architecture framework for TypeScript.</strong></p>
|
|
2861
|
+
<p>Build long-lived applications with explicit dependency injection, DDD, CQRS, and transport-independent business logic.</p>
|
|
2581
2862
|
|
|
2582
2863
|
<p>
|
|
2583
|
-
<a href="https://
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
<a href="https://github.com/xeno-js/xeno-js/blob/main/LICENSE">
|
|
2587
|
-
<img src="https://img.shields.io/npm/l/@xeno?style=flat-square" alt="License: ISC" />
|
|
2588
|
-
</a>
|
|
2589
|
-
<a href="https://www.npmjs.com/package/@xeno/core">
|
|
2590
|
-
<img src="https://img.shields.io/npm/v/@xeno/core?style=flat-square" alt="NPM Version" />
|
|
2591
|
-
</a>
|
|
2864
|
+
<a href="https://www.npmjs.com/package/@xeno-js/core"><img src="https://img.shields.io/npm/v/@xeno-js/core?style=flat-square" alt="NPM Version" /></a>
|
|
2865
|
+
<a href="https://github.com/xeno-js/xeno-js"><img src="https://img.shields.io/badge/Powered%20by-Xeno-blueviolet?style=flat-square" alt="Powered by Xeno" /></a>
|
|
2866
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License: MIT" /></a>
|
|
2592
2867
|
<a href="https://buymeacoffee.com/xenojs">
|
|
2593
2868
|
<img src="https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-FFdd00?style=flat-square&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me A Coffee" />
|
|
2594
2869
|
</a>
|
|
@@ -2599,391 +2874,443 @@ var init_readme_generator2 = __esm({
|
|
|
2599
2874
|
|
|
2600
2875
|
## What is Xeno?
|
|
2601
2876
|
|
|
2602
|
-
|
|
2603
|
-
Node.js built natively with TypeScript. It provides structural primitives for
|
|
2604
|
-
implementing robust **Domain-Driven Design (DDD)** and **Command Query
|
|
2605
|
-
Responsibility Segregation (CQRS)** patterns. By shifting operational logic away
|
|
2606
|
-
from delivery mechanisms and transport frameworks, Xeno ensures your core
|
|
2607
|
-
application architecture remains pristine, testable, and completely isolated
|
|
2608
|
-
from external infrastructural churn.
|
|
2877
|
+
Xeno is a TypeScript application architecture framework for Node.js.
|
|
2609
2878
|
|
|
2610
|
-
|
|
2879
|
+
It provides explicit building blocks for applications organized around:
|
|
2880
|
+
|
|
2881
|
+
- **Dependency Injection** with explicit service registration and lifetimes
|
|
2882
|
+
- **Domain-Driven Design (DDD)** and domain/application boundaries
|
|
2883
|
+
- **CQRS** with commands, queries, handlers, and composable pipelines
|
|
2884
|
+
- **Request context** built around asynchronous execution context
|
|
2885
|
+
- **Repositories and data sources** that keep persistence behind application
|
|
2886
|
+
boundaries
|
|
2887
|
+
- **Infrastructure adapters** for databases, Redis, authentication, logging,
|
|
2888
|
+
resilience, and other integrations
|
|
2889
|
+
|
|
2890
|
+
The goal is simple: **make application architecture explicit in code.**
|
|
2611
2891
|
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
Modern Node.js frameworks often tie business workflows tightly to HTTP server
|
|
2615
|
-
abstractions or rely heavily on experimental language features. Xeno fixes this
|
|
2616
|
-
with an emphasis on developer experience, type safety, and clean separation of
|
|
2617
|
-
concerns.
|
|
2618
|
-
|
|
2619
|
-
- **Zero Decorators**: Xeno eliminates reliance on experimental or unstable TS
|
|
2620
|
-
decorator specifications (\`reflect-metadata\`). The IoC container
|
|
2621
|
-
(\`ServiceContainer\`) uses pure, explicit functional factories that optimize
|
|
2622
|
-
compilation speeds and eliminate runtime black-box behaviors.
|
|
2623
|
-
- **Complete Server Decoupling**: Xeno does not care if you use Fastify, Hono,
|
|
2624
|
-
Express, Koa, or AWS Lambda. The presentation layer handles incoming data
|
|
2625
|
-
using plain, primitive contracts, making migration or multi-runtime hosting
|
|
2626
|
-
completely seamless.
|
|
2627
|
-
- **Pay-For-What-You-Use (Opt-in Modularity)**: Core dependencies are
|
|
2628
|
-
strategically classified as optional peer dependencies. If your architecture
|
|
2629
|
-
doesn't use Redis, Sentry, or Supabase, you do not pull them into your node
|
|
2630
|
-
modules.
|
|
2631
|
-
- **Enterprise-Grade Resiliency & Cross-Cutting Pipelines**: Address complex
|
|
2632
|
-
distributed patterns natively without code duplication. Xeno provides
|
|
2633
|
-
out-of-the-box composite behaviors:
|
|
2634
|
-
- **Idempotency**: Implements multi-tenant logic keyspaces matching advanced
|
|
2635
|
-
SaaS factory patterns for logical partitioning.
|
|
2636
|
-
- **Concurrency Control**: Mitigates thundering herd impacts via advanced
|
|
2637
|
-
backoff retry strategies coupled with randomized jitter.
|
|
2638
|
-
- **Resilience Policies**: Deep integration with circuit breakers, bulkheads,
|
|
2639
|
-
and fallbacks.
|
|
2640
|
-
- **Deterministic Type Safety**: Strong infrastructure validation strategies
|
|
2641
|
-
using Zod schemas.
|
|
2892
|
+
Xeno is not tied to a specific HTTP server. Your application layer can remain
|
|
2893
|
+
independent from the transport that delivers a request.
|
|
2642
2894
|
|
|
2643
2895
|
---
|
|
2644
2896
|
|
|
2645
|
-
##
|
|
2897
|
+
## Why Xeno?
|
|
2646
2898
|
|
|
2647
|
-
|
|
2648
|
-
workflows of Xeno, read the full technical manuals located inside the main
|
|
2649
|
-
documentation hub:
|
|
2899
|
+
### 01 \u2014 Explicit Architecture
|
|
2650
2900
|
|
|
2651
|
-
|
|
2901
|
+
**Your dependency graph is code.**
|
|
2652
2902
|
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2903
|
+
Xeno does not require decorators, runtime scanning, or implicit dependency
|
|
2904
|
+
discovery. Services are registered explicitly, and their lifetimes are visible
|
|
2905
|
+
at the composition root.
|
|
2656
2906
|
|
|
2657
|
-
|
|
2907
|
+
\`\`\`typescript
|
|
2908
|
+
services.addScoped('USER_REPOSITORY', (container) => {
|
|
2909
|
+
return new UserRepository(
|
|
2910
|
+
container.resolve('USER_DATA_SOURCE'),
|
|
2911
|
+
container.resolve('USER_MAPPER'),
|
|
2912
|
+
)
|
|
2913
|
+
})
|
|
2914
|
+
\`\`\`
|
|
2915
|
+
|
|
2916
|
+
This makes the composition of the application easier to inspect, test, and
|
|
2917
|
+
reason about.
|
|
2918
|
+
|
|
2919
|
+
### 02 \u2014 Transport Independence
|
|
2920
|
+
|
|
2921
|
+
Business logic should not belong to your HTTP framework.
|
|
2922
|
+
|
|
2923
|
+
Xeno keeps application concerns separate from delivery mechanisms, allowing the
|
|
2924
|
+
same application architecture to be hosted behind transports such as Fastify,
|
|
2925
|
+
Hono, Express, or other adapters.
|
|
2926
|
+
|
|
2927
|
+
\`\`\`text
|
|
2928
|
+
HTTP / CLI / Worker / Lambda
|
|
2929
|
+
|
|
|
2930
|
+
v
|
|
2931
|
+
Presentation
|
|
2932
|
+
|
|
|
2933
|
+
v
|
|
2934
|
+
Application
|
|
2935
|
+
Commands / Queries
|
|
2936
|
+
|
|
|
2937
|
+
v
|
|
2938
|
+
Domain
|
|
2939
|
+
|
|
|
2940
|
+
v
|
|
2941
|
+
Infrastructure
|
|
2942
|
+
DB / Redis / APIs
|
|
2943
|
+
\`\`\`
|
|
2944
|
+
|
|
2945
|
+
### 03 \u2014 CQRS as an Application Primitive
|
|
2946
|
+
|
|
2947
|
+
Commands and queries are first-class application concepts.
|
|
2948
|
+
|
|
2949
|
+
Pipelines can compose cross-cutting behavior around execution, such as:
|
|
2950
|
+
|
|
2951
|
+
- authorization
|
|
2952
|
+
- idempotency
|
|
2953
|
+
- concurrency control
|
|
2954
|
+
- caching
|
|
2955
|
+
- resilience policies
|
|
2956
|
+
- request context
|
|
2957
|
+
|
|
2958
|
+
This keeps cross-cutting concerns out of individual handlers.
|
|
2658
2959
|
|
|
2659
|
-
|
|
2960
|
+
### 04 \u2014 Explicit Lifetimes and Request Boundaries
|
|
2660
2961
|
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
resilient schema handling.
|
|
2962
|
+
Xeno distinguishes service lifetimes such as singleton, scoped, and transient
|
|
2963
|
+
services.
|
|
2664
2964
|
|
|
2665
|
-
|
|
2666
|
-
|
|
2965
|
+
Request-scoped dependencies are resolved inside an explicit application scope,
|
|
2966
|
+
while request metadata can be carried through asynchronous execution using
|
|
2967
|
+
\`AsyncLocalStorage\`.
|
|
2667
2968
|
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2969
|
+
Database transaction state is scoped to the same application boundary, allowing
|
|
2970
|
+
\`UnitOfWork\` and \`DbContext\` to operate against the transaction associated with
|
|
2971
|
+
the current scope.
|
|
2972
|
+
|
|
2973
|
+
### 05 \u2014 Infrastructure Stays Outside the Domain
|
|
2974
|
+
|
|
2975
|
+
Database clients, Redis, HTTP clients, authentication providers, loggers, and
|
|
2976
|
+
other infrastructure integrations are composed at the edge of the application.
|
|
2977
|
+
|
|
2978
|
+
Your domain and application code can depend on contracts instead of concrete
|
|
2979
|
+
infrastructure.
|
|
2672
2980
|
|
|
2673
2981
|
---
|
|
2674
2982
|
|
|
2675
|
-
##
|
|
2983
|
+
## Architecture
|
|
2984
|
+
|
|
2985
|
+
A typical Xeno application can be organized like this:
|
|
2986
|
+
|
|
2987
|
+
\`\`\`text
|
|
2988
|
+
+ ------------------------------------------+
|
|
2989
|
+
| Presentation |
|
|
2990
|
+
| HTTP / CLI / Workers / Lambda |
|
|
2991
|
+
+--------------------- +--------------------+
|
|
2992
|
+
|
|
|
2993
|
+
v
|
|
2994
|
+
+ ------------------------------------------+
|
|
2995
|
+
| Application |
|
|
2996
|
+
| Commands / Queries / Handlers / Pipes |
|
|
2997
|
+
+--------------------- +--------------------+
|
|
2998
|
+
|
|
|
2999
|
+
v
|
|
3000
|
+
+ ------------------------------------------+
|
|
3001
|
+
| Domain |
|
|
3002
|
+
| Entities / Policies / Rules |
|
|
3003
|
+
+--------------------- +--------------------+
|
|
3004
|
+
|
|
|
3005
|
+
v
|
|
3006
|
+
+ ------------------------------------------+
|
|
3007
|
+
| Infrastructure |
|
|
3008
|
+
| DB / Redis / APIs / Auth / Logs |
|
|
3009
|
+
+------------------------------------------+
|
|
3010
|
+
\`\`\`
|
|
3011
|
+
|
|
3012
|
+
Xeno's core is focused on composition and application architecture.
|
|
3013
|
+
Infrastructure capabilities can be enabled only when they are needed.
|
|
2676
3014
|
|
|
2677
|
-
|
|
3015
|
+
---
|
|
2678
3016
|
|
|
2679
|
-
|
|
2680
|
-
npm install @xeno-js/core
|
|
3017
|
+
## Core Concepts
|
|
2681
3018
|
|
|
2682
|
-
|
|
3019
|
+
| Concept | Purpose |
|
|
3020
|
+
| ------------------ | --------------------------------------------------------- |
|
|
3021
|
+
| \`AppBuilder\` | Composition root for assembling an application |
|
|
3022
|
+
| \`ServiceContainer\` | Explicit dependency injection and service lifetimes |
|
|
3023
|
+
| \`CQRS\` | Commands, queries, handlers, and mediator-based execution |
|
|
3024
|
+
| \`Pipelines\` | Cross-cutting behavior around application execution |
|
|
3025
|
+
| \`Request Context\` | Request metadata across asynchronous execution |
|
|
3026
|
+
| \`Repository\` | Application-facing persistence abstraction |
|
|
3027
|
+
| \`DataSource\` | Infrastructure-facing data access implementation |
|
|
3028
|
+
| \`Module\` | Explicit registration of related capabilities |
|
|
3029
|
+
| \`Result\` | Typed success/failure flow for application operations |
|
|
2683
3030
|
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
3031
|
+
---
|
|
3032
|
+
|
|
3033
|
+
## Installation
|
|
2687
3034
|
|
|
2688
3035
|
\`\`\`bash
|
|
2689
|
-
|
|
2690
|
-
|
|
3036
|
+
npm install @xeno-js / core
|
|
3037
|
+
\`\`\`
|
|
2691
3038
|
|
|
2692
|
-
|
|
3039
|
+
Install only the integrations your application uses. Xeno exposes optional
|
|
3040
|
+
infrastructure dependencies for capabilities such as databases, Redis, logging,
|
|
3041
|
+
resilience, and authentication.
|
|
2693
3042
|
|
|
2694
|
-
|
|
3043
|
+
For example:
|
|
3044
|
+
|
|
3045
|
+
\`\`\`bash
|
|
3046
|
+
npm install zod pino cockatiel drizzle - orm
|
|
3047
|
+
\`\`\`
|
|
2695
3048
|
|
|
2696
|
-
|
|
3049
|
+
---
|
|
2697
3050
|
|
|
2698
|
-
|
|
2699
|
-
\`ServiceContainer\`, load core modules, and process an incoming application
|
|
2700
|
-
payload natively inside a server middleware wrapper.
|
|
3051
|
+
## A Small Example
|
|
2701
3052
|
|
|
2702
|
-
|
|
3053
|
+
The composition root is explicit:
|
|
2703
3054
|
|
|
2704
3055
|
\`\`\`typescript
|
|
2705
|
-
|
|
2706
|
-
import {
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
// Map your registry token with XenoRegistry<TSchemaDb, TExtension>
|
|
2713
|
-
type MyRegistry = XenoRegistry<{ /** Your Db Schema here **/}, {
|
|
2714
|
-
USER_MAPPER_TOKEN: UserMapper
|
|
2715
|
-
USER_DS_TOKEN: UserDataSource
|
|
2716
|
-
USER_REPOSITORY_TOKEN: UserWriteRepository
|
|
2717
|
-
FIND_USER_QUERY_HANDLER_TOKEN: FindUserQueryHandler
|
|
2718
|
-
FIND_USER_CONTROLLER_TOKEN: FindUserController
|
|
2719
|
-
}>
|
|
2720
|
-
|
|
2721
|
-
// Create the root IoC container context
|
|
2722
|
-
export const bootstrap = new AppBuilder<MyRegistry>()
|
|
2723
|
-
// Configure middleware and only PUBLIC routes
|
|
2724
|
-
.addMiddlewares(opts => {
|
|
2725
|
-
opts.routeRegistry = {
|
|
2726
|
-
'/api/user/:id': ['GET', 'POST'],
|
|
2727
|
-
}
|
|
2728
|
-
})
|
|
2729
|
-
// Configure the CQRS pipeline
|
|
2730
|
-
// Can register Policies for your intent
|
|
2731
|
-
.addPipeline((config) => {
|
|
2732
|
-
config.authorization.policies = {
|
|
2733
|
-
'FIND_USER_QUERY_HANDLER_TOKEN': {
|
|
2734
|
-
// Add authz by user id
|
|
2735
|
-
userId: true
|
|
2736
|
-
// Add authz by tenant id
|
|
2737
|
-
tenantId: true
|
|
2738
|
-
// Add authz by roles
|
|
2739
|
-
roles: ['admin']
|
|
2740
|
-
// Add authz by perissions
|
|
2741
|
-
permissions: ['read'],
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
// Can add idempotency pipeline for command
|
|
2745
|
-
config.commandBus.idempotency = { lockTtlSeconds: 30, processedTtlSeconds: 60 }
|
|
2746
|
-
// Can add concurrency pipeline for command
|
|
2747
|
-
config.commandBus.concurrency = { delayConfig: { baseDelayMs: 100, maxJitterMs: 500 }, maxRetries: 3 }
|
|
2748
|
-
// Can add caching pipeline for query
|
|
2749
|
-
config.queryBus.isEnabled = true
|
|
2750
|
-
})
|
|
2751
|
-
// Configure Database with drizzle
|
|
2752
|
-
.addDb((opts, config) => {
|
|
2753
|
-
opts.connectionString = config.getOrThrow('DATABASE_URL')
|
|
2754
|
-
})
|
|
2755
|
-
// Configure Authentication with supabase
|
|
2756
|
-
.addAuth((opts, config) => {
|
|
2757
|
-
opts.key = 'demo-key'
|
|
2758
|
-
opts.url = config.getOrThrow('API_BASE_URL')
|
|
3056
|
+
// src/bootstrap.ts
|
|
3057
|
+
import { AppBuilder } from '@xeno-js/core'
|
|
3058
|
+
|
|
3059
|
+
const app = new AppBuilder()
|
|
3060
|
+
.addServices((services) => {
|
|
3061
|
+
services.addScoped('USER_REPOSITORY', (container) => {
|
|
3062
|
+
return new UserRepository(container.resolve('USER_DATA_SOURCE'))
|
|
2759
3063
|
})
|
|
2760
|
-
|
|
2761
|
-
.
|
|
2762
|
-
|
|
2763
|
-
config.console = true
|
|
3064
|
+
|
|
3065
|
+
services.addScoped('FIND_USER_HANDLER', (container) => {
|
|
3066
|
+
return new FindUserHandler(container.resolve('USER_REPOSITORY'))
|
|
2764
3067
|
})
|
|
2765
|
-
|
|
2766
|
-
.
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
services.addScoped('USER_DS_TOKEN', (c) => new UserDataSource(c.resolve(TOKENS.DB_CONTEXT)))
|
|
2772
|
-
|
|
2773
|
-
// REGISTER REPOSITORIES
|
|
2774
|
-
services.addScoped('USER_REPOSITORY_TOKEN', (c) => new UserWriteRepository(c.resolve('USER_DS_TOKEN'), c.resolve('USER_MAPPER_TOKEN')))
|
|
2775
|
-
|
|
2776
|
-
// REGISTER HANDLERS
|
|
2777
|
-
services.addScoped('FIND_USER_QUERY_HANDLER_TOKEN', (c) => {
|
|
2778
|
-
const requestcontext = c.resolve('USER_CONTEXT_FACTORY')
|
|
2779
|
-
const repository = c.resolve('USER_READ_REPOSITORY')
|
|
2780
|
-
return new FindUserQueryHandler(repository, requestcontext)
|
|
2781
|
-
})
|
|
2782
|
-
|
|
2783
|
-
// REGISTER CONTROLLERS
|
|
2784
|
-
services.addTransient('FIND_USER_CONTROLLER_TOKEN', (c) => {
|
|
2785
|
-
return new FindUserController(c.resolve('CONTEXT_ACCESSOR'), c.resolve('MEDIATOR'))
|
|
2786
|
-
})
|
|
3068
|
+
|
|
3069
|
+
services.addTransient('FIND_USER_CONTROLLER', (c) => {
|
|
3070
|
+
return new FindUserController(
|
|
3071
|
+
c.resolve(TOKENS.REQUEST_CONTEXT),
|
|
3072
|
+
c.resolve(TOKENS.MEDIATOR),
|
|
3073
|
+
)
|
|
2787
3074
|
})
|
|
3075
|
+
})
|
|
2788
3076
|
|
|
2789
|
-
|
|
3077
|
+
await app.build()
|
|
3078
|
+
\`\`\`
|
|
3079
|
+
|
|
3080
|
+
The transport remains outside the application composition:
|
|
2790
3081
|
|
|
2791
|
-
|
|
3082
|
+
> \u26A0\uFE0F **Implementation note: Example using Fastify** The following snippet uses
|
|
3083
|
+
> **Fastify** solely for demonstration purposes to illustrate the transport
|
|
3084
|
+
> layer. Thanks to the framework's agnostic architecture, the underlying logic
|
|
3085
|
+
> (\`container\` and \`handler\`) remains unchanged regardless of the chosen HTTP
|
|
3086
|
+
> system (e.g., Express, Koa) or interface (CLI, gRPC).
|
|
2792
3087
|
|
|
2793
3088
|
\`\`\`typescript
|
|
2794
|
-
import '
|
|
2795
|
-
import {
|
|
2796
|
-
import fastify from 'fastify'
|
|
2797
|
-
import { bootstrap } from './bootstrap'
|
|
3089
|
+
import Fastify from 'fastify'
|
|
3090
|
+
import { app } from './bootstrap'
|
|
2798
3091
|
|
|
2799
|
-
|
|
2800
|
-
console.log('\u2699\uFE0F Initialized Xeno Container...')
|
|
2801
|
-
try {
|
|
2802
|
-
// 1. Bootstrap the application and get the service container
|
|
2803
|
-
const xenoApp = await bootstrap.build()
|
|
2804
|
-
|
|
2805
|
-
console.log('\u{1F680} Starting Fastify server on http://localhost:3000...')
|
|
2806
|
-
|
|
2807
|
-
// 2. Resolve the middleware from the container
|
|
2808
|
-
const middleware = xeno.resolve('MIDDLEWARE')
|
|
2809
|
-
// 3. Create a Fastify instance to handle HTTP requests
|
|
2810
|
-
const app = fastify()
|
|
2811
|
-
|
|
2812
|
-
// \u2500\u2500\u2500 ENDPOINT 2: QUERY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2813
|
-
app.get('/api/user/:id', async (request, reply) => {
|
|
2814
|
-
// 4. Execute the middleware to handle the request context and authentication, then call the StatusController's handle method with the request payload.
|
|
2815
|
-
const responseDto = await middleware.execute(
|
|
2816
|
-
{ path: '/api/user/:id', method: 'GET', transport: { res: reply, req: request } },
|
|
2817
|
-
{ ...request.headers },
|
|
2818
|
-
async () => {
|
|
2819
|
-
const { id } = request.params as any
|
|
2820
|
-
const controller = ContainerUtils.resolveServiceScoped('FIND_USER_CONTROLLER_TOKEN', xenoApp)
|
|
2821
|
-
return await controller.handle({ id: id ?? '123' });
|
|
2822
|
-
}
|
|
2823
|
-
);
|
|
3092
|
+
const fastify = Fastify({ logger: true })
|
|
2824
3093
|
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
.type('application/json')
|
|
2828
|
-
.send(responseDto.data)
|
|
2829
|
-
})
|
|
3094
|
+
fastify.get('/users/:id', async (req, reply) => {
|
|
3095
|
+
const endpoint = req.url
|
|
2830
3096
|
|
|
2831
|
-
|
|
3097
|
+
const action = async () => {
|
|
3098
|
+
const controller = ContainerUtils.resolveServiceScoped(
|
|
3099
|
+
'FIND_USER_CONTROLLER',
|
|
3100
|
+
app,
|
|
3101
|
+
)
|
|
3102
|
+
return await controller.handle()
|
|
3103
|
+
}
|
|
2832
3104
|
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
process.exit(1)
|
|
2842
|
-
}
|
|
2843
|
-
} catch (error) {
|
|
2844
|
-
console.error('Error during bootstrap or server setup:', error)
|
|
2845
|
-
process.exit(1)
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
3105
|
+
const result = await ContainerUtils.runExecute(
|
|
3106
|
+
endpoint,
|
|
3107
|
+
req.method,
|
|
3108
|
+
req.headers,
|
|
3109
|
+
{ reply, req },
|
|
3110
|
+
app,
|
|
3111
|
+
action,
|
|
3112
|
+
)
|
|
2848
3113
|
|
|
2849
|
-
|
|
2850
|
-
|
|
3114
|
+
return reply.send(result)
|
|
3115
|
+
})
|
|
3116
|
+
\`\`\`
|
|
3117
|
+
|
|
3118
|
+
The HTTP adapter is responsible for HTTP. The application handler is responsible
|
|
3119
|
+
for the use case.
|
|
2851
3120
|
|
|
2852
3121
|
---
|
|
2853
3122
|
|
|
2854
|
-
##
|
|
3123
|
+
## CQRS & Pipelines
|
|
3124
|
+
|
|
3125
|
+
Cross-cutting behavior can be composed around commands and queries:
|
|
3126
|
+
|
|
3127
|
+
\`\`\`typescript
|
|
3128
|
+
.addPipeline((config) => {
|
|
3129
|
+
config.authorization.policies = {
|
|
3130
|
+
FIND_USER_QUERY_HANDLER: {
|
|
3131
|
+
roles: ['admin'],
|
|
3132
|
+
permissions: ['read'],
|
|
3133
|
+
},
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
config.commandBus.idempotency = {
|
|
3137
|
+
lockTtlSeconds: 30,
|
|
3138
|
+
processedTtlSeconds: 60,
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
config.commandBus.concurrency = {
|
|
3142
|
+
delayConfig: {
|
|
3143
|
+
baseDelayMs: 100,
|
|
3144
|
+
maxJitterMs: 500,
|
|
3145
|
+
},
|
|
3146
|
+
maxRetries: 3,
|
|
3147
|
+
}
|
|
2855
3148
|
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
a clean, pre-configured architecture tailored to your specific requirements.
|
|
3149
|
+
config.queryBus.isEnabled = true
|
|
3150
|
+
})
|
|
3151
|
+
\`\`\`
|
|
2860
3152
|
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
**[CLI Documentation](https://www.xeno-js.it/cli/overview)**.
|
|
3153
|
+
The exact pipeline configuration depends on the integrations enabled by your
|
|
3154
|
+
application.
|
|
2864
3155
|
|
|
2865
3156
|
---
|
|
2866
3157
|
|
|
2867
|
-
##
|
|
3158
|
+
## Infrastructure & Integrations
|
|
2868
3159
|
|
|
2869
|
-
|
|
2870
|
-
stability of the core framework, **direct pushes to the \`main\` and \`develop\`
|
|
2871
|
-
branches are strictly prohibited.** Please follow this Git Flow to contribute:
|
|
3160
|
+
Xeno Core can be composed with infrastructure such as:
|
|
2872
3161
|
|
|
2873
|
-
|
|
2874
|
-
|
|
3162
|
+
- **Database:** Drizzle ORM, PostgreSQL, LibSQL
|
|
3163
|
+
- **Cache / distributed coordination:** Redis
|
|
3164
|
+
- **Authentication:** Supabase integrations and custom strategies
|
|
3165
|
+
- **HTTP clients:** Axios
|
|
3166
|
+
- **Resilience:** Cockatiel
|
|
3167
|
+
- **Logging:** Console, Pino, Sentry, or custom loggers
|
|
3168
|
+
- **Validation:** Zod
|
|
2875
3169
|
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
\`\`\`
|
|
3170
|
+
These integrations are opt-in rather than mandatory parts of the application
|
|
3171
|
+
architecture.
|
|
3172
|
+
|
|
3173
|
+
---
|
|
2881
3174
|
|
|
2882
|
-
|
|
2883
|
-
|
|
3175
|
+
## CLI
|
|
3176
|
+
|
|
3177
|
+
Use the official CLI to scaffold a Xeno application:
|
|
2884
3178
|
|
|
2885
3179
|
\`\`\`bash
|
|
2886
|
-
npm
|
|
2887
|
-
|
|
3180
|
+
npm install @xeno-js / cli
|
|
3181
|
+
xeno - js new my - xeno - app--core
|
|
3182
|
+
\`\`\`
|
|
2888
3183
|
|
|
2889
|
-
|
|
2890
|
-
[Conventional Commits](https://www.conventionalcommits.org/). Husky will
|
|
2891
|
-
verify your commit message format.
|
|
3184
|
+
See the [CLI documentation](https://www.xeno-js.it/cli/overview).
|
|
2892
3185
|
|
|
2893
|
-
|
|
3186
|
+
---
|
|
2894
3187
|
|
|
2895
|
-
|
|
2896
|
-
feat(scope): add new feature
|
|
2897
|
-
fix(scope): resolve bug
|
|
2898
|
-
chore(scope): update dependencies
|
|
2899
|
-
\`\`\`
|
|
3188
|
+
## Documentation
|
|
2900
3189
|
|
|
2901
|
-
|
|
2902
|
-
Request targeting the **\`develop\`** branch.
|
|
3190
|
+
The documentation hub contains the architecture and integration guides:
|
|
2903
3191
|
|
|
2904
|
-
|
|
2905
|
-
and merge it into \`develop\`.
|
|
3192
|
+
**[xeno-js.it](https://www.xeno-js.it/introduction)**
|
|
2906
3193
|
|
|
2907
|
-
|
|
2908
|
-
flows from feature branches \u27A1\uFE0F \`develop\` \u27A1\uFE0F \`main\`._
|
|
3194
|
+
Recommended starting points:
|
|
2909
3195
|
|
|
2910
|
-
|
|
3196
|
+
- [Introduction](https://www.xeno-js.it/introduction)
|
|
3197
|
+
- [Fundamentals](https://www.xeno-js.it/docs/core/overview)
|
|
3198
|
+
- [Dependency Injection](https://www.xeno-js.it/docs/core/fundamentals/app-builder)
|
|
3199
|
+
- [CQRS](https://www.xeno-js.it/docs/core/fundamentals/command-query)
|
|
3200
|
+
- [Pipelines](https://www.xeno-js.it/docs/core/cqrs/overview)
|
|
3201
|
+
- [Request Lifecycle](https://www.xeno-js.it/docs/core/fundamentals/service-container)
|
|
3202
|
+
- [Middleware](https://www.xeno-js.it/docs/core/fundamentals/middleware)
|
|
3203
|
+
- [CLI](https://www.xeno-js.it/docs/cli/overview)
|
|
2911
3204
|
|
|
2912
|
-
|
|
2913
|
-
| ----------------------- | ---------------------------------------------- |
|
|
2914
|
-
| \`npm run build\` | Builds the TypeScript source code into \`dist/\` |
|
|
2915
|
-
| \`npm run typecheck\` | Checks types without emitting files |
|
|
2916
|
-
| \`npm run lint\` | Runs ESLint |
|
|
2917
|
-
| \`npm run format\` | Formats code with Prettier |
|
|
2918
|
-
| \`npm run test\` | Runs the Vitest test suite |
|
|
2919
|
-
| \`npm run test:coverage\` | Runs tests and generates a coverage report |
|
|
3205
|
+
---
|
|
2920
3206
|
|
|
2921
|
-
|
|
3207
|
+
## Ecosystem
|
|
2922
3208
|
|
|
2923
|
-
|
|
2924
|
-
repository:
|
|
3209
|
+
Xeno is designed as an ecosystem rather than a single monolithic package:
|
|
2925
3210
|
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3211
|
+
| Package | Role |
|
|
3212
|
+
| ----------------- | ----------------------------------------- |
|
|
3213
|
+
| \`@xeno-js / core\` | Application architecture and backend core |
|
|
3214
|
+
| \`@xeno-js / shared\` | Shared contracts and types |
|
|
3215
|
+
| \`@xeno-js / vue\` | Vue integration |
|
|
3216
|
+
| \`@xeno-js / cli\` | Project scaffolding and developer tooling |
|
|
2931
3217
|
|
|
2932
3218
|
---
|
|
2933
3219
|
|
|
2934
|
-
##
|
|
3220
|
+
## What Xeno Is Not
|
|
2935
3221
|
|
|
2936
|
-
|
|
2937
|
-
open-source framework requires a massive amount of continuous dedication and
|
|
2938
|
-
architectural engineering.
|
|
3222
|
+
Xeno is not primarily an HTTP framework.
|
|
2939
3223
|
|
|
2940
|
-
If
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
strategic roadmap for new out-of-the-box transport integrations (such as gRPC,
|
|
2944
|
-
RabbitMQ, and GraphQL) and keeps the documentation pristine.
|
|
3224
|
+
If you are looking for a framework centered on routing, controllers, middleware,
|
|
3225
|
+
and server lifecycle, there are excellent options already available in the
|
|
3226
|
+
Node.js ecosystem.
|
|
2945
3227
|
|
|
2946
|
-
|
|
2947
|
-
commitment of our community to keep the project independent and thriving.
|
|
2948
|
-
Whether you are an individual developer or a business using Xeno, your support
|
|
2949
|
-
makes a real difference.
|
|
3228
|
+
Xeno focuses on the layer above transport:
|
|
2950
3229
|
|
|
2951
|
-
|
|
2952
|
-
|
|
3230
|
+
> **How should a TypeScript application be structured so that its business
|
|
3231
|
+
> logic, dependencies, and infrastructure boundaries remain explicit as the
|
|
3232
|
+
> application grows?**
|
|
2953
3233
|
|
|
2954
|
-
|
|
3234
|
+
---
|
|
2955
3235
|
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3236
|
+
## Production Considerations
|
|
3237
|
+
|
|
3238
|
+
Xeno provides architectural primitives, but application correctness still
|
|
3239
|
+
depends on how those primitives are composed.
|
|
3240
|
+
|
|
3241
|
+
Before deploying an application, test the behaviors that matter to your
|
|
3242
|
+
workload, especially:
|
|
3243
|
+
|
|
3244
|
+
- request and transaction isolation
|
|
3245
|
+
- service lifetime boundaries
|
|
3246
|
+
- authorization policies
|
|
3247
|
+
- idempotency semantics
|
|
3248
|
+
- concurrency behavior
|
|
3249
|
+
- cache consistency
|
|
3250
|
+
- failure and retry behavior
|
|
3251
|
+
- trusted proxy / client IP configuration
|
|
3252
|
+
- database transaction boundaries
|
|
3253
|
+
|
|
3254
|
+
The framework is designed to make these boundaries explicit rather than hide
|
|
3255
|
+
them behind conventions.
|
|
2961
3256
|
|
|
2962
3257
|
---
|
|
2963
3258
|
|
|
2964
|
-
##
|
|
2965
|
-
|
|
2966
|
-
If you are using Xeno in your project, let the world know! Add this badge to
|
|
2967
|
-
your README:
|
|
2968
|
-
|
|
2969
|
-
\`\`\`html
|
|
2970
|
-
<a
|
|
2971
|
-
href="[https://github.com/xeno-js/xeno-js](https://github.com/xeno-js/xeno-js)"
|
|
2972
|
-
target="_blank"
|
|
2973
|
-
>
|
|
2974
|
-
<img
|
|
2975
|
-
src="[https://img.shields.io/badge/Powered%20by-Xeno-black?style=flat-square](https://img.shields.io/badge/Powered%20by-Xeno-black?style=flat-square)"
|
|
2976
|
-
alt="Powered by Xeno"
|
|
2977
|
-
height="20"
|
|
2978
|
-
/>
|
|
2979
|
-
</a>
|
|
2980
|
-
\`\`\`
|
|
3259
|
+
## Contributing
|
|
2981
3260
|
|
|
2982
|
-
|
|
3261
|
+
Contributions are welcome.
|
|
2983
3262
|
|
|
2984
|
-
|
|
3263
|
+
Development happens from feature branches targeting \`develop\`.
|
|
2985
3264
|
|
|
2986
|
-
|
|
3265
|
+
\`\`\`bash
|
|
3266
|
+
git checkout develop
|
|
3267
|
+
git pull origin develop
|
|
3268
|
+
git checkout - b feat / your - feature
|
|
3269
|
+
|
|
3270
|
+
npm install
|
|
3271
|
+
npm run check
|
|
3272
|
+
\`\`\`
|
|
3273
|
+
|
|
3274
|
+
We use Conventional Commits:
|
|
3275
|
+
|
|
3276
|
+
\`\`\`bash
|
|
3277
|
+
feat(scope): add new feature
|
|
3278
|
+
fix(scope): resolve bug
|
|
3279
|
+
chore(scope): update dependencies
|
|
3280
|
+
\`\`\`
|
|
3281
|
+
|
|
3282
|
+
Before opening a pull request, run:
|
|
3283
|
+
|
|
3284
|
+
\`\`\`bash
|
|
3285
|
+
npm run check
|
|
3286
|
+
\`\`\`
|
|
3287
|
+
|
|
3288
|
+
| Command | Description |
|
|
3289
|
+
| ----------------------- | ------------------------ |
|
|
3290
|
+
| \`npm run build\` | Build the package |
|
|
3291
|
+
| \`npm run typecheck\` | TypeScript type checking |
|
|
3292
|
+
| \`npm run lint\` | ESLint |
|
|
3293
|
+
| \`npm run format: check\` | Prettier validation |
|
|
3294
|
+
| \`npm run test\` | Vitest test suite |
|
|
3295
|
+
| \`npm run test: coverage\` | Test suite with coverage |
|
|
3296
|
+
|
|
3297
|
+
---
|
|
3298
|
+
|
|
3299
|
+
## Support
|
|
3300
|
+
|
|
3301
|
+
If Xeno is useful to you, you can support the project through the community and
|
|
3302
|
+
sponsorship channels documented on the website:
|
|
3303
|
+
|
|
3304
|
+
**[Support Xeno](https://www.xeno-js.it/docs/support-us)**
|
|
3305
|
+
|
|
3306
|
+
---
|
|
3307
|
+
|
|
3308
|
+
## License
|
|
3309
|
+
|
|
3310
|
+
Copyright (c) 2026 Xeno.
|
|
3311
|
+
|
|
3312
|
+
Licensed under the [MIT License](LICENSE).
|
|
3313
|
+
`;
|
|
2987
3314
|
const filePath = import_node_path25.default.join(projectPath, "readme");
|
|
2988
3315
|
await this._fileService.writeFileRecursive(filePath, content);
|
|
2989
3316
|
}
|
|
@@ -3050,13 +3377,12 @@ var init_tsconfig_generator2 = __esm({
|
|
|
3050
3377
|
"src/infrastructure/generators/core/tsconfig.generator.ts"() {
|
|
3051
3378
|
"use strict";
|
|
3052
3379
|
import_node_path27 = __toESM(require("path"));
|
|
3053
|
-
init_shared();
|
|
3054
3380
|
TsconfigGenerator = class {
|
|
3055
3381
|
constructor(_fileService) {
|
|
3056
3382
|
this._fileService = _fileService;
|
|
3057
3383
|
}
|
|
3058
3384
|
_fileService;
|
|
3059
|
-
async generate(projectPath,
|
|
3385
|
+
async generate(projectPath, _options) {
|
|
3060
3386
|
const tsconfig = {
|
|
3061
3387
|
compilerOptions: {
|
|
3062
3388
|
target: "ES2022",
|
|
@@ -3075,9 +3401,6 @@ var init_tsconfig_generator2 = __esm({
|
|
|
3075
3401
|
},
|
|
3076
3402
|
include: ["src/**/*.ts"]
|
|
3077
3403
|
};
|
|
3078
|
-
if (options.database === CORE_CONSTANTS.DRIZZLE || options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
3079
|
-
tsconfig.include.push("drizzle.config.ts");
|
|
3080
|
-
}
|
|
3081
3404
|
const filePath = import_node_path27.default.join(projectPath, "tsconfig.json");
|
|
3082
3405
|
await this._fileService.writeFileRecursive(filePath, JSON.stringify(tsconfig, null, 2));
|
|
3083
3406
|
}
|
|
@@ -3111,7 +3434,8 @@ var init_drizzle_generator = __esm({
|
|
|
3111
3434
|
);
|
|
3112
3435
|
}
|
|
3113
3436
|
composeDrizzleConfig() {
|
|
3114
|
-
return
|
|
3437
|
+
return `/// <reference types="node" />
|
|
3438
|
+
import 'dotenv/config';
|
|
3115
3439
|
import { defineConfig } from 'drizzle-kit';
|
|
3116
3440
|
|
|
3117
3441
|
/**
|
|
@@ -3191,7 +3515,8 @@ var init_drizzle_sql_lite_generator = __esm({
|
|
|
3191
3515
|
await this._fileService.writeFileRecursive(schemaPath, schemaContent);
|
|
3192
3516
|
}
|
|
3193
3517
|
composeDrizzleConfig() {
|
|
3194
|
-
return
|
|
3518
|
+
return `/// <reference types="node" />
|
|
3519
|
+
import 'dotenv/config';
|
|
3195
3520
|
import { defineConfig } from 'drizzle-kit';
|
|
3196
3521
|
|
|
3197
3522
|
/**
|
|
@@ -3400,7 +3725,7 @@ var CommandRunner = class {
|
|
|
3400
3725
|
const child = (0, import_node_child_process.spawn)(cmd, args, {
|
|
3401
3726
|
cwd,
|
|
3402
3727
|
stdio: "inherit",
|
|
3403
|
-
shell:
|
|
3728
|
+
shell: isWin
|
|
3404
3729
|
});
|
|
3405
3730
|
child.on("error", (error) => {
|
|
3406
3731
|
reject(new Error(`Error during execution of the command ${command}: ${error.message}`));
|
|
@@ -3420,11 +3745,16 @@ init_command_constants();
|
|
|
3420
3745
|
|
|
3421
3746
|
// src/infrastructure/file/file.service.ts
|
|
3422
3747
|
var import_promises = require("fs/promises");
|
|
3423
|
-
var import_path = require("path");
|
|
3424
3748
|
var FileUtils = class {
|
|
3425
3749
|
async writeFileRecursive(filePath, content) {
|
|
3426
|
-
|
|
3427
|
-
|
|
3750
|
+
try {
|
|
3751
|
+
await (0, import_promises.access)(filePath);
|
|
3752
|
+
throw new Error(`[Xeno CLI Error]: Aborted. File already exists at path: ${filePath}`);
|
|
3753
|
+
} catch (error) {
|
|
3754
|
+
if (error.code !== "ENOENT") {
|
|
3755
|
+
throw error;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3428
3758
|
await (0, import_promises.writeFile)(filePath, content, "utf8");
|
|
3429
3759
|
}
|
|
3430
3760
|
};
|