2 Commits
v0.1.2 ... main

Author SHA1 Message Date
7051a95fb9 release: 0.1.2-1 2024-08-02 04:31:08 +08:00
89c5ed561a release: 0.0.3-1 2024-08-01 13:26:50 +08:00
21 changed files with 1263 additions and 452 deletions

View File

@ -6,7 +6,7 @@ const App = new rootRouter<any, any>();
App.binding( App.binding(
'/(.*)', '/(.*)',
new handler('ANY', [ new handler('ANY', [
async (request, response) => { async (request, _response) => {
console.log(request); console.log(request);
return undefined; return undefined;
}, },

30
eslint.config.js Normal file
View File

@ -0,0 +1,30 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
{ files: ['src/**/*.{js,mjs,cjs,ts}', 'test/**/*.{js,mjs,cjs,ts}'] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
},
},
];

View File

@ -7,6 +7,7 @@ export { methodENUM as method } from './src/interface/method';
export { response } from './src/interface/response'; export { response } from './src/interface/response';
export { request } from './src/interface/request'; export { request } from './src/interface/request';
export * as platformAdapater from './src/platform/export'; export * as platformAdapater from './src/platform/export';
export * as interfaces from './src/interface/index';
export { rootRouter }; export { rootRouter };
export default rootRouter; export default rootRouter;

View File

@ -1,6 +0,0 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
export default {
preset: 'ts-jest',
testEnvironment: 'node',
forceExit: true
};

22
jest.config.ts Normal file
View File

@ -0,0 +1,22 @@
// jest.config.ts
import type { JestConfigWithTsJest } from 'ts-jest';
const jestConfig: JestConfigWithTsJest = {
// [...]
preset: 'ts-jest/presets/default-esm', // or other ESM presets
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
// '^.+\\.[tj]sx?$' to process ts,js,tsx,jsx with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process ts,js,tsx,jsx,mts,mjs,mtsx,mjsx with `ts-jest`
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};
export default jestConfig;

View File

@ -1,12 +1,17 @@
{ {
"name": "handlers.js", "name": "handlers.js",
"description": "Handlers.js is a unified and lightweight web application framework for multiple platforms.", "description": "Handlers.js is a unified and lightweight web application framework for multiple platforms.",
"version": "0.1.2", "version": "0.1.2-1",
"main": "./dist/index.js", "main": "./dist/index.js",
"webpack": "./dist/index.js", "types": "./dist/index.d.ts",
"browser": "./dist/main.serviceworker.js", "exports": {
"module": "./dist/index.js", ".": {
"types": "dist/index.d.ts", "import": "./dist/index.js",
"require": "./dist/index.node.js",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [ "files": [
"dist", "dist",
"LICENSE" "LICENSE"
@ -24,23 +29,27 @@
"unified" "unified"
], ],
"dependencies": { "dependencies": {
"path-to-regexp": "^6.2.1" "path-to-regexp": "6"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^3.13.0", "@cloudflare/workers-types": "^3.13.0",
"@eslint/js": "^9.8.0",
"@types/jest": "^28.1.4", "@types/jest": "^28.1.4",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"@webpack-cli/generators": "^2.5.0", "@webpack-cli/generators": "^2.5.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"bun-types": "^0.1.4", "bun-types": "^0.1.4",
"jest": "^28.1.2", "eslint": "9.x",
"globals": "^15.8.0",
"jest": "^29.7.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"ts-jest": "^28.0.5", "ts-jest": "^29.2.4",
"ts-loader": "^9.3.1", "ts-loader": "^9.3.1",
"ts-node": "^10.8.1", "ts-node": "^10.8.1",
"tsc-alias": "^1.8.10", "tsc-alias": "^1.8.10",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"typescript-eslint": "^8.0.0",
"webpack": "^5.73.0", "webpack": "^5.73.0",
"webpack-cli": "^4.10.0" "webpack-cli": "^4.10.0"
}, },
@ -57,9 +66,10 @@
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",
"demo": "env NODE_ENV=development yarn build:node && node ./dist/main.node.js", "demo": "env NODE_ENV=development yarn build:node && node ./dist/main.node.js",
"tsc": "tsc && tsc-alias", "tsc": "tsc && tsc-alias",
"test:node": "jest ./test/node.test.ts", "lint": "eslint --fix **/*.ts",
"test:node": "NODE_OPTIONS=--experimental-vm-modules jest ./test/node.test.ts",
"test:deno": "BUILD_TARGET=deno:test webpack && jest ./test/deno.test.ts", "test:deno": "BUILD_TARGET=deno:test webpack && jest ./test/deno.test.ts",
"coverage": "jest --collectCoverage --", "coverage": "ODE_OPTIONS=--experimental-vm-modules jest --collectCoverage --",
"prepublish": "env NODE_ENV=production yarn build && yarn tsc", "prepublish": "env NODE_ENV=production yarn build && yarn tsc",
"format": "prettier --write \"**/*.{ts,json,md}\" " "format": "prettier --write \"**/*.{ts,json,md}\" "
}, },

View File

@ -29,8 +29,8 @@ export class handler<RequestCustomType, ResponseCustomType> {
case 1: case 1:
return this.responders[0](request, responseMessage); return this.responders[0](request, responseMessage);
default: default:
for (let responder of this.responders) { for (const responder of this.responders) {
let thisResponse = await responder( const thisResponse = await responder(
request, request,
responseMessage, responseMessage,
); );

View File

@ -1,5 +1,5 @@
import headers from './headers'; import headers from './headers';
import packageJSON from '../../package.json'; import packageJSON from '../../package.json' assert { type: 'json' };
import { platform, version } from '../lib'; import { platform, version } from '../lib';
export class defaultHeaders extends headers { export class defaultHeaders extends headers {

View File

@ -20,7 +20,9 @@ export const platform = (() => {
})(); })();
export const version = (() => { export const version = (() => {
switch (platform) { switch (platform) {
case 'Node.js' || 'Bun': case 'Node.js':
return process.version;
case 'Bun':
return process.version; return process.version;
case 'Deno': case 'Deno':
return Deno.version.deno; return Deno.version.deno;

View File

@ -20,8 +20,8 @@ const DefaultConn: Deno.Conn = {
closeWrite: async () => undefined, closeWrite: async () => undefined,
readable: '', readable: '',
writable: '', writable: '',
read: async (p: Uint8Array) => null, read: async (_p: Uint8Array) => null,
write: async (p: Uint8Array) => 0, write: async (_p: Uint8Array) => 0,
close: () => undefined, close: () => undefined,
}; };
@ -29,10 +29,12 @@ export class DenoPlatformAdapter<T = any, K = any>
extends SWPlatformAdapter<T, K> extends SWPlatformAdapter<T, K>
implements platformAdapater<T, K> implements platformAdapater<T, K>
{ {
async listen(port: number): Promise<void> { public server: Deno.Listener;
const Server: Deno.Listener = Deno.listen({ port });
for await (const connection of Server) { async listen(port: number): Promise<void> {
this.server = Deno.listen({ port });
for await (const connection of this.server) {
const httpConnection = Deno.serveHttp(connection); const httpConnection = Deno.serveHttp(connection);
for await (const requestEvent of httpConnection) { for await (const requestEvent of httpConnection) {
@ -43,6 +45,10 @@ export class DenoPlatformAdapter<T = any, K = any>
} }
} }
close() {
this.server.close();
}
async handleRequest( async handleRequest(
nativeRequest: Request, nativeRequest: Request,
connection: Deno.Conn = DefaultConn, connection: Deno.Conn = DefaultConn,
@ -56,8 +62,9 @@ export class DenoPlatformAdapter<T = any, K = any>
requestHeaders, requestHeaders,
await nativeRequest.text(), await nativeRequest.text(),
{}, {},
`${connection.remoteAddr.hostname}:${connection.remoteAddr.port}` || `${connection.remoteAddr.hostname ?? '0.0.0.0'}:${
'', connection.remoteAddr.port ?? '0'
}`,
); );
return requestMessage; return requestMessage;
} }

View File

@ -4,6 +4,7 @@ import { router } from '../router';
export interface platformAdapater<T = any, K = any> { export interface platformAdapater<T = any, K = any> {
router: router<T, K>; router: router<T, K>;
listen(port: number): void; listen(port: number): void;
close(): void;
handleRequest(nativeRequest: any): Promise<request<T>>; handleRequest(nativeRequest: any): Promise<request<T>>;
handleResponse( handleResponse(
response: response<K> | Promise<response<K>>, response: response<K> | Promise<response<K>>,

View File

@ -8,14 +8,16 @@ import { methodENUM } from 'src/interface/method';
export class NodePlatformAdapter<T = any, K = any> implements platformAdapater { export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
public router: router<T, K>; public router: router<T, K>;
public server: http.Server;
constructor(router: router<T, K>) { constructor(router: router<T, K>, server?: http.Server) {
this.router = router; this.router = router;
if (server) this.server = server;
else this.server = http.createServer();
} }
async listen(port: number): Promise<void> { async listen(port: number): Promise<void> {
const server = http.createServer(); this.server.on(
server.on(
'request', 'request',
async (req: http.IncomingMessage, res: http.ServerResponse) => { async (req: http.IncomingMessage, res: http.ServerResponse) => {
const request = await this.handleRequest(req); const request = await this.handleRequest(req);
@ -23,8 +25,11 @@ export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
this.handleResponse(response, res); this.handleResponse(response, res);
}, },
); );
server.listen(port); this.server.listen(port);
return; }
close() {
this.server.close();
} }
async handleRequest( async handleRequest(

View File

@ -8,15 +8,20 @@ import { methodENUM } from 'src/interface/method';
export class SWPlatformAdapter<T = any, K = any> implements platformAdapater { export class SWPlatformAdapter<T = any, K = any> implements platformAdapater {
public router: router<T, K>; public router: router<T, K>;
private eventHandler = (event: FetchEvent) => {
event.respondWith(this.handler(event));
};
constructor(router: router<T, K>) { constructor(router: router<T, K>) {
this.router = router; this.router = router;
} }
async listen(_port?: number): Promise<void> { async listen(_port?: number): Promise<void> {
self.addEventListener('fetch', (event: FetchEvent) => { self.addEventListener('fetch', this.eventHandler);
event.respondWith(this.handler(event)); }
});
close() {
self.removeEventListener('fetch', this.eventHandler);
} }
async handleRequest(nativeRequest: Request): Promise<request<T>> { async handleRequest(nativeRequest: Request): Promise<request<T>> {

View File

@ -3,7 +3,7 @@ import { response } from '../../interface';
import { headers } from '../../interface/headers'; import { headers } from '../../interface/headers';
import { methodENUM } from '../../interface/method'; import { methodENUM } from '../../interface/method';
import statusCode from './statusCode.json'; import statusCode from './statusCode.json' assert { type: 'json' };
export class HttpConn { export class HttpConn {
private closed: boolean = false; private closed: boolean = false;
@ -35,8 +35,10 @@ export class HttpConn {
const url = new URL( const url = new URL(
path, path,
`http://${requestHeaders.get('Host')}/` ?? `http://${
`http://${this.conn.localAddress.ip}:${this.conn.localAddress.port}/`, requestHeaders.get('Host') ??
`${this.conn.localAddress.ip}:${this.conn.localAddress.port}`
}/`,
); );
const body = lines.slice(dividingIndex + 1).join('\n'); const body = lines.slice(dividingIndex + 1).join('\n');
@ -56,9 +58,9 @@ export class HttpConn {
let responseMessage: string = ''; let responseMessage: string = '';
responseMessage += responseMessage +=
'HTTP/1.1 ' + 'HTTP/1.1 ' +
response.status + response.status +
' ' + ' ' +
statusCode[<'100'>response.status.toString()] ?? ''; (statusCode[<'100'>response.status.toString()] ?? '');
response.headers.forEach((key, value) => { response.headers.forEach((key, value) => {
responseMessage += '\n' + key + ': ' + value; responseMessage += '\n' + key + ': ' + value;
@ -95,6 +97,7 @@ export class HttpConn {
} }
[Symbol.asyncIterator]() { [Symbol.asyncIterator]() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const httpConn = this; const httpConn = this;
return { return {

View File

@ -8,15 +8,16 @@ export class TxikiPlatformAdapter<T = any, K = any>
implements platformAdapater implements platformAdapater
{ {
public router: router<T, K>; public router: router<T, K>;
public server: tjs.Listener;
constructor(router: router<T, K>) { constructor(router: router<T, K>) {
this.router = router; this.router = router;
} }
async listen(port?: number): Promise<void> { async listen(port?: number): Promise<void> {
const Server = await tjs.listen('tcp', '0.0.0.0', port); this.server = await tjs.listen('tcp', '0.0.0.0', port);
for await (const conn of Server) { for await (const conn of this.server) {
const httpConn = serveHttp(conn); const httpConn = serveHttp(conn);
for await (const conn of httpConn) { for await (const conn of httpConn) {
@ -31,6 +32,10 @@ export class TxikiPlatformAdapter<T = any, K = any>
} }
} }
close() {
this.server.close();
}
async handleRequest(nativeRequest: request<any>): Promise<request<T>> { async handleRequest(nativeRequest: request<any>): Promise<request<T>> {
return nativeRequest; return nativeRequest;
} }

View File

@ -42,7 +42,7 @@ export class route {
attributes: [], attributes: [],
}; };
let attributes: matchedStatus['attributes'] = []; const attributes: matchedStatus['attributes'] = [];
it.keys.forEach((key, index) => { it.keys.forEach((key, index) => {
attributes.push({ attributes.push({

View File

@ -98,7 +98,7 @@ export class router<K = any, V = any> {
let mismatchCount = 0; let mismatchCount = 0;
for (let route of this.routes) { for (const route of this.routes) {
const isMatched = await route.exec(request.url.pathname); const isMatched = await route.exec(request.url.pathname);
if (!isMatched.matched) { if (!isMatched.matched) {
@ -112,7 +112,7 @@ export class router<K = any, V = any> {
try { try {
let thisResponse: response<V> | void = responseMessage; let thisResponse: response<V> | void = responseMessage;
for (let handler of route.handlers) { for (const handler of route.handlers) {
if ( if (
handler.method != request.method && handler.method != request.method &&
handler.method != methodENUM.ANY handler.method != methodENUM.ANY
@ -215,10 +215,12 @@ export class rootRouter<K = any, V = any> extends router<K, V> {
return responseMessage; return responseMessage;
}; };
useAdapater(adapater: platformAdapaterConstructor): this { useAdapater(adapater: platformAdapaterConstructor): this {
this.adapater = createPlatformAdapater(adapater, this); this.adapater = createPlatformAdapater(adapater, this);
return this; return this;
} }
useMappingAdapter( useMappingAdapter(
mapping: { mapping: {
[platform: string]: platformAdapaterConstructor; [platform: string]: platformAdapaterConstructor;
@ -231,8 +233,14 @@ export class rootRouter<K = any, V = any> extends router<K, V> {
else this.useAdapater(mapping[platform]); else this.useAdapater(mapping[platform]);
return this; return this;
} }
listen(port: number): void { listen(port: number): void {
if (this.adapater == null) throw new Error('No platform adapter set'); if (this.adapater == null) throw new Error('No platform adapter set');
this.adapater.listen(port); this.adapater.listen(port);
} }
close(): void {
if (this.adapater == null) throw new Error('No platform adapter set');
this.adapater.close();
}
} }

View File

@ -81,3 +81,8 @@ describe('Test server', () => {
expect(secondData).toEqual('hit'); expect(secondData).toEqual('hit');
}); });
}); });
afterAll((done) => {
_.close();
done();
});

View File

@ -45,7 +45,7 @@ App.route('/info/(.*)')
App.create( App.create(
'GET', 'GET',
(): Promise<handlersJS.response<any>> => (): Promise<handlersJS.response<any>> =>
new Promise((resolve) => { new Promise(() => {
throw new handlersJS.response('hit'); throw new handlersJS.response('hit');
}), }),
), ),

View File

@ -1,10 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ESNext",
"module": "esnext", "module": "ESNext",
"lib": [ "lib": ["ESNext"],
"ESNext"
],
"types": [ "types": [
"@cloudflare/workers-types", "@cloudflare/workers-types",
"@types/node", "@types/node",
@ -37,15 +35,8 @@
], ],
"declaration": true "declaration": true
}, },
"exclude": [ "exclude": ["node_modules"],
"node_modules" "include": ["index.ts", "src/**/*.ts", "demo/**/*.ts", "types/*.d.ts"],
],
"include": [
"index.ts",
"src/**/*.ts",
"demo/**/*.ts",
"types/*.d.ts"
],
"tsc-alias": { "tsc-alias": {
"resolveFullPaths": true, "resolveFullPaths": true,
"verbose": true "verbose": true

1500
yarn.lock

File diff suppressed because it is too large Load Diff