feat(project) cleanup, removed dependencies, updated structure
This commit is contained in:
182
.gitignore
vendored
182
.gitignore
vendored
@@ -1,169 +1,27 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
# bun
|
||||
bun.lockb
|
||||
|
||||
web_modules/
|
||||
# environments
|
||||
*.env
|
||||
*.env.*
|
||||
!.env.example
|
||||
|
||||
# TypeScript cache
|
||||
# logs
|
||||
*.log
|
||||
|
||||
\*.tsbuildinfo
|
||||
# system files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Optional npm cache directory
|
||||
# builds
|
||||
dist/
|
||||
build/
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
# ides
|
||||
.idea/
|
||||
.vscode/
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
24
.npmignore
Normal file
24
.npmignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# docs
|
||||
docs/
|
||||
|
||||
# web
|
||||
website/
|
||||
|
||||
# examples
|
||||
examples/
|
||||
|
||||
# test
|
||||
tests/
|
||||
|
||||
# dev
|
||||
node_modules/
|
||||
*.log
|
||||
*.env
|
||||
*.bun
|
||||
|
||||
# system
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# bun
|
||||
bun.lockb
|
||||
9
LICENSE
Normal file
9
LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Benjamin Wegener <contact@benni.fyi>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
78
examples/index.js
Normal file
78
examples/index.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { LayServer } from '../src';
|
||||
|
||||
class InfoController {
|
||||
static namespace = 'info';
|
||||
constructor(server) {
|
||||
server.get('/', this.index);
|
||||
server.get('/:country', this.countryInfo);
|
||||
server.get('/multiple/:a/:a/:a', this.multiple);
|
||||
}
|
||||
|
||||
index(req, res) {
|
||||
res.text('I think you are right here');
|
||||
}
|
||||
|
||||
countryInfo(req, res) {
|
||||
res.json({
|
||||
name: req.params.get('country'),
|
||||
description: `the country is ${req.params.get('country')}`,
|
||||
params: [...req.params.entries()],
|
||||
});
|
||||
}
|
||||
|
||||
multiple(req, res) {
|
||||
res.json({
|
||||
params: [...req.params],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const server = new LayServer({
|
||||
controllers: [
|
||||
function Main(server) {
|
||||
server.get('/', async (req, res) => {
|
||||
return {
|
||||
message: 'Hi mom, i am on the internet!',
|
||||
batman: req.query.get('batman'),
|
||||
body: req.query,
|
||||
};
|
||||
});
|
||||
server.all('/data', (req, res) => {
|
||||
let respBody = `your ${req.method} request was well recieved`;
|
||||
switch (req.method) {
|
||||
case 'POST':
|
||||
case 'PUT':
|
||||
return {
|
||||
message: respBody,
|
||||
data: {
|
||||
body: req.body,
|
||||
},
|
||||
};
|
||||
case 'PATCH':
|
||||
respBody = `we can try but i think there is no way to ${req.method} this out.`;
|
||||
break;
|
||||
case 'DELETE':
|
||||
respBody = 'What do you want to delete?';
|
||||
break;
|
||||
case 'OPTIONS':
|
||||
respBody = `I guess, we are out of ${req.method}. 💩`;
|
||||
break;
|
||||
case 'HEAD':
|
||||
respBody = `this response has not really a body but a big ${req.method}`;
|
||||
break;
|
||||
}
|
||||
return new Response(respBody, {
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
server.get('/contact', (req, res) => {
|
||||
return new Response('This is how you remind me!', {
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
},
|
||||
InfoController,
|
||||
],
|
||||
});
|
||||
|
||||
server.listen(42069);
|
||||
@@ -1 +0,0 @@
|
||||
console.log("Hello via Bun!");
|
||||
18
package.json
18
package.json
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "layc",
|
||||
"module": "lib/index.js",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
"name": "layc",
|
||||
"version": "1.0.0",
|
||||
"main": "lib/layc.cjs",
|
||||
"module": "lib/layc.js",
|
||||
"scripts": {
|
||||
"test": "bun test",
|
||||
"examples": "bun run ./examples/index.js"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
21
src/IO/LayRequest.js
Normal file
21
src/IO/LayRequest.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import RequestParser from '../Utils/RequestParser';
|
||||
|
||||
export default class LayRequest extends Request {
|
||||
body = null;
|
||||
constructor(req) {
|
||||
super(req);
|
||||
|
||||
this.params = new Map();
|
||||
this.query = new Map();
|
||||
|
||||
this.parseBody(req);
|
||||
}
|
||||
|
||||
header(key) {
|
||||
this.headers?.get(key);
|
||||
}
|
||||
|
||||
async parseBody(req) {
|
||||
this.body = await RequestParser(req);
|
||||
}
|
||||
}
|
||||
102
src/IO/LayResponse.js
Normal file
102
src/IO/LayResponse.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import MimeTypeMap from './MimeTypeMap';
|
||||
|
||||
export default class LayResponse {
|
||||
body;
|
||||
statusCode;
|
||||
headerList = new Map();
|
||||
mimeType;
|
||||
options = {
|
||||
disableFileCheck: false,
|
||||
};
|
||||
|
||||
constructor(router) {
|
||||
this.router = router;
|
||||
this.body = 'Not Implemented';
|
||||
this.statusCode = 502;
|
||||
this.headerList = new Map();
|
||||
this.mimeType = 'text/plain';
|
||||
}
|
||||
|
||||
async send(str) {
|
||||
this.type('text');
|
||||
this.body = str;
|
||||
|
||||
if (this.options.disableFileCheck) return;
|
||||
try {
|
||||
const temp = Bun.file(str);
|
||||
|
||||
if (await temp.exists()) {
|
||||
this.file = str;
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
json(data, options = {}) {
|
||||
let { replacer, space } = options;
|
||||
if (!Array.isArray(replacer) && typeof replacer != 'function') {
|
||||
replacer = null;
|
||||
}
|
||||
if (isNaN(space) || Math.floor(space) < 0) {
|
||||
space = 0;
|
||||
}
|
||||
this.type('json');
|
||||
this.body = JSON.stringify(data, replacer, space);
|
||||
}
|
||||
|
||||
type(key) {
|
||||
if (!MimeTypeMap.has(key)) {
|
||||
this.mimeType = MimeTypeMap.get('text');
|
||||
return;
|
||||
}
|
||||
this.mimeType = MimeTypeMap.get(key);
|
||||
}
|
||||
|
||||
status(value = null) {
|
||||
if (isNaN(value)) throw new Error('status has to be a number');
|
||||
this.statusCode = value || 502;
|
||||
}
|
||||
|
||||
text(value = null) {
|
||||
if (typeof value != 'string') {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
|
||||
this.type('text');
|
||||
this.body = value;
|
||||
}
|
||||
|
||||
json(value = null) {
|
||||
this.text(value);
|
||||
this.type('json');
|
||||
}
|
||||
|
||||
redirect(value = null, status = 302) {
|
||||
if (value === null) {
|
||||
return this.redirectUrl;
|
||||
}
|
||||
|
||||
this.redirectUrl = value;
|
||||
this.status(status);
|
||||
}
|
||||
|
||||
headers() {
|
||||
if (!this.headerList.get('Content-Type')) {
|
||||
this.headerList.set('Content-Type', `${this.mimeType}`);
|
||||
}
|
||||
return this.headerList;
|
||||
}
|
||||
|
||||
header(key, value) {
|
||||
this.headerList.set(key, value);
|
||||
}
|
||||
|
||||
build(options = {}) {
|
||||
if (this.redirectUrl) {
|
||||
return Response.redirect(this.redirectUrl, this.status());
|
||||
}
|
||||
return new Response(this.body, {
|
||||
status: this.status(),
|
||||
headers: this.headers(),
|
||||
});
|
||||
}
|
||||
}
|
||||
24
src/IO/MimeTypeMap.js
Normal file
24
src/IO/MimeTypeMap.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export default new Map([
|
||||
['text', 'text/plain'],
|
||||
['html', 'text/html'],
|
||||
['css', 'text/css'],
|
||||
['csv', 'text/csv'],
|
||||
['js', 'text/javascript'],
|
||||
['gif', 'image/gif'],
|
||||
['jpeg', 'image/jpeg'],
|
||||
['png', 'image/png'],
|
||||
['svg', 'image/svg+xml'],
|
||||
['midi', 'audio/midi'],
|
||||
['mp3', 'audio/mpeg'],
|
||||
['oga', 'audio/ogg'],
|
||||
['mp4', 'video/mp4'],
|
||||
['mpeg', 'video/mpeg'],
|
||||
['ogv', 'video/ogg'],
|
||||
['zip', 'application/zip'],
|
||||
['pdf', 'application/pdf'],
|
||||
['epub', 'application/epub+zip'],
|
||||
['gz', 'application/gzip'],
|
||||
['bin', 'application/octet-stream'],
|
||||
['json', 'application/json'],
|
||||
['jsonld', 'application/ld+json'],
|
||||
]);
|
||||
168
src/LayServer.js
Normal file
168
src/LayServer.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import Router from './Routing/Router';
|
||||
import LayRouting from './Routing/LayRouting';
|
||||
import HtmlEngine from './Views/HtmlEngine';
|
||||
|
||||
export default class LayServer {
|
||||
controllers = new Map();
|
||||
proc = null;
|
||||
router;
|
||||
publicFiles;
|
||||
options = new Map([
|
||||
['publicDir', undefined],
|
||||
['viewsPath', undefined],
|
||||
['viewsEngine', undefined],
|
||||
['onlySingleRoutes', true],
|
||||
['preferIndexFiles', false],
|
||||
['discoverPublicFiles', true],
|
||||
['enforceTrailingSlash', true],
|
||||
]);
|
||||
|
||||
constructor(options = {}) {
|
||||
if (typeof options == 'object') {
|
||||
const optionKeys = Object.getOwnPropertyNames(options);
|
||||
|
||||
for (const key of optionKeys) {
|
||||
if (!this.options.has(key)) continue;
|
||||
const valueType = typeof this.options.get(key);
|
||||
if (typeof options[key] == valueType)
|
||||
this.options.set(key, options[key]);
|
||||
}
|
||||
}
|
||||
|
||||
this.initRouting(options.router);
|
||||
|
||||
if (options.hasOwnProperty('controllers')) {
|
||||
this.registerControllers(options.controllers);
|
||||
}
|
||||
|
||||
if (options.publicFiles) {
|
||||
this.addPublicFileRoutes(options.publicFiles);
|
||||
}
|
||||
|
||||
if (
|
||||
!options.publicFiles &&
|
||||
this.options.discoverPublicFiles &&
|
||||
this.options.publicDir
|
||||
)
|
||||
this.indexPublicFiles(this.options.publicDir);
|
||||
|
||||
if (!this.options.viewsEngine && this.options.viewsPath) {
|
||||
this.options.viewsEngine = new HtmlEngine();
|
||||
}
|
||||
}
|
||||
|
||||
option(key, value = undefined) {
|
||||
if (!this.options.has(key)) return undefined;
|
||||
if (value !== undefined) this.option.set(key, value);
|
||||
return this.options.get(key);
|
||||
}
|
||||
|
||||
addPublicFileRoutes(filePaths) {
|
||||
if (!filePaths) return;
|
||||
if (!Array.isArray(filePaths))
|
||||
throw new Error(
|
||||
'publicFiles option has to be an array or undefined'
|
||||
);
|
||||
for (let path of filePaths) {
|
||||
}
|
||||
}
|
||||
|
||||
indexPublicFiles(publicDir) {}
|
||||
|
||||
initRouting(router = null) {
|
||||
if (router) this.router = new router(this);
|
||||
if (typeof this.router == 'undefined') this.router = new Router(this);
|
||||
this.routeControl = new LayRouting(this.router);
|
||||
}
|
||||
|
||||
registerControllers(controllers) {
|
||||
if (!Array.isArray(controllers))
|
||||
throw new Error('controllers is not an Array');
|
||||
|
||||
for (let controller of controllers) {
|
||||
if (typeof controller != 'function') {
|
||||
console.log(`ignored ${controller} for not being a function`);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.controllers.set(
|
||||
controller.name,
|
||||
this.instantiateController(controller)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
instantiateController(controller) {
|
||||
this.routeControl.setNamespace(controller.namespace);
|
||||
const _temp = new controller(this.routeControl);
|
||||
if (controller.namespace) this.routeControl.setNamespace(null);
|
||||
return _temp;
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
proc != null;
|
||||
}
|
||||
|
||||
async handleRequest(req) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
if (
|
||||
!url.pathname.endsWith('/') &&
|
||||
Array.isArray(this.publicFiles) &&
|
||||
this.publicFiles.length > 0
|
||||
) {
|
||||
let path = this.publicFiles.find(
|
||||
(v) => url.pathname.substring(1) === v
|
||||
);
|
||||
if (path) {
|
||||
try {
|
||||
return new Response(
|
||||
Bun.file(resolvePath(import.meta.dir, 'public', path))
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return new Response('Internal Server Error', {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.enforceTrailingSlash && !url.pathname.endsWith('/')) {
|
||||
return Response.redirect(`${url.toString()}/`, 302);
|
||||
}
|
||||
|
||||
return await this.router.handle(req);
|
||||
}
|
||||
|
||||
listen(port = 3000) {
|
||||
try {
|
||||
this.proc = Bun.serve({
|
||||
port,
|
||||
development: Bun.env.BUN_ENV != 'production',
|
||||
fetch: this.handleRequest.bind(this),
|
||||
error(err) {
|
||||
console.log(err.stack);
|
||||
return new Response(`<pre>${err}\n${err.stack}</pre>`, {
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.proc) {
|
||||
throw new Error('something went wrong');
|
||||
}
|
||||
|
||||
console.log(
|
||||
this.proc,
|
||||
`listening on ${this.proc.hostname} with port ${this.proc.port}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.log('where', err.stack);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/Routing/BaseController.js
Normal file
3
src/Routing/BaseController.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default class BaseController {
|
||||
constructor() {}
|
||||
}
|
||||
84
src/Routing/LayRouting.js
Normal file
84
src/Routing/LayRouting.js
Normal file
@@ -0,0 +1,84 @@
|
||||
export default class LayRouting {
|
||||
router;
|
||||
namespace;
|
||||
constructor(router, namespace = null) {
|
||||
this.router = router;
|
||||
|
||||
if (namespace !== null) this.namespace = namespace;
|
||||
}
|
||||
|
||||
setNamespace(ns) {
|
||||
if (!ns || typeof ns != 'string') this.namespace = null;
|
||||
this.namespace = ns;
|
||||
}
|
||||
|
||||
routes() {
|
||||
return this.router.routes;
|
||||
}
|
||||
|
||||
add(path, cb, method) {
|
||||
if (this.namespace) {
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
path = this.namespace + path;
|
||||
}
|
||||
|
||||
this.router.add(path, cb, method);
|
||||
}
|
||||
|
||||
route(config = {}) {
|
||||
if (typeof config != 'object')
|
||||
throw new Error('route config is not an object');
|
||||
|
||||
const { path, handle, method = 'get' } = config;
|
||||
if (typeof path != 'string')
|
||||
throw new Error('route path is not a string');
|
||||
if (typeof handle != 'function')
|
||||
throw new Error('route handle is not a function');
|
||||
this.add(path, handle, method);
|
||||
return this;
|
||||
}
|
||||
|
||||
any(path, cb) {
|
||||
this.all(path, cb);
|
||||
}
|
||||
|
||||
all(path, cb) {
|
||||
this.add(path, cb, 'all');
|
||||
return this;
|
||||
}
|
||||
|
||||
get(path, cb) {
|
||||
this.add(path, cb, 'get');
|
||||
return this;
|
||||
}
|
||||
|
||||
post(path, cb) {
|
||||
this.add(path, cb, 'post');
|
||||
return this;
|
||||
}
|
||||
|
||||
put(path, cb) {
|
||||
this.add(path, cb, 'put');
|
||||
return this;
|
||||
}
|
||||
|
||||
patch(path, cb) {
|
||||
this.add(path, cb, 'patch');
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(path, cb) {
|
||||
this.add(path, cb, 'delete');
|
||||
return this;
|
||||
}
|
||||
|
||||
head(path, cb) {
|
||||
this.add(path, cb, 'head');
|
||||
return this;
|
||||
}
|
||||
|
||||
options(path, cb) {
|
||||
this.add(path, cb, 'options');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
251
src/Routing/Router.js
Normal file
251
src/Routing/Router.js
Normal file
@@ -0,0 +1,251 @@
|
||||
import LayRequest from '../IO/LayRequest';
|
||||
import LayResponse from '../IO/LayResponse';
|
||||
import RequestParser from '../Utils/RequestParser';
|
||||
|
||||
export default class Router {
|
||||
server = undefined;
|
||||
routes = new Map();
|
||||
static allMethodKey = '$all';
|
||||
static methods = ['all', 'get', 'post', 'put', 'patch', 'head', 'options'];
|
||||
headerList = new Map();
|
||||
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
async handle(req) {
|
||||
if (!(req instanceof Request)) {
|
||||
throw new Error('`req` is not a proper Request');
|
||||
}
|
||||
|
||||
let method = (req.method || '').toString().toLowerCase();
|
||||
const methodKey = `$${method.toLowerCase()}`;
|
||||
let path;
|
||||
let url = req.url;
|
||||
|
||||
if (!(url instanceof URL) && typeof url == 'string') {
|
||||
url = new URL(url);
|
||||
path = url.pathname;
|
||||
}
|
||||
if (path.startsWith('/')) path = path.substring(1);
|
||||
if (path.endsWith('/')) path = path.substring(0, path.length - 1);
|
||||
|
||||
let currentNode = this.routes;
|
||||
let paramsList = [];
|
||||
|
||||
if (path != '') {
|
||||
let segments = path.split('/');
|
||||
for (let index = 0; index < segments.length; index++) {
|
||||
const segment = segments[index];
|
||||
|
||||
if (currentNode.has(segment)) {
|
||||
currentNode = currentNode.get(segment);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentNode.has(':') || currentNode.has('?')) {
|
||||
paramsList.push(segment);
|
||||
currentNode = currentNode.get(':') ?? currentNode.get('?');
|
||||
continue;
|
||||
}
|
||||
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!(currentNode instanceof Map) ||
|
||||
(['get', 'head'].indexOf(method) < 0 &&
|
||||
!currentNode.has(methodKey) &&
|
||||
!currentNode.has(Router.allMethodKey))
|
||||
) {
|
||||
return new Response(`[${method}] Method Not Allowed`, {
|
||||
status: 405,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!currentNode.has(methodKey) &&
|
||||
!currentNode.get(Router.allMethodKey)
|
||||
) {
|
||||
return new Response(`Not Implemented: [${method}, ${req.url}]`, {
|
||||
status: 501,
|
||||
});
|
||||
}
|
||||
|
||||
const reqContext = {
|
||||
request: null,
|
||||
response: null,
|
||||
};
|
||||
try {
|
||||
reqContext.request = new LayRequest(req);
|
||||
reqContext.response = new LayResponse(this);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
let handle =
|
||||
currentNode.get(methodKey) || currentNode.get(Router.allMethodKey);
|
||||
|
||||
if (typeof handle == 'object' && typeof handle.callback == 'function') {
|
||||
if (Array.isArray(handle.params)) {
|
||||
for (const name of handle.params) {
|
||||
let value = paramsList[reqContext.request.params.size];
|
||||
if (reqContext.request.params.has(name)) {
|
||||
let yet = reqContext.request.params.get(name);
|
||||
if (!Array.isArray(yet)) {
|
||||
yet = [yet];
|
||||
}
|
||||
yet.push(value);
|
||||
value = yet;
|
||||
}
|
||||
reqContext.request.params.set(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const retVal = await handle.callback(
|
||||
reqContext.request,
|
||||
reqContext.response
|
||||
);
|
||||
if (typeof retVal != 'undefined') {
|
||||
if (retVal instanceof Response) {
|
||||
return retVal;
|
||||
} else if (typeof retVal == 'string') {
|
||||
reqContext.response.send(retVal);
|
||||
} else {
|
||||
reqContext.response.json(retVal);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
|
||||
if (reqContext.response.redirect()) {
|
||||
return Response.redirect(
|
||||
reqContext.response.redirect,
|
||||
reqContext.response.status || 302
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(reqContext.response.body, {
|
||||
status: reqContext.response.status() || 200,
|
||||
headers: reqContext.response.headers() || {},
|
||||
});
|
||||
}
|
||||
|
||||
add(route, callback, method = 'get', middleware = null) {
|
||||
if (typeof callback != 'function')
|
||||
throw new Error('`callback` is not a function');
|
||||
if (typeof route != 'string')
|
||||
throw new Error('`route` is not a string');
|
||||
if (Router.methods.indexOf(method) < 0)
|
||||
throw new Error(
|
||||
`invalid \`method\` (${method}) given`,
|
||||
Router.methods
|
||||
);
|
||||
if (!Array.isArray(middleware)) {
|
||||
middleware = typeof middleware == 'function' ? [middleware] : null;
|
||||
}
|
||||
|
||||
const methodKey = `$${method.toLowerCase()}`;
|
||||
if (route.startsWith('/')) route = route.substring(1);
|
||||
if (route.endsWith('/')) route = route.substring(0, route.length - 1);
|
||||
|
||||
let params = [];
|
||||
let segments = route.split('/');
|
||||
let currentNode = this.routes;
|
||||
|
||||
if (route == '') {
|
||||
if (currentNode.get(methodKey)) {
|
||||
if (!this.server.option('allowOverwrite')) {
|
||||
console.log(
|
||||
`overwriting of routes is prohibited: [${method}] \`${route}\``
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`overwriting route for \`${route}\``);
|
||||
}
|
||||
currentNode.set(methodKey, { callback, middleware, params: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
// add method to the segments to store the callback with
|
||||
// and to allow different methods for the same route
|
||||
for (let index = 0; index < segments.length; index++) {
|
||||
let segment = segments[index];
|
||||
let varName = null;
|
||||
|
||||
if (segment.startsWith(':')) {
|
||||
let newSegment = ':';
|
||||
varName = segment.substring(1);
|
||||
if (segment.endsWith('?')) {
|
||||
// optional param
|
||||
newSegment = '?';
|
||||
varName = varName.substring(0, -1);
|
||||
}
|
||||
segment = newSegment;
|
||||
params.push(varName);
|
||||
}
|
||||
|
||||
if (!currentNode.has(segment)) currentNode.set(segment, new Map());
|
||||
|
||||
currentNode = currentNode.get(segment);
|
||||
}
|
||||
|
||||
if (currentNode.has(methodKey)) {
|
||||
if (!this.server.option('allowOverwrite')) {
|
||||
console.log(
|
||||
`overwriting of routes is prohibited: [${method}] \`${route}\``
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`overwriting route for \`${route}\``);
|
||||
}
|
||||
|
||||
currentNode.set(methodKey, { callback, middleware, params });
|
||||
}
|
||||
|
||||
remove(route, method, cb = null) {
|
||||
const methodKey = `$${method.toLowerCase()}`;
|
||||
if (route.startsWith('/')) route = route.substring(1);
|
||||
if (route.endsWith('/')) route = route.substring(0, route.length - 1);
|
||||
|
||||
let currentNode = this.routes;
|
||||
|
||||
if (route != '') {
|
||||
let segments = route.toString().split('/');
|
||||
|
||||
for (let index = 0; index < segments.length; index++) {
|
||||
const segment = segments[index];
|
||||
|
||||
if (currentNode.has(segment)) {
|
||||
currentNode = currentNode.get(segment);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentNode.has(':') || currentNode.has('?')) {
|
||||
currentNode = currentNode.get(':') ?? currentNode.get('?');
|
||||
continue;
|
||||
}
|
||||
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!(currentNode instanceof Map) ||
|
||||
!currentNode.has(methodKey) ||
|
||||
(typeof cb == 'function' && currentNode.get(methodKey) != cb)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentNode.delete(methodKey);
|
||||
}
|
||||
}
|
||||
5
src/Utils/Logger.js
Normal file
5
src/Utils/Logger.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export class Logger {
|
||||
static info() {
|
||||
const [message, data] = arguments;
|
||||
}
|
||||
}
|
||||
46
src/Utils/RequestParser.js
Normal file
46
src/Utils/RequestParser.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export async function parseFormData(req, multipart = false) {
|
||||
const data = await req.formData();
|
||||
|
||||
// console.log(tempFile, new Response(tempFile).headers.get('Content-Type'));
|
||||
// console.log(upload.type, upload.type == tempFile.type);
|
||||
|
||||
for (const key of data.keys()) {
|
||||
const value = data.get(key);
|
||||
if (value.constructor.name == 'Blob' && multipart) {
|
||||
// const ext = value.name.toString().split('.').pop().toLowerCase();
|
||||
const tempFilePath = `/tmp/${Date.now().toString(36)}`;
|
||||
await Bun.write(tempFilePath, value);
|
||||
let tempFile = Bun.file(tempFilePath, { type: value.type });
|
||||
console.log(
|
||||
tempFile,
|
||||
new Response(tempFile).headers.get('Content-Type')
|
||||
);
|
||||
console.log(value.type, value.type == tempFile.type);
|
||||
// data.set(key, await Bun.write())
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default async function RequestParser(req) {
|
||||
try {
|
||||
const contentType = req.headers.get('Content-Type')?.toLowerCase();
|
||||
|
||||
let val = await req.text();
|
||||
console.log(val);
|
||||
return {};
|
||||
|
||||
if (!contentType) return await req.text();
|
||||
|
||||
if (contentType.startsWith('multipart/form-data'))
|
||||
return await parseFormData(req, true);
|
||||
if (contentType == 'application/x-www-form-urlencoded')
|
||||
return await parseFormData(req);
|
||||
if (contentType == 'application/json') return await req.json();
|
||||
|
||||
return await req.text();
|
||||
} catch (err) {
|
||||
console.log(err, typeof req, req.constructor.name);
|
||||
}
|
||||
}
|
||||
7
src/Views/HtmlEngine.js
Normal file
7
src/Views/HtmlEngine.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default class HtmlEngine {
|
||||
constructor() {}
|
||||
|
||||
render(tpl, context = {}) {
|
||||
return tpl;
|
||||
}
|
||||
}
|
||||
1
src/index.js
Normal file
1
src/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as LayServer } from './LayServer';
|
||||
Reference in New Issue
Block a user