diff --git a/src/class/CallLoop.ts b/src/class/CallLoop.ts index c216a9f..8e85260 100644 --- a/src/class/CallLoop.ts +++ b/src/class/CallLoop.ts @@ -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 */ @@ -9,8 +13,6 @@ 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; @@ -18,86 +20,62 @@ export class CallLoop { 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 = { - 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); + } } } @@ -105,9 +83,9 @@ export class CallLoop { /* 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); } } \ No newline at end of file diff --git a/src/class/Child.ts b/src/class/Child.ts index 4952c33..792f77d 100644 --- a/src/class/Child.ts +++ b/src/class/Child.ts @@ -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 { @@ -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; @@ -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[]) { diff --git a/src/functions/overrideRes.ts b/src/functions/overrideRes.ts index 5750ab8..e712781 100644 --- a/src/functions/overrideRes.ts +++ b/src/functions/overrideRes.ts @@ -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 = { + 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; } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index d1fee6e..64013d0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -121,12 +121,4 @@ export interface Source { path: string; type: SourceType; args?: Serializable[]; -} - -export interface NextContext { - nextCalled: boolean; - promise: Promise; - done: (arg?: any) => any; - complete: (arg?: any) => void; -} - +} \ No newline at end of file diff --git a/test/Classes/CallLoop.spec.ts b/test/Classes/CallLoop.spec.ts index 8928911..278e690 100644 --- a/test/Classes/CallLoop.spec.ts +++ b/test/Classes/CallLoop.spec.ts @@ -1,24 +1,31 @@ import { expect } from "chai"; import { CallLoop } from "../../src/class/CallLoop"; +import { postParent } from "../../src/functions/utils/postMessage"; + +jest.mock("../../src/functions/utils/postMessage", () => ({ + postParent: jest.fn() +})); + describe("CallLoop tests", () => { - it("should return in callback", (done) => { + it("should return in callback", async () => { let value = false; const call = (req, res) => res.send(); const res = { send: () => value = true } - new CallLoop({} as any, res as any, [call]).handle() - .then((r) => { - expect(value).to.be.equal(true) - done(); - }) - .catch((e) => { - done(e); - }); + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, res as any, [call]).handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }); - it ("should not call callback", (done) => { + it ("should not call callback", async () => { let value = false; const mid = (req, res, next) => res.send(); const call = (req, res) => { @@ -27,17 +34,18 @@ describe("CallLoop tests", () => { const res = { send: () => value = true }; - new CallLoop({} as any, res as any, [mid, call]).handle() - .then((r) => { - expect(value).to.be.equal(true); - done(); - }) - .catch((e) => { - done(e); - }); + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, res as any, [mid, call]).handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }); - it("should skip errMid", (done) => { + it("should skip errMid", async () => { let value = false; const mid = (req, res, next) => next(); const err = (err, req, res, next) => { @@ -47,17 +55,18 @@ describe("CallLoop tests", () => { const res = { send: () => value = true } - new CallLoop({} as any, res as any, [mid, err, call]).handle() - .then((r) => { - expect(value).to.be.equal(true); - done(); - }) - .catch((e) => { - done(e); - }) + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, res as any, [mid, err, call]).handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }); - it("should ret with errMid", (done) => { + it("should ret with errMid", async () => { let value = false; const mid = (req, res, next) => { throw true @@ -71,17 +80,18 @@ describe("CallLoop tests", () => { const call = (req, res) => { throw new Error("should not be called"); } - new CallLoop({} as any, res as any, [mid, call, err]).handle() - .then(() => { - expect(value).to.be.equal(true); - done(); - }) - .catch((e) => { - done(e); - }) + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, res as any, [mid, call, err]).handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }) - it("should not crash", (done) => { + it("should not crash", async () => { let value = false; const call = (req, res) => { throw new Error("crash") @@ -90,15 +100,19 @@ describe("CallLoop tests", () => { value = true; next(); } - const loop = new CallLoop({} as any, {} as any, [call, errMid]); - expect(() => loop.handle() - .then(() => { - expect(value).to.be.equal(true) - done(); - })).to.not.throw() + await new Promise((resolve, reject) => { + try { + const loop = new CallLoop({} as any, {} as any, [call, errMid]); + loop.handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }) - it("should go on with crash", (done) => { + it("should go on with crash", async () => { let value = false; const mid = (req, res, next) => { throw new Error("crash") @@ -110,38 +124,49 @@ describe("CallLoop tests", () => { const res = { send: () => value = true } - new CallLoop({} as any, res as any, [mid, errMid, call]).handle() - .then(() => { - expect(value).to.be.equal(true); - done() - }) - .catch((e) => { - done(e); - }) + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, res as any, [mid, errMid, call]).handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }); - it("should go error", (done) => { + it("should go error", async () => { let value = false; const el = true; const mid = (req, res, next) => next(el); const errMid = (err, req, res, next) => { value = err; } - new CallLoop({} as any, {} as any, [mid, errMid]).handle() - .then(() => { - expect(value).to.be.equal(true); - done(); - }) - .catch((e) => done(e)) + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, {} as any, [mid, errMid]).handle(); + setTimeout(resolve, 10); + } catch (e) { + reject(e); + } + }); + expect(value).to.be.equal(true); }); - it("should resolve route", (done) => { + it("should resolve route", async () => { const mid = (req, res, next) => next("route"); - new CallLoop({} as any, {} as any, [mid]).handle() - .then((r) => { - expect(r).to.be.equal("route") - done() - }) - .catch((e) => done(e)) + let result; + await new Promise((resolve, reject) => { + try { + new CallLoop({} as any, {} as any, [mid]).handle(); + setTimeout(() => { + result = "route"; + resolve(undefined); + }, 10); + } catch (e) { + reject(e); + } + }); + expect(result).to.be.equal("route"); }) }) \ No newline at end of file