diff --git a/src/composer/compose.ts b/src/composer/compose.ts index f2c4958..0f23676 100644 --- a/src/composer/compose.ts +++ b/src/composer/compose.ts @@ -19,24 +19,9 @@ const compose = ((isUsing) => ( (table) => - p.thrush && typeof p.thrush === "object" && p.onError - ? onError(table.isAsync)( - resolveF(o)(table)(p)(isUsing) as ( - ctx: Request, - ) => Response, - )( - resolveF(o)(table)({ - // Uses the same petition's body and overdrives `onError` to avoid infinite recursions - ...p, - f: p.onError, - onError: undefined, - })(isUsing) as ( - ctx: Request, - ) => (error: unknown) => Response, - ) - : resolveF(o)(table)(p)(isUsing) as ( - ctx: Request, - ) => Promise | Response + resolveF(o)(table)(p)(isUsing) as ( + ctx: Request, + ) => Promise | Response )( //elements int table { @@ -82,7 +67,6 @@ const resolveF = linker(o)(p)(isUsing), ); - // Passes value, mainly used for `resolve` and `branch` default: return getResponse(table.isAsync || table.asyncResolve)()(p.f)( linker(o)(p)(isUsing), @@ -177,6 +161,15 @@ async (request: Request): Promise => { // On error wraps +const getApplyTo = (isAsync: boolean) => + isAsync + //@ts-ignore-start + ? () => (f) => (context) => (request) => async (error) => + await f(await context(request)(error)) + //@ts-ignore + : () => (f) => (context) => (request) => (error) => + f(context(request)(error)); + const onError = (isAsync: boolean) => isAsync ? asyncOnError : syncOnError; const asyncOnError = @@ -197,6 +190,8 @@ const syncOnError = try { return f(r); } catch (error) { + console.log(m.toString()); + return m(r)(error); } }; diff --git a/src/composer/linker.ts b/src/composer/linker.ts index ee001af..6f0a126 100644 --- a/src/composer/linker.ts +++ b/src/composer/linker.ts @@ -10,13 +10,17 @@ export type specialOptions = { } & FunRouterOptions; export default (o?: specialOptions) => (p: Petition) => (isUsing: string[]) => { + // Checks if the function has to be applied again for an extra function + const isApplyTo = p.applyTo !== undefined && typeof p.applyTo === "object"; + // Base case: if 'isUsing' is empty and 'branch' is not in options, return the identity function // TODO: change it and add the branch flag in the petition - if (isUsing.length === 0 && !(o && "branch" in o)) { + + if (isUsing.length === 0 && !(o && "branch" in o) && !isApplyTo) { return (r: Request) => r; } - if (o && "branch" in o && p.thrush) { + if (o && "branch" in o && isApplyTo) { // TODO: implement this, just add another wrap , do it before 0.2.0 throw new Error("PANIC_ONERROR: branch cannot have an on error yet"); } @@ -38,8 +42,8 @@ export default (o?: specialOptions) => (p: Petition) => (isUsing: string[]) => { .map((x) => `${x.name}=>`) .reduceRight( (acc, v) => v + acc, - // If thrush is in - typeof p.thrush === "object" ? "onError=>" : "", + // If applyTo is in + typeof p.applyTo === "object" ? "onError=>" : "", ); // Determine the function signature based on options @@ -47,17 +51,15 @@ export default (o?: specialOptions) => (p: Petition) => (isUsing: string[]) => { if (needsAsync) { // We are using the same structure of a branch for OnError - functionSignature = (o && "branch" in o) || typeof p.thrush === "object" + functionSignature = (o && "branch" in o) || isApplyTo ? " r=> async b=> " : " async r=> "; } else { - functionSignature = (o && "branch" in o) || p.thrush ? "r=>b=>" : "r=>"; + functionSignature = (o && "branch" in o) || isApplyTo ? "r=>b=>" : "r=>"; } - // Adding thrush at the table to insert it in the final CTX object - const expandedTable = typeof p.thrush === "object" - ? [...table, p.thrush] - : table; + // Adding applyTo at the table to insert it in the final CTX object + const expandedTable = isApplyTo ? [...table, p.applyTo] : table; // Build the function body, injecting variables from 'table' const functionBody = `({${ @@ -74,8 +76,7 @@ export default (o?: specialOptions) => (p: Petition) => (isUsing: string[]) => { // Reduce the functions over the generated function const resultFunction = functions.reduce((s, k) => s(k), generatedFunc); - if (typeof p.thrush === "object") { - // We apply it so we need a `Request` and the thrown unknown to resolve the ctx + if (isApplyTo) { return resultFunction(p.f); } diff --git a/src/morphism.ts b/src/morphism.ts index 61338ab..79aad3d 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -92,7 +92,7 @@ export const petitions = { ...I, o, // This is done to trigger a flag in compose.ts in the compposer - thrush: { + applyTo: { name: "onError", value: "onError(r)(b)", type: 1, @@ -515,7 +515,13 @@ export const petitions = { ] as Petition[], }; -type typeMorphism = "response" | "request" | "morphism" | "base" | "add"; +type typeMorphism = + | "response" + | "request" + | "morphism" + | "base" + | "add" + | "applyTo"; export type ResolveMap = { [K in keyof T]: T[K] extends Morphism< @@ -547,7 +553,7 @@ export type BranchMap = { : never; }; -type ThrushCTX = { +type ApplyToCTX = { name: string; value: string; type: 0 | 1; @@ -594,13 +600,6 @@ type PetitionHeader = { status?: number; }; -type Context = { - resolve?: ResolveMap; - branch?: BranchMap; - query?: QueryOptions; - param?: ParamOptions; - crypto?: CryptoOptions; -}; export type Morphism< MO extends MapOptions = MapOptions, RM extends ResolveMap = any, @@ -640,7 +639,7 @@ export type Morphism< readonly query?: QO; readonly cookie?: CookieOptions; readonly param?: PO; - readonly thrush?: MO["specialVisible"] extends true ? ThrushCTX : never; + readonly applyTo?: MO["specialVisible"] extends true ? ApplyToCTX : never; readonly plugins?: ExtractPluginTypes; readonly headings?: PetitionHeader; readonly isAsync?: MO["isAPetition"] extends true | false ? boolean @@ -762,8 +761,8 @@ export type WithPlugins< : {}) & CryptoContext; -// type Thrush = -// TH extends ThrushCTX +// type ApplyTo = +// TH extends ApplyToCTX // ? TH['name'] // : {} diff --git a/test/composer/linker.test.ts b/test/composer/linker.test.ts new file mode 100644 index 0000000..a030eb3 --- /dev/null +++ b/test/composer/linker.test.ts @@ -0,0 +1,244 @@ +import linker from "../../src/composer/linker.ts"; +import { assertEquals } from "@std/assert"; +import { test } from "@cross/test"; +import { petitions } from "../../main.ts"; + +/** + * Important!: + * `mockFunction` does absoulitly nothing + * the linker has not inference over what `isUsing` + * + * Also the linker doesn't directly manage async safety + */ +const mockFunction = () => new Response(); +const requestForTest = new Request("http://localhost/1/2/3?hello=world"); + +test("returns identity of the request", async () => { + assertEquals( + linker({})({ + type: "add", + path: "./test", + f: mockFunction, + })([])(requestForTest), + requestForTest, + ); + + // It's based on `isUsing` so even if we are using `param` + // it retruns identity + assertEquals( + linker({})({ + type: "add", + path: "./test", + f: mockFunction, + })([])(requestForTest), + requestForTest, + ); +}); + +test("Checking basic functions", async () => { + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + f: mockFunction, + })(["param"])(requestForTest), + { + param: { + id: "3", + }, + }, + ); + + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + query: { + only: ["hello"], + }, + f: mockFunction, + })(["query"])(requestForTest), + { + query: { + hello: "world", + }, + }, + ); + + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + query: { + only: ["hello"], + }, + f: mockFunction, + })(["query", "param"])(requestForTest), + { + param: { + id: "3", + }, + query: { + hello: "world", + }, + }, + ); +}); + +test("Checking resolve", async () => { + const hello = petitions.resolve()({ + f: () => "world", + }); + + const nestedHello = petitions.resolve()({ + resolve: { + nested: { + f: () => "world", + }, + }, + f: ({ resolve }) => resolve.nested, + }); + + const asyncHello = petitions.resolve()({ + f: async () => await new Promise((res) => res("world")), + }); + + const nestedAsyncHello = petitions.resolve()({ + resolve: { + nested: { + f: async () => await new Promise((res) => res("world")), + }, + }, + f: ({ resolve }) => resolve.nested, + }); + + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + resolve: { + hello, + }, + f: mockFunction, + })(["resolve"])(requestForTest), + { + resolve: { + hello: "world", + }, + }, + ); + + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + resolve: { + hello: nestedHello, + }, + f: mockFunction, + })(["resolve"])(requestForTest), + { + resolve: { + hello: "world", + }, + }, + ); + + //we need to await here to resolve because we are not using the composer + assertEquals( + await linker({})({ + type: "add", + path: "/1/2/:id", + resolve: { + hello: asyncHello, + }, + f: mockFunction, + })(["resolve"])(requestForTest), + { + resolve: { + hello: "world", + }, + }, + ); + + //we need to await here to resolve because we are not using the composer + assertEquals( + await linker({})({ + type: "add", + path: "/1/2/:id", + resolve: { + hello: nestedAsyncHello, + }, + f: mockFunction, + })(["resolve"])(requestForTest), + { + resolve: { + hello: "world", + }, + }, + ); +}); + +/* + Branches have to be resolve from the object `branch` +*/ +test("Checking branch", async () => { + const hello = petitions.branch()({ + f: () => "world", + }); + + const nestedHello = petitions.branch()({ + resolve: { + nested: { + f: () => "world", + }, + }, + f: ({ resolve }) => resolve.nested, + }); + + const asyncHello = petitions.branch()({ + f: async () => await new Promise((res) => res("world")), + }); + + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + branch: { + hello, + }, + f: mockFunction, + })(["branch"])(requestForTest).branch.hello(), + "world", + ); + + assertEquals( + linker({})({ + type: "add", + path: "/1/2/:id", + branch: { + hello: nestedHello, + }, + f: mockFunction, + })(["branch"])(requestForTest).branch.hello(), + "world", + ); + + await linker({})({ + type: "add", + path: "/1/2/:id", + branch: { + hello: asyncHello, + }, + f: mockFunction, + })(["branch"])(requestForTest).branch + // Forcing to resolve the branch on its context + .then( + async (x: { hello: () => any }) => { + assertEquals( + await x.hello(), + "world", + ); + }, + ); +});