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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ Currently Axon is 2X faster than Express. :D please checkout [Axon Benchmarks](.

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

Latest change: (v0.9.0)
Latest change: (v0.10.0)
- Cookie manager added to Axon.
You can access cookie manager by importing AxonCookie class in your code.
AxonCookie has some static methods for managing cookies easily.
Example:
```ts
import { AxonCookie } from "@axonlabs/core";

AxonCookie.set(res, name, value, options);
AxonCookie.parse(req);
AxonCookie.clear(res, name, options);
```

Past changes: (v0.9.0)
- Plugin system updated.
- Project environment state added to core config.
- Validation system added to router.
Expand Down Expand Up @@ -78,6 +91,7 @@ You can checkout Axon benchmarks document and results from below link.
- Controllers and Middlewares
- Default cors configuration method
- Support https server
- Auto validation handler (Yup, Zod, Joi)

**More features soon...**

Expand Down
41 changes: 40 additions & 1 deletion examples/routes/v2.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Router } from "../../src";
import { Router, AxonCookie } from "../../src";

// you can set route prefix in Router
const router = Router("/api/v1")
Expand All @@ -11,4 +11,43 @@ router.get('/{name}([a-z]+)/{id}(\\d+)', async (req, res) => {
})
})

// set cookie
router.post('hello', async (req, res) => {
AxonCookie.set(res, "user", "Asal <3", {
sameSite: "Strict",
duration: "1d",
domain: "127.0.0.1",
httpOnly: true,
path: "/api/v1/hello",
secure: true
})

return res.status(201).body({
message: "Cookie set successfuly"
});
});

// get cookie
router.get('hello', async (req, res) => {
return res.status(200).body({
cookie: AxonCookie.parse(req)
});
});

// delete cookie
router.delete('hello', async (req, res) => {
// All options which used while creating cookie must set for removing cookie.
AxonCookie.clear(res, "user", {
sameSite: "Strict",
domain: "127.0.0.1",
httpOnly: true,
path: "/api/v1/hello",
secure: true
});

return res.status(200).body({
message: "Cookie cleared successfuly"
});
});

