@fastact/core 0.1.0-dev → 0.2.0-dev
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/README.md +99 -0
- package/dist/app-factory.d.ts +8 -0
- package/dist/app-factory.js +24 -0
- package/dist/app.d.ts +17 -0
- package/dist/app.js +63 -0
- package/dist/constants.js +3 -3
- package/dist/define-config.d.ts +16 -0
- package/dist/define-config.js +17 -0
- package/dist/errors/base.d.ts +6 -0
- package/dist/errors/base.js +10 -0
- package/dist/errors/domain.error.d.ts +3 -0
- package/dist/errors/domain.error.js +6 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -2
- package/dist/ioc/container-builder.d.ts +10 -5
- package/dist/ioc/container-builder.js +100 -37
- package/dist/ioc/container.d.ts +9 -9
- package/dist/ioc/container.js +43 -33
- package/dist/ioc/fastify-plugin.d.ts +18 -0
- package/dist/ioc/fastify-plugin.js +21 -0
- package/dist/ioc/index.d.ts +2 -0
- package/dist/ioc/index.js +2 -0
- package/dist/ioc/types.d.ts +15 -15
- package/dist/types.d.ts +39 -9
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +17 -0
- package/dist/validation/errors.d.ts +6 -0
- package/dist/validation/errors.js +11 -0
- package/dist/validation/plugin.d.ts +1 -0
- package/dist/validation/plugin.js +23 -0
- package/package.json +15 -7
- package/dist/application-factory.d.ts +0 -2
- package/dist/application-factory.js +0 -7
- package/dist/application.d.ts +0 -13
- package/dist/application.js +0 -42
package/README.md
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# ⚡ FastAct — Backend Without the Bullsh\*t.
|
|
2
|
+
|
|
3
|
+
**FastAct** is a lightweight, ultra-fast backend framework for Node.js built with **TypeScript** and **Fastify**.
|
|
4
|
+
|
|
5
|
+
It is designed for engineers who are tired of bloated enterprise monsters, endless decorators, heavy reflection magic, and overwhelming boilerplate. **FastAct** brings back the joy of writing clean, predictable, and rock-solid server-side code.
|
|
6
|
+
|
|
7
|
+
[Documentation (Coming Soon)] • [CLI Utility: fac] • [Community]
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🔥 Key Features & Philosophy
|
|
12
|
+
|
|
13
|
+
### 🚫 No Decorator Pollution
|
|
14
|
+
|
|
15
|
+
Forget about `@Injectable()`, `@Inject()`, or `@Module()`. Your code should be pure TypeScript. FastAct uses an explicit, declarative, and fluent API for building dependencies, which plays perfectly with native tools and unit tests.
|
|
16
|
+
|
|
17
|
+
### 🛡 Symbol-Driven IoC Container
|
|
18
|
+
|
|
19
|
+
Our custom built-in Dependency Injection (DI) container operates entirely on unique `Symbol` tokens. No string name collisions, no accidental service overwrites. You get 100% type safety and perfect IDE autocomplete out of the box.
|
|
20
|
+
|
|
21
|
+
### 🧬 Advanced Lifecycles (Singleton & Scoped)
|
|
22
|
+
|
|
23
|
+
FastAct natively supports both lazy `Singleton` instances and fully isolated `Scoped` contexts for every single HTTP request or CLI command. This makes it an ideal fit for managing MikroORM's `EntityManager` and isolating database transactions safely.
|
|
24
|
+
|
|
25
|
+
### 🔀 Unified Web & CLI Architecture (Standalone Mode)
|
|
26
|
+
|
|
27
|
+
Initialize your entire application with a single `createApp()` function. Need a web server? Pass `runServer: true`. Need a lightweight cron job, queue worker, or a CLI command via our `fac` utility? Turn off the server, and the IoC container will assemble only the required services without opening ports or wasting memory.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🎹 Code Showcase
|
|
32
|
+
|
|
33
|
+
### 1. Define Unique Tokens
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// src/modules/auth/auth.tokens.ts
|
|
37
|
+
import type { InjectionToken } from '@fastactjs/core';
|
|
38
|
+
import type { AuthService } from './auth.service';
|
|
39
|
+
|
|
40
|
+
export const AUTH_DI = {
|
|
41
|
+
AuthService: Symbol('AuthService') as InjectionToken<AuthService>,
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Register Dependencies Cleanly (Fluent API)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// src/modules/auth/auth.module.ts
|
|
49
|
+
import { AUTH_DI } from './auth.tokens';
|
|
50
|
+
import { AuthService } from './auth.service';
|
|
51
|
+
import type { ContainerBuilder } from '@fastactjs/core';
|
|
52
|
+
|
|
53
|
+
export async function createContainerModule(builder: ContainerBuilder) {
|
|
54
|
+
// Simple, elegant, and instantly readable for developers worldwide
|
|
55
|
+
builder.add(AUTH_DI.AuthService).asClass(AuthService).singleton();
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. Ignite the Engine
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// src/main.ts
|
|
63
|
+
import { createApp } from '@fastactjs/core';
|
|
64
|
+
import path from 'node:path';
|
|
65
|
+
|
|
66
|
+
async function bootstrap() {
|
|
67
|
+
const app = await createApp({
|
|
68
|
+
modulesDir: path.join(__dirname, 'modules'),
|
|
69
|
+
runServer: true,
|
|
70
|
+
port: 3000,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await app.start();
|
|
74
|
+
}
|
|
75
|
+
bootstrap();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🛠 Our Powerful CLI: `fac`
|
|
81
|
+
|
|
82
|
+
Manage your FastAct applications with our sharp and blunt console utility — `fac`. Fast, efficient, straight to the point.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Generate a new module structure automatically (Zero Boilerplate)
|
|
86
|
+
\$ fac add module user
|
|
87
|
+
|
|
88
|
+
# Run the project in development mode with hot-reload
|
|
89
|
+
\$ fac start --dev
|
|
90
|
+
|
|
91
|
+
# Execute a standalone CLI command / cron script
|
|
92
|
+
\$ fac run cron:sync-users
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🌏 Join the Movement
|
|
98
|
+
|
|
99
|
+
We are building a backend framework that speaks the same language of simplicity to engineers in Moscow, Seoul, Tokyo, and San Francisco. If you share our passion for clean JavaScript/TypeScript without "magic" crutches — drop a star ⭐️ and let's reshape backend development together.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { App } from './app';
|
|
2
|
+
import type { NormalAppOptions, StandaloneAppOptions } from './types';
|
|
3
|
+
export declare class AppFactory {
|
|
4
|
+
static create(options?: NormalAppOptions): Promise<App>;
|
|
5
|
+
static createStandalone(options?: StandaloneAppOptions): Promise<App>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createApp(options?: NormalAppOptions): Promise<App>;
|
|
8
|
+
export declare function createStandaloneApp(options?: StandaloneAppOptions): Promise<App>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { App } from './app';
|
|
2
|
+
import { ContainerBuilder } from './ioc';
|
|
3
|
+
export class AppFactory {
|
|
4
|
+
// prettier-ignore
|
|
5
|
+
static async create(options) {
|
|
6
|
+
const container = await new ContainerBuilder().build();
|
|
7
|
+
return new App(container, { runServer: true, ...options });
|
|
8
|
+
}
|
|
9
|
+
// prettier-ignore
|
|
10
|
+
static async createStandalone(options) {
|
|
11
|
+
const container = await new ContainerBuilder().build();
|
|
12
|
+
return new App(container, { runServer: false, ...options });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// prettier-ignore
|
|
16
|
+
export async function createApp(options) {
|
|
17
|
+
const container = await new ContainerBuilder().build();
|
|
18
|
+
return new App(container, { runServer: true, ...options });
|
|
19
|
+
}
|
|
20
|
+
// prettier-ignore
|
|
21
|
+
export async function createStandaloneApp(options) {
|
|
22
|
+
const container = await new ContainerBuilder().build();
|
|
23
|
+
return new App(container, { runServer: false, ...options });
|
|
24
|
+
}
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FastifyInstance } from 'fastify';
|
|
2
|
+
import { type ContainerInstance } from './ioc';
|
|
3
|
+
import type { NormalAppOptions, StandaloneAppOptions } from './types';
|
|
4
|
+
export type AppOptions = NormalAppOptions & StandaloneAppOptions & {
|
|
5
|
+
runServer?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare class App {
|
|
8
|
+
private readonly container;
|
|
9
|
+
private readonly server;
|
|
10
|
+
private readonly options;
|
|
11
|
+
constructor(container: ContainerInstance, options?: AppOptions);
|
|
12
|
+
getContainer(): ContainerInstance;
|
|
13
|
+
getServer(): FastifyInstance;
|
|
14
|
+
start(): Promise<void>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
private stopServer;
|
|
17
|
+
}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fastify from 'fastify';
|
|
3
|
+
import { fastactIocPlugin } from './ioc';
|
|
4
|
+
import { DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT, LOG_PREFIX, } from './constants';
|
|
5
|
+
export class App {
|
|
6
|
+
container;
|
|
7
|
+
server;
|
|
8
|
+
options;
|
|
9
|
+
constructor(container, options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.container = container;
|
|
12
|
+
if (this.options.runServer !== false) {
|
|
13
|
+
this.server = fastify(this.options.server);
|
|
14
|
+
this.server.register(fastactIocPlugin, {
|
|
15
|
+
container: this.container,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
getContainer() {
|
|
20
|
+
return this.container;
|
|
21
|
+
}
|
|
22
|
+
getServer() {
|
|
23
|
+
if (!this.server) {
|
|
24
|
+
throw new Error('Fastify server is not initialized in standalone mode');
|
|
25
|
+
}
|
|
26
|
+
return this.server;
|
|
27
|
+
}
|
|
28
|
+
async start() {
|
|
29
|
+
if (this.options.runServer === false) {
|
|
30
|
+
// prettier-ignore
|
|
31
|
+
console.log(`${LOG_PREFIX.WARN} Standalone context initialized (CLI mode)`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Защита для TypeScript (хотя при runServer !== false сервер точно есть)
|
|
35
|
+
if (!this.server)
|
|
36
|
+
return;
|
|
37
|
+
const host = this.options.host ?? DEFAULT_SERVER_HOST;
|
|
38
|
+
const port = this.options.port ?? DEFAULT_SERVER_PORT;
|
|
39
|
+
try {
|
|
40
|
+
await this.server.listen({ port, host });
|
|
41
|
+
console.log(`${chalk.green.bold('[FastAct]')} Server is now listening on ${host}:${port}`);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// console.log(`${LOG_PREFIX.ERROR} ${err}`);
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
//if (this.container?.dispose) await this.container.dispose();
|
|
50
|
+
await this.stopServer();
|
|
51
|
+
if (this.options.runServer !== false)
|
|
52
|
+
return;
|
|
53
|
+
console.log(`${LOG_PREFIX.INFO} Standalone application stopped`);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
async stopServer() {
|
|
57
|
+
if (!this.server)
|
|
58
|
+
return;
|
|
59
|
+
console.log(`${LOG_PREFIX.WARN} Shutting down the server...`);
|
|
60
|
+
await this.server.close();
|
|
61
|
+
console.log(`${LOG_PREFIX.INFO} Server gracefully stopped`);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/constants.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
export const DEFAULT_SERVER_HOST = 'localhost';
|
|
3
3
|
export const DEFAULT_SERVER_PORT = 3000;
|
|
4
4
|
export const LOG_PREFIX = {
|
|
5
|
-
INFO: chalk.green.bold('[
|
|
6
|
-
WARN: chalk.yellow.bold('[
|
|
7
|
-
ERROR: chalk.red.bold('[
|
|
5
|
+
INFO: chalk.green.bold('[FastAct]'),
|
|
6
|
+
WARN: chalk.yellow.bold('[FastAct]'),
|
|
7
|
+
ERROR: chalk.red.bold('[FastAct]'),
|
|
8
8
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AppConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Helper function to define FastAct configuration with TypeScript inference
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // fastact.config.ts
|
|
8
|
+
* import { defineConfig } from '@fastact/core'
|
|
9
|
+
*
|
|
10
|
+
* export default defineConfig({
|
|
11
|
+
* modules: ['./dist/**\/*.module.js'],
|
|
12
|
+
* modulesTs: ['./src/**\/*.module.ts'],
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function defineConfig<T extends AppConfig = AppConfig>(config: T): T;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to define FastAct configuration with TypeScript inference
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* // fastact.config.ts
|
|
7
|
+
* import { defineConfig } from '@fastact/core'
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* modules: ['./dist/**\/*.module.js'],
|
|
11
|
+
* modulesTs: ['./src/**\/*.module.ts'],
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function defineConfig(config) {
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './domain.error';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './domain.error';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './errors';
|
|
2
2
|
export * from './types';
|
|
3
|
-
export
|
|
3
|
+
export * from './ioc';
|
|
4
|
+
export { defineConfig } from './define-config';
|
|
5
|
+
export { createApp, createStandaloneApp, AppFactory as FastAct } from './app-factory';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './errors';
|
|
2
2
|
export * from './types';
|
|
3
|
-
export
|
|
3
|
+
export * from './ioc';
|
|
4
|
+
export { defineConfig } from './define-config';
|
|
5
|
+
// prettier-ignore
|
|
6
|
+
export { createApp, createStandaloneApp, AppFactory as FastAct } from './app-factory';
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ContainerInstance, InjectionToken, RegistrationTarget } from './types';
|
|
2
|
+
/** Represents a module that can be loaded into the container builder. */
|
|
3
|
+
export type ContainerModule = (builder: ContainerBuilder) => Promise<void>;
|
|
2
4
|
/**
|
|
3
5
|
* Dependency container builder with a fluent registration API.
|
|
4
6
|
*
|
|
5
|
-
* Example: `builder.add(DI.UserService).asClass(
|
|
7
|
+
* Example: `builder.add(DI.UserService).asClass(UserService).withDeps(DI.Database).scoped()`.
|
|
6
8
|
*/
|
|
7
|
-
export declare class ContainerBuilder
|
|
9
|
+
export declare class ContainerBuilder {
|
|
8
10
|
private container;
|
|
9
11
|
private register;
|
|
10
12
|
/**
|
|
11
|
-
* Starts dependency registration using its
|
|
13
|
+
* Starts dependency registration using its token.
|
|
12
14
|
*
|
|
13
15
|
* Specify a class with `asClass()`, a factory with `asFactory()`, or a ready
|
|
14
16
|
* value with `asValue()`.
|
|
15
17
|
*/
|
|
16
|
-
add<T>(token: InjectionToken<T>): RegistrationTarget<T>;
|
|
18
|
+
add<T>(token: InjectionToken<T>): RegistrationTarget<T, this>;
|
|
17
19
|
build(): Promise<ContainerInstance>;
|
|
20
|
+
private findProjectRoot;
|
|
21
|
+
/** Helper method for loading the config with TS/JS support. */
|
|
22
|
+
private loadConfig;
|
|
18
23
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
2
4
|
import { glob } from 'node:fs/promises';
|
|
3
5
|
import chalk from 'chalk';
|
|
4
6
|
import { Container } from './container';
|
|
5
7
|
/**
|
|
6
8
|
* Dependency container builder with a fluent registration API.
|
|
7
9
|
*
|
|
8
|
-
* Example: `builder.add(DI.UserService).asClass(
|
|
10
|
+
* Example: `builder.add(DI.UserService).asClass(UserService).withDeps(DI.Database).scoped()`.
|
|
9
11
|
*/
|
|
10
12
|
export class ContainerBuilder {
|
|
11
13
|
container = new Container();
|
|
@@ -14,20 +16,20 @@ export class ContainerBuilder {
|
|
|
14
16
|
return this;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
|
-
* Starts dependency registration using its
|
|
19
|
+
* Starts dependency registration using its token.
|
|
18
20
|
*
|
|
19
21
|
* Specify a class with `asClass()`, a factory with `asFactory()`, or a ready
|
|
20
22
|
* value with `asValue()`.
|
|
21
23
|
*/
|
|
22
24
|
add(token) {
|
|
23
25
|
const withFactory = (factory) => {
|
|
26
|
+
// prettier-ignore
|
|
24
27
|
// prettier-ignore
|
|
25
28
|
const withLifecycle = (deps = []) => ({
|
|
26
|
-
/** One instance per scope, such as an HTTP request. */
|
|
27
29
|
scoped: () => this.register(token, factory, deps, 'scoped'),
|
|
28
|
-
/**
|
|
30
|
+
/** Registers one instance for the container's entire lifetime. */
|
|
29
31
|
singleton: () => this.register(token, factory, deps, 'singleton'),
|
|
30
|
-
/**
|
|
32
|
+
/** Registers a new instance for each resolution. */
|
|
31
33
|
transient: () => this.register(token, factory, deps, 'transient'),
|
|
32
34
|
});
|
|
33
35
|
return {
|
|
@@ -42,6 +44,25 @@ export class ContainerBuilder {
|
|
|
42
44
|
return {
|
|
43
45
|
/** Specifies a class that the container instantiates with `new`. */
|
|
44
46
|
asClass: (Class) => withFactory((...deps) => new Class(...deps)),
|
|
47
|
+
/*
|
|
48
|
+
withFactory((...deps) => {
|
|
49
|
+
const instance = new Class(...deps);
|
|
50
|
+
const allProps = new Set([
|
|
51
|
+
...Object.getOwnPropertyNames(instance),
|
|
52
|
+
...Object.getOwnPropertyNames(Object.getPrototypeOf(instance))
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
allProps.forEach((name) => {
|
|
56
|
+
if (name === 'constructor') return;
|
|
57
|
+
const value = (instance as any)[name];
|
|
58
|
+
if (typeof value === 'function' && value.prototype !== undefined) {
|
|
59
|
+
(instance as any)[name] = value.bind(instance);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return instance;
|
|
64
|
+
}),
|
|
65
|
+
*/
|
|
45
66
|
/** Specifies a factory that creates the dependency instance. */
|
|
46
67
|
asFactory: (factory) => withFactory(factory),
|
|
47
68
|
/** Registers a ready value as a singleton dependency. */
|
|
@@ -55,43 +76,85 @@ export class ContainerBuilder {
|
|
|
55
76
|
let moduleErrorName;
|
|
56
77
|
try {
|
|
57
78
|
const runDir = path.dirname(process.argv[1]);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
const root = this.findProjectRoot(runDir);
|
|
80
|
+
const config = await this.loadConfig(root);
|
|
81
|
+
const isTypeScriptRuntime = process.argv[1]?.endsWith('.ts') ||
|
|
82
|
+
typeof globalThis.Bun !==
|
|
83
|
+
'undefined';
|
|
84
|
+
let masks;
|
|
85
|
+
if (isTypeScriptRuntime) {
|
|
86
|
+
masks =
|
|
87
|
+
config.modulesTs && config.modulesTs.length > 0
|
|
88
|
+
? config.modulesTs
|
|
89
|
+
: ['src/**/*.module.ts'];
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
masks =
|
|
93
|
+
config.modules && config.modules.length > 0
|
|
94
|
+
? config.modules
|
|
95
|
+
: ['dist/**/*.module.js'];
|
|
96
|
+
}
|
|
97
|
+
for (const mask of masks) {
|
|
98
|
+
const cleanPathMask = mask.startsWith('./') ? mask.slice(2) : mask;
|
|
99
|
+
const modulesGlob = glob(cleanPathMask, {
|
|
100
|
+
cwd: root,
|
|
101
|
+
exclude: (p) => p.includes('node_modules') ||
|
|
102
|
+
p.includes('.git') ||
|
|
103
|
+
(isTypeScriptRuntime ? p.endsWith('.js') : p.endsWith('.ts')), // Smart exclusion: ignore dist in TS runtime, ignore src in JS runtime
|
|
104
|
+
});
|
|
105
|
+
for await (const relativePath of modulesGlob) {
|
|
106
|
+
const fullPath = path.resolve(root, relativePath);
|
|
107
|
+
moduleErrorName = fullPath;
|
|
108
|
+
// In production this will be a native, fast import() of a plain JS file
|
|
109
|
+
const fileUrl = pathToFileURL(fullPath).href;
|
|
110
|
+
const moduleExport = await import(fileUrl);
|
|
111
|
+
const initModule = moduleExport.createContainerModule;
|
|
112
|
+
if (typeof initModule === 'function') {
|
|
113
|
+
await initModule(this);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
throw new Error(`Missing required "export function createContainerModule(builder) { ... }"`);
|
|
117
|
+
}
|
|
75
118
|
}
|
|
76
119
|
}
|
|
77
120
|
}
|
|
78
121
|
catch (err) {
|
|
79
|
-
console.log(`${chalk.red.bold('[
|
|
122
|
+
console.log(`${chalk.red.bold('[FastAct]')} IoC auto-load failed: ${err instanceof Error ? err.message : err} in the module: ${moduleErrorName}`);
|
|
80
123
|
}
|
|
81
|
-
const container = this.container;
|
|
82
|
-
/* container.get = <T>(token: InjectionToken<T>): T => {
|
|
83
|
-
const key = token.description;
|
|
84
|
-
if (!key) {
|
|
85
|
-
throw new Error('Symbol token must have a description to be resolved!');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const dependency = container[key];
|
|
89
|
-
if (!dependency) {
|
|
90
|
-
throw new Error(`IoC dependency for Symbol(${key}) not found`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return dependency;
|
|
94
|
-
}; */
|
|
95
124
|
return this.container;
|
|
96
125
|
}
|
|
126
|
+
findProjectRoot(startDir) {
|
|
127
|
+
let currentDir = startDir;
|
|
128
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
129
|
+
if (existsSync(path.join(currentDir, 'package.json'))) {
|
|
130
|
+
return currentDir;
|
|
131
|
+
}
|
|
132
|
+
currentDir = path.dirname(currentDir);
|
|
133
|
+
}
|
|
134
|
+
return startDir;
|
|
135
|
+
}
|
|
136
|
+
/** Helper method for loading the config with TS/JS support. */
|
|
137
|
+
async loadConfig(projectRootDir) {
|
|
138
|
+
const extensions = ['.ts', '.js', '.mts', '.mjs', '.cts', '.cjs'];
|
|
139
|
+
let configPath = '';
|
|
140
|
+
for (const ext of extensions) {
|
|
141
|
+
const file = path.join(projectRootDir, `fastact.config${ext}`);
|
|
142
|
+
if (existsSync(file)) {
|
|
143
|
+
configPath = file;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!configPath)
|
|
148
|
+
return {};
|
|
149
|
+
try {
|
|
150
|
+
// Convert the absolute path to a file:// URL (required for ESM import)
|
|
151
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
152
|
+
const configModule = await import(fileUrl);
|
|
153
|
+
return configModule.default || configModule;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
console.log(`${chalk.yellow.bold('[FastAct]')} Failed to load config file: ${err instanceof Error ? err.message : err}`);
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
97
160
|
}
|
package/dist/ioc/container.d.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { Lifecycle, Factory, InjectionToken } from './types';
|
|
2
2
|
export declare class Container {
|
|
3
3
|
private registry;
|
|
4
|
-
private
|
|
5
|
-
/** Registers a dependency factory and its
|
|
6
|
-
register<T, D extends any[]>(token: InjectionToken<T>, factory: Factory<T, D>, deps
|
|
4
|
+
private scopeCaches;
|
|
5
|
+
/** Registers a dependency factory, its dependencies, and its lifecycle. */
|
|
6
|
+
register<T, D extends any[]>(token: InjectionToken<T>, factory: Factory<T, D>, deps?: InjectionToken<D[number]>[], lifecycle?: Lifecycle): this;
|
|
7
7
|
/** Registers a ready value as a singleton dependency. */
|
|
8
8
|
registerValue<T>(token: InjectionToken<T>, value: T): this;
|
|
9
|
-
/**
|
|
10
|
-
* Resolves a dependency. Scoped dependencies require a scope identifier,
|
|
11
|
-
* such as an HTTP request identifier.
|
|
12
|
-
*/
|
|
9
|
+
/** Resolves a dependency according to its lifecycle. */
|
|
13
10
|
get<T>(token: InjectionToken<T>, scopeId?: string, resolvesSingleton?: boolean, resolutionPath?: InjectionToken<any>[]): T;
|
|
11
|
+
private resolveSingleton;
|
|
12
|
+
private resolveScoped;
|
|
13
|
+
private getScopeCache;
|
|
14
14
|
private build;
|
|
15
|
-
/** Creates
|
|
15
|
+
/** Creates a scoped resolver that reuses instances for the given scope ID. */
|
|
16
16
|
createScope(id: string): ScopedContainer;
|
|
17
17
|
/** Removes all cached scoped dependencies for the specified scope. */
|
|
18
18
|
clearScope(id: string): void;
|
|
19
19
|
private clearTokenFromScopes;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
21
|
+
/** Resolves dependencies within a predefined scope. */
|
|
22
22
|
export declare class ScopedContainer {
|
|
23
23
|
private parent;
|
|
24
24
|
scopeId: string;
|
package/dist/ioc/container.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export class Container {
|
|
2
2
|
registry = new Map();
|
|
3
|
-
|
|
4
|
-
/** Registers a dependency factory and its
|
|
5
|
-
register(token, factory, deps, lifecycle = '
|
|
3
|
+
scopeCaches = new Map();
|
|
4
|
+
/** Registers a dependency factory, its dependencies, and its lifecycle. */
|
|
5
|
+
register(token, factory, deps = [], lifecycle = 'singleton') {
|
|
6
6
|
this.registry.set(token, {
|
|
7
7
|
factory,
|
|
8
8
|
deps,
|
|
@@ -24,37 +24,47 @@ export class Container {
|
|
|
24
24
|
this.clearTokenFromScopes(token);
|
|
25
25
|
return this;
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolves a dependency. Scoped dependencies require a scope identifier,
|
|
29
|
-
* such as an HTTP request identifier.
|
|
30
|
-
*/
|
|
27
|
+
/** Resolves a dependency according to its lifecycle. */
|
|
31
28
|
get(token, scopeId, resolvesSingleton = false, resolutionPath = []) {
|
|
32
29
|
const entry = this.registry.get(token);
|
|
33
|
-
if (!entry)
|
|
30
|
+
if (!entry) {
|
|
34
31
|
throw new Error(`Dependency ${String(token)} not found`);
|
|
35
|
-
if (entry.lifecycle === 'singleton') {
|
|
36
|
-
if (!entry.isInitialized) {
|
|
37
|
-
entry.instance = this.build(token, entry, scopeId, true, resolutionPath);
|
|
38
|
-
entry.isInitialized = true;
|
|
39
|
-
}
|
|
40
|
-
return entry.instance;
|
|
41
32
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
33
|
+
const resolvers = {
|
|
34
|
+
singleton: () => this.resolveSingleton(token, entry, scopeId, resolutionPath),
|
|
35
|
+
scoped: () =>
|
|
36
|
+
// prettier-ignore
|
|
37
|
+
this.resolveScoped(token, entry, scopeId, resolvesSingleton, resolutionPath),
|
|
38
|
+
transient: () => this.build(token, entry, scopeId, resolvesSingleton, resolutionPath),
|
|
39
|
+
};
|
|
40
|
+
return resolvers[entry.lifecycle]?.();
|
|
41
|
+
}
|
|
42
|
+
resolveSingleton(token, entry, scopeId, resolutionPath) {
|
|
43
|
+
if (!entry.isInitialized) {
|
|
44
|
+
entry.instance = this.build(token, entry, scopeId, true, resolutionPath);
|
|
45
|
+
entry.isInitialized = true;
|
|
46
|
+
}
|
|
47
|
+
return entry.instance;
|
|
48
|
+
}
|
|
49
|
+
resolveScoped(token, entry, scopeId, resolvesSingleton, resolutionPath) {
|
|
50
|
+
if (resolvesSingleton) {
|
|
51
|
+
throw new Error(`Singleton dependency chain cannot include scoped dependency ${String(token)}`);
|
|
52
|
+
}
|
|
53
|
+
if (!scopeId)
|
|
54
|
+
throw new Error(`Scope ID required for ${String(token)}`);
|
|
55
|
+
const scopeCache = this.getScopeCache(scopeId);
|
|
56
|
+
if (!scopeCache.has(token)) {
|
|
57
|
+
scopeCache.set(token, this.build(token, entry, scopeId, false, resolutionPath));
|
|
58
|
+
}
|
|
59
|
+
return scopeCache.get(token);
|
|
60
|
+
}
|
|
61
|
+
getScopeCache(scopeId) {
|
|
62
|
+
let scopeCache = this.scopeCaches.get(scopeId);
|
|
63
|
+
if (!scopeCache) {
|
|
64
|
+
scopeCache = new Map();
|
|
65
|
+
this.scopeCaches.set(scopeId, scopeCache);
|
|
56
66
|
}
|
|
57
|
-
return
|
|
67
|
+
return scopeCache;
|
|
58
68
|
}
|
|
59
69
|
build(token, entry, scopeId, resolvesSingleton, resolutionPath) {
|
|
60
70
|
if (resolutionPath.includes(token)) {
|
|
@@ -65,21 +75,21 @@ export class Container {
|
|
|
65
75
|
const args = entry.deps.map((dep) => this.get(dep, scopeId, resolvesSingleton, nextPath));
|
|
66
76
|
return entry.factory(...args);
|
|
67
77
|
}
|
|
68
|
-
/** Creates
|
|
78
|
+
/** Creates a scoped resolver that reuses instances for the given scope ID. */
|
|
69
79
|
createScope(id) {
|
|
70
80
|
return new ScopedContainer(this, id);
|
|
71
81
|
}
|
|
72
82
|
/** Removes all cached scoped dependencies for the specified scope. */
|
|
73
83
|
clearScope(id) {
|
|
74
|
-
this.
|
|
84
|
+
this.scopeCaches.delete(id);
|
|
75
85
|
}
|
|
76
86
|
clearTokenFromScopes(token) {
|
|
77
|
-
for (const scopeCache of this.
|
|
87
|
+
for (const scopeCache of this.scopeCaches.values()) {
|
|
78
88
|
scopeCache.delete(token);
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
|
-
/**
|
|
92
|
+
/** Resolves dependencies within a predefined scope. */
|
|
83
93
|
export class ScopedContainer {
|
|
84
94
|
parent;
|
|
85
95
|
scopeId;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
import type { ContainerInstance, ScopedContainerInstance } from './types';
|
|
3
|
+
interface InternalRootContainer extends ContainerInstance {
|
|
4
|
+
createScope(id: string): InternalScopedContainer;
|
|
5
|
+
}
|
|
6
|
+
interface InternalScopedContainer extends ScopedContainerInstance {
|
|
7
|
+
dispose(): void | Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
declare module 'fastify' {
|
|
10
|
+
interface FastifyRequest {
|
|
11
|
+
container: ContainerInstance;
|
|
12
|
+
scopedContainer: ScopedContainerInstance;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export declare const fastactIocPlugin: FastifyPluginAsync<{
|
|
16
|
+
container: InternalRootContainer;
|
|
17
|
+
}>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
// prettier-ignore
|
|
3
|
+
const plugin = async (fastify, options) => {
|
|
4
|
+
fastify.decorateRequest('container', null);
|
|
5
|
+
fastify.decorateRequest('scopedContainer', null);
|
|
6
|
+
fastify.addHook('onRequest', async (req) => {
|
|
7
|
+
const r = req;
|
|
8
|
+
r.container = options.container;
|
|
9
|
+
r.scopedContainer = options.container.createScope(req.id);
|
|
10
|
+
});
|
|
11
|
+
fastify.addHook('onResponse', async (req) => {
|
|
12
|
+
const r = req;
|
|
13
|
+
if (r.scopedContainer && typeof r.scopedContainer.dispose === 'function') {
|
|
14
|
+
await r.scopedContainer.dispose();
|
|
15
|
+
}
|
|
16
|
+
// Зануляем ссылки, очищая память для Garbage Collector
|
|
17
|
+
r.container = null;
|
|
18
|
+
r.scopedContainer = null;
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
export const fastactIocPlugin = fp(plugin, { name: '@fastact/ioc-plugin' });
|
package/dist/ioc/index.d.ts
CHANGED
package/dist/ioc/index.js
CHANGED
package/dist/ioc/types.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/** Public contract for configuring and building a dependency container. */
|
|
2
|
-
export interface IContainerBuilder {
|
|
3
|
-
add<T>(token: InjectionToken<T>): RegistrationTarget<T>;
|
|
4
|
-
build(): Promise<ContainerInstance>;
|
|
5
|
-
}
|
|
6
1
|
export interface ContainerInstance {
|
|
7
2
|
get<T>(token: InjectionToken<T>): T;
|
|
8
3
|
}
|
|
4
|
+
export interface ScopedContainerInstance {
|
|
5
|
+
get<T>(token: InjectionToken<T>): T;
|
|
6
|
+
}
|
|
7
|
+
/** A symbol token carrying the type of the dependency it identifies. */
|
|
9
8
|
export interface SymbolToken<T> extends Symbol {
|
|
10
9
|
readonly __type?: T;
|
|
11
10
|
}
|
|
11
|
+
/** A string or typed symbol used to identify a dependency. */
|
|
12
12
|
export type InjectionToken<T> = string | SymbolToken<T>;
|
|
13
13
|
/** The lifetime of a registered dependency. */
|
|
14
14
|
export type Lifecycle = 'singleton' | 'scoped' | 'transient';
|
|
@@ -20,16 +20,16 @@ export interface DepEntry<T> {
|
|
|
20
20
|
instance?: T;
|
|
21
21
|
isInitialized: boolean;
|
|
22
22
|
}
|
|
23
|
-
export interface RegistrationLifecycle {
|
|
24
|
-
scoped():
|
|
25
|
-
singleton():
|
|
26
|
-
transient():
|
|
23
|
+
export interface RegistrationLifecycle<B = any> {
|
|
24
|
+
scoped(): B;
|
|
25
|
+
singleton(): B;
|
|
26
|
+
transient(): B;
|
|
27
27
|
}
|
|
28
|
-
export interface RegistrationWithDependencies extends RegistrationLifecycle {
|
|
29
|
-
withDeps(...deps: Array<InjectionToken<any> | InjectionToken<any>[]>): RegistrationLifecycle
|
|
28
|
+
export interface RegistrationWithDependencies<B> extends RegistrationLifecycle<B> {
|
|
29
|
+
withDeps(...deps: Array<InjectionToken<any> | InjectionToken<any>[]>): RegistrationLifecycle<B>;
|
|
30
30
|
}
|
|
31
|
-
export interface RegistrationTarget<
|
|
32
|
-
asClass(Class: new (...
|
|
33
|
-
asFactory(factory:
|
|
34
|
-
asValue(value:
|
|
31
|
+
export interface RegistrationTarget<V, B> {
|
|
32
|
+
asClass(Class: new (...args: any[]) => V): RegistrationWithDependencies<B>;
|
|
33
|
+
asFactory(factory: (...args: any[]) => V): RegistrationWithDependencies<B>;
|
|
34
|
+
asValue(value: V): B;
|
|
35
35
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
|
-
import type { FastifyInstance,
|
|
2
|
-
import { ContainerInstance } from './ioc';
|
|
1
|
+
import type { FastifyInstance, FastifyHttpOptions } from 'fastify';
|
|
2
|
+
import type { ContainerInstance } from './ioc';
|
|
3
3
|
export interface Application {
|
|
4
4
|
getContainer(): ContainerInstance;
|
|
5
5
|
getServer(): FastifyInstance;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/** Stops the application. */
|
|
9
|
-
stop(): Promise<void>;
|
|
6
|
+
/** Close the application. */
|
|
7
|
+
close(): Promise<void>;
|
|
10
8
|
}
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
export interface ApplicationOptions<
|
|
11
|
+
RawServer extends RawServerBase = RawServerDefault,
|
|
12
|
+
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
|
13
|
+
> {
|
|
14
|
+
host?: string;
|
|
15
|
+
port?: number;
|
|
16
|
+
// runServer?: boolean;
|
|
17
|
+
server?: FastifyServerOptions<RawServer, Logger>;
|
|
18
|
+
}
|
|
19
|
+
**/
|
|
20
|
+
export interface NormalAppOptions {
|
|
12
21
|
host?: string;
|
|
13
22
|
port?: number;
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
server?: FastifyHttpOptions<any>;
|
|
24
|
+
}
|
|
25
|
+
export interface StandaloneAppOptions {
|
|
26
|
+
}
|
|
27
|
+
export interface AppConfig {
|
|
28
|
+
/**
|
|
29
|
+
* Glob patterns for compiled JS module files to auto-load.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```js
|
|
33
|
+
* modules: ['./dist/**\/*.module.js'],
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
modules?: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Glob patterns for TypeScript module files to auto-load.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* modulesTs: ['./src/**\/*.module.ts'],
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
modulesTs?: string[];
|
|
16
46
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './logger';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './logger';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const RESET = '\x1b[0m';
|
|
2
|
+
const BOLD_YELLOW = '\x1b[1;33m';
|
|
3
|
+
const BOLD_RED = '\x1b[1;31m';
|
|
4
|
+
const BOLD_GREEN = '\x1b[1;32m';
|
|
5
|
+
export const logger = {
|
|
6
|
+
warn(message) {
|
|
7
|
+
console.warn(`${BOLD_YELLOW}[FastAct]${RESET} ${message}`);
|
|
8
|
+
},
|
|
9
|
+
error(message, error) {
|
|
10
|
+
const errMessage = error instanceof Error ? error.message : String(error || '');
|
|
11
|
+
const suffix = errMessage ? `: ${errMessage}` : '';
|
|
12
|
+
console.error(`${BOLD_RED}[FastAct]${RESET} ${message}${suffix}`);
|
|
13
|
+
},
|
|
14
|
+
info(message) {
|
|
15
|
+
console.log(`${BOLD_GREEN}[FastAct]${RESET} ${message}`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class ValidationError extends Error {
|
|
2
|
+
code = 'E_VALIDATION_ERROR';
|
|
3
|
+
statusCode = 422;
|
|
4
|
+
errors;
|
|
5
|
+
constructor(rawErrors) {
|
|
6
|
+
super('Validation failed');
|
|
7
|
+
this.name = 'ValidationError';
|
|
8
|
+
this.errors = rawErrors;
|
|
9
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fastactValidationPlugin: (fastify: import("fastify").FastifyInstance<import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>) => Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
import Ajv from 'ajv';
|
|
3
|
+
import { ValidationError } from './errors';
|
|
4
|
+
const ajv = new Ajv({ coerceTypes: true, useDefaults: true, removeAdditional: true });
|
|
5
|
+
const compilersCache = new Map();
|
|
6
|
+
export const fastactValidationPlugin = fp(async (fastify) => {
|
|
7
|
+
// Регистрируем метод в прототип запроса Fastify
|
|
8
|
+
fastify.decorateRequest('validateWith', async function (schema) {
|
|
9
|
+
const request = this;
|
|
10
|
+
let validateFn = compilersCache.get(schema);
|
|
11
|
+
if (!validateFn) {
|
|
12
|
+
validateFn = ajv.compile(schema);
|
|
13
|
+
compilersCache.set(schema, validateFn);
|
|
14
|
+
}
|
|
15
|
+
// Валидируем body (или можно расширить до query/params)
|
|
16
|
+
const isValid = validateFn(request.body);
|
|
17
|
+
if (!isValid) {
|
|
18
|
+
// Выкидываем кастомную ошибку пакета validation
|
|
19
|
+
throw new ValidationError(validateFn.errors);
|
|
20
|
+
}
|
|
21
|
+
return request.body;
|
|
22
|
+
});
|
|
23
|
+
});
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastact/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-dev",
|
|
4
4
|
"description": "A fantastic, vanilla-flavored web framework core powered by Fastify. Built for JS purists: strictly 0 decorators, 0 reflect-metadata magic, and 100% predictable asynchronous flow.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Igor Bezlepkin",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "github
|
|
9
|
+
"url": "git+https://github.com/fastactjs/core.git"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "./dist/index.js",
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "tsc"
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:ui": "vitest --ui"
|
|
16
19
|
},
|
|
17
20
|
"keywords": [
|
|
18
21
|
"fastact",
|
|
@@ -31,14 +34,19 @@
|
|
|
31
34
|
"node": ">=22.0.0"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
|
-
"chalk": "^6.0.0"
|
|
35
|
-
|
|
37
|
+
"chalk": "^6.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"fastify": "^5.12.1",
|
|
41
|
+
"fastify-plugin": "^6.0.0"
|
|
36
42
|
},
|
|
37
43
|
"devDependencies": {
|
|
38
44
|
"@types/node": "^22.0.0",
|
|
39
|
-
"
|
|
45
|
+
"fastify": "^5.12.1",
|
|
46
|
+
"typescript": "^5.5.0",
|
|
47
|
+
"vitest": "^5.0.0"
|
|
40
48
|
},
|
|
41
49
|
"files": [
|
|
42
50
|
"dist"
|
|
43
51
|
]
|
|
44
|
-
}
|
|
52
|
+
}
|
package/dist/application.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import type { ContainerInstance } from './ioc';
|
|
3
|
-
import type { ApplicationOptions, Application } from './types';
|
|
4
|
-
export declare class App implements Application {
|
|
5
|
-
private readonly container;
|
|
6
|
-
private readonly server;
|
|
7
|
-
private readonly options;
|
|
8
|
-
constructor(container: ContainerInstance, options?: ApplicationOptions);
|
|
9
|
-
getContainer(): ContainerInstance;
|
|
10
|
-
getServer(): FastifyInstance;
|
|
11
|
-
start(): Promise<void>;
|
|
12
|
-
stop(): Promise<void>;
|
|
13
|
-
}
|
package/dist/application.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import fastify from 'fastify';
|
|
3
|
-
import { DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT } from './constants';
|
|
4
|
-
export class App {
|
|
5
|
-
container;
|
|
6
|
-
server;
|
|
7
|
-
options;
|
|
8
|
-
constructor(container, options = {}) {
|
|
9
|
-
this.options = options;
|
|
10
|
-
this.container = container;
|
|
11
|
-
this.server = fastify(this.options.server);
|
|
12
|
-
}
|
|
13
|
-
getContainer() {
|
|
14
|
-
return this.container;
|
|
15
|
-
}
|
|
16
|
-
getServer() {
|
|
17
|
-
return this.server;
|
|
18
|
-
}
|
|
19
|
-
async start() {
|
|
20
|
-
/** Exit immediately when running as a CLI command or worker without a server. */
|
|
21
|
-
if (this.options.runServer === false) {
|
|
22
|
-
console.log(chalk.yellow('FastAct initialized in standalone (CLI) mode'));
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const host = this.options.host ?? DEFAULT_SERVER_HOST;
|
|
26
|
-
const port = this.options.port ?? DEFAULT_SERVER_PORT;
|
|
27
|
-
try {
|
|
28
|
-
await this.server.ready();
|
|
29
|
-
await this.server.listen({ port, host });
|
|
30
|
-
console.log(`${chalk.green.bold('[fastact]')} Server is now listening on ${host}:${port}`);
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
console.log(`${chalk.red.bold('[fastact]')} ${err}`);
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async stop() {
|
|
38
|
-
console.log(`${chalk.yellow.bold('[fastact] Shutting down the server...')}`);
|
|
39
|
-
await this.server.close();
|
|
40
|
-
console.log(chalk.bgGreenBright.bold('[fastact] Server gracefully stopped'));
|
|
41
|
-
}
|
|
42
|
-
}
|