From 2e1dadbdeaa33b63aa2a0ee947eaf32d848315cd Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Sat, 2 Nov 2024 12:44:54 +0000 Subject: [PATCH] benchmark nonsense --- benchmark/compiler/compiler.mjs | 116 +++ benchmark/compiler/utils.mjs | 39 + benchmark/const.js | 30 +- benchmark/ifs.mjs | 148 ++++ benchmark/ifs_compile.mjs | 85 ++ benchmark/map_query.mjs | 106 +++ benchmark/mapl_compiler.mjs | 25 + benchmark/mapl_router_compile.mjs | 31 + benchmark/package-lock.json | 1320 ----------------------------- benchmark/package.json | 4 +- benchmark/promise.mjs | 221 +++++ benchmark/router_flaska_v2.mjs | 88 ++ benchmark/router_v2.mjs | 517 +++++++++++ benchmark/router_v2_compile.mjs | 16 + benchmark/string.js | 80 ++ benchmark/strings.mjs | 121 +++ benchmark/strings_compare.mjs | 115 +++ benchmark/strings_v2.mjs | 44 + benchmark/test.mjs | 33 + benchmark/utils.mjs | 15 + flaska.mjs | 2 +- router_v2.mjs | 242 ++++++ 22 files changed, 2074 insertions(+), 1324 deletions(-) create mode 100644 benchmark/compiler/compiler.mjs create mode 100644 benchmark/compiler/utils.mjs create mode 100644 benchmark/ifs.mjs create mode 100644 benchmark/ifs_compile.mjs create mode 100644 benchmark/map_query.mjs create mode 100644 benchmark/mapl_compiler.mjs create mode 100644 benchmark/mapl_router_compile.mjs delete mode 100644 benchmark/package-lock.json create mode 100644 benchmark/promise.mjs create mode 100644 benchmark/router_flaska_v2.mjs create mode 100644 benchmark/router_v2.mjs create mode 100644 benchmark/router_v2_compile.mjs create mode 100644 benchmark/string.js create mode 100644 benchmark/strings.mjs create mode 100644 benchmark/strings_compare.mjs create mode 100644 benchmark/strings_v2.mjs create mode 100644 benchmark/test.mjs create mode 100644 benchmark/utils.mjs create mode 100644 router_v2.mjs diff --git a/benchmark/compiler/compiler.mjs b/benchmark/compiler/compiler.mjs new file mode 100644 index 0000000..185c41c --- /dev/null +++ b/benchmark/compiler/compiler.mjs @@ -0,0 +1,116 @@ +import { printTree } from "./utils.mjs" + +/** + * Child { + * char: 'a', + * children: [Child, Child], + * path: null / 'full/path', + * count: 0 + * } + */ +function Child(char) { + this.char = char + this.children = [] + this.path = null + this.count = 0 +} + +function buildChild(i, arr) { + let letter = new Child(arr[0][i]) + let consume = [] + if (arr[0].length === i + 1) { + letter.path = arr[0] + letter.count += 1 + } else { + consume = [arr[0]] + } + + for (let y = 1; y < arr.length; y++) { + if (arr[y][i] !== letter.char) break + consume.push(arr[y]) + } + + letter.count += consume.length + while (consume.length) { + letter.children.push(buildChild(i + 1, consume)) + consume.splice(0, letter.children[letter.children.length - 1].count) + } + return letter +} + +export function buildTree(all) { + let paths = Array.from(new Set(all)).sort() + let builder = [] + while (paths.length) { + builder.push(buildChild(0, paths)) + paths.splice(0, builder[builder.length - 1].count) + } + printTree(builder) + return builder +} + +function IfTreeBranch(branches, indent = 0) { + let output = '' + let indentation = ''.padStart(indent * 2) + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + output += `${i > 0 ? 'else ': ''}if (str.charCodeAt(${indent}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (str.length === ${branch.path.length}) {` + output += '\n' + indentation + ` return "${branch.path}"` + output += '\n' + indentation + ` }` + } + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += IfTreeBranch(branch.children, indent + 1) + } + output += '\n' + indentation + '} ' + } + return output +} + +export function compileTreeIntoIfs(tree) { + let output = IfTreeBranch(tree) + output += '\nreturn null' + return new Function('str', output) +} + + +function IfTreeBranchBuffer(branches, indent = 0) { + let output = '' + let indentation = ''.padStart(indent * 2) + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + output += `${i > 0 ? 'else ': ''}if (uint[${indent}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (uint.length === ${branch.path.length}) {` + output += '\n' + indentation + ` return "${branch.path}"` + output += '\n' + indentation + ` }` + } + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += IfTreeBranchBuffer(branch.children, indent + 1) + } + output += '\n' + indentation + '} ' + } + return output +} + +export function compileTreeIntoIfsWithBuffer(tree) { + let output = `var uint = Buffer.from(str)\n` + output += IfTreeBranchBuffer(tree) + output += '\nreturn null' + return new Function('str', output) +} diff --git a/benchmark/compiler/utils.mjs b/benchmark/compiler/utils.mjs new file mode 100644 index 0000000..8ac58d8 --- /dev/null +++ b/benchmark/compiler/utils.mjs @@ -0,0 +1,39 @@ +export function printTree(children, indent = 0) { + if (!children.length) return + + for (let child of children) { + console.log(child.char.padStart(1 + indent * 2)) + printTree(child.children, indent + 1) + } +} + +export function printIfNotEightyOne(fn) { + let opt = %GetOptimizationStatus(fn) + if (opt === 81) return + printCurrentStatus() +} + +export function printCurrentStatus(fn) { + let opt = %GetOptimizationStatus(fn) + console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt})`) +} +export function printStatusHelperText() { + console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized +│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized +│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan +│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted +│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization +│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization +│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently +│ │ │ │ │ │ └─────────────────────╸ is executing +│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned +│ │ │ │ └─────────────────────────╸ lite mode +│ │ │ └───────────────────────────╸ marked for deoptimization +│ │ └─────────────────────────────╸ baseline +│ └───────────────────────────────╸ topmost frame is interpreted +└─────────────────────────────────╸ topmost frame is baseline`) +} \ No newline at end of file diff --git a/benchmark/const.js b/benchmark/const.js index 06873e4..1855177 100644 --- a/benchmark/const.js +++ b/benchmark/const.js @@ -6,6 +6,15 @@ export const overrideDummy = function(override) { export const dummy = function() { callItem() } +export const testRoutes = [ + // '/api/articles/:id/file/:fileId', + '/', + // '/api/articles', + // '/api/articles/:id/file', + // '/api/articles/:id', + '/::rest', +] + export const allRoutes = [ '/', '/api/articles', @@ -23,6 +32,22 @@ export const allRoutes = [ '/api/pages/:id/articles/public', '/api/staff', '/api/staff/:id', + '/::rest', +] + +export const allRoutesGoldenBalance = [ + '/', + '/a/:a/aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/:aa', + '/b/:b/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/:bb', + '/c/:c/ccccccccccccccccccccccccccccc/:cc', + '/d/:d/ddddddddddddddddddddddddddddd/:dd', + '/e/:e/eeeeeeeeeeeeeeeeeeeeeeeeeeeee/:ee', + '/f/:f/fffffffffffffffffffffffffffff/:ff', + '/g/:g/ggggggggggggggggggggggggggggg/:gg', + '/h/:h/hhhhhhhhhhhhhhhhhhhhhhhhhhhhh/:hh', + '/i/:i/iiiiiiiiiiiiiiiiiiiiiiiiiiiii/:ii', + '/j/:j/jjjjjjjjjjjjjjjjjjjjjjjjjjjjj/:jj', + '/::rest', ] export const allManyRoutes = [ @@ -37,7 +62,7 @@ export const allManyRoutes = [ '/api/categories/:categoryId/properties', '/api/categories/:categoryId/values/:props', '/api/categories/:categoryId', - '/api/categories/:categoryId/products/:productId', + //'/api/categories/:categoryId/products/:productId', '/api/categories/:categoryId/products/:productId', '/api/customers', '/api/customers/:id', @@ -61,7 +86,7 @@ export const allManyRoutes = [ '/api/products/:id', '/api/products/:id/movement', '/api/products/:id/sub_products/:productId', - '/api/products/:id/sub_products/:productId', + //'/api/products/:id/sub_products/:productId', '/api/products/code/:code', '/api/products/property/:propertyId', '/api/properties', @@ -84,4 +109,5 @@ export const allManyRoutes = [ '/api/works/public', '/api/staff', '/api/staff/:id', + '/::rest', ] diff --git a/benchmark/ifs.mjs b/benchmark/ifs.mjs new file mode 100644 index 0000000..f5666c3 --- /dev/null +++ b/benchmark/ifs.mjs @@ -0,0 +1,148 @@ +import { summary, run, bench } from 'mitata'; + +function printCurrentStatus(fn) { + let opt = %GetOptimizationStatus(fn) + console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`) +} +function printStatusHelperText() { + console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized +│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized +│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan +│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted +│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization +│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization +│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently +│ │ │ │ │ │ └─────────────────────╸ is executing +│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned +│ │ │ │ └─────────────────────────╸ lite mode +│ │ │ └───────────────────────────╸ marked for deoptimization +│ │ └─────────────────────────────╸ baseline +│ └───────────────────────────────╸ topmost frame is interpreted +└─────────────────────────────────╸ topmost frame is baseline`) +} + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +function ifSingle(a) { + if (a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) { + return 100 + } + return 10 +} + +function ifChain(a) { + if (a[0] === 97) + if (a[1] === 97) + if (a[2] === 97) + if (a[3] === 97) + if (a.length === 4) { + return 100 + } + return 10 +} + +function ifSingleOpt(a) { + if (a.length >= 4 && a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) { + return 100 + } + return 10 +} + +function ifChainOpt(a) { + if (a.length >= 4) + if (a[0] === 97) + if (a[1] === 97) + if (a[2] === 97) + if (a[3] === 97) + if (a.length === 4) { + return 100 + } + return 10 +} + +function ifSingleOptAlt(a) { + if (a.length >= 4 && a[0] == 97 && a[1] == 97 && a[2] == 97 && a[3] == 97 && a.length == 4) { + return 100 + } + return 10 +} + +function ifChainOptAlt(a) { + if (a.length >= 4) + if (a[0] == 97) + if (a[1] == 97) + if (a[2] == 97) + if (a[3] == 97) + if (a.length == 4) { + return 100 + } + return 10 +} + +let paths = [ + [97, 97, 97, 97], + [97, 97, 97, 97, 97], + [97, 97, 96, 97], + [97, 96, 97, 97], + [96, 97, 97, 97], + [], + [98], + [97, 97, 97], + [97, 97], + [97], + [97, 97, 96], + [97, 96], + [96], +] + +let paths2 = [ + [97, 97, 97, 97], + [97, 97, 97, 97, 97], + [97, 97, 96, 97], + [97, 96, 97, 97], + [96, 97, 97, 97], + [97, 97, 97, 97], + [97, 97, 97, 97, 97], + [97, 97, 96, 97], + [97, 96, 97, 97], + [96, 97, 97, 97], + [97, 97, 97, 97, 97, 97], + [97, 97, 97, 97, 96, 97], + [97, 97, 97, 97, 97, 96], +] + +let func1 = [ifSingle, ifChain, ifSingleOpt, ifChainOpt, ifSingleOptAlt, ifChainOptAlt]; +for (let fun of func1) { + console.log('-- begin', fun.name) + for (var i = 0; i < 1000000; i++) { + paths.map(fun) + } + printCurrentStatus(fun); +} +printStatusHelperText() + + +summary(() => { + func1.forEach(function(fun) { + bench(fun.name + ' first', function() { + return paths.map(fun) + }) + }) + func1.forEach(function(fun) { + bench(fun.name + ' second', function() { + return paths2.map(fun) + }) + }) +}) +run().then(function() { + for (let fun of func1) { + printCurrentStatus(fun); + } + printStatusHelperText() +}); diff --git a/benchmark/ifs_compile.mjs b/benchmark/ifs_compile.mjs new file mode 100644 index 0000000..b6553a0 --- /dev/null +++ b/benchmark/ifs_compile.mjs @@ -0,0 +1,85 @@ +import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs"; + +function ifSingle(a) { + if (a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) { + return 100 + } + return 10 +} + +function ifChain(a) { + if (a[0] === 97) + if (a[1] === 97) + if (a[2] === 97) + if (a[3] === 97) + if (a.length === 4) { + return 100 + } + return 10 +} + +function ifSingleOptimized(a) { + if (a.length >= 4 && a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) { + return 100 + } + return 10 +} + +function ifChainOptimized(a) { + if (a.length >= 4) + if (a[0] === 97) + if (a[1] === 97) + if (a[2] === 97) + if (a[3] === 97) + if (a.length === 4) { + return 100 + } + return 10 +} + +function ifSingleOptimizedAlt(a) { + if (a.length >= 4 && a[0] == 97 && a[1] == 97 && a[2] == 97 && a[3] == 97 && a.length == 4) { + return 100 + } + return 10 +} + +function ifChainOptimizedAlt(a) { + if (a.length >= 4) + if (a[0] == 97) + if (a[1] == 97) + if (a[2] == 97) + if (a[3] == 97) + if (a.length == 4) { + return 100 + } + return 10 +} + +let paths = [ + [97, 97, 97, 97], + [97, 97, 97, 97, 97], + [97, 97, 96, 97], + [97, 96, 97, 97], + [96, 97, 97, 97], + [], + [98], + [97, 97, 97], + [97, 97], + [97], + [97, 97, 96], + [97, 96], + [96], +] + +let func1 = [ifSingle, ifChain, ifSingleOptimized, ifChainOptimized, ifSingleOptimizedAlt, ifChainOptimizedAlt]; +for (let fun of func1) { + console.log('-- begin', fun.name) + for (var i = 0; i < 1000000; i++) { + paths.map(x => fun(x)) + } + printCurrentStatus(fun); +} +printStatusHelperText() + + diff --git a/benchmark/map_query.mjs b/benchmark/map_query.mjs new file mode 100644 index 0000000..4a458f5 --- /dev/null +++ b/benchmark/map_query.mjs @@ -0,0 +1,106 @@ +import { summary, run, bench } from 'mitata'; + +function printCurrentStatus(fn) { + let opt = %GetOptimizationStatus(fn) + console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`) +} +function printStatusHelperText() { + console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized +│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized +│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan +│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted +│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization +│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization +│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently +│ │ │ │ │ │ └─────────────────────╸ is executing +│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned +│ │ │ │ └─────────────────────────╸ lite mode +│ │ │ └───────────────────────────╸ marked for deoptimization +│ │ └─────────────────────────────╸ baseline +│ └───────────────────────────────╸ topmost frame is interpreted +└─────────────────────────────────╸ topmost frame is baseline`) +} + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +function mapGetAndCheck(map, t) { + let x = map.get(t) + if (x) return x + return t +} + +function mapCheckBeforeGet(map, t) { + if (map.has(t)) { + return map.get(t) + } + return t +} + +let paths = [ + 'a', + 'aa', + 'aaa', + 'aaaa', + 'aaaaa', + 'aaaaaa', + 'aaaaaaa', + 'aaaaaaaa', +] +let tests = [ + 'a', + 'aa', + 'aaa', + 'aaaa', + 'aaaaa', + 'aaaaaa', + 'aaaaaaa', + 'aaaaaaaa', + 'b', + 'bb', + 'bbb', + 'bbbb', + 'bbbbb', + 'bbbbbb', + 'bbbbbbb', + 'bbbbbbbb', + 'c', + 'cc', + 'ccc', + 'cccc', + 'ccccc', + 'cccccc', + 'ccccccc', + 'cccccccc', +] + +let map = new Map(paths.map(x => [x, { a: x }])) + +let func1 = [mapGetAndCheck, mapCheckBeforeGet]; +for (let fun of func1) { + console.log('-- begin', fun.name) + for (var i = 0; i < 1000000; i++) { + tests.map(t => fun(map, t)) + } + printCurrentStatus(fun); +} +printStatusHelperText() + +summary(() => { + func1.forEach(function(fun) { + bench(fun.name, function() { + return paths.map(t => fun(map, t)) + }) + }) +}) +run().then(function() { + for (let fun of func1) { + printCurrentStatus(fun); + } + printStatusHelperText() +}); diff --git a/benchmark/mapl_compiler.mjs b/benchmark/mapl_compiler.mjs new file mode 100644 index 0000000..ec52204 --- /dev/null +++ b/benchmark/mapl_compiler.mjs @@ -0,0 +1,25 @@ +import { getExternalKeys } from '@mapl/compiler'; +import { compileRouter as compileRouterContent } from '@mapl/router'; +const PATH = "__req_p"; +const REQ = '__req'; +const PARAMS = `${REQ}_ps`; + +export function compileRouter(root) { + const state = { + contentBuilder: [], + declarationBuilders: [], + externalValues: [], + + compileItem: (item, state, hasParam) => { + state.contentBuilder.push(`return [f${state.externalValues.push(item)},${hasParam ? PARAMS : '[]'}];`); + } + }; + + compileRouterContent(root, state); + + // eslint-disable-next-line + return Function( + ...getExternalKeys(state), + `return (${PATH})=>{${state.contentBuilder.join('')}return null;}` + )(...state.externalValues); +} \ No newline at end of file diff --git a/benchmark/mapl_router_compile.mjs b/benchmark/mapl_router_compile.mjs new file mode 100644 index 0000000..d4b164c --- /dev/null +++ b/benchmark/mapl_router_compile.mjs @@ -0,0 +1,31 @@ +import { createRouter, insertItem } from '@mapl/router' +import { compileRouter } from './mapl_compiler.mjs' +import { printTime } from './utils.mjs' +import * as consts from './const.js' + +let testPaths = consts.allManyRoutes // consts.allManyRoutes + +/*testPaths = [ + '/api/works/:id/lock', + '/api/staff/:id', + // '/::rest', +]*/ + +testPaths = testPaths.map(x => x.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*')) + +const r = createRouter(); +for (let route of testPaths) { + let cleaned = route.replace(/:[^\/]+/g, '*') + insertItem(r, cleaned, () => { }) +} + +let s1 = process.hrtime.bigint() +let s2 = process.hrtime.bigint() + +compileRouter(r); + +let s3 = process.hrtime.bigint() + +let time = s3 - s2 - (s2 - s1) + +printTime(time) diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json deleted file mode 100644 index 6f70a73..0000000 --- a/benchmark/package-lock.json +++ /dev/null @@ -1,1320 +0,0 @@ -{ - "name": "benchmark", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "benchmark", - "version": "1.0.0", - "license": "WTFPL", - "dependencies": { - "benchmarkjs-pretty": "^2.0.0", - "express": "^4.17.1", - "koa-router": "^8.0.8" - } - }, - "node_modules/@types/benchmark": { - "version": "1.0.31", - "resolved": "https://registry.npmjs.org/@types/benchmark/-/benchmark-1.0.31.tgz", - "integrity": "sha512-F6fVNOkGEkSdo/19yWYOwVKGvzbTeWkR/XQYBKtGBQ9oGRjBN9f/L4aJI4sDcVPJO58Y1CJZN8va9V2BhrZapA==" - }, - "node_modules/@types/chalk": { - "version": "0.4.31", - "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz", - "integrity": "sha1-ox10JBprHtu5c8822XooloNKUfk=" - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/benchmark": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", - "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", - "dependencies": { - "lodash": "^4.17.4", - "platform": "^1.3.3" - } - }, - "node_modules/benchmarkjs-pretty": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/benchmarkjs-pretty/-/benchmarkjs-pretty-2.0.0.tgz", - "integrity": "sha512-t5a+ztdAuim1HPEbQwBELN9ugqe5WCSbjwPh79olqS+zgw44Bi/3qPz472LNPIfXlTernAo+meL8KULaXuWAeQ==", - "dependencies": { - "@types/benchmark": "^1.0.30", - "@types/chalk": "^0.4.31", - "benchmark": "^2.1.4", - "chalk": "^2.0.1" - } - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" - }, - "node_modules/koa-router": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-8.0.8.tgz", - "integrity": "sha512-2rNF2cgu/EWi/NV8GlBE5+H/QBoaof83X6Z0dULmalkbt7W610/lyP2EOLVqVrUUFfjsVWL/Ju5TVBcGJDY9XQ==", - "dependencies": { - "debug": "^4.1.1", - "http-errors": "^1.7.3", - "koa-compose": "^4.1.0", - "methods": "^1.1.2", - "path-to-regexp": "1.x", - "urijs": "^1.19.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/koa-router/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dependencies": { - "mime-db": "1.43.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" - }, - "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/urijs": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz", - "integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - } - }, - "dependencies": { - "@types/benchmark": { - "version": "1.0.31", - "resolved": "https://registry.npmjs.org/@types/benchmark/-/benchmark-1.0.31.tgz", - "integrity": "sha512-F6fVNOkGEkSdo/19yWYOwVKGvzbTeWkR/XQYBKtGBQ9oGRjBN9f/L4aJI4sDcVPJO58Y1CJZN8va9V2BhrZapA==" - }, - "@types/chalk": { - "version": "0.4.31", - "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz", - "integrity": "sha1-ox10JBprHtu5c8822XooloNKUfk=" - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "benchmark": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", - "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", - "requires": { - "lodash": "^4.17.4", - "platform": "^1.3.3" - } - }, - "benchmarkjs-pretty": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/benchmarkjs-pretty/-/benchmarkjs-pretty-2.0.0.tgz", - "integrity": "sha512-t5a+ztdAuim1HPEbQwBELN9ugqe5WCSbjwPh79olqS+zgw44Bi/3qPz472LNPIfXlTernAo+meL8KULaXuWAeQ==", - "requires": { - "@types/benchmark": "^1.0.30", - "@types/chalk": "^0.4.31", - "benchmark": "^2.1.4", - "chalk": "^2.0.1" - } - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" - }, - "koa-router": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-8.0.8.tgz", - "integrity": "sha512-2rNF2cgu/EWi/NV8GlBE5+H/QBoaof83X6Z0dULmalkbt7W610/lyP2EOLVqVrUUFfjsVWL/Ju5TVBcGJDY9XQ==", - "requires": { - "debug": "^4.1.1", - "http-errors": "^1.7.3", - "koa-compose": "^4.1.0", - "methods": "^1.1.2", - "path-to-regexp": "1.x", - "urijs": "^1.19.2" - }, - "dependencies": { - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - } - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "requires": { - "mime-db": "1.43.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" - }, - "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "urijs": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz", - "integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - } - } -} diff --git a/benchmark/package.json b/benchmark/package.json index 558f166..2c9089b 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -11,8 +11,10 @@ "author": "", "license": "WTFPL", "dependencies": { + "@mapl/router": "^0.0.16", "benchmarkjs-pretty": "^2.0.0", "express": "^4.17.1", - "koa-router": "^8.0.8" + "koa-router": "^8.0.8", + "mitata": "^1.0.10" } } diff --git a/benchmark/promise.mjs b/benchmark/promise.mjs new file mode 100644 index 0000000..99f585f --- /dev/null +++ b/benchmark/promise.mjs @@ -0,0 +1,221 @@ +import { summary, run, bench } from 'mitata'; + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +// Example benchmark +/*summary(() => { + const fn = () => null + const fnAlt = function() {} + function fnBasic() {} + const fnAltWithReturn = function() { return null } + function fnBasicWithReturn() { return null } + + bench('Reject with uncached error handler', async () => await Promise.reject().catch(() => null)); + bench('Reject with cached error handler', async () => await Promise.reject().catch(fn)); + + bench('TT Reject with uncached error handler', () => Promise.reject().catch(() => null)); + bench('TT Reject with cached error handler', () => Promise.reject().catch(fnAlt)); + bench('TT Reject with cached basic error handler', () => Promise.reject().catch(fnBasic)); + bench('TT Reject with cached error handler with return', () => Promise.reject().catch(fnAltWithReturn)); + bench('TT Reject with cached basic error handler with return ', () => Promise.reject().catch(fnBasicWithReturn)); +});*/ + +/* +summary(() => { + function triple () { + const bla = Math.round(Math.random()) ? true : null + return bla === null + } + function doubleNull () { + const bla = Math.round(Math.random()) ? true : null + return bla == null + } + function doubleUndefined () { + const bla = Math.round(Math.random()) ? true : undefined + return bla == null + } + bench('null === null', triple); + bench('null == null', doubleNull); + bench('undefined == null', doubleUndefined); + + bench('2x null === null', triple); + bench('2x null == null', doubleNull); + bench('2x undefined == null', doubleUndefined); +}) + + +summary(() => { + function triple () { + const bla = false ? true : null + return bla === null + } + function doubleNull () { + const bla = false ? true : null + return bla == null + } + function doubleUndefined () { + const bla = false ? true : undefined + return bla == null + } + bench('const null === null', triple); + bench('const null == null', doubleNull); + bench('const undefined == null', doubleUndefined); + + bench('const 2x null === null', triple); + bench('const 2x null == null', doubleNull); + bench('const 2x undefined == null', doubleUndefined); +})*/ + +function printCurrentStatus(fn) { + let opt = %GetOptimizationStatus(fn) + console.log(`${fn.toString()} +${opt.toString(2).padStart(12, '0').split('').join(' ')}`) +} +function printTree() { + console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ │ │ │ │ │ └─╸ is function +│ │ │ │ │ │ │ │ │ │ └───╸ is never optimized +│ │ │ │ │ │ │ │ │ └─────╸ is always optimized +│ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized +│ │ │ │ │ │ │ └─────────╸ is optimized +│ │ │ │ │ │ └───────────╸ is optimized by TurboFan +│ │ │ │ │ └─────────────╸ is interpreted +│ │ │ │ └───────────────╸ is marked for optimization +│ │ │ └─────────────────╸ is marked for concurrent optimization +│ │ └───────────────────╸ is optimizing concurrently +│ └─────────────────────╸ is executing +└───────────────────────╸ topmost frame is turbo fanned`) +} + +let func1 +let func2 +let func3 + +await (async function () { + return + const fn1 = async () => await Promise.reject().catch(() => null) === null ? 1 : 0; + const fn2 = async () => await Promise.reject().catch(() => {}) == null ? 1 : 0; + const fn3 = async () => await Promise.reject().catch(() => null) == null ? 1 : 0; + const noop = () => null; + const fn4 = async () => await Promise.reject().catch(noop) == null ? 1 : 0; + const fn5 = async () => await Promise.reject().catch(noop) === null ? 1 : 0; + + + const fn6 = () => Promise.reject().catch(() => null).then(x => x === null ? 1 : 0); + const fn7 = () => Promise.reject().catch(() => {}).then(x => x == null ? 1 : 0); + const fn8 = () => Promise.reject().catch(() => null).then(x => x == null ? 1 : 0); + const fn9 = () => Promise.reject().catch(noop).then(x => x == null ? 1 : 0); + const fn10 = () => Promise.reject().catch(noop).then(x => x === null ? 1 : 0); + + func1 = [fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10]; + for (let fun of func1) { + %OptimizeFunctionOnNextCall(fun); + } + for (var i = 0; i < 100000; i++) { + for (let fun of func1) { + await fun() + } + } + for (let fun of func1) { + printCurrentStatus(fun); + } + printTree() + + summary(() => { + bench('Null with ===', fn1); + bench('Undefined with ==', fn2); + bench('Null with ==', fn3); + bench('Cached Null with ==', fn4); + bench('Cached Null with ===', fn5); + + + bench('[sync] Null with ===', fn6); + bench('[sync] Undefined with ==', fn7); + bench('[sync] Null with ==', fn8); + bench('[sync] Cached Null with ==', fn9); + bench('[sync] Cached Null with ===', fn10); + }); +}) + + +await (async function () { + const alt1 = function () { (Math.round(Math.random()) ? null : 1) ?? 0 }; + const alt2 = function () { (Math.round(Math.random()) ? null : 1) || 0 }; + const alt3 = function () { (Math.round(Math.random()) ? undefined : 1) ?? 0 }; + const alt4 = function () { (Math.round(Math.random()) ? undefined : 1) || 0 }; + + let check = new Array(100).fill(0).map(() => Math.round(Math.random()) ? null : 1) + let check2 = new Array(100).fill(0).map(() => Math.round(Math.random()) ? undefined : 1) + + const alt5 = function () { let out = 0; for (let x of check) { out += x ?? 0 } return out }; + const alt6 = function () { let out = 0; for (let x of check) { out += x || 0 } return out }; + const alt7 = function () { let out = 0; for (let x of check2) { out += x ?? 0 } return out }; + const alt8 = function () { let out = 0; for (let x of check2) { out += x || 0 } return out }; + + func2 = [alt1, alt2, alt3, alt4, alt5, alt6, alt7, alt8]; + for (let fun of func2) { + %OptimizeFunctionOnNextCall(fun); + } + for (var i = 0; i < 100000; i++) { + for (let fun of func2) { + fun() + } + } + for (let fun of func2) { + printCurrentStatus(fun); + } + printTree() + + summary(() => { + bench('null/1 ?? 0', alt1); + bench('null/1 || 0', alt2); + bench('undefined/1 ?? 0', alt3); + bench('undefined/1 || 0', alt4); + }); + + summary(() => { + bench('arr null/1 ?? null', alt5); + bench('arr null/1 || null', alt6); + bench('arr und/1 ?? null', alt7); + bench('arr und/1 || null', alt8); + }); +})() + + +await (async function () { + const alt1 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? null : 1) ?? 0 } return out }; + const alt2 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? null : 1) || 0 } return out }; + const alt3 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? undefined : 1) ?? 0 } return out }; + const alt4 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? undefined : 1) || 0 } return out }; + + func3 = [alt1, alt2, alt3, alt4]; + for (let fun of func3) { + %OptimizeFunctionOnNextCall(fun); + } + for (var i = 0; i < 100000; i++) { + for (let fun of func3) { + fun() + } + } + for (let fun of func3) { + printCurrentStatus(fun); + } + printTree() + + summary(() => { + bench('loop null/1 ?? 0', alt1); + bench('loop null/1 || 0', alt2); + bench('loop und/1 ?? 0', alt3); + bench('loop und/1 || 0', alt4); + }); +})() + +// Start the benchmark +run().then(() => { + for (let fun of func3) { + printCurrentStatus(fun); + } + printTree() +}); \ No newline at end of file diff --git a/benchmark/router_flaska_v2.mjs b/benchmark/router_flaska_v2.mjs new file mode 100644 index 0000000..7c5a805 --- /dev/null +++ b/benchmark/router_flaska_v2.mjs @@ -0,0 +1,88 @@ +import { summary, run, bench } from 'mitata'; +import { createRouter, insertItem } from '@mapl/router' +import { compileRouter } from './mapl_compiler.mjs' +import assert from 'assert' +import { compilePaths } from "./router_v2.mjs" +import { compilePaths as mainCompiler, compilePathsClosure } from "../router_v2.mjs" +import * as consts from './const.js' + +function printCurrentStatus(fn) { + // console.log(`--- checking optimizations status on ${fn.name} ---`) + // let opt = %GetOptimizationStatus(fn) + // console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`) +} +function printStatusHelperText() { + console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function +│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized +│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized +│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized +│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan +│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted +│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization +│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization +│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently +│ │ │ │ │ │ └─────────────────────╸ is executing +│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned +│ │ │ │ └─────────────────────────╸ lite mode +│ │ │ └───────────────────────────╸ marked for deoptimization +│ │ └─────────────────────────────╸ baseline +│ └───────────────────────────────╸ topmost frame is interpreted +└─────────────────────────────────╸ topmost frame is baseline`) +} + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +let paths = consts.allManyRoutes.map(x => ({ path: x })) +let tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p])) +let testStrings = tests.map(x => x[0]) +let testStringsMapl = testStrings.map(x => x.slice(1)) + +let func = [[testStrings, ...mainCompiler(paths)]] +func.push([testStrings, ...compilePathsClosure(paths)]) + +let maplPaths = paths.map(x => x.path.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*')) +const maplRouter = createRouter(); +for (let route of maplPaths) { + insertItem(maplRouter, route, { path: route }) +} +let maplMatcher = compileRouter(maplRouter) + +func.push([testStringsMapl, maplMatcher, maplMatcher]) + +for (let [tests, _, fun] of func) { + console.log(`--- warming up ${fun.name || 'mapl'} ---`) + for (var i = 0; i < 10000; i++) { + tests.forEach(fun) + } +} +for (let [tests, _, fun] of func) { + console.log(`--- Sanity checking ${fun.name || 'mapl'} ---`) + for (let a of tests) { + // console.log(a, fun(a)) + } +} +console.log('--- sleeping ---') +await new Promise(res => setTimeout(res, 1000)) +for (let [_, org] of func) { + printCurrentStatus(org); +} +printStatusHelperText() + +summary(() => { + func.forEach(function([tests, _, fun]) { + // console.log(tests, fun, tests.map(fun)) + bench((fun.name || 'bound mapl').slice(6), function() { + return tests.map(fun) + }) + }) +}) +run().then(function() { + for (let [_, check] of func) { + printCurrentStatus(check); + } + printStatusHelperText() +}); diff --git a/benchmark/router_v2.mjs b/benchmark/router_v2.mjs new file mode 100644 index 0000000..a1384ad --- /dev/null +++ b/benchmark/router_v2.mjs @@ -0,0 +1,517 @@ +const Debug = false + +export function printTree(children, indent = 0) { + if (!children.length || !Debug) return + + for (let child of children) { + console.log(''.padStart(indent * 2) + (child.char || '*') + + ' ' + (child.isParams ? `{ params = ${child.isParams} }` : '') + + (child.isFullParams ? `{ fullParams = ${child.isFullParams} }` : '') + + ' ' + (child.path ? `{ handler = ${child.path.path} }` : '')) + printTree(child.children, indent + 1) + } +} + +class RouterError extends Error { + constructor(route1, route2, ...params) { + // Pass remaining arguments (including vendor specific ones) to parent constructor + super(...params); + + // Maintains proper stack trace for where our error was thrown (only available on V8) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, RouterError); + } + + this.name = "RouterError"; + this.routeA = route1 + this.routeB = route2 + } +} + +function Child(split, x, i) { + this.path = null + this.isParams = split[x].isParams ? split[x].word : null + this.isFullParams = split[x].isFullParams ? split[x].word : null + this.paramVarName = split[x].paramVarName ?? null + this.char = !this.isParams && !this.isFullParams ? split[x].word[i] || '/' : null + this.count = 0 + this.children = [] +} + +function buildChild(x, i, splitPaths) { + let splitPath = splitPaths[0] + let letter = new Child(splitPath.split, x, i) + + let consume = [] + if (splitPath.split.length === x + 1 + && (splitPath.split[x].isParams + || splitPath.split[x].isFullParams + || splitPath.split[x].word.length === i + 1)) { + letter.path = splitPath.entry + letter.count += 1 + } else { + consume = [splitPath] + } + + for (let y = 1; y < splitPaths.length; y++) { + let checkPath = splitPaths[y] + if (!checkPath.split[x] + || checkPath.split[x].isParams !== splitPath.split[x].isParams + || checkPath.split[x].isFullParams !== splitPath.split[x].isFullParams + || !checkPath.split[x].isParams + && !checkPath.split[x].isFullParams + && (checkPath.split[x].word[i] || '/') !== letter.char) break + consume.push(checkPath) + } + + letter.count += consume.length + if (splitPath.split[x].word.length === i || splitPath.split[x].isParams || splitPath.split[x].isFullParams) { + x++ + i = -1 + } + while (consume.length) { + letter.children.push(buildChild(x, i + 1, consume)) + consume.splice(0, letter.children[letter.children.length - 1].count) + } + return letter +} + +export function buildTree(splitPaths) { + let builder = [] + while (splitPaths.length) { + builder.push(buildChild(0, 0, splitPaths)) + splitPaths.splice(0, builder[builder.length - 1].count) + } + return builder +} + +const regParamPrefix = /^::?/ +const regCleanNonAschii = /(?![a-zA-Z_])./g +const regCleanRest = /_+/g + +function splitAndSortPaths(paths, separateStatic = true) { + let staticPaths = new Map() + let paramsPaths = [] + let collator = new Intl.Collator('en', { sensitivity: 'accent' }); + + paths.forEach(function(entry) { + if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start') + + // Collect static paths separately + if (entry.path.indexOf('/:') < 0 && separateStatic) { + return staticPaths.set(entry.path, { + path: entry, + params: {} + }) + } + + // Collect params path separately + paramsPaths.push({ + split: entry.path.slice(1).split(/\//g).map(function(word) { + let actualWord = word.replace(regParamPrefix, '') + return { + word: actualWord, + isParams: word[0] === ':' && word[1] !== ':', + isFullParams: word[0] === ':' && word[1] === ':', + paramVarName: word[0] === ':' + ? actualWord.replace(regCleanNonAschii, '_').replace(regCleanRest, '_') + : null + } + }), + entry, + }) + }) + + paramsPaths.sort(function(aGroup, bGroup) { + let length = Math.max(aGroup.split.length, bGroup.split.length) + for (let x = 0; x < length; x++) { + let a = aGroup.split[x] + let b = bGroup.split[x] + if (!a) return -1 + if (!b) return 1 + // Full params go last + if (a.isFullParams && b.isFullParams) throw new RouterError(aGroup.entry, bGroup.entry, 'Two full path routes found on same level') + if (a.isFullParams) return 1 + if (b.isFullParams) return -1 + // Params go second last + if (a.isParams && !b.isParams) return 1 + if (!a.isParams && b.isParams) return -1 + // otherwise sort alphabetically if not identical + if (a.word !== b.word) return collator.compare(a.word, b.word) + } + throw new RouterError(aGroup, bGroup, 'Two identical paths were found') + }) + + return { + staticPaths, + paramsPaths, + } +} + +const SlashCode = '/'.charCodeAt(0) + +function getIndex(offset, additions, params) { + return (offset + additions) + + (params.length + ? ' + ' + params.map(a => `offset${a[1]}`).join(' + ') + : '') +} + +function treeIntoCompiledTreeBufferReturnPath(indentString, paths, branch, params, alt) { + let pathIndex = paths.indexOf(branch.path) + if (pathIndex < 0) { + throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths') + } + let output = '' + output += '\n' + indentString + `return {` + output += '\n' + indentString + ` path: paths[${pathIndex}],` + if (params.length) { + output += '\n' + indentString + ` params: {` + for (let param of params) { + if (alt === 0) { + output += '\n' + indentString + ` ${param[0]}: s${param[1]}.toString(),` + } else if (alt === 1 || alt === 3) { + output += '\n' + indentString + ` ${param[0]}: s${param[1]},` + } else if (alt === 2) { + output += '\n' + indentString + ` ${param[0]}: textDecoder.decode(s${param[1]}),` + } + } + output += '\n' + indentString + ` },` + } else { + output += '\n' + indentString + ` params: {},` + } + output += '\n' + indentString + `}` + return output +} + +function treeIntoCompiledTreeBufferBranch(paths, branches, indent = 0, params = [], alt = 0) { + let output = '' + let indentation = ''.padStart((indent - params.length) * 2) + let addEndBracket = true + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + if (i > 0) { + if (!branch.isParams && !branch.isFullParams) { + output += ' else ' + } else { + // output += '} //' + output += '\n' + indentation + } + } + + if (!branch.isParams && !branch.isFullParams) { + output += `if (buf[${getIndex(indent, 0, params)}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (buf.length === ${getIndex(indent, 1, params)}) {` + output += treeIntoCompiledTreeBufferReturnPath(indentation + ' ', paths, branch, params, alt) + output += '\n' + indentation + ` }` + } + } else { + addEndBracket = false + let paramVarName = (params.length + 1) + '_' + branch.paramVarName + if (alt === 1 || alt === 3) { + output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${getIndex(indent, 0, params)}) >>> 0`})` + } else { + output += `let s${paramVarName} = buf.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${getIndex(indent, 0, params)}) >>> 0`})` + } + output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length` + output += '\n' + indentation + params.push([branch.isParams || branch.isFullParams, paramVarName]) + + if (branch.isFullParams) { + output += treeIntoCompiledTreeBufferReturnPath(indentation, paths, branch, params, alt) + } else if (branch.path) { + output += '\n' + indentation + `if (buf.length === ${getIndex(indent, 0, params)}) {` + output += treeIntoCompiledTreeBufferReturnPath(indentation + ' ', paths, branch, params, alt) + output += '\n' + indentation + `}` + } + } + + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += treeIntoCompiledTreeBufferBranch(paths, branch.children, indent + 1, params.slice()) + } + if (addEndBracket) { + output += '\n' + indentation + '} ' + } + } + return output +} + +export function treeIntoCompiledTreeBuffer(paths, tree) { + let output = 'return function RBuffer(paths, static, str) {' + output += '\n let checkStatic = static.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n var buf = Buffer.from(str)' + output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1) + output += '\n return null' + output += '\n}' + if (Debug) { + console.log(output) + } + return new Function(output)() +} + + +function treeIntoCompiledTreeStringReturnPath(indentString, paths, branch, params) { + let pathIndex = paths.indexOf(branch.path) + if (pathIndex < 0) { + throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths') + } + let output = '' + output += '\n' + indentString + `return {` + output += '\n' + indentString + ` path: paths[${pathIndex}],` + if (params.length) { + output += '\n' + indentString + ` params: {` + for (let param of params) { + output += '\n' + indentString + ` ${param[0]}: s${param[1]},` + } + output += '\n' + indentString + ` },` + } else { + output += '\n' + indentString + ` params: {},` + } + output += '\n' + indentString + `}` + return output +} + +function treeIntoCompiledTreeStringBranch(paths, branches, indent = 0, params = []) { + let output = '' + let indentation = ''.padStart((indent - params.length) * 2) + let addEndBracket = true + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + if (i > 0) { + if (!branch.isParams && !branch.isFullParams) { + output += ' else ' + } else { + // output += '} //' + output += '\n' + indentation + } + } + + if (!branch.isParams && !branch.isFullParams) { + output += `if (str.charCodeAt(${getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (str.length === ${getIndex(indent, 1, params)}) {` + output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params) + output += '\n' + indentation + ` }` + } + } else { + addEndBracket = false + let paramVarName = (params.length + 1) + '_' + branch.paramVarName + output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${getIndex(indent, 0, params)}) >>> 0`})` + output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length` + output += '\n' + indentation + params.push([branch.isParams || branch.isFullParams, paramVarName]) + + if (branch.isFullParams) { + output += treeIntoCompiledTreeStringReturnPath(indentation, paths, branch, params) + } else if (branch.path) { + output += '\n' + indentation + `if (str.length === ${getIndex(indent, 0, params)}) {` + output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params) + output += '\n' + indentation + `}` + } + } + + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += treeIntoCompiledTreeStringBranch(paths, branch.children, indent + 1, params.slice()) + } + if (addEndBracket) { + output += '\n' + indentation + '} ' + } + } + return output +} + +function treeIntoCompiledTreeStringBranchIndices(paths, branches, indent = 0, params = []) { + let output = '' + let indentation = ''.padStart((indent - params.length) * 2) + let addEndBracket = true + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + if (i > 0) { + if (!branch.isParams && !branch.isFullParams) { + output += ' else ' + } else { + // output += '} //' + output += '\n' + indentation + } + } + + if (!branch.isParams && !branch.isFullParams) { + output += `if (str[${getIndex(indent, 0, params)}] === '${branch.char}') { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (str.length === ${getIndex(indent, 1, params)}) {` + output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params) + output += '\n' + indentation + ` }` + } + } else { + addEndBracket = false + let paramVarName = (params.length + 1) + '_' + branch.paramVarName + output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${getIndex(indent, 0, params)}) >>> 0`})` + output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length` + output += '\n' + indentation + params.push([branch.isParams || branch.isFullParams, paramVarName]) + + if (branch.isFullParams) { + output += treeIntoCompiledTreeStringReturnPath(indentation, paths, branch, params) + } else if (branch.path) { + output += '\n' + indentation + `if (str.length === ${getIndex(indent, 0, params)}) {` + output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params) + output += '\n' + indentation + `}` + } + } + + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += treeIntoCompiledTreeStringBranch(paths, branch.children, indent + 1, params.slice()) + } + if (addEndBracket) { + output += '\n' + indentation + '} ' + } + } + return output +} + +export function treeIntoCompiledTreeString(paths, tree) { + let output = 'return function RString(paths, static, str) {' + output += '\n let checkStatic = static.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n ' + treeIntoCompiledTreeStringBranch(paths, tree, 1) + output += '\n return null' + output += '\n}' + if (Debug) { + console.log(output) + } + return new Function(output)() +} + +export function treeIntoCompiledTreeStringIndices(paths, tree) { + let output = 'return function RStringIndices(paths, static, str) {' + output += '\n let checkStatic = static.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n ' + treeIntoCompiledTreeStringBranchIndices(paths, tree, 1) + output += '\n return null' + output += '\n}' + if (Debug) { + console.log(output) + } + return new Function(output)() +} + +export function treeIntoCompiledTreeBufferAltOne(paths, tree) { + let output = 'return function RBufferStrSlice(paths, static, str) {' + output += '\n let checkStatic = static.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n var buf = Buffer.from(str)' + output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1, [], 1) + output += '\n return null' + output += '\n}' + if (Debug) { + console.log(output) + } + return new Function(output)() +} + +export function treeIntoCompiledTreeBufferAltTwo(paths, tree) { + let output = 'return function RTextEncoderDecoder(textEncoder, textDecoder, paths, static, str) {' + output += '\n let checkStatic = static.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n var buf = textEncoder.encode(str)' + output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1, [], 2) + output += '\n return null' + output += '\n}' + if (Debug) { + console.log(output) + } + return new Function(output)() +} + +export function treeIntoCompiledTreeBufferAltThree(paths, tree) { + let output = 'return function RTextEncoderStrSlice(textEncoder, paths, static, str) {' + output += '\n let checkStatic = static.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n var buf = textEncoder.encode(str)' + output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1, [], 3) + output += '\n return null' + output += '\n}' + if (Debug) { + console.log(output) + } + return new Function(output)() +} + +export function compilePaths(paths) { + let splitPaths = splitAndSortPaths(paths) + let tree = buildTree(splitPaths.paramsPaths.slice()) + printTree(tree, 0) + let compiled = treeIntoCompiledTreeBuffer(paths, tree) + let compiledString = treeIntoCompiledTreeString(paths, tree) + let compiledStringIndices = treeIntoCompiledTreeStringIndices(paths, tree) + + let compiledTextOne = treeIntoCompiledTreeBufferAltOne(paths, tree) + + // let compiledTextTwo = treeIntoCompiledTreeBufferAltTwo(paths, tree) + // let compiledTextThree = treeIntoCompiledTreeBufferAltThree(paths, tree) + return [ + [compiled, compiled.bind(null, paths, splitPaths.staticPaths)], + [compiledTextOne, compiledTextOne.bind(null, paths, splitPaths.staticPaths)], + // [compiledTextTwo, compiledTextTwo.bind(null, new TextEncoder(), new TextDecoder(), paths, splitPaths.staticPaths)], + // [compiledTextThree, compiledTextThree.bind(null, new TextEncoder(), paths, splitPaths.staticPaths)], + [compiledString, compiledString.bind(null, paths, splitPaths.staticPaths)], + [compiledStringIndices, compiledStringIndices.bind(null, paths, splitPaths.staticPaths)], + ] +} + +export function compilePathsBuffer(paths) { + let splitPaths = splitAndSortPaths(paths) + let tree = buildTree(splitPaths.paramsPaths.slice()) + printTree(tree, 0) + let compiled = treeIntoCompiledTreeBuffer(paths, tree) + let compiledString = treeIntoCompiledTreeString(paths, tree) + let compiledStringIndices = treeIntoCompiledTreeStringIndices(paths, tree) + + let compiledTextOne = treeIntoCompiledTreeBufferAltOne(paths, tree) + + // let compiledTextTwo = treeIntoCompiledTreeBufferAltTwo(paths, tree) + // let compiledTextThree = treeIntoCompiledTreeBufferAltThree(paths, tree) + return [ + [compiled, compiled.bind(null, paths, splitPaths.staticPaths)], + [compiledTextOne, compiledTextOne.bind(null, paths, splitPaths.staticPaths)], + // [compiledTextTwo, compiledTextTwo.bind(null, new TextEncoder(), new TextDecoder(), paths, splitPaths.staticPaths)], + // [compiledTextThree, compiledTextThree.bind(null, new TextEncoder(), paths, splitPaths.staticPaths)], + [compiledString, compiledString.bind(null, paths, splitPaths.staticPaths)], + [compiledStringIndices, compiledStringIndices.bind(null, paths, splitPaths.staticPaths)], + ] +} diff --git a/benchmark/router_v2_compile.mjs b/benchmark/router_v2_compile.mjs new file mode 100644 index 0000000..1533eec --- /dev/null +++ b/benchmark/router_v2_compile.mjs @@ -0,0 +1,16 @@ +import { compilePaths } from "../router_v2.mjs" +import { printTime } from './utils.mjs' +import * as consts from './const.js' + +let paths = consts.allManyRoutes.map(x => ({ path: x })) + +let s1 = process.hrtime.bigint() +let s2 = process.hrtime.bigint() + +compilePaths(paths) + +let s3 = process.hrtime.bigint() + +let time = s3 - s2 - (s2 - s1) + +printTime(time) diff --git a/benchmark/string.js b/benchmark/string.js new file mode 100644 index 0000000..b0f8fc2 --- /dev/null +++ b/benchmark/string.js @@ -0,0 +1,80 @@ +import { summary, run, bench } from 'mitata'; + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +// Example benchmark +summary(() => { + const dataset = new Array(100).fill(0).map( + () => new Array(10).fill(0).map(() => String.fromCharCode(97 + Math.round(Math.random() * 22))).join('') + ); + console.log(dataset) + + const fn1 = (str) => (str[0] === 'c' ? 1 : 0) + (str[1] === 'c' ? 1 : 0) + (str[2] === 'c' ? 1 : 0); + fn1('a'); + fn1('b'); + fn1('c'); + fn1('d'); + // optimizeNextInvocation(fn1); + bench('Char check', () => dataset.map(fn1)) + + const fn2 = (str) => (str.charCodeAt(0) === 99 ? 1 : 0) + (str.charCodeAt(1) === 99 ? 1 : 0) + (str.charCodeAt(2) === 99 ? 1 : 0); + fn2('a'); + fn2('b'); + fn2('c'); + fn2('d'); + // optimizeNextInvocation(fn2); + bench('Char code check', () => dataset.map(fn2)); + + + bench('2x Char check', () => dataset.map(fn1)) + bench('2x Char code check', () => dataset.map(fn2)); +}); + +// Example benchmark +summary(() => { + let paths = [ + 'test1/', + 'test/', + 'test3/', + 'test/', + 'test5/', + 'something/', + 'else/', + 'goes/', + 'here/', + 'too/', + ] + + function fastCheck(str) { + if (str.charCodeAt(0) === 116) { + if (str.charCodeAt(1) === 101) { + if (str.charCodeAt(2) === 115) { + if (str.charCodeAt(3) === 116) { + if (str.charCodeAt(4) === 47) { + if (str.indexOf('/', 5) === -1) { + return true + } + } + } + } + } + } + return false + } + + let r = new RegExp('^test/$') + function regexCheck(str) { + return r.test(str) + } + + console.log(paths.map(fastCheck)) + console.log(paths.map(regexCheck)) + + bench('fastCheck', () => paths.map(fastCheck)) + bench('regexCheck', () => paths.map(regexCheck)); + +}); + +run(); \ No newline at end of file diff --git a/benchmark/strings.mjs b/benchmark/strings.mjs new file mode 100644 index 0000000..b7f69b4 --- /dev/null +++ b/benchmark/strings.mjs @@ -0,0 +1,121 @@ +import { buildTree, compileTreeIntoIfs, compileTreeIntoIfsWithBuffer } from "./compiler/compiler.mjs" +import { summary, run, bench } from 'mitata'; +import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs"; +import { FlaskaRouter } from "../flaska.mjs"; + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +let paths = [ + '/fsdafasfa', + '/ymreklhmse', + '/h34nmlaeknmgl', + '/asdgsdagas', + '/ahaewhweaaa', + '/adshashaea', + '/sdafasfsadfasdfas', + //'/gdfsfgsfdsgsdrgsregsergsregersgserersgsergersg', + //'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', +] +let pathsBla = [ + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', + '/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk', +] +let pathsBuffer = paths.map(x => new Uint8Array(Buffer.from(x))) + +let tree = buildTree(paths) +let noop = function() { } + +const ifTree = compileTreeIntoIfs(tree) +// console.log(ifTree.toString()) +const ifTreeBuffer = compileTreeIntoIfsWithBuffer(tree) +// console.log(ifTreeBuffer.toString()) +const flaskaRouter = new FlaskaRouter() +for (let path of paths) { + flaskaRouter.addRoute(path, noop) +} + +const m = new Map(paths.map(x => [x, x])) +function mapFlat(str) { + return m.get(str) ?? null +} + +function toBuffer(str) { + return Buffer.from(str) +} +function toUint(str) { + return new Uint8Array(Buffer.from(str)) +} +function toManualArray(str) { + let length = str.length + let out = new Array(length) + for (let i = 0; i < length; i++) { + out[i] = str.charCodeAt(i) + } + return out +} +function allocBufferUnsafe(str) { + return Buffer.allocUnsafe(str.length) +} +function allocBufferSafe(str) { + return Buffer.alloc(str.length) +} +function allocUint8(str) { + return new Uint8Array(str.length) +} +function toManualArraySplitMap(str) { + return str.split('').map(x => x.charCodeAt(0)) +} + +let func1 = [mapFlat, toBuffer, toUint, toManualArray, toManualArraySplitMap, allocBufferUnsafe, allocBufferSafe, allocUint8, ifTree]; +for (var i = 0; i < 100000; i++) { + for (let fun of func1) { + paths.map(fun) + } +} +for (var i = 0; i < 100000; i++) { + for (let path of paths) { + flaskaRouter.match(path) + } +} +for (let fun of func1) { + printCurrentStatus(fun); +} +printCurrentStatus(flaskaRouter.match) +printStatusHelperText() + +summary(() => { + bench('if tree', function() { + return paths.map(ifTree) + }) + bench('if tree buffer edition', function() { + return paths.map(ifTreeBuffer) + }); + bench('flaskarouter', function() { + return paths.map(flaskaRouter.match.bind(flaskaRouter)) + }); + /*bench('map edition', function() { + return paths.map(mapFlat) + }); + bench('if tree pre buffer edition', function() { + return pathsBuffer.map(ifTreeBuffer) + }); + bench('toBuffer', () => pathsBla.map(toBuffer)); + bench('toUint', () => pathsBla.map(toUint)); + bench('toManualArraySplitMap', () => pathsBla.map(toManualArraySplitMap)) + bench('toManualArray', () => pathsBla.map(toManualArray))*/ + bench('allocBufferUnsafe', () => pathsBla.map(allocBufferUnsafe)) + bench('allocBufferSafe', () => pathsBla.map(allocBufferSafe)) + bench('allocUint8', () => pathsBla.map(allocUint8)) +}) + +run(); \ No newline at end of file diff --git a/benchmark/strings_compare.mjs b/benchmark/strings_compare.mjs new file mode 100644 index 0000000..f3f2897 --- /dev/null +++ b/benchmark/strings_compare.mjs @@ -0,0 +1,115 @@ +import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs"; +import { summary, run, bench } from 'mitata'; + +// Do warmup +bench('noop', () => { }) +bench('noop2', () => { }) + +// Do checks with arrays +let arr = ['hi', 'there', 'nagna', 'randomlongstring', 'small'] +arr = arr.map(x => [x, { path: x }]) +let paths = arr.map(x => x[1]) + +// Do checks with objects +let obj = {}; +let map = new Map() +for (let i = 0, l = arr.length; i < l; ++i) { + obj[arr[i][0]] = arr[i][1] + map.set(arr[i][0], arr[i][1]) +}; +let objHas1 = (obj, item) => { let x = obj[item]; if (x) { return x } return null }; +let objHas2 = (obj, item) => { if (item in obj) { return obj[item] } return null } +let mapGet = (map, item) => { let x = map.get(item); if (x) { return x }; return null }; + + +// Generate a function which compares the input string to all strings in the list +let basicIfChain = Function(` + return (paths, str) => { + ${arr.map((item, i) => 'if (str === "' + item[0] + '") { return paths[' + i + '] }').join('\n ')} + return null + } +`)(); +let basicIfChainAlt = Function(` + return (str) => { + ${arr.map((item, i) => 'if (str === "' + item[0] + '") { return "' + item[0] + '" }').join('\n ')} + return null + } +`)(); +function basicIfChainNoFunc(str) { + if (str === "hi") { return "hi" } + if (str === "there") { return "there" } + if (str === "nagna") { return "nagna" } + if (str === "randomlongstring") { return "randomlongstring" } + if (str === "small") { return "small" } + return null +} +function addTwo(a, b) { + return a + b +} + +console.log(` + return (str) => { + ${arr.map((item, i) => 'if (str === "' + item[0] + '") { return "' + item[0] + '" }').join('\n ')} + return null + } +`) + +let func = [ + ['basicIfChainNoFunc', basicIfChainNoFunc, basicIfChainNoFunc], + ['basicIfChainAlt', basicIfChainAlt, basicIfChainAlt], + ['objHas1', objHas1, objHas1.bind(null, obj)], + ['objHas2', objHas2, objHas2.bind(null, obj)], + ['mapGet', mapGet, mapGet.bind(null, map)], + ['basicIfChain', basicIfChain, basicIfChain.bind(null, paths)], +] + +for (let [name, __, fun] of func) { + console.log(`--- Sanity checking ${name} ---`) + for (let a of arr) { + console.log(a[0], fun(a[0])) + } +} + +// Generate strings +let newArr = []; +for (let i = 0; i < 100000; i++) + newArr.push(Math.random() < 0.5 ? `${Math.random()}` : arr[Math.round(Math.random() * 4)].slice(0, -1 >>> 0)); + +console.log(`--- warming up addTwo ---`) +for (let i = 0; i < 100; i++) { + console.log(`--- run ${i} ---`) + for (let num of newArr) { + addTwo(num.length, num.length + 1) + } + await new Promise(res => setTimeout(res, 1000)) + printCurrentStatus(addTwo); +} + + +for (let [name, org, fun] of func) { + console.log(`--- warming up ${name} ---`) + for (let i = 0; i < 100; i++) { + console.log(`--- run ${i} ---`) + newArr.forEach(fun) + await new Promise(res => setTimeout(res, 1000)) + printCurrentStatus(org); + } +} + +console.log('--- sleeping ---') +await new Promise(res => setTimeout(res, 1000)) + + +for (let [_, org] of func) { + printCurrentStatus(org); +} +printStatusHelperText() + + +summary(() => { + func.forEach(([name, _, fun]) => { + bench(name, () => newArr.map(fun)); + }) +}); + +run(); diff --git a/benchmark/strings_v2.mjs b/benchmark/strings_v2.mjs new file mode 100644 index 0000000..6581c66 --- /dev/null +++ b/benchmark/strings_v2.mjs @@ -0,0 +1,44 @@ +import { summary, run, bench } from 'mitata'; +import { FlaskaRouter } from "../flaska.mjs"; + +// Warmup (de-optimize `bench()` calls) +bench('noop', () => { }); +bench('noop2', () => { }); + +function padStart(length) { + return ''.padStart(length * 2) +} + +const spaces = ''.padStart(256) + +function strSlice(length) { + return spaces.slice(0, length) +} + +function strSubstring(length) { + return spaces.substring(0, length) +} + +const testData = new Array(100000) +for (let i = 0; i < testData.length; i++) { + testData[i] = Math.round(Math.random() * 200) +} + +let func = [padStart, strSlice, strSubstring]; +for (let fun of func) { + console.log(`--- warming up ${fun.name || 'mapl'} ---`) + for (var i = 0; i < 100; i++) { + testData.map(fun) + } +} + +summary(() => { + func.forEach(function(fun) { + // console.log(tests, fun, tests.map(fun)) + bench(fun.name, function() { + return testData.map(fun) + }) + }) +}) + +run(); diff --git a/benchmark/test.mjs b/benchmark/test.mjs new file mode 100644 index 0000000..1cf51b1 --- /dev/null +++ b/benchmark/test.mjs @@ -0,0 +1,33 @@ +import assert from 'assert' +import { compilePaths } from "./router_v2.mjs" +import * as consts from './const.js' + +let paths = [ + { path: '/aa/aa', }, + { path: '/aa/:blabla', }, + { path: '/::rest', }, +] + +// paths = consts.allManyRoutes.map(x => ({ path: x })) + +let tests = [ + ['/', paths[5]], + ['/aa', paths[5]], + ['/aa/aa', paths[0]], + ['/aa/_', paths[1]], + ['/aa/_/aa', paths[2]], + ['/aa/_/ab', paths[3]], + ['/aa/_/bb', paths[4]], +] + +tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p])) + +let func = compilePaths(paths) +for (let [_, fun] of func) { + console.log(`--- ${fun.name} ---`) + for (let test of tests) { + let check = fun(test[0]) + console.log(test[0], check) + assert.strictEqual(check.path, test[1]) + } +} diff --git a/benchmark/utils.mjs b/benchmark/utils.mjs new file mode 100644 index 0000000..d26a56b --- /dev/null +++ b/benchmark/utils.mjs @@ -0,0 +1,15 @@ +export function printTime (t) { + let time = Number(t) + let units = ['n', 'μ', 'm', 'c', 's'] + let unit = units[0] + let unitPower = 1 + for (let i = 0; i < units.length; i++) { + let power = Math.pow(10, (i + 1) * 3) + if (power * 2 > time) { + break + } + unitPower = power + unit = units[i] + } + console.log(t, '=', Number((time / unitPower).toFixed(2)), unit) +} \ No newline at end of file diff --git a/flaska.mjs b/flaska.mjs index db51c89..da65cdf 100644 --- a/flaska.mjs +++ b/flaska.mjs @@ -431,7 +431,7 @@ export class FlaskaRouter { let isFullParam = false let branch = this.root - if (route.indexOf(':') < 0) { + if (route.indexOf(':') < 0 && false) { let name = route if (name.length > 1 && name[name.length - 1] === '/') { name = name.slice(0, -1) diff --git a/router_v2.mjs b/router_v2.mjs new file mode 100644 index 0000000..b292de2 --- /dev/null +++ b/router_v2.mjs @@ -0,0 +1,242 @@ + +class RouterError extends Error { + constructor(route1, route2, ...params) { + // Pass remaining arguments (including vendor specific ones) to parent constructor + super(...params); + + // Maintains proper stack trace for where our error was thrown (only available on V8) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, RouterError); + } + + this.name = "RouterError"; + this.routeA = route1 + this.routeB = route2 + } +} + +function Child(split, x, i) { + this.path = null + this.isParams = split[x].isParams ? split[x].word : null + this.isFullParams = split[x].isFullParams ? split[x].word : null + this.paramVarName = split[x].paramVarName ?? null + this.char = !this.isParams && !this.isFullParams ? split[x].word[i] || '/' : null + this.count = 0 + this.children = [] +} + +function buildChild(x, i, splitPaths) { + let splitPath = splitPaths[0] + let letter = new Child(splitPath.split, x, i) + + let consume = [] + if (splitPath.split.length === x + 1 + && (splitPath.split[x].isParams + || splitPath.split[x].isFullParams + || splitPath.split[x].word.length === i + 1)) { + letter.path = splitPath.entry + letter.count += 1 + } else { + consume = [splitPath] + } + + for (let y = 1; y < splitPaths.length; y++) { + let checkPath = splitPaths[y] + if (!checkPath.split[x] + || checkPath.split[x].isParams !== splitPath.split[x].isParams + || checkPath.split[x].isFullParams !== splitPath.split[x].isFullParams + || !checkPath.split[x].isParams + && !checkPath.split[x].isFullParams + && (checkPath.split[x].word[i] || '/') !== letter.char) break + consume.push(checkPath) + } + + letter.count += consume.length + if (splitPath.split[x].word.length === i || splitPath.split[x].isParams || splitPath.split[x].isFullParams) { + x++ + i = -1 + } + while (consume.length) { + letter.children.push(buildChild(x, i + 1, consume)) + consume.splice(0, letter.children[letter.children.length - 1].count) + } + return letter +} + +function buildTree(splitPaths) { + let builder = [] + while (splitPaths.length) { + builder.push(buildChild(0, 0, splitPaths)) + splitPaths.splice(0, builder[builder.length - 1].count) + } + return builder +} + +const regParamPrefix = /^::?/ +const regCleanNonAschii = /(?![a-zA-Z_])./g +const regCleanRest = /_+/g + +function splitAndSortPaths(paths, separateStatic = true) { + let staticPaths = new Map() + let paramsPaths = [] + let collator = new Intl.Collator('en', { sensitivity: 'accent' }); + + paths.forEach(function(entry) { + if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start') + + // Collect static paths separately + if (entry.path.indexOf('/:') < 0 && separateStatic) { + return staticPaths.set(entry.path, { + path: entry, + params: {} + }) + } + + // Collect params path separately + paramsPaths.push({ + split: entry.path.slice(1).split(/\//g).map(function(word) { + let actualWord = word.replace(regParamPrefix, '') + return { + word: actualWord, + isParams: word[0] === ':' && word[1] !== ':', + isFullParams: word[0] === ':' && word[1] === ':', + paramVarName: word[0] === ':' + ? actualWord.replace(regCleanNonAschii, '_').replace(regCleanRest, '_') + : null + } + }), + entry, + }) + }) + + paramsPaths.sort(function(aGroup, bGroup) { + let length = Math.max(aGroup.split.length, bGroup.split.length) + for (let x = 0; x < length; x++) { + let a = aGroup.split[x] + let b = bGroup.split[x] + if (!a) return -1 + if (!b) return 1 + // Full params go last + if (a.isFullParams && b.isFullParams) throw new RouterError(aGroup.entry, bGroup.entry, 'Two full path routes found on same level') + if (a.isFullParams) return 1 + if (b.isFullParams) return -1 + // Params go second last + if (a.isParams && !b.isParams) return 1 + if (!a.isParams && b.isParams) return -1 + // otherwise sort alphabetically if not identical + if (a.word !== b.word) return collator.compare(a.word, b.word) + } + throw new RouterError(aGroup, bGroup, 'Two identical paths were found') + }) + + return { + staticPaths, + paramsPaths, + } +} + +const SlashCode = '/'.charCodeAt(0) + +function getIndex(offset, additions, params) { + return (offset + additions) + + (params.length + ? ' + ' + params.map(a => `offset${a[1]}`).join(' + ') + : '') +} + +function treeIntoCompiledCodeReturnPath(indentString, paths, branch, params) { + let pathIndex = paths.indexOf(branch.path) + if (pathIndex < 0) { + throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths') + } + let output = '\n' + indentString + `return {` + output += '\n' + indentString + ` path: paths[${pathIndex}],` + if (params.length) { + output += '\n' + indentString + ` params: {` + for (let param of params) { + output += '\n' + indentString + ` ${param[0]}: s${param[1]},` + } + output += '\n' + indentString + ` },` + } else { + output += '\n' + indentString + ` params: {},` + } + output += '\n' + indentString + `}` + return output +} + +const spaces = ' ' + +function treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = []) { + let output = '' + let indentation = spaces.slice(0, (indent - params.length) * 2) + let addEndBracket = true + + for (let i = 0; i < branches.length; i++) { + let branch = branches[i] + if (i > 0) { + if (!branch.isParams && !branch.isFullParams) { + output += ' else ' + } else { + // output += '} //' + output += '\n' + indentation + } + } + + if (!branch.isParams && !branch.isFullParams) { + output += `if (buf[${getIndex(indent, 0, params)}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}` + + if (branch.path) { + output += '\n' + indentation + ` if (buf.length === ${getIndex(indent, 1, params)}) {` + output += treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params) + output += '\n' + indentation + ` }` + } + } else { + addEndBracket = false + let paramVarName = (params.length + 1) + '_' + branch.paramVarName + output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${getIndex(indent, 0, params)}) >>> 0`})` + output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length` + output += '\n' + indentation + params.push([branch.isParams || branch.isFullParams, paramVarName]) + + if (branch.isFullParams) { + output += treeIntoCompiledCodeReturnPath(indentation, paths, branch, params) + } else if (branch.path) { + output += '\n' + indentation + `if (buf.length === ${getIndex(indent, 0, params)}) {` + output += treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params) + output += '\n' + indentation + `}` + } + } + + if (branch.children.length) { + if (branch.path) { + output += ' else ' + } else { + output += '\n' + indentation + ' ' + } + output += treeIntoCompiledCodeBranch(paths, branch.children, indent + 1, params.slice()) + } + if (addEndBracket) { + output += '\n' + indentation + '} ' + } + } + return output +} + +function treeIntoCompiledCodeClosure(paths, tree, staticList) { + let output = 'return function RBufferStrSliceClosure(str) {' + output += '\n let checkStatic = staticList.get(str)' + output += '\n if(checkStatic) {' + output += '\n return checkStatic' + output += '\n }' + output += '\n var buf = Buffer.from(str)' + output += '\n ' + treeIntoCompiledCodeBranch(paths, tree, 1, []) + output += '\n return null' + output += '\n}' + return new Function('paths', 'staticList', output)(paths, staticList) +} + +export function compilePaths(paths) { + let splitPaths = splitAndSortPaths(paths) + let tree = buildTree(splitPaths.paramsPaths.slice()) + return treeIntoCompiledCodeClosure(paths, tree, splitPaths.staticPaths) +}