export { router as v2Routes }
12 changes: 2 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@axonlabs/core",
"version": "0.9.0",
"version": "0.10.0",
"description": "A backend library that aims to be simple and powerful.",
"author": "Mr.MKZ",
"license": "ISC",
Expand All @@ -12,20 +12,12 @@
"url": "https://github.com/AxonJsLabs/AxonJs.git"
},
"keywords": [
"http-router",
"http",
"backend",
"restapi",
"rest-api",
"AxonJs",
"Axon",
"framework",
"web",
"rest",
"restful",
"router",
"app",
"api"
"restful"
],
"bugs": {
"url": "https://github.com/AxonJsLabs/AxonJs/issues"
Expand Down
14 changes: 7 additions & 7 deletions src/Router/AxonRouter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import RouterException from "@/Router/exceptions/RouterException";
import addRoutePrefix from "@/core/utils/routePrefixHandler";
import { FuncController, Middleware, RouteParams, HttpMethods, MiddlewareStorage } from "@/types/RouterTypes";
import { logger } from "@/core/utils/coreLogger";
import { resolveConfig } from "@/core/config/AxonConfig";
import { AxonValidator } from "@/core/validation/AxonValidator";
import type { ValidationObj } from "@/types/RouterTypes";
import RouterException from "./exceptions/RouterException";
import addRoutePrefix from "../core/utils/routePrefixHandler";
import { FuncController, Middleware, RouteParams, HttpMethods, MiddlewareStorage } from "../types/RouterTypes";
import { logger } from "../core/utils/coreLogger";
import { resolveConfig } from "../core/config/AxonConfig";
import { AxonValidator } from "../core/validation/AxonValidator";
import type { ValidationObj } from "../types/RouterTypes";

const duplicateError = (path: string, method: keyof HttpMethods) => {
throw new RouterException({
Expand Down
2 changes: 1 addition & 1 deletion src/Router/exceptions/RouterException.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExceptionMeta, RouterExceptionError } from "@/types/GlobalTypes";
import { ExceptionMeta, RouterExceptionError } from "../../types/GlobalTypes";

class RouterException extends Error {
public name: string;
Expand Down
30 changes: 15 additions & 15 deletions src/core/AxonCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ import { colors } from "@spacingbat3/kolor"
import { performance } from "perf_hooks";

// Utils
import { logger } from "@/core/utils/coreLogger";
import getRequestBody from "@/core/utils/getRequestBody";
import { logger } from "./utils/coreLogger";
import getRequestBody from "./utils/getRequestBody";

// Types
import type { FuncController, Request, Response, Middleware, HttpMethods } from "@/.";
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 type { FuncController, Request, Response, Middleware, HttpMethods } from "..";
import type { AxonPlugin } from "../types/PluginTypes";
import type { JsonResponse } from "../types/GlobalTypes";
import type { AxonConfig } from "../types/ConfigTypes";
import type { UnloadRouteParams } from "../types/CoreTypes";

// Exceptions
import { routeDuplicateException } from "@/core/exceptions/CoreExceptions";
import { routeDuplicateException } from "./exceptions/CoreExceptions";

// Instances
import Router from "@/Router/AxonRouter";
import Router from "../Router/AxonRouter";

// Features
import AxonResponse from "@/core/response/AxonResponse";
import AxonCors from "@/core/cors/AxonCors";
import { PluginLoader } from "@/core/plugin/PluginLoader";
import { resolveConfig } from "@/core/config/AxonConfig";
import { unloadRouteService, unloadRoutesService } from "@/core/services/unloadRoutesService";
import { MiddlewareStorage } from "@/types/RouterTypes";
import AxonResponse from "./response/AxonResponse";
import AxonCors from "./cors/AxonCors";
import { PluginLoader } from "./plugin/PluginLoader";
import { resolveConfig } from "./config/AxonConfig";
import { unloadRouteService, unloadRoutesService } from "./services/unloadRoutesService";
import { MiddlewareStorage } from "../types/RouterTypes";

// Default values
const defaultResponses = {
Expand Down
6 changes: 3 additions & 3 deletions src/core/config/AxonConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import esbuild from 'esbuild';
import { pathToFileURL } from 'url';

// utils
import { logger } from '@/core/utils/coreLogger';
import { logger } from '../utils/coreLogger';

// types
import type { AxonConfig } from "@/types/ConfigTypes";
import type { AxonConfig } from "../../types/ConfigTypes";

// default items
import defaultConfig from '@/core/config/defaultConfig';
import defaultConfig from '../config/defaultConfig';

const dynamicImport = new Function('file', 'return import(file)');

Expand Down
2 changes: 1 addition & 1 deletion src/core/config/defaultConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// types
import type { AxonConfig } from "@/types/ConfigTypes";
import type { AxonConfig } from "../../types/ConfigTypes";

export default {
PROJECT_ENV: "development",
Expand Down
106 changes: 106 additions & 0 deletions src/core/cookie/AxonCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Request, Response } from "../..";
import type { CookieOptions } from "../../types/CookieTypes";

type TimeUnit = 's' | 'm' | 'h' | 'd' | 'M' | 'y';

class AxonCookie {
/**
* Parse cookies from request headers
*/
static parse(req: Request<any>): Record<string, string> {
const cookies: Record<string, string> = {};
const cookieHeader = req.headers.cookie;

if (cookieHeader) {
cookieHeader.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
cookies[name] = decodeURIComponent(value);
});
}

return cookies;
}

/**
* Internal: convert duration string (e.g. "1d2h") to expires and maxAge
*/
private static parseDurationToExpireOptions(duration: string): { expires: Date; maxAge: number } {
const now = new Date();
const originalTime = now.getTime();
const matches = [...duration.matchAll(/(\d+)([smhdyM])/g)];

if (matches.length === 0) {
throw new Error("Invalid cookie duration format");
}

for (const match of matches) {
const num = parseInt(match[1], 10);
const unit = match[2] as TimeUnit;

switch (unit) {
case 's': now.setSeconds(now.getSeconds() + num); break;
case 'm': now.setMinutes(now.getMinutes() + num); break;
case 'h': now.setHours(now.getHours() + num); break;
case 'd': now.setDate(now.getDate() + num); break;
case 'M': now.setMonth(now.getMonth() + num); break;
case 'y': now.setFullYear(now.getFullYear() + num); break;
default: throw new Error(`Unsupported time unit: ${unit}`);
}
}

const maxAge = Math.floor((now.getTime() - originalTime) / 1000);
return { expires: now, maxAge };
}

/**
* Set a cookie in the response
*/
static set(res: Response, name: string, value: string, options: CookieOptions = {}): void {
let cookieString = `${name}=${encodeURIComponent(value)}`;

// if duration string is provided, convert it
if (options.duration) {
const { expires, maxAge } = this.parseDurationToExpireOptions(options.duration);
options.expires = expires;
options.maxAge = maxAge;
}

if (options.maxAge != null) cookieString += `; Max-Age=${options.maxAge}`;
if (options.expires) cookieString += `; Expires=${options.expires.toUTCString()}`;
if (options.path) cookieString += `; Path=${options.path}`;
if (options.domain) cookieString += `; Domain=${options.domain}`;
if (options.secure) cookieString += `; Secure`;
if (options.httpOnly) cookieString += `; HttpOnly`;
if (options.sameSite) cookieString += `; SameSite=${options.sameSite}`;

const existingCookies = res.getHeader('Set-Cookie');
const cookiesArray = Array.isArray(existingCookies)
? existingCookies
: typeof existingCookies === 'string'
? [existingCookies]
: [];

res.setHeader('Set-Cookie', [
...cookiesArray,
cookieString
]);
}

/**
* Clear a cookie
*
* You have to pass all options which you set while creating cookie to delete that cookie.
*/
static clear(res: Response, name: string, options: CookieOptions = {}): void {
const clearOptions = {
...options,
expires: new Date(0),
maxAge: 0,
duration: "0s"
};

this.set(res, name, '', clearOptions);
}
}

export default AxonCookie;
2 changes: 1 addition & 1 deletion src/core/cors/AxonCors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assign from "object-assign";
// import { nextFn, Request, Response } from "../..";
import { NextFunc, Request, Response } from "@/types/RouterTypes";
import { NextFunc, Request, Response } from "../../types/RouterTypes";
import vary from "vary";

const defaults = {
Expand Down
4 changes: 2 additions & 2 deletions src/core/exceptions/CoreExceptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import RouterException from "@/Router/exceptions/RouterException"
import { HttpMethods } from "@/types/RouterTypes"
import RouterException from "../../Router/exceptions/RouterException"
import { HttpMethods } from "../../types/RouterTypes"

/**
* throw new route duplicate error from core to client
Expand Down
6 changes: 3 additions & 3 deletions src/core/plugin/PluginLoader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AxonPlugin } from "@/types/PluginTypes";
import AxonCore from "@/core/AxonCore";
import { logger } from "@/core/utils/coreLogger";
import { AxonPlugin } from "../../types/PluginTypes";
import AxonCore from "../AxonCore";
import { logger } from "../utils/coreLogger";

export class PluginLoader {
private plugins: AxonPlugin[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/core/response/AxonResponse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Response } from "@/.";
import { Response } from "../..";

class AxonResponse {
private res: Response;
Expand Down
6 changes: 3 additions & 3 deletions src/core/services/unloadRoutesService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import AxonRouter from "@/Router/AxonRouter";
import { HttpMethods } from "@/types/RouterTypes";
import { logger } from "@/core/utils/coreLogger";
import AxonRouter from "../../Router/AxonRouter";
import { HttpMethods } from "../../types/RouterTypes";
import { logger } from "../utils/coreLogger";

interface UnloadRouteParams {
router?: AxonRouter;
Expand Down
2 changes: 1 addition & 1 deletion src/core/utils/getRequestBody.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Request } from "@/types/RouterTypes";
import { Request } from "../../types/RouterTypes";

const getRequestBody = async (req: Request<any>): Promise<string | Record<string, string | undefined> | undefined> => {
return new Promise((resolve, reject) => {
Expand Down
2 changes: 1 addition & 1 deletion src/core/validation/AxonValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as yup from "yup";
import joi from "joi";
import { ZodSchema, z } from "zod";

import { Middleware } from "@/types/RouterTypes";
import { Middleware } from "../../types/RouterTypes";

import type {
ValidationConfig,
Expand Down
Loading