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
112 changes: 45 additions & 67 deletions src/class/CallLoop.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/* Functions */
import { postParent } from "../functions/utils/postMessage";
import { makeObj } from "../functions/utils/makeObj";

/* Types */
import {Middleware, Callback, Layer, NextContext, ErrorMiddleware } from "../types";
import { Middleware, Callback, Layer, ErrorMiddleware, ChildCmd, Serializable } from "../types";
import { Request, Response, NextFunction } from "express";

/* Constants */
Expand All @@ -9,105 +13,79 @@ export class CallLoop {
private req : Request;
private res : Response;
private callstack : Layer[];
private resolve?: (value: unknown) => void;
private reject?: (value: unknown) => void;

constructor(req : Request, res: Response, callstack: Layer[]) {
this.req = req;
this.res = res;
this.callstack = callstack;
}

public handle() {
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
this.exec();
});
private pNext(arg: Serializable) {
postParent({
cmd: ChildCmd.next,
id: (this.res as any)._id,
tid: (this.res as any)._tid,
arg
})
}

private handler = {
/* It's a callback */
2: async (i: number, _err?: any) => {
2: (i: number, _err?: any) => {
/* Call endpoint */
await (this.callstack[i] as Callback)(this.req, this.res);
/* Resolve promise */
return this.resolve!(undefined);
(this.callstack[i] as Callback)(this.req, this.res);
},
/* It's a middleware */
3: async (i: number, _err?: any) => {
/* Get next fn */
const ctx = this.getNext();
/* Launch promise race between next & middleware fn */
await Promise.race([(this.callstack[i] as Middleware)(this.req, this.res, ctx.done), ctx.promise]);
/* Handle next call */
this.handleNextCall(ctx, i + 1, () => this.exec(i + 1))
3: (i: number, _err?: any) => {
/* Call middleware */
(this.callstack[i] as Middleware)(this.req, this.res, this.getNext(i + 1))
},
/* It's an error middleware */
4: async (i: number, err?: any) => {
4: (i: number, err?: any) => {
/* Skip if no err */
if (!err)
return this.exec(i + 1);
/* Get next fn */
const ctx = this.getNext();
await Promise.race([(this.callstack[i] as ErrorMiddleware)(err, this.req, this.res, ctx.done), ctx.promise]);
/* Handle next call */
this.handleNextCall(ctx, i + 1, () => this.exec(i + 1))
return this.handle(i + 1);
/* Call error middleware */
(this.callstack[i] as ErrorMiddleware)(err, this.req, this.res, this.getNext(i + 1));
}
}

public exec(i = 0, err?: any) {
public handle(i = 0, err?: any) {
/* End of callstack or bad call */
if (i === this.callstack.length || this.callstack[i].length < 2 || this.callstack[i].length > 4)
return err ? this.reject!(err) : this.resolve!(undefined);
this.handler[this.callstack[i].length](i, err)
.catch((e: any) => this.goError(i + 1, e));
}

private getNext() : NextContext {
/* Create base obj */
const ret : Record<string, any> = {
nextCalled: false,
};
/* Add promise to context */
ret.promise = new Promise((solve) => {
/* Create passed next fn */
ret.done = (arg?: any) => {
if (ret.nextCalled) return;
ret.nextCalled = true;
solve(arg);
};
/* Register solve fn to avoid loosing promise */
ret.complete = solve;
});
return ret as unknown as NextContext;
return this.pNext(
err ? makeObj(err, Object.getOwnPropertyNames(err))
: undefined
)
try {
this.handler[this.callstack[i].length](i, err)
} catch (e) {
this.goError(i + 1, e);
}
}

private handleNextCall(ctx: NextContext, i : number, cb: () => void){
if (ctx.nextCalled) {
ctx.promise
.then((r) => {
if (r === route || r === router)
this.resolve!(r)
else if (r)
this.goError(i, r);
else
cb()
})
} else {
/* Solve promise in case next was not called */
ctx.complete()
this.resolve!(undefined)
private getNext(index: number): NextFunction {
return (arg?: any | "route" | "router") => {
if (arg === route || arg === router) {
/* If next is called with route or router, resolve with it */
this.pNext(arg);
} else if (arg) {
/* Jump to next error middleware */
this.goError(index, arg);
} else {
/* Go to next callstack elem */
this.handle(index);
}
}
}

private goError(i: number, err: any) {
/* Fetch next error middleware */
for (let j = i; j < this.callstack.length; j++) {
if (this.callstack[j].length === 4)
return this.exec(j, err);
return this.handle(j, err);
}
/* No error middleware reject */
return this.exec(this.callstack.length, err);
return this.handle(this.callstack.length, err);
}
}
14 changes: 0 additions & 14 deletions src/class/Child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { pathToRoute } from "../functions/pathToRoute";
import { overrideRes } from "../functions/overrideRes";
import { postParent } from "../functions/utils/postMessage";
import { importModule } from "../functions/utils/importModule";
import { makeObj } from "../functions/utils/makeObj";

/* Types */
import {
Expand All @@ -34,15 +33,6 @@ import { Request } from "express";
/* Constants */
import {message, noMain, fnStr, routeNotFound, route, router} from "../constants/strings";

const pNext = function (id: number, tid: string, arg: Serializable) : void {
postParent({
cmd: ChildCmd.next,
id,
tid,
arg
});
};

class Child {
/* Id of child */
private id : number;
Expand Down Expand Up @@ -95,10 +85,6 @@ class Child {
throw new Error(routeNotFound);
/* Loop through callstack */
new CallLoop(req, overrideRes(this.id, _id), this.routes[k].callstack!).handle()
/* Handle next() */
.then((res: unknown) => pNext(this.id, _id, res === route || res === router ? res : undefined))
/* Handle errors */
.catch((e: Error) => pNext(this.id, _id, makeObj(e, Object.getOwnPropertyNames(e))));
};

private setSources(sources: Source[]) {
Expand Down
126 changes: 39 additions & 87 deletions src/functions/overrideRes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,94 +6,46 @@ import { notImplemented } from "../constants/strings";
import { ChildCmd, Serializable } from "../types";
import { postParent } from "./utils/postMessage";

const overrideObj = {
_id: -1,
_tid: -1,
transferCall: function transferCall(call: string, ...args: Serializable[]) {
postParent({
cmd: ChildCmd.response,
call,
args,
id: this._id,
tid: this._tid
});
return this as any as Response;
},

append: function append(...args: Serializable[]) {
return this.transferCall("append", ...args);
},
attachment: function attachment(...args: Serializable[]) {
return this.transferCall("attachment", ...args);
},
cookie: function cookie(...args: Serializable[]) {
return this.transferCall("cookie", ...args);
},
clearCookie: function clearCookie(...args: Serializable[]) {
return this.transferCall("clearCookie", ...args);
},
download: function download(...args: Serializable[]) {// WARNING CALLBACK
return this.transferCall("download", ...args);
},
end: function end(...args: Serializable[]) {
return this.transferCall("end", ...args);
},
format: function format(..._args: Serializable[]) {
throw new Error(notImplemented);
},
get: function get() {
throw new Error(notImplemented);
},
json: function json(...args: Serializable[]) {
return this.transferCall("json", ...args);
},
jsonp: function jsonp(...args: Serializable[]) {
return this.transferCall("jsonp", ...args);
},
links: function links(...args: Serializable[]) {
return this.transferCall("links", ...args);
},
location: function location(...args: Serializable[]) {
return this.transferCall("location", ...args);
},
redirect: function redirect(...args: Serializable[]) {
return this.transferCall("redirect", ...args);
},
render: function render(...args: Serializable[]) {// WARNING CALLBACK
return this.transferCall("render", ...args);
},
send: function send(...args: Serializable[]) {
return this.transferCall("send", ...args);
},
sendFile: function sendFile(...args: Serializable[]) {// WARNING CALLBACK
return this.transferCall("sendFile", ...args);
},
sendStatus: function sendStatus(...args: Serializable[]) {
return this.transferCall("sendStatus", ...args);
},
set: function set(...args: Serializable[]) {
return this.transferCall("set", ...args);
},
setHeader: function setHeader(...args: Serializable[]) {
return this.transferCall("setHeader", ...args);
},
removeHeader: function removeHeader(...args: Serializable[]) {
return this.transferCall("removeHeader", ...args);
},
status: function status(...args: Serializable[]) {
return this.transferCall("status", ...args);
},
type: function type(...args: Serializable[]) {
return this.transferCall("type", ...args);
},
vary: function vary(...args: Serializable[]) {
return this.transferCall("vary", ...args);
const handler: ProxyHandler<any> = {
get(target, prop, receiver) {
/* Internal props */
if (prop === "_id" || prop === "_tid") {
return target[prop];
}
/* Transfer call to parent */
if (prop === "transferCall") {
return function (call: string, ...args: Serializable[]) {
postParent({
cmd: ChildCmd.response,
call,
args,
id: target._id,
tid: target._tid
});
return receiver;
};
}
/* Methods that are not implemented */
const notImpl = ["format", "get"];
if (notImpl.includes(prop as string)) {
return function () {
throw new Error(notImplemented);
};
}
/* Else */
return function (...args: Serializable[]) {
return receiver.transferCall(prop, ...args);
};
},
set(target, prop, value) {
if (prop === "_id" || prop === "_tid") {
target[prop] = value;
}
return value;
}
};

export function overrideRes(_id: number, _tid: string) : Response {
const obj = Object.create(overrideObj);
obj._id = _id;
obj._tid = _tid;
return obj as any as Response;
export function overrideRes(_id: number, _tid: string): Response {
const target = { _id, _tid };
return new Proxy(target, handler) as any as Response;
}
10 changes: 1 addition & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,4 @@ export interface Source {
path: string;
type: SourceType;
args?: Serializable[];
}

export interface NextContext {
nextCalled: boolean;
promise: Promise<any>;
done: (arg?: any) => any;
complete: (arg?: any) => void;
}

}
Loading