From f6bee4fdecfd17f508727e414b0f585556a81756 Mon Sep 17 00:00:00 2001 From: mimiMonads <122406442+mimiMonads@users.noreply.github.com> Date: Fri, 20 Jan 2023 12:23:48 +0000 Subject: [PATCH] let's have some fun --- ReadMe.md | 182 ++++++++++++++++++++++++++++++ builder/arraySwap.ts | 48 ++++++++ builder/atlas.ts | 118 +++++++++++++++++++ builder/methods.ts | 2 + builder/position.ts | 42 +++++++ builder/rComposer.ts | 16 +++ builder/resolver.ts | 89 +++++++++++++++ builder/sComposer.ts | 31 +++++ builder/sResolver.ts | 63 +++++++++++ builder/sSpecialString.ts | 20 ++++ builder/stringParser.ts | 38 +++++++ components/util/badMethod.ts | 2 + components/util/elements.ts | 3 + components/util/mime.ts | 85 ++++++++++++++ components/util/notFound.ts | 2 + fun.ts | 28 +++++ optimizer/aComposer.ts | 36 ++++++ optimizer/checker.ts | 12 ++ optimizer/optimize.ts | 29 +++++ optimizer/params1.ts | 89 +++++++++++++++ optimizer/query1.ts | 41 +++++++ optimizer/response1.ts | 38 +++++++ optimizer/staticFiles.ts | 30 +++++ optimizer/staticPaths.ts | 37 ++++++ optimizer/syncCheckDir.ts | 42 +++++++ optimizer/types.ts | 146 ++++++++++++++++++++++++ test/builder/arraySwap.test.ts | 80 +++++++++++++ test/builder/atlas.test.ts | 110 ++++++++++++++++++ test/builder/resolver.test.ts | 112 ++++++++++++++++++ test/builder/sComposer.test.ts | 2 + test/builder/sResolver.test.ts | 19 ++++ test/builder/stringParser.test.ts | 166 +++++++++++++++++++++++++++ test/fun.test.ts | 146 ++++++++++++++++++++++++ test/optimizer/aComposer.test.ts | 89 +++++++++++++++ test/optimizer/cheker.test.ts | 11 ++ test/optimizer/optimize1.test.ts | 51 +++++++++ test/optimizer/response1.test.ts | 37 ++++++ test/optimizer/static.test.ts | 19 ++++ test/util/paths.ts | 29 +++++ test/util/url.ts | 16 +++ types.ts | 26 +++++ 41 files changed, 2182 insertions(+) create mode 100644 ReadMe.md create mode 100644 builder/arraySwap.ts create mode 100644 builder/atlas.ts create mode 100644 builder/methods.ts create mode 100644 builder/position.ts create mode 100644 builder/rComposer.ts create mode 100644 builder/resolver.ts create mode 100644 builder/sComposer.ts create mode 100644 builder/sResolver.ts create mode 100644 builder/sSpecialString.ts create mode 100644 builder/stringParser.ts create mode 100644 components/util/badMethod.ts create mode 100644 components/util/elements.ts create mode 100644 components/util/mime.ts create mode 100644 components/util/notFound.ts create mode 100644 fun.ts create mode 100644 optimizer/aComposer.ts create mode 100644 optimizer/checker.ts create mode 100644 optimizer/optimize.ts create mode 100644 optimizer/params1.ts create mode 100644 optimizer/query1.ts create mode 100644 optimizer/response1.ts create mode 100644 optimizer/staticFiles.ts create mode 100644 optimizer/staticPaths.ts create mode 100644 optimizer/syncCheckDir.ts create mode 100644 optimizer/types.ts create mode 100644 test/builder/arraySwap.test.ts create mode 100644 test/builder/atlas.test.ts create mode 100644 test/builder/resolver.test.ts create mode 100644 test/builder/sComposer.test.ts create mode 100644 test/builder/sResolver.test.ts create mode 100644 test/builder/stringParser.test.ts create mode 100644 test/fun.test.ts create mode 100644 test/optimizer/aComposer.test.ts create mode 100644 test/optimizer/cheker.test.ts create mode 100644 test/optimizer/optimize1.test.ts create mode 100644 test/optimizer/response1.test.ts create mode 100644 test/optimizer/static.test.ts create mode 100644 test/util/paths.ts create mode 100644 test/util/url.ts create mode 100644 types.ts diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..549ac64 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,182 @@ +# Functor + +Faster router in Deno (Working in the benchmarks) + +Functor was designed to be: + +- Independent +- Fast +- Scalable +- Predictable + +Get started in 10 Minutes! +## Get Functor and a server + +```typescript +//Functor is just a router, so a server that give a Request and expect Response is needed + +import { serve } from "https://deno.land/std@0.159.0/http/server.ts"; + +// import fun + +import fun from "https://deno.land/x/functor/fun.ts"; +``` +## Give a path and a function + +```typescript +// the function has to return a valid BodyInt or Promise +await serve( + fun()([ + { + path: "/", + f: (_) => "hello world", + }, + ]), + { port: 8080 }, +); +``` +## Add parameters, a query, a status, or a header! + +```typescript +// the router auto detect if you are using them, unless you send the arguments out of the scope +// r: (arguments) => outOfScope(arguments), +// you can add or remove them with "add", "delete" + + +await serve( + fun( + { hasName: "http://127.0.0.1:8080/" }, +)([ + { + path: "/test/:id", + status: 201, + header: new Headers(), + r: (f) => f.param.id + " " + (f.query?.hello || ""), + }, + ]), + { port: 8080, hostname: "127.0.0.1" }, +); +``` + +## Parameters + +Parameters must be chained, without gaps. + +```typescript +// valid (It is important to note that the following routes are different) + "/hello/:id" + "/hello/:id/" + "/hello/:id/:page/:time" + "/hello/:id/:page/:time/" + +// invalid + "/hello/:id/page/:time" + "/hello/:id/page/:time/" + + +``` + + +## Do you need more control ? + +```typescript +// use the type:"request" to return a Response or Promise +// you can use params and query here too! + + +await serve( + fun( + { hasName: "http://127.0.0.1:8080/" }, +)([ + { + type: "request", + path: "/abc", + f: (f) => new Response(f.query?.hello || "abc"), + }, + ]), + { port: 8080, hostname: "127.0.0.1" }, +); +``` +## Do you need Functor just to route your function? I've got you covered! + +```typescript +// use the type:"response" to return a Response or Promise +await serve( + fun( +{ hasName: "http://127.0.0.1:8080/" }, +)([ + { + type: "response", + path: "/", + r: (_) => new Response("hello world"), + + }, ]), + { port: 8080, hostname: "127.0.0.1" }, +); +``` +## Static file is natively build in functor! + +```typescript +// "path" is relative to terminal +// remove mime types with mime:false +// add mime with extra: [ [header,extension]] +await serve( + fun( + { hasName: "http://127.0.0.1:8080/" }, +)([ + { + type: "static", + name: "/s", + path: "./", + }, +, ]), + { port: 8080, hostname: "127.0.0.1" }, +); +``` + Thanks and have fun ~ + + + + +# Specifications + + +## Route options + + + +```typescript + type funRouterOptions = { + hasName?: string; + paramsStartsWith?: string; + notFound?: { (x: Request): Response }; + badMethod?: { (x: Request): Response }; +}; + +``` + +- **"hasName"**: is the name of the server, and it always has to finish with "/" + , +example: "http://127.0.0.1:8080/", the router will be 5% faster if a name is + given. + +- **"paramsStartsWith"**: by default, a parameter is defined by ":" next to a + "/", changing this value, will take the first character and check if it's + follow by "/" to star a new parameter. + +- **"notFound"**: changes the default NOT_FOUND. + +- **"badMethod"**: changes the default BAD_METHOD. + +## Methods + +There are 4 methods + +```typescript +type ParamsMethod = "GET" | "HEAD" | "POST" | "DELETE"; +``` + + +## License + +[CC BY-ND 4.0](https://creativecommons.org/licenses/by-nd/4.0/legalcode.txt) \ No newline at end of file diff --git a/builder/arraySwap.ts b/builder/arraySwap.ts new file mode 100644 index 0000000..59cd08a --- /dev/null +++ b/builder/arraySwap.ts @@ -0,0 +1,48 @@ +import { + ArrayFiler, + ArraySwap, + funRouterOptions, + RouteTypes, +} from "../types.ts"; + +export default (o?: funRouterOptions) => (a: RouteTypes[]): ArrayFiler => + ( + (fl) => + ( + (sp) => [ + fl + .map((x) => [x[1].split("/"), x[0], x[1], x[2]]) + .map((x) => + [x[0].length - 1, x[0], x[1], x[2], x[3]] as [ + number, + string[], + RouteTypes[0], + RouteTypes[1], + RouteTypes[2], + ] + ) + .map((x) => + [ + x[3] === "/" && o?.globalNotFound === true ? 0 : x[0], + ((y) => + ((a) => + a.length <= 1 ? a.join("") : a[0] + a.slice(1).join("/"))( + x[1] + .filter((z) => z[0] !== y), + ))( + typeof o?.paramsStartsWith === "string" + ? o?.paramsStartsWith + : ":", + ), + x[2], + x[4], + ] as ArraySwap + ), + sp, + ] + )( + a.filter((x) => typeof x[3] === "string") as RouteTypes[], + ) + )( + a.filter((x) => x[3] === false), + ); diff --git a/builder/atlas.ts b/builder/atlas.ts new file mode 100644 index 0000000..839bfc8 --- /dev/null +++ b/builder/atlas.ts @@ -0,0 +1,118 @@ +import { + ArrayFiler, + funRouterOptions, + ParamsMethod, + RequestFunction, +} from "../types.ts"; +import badMethod from "../components/util/badMethod.ts"; +import notFound from "../components/util/notFound.ts"; + +type InnerObj = [string, RequestFunction] | []; +type InnerObjEmpty = [string, RequestFunction]; +type Map = Record>; + +type Atlas = [ + ParamsMethod[], + number[][], + string[][][], + RequestFunction[], + ArrayFiler[1], +]; +export default (_o?: funRouterOptions) => (a: ArrayFiler): Atlas => + ( + (am) => + ( + (ob) => + ( + (il) => + ( + (al) => + ( + (ul) => [am, il, al, ul, a[1]] + )( + il.map( + (x, i) => + x.map( + (y) => + ob[i][y].map( + (z) => [z[1]], + ), + ) as [RequestFunction][][], + ) + .flat(3) + .concat(a[1].map(([_, _a, x]) => x)) + .concat(notFound) + .concat(badMethod), + ) + )( + il.map( + (x, i) => + x.map( + (y) => ob[i][y].map((x) => x[0]), + ), + ) as string[][][], + ) + )( + Object.keys(ob) + .map((x) => Number(x)) + .map( + (x) => + Object + .keys(ob[Number(x)]) + .map((y) => Number(y)), + ) as [number[]], + ) + )( + Object.fromEntries( + am.map((x) => a[0].filter((y) => x === y[2])) + .map( + (x) => + ((p) => + Object.fromEntries( + p.map( + (y) => [ + y, + ( + (za) => + za[0][0] === "" + ? za + .slice(1) + .reduceRight( + (acc, z) => acc.concat([z]), + [za[0]], + ) + .reverse() + : za + )( + x + .reduce( + (acc, z) => + z[0] === y + ? [...acc, [z[1], z[3]]] as InnerObjEmpty[] + : acc, + [] as InnerObjEmpty[], + ).sort((a, b) => b[0].length - a[0].length), + ), + ], + ), + ))( + x.map((y) => y[0]) + .reduce( + (acc, y) => acc.includes(y) ? acc : acc.concat([y]), + [] as number[], + ) as number[], + ), + ) + .map( + (x, i) => [i, x], + ), + ) as Map, + ) + )( + a[0] + .reduce((acc: ParamsMethod[], am) => + acc + .includes(am[2]) + ? acc + : [...acc, am[2]], []) as ParamsMethod[], + ); diff --git a/builder/methods.ts b/builder/methods.ts new file mode 100644 index 0000000..6ce2614 --- /dev/null +++ b/builder/methods.ts @@ -0,0 +1,2 @@ +export default (a: string[]) => + ((o) => (s: string) => o.indexOf(s[0]))(a.map((x) => x[0])); diff --git a/builder/position.ts b/builder/position.ts new file mode 100644 index 0000000..b966fc9 --- /dev/null +++ b/builder/position.ts @@ -0,0 +1,42 @@ +import { funRouterOptions } from "../types.ts"; + +export default (_o?: funRouterOptions) => +(m: number[][]) => +(c: string[][][]) => + ((h) => + ((j) => + h.map( + (x, i) => + ((a) => + m[i][0] === 1 || m[i][0] === 0 + ? a.map((y, a) => a === 0 && i === 0 ? 0 : y) + : ([0].concat(a.map((x) => x + 1))).slice(0, -1))( + x.map( + (y, a, b) => + y + (i === 0 + ? b.reduce( + (acc, y, u) => a > u ? acc + y : acc, + -1, + ) + : j.reduce((acc, y, u) => u < i ? acc + y : acc) - 1), + ), + ), + ))( + h.map( + (x) => + x + .map( + (y, i, a) => + y + + a.reduce( + (acc, z, u) => i < u ? acc + z : acc, + 0, + ), + ) + .reduce((acc, y) => y > acc ? y : acc, 0), + ), + ))( + c.map( + (x) => x.map((y) => y.length), + ), + ); diff --git a/builder/rComposer.ts b/builder/rComposer.ts new file mode 100644 index 0000000..346e044 --- /dev/null +++ b/builder/rComposer.ts @@ -0,0 +1,16 @@ +export default (a: string[][][]) => + a.map( + (x) => + x.map((y) => + ( + new Function( + `return s=>${ + y.reduceRight( + (acc, z, i) => `s.indexOf("${z}")===0?${i}:` + acc, + "-1", + ) + }`, + ) + )() + ), + ); diff --git a/builder/resolver.ts b/builder/resolver.ts new file mode 100644 index 0000000..682ef3c --- /dev/null +++ b/builder/resolver.ts @@ -0,0 +1,89 @@ +import { funRouterOptions, RouteTypes } from "../types.ts"; +import stringParser from "./stringParser.ts"; +import position from "./position.ts"; +import sResolver from "./sResolver.ts" +import sSpecialString from "./sSpecialString.ts" + + +export default (o?: funRouterOptions) => +(last: number) => +(m: number[][]) => +(g: string[][][]) => +(se: RouteTypes[]) => + ((l) => + ((pu) => + ((p) => + ((u) => ( + sr => + se.length === 0 + ? (n: number) => (s: string) => + n !== -1 + ? ( + (sp) => + ( + (a) => + a !== -1 + ? ( + (b) => b !== -1 ? u[n][a] + b : pu + )( + sr[n][a](sp[1]), + ) + : pu + )( + m[n].indexOf(sp[0]), + ) + )( + p(s), + ) + : l + : ( + (sc) => (n: number) => (s: string) => + n !== -1 + ? ( + (sp) => + ( + (w) => + w === -1 + ? ( + (a) => + a !== -1 + ? ( + (b) => b !== -1 ? u[n][a] + b : pu + )( + sr[n][a](sp[1]), + ) + : pu + )( + m[n].indexOf(sp[0]), + ) + : w + )( + sc(sp[1]), + ) + )( + p(s), + ) + : l + + )( + + + sSpecialString({})(l)( + se.map((x) => [x[0], x[3], x[2], x[1]] as RouteTypes), + ), + ) + )( + sResolver(g) + ) + ) + ( + position(o)(m)(g), + ))( + stringParser(o)(m), + ))( + l - 1, + ))( + last - 1, + ); + + diff --git a/builder/sComposer.ts b/builder/sComposer.ts new file mode 100644 index 0000000..0135147 --- /dev/null +++ b/builder/sComposer.ts @@ -0,0 +1,31 @@ +import { funRouterOptions } from "../types.ts"; + +export default (o?: funRouterOptions) => (an: number[][]) => + ( + (n) => + new Function(`return (s =>${ + Array.from( + { + length: n + 1, + }, + ( + _, + i, + ) => [ + `(a${i}=>(a${i}<1?${ + typeof o?.globalNotFound === "boolean" || + typeof o?.hasName === "string" + ? i + 1 + : i + }:${i === n ? i + 1 : ""}`, + `))(s.indexOf("/"${i !== 0 ? `,a${i - 1}` : ""}) + 1)`, + ], + ).reverse().reduce((acc, v) => v[0] + acc + v[1], "") + } +)`)() + )( + an.reduce((acc, x) => + ( + (w) => w > acc ? w : acc + )(x.reduce((acc1, y) => y > acc ? y : acc1, 0)), 0), + ); diff --git a/builder/sResolver.ts b/builder/sResolver.ts new file mode 100644 index 0000000..61ff767 --- /dev/null +++ b/builder/sResolver.ts @@ -0,0 +1,63 @@ +export default (ar: string[][][]) => ( + parser => ( + resolver => ar + .map( x => x + .map( y => resolver(parser(y)))) + )( + (a: [string, number, number][][]) => + new Function( + `return s => ${ + a + .map((x, i) => + x.length === 0 + ? `s === "" ? ${i} : ` + : ( + (x.reduce( + (acc, y) => y[0] === "" ? 's === ""' : `&& s.slice(${y[1]},${y[2]}) === "${y[0]}"` + acc, + "" + ) as string) + `? ${i} :` + ).slice(2) + ) + .reduceRight((acc, x) => x + acc) + "-1" + }` + )() as (s:string) => number + ) +)( + (s: string[]) => + ((s1) => + s1 + .map( + (x) => + [ + x, + x + .map((y) => (y !== "" ? y.length : 0)) + .map((y, i, a) => + i === 0 ? y : a.slice(0, i).reduce((acc, s) => acc + s) + i + y + ) + + .map((y, i, a) => (i === 0 ? [0, y] : [a[i - 1] + 1, y])), + ] as [string[], number[][]] + ) + .map(([a, n]) => a.map((x, i) => [x, n[i][0], n[i][1]]))) + ( + s + .map( x => x === "" ? x : "/" + x) + .map((x) => + x.at(-1) === "/" + ? x + .split("/", x.split("/").length - 1) + .map((x, b, a) => (a.length <= b + 1 ? x + "/" : x)) + .slice(1) + : x.split("/").slice(1) + ) + ) as [string, number, number][][] +) + + + + + + + + diff --git a/builder/sSpecialString.ts b/builder/sSpecialString.ts new file mode 100644 index 0000000..b36be9e --- /dev/null +++ b/builder/sSpecialString.ts @@ -0,0 +1,20 @@ +import { funRouterOptions, RouteTypes } from "../types.ts"; + +export default (_o?: funRouterOptions) => (max: number) => (ar: RouteTypes[]) => + ( + (nar) => + ( + (f) => f(nar) + )( + (ar: string[]) => + (new Function(`return s=>${ + ar.reduceRight( + (acc, v, i) => `s.indexOf("${v}")===0?${i + max - 2}:` + acc, + "-1", + ) + }`))() as (s: string) => number, + ) + )( + ar.map(([_, a]) => a.slice(1)) + .sort((a, b) => b.length - a.length), + ); diff --git a/builder/stringParser.ts b/builder/stringParser.ts new file mode 100644 index 0000000..6f0950f --- /dev/null +++ b/builder/stringParser.ts @@ -0,0 +1,38 @@ +import { funRouterOptions } from "../types.ts"; +import sComposer from "./sComposer.ts"; + +export default (o?: funRouterOptions) => (an: number[][]) => + ((re) => + typeof o?.hasName === "string" + ? o?.globalNotFound === true + ? ((n: number) => (s: string): [number, string] => + ((ar) => ar === "" ? [0, ""] : [re(ar), ar])( + s.slice(n), + ))(o.hasName.length) + : ((b) => (n: number) => (s: string): [number, string] => + ((ar) => ar === "" ? b : [re(ar), ar])(s.slice(n)))( + [1, ""] as [number, string], + )( + o.hasName.length, + ) + : o?.globalNotFound === true + ? ((n: number) => (s: string): [number, string] => + ((ar) => ar !== "" ? [re(ar), ar] : [0, ""])( + n !== -1 ? s.slice(n) : s.slice( + n = s + .split("/") + .filter((x) => x !== "") + .reduce((acc, x, u) => u <= 1 ? acc + x.length : acc, 3), + ), + ))(-1) + : ((n: number) => (s: string): [number, string] => + ((ar) => [re(ar) + 1, ar])( + n !== -1 ? s.slice(n) : s.slice( + n = s + .split("/") + .filter((x) => x !== "") + .reduce((acc, x, u) => u <= 1 ? acc + x.length : acc, 3), + ), + ))(-1))( + sComposer(o)(an) as (s: string) => number, + ); diff --git a/components/util/badMethod.ts b/components/util/badMethod.ts new file mode 100644 index 0000000..51a2660 --- /dev/null +++ b/components/util/badMethod.ts @@ -0,0 +1,2 @@ +export default (_: Request) => + ((re) => re)(new Response(" Method Not Allowed ", { status: 405 })); diff --git a/components/util/elements.ts b/components/util/elements.ts new file mode 100644 index 0000000..9d624d4 --- /dev/null +++ b/components/util/elements.ts @@ -0,0 +1,3 @@ +import { AddOptions } from "../../optimizer/types.ts"; + +export default ["req", "query", "param"] as AddOptions; diff --git a/components/util/mime.ts b/components/util/mime.ts new file mode 100644 index 0000000..64fda4e --- /dev/null +++ b/components/util/mime.ts @@ -0,0 +1,85 @@ +export default [ + [".aac", "audio/aac"], + [".abw", "application/x-abiword"], + [".arc", "application/x-freearc"], + [".avif", "image/avif"], + [".avi", "video/x-msvideo"], + [".azw", "application/vnd.amazon.ebook"], + [".azw", "application/vnd.amazon.ebook"], + [".bmp", "image/bmp"], + [".bz", "application/x-bzip"], + [".bz2", "application/x-bzip2"], + [".cda", "application/x-cdf"], + [".csh", "application/x-csh"], + [".css", "text/css"], + [".csv", "text/csv"], + [".doc", "application/msword"], + [ + ".docx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ], + [".eot", "application/vnd.ms-fontobject"], + [".epub", "application/epub+zip"], + [".gz", "application/gzip"], + [".gif", "image/gif"], + [".htm", "text/html"], + [".html", "text/html"], + [".ico", "image/vnd.microsoft.icon"], + [".ics", "text/calendar"], + [".jar", "application/java-archive"], + [".jpeg", "image/jpeg"], + [".js", "text/javascript"], + [".json", "application/json"], + [".jsonld", "application/ld+json"], + [".mid", "audio/x-midi"], + [".mjs", "text/javascript"], + [".mp3", "audio/mpeg"], + [".mp4", "video/mp4"], + [".mpeg", "video/mpeg"], + [".mpkg", "application/vnd.apple.installer+xml"], + [".odp", "application/vnd.oasis.opendocument.presentation"], + [".ods", "application/vnd.oasis.opendocument.spreadsheet"], + [".odt", "application/vnd.oasis.opendocument.text"], + [".oga", "audio/ogg"], + [".ogv", "video/ogg"], + [".ogx", "application/ogg"], + [".opus", "audio/opus"], + [".otf", "font/otf"], + [".png", "image/png"], + [".pdf", "application/pdf"], + [".php", "application/x-httpd-php"], + [".ppt", "application/vnd.ms-powerpoint"], + [ + ".pptx", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ], + [".rar", "application/vnd.rar"], + [".rtf", "application/rtf"], + [".sh", "application/x-sh"], + [".svg", "image/svg+xml"], + [".tar", "application/x-tar"], + [".tif", "image/tiff"], + [".tiff", "image/tiff"], + [".ts", "video/mp2t"], + [".ttf", "font/ttf"], + [".txt", "text/plain"], + [".vsd", "application/vnd.visio"], + [".wav", "audio/wav"], + [".weba", "audio/webm"], + [".webm", "video/webm"], + [".webp", "image/webp"], + [".woff", "font/woff"], + [".woff2", "font/woff2"], + [".xhtml", "application/xhtml+xml"], + [".xls", "application/vnd.ms-excel"], + [ + ".xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ], + [".xml", "application/xml"], + [".xul", "application/vnd.mozilla.xul+xml"], + [".zip", "archive application/zip"], + [".3gp", "audio/video"], + [".3g2", "audio/video"], + [".7z", "application/x-7z-compressed"], +] as [string, string][]; diff --git a/components/util/notFound.ts b/components/util/notFound.ts new file mode 100644 index 0000000..959d91d --- /dev/null +++ b/components/util/notFound.ts @@ -0,0 +1,2 @@ +export default (_: Request) => + ((re) => re)(new Response("Not found", { status: 404 })); diff --git a/fun.ts b/fun.ts new file mode 100644 index 0000000..a24c28b --- /dev/null +++ b/fun.ts @@ -0,0 +1,28 @@ +import { funRouterOptions } from "./types.ts"; +import { ObjectRawResponse } from "./optimizer/types.ts"; +import optimizer from "./optimizer/optimize.ts"; +import methods from "./builder/methods.ts"; +import atlas from "./builder/atlas.ts"; +import arraySwap from "./builder/arraySwap.ts"; +import resolver from "./builder/resolver.ts"; + +export default (o?: funRouterOptions) => (routes: ObjectRawResponse[]) => + ((re) => + ((fm) => + ((s) => (r: Request) => + ( + ((p) => re[3][p](r))( + s(fm(r.method))(r.url), + ) + ) + )( + resolver(o)(re[3].length)(re[1])(re[2])(re[4]), + ))( + methods(re[0]), + ))( + atlas(o)( + arraySwap(o)( + optimizer(o)(routes), + ), + ), + ); diff --git a/optimizer/aComposer.ts b/optimizer/aComposer.ts new file mode 100644 index 0000000..77df4cc --- /dev/null +++ b/optimizer/aComposer.ts @@ -0,0 +1,36 @@ +import params from "./params1.ts"; +import query from "./query1.ts"; +import { funRouterOptions } from "../types.ts"; +import { ObjectRawResponseCommon, RequestArguments } from "./types.ts"; + +export default (o?: funRouterOptions) => +(f: ObjectRawResponseCommon) => +(ar: string[]) => + ((el) => + ( + new Function( + `return ${ + el.reduce( + (acc, y) => + y.type == 1 && ar.includes(y.name) + ? "(" + y.name + "=>" + acc + ")(" + y.f(o)(f) + + ")" + : acc, + `r=>({${ + el.reduce((acc, v) => + ar.includes(v.name) + ? (v.type === 0) + ? acc + `${v.name}:r,` + : acc + `${v.name}:${v.name}(r.url),` + : acc, "") + }})`, + ) + }`, + ) + )() as (r: Request) => RequestArguments)( + [{ name: "req", type: 0, f: query }, { + name: "query", + type: 1, + f: query, + }, { name: "param", type: 1, f: params }], + ); diff --git a/optimizer/checker.ts b/optimizer/checker.ts new file mode 100644 index 0000000..db2f224 --- /dev/null +++ b/optimizer/checker.ts @@ -0,0 +1,12 @@ +export default (n: string[]) => (a: string[]) => (p: string[]) => (s: string) => + a + .filter((x) => n.length > 1 ? n.some((y) => y !== x) : true) + .reduce( + (acc, v) => s.includes(v) ? acc.concat(v) : acc, + [] as string[], + ) + .concat(p) + .reduce( + (acc, v) => acc.includes(v) === false ? acc.concat(v) : acc, + [] as string[], + ); diff --git a/optimizer/optimize.ts b/optimizer/optimize.ts new file mode 100644 index 0000000..83f994c --- /dev/null +++ b/optimizer/optimize.ts @@ -0,0 +1,29 @@ +import { funRouterOptions, RouteTypes } from "../types.ts"; +import { ObjectRawResponse } from "./types.ts"; +import response from "./response1.ts"; +import staticFiles from "./staticFiles.ts"; +export default ( + o?: funRouterOptions, +): (ar: ObjectRawResponse[]) => RouteTypes[] => +(ar) => + ar + .map( + (x) => + "type" in x + ? x.type === "response" + ? [x?.method ? x.method : "GET", x.path, x.r, false] as RouteTypes + : x.type === "static" + ? ["GET", x.path, staticFiles(o)(x), x.name] as RouteTypes + : [ + x?.method ? x.method : "GET", + x.path, + response(o)(x), + false, + ] as RouteTypes + : [ + x?.method ? x.method : "GET", + x.path, + response(o)(x), + false, + ] as RouteTypes, + ); diff --git a/optimizer/params1.ts b/optimizer/params1.ts new file mode 100644 index 0000000..caba2a2 --- /dev/null +++ b/optimizer/params1.ts @@ -0,0 +1,89 @@ +import { ObjectRawResponseCommon } from "./types.ts"; +import { funRouterOptions } from "../types.ts"; + +type ParamsResult = string | { (s: string): Record }; + +export default (o?: funRouterOptions) => (f: ObjectRawResponseCommon) => + ((sp) => + ((el) => + ((p) => + ( + (v) => + p === -1 && v + ? (new Function( + `return e => ( ${ + el.reduce( + (acc, v) => acc + `"${v}": "Error in the Parameters" ,`, + "", + ) + })`, + ))() + : ( + (st) => + ((l) => + l !== -1 + ? `(f=>s=>f(s.indexOf("?") === -1?s.slice(${ + f.path.indexOf(sp) + l - 1 + }) + .split("/") + .filter((x) => x !== ""):s.slice(${ + f.path.indexOf(sp) + l - 1 + }) + .slice(0,s.slice(${ + f.path.indexOf(sp) + l - 1 + }).lastIndexOf("?")) + .split("/") + .filter((x) => x !== "")))(${st.toString()}) + ` + : `(n=>st=>s=> + n !== -1 + ? s.indexOf("?") === -1? + st( + s.slice(n) + .split("/") + .filter((x) => x !== "") + ):st( + s.slice(n).slice(0,s.slice(n).indexOf("?")) + .split("/") + .filter((x) => x !== "") + ) + : st( + s.slice( + n = s + .split("/") + .filter((x) => x !== "") + .reduce( + (acc, x, u) => + u <= 1 ? acc + x.length : acc, + 2, + ) + + ${ + f.path + .indexOf(sp) + } + ) + .split("/") + .filter((x) => x !== "") + ) + )(-1)(${st.toString()})`)( + typeof o?.hasName === "string" ? o.hasName.length : -1, + ) + )( + ((a: string[]) => + (new Function( + `return e => ({ ${ + a.reduce((acc, v, i) => acc + `"${v}": e[${i}],`, "") + }})`, + ))())( + el.slice(p).map((x) => x.slice(1)), + ) as (s: string[]) => Record, + ) + )( + el.slice(p).every((x) => x[0] === sp), + ))(el.findIndex((x) => x[0] === sp)))( + f.path + .split("/") + .filter((x) => x !== ""), + ))( + typeof o?.paramsStartsWith === "string" ? o.paramsStartsWith : ":", + ); diff --git a/optimizer/query1.ts b/optimizer/query1.ts new file mode 100644 index 0000000..7739751 --- /dev/null +++ b/optimizer/query1.ts @@ -0,0 +1,41 @@ +import { funRouterOptions } from "../types.ts"; +import { ObjectRawResponseCommon } from "./types.ts"; + +export default (o?: funRouterOptions) => (f: ObjectRawResponseCommon) => + ( + (b) => + ( + (p) => + b !== -1 + ? `s=>(i=> + i!==-1? + Object.fromEntries( + s.slice(i+1).split("&").map((x) => x.split("=")) + ) + :null)(s.indexOf("?"))` + : ` (b=>s => + + b !== -1 + ? Object.fromEntries( + s.slice(b).split("&").map((x) => x.split("=")), + ) + : Object.fromEntries( + s.slice( + b = s + .split("/") + .filter((x) => x !== "") + .reduce( + (acc, x, u) => u <= 1 ? acc + x.length : acc, + 3, + ) + + ${p}, + ).split("&").map((x) => x.split("=")), + ))(-1)` + )( + f.path.includes("/" + (o?.paramsStartsWith || ":")) + ? f.path.indexOf("/" + (o?.paramsStartsWith || ":")) + : f.path.length, + ) + )( + typeof o?.hasName === "string" ? o.hasName.length : -1, + ); diff --git a/optimizer/response1.ts b/optimizer/response1.ts new file mode 100644 index 0000000..a934b55 --- /dev/null +++ b/optimizer/response1.ts @@ -0,0 +1,38 @@ +import { funRouterOptions } from "../types.ts"; +import { ObjectRawCommonRequest, ObjectRawResponseCommon } from "./types.ts"; +import checker from "./checker.ts"; +import aComposer from "./aComposer.ts"; + +export default (o?: funRouterOptions) => +(f: ObjectRawResponseCommon | ObjectRawCommonRequest) => + ((el) => + ((c) => + "type" in f + ? (r: Request) => f.f(c(r)) + : f.f.constructor.name === "AsyncFunction" + ? "status" in f || "header" in f + ? ((h: ResponseInit) => async (r: Request) => + new Response(await f.f(c(r)) as BodyInit, h))( + (new Function( + `{${"header" in f ? "header:" + f.header + "," : ""}${ + "status" in f ? "status:" + f.status + "," : "" + }}`, + ))(), + ) + : async (r: Request) => new Response(await f.f(c(r)) as BodyInit) + : "status" in f || "header" in f + ? ((h: ResponseInit) => (r: Request) => + new Response(f.f(c(r)) as BodyInit, h))( + (new Function( + `{${"header" in f ? "header:" + f.header + "," : ""}${ + "status" in f ? "status:" + f.status + "," : "" + }}`, + ))(), + ) + : (r: Request) => new Response(f.f(c(r)) as BodyInit))( + aComposer(o)(f as ObjectRawResponseCommon)(el), + ))( + checker(f?.delete || [])(["param", "query", "req"])(f?.add || [])( + f.f.toString(), + ), + ); diff --git a/optimizer/staticFiles.ts b/optimizer/staticFiles.ts new file mode 100644 index 0000000..275625f --- /dev/null +++ b/optimizer/staticFiles.ts @@ -0,0 +1,30 @@ +import { funRouterOptions } from "../types.ts"; +import { ObjectRawResponseStatic } from "./types.ts"; +import syncCheckDir from "./syncCheckDir.ts"; +import atlas from "../builder/atlas.ts"; +import arraySwap from "../builder/arraySwap.ts"; +import resolver from "../builder/resolver.ts"; +import staticPaths from "./staticPaths.ts"; +import mime from "../components/util/mime.ts"; + +export default (o?: funRouterOptions) => +(f: ObjectRawResponseStatic): (r: Request) => Response | Promise => + ((p) => + ((re) => + ( + (s) => (r: Request) => ( + ((p) => re[3][p](r))( + s(0)(r.url), + ) + ) + )( + resolver(o)(re[3].length)(re[1])(re[2])([]), + ))( + atlas(o)( + arraySwap(o)( + staticPaths( "mime" in f && f.mime === false ? [] : "extra" in f ? mime.concat(f.extra): mime )(p)(f.name), + ), + ), + ))( + syncCheckDir(f.path).map((y) => y[0]).flat(), + ); diff --git a/optimizer/staticPaths.ts b/optimizer/staticPaths.ts new file mode 100644 index 0000000..935ddb8 --- /dev/null +++ b/optimizer/staticPaths.ts @@ -0,0 +1,37 @@ +import { RouteTypes } from "../types.ts"; + +export default (mine: [string, string][]) => +(arr: string[]) => +(name: string) => + arr.map( + (x) => + ( + (ext) => + ( + (el) => + el + ? [ + "GET", + name + x.slice(1), + (new Function( + `return async _=> new Response( await Deno.readTextFile("${x}"), + {headers: {'Content-Type': '${el[1]}'}} + )`, + ))(), + false, + ] + : [ + "GET", + name + x.slice(1), + (new Function( + `return async _=> new Response(await Deno.readTextFile("${x}"))`, + ))(), + false, + ] + )( + mine.find((x) => x[0] === ext) || null, + ) + )( + "." + x.split(".").at(-1), + ), + ) as RouteTypes[]; diff --git a/optimizer/syncCheckDir.ts b/optimizer/syncCheckDir.ts new file mode 100644 index 0000000..a863750 --- /dev/null +++ b/optimizer/syncCheckDir.ts @@ -0,0 +1,42 @@ +// deno-lint-ignore-file + +export default ((start) => + ( + (Y) => + ( + (searcher) => + ((mapper) => Y(mapper)(searcher))( + (f: (arg0: any) => any) => (m: any[]) => + m.some((x: boolean[]) => x[1] === true) + ? f(m.reduce((acc: any[], x: string[]) => + !x[1] ? acc.concat([x]) : acc.concat( + Y((f: (arg0: any) => ConcatArray) => + ((p) => (o: { next: () => any }) => + ((s) => + s.done === true + ? [] + : [[p + s.value.name, s.value.isDirectory]] + .concat(f(o)))( + o.next(), + ))(x[0] + "/") + )(Deno.readDirSync(x[0] as string)), + ), [])) + : m, + ) + )( + Y((f: (arg0: any) => ConcatArray) => + ((p) => (o: { next: () => any }) => + ((s) => + s.done === true + ? [] + : [[p + s.value.name, s.value.isDirectory]].concat(f(o)))( + o.next(), + ))(start) + )(Deno.readDirSync(start)), + ) + )( + (f: (arg0: (y: any) => any) => any) => + ((x) => x(x))((x: (arg0: any) => { (arg0: any): any; new (): any }) => + f((y: any) => x(x)(y)) + ), + )) as (s: string) => string[]; diff --git a/optimizer/types.ts b/optimizer/types.ts new file mode 100644 index 0000000..8c2567c --- /dev/null +++ b/optimizer/types.ts @@ -0,0 +1,146 @@ +import { ParamsMethod } from "../types.ts"; + +export type ParamsOptions = { + elements: string[]; +}; +export type QueryOptions = { + only?: string[]; +}; +export type AddOption = "req" | "query" | "param"; +export type AddOptions = AddOption[]; +export type RequestArguments = { + req: Request; + query: Record; + param: Record; +}; +export type ObjectRawResponse = + | ObjectRawResponseCommon + | ObjectRawResponseReturn + | ObjectRawCommonRequest + | ObjectRawResponseStatic; + +export type ObjectRawResponseCommon = { + path: string; + f: (r: RequestArguments) => BodyInit | Promise; + param?: ParamsOptions; + query?: QueryOptions; + add?: AddOptions; + delete?: AddOptions; + dev?: "test"; + method?: ParamsMethod; + status?: number; + header?: Headers; + // mime?: defaultMime; +}; + +export type ObjectRawCommonRequest = { + type: "request"; + path: string; + f: (r: RequestArguments) => Response | Promise; + param?: ParamsOptions; + query?: QueryOptions; + add?: AddOptions; + delete?: AddOptions; + dev?: "test"; + method?: ParamsMethod; +}; + +export type ObjectRawResponseReturn = { + type: "response"; + path: string; + r: (r: Request) => Response | Promise; + method?: ParamsMethod; +}; + +export type ObjectRawResponseStatic = { + type: "static"; + name: string; + path: string; +} | { + type: "static"; + name: string; + path: string; + mime?: true; + extra: [string, string][]; +} | { + type: "static"; + name: string; + path: string; + mime: false; +} ; + +export type defaultMime = + | ".aac" + | ".abw" + | ".arc" + | ".avif" + | ".avi" + | ".azw" + | ".azw" + | ".bmp" + | ".bz" + | ".bz2" + | ".cda" + | ".csh" + | ".css" + | ".csv" + | ".doc" + | ".docx" + | ".eot" + | ".epub" + | ".gz" + | ".gif" + | ".htm" + | ".html" + | ".ico" + | ".ics" + | ".jar" + | ".jpeg" + | ".js" + | ".json" + | ".jsonld" + | ".mid" + | ".mjs" + | ".mp3" + | ".mp4" + | ".mpeg" + | ".mpkg" + | ".odp" + | ".ods" + | ".odt" + | ".oga" + | ".ogv" + | ".ogx" + | ".opus" + | ".otf" + | ".png" + | ".pdf" + | ".php" + | ".ppt" + | ".pptx" + | ".rar" + | ".rtf" + | ".sh" + | ".svg" + | ".tar" + | ".tif" + | ".tiff" + | ".ts" + | ".ttf" + | ".txt" + | ".vsd" + | ".wav" + | ".weba" + | ".webm" + | ".webp" + | ".woff" + | ".woff2" + | ".xhtml" + | ".xls" + | ".xlsx" + | ".xml" + | ".xul" + | ".zip" + | ".3gp" + | ".3g2" + | ".7z"; diff --git a/test/builder/arraySwap.test.ts b/test/builder/arraySwap.test.ts new file mode 100644 index 0000000..b5ac050 --- /dev/null +++ b/test/builder/arraySwap.test.ts @@ -0,0 +1,80 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import arraySwap from "../../builder/arraySwap.ts"; +import paths from "../util/paths.ts"; +import optimize from "../../optimizer/optimize.ts"; +import { funRouterOptions } from "../../types.ts"; + +Deno.test( + "arraySwap", + (_) => + assertEquals( + arraySwap()(optimize()(paths))[0].map((x) => [x[0], x[1], x[2]]), + [ + [1, "", "GET"], + [1, "test", "GET"], + [2, "test/", "GET"], + [4, "test/", "GET"], + [1, "", "POST"], + [1, "", "HEAD"], + [1, "", "DELETE"], + ], + ), +); + +Deno.test( + "arraySwap", + (_) => + assertEquals( + arraySwap({ + paramsStartsWith: "!", + })(optimize()(paths))[0].map((x) => [x[0], x[1], x[2]]), + [ + [1, "", "GET"], + [1, "test", "GET"], + [2, "test/", "GET"], + [4, "test/:id/:name/", "GET"], + [1, "", "POST"], + [1, "", "HEAD"], + [1, "", "DELETE"], + ], + ), +); +Deno.test( + "arraySwap", + ((o: funRouterOptions) => (_) => + assertEquals( + arraySwap(o)(optimize(o)(paths))[0].map((x) => [x[0], x[1], x[2]]), + [ + [0, "", "GET"], + [1, "test", "GET"], + [2, "test/", "GET"], + [4, "test/", "GET"], + [0, "", "POST"], + [0, "", "HEAD"], + [0, "", "DELETE"], + ], + ))( + { + globalNotFound: true, + }, + ), +); +Deno.test( + "arraySwap", + (_) => + assertEquals( + arraySwap({ + paramsStartsWith: "!", + globalNotFound: true, + })(optimize()(paths))[0].map((x) => [x[0], x[1], x[2]]), + [ + [0, "", "GET"], + [1, "test", "GET"], + [2, "test/", "GET"], + [4, "test/:id/:name/", "GET"], + [0, "", "POST"], + [0, "", "HEAD"], + [0, "", "DELETE"], + ], + ), +); diff --git a/test/builder/atlas.test.ts b/test/builder/atlas.test.ts new file mode 100644 index 0000000..6465758 --- /dev/null +++ b/test/builder/atlas.test.ts @@ -0,0 +1,110 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import arraySwap from "../../builder/arraySwap.ts"; +import atlas from "../../builder/atlas.ts"; +import optimize from "../../optimizer/optimize.ts"; +import paths from "../util/paths.ts"; + +Deno.test( + "Atlas", + (_) => + assertEquals( + ((r) => [r[0], r[1], r[2]])( + atlas()(arraySwap()(optimize()(paths))), + ), + [ + [ + "GET", + "POST", + "HEAD", + "DELETE", + ], + [ + [ + 1, + 2, + 4, + ], + [ + 1, + ], + [ + 1, + ], + [ + 1, + ], + ], + [ + [ + ["test", ""], + ["test/"], + ["test/"], + ], + [ + [""], + ], + [ + [""], + ], + [ + [""], + ], + ], + ], + ), +); + +// Deno.test( +// "Atlas", +// (_) => +// assertEquals( +// ((r) => [r[1], r[2]])( +// atlas()( +// arraySwap()( +// optimize()([ +// { +// type: "request", +// path: "/", +// r: (f) => new Response("hello world"), +// }, +// { +// type: "params", +// path: "/test/:id", +// param: { +// elements: ["id"], +// }, +// r: (f) => f.param.id, +// }, +// { +// type: "query", +// path: "/test", +// r: (f) => (f.query?.hello || ""), +// }, +// { +// type: "paramsAndQuery", +// path: "/test/both/:id", +// param: { +// elements: ["id"], +// }, +// r: (f) => f.param.id + " " + (f.query?.hello || ""), +// }, +// { +// type: "paramsAndQuery", +// path: "/test/mul/:a/:b/:c", +// param: { +// elements: ["b"], +// }, +// r: (f) => f.param.b + " " + (f.query?.e || ""), +// }, +// { +// type: "query", +// path: "/q", +// r: (f) => (f.query?.e || ""), +// }, +// ]), +// ), +// ), +// ), +// null, +// ), +// ); diff --git a/test/builder/resolver.test.ts b/test/builder/resolver.test.ts new file mode 100644 index 0000000..8f45030 --- /dev/null +++ b/test/builder/resolver.test.ts @@ -0,0 +1,112 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import resolver from "../../builder/resolver.ts"; +import atlas from "../../builder/atlas.ts"; +import paths from "../util/paths.ts"; +import arraySwap from "../../builder/arraySwap.ts"; +import optimize from "../../optimizer/optimize.ts"; + +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(0)("notFound/"), + 1, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(-1)("notFound/"), + 8, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); + +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(0)( + "http://localhost:8080/test", + ), + 0, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); + + +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(0)( + "http://localhost:8080/", + ), + 1, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(0)( + "http://localhost:8080/test/", + ), + 2, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); + +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(0)( + "http://localhost:8080/test/id/num/", + ), + 3, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); +Deno.test( + "Resolver", + (_) => + ( + (a) => + assertEquals( + resolver()(a[3].length)(a[1])(a[2])(a[4])(3)( + "http://localhost:8080/", + ), + 6, + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); diff --git a/test/builder/sComposer.test.ts b/test/builder/sComposer.test.ts new file mode 100644 index 0000000..b06e4c8 --- /dev/null +++ b/test/builder/sComposer.test.ts @@ -0,0 +1,2 @@ +// import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +// import sComposer from "../../builder/sComposer.ts" diff --git a/test/builder/sResolver.test.ts b/test/builder/sResolver.test.ts new file mode 100644 index 0000000..2ba3d9f --- /dev/null +++ b/test/builder/sResolver.test.ts @@ -0,0 +1,19 @@ +import sResolver from "../../builder/sResolver.ts" +import url from "../util/url.ts" +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; + +Deno.test( + "hello", _ => + assertEquals( + sResolver(url)[0][0](""), + 1 + ) +) + +Deno.test( + "hello", _ => + assertEquals( + sResolver(url)[0][0]("test/"), + 0 + ) +) diff --git a/test/builder/stringParser.test.ts b/test/builder/stringParser.test.ts new file mode 100644 index 0000000..e57522f --- /dev/null +++ b/test/builder/stringParser.test.ts @@ -0,0 +1,166 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import stringParser from "../../builder/stringParser.ts"; +import arraySwap from "../../builder/arraySwap.ts"; +import atlas from "../../builder/atlas.ts"; +import optimize from "../../optimizer/optimize.ts"; +import paths from "../util/paths.ts"; +import { funRouterOptions } from "../../types.ts"; + +Deno.test( + "StringParser", + (_) => + ( + (r) => + assertEquals( + stringParser()(r[1])("http://localhost:8080/"), + [1, ""], + ) + )( + atlas()(arraySwap()(optimize()(paths))), + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser(o)(r[1])("http://localhost:8080/"), + [0, ""], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + { globalNotFound: true }, + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser(o)(r[1])("http://localhost:8080/"), + [0, ""], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + { globalNotFound: true, hasName: "http://localhost:8080/" }, + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser()(r[1])("http://localhost:8080/hello"), + [1, "hello"], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + { globalNotFound: true }, + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser()(r[1])("http://localhost:8080/hello"), + [1, "hello"], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + { globalNotFound: true, hasName: "http://localhost:8080/hello" }, + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser()(r[1])("http://localhost:8080/hello"), + [1, "hello"], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + {}, + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser(o)(r[1])("http://localhost:8080/test/id/num/"), + [4, "test/id/num/"], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + {}, + ), +); +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser(o)(r[1])("http://localhost:8080/test/id/num/"), + [4, "test/id/num/"], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + { globalNotFound: true }, + ), +); + +Deno.test( + "StringParser", + (_) => + ( + (o: funRouterOptions) => + ( + (r) => + assertEquals( + stringParser(o)(r[1])("http://localhost:8080/test/id/num/"), + [4, "test/id/num/"], + ) + )( + atlas(o)(arraySwap(o)(optimize(o)(paths))), + ) + )( + { globalNotFound: true, hasName: "http://localhost:8080/" }, + ), +); diff --git a/test/fun.test.ts b/test/fun.test.ts new file mode 100644 index 0000000..4f14378 --- /dev/null +++ b/test/fun.test.ts @@ -0,0 +1,146 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import paths from "./util/paths.ts"; +import fun from "../fun.ts"; + +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)(new Request("http://localhost:8080/")).text(), + "GET:main", + ), +); + +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)(new Request("http://localhost:8080/test")).text(), + "GET:test", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)(new Request("http://localhost:8080/test/")).text(), + "GET:test/", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)( + new Request("http://localhost:8080/", { method: "POST" }), + ).text(), + "POST:main", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)( + new Request("http://localhost:8080/", { method: "HEAD" }), + ).text(), + "HEAD:main", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)(new Request("http://localhost:8080/test/")).text(), + "GET:test/", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun({ + globalNotFound: true, + })(paths)(new Request("http://localhost:8080/notFound")).status, + 404, + ), +); + +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)( + new Request("http://localhost:8080/notFound", { method: "BAD_METHOD" }), + ).status, + 405, + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)(new Request("http://localhost:8080/notFound/")).status, + 404, + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)( + new Request("http://localhost:8080/", { method: "DELETE" }), + ).text(), + "DELETE:main", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun({ + globalNotFound: true, + })(paths)(new Request("http://localhost:8080/", { method: "DELETE" })) + .text(), + "DELETE:main", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun()(paths)(new Request("http://localhost:8080/test/1/2/")).text(), + "GET:test/:id/:name/", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun({ + globalNotFound: true, + })(paths)(new Request("http://localhost:8080/test/1/2/")).text(), + "GET:test/:id/:name/", + ), +); +Deno.test( + "main", + async (_) => + assertEquals( + await fun({ + hasName: "http://localhost:8080/", + })(paths)(new Request("http://localhost:8080/")).text(), + "GET:main", + ), +); + +Deno.test( + "main", + async (_) => + assertEquals( + await fun({ + hasName: "http://localhost:8080/", + })(paths)(new Request("http://localhost:8080/test/1/2/")).text(), + "GET:test/:id/:name/", + ), +); diff --git a/test/optimizer/aComposer.test.ts b/test/optimizer/aComposer.test.ts new file mode 100644 index 0000000..3822a33 --- /dev/null +++ b/test/optimizer/aComposer.test.ts @@ -0,0 +1,89 @@ +import aComposer from "../../optimizer/aComposer.ts"; +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; + +Deno.test( + "Query", + (_) => + assertEquals( + aComposer({ hasName: "http://localhost:8080/" })({ + param: { elements: ["id"] }, + path: "/test", + f: (r) => r.query.hello || "nothing", + })(["query"])(new Request("http://localhost:8080/test?hello=hi")).query + .hello, + "hi", + ), +); +Deno.test( + "Query", + (_) => + assertEquals( + aComposer()({ + path: "/test", + param: { elements: ["id"] }, + f: (r) => r.query.hello || "nothing", + })(["query"])(new Request("http://localhost:8080/test?hello=hi")).query + .hello, + "hi", + ), +); +Deno.test( + "Query", + (_) => + assertEquals( + aComposer({ hasName: "http://localhost:8080/" })({ + param: { elements: ["id"] }, + path: "/test", + f: (r) => r.query.hello || "nothing", + })(["query", "req"])(new Request("http://localhost:8080/test?hello=hi")) + .query.hello, + "hi", + ), +); +Deno.test( + "Params", + (_) => + assertEquals( + aComposer({ hasName: "http://localhost:8080/" })({ + path: "/test/:id", + param: { elements: ["id"] }, + f: (r) => r.param.id, + })(["param"])(new Request("http://localhost:8080/test/1")).param.id, + "1", + ), +); +Deno.test( + "Params", + (_) => + assertEquals( + aComposer({ hasName: "http://localhost:8080/" })({ + path: "/test/:id/", + param: { elements: ["id"] }, + f: (r) => r.param.id, + })(["param"])(new Request("http://localhost:8080/test/1/")).param.id, + "1", + ), +); +Deno.test( + "Params", + (_) => + assertEquals( + aComposer({ hasName: "http://localhost:8080/" })({ + path: "/test/:a/:b/:c/", + param: { elements: ["a", "b", "c"] }, + f: (r) => r.param.id, + })(["param"])(new Request("http://localhost:8080/test/1/2/3/")).param.b, + "2", + ), +); +Deno.test( + "Params", + (_) => + assertEquals( + aComposer()({ + path: "/test/:a/:b/:c/", + f: (r) => r.param.id.toString(), + })(["param"])(new Request("http://localhost:8080/test/1/2/3/")).param.b, + "2", + ), +); diff --git a/test/optimizer/cheker.test.ts b/test/optimizer/cheker.test.ts new file mode 100644 index 0000000..549ce0b --- /dev/null +++ b/test/optimizer/cheker.test.ts @@ -0,0 +1,11 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import checker from "../../optimizer/checker.ts"; + +Deno.test( + "Params", + (_) => + assertEquals( + checker([])(["param"])([])("r=> r. param "), + ["param"], + ), +); diff --git a/test/optimizer/optimize1.test.ts b/test/optimizer/optimize1.test.ts new file mode 100644 index 0000000..7a6ace9 --- /dev/null +++ b/test/optimizer/optimize1.test.ts @@ -0,0 +1,51 @@ +// import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +// import optimizer from "../../optimizer/optimize1.ts"; + +// Deno.test( +// "Params", +// (_) => +// assertEquals( +// optimizer({})([ +// { type: "response", path: "/", r: (_) => new Response("hello world") }, +// { +// type: "response", +// path: "/a/b/c/d/e/f/g/", +// r: (_) => new Response("hello world2"), +// }, +// { + +// path: "/test/:id", +// f: (f) => f.param.id, +// }, +// { + +// path: "/test", +// f: (f) => (f.query?.hello || ""), +// }, +// { + +// path: "/test/both/:id", + +// f: (f) => f.param.id + " " + (f.query?.hello || ""), +// }, +// { + +// path: "/test/mul/:a/:b/:c", + +// f: (f) => f.param.b, +// }, +// { + +// path: "/test/mul2/:a/:b/:c", + +// f: (f) => f.param.b + " " + (f.query?.e || ""), +// }, +// { + +// path: "/q", +// f: (f) => (f.query?.e || ""), +// }, +// ]), +// null +// ), +// ); diff --git a/test/optimizer/response1.test.ts b/test/optimizer/response1.test.ts new file mode 100644 index 0000000..a6cd1ce --- /dev/null +++ b/test/optimizer/response1.test.ts @@ -0,0 +1,37 @@ +import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +import response from "../../optimizer/response1.ts"; + +// Deno.test( +// "Response response", +// async (_) => +// assertEquals( +// await (await (response()({ path: "/", f: (_) => "got it" })( +// new Request("http://localhost:8080/"), +// ))).text(), +// "got it", +// ), +// ); + +Deno.test( + "Response response", + async (_) => + assertEquals( + await (await (response()({ + path: "/hello/:id", + f: (f) => JSON.stringify(f.param), + })(new Request("http://localhost:8080/hello/hello")))).text(), + `{"id":"hello"}`, + ), +); + +// Deno.test( +// "Response response", +// async (_) => +// assertEquals( +// await (await (response()({ +// path: "/hello/:id", +// r: (f) => JSON.stringify({...f.param,...f?.query}), +// })(new Request("http://localhost:8080/hello/hello?ho=hi")))).text(), +// `{"id":"hello"}`, +// ), +// ); diff --git a/test/optimizer/static.test.ts b/test/optimizer/static.test.ts new file mode 100644 index 0000000..8feca71 --- /dev/null +++ b/test/optimizer/static.test.ts @@ -0,0 +1,19 @@ +// import { assertEquals } from "https://deno.land/std@0.160.0/testing/asserts.ts"; +// import staticFiles from "../../optimizer/staticFiles.ts"; + +// Deno.test( +// "staticFiles", +// async (_) => +// assertEquals( +// JSON.parse( +// await (staticFiles({})({ type: "static", name: "/s", path: "./" })( +// new Request("http://localhost:8080/s/people.json"), +// )) as unknown as string, +// ), +// [ +// { "id": 1, "name": "John", "age": 23 }, +// { "id": 2, "name": "Sandra", "age": 51 }, +// { "id": 5, "name": "Devika", "age": 11 }, +// ], +// ), +// ); diff --git a/test/util/paths.ts b/test/util/paths.ts new file mode 100644 index 0000000..39b6afc --- /dev/null +++ b/test/util/paths.ts @@ -0,0 +1,29 @@ +import { ObjectRawResponse } from "../../optimizer/types.ts"; +export default [ + { type: "response", path: "/", r: (_) => new Response("GET:main") }, + { type: "response", path: "/test", r: (_) => new Response("GET:test") }, + { type: "response", path: "/test/", r: (_) => new Response("GET:test/") }, + { + type: "response", + path: "/test/:id/:name/", + r: (_) => new Response("GET:test/:id/:name/"), + }, + { + type: "response", + method: "POST", + path: "/", + r: (_) => new Response("POST:main"), + }, + { + type: "response", + method: "HEAD", + path: "/", + r: (_) => new Response("HEAD:main"), + }, + { + type: "response", + method: "DELETE", + path: "/", + r: (_) => new Response("DELETE:main"), + }, +] as ObjectRawResponse[]; diff --git a/test/util/url.ts b/test/util/url.ts new file mode 100644 index 0000000..3434f73 --- /dev/null +++ b/test/util/url.ts @@ -0,0 +1,16 @@ +export default [ + [ + ["test", ""], + ["test/"], + ["test/"], + ], + [ + [""], + ], + [ + [""], + ], + [ + [""], + ], + ] as string [][][] \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..191bc3b --- /dev/null +++ b/types.ts @@ -0,0 +1,26 @@ +/// +export type ParamsMethod = "GET" | "HEAD" | "POST" | "DELETE"; + +export type funRouterOptions = { + hasName?: string; +/** + * @deprecated The method should not be used + */ + globalNotFound?: true; + paramsStartsWith?: string; + notFound?: { (x: Request): Response }; + badMethod?: { (x: Request): Response }; +}; + +export type ArrayFiler = [ArraySwap[], RouteTypes[]]; +export type ArraySwap = [number, string, ParamsMethod, RequestFunction]; +export type RequestUrl = [string[], string]; +export type RequestFunction = { (r: Request): Response }; +export type RouteTypes = [ + ParamsMethod, + string, + RequestFunction, + string | false, +]; +export type SearchIn = [number, string[], RequestFunction]; +export type OptimizeList = [ParamsMethod[], SearchIn[][]];