Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
/.idea
coverage
61 changes: 57 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# <img src="https://avatars.githubusercontent.com/u/198393404?s=200&v=4" width="45px" style="position: relative; top: 10px; border-radius: 20%"> Axon.js
<img src="https://avatars.githubusercontent.com/u/198393404?s=200&v=4" width="80px" style="position: relative; top: 10px; left: 50%; transform: translateX(-50%); border-radius: 20%">

# Axon.js


**like the brain's powerful neural pathways, simple yet strong. 🧠**
Expand All @@ -10,7 +12,60 @@ Currently Axon is 2X faster than Express. :D please checkout [Axon Benchmarks](.

[Axon telegram channel](https://t.me/axonjs)

Latest change: (v0.11.0)
Latest change: (v0.12.0)
- Dependency injection
Axon's new feature is dependency injection for class methods and controller functions.
support for class constructor and middlewar;es will add soon.
How to use:
1. Register your dependency into the Axon core:

```typescript
// class instance
core.registerDependency("dependencyName", new DependencyClass(arg1, arg2));

// class without instance (core will make instance automatically)
core.registerDependency("dependencyName2", DependencyClass);

// function
core.registerDependency("dependencyName3", dependencyFunction);

// Also you can register dependencies with name aliases
core.registerDependency(["depClass", "classDep", "DB"], new DependencyClass());
```

2. Use you dependency in controller

```typescript
// When you want to use dependencies, make sure you enter exact dependency name or alias
// in 3rd argument of controller method or function into the object.
interface IDependencies {
DB: DependencyClass;
dependencyName3: (...args: any[]) => any
}

class UserController extends BaseController {
async index(req: Request<any>, res: Response, { DB, dependencyName3 }: IDependencies) {
const result = dependencyName3();
const db = DB.get();
}
}

// Or functional controller:
const getUsers = async (
req: Request<any>,
res: Response,
{ DB, dependencyName3 }: IDependencies
) => {
const result = dependencyName3();
const db = DB.get();
}

router.get("/users/class", [UserController, "index"]);
router.get("/users/function", getUsers);
```

Past changes:
#### (v0.11.0)
- Class-Based Controllers
You can use the new generation of controllers in Axon.
In class-based controllers you can use state managing of classes in your application without any instance making.
Expand Down Expand Up @@ -38,8 +93,6 @@ Latest change: (v0.11.0)

// Error: Controller class must extends from BaseController
```

Past changes:
#### (v0.10.0)
- Cookie manager added to Axon.
You can access cookie manager by importing AxonCookie class in your code.
Expand Down
17 changes: 17 additions & 0 deletions examples/dependency-injection/controllers/User.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BaseController } from "../../../src";
import type { Response, Request } from "../../../src";
import { MainDB } from "../main";

class UserController extends BaseController {
// * You can inject your dependency as third arguments in an object and core will detect and injdect it automatically
// ! (types are not important)
async get(req: Request<any>, res: Response, { MainDB }: { MainDB: MainDB }) {
await MainDB.getUser(12);
}

async find(req: Request<any>, res: Response, { userQuery }: { userQuery: (...args: any[]) => any }) {
userQuery();
}
}

export default UserController;
34 changes: 34 additions & 0 deletions examples/dependency-injection/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Axon } from "../../src";

export class MainDB {
async getUser(id: number) {
return {
id,
name: "Erfan"
}
}
}

export const findUser = async (id: number) => {
return {
msg: "User not found!"
}
}

const core = Axon();

// Register dependency with a name
// class as dependency
core.registerDependency("MainDB", new MainDB());

// Register dependency with aliases
// Main name will choose ReplicateDB (first item)
core.registerDependency(["ReplicateDB", "RDB", "secondDB"], new MainDB());

// Register dependency without making instance. (core will make instance automatically)
core.registerDependency("testDep", MainDB);

// Register function as dependency
core.registerDependency("userQuery", findUser);

core.listen();
5 changes: 1 addition & 4 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,5 @@ import { compilerOptions } from './tsconfig.json';

export default {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: '<rootDir>/',
}),
testEnvironment: 'node'
};
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@axonlabs/core",
"version": "0.11.0",
"version": "0.12.0",
"description": "A backend library that aims to be simple and powerful.",
"author": "Mr.MKZ",
"license": "ISC",
Expand Down Expand Up @@ -54,14 +54,16 @@
"dependencies": {
"@spacingbat3/kolor": "^4.0.0",
"esbuild": "^0.25.0",
"joi": "^17.13.3",
"lilconfig": "^3.1.3",
"moment": "^2.30.1",
"object-assign": "^4.1.1",
"pino": "^9.4.0",
"pino-pretty": "^13.0.0",
"vary": "^1.1.2",
"vary": "^1.1.2"
},
"peerDependencies": {
"yup": "^1.4.0",
"zod": "^3.23.8"
"zod": "^3.23.8",
"joi": "^17.13.3"
}
}
18 changes: 13 additions & 5 deletions src/core/AxonCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { AxonPlugin } from "../types/PluginTypes";
import type { JsonResponse } from "../types/GlobalTypes";
import type { AxonConfig } from "../types/ConfigTypes";
import type { UnloadRouteParams } from "../types/CoreTypes";
import { ClassController, MiddlewareStorage } from "../types/RouterTypes";
import type { ClassController, MiddlewareStorage } from "../types/RouterTypes";

// Exceptions
import { routeDuplicateException } from "./exceptions/CoreExceptions";
Expand All @@ -29,6 +29,7 @@ import { PluginLoader } from "./plugin/PluginLoader";
import { resolveConfig } from "./config/AxonConfig";
import { unloadRouteService, unloadRoutesService } from "./services/unloadRoutesService";
import { isAsync } from "./utils/helpers";
import { registerDependency as DIRegisterDependency, funcRunner, DependencyValue } from "./DI";

// Default values
const defaultResponses = {
Expand Down Expand Up @@ -237,6 +238,10 @@ export default class AxonCore {
await unloadRoutesService(this.routes)
}

async registerDependency(name: string | string[], dependency: DependencyValue) {
DIRegisterDependency(name, dependency);
}

/**
* You can set one or many middlewares in global scope with this method.
* @example
Expand Down Expand Up @@ -366,14 +371,17 @@ export default class AxonCore {
const controllerInstance: FuncController | ClassController<any, any> = route["handler"]["_controller"];

let controller: FuncController;
let manualArgs: string[];

if (Array.isArray(controllerInstance)) {
// --- Logic for ClassController [Controller, 'method'] ---
// In this block, TypeScript knows controllerInstance is an array.
// This is where you use the factory function we built.

// The 'controllerInstance' is exactly what createClassHandler was designed for!
controller = createClassHandler(controllerInstance);
const [handler, args] = createClassHandler(controllerInstance);
controller = handler;
manualArgs = args;

} else if (typeof controllerInstance === 'function') {
// --- Logic for FuncController ---
Expand All @@ -399,10 +407,10 @@ export default class AxonCore {
await this.handleMiddleware(req, res, async () => {
await this.handleMiddleware(req, res, async () => {
await this.handleMiddleware(req, res, async () => {
if (isAsync(controller)) {
await controller(req, res);
if (manualArgs) {
await funcRunner(controller, req, res, manualArgs);
} else {
controller(req, res);
await funcRunner(controller, req, res);
}

// log incoming requests
Expand Down
81 changes: 81 additions & 0 deletions src/core/DI/DependencyHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type {
Constructor,
Func,
Instance,
DependencyValue
} from ".";
import { isFunction, isConstructor, isInstance, extractArgs } from ".";
import { Request, Response } from "../../types/RouterTypes";
import { isAsync } from "../utils/helpers";

const DependecyStorage = new Map<string, DependencyValue>();
const DependencyAliases = new Map<string, string>();

const registerDependency = async (name: string | string[], dependency: DependencyValue) => {
const names = Array.isArray(name) ? name : [name];
const [mainName, ...aliases] = names;

if (!isConstructor(dependency) && !isInstance(dependency) && !isFunction(dependency)) {
throw new Error(`Unsupported dependency type for '${mainName}'`);
}

// Set main item, first name as main name.
DependecyStorage.set(mainName, dependency);

// Map all aliases for dependency to main item.
for (const alias of aliases) {
DependencyAliases.set(alias, mainName);
}
}

const funcRunner = async (func: Function, req: Request<any>, res: Response, manualArgs?: string[]) => {
let args = manualArgs || extractArgs(func);

const dependencies: { [name: string]: any } = {};

args.map(arg => {
const realName = DependencyAliases.get(arg) || arg;
let dep = DependecyStorage.get(realName);

if (!dep) {
throw new Error(
`Dependency '${arg}' not found ❌`,
{
cause: "You can only add dependencies that the core has them."
}
)
}

if (isConstructor(dep)) {
const instance = new dep();
DependecyStorage.set(realName, instance);
dependencies[arg] = instance;
return;
}

if (isInstance(dep)) {
dependencies[arg] = dep;
return;
}

if (isFunction(dep)) {
dependencies[arg] = dep;
return;
}

throw new Error(`Unsupported dependency type for '${arg}'`);
});

if (isAsync(func)) {
await func(req, res, dependencies);
} else {
func(req, res, dependencies);
}
}

export {
DependecyStorage,
DependencyAliases,
funcRunner,
registerDependency
}
71 changes: 71 additions & 0 deletions src/core/DI/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { logger } from "../utils/coreLogger";

type Constructor = new (...args: any[]) => any;
type Func = (...args: any[]) => any;
type Instance = object & { constructor: Function }
type DependencyValue = Constructor | Instance | Func;

/** Check if value is a class constructor */
const isConstructor = (value: DependencyValue): value is Constructor => {
return typeof value === 'function' &&
!!value.prototype &&
!!value.prototype.constructor &&
value.prototype.constructor === value;
}

/** Check if value is a n instance (object) */
const isInstance = (value: DependencyValue): value is Instance => {
return typeof value === 'object' &&
value !== null &&
typeof (value as any).constructor === 'function' &&
(value as any).constructor.name !== 'Object';
}

/** Check if value is a regular function (not a class) */
const isFunction = (value: DependencyValue): value is Func => {
return typeof value === 'function' &&
(!value.prototype || value.prototype.constructor !== value);
}

/** Extract function arguments and return array of argument names */
const extractArgs = (fn: Function): string[] => {
const fnStr = fn.toString().replace(/\/\/.*$|\/\*[\s\S]*?\*\//gm, '');
const match = fnStr.match(/\(([^)]*)\)/);
if (!match) return [];

const args = match[1]
.split(',')
.map(a => a.trim())
.filter(Boolean);

if (args.length < 3) return [];

const thirdArg = args[2];

// Match only if it's destructured like { db, logger }
const destructureMatch = thirdArg.match(/^\{([^}]+)\}$/);
if (!destructureMatch) return [];

return destructureMatch[1]
.split(',')
.map(k => k.trim().replace(/=.*$/, '')) // remove default values
.filter(Boolean);
}

export {
Func,
Constructor,
Instance,
isFunction,
isConstructor,
isInstance,
extractArgs,
DependencyValue
}

export {
registerDependency,
funcRunner,
DependecyStorage,
DependencyAliases
} from "./DependencyHandler"
Loading