Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
d031efb89f | |||
bed505fbe2 | |||
479f98a367 | |||
ee0326c9f4 | |||
566c59ec95 | |||
f48d63038d | |||
2e1dadbdea | |||
8db66f416a |
33 changed files with 2631 additions and 1891 deletions
44
.forgejo/workflows/deploy.yml
Normal file
44
.forgejo/workflows/deploy.yml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: arch
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install dependencies
|
||||||
|
run: time pnpm install
|
||||||
|
- name: Run Tests
|
||||||
|
run: pnpm run test --ignore-only
|
||||||
|
- name: Deply if new version
|
||||||
|
run: |
|
||||||
|
echo ""
|
||||||
|
echo "------------------------------------"
|
||||||
|
echo ""
|
||||||
|
CURR_VER="$(cat package.json | jq -r .name)_v$(cat package.json | jq -r .version)"
|
||||||
|
CURR_NAME="$(cat package.json | jq -r .name) v$(cat package.json | jq -r .version)"
|
||||||
|
|
||||||
|
echo "Checking https://git.nfp.is/api/v1/repos/${{ github.repository }}/releases for name ${CURR_NAME}"
|
||||||
|
|
||||||
|
if curl -s -X GET -H "Authorization: token ${{ secrets.deploytoken }}" https://git.nfp.is/api/v1/repos/${{ github.repository }}/releases | grep -o "\"name\":\"${CURR_NAME}\"" > /dev/null; then
|
||||||
|
echo "Skipping ${{ github.job }} since $CURR_NAME already exists";
|
||||||
|
exit;
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "New release ${CURR_VER} found, beginning deployment"
|
||||||
|
|
||||||
|
echo "Creating ${CURR_VER} release on forgejo"
|
||||||
|
curl \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.deploytoken }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
https://git.nfp.is/api/v1/repos/${{ github.repository }}/releases \
|
||||||
|
-d "{\"tag_name\":\"${CURR_VER}\",\"name\":\"${CURR_NAME}\",\"body\":\"Automatic release from Appveyor from ${{ github.sha }} :\n\n${{ github.event.head_commit.message }}\"}" | jq
|
||||||
|
|
||||||
|
echo "//registry.npmjs.org/:_authToken=${{ secrets.npmtoken }}" > ~/.npmrc
|
||||||
|
echo "Publishing new version to npm"
|
||||||
|
npm publish
|
|
@ -189,6 +189,10 @@ Parse the search query and create a map with key->value in `ctx.query`.
|
||||||
|
|
||||||
Parse incoming request body as json and store it in `ctx.req.body`.
|
Parse incoming request body as json and store it in `ctx.req.body`.
|
||||||
|
|
||||||
|
* `CorsHandler()`
|
||||||
|
|
||||||
|
A secure implementation for handling CORS requests.
|
||||||
|
|
||||||
* `FormidableHandler()`
|
* `FormidableHandler()`
|
||||||
|
|
||||||
Provides a wrapper to handle an incoming file upload using `Formidable@1`.
|
Provides a wrapper to handle an incoming file upload using `Formidable@1`.
|
||||||
|
|
116
benchmark/compiler/compiler.mjs
Normal file
116
benchmark/compiler/compiler.mjs
Normal file
|
@ -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)
|
||||||
|
}
|
39
benchmark/compiler/utils.mjs
Normal file
39
benchmark/compiler/utils.mjs
Normal file
|
@ -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`)
|
||||||
|
}
|
|
@ -6,6 +6,15 @@ export const overrideDummy = function(override) {
|
||||||
|
|
||||||
export const dummy = function() { callItem() }
|
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 = [
|
export const allRoutes = [
|
||||||
'/',
|
'/',
|
||||||
'/api/articles',
|
'/api/articles',
|
||||||
|
@ -23,6 +32,22 @@ export const allRoutes = [
|
||||||
'/api/pages/:id/articles/public',
|
'/api/pages/:id/articles/public',
|
||||||
'/api/staff',
|
'/api/staff',
|
||||||
'/api/staff/:id',
|
'/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 = [
|
export const allManyRoutes = [
|
||||||
|
@ -37,7 +62,7 @@ export const allManyRoutes = [
|
||||||
'/api/categories/:categoryId/properties',
|
'/api/categories/:categoryId/properties',
|
||||||
'/api/categories/:categoryId/values/:props',
|
'/api/categories/:categoryId/values/:props',
|
||||||
'/api/categories/:categoryId',
|
'/api/categories/:categoryId',
|
||||||
'/api/categories/:categoryId/products/:productId',
|
//'/api/categories/:categoryId/products/:productId',
|
||||||
'/api/categories/:categoryId/products/:productId',
|
'/api/categories/:categoryId/products/:productId',
|
||||||
'/api/customers',
|
'/api/customers',
|
||||||
'/api/customers/:id',
|
'/api/customers/:id',
|
||||||
|
@ -61,7 +86,7 @@ export const allManyRoutes = [
|
||||||
'/api/products/:id',
|
'/api/products/:id',
|
||||||
'/api/products/:id/movement',
|
'/api/products/:id/movement',
|
||||||
'/api/products/:id/sub_products/:productId',
|
'/api/products/:id/sub_products/:productId',
|
||||||
'/api/products/:id/sub_products/:productId',
|
//'/api/products/:id/sub_products/:productId',
|
||||||
'/api/products/code/:code',
|
'/api/products/code/:code',
|
||||||
'/api/products/property/:propertyId',
|
'/api/products/property/:propertyId',
|
||||||
'/api/properties',
|
'/api/properties',
|
||||||
|
@ -84,4 +109,5 @@ export const allManyRoutes = [
|
||||||
'/api/works/public',
|
'/api/works/public',
|
||||||
'/api/staff',
|
'/api/staff',
|
||||||
'/api/staff/:id',
|
'/api/staff/:id',
|
||||||
|
'/::rest',
|
||||||
]
|
]
|
||||||
|
|
148
benchmark/ifs.mjs
Normal file
148
benchmark/ifs.mjs
Normal file
|
@ -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()
|
||||||
|
});
|
85
benchmark/ifs_compile.mjs
Normal file
85
benchmark/ifs_compile.mjs
Normal file
|
@ -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()
|
||||||
|
|
||||||
|
|
106
benchmark/map_query.mjs
Normal file
106
benchmark/map_query.mjs
Normal file
|
@ -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()
|
||||||
|
});
|
25
benchmark/mapl_compiler.mjs
Normal file
25
benchmark/mapl_compiler.mjs
Normal file
|
@ -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);
|
||||||
|
}
|
31
benchmark/mapl_router_compile.mjs
Normal file
31
benchmark/mapl_router_compile.mjs
Normal file
|
@ -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)
|
1320
benchmark/package-lock.json
generated
1320
benchmark/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,8 +11,10 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mapl/router": "^0.0.16",
|
||||||
"benchmarkjs-pretty": "^2.0.0",
|
"benchmarkjs-pretty": "^2.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"koa-router": "^8.0.8"
|
"koa-router": "^8.0.8",
|
||||||
|
"mitata": "^1.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
221
benchmark/promise.mjs
Normal file
221
benchmark/promise.mjs
Normal file
|
@ -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()
|
||||||
|
});
|
97
benchmark/router_flaska_v2.mjs
Normal file
97
benchmark/router_flaska_v2.mjs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
import { createRouter, insertItem } from '@mapl/router'
|
||||||
|
import { compileRouter } from './mapl_compiler.mjs'
|
||||||
|
import { compilePaths } from "./router_v2.mjs"
|
||||||
|
import { compilePaths as mainCompiler } from "../router_v2.mjs"
|
||||||
|
import { FlaskaRouter as FlaskaRouterBuffer } from "../flaska_buffer.mjs"
|
||||||
|
import { FlaskaRouter as FlaskaRouterFast } from "../flaska_fast.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.allRoutes.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)]]
|
||||||
|
|
||||||
|
let flaskaRouterBuffer = new FlaskaRouterBuffer()
|
||||||
|
flaskaRouterBuffer.paths = paths.slice()
|
||||||
|
flaskaRouterBuffer.compile()
|
||||||
|
func.push([testStrings, flaskaRouterBuffer.match])
|
||||||
|
|
||||||
|
let flaskaRouterFast = new FlaskaRouterFast()
|
||||||
|
flaskaRouterFast.paths = paths.slice()
|
||||||
|
flaskaRouterFast.compile()
|
||||||
|
func.push([testStrings, flaskaRouterFast.match])
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
for (let [tests, fun] of func) {
|
||||||
|
console.log(`--- warming up ${fun.name || 'mapl'} ---`)
|
||||||
|
for (var i = 0; i < 100000; 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 || 'mapl', function() {
|
||||||
|
return tests.map(fun)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
run().then(function() {
|
||||||
|
for (let [_, check] of func) {
|
||||||
|
printCurrentStatus(check);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
});
|
517
benchmark/router_v2.mjs
Normal file
517
benchmark/router_v2.mjs
Normal file
|
@ -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)],
|
||||||
|
]
|
||||||
|
}
|
16
benchmark/router_v2_compile.mjs
Normal file
16
benchmark/router_v2_compile.mjs
Normal file
|
@ -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)
|
117
benchmark/set_arr.mjs
Normal file
117
benchmark/set_arr.mjs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
function padStart(length) {
|
||||||
|
return ''.padStart(length * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
'/',
|
||||||
|
'/api/articles',
|
||||||
|
'/api/articles/:id/file',
|
||||||
|
'/api/articles/:id',
|
||||||
|
'/api/articles/public',
|
||||||
|
'/api/articles/public/:id',
|
||||||
|
'/api/categories',
|
||||||
|
'/api/categories/:categoryId/products',
|
||||||
|
'/api/categories/:categoryId/properties',
|
||||||
|
'/api/categories/:categoryId/values/:props',
|
||||||
|
'/api/categories/:categoryId',
|
||||||
|
//'/api/categories/:categoryId/products/:productId',
|
||||||
|
'/api/categories/:categoryId/products/:productId',
|
||||||
|
'/api/customers',
|
||||||
|
'/api/customers/:id',
|
||||||
|
'/api/customers/kennitala/:kennitala',
|
||||||
|
'/api/customers/public/kennitala/:kennitala',
|
||||||
|
'/api/customers/search/:search',
|
||||||
|
'/api/file',
|
||||||
|
'/api/file/:id',
|
||||||
|
'/api/media',
|
||||||
|
'/api/media/:id',
|
||||||
|
'/api/orderitem',
|
||||||
|
'/api/orderitem/:id',
|
||||||
|
'/api/orders',
|
||||||
|
'/api/orders/:orderId',
|
||||||
|
'/api/orders/:orderId/sell',
|
||||||
|
'/api/pages',
|
||||||
|
'/api/pages/:pageId',
|
||||||
|
'/api/pages/:pageId/articles',
|
||||||
|
'/api/pages/:pageId/articles/public',
|
||||||
|
'/api/products',
|
||||||
|
'/api/products/:id',
|
||||||
|
'/api/products/:id/movement',
|
||||||
|
'/api/products/:id/sub_products/:productId',
|
||||||
|
//'/api/products/:id/sub_products/:productId',
|
||||||
|
'/api/products/code/:code',
|
||||||
|
'/api/products/property/:propertyId',
|
||||||
|
'/api/properties',
|
||||||
|
'/api/properties/:id',
|
||||||
|
'/api/sales',
|
||||||
|
'/api/sales/:id',
|
||||||
|
'/api/stockitem',
|
||||||
|
'/api/stockitem/:id',
|
||||||
|
'/api/stocks',
|
||||||
|
'/api/stocks/:id',
|
||||||
|
'/api/stocks/:id/commit',
|
||||||
|
'/api/test',
|
||||||
|
'/api/test/auth',
|
||||||
|
'/api/test/error',
|
||||||
|
'/api/workentries',
|
||||||
|
'/api/workentries/:id',
|
||||||
|
'/api/works',
|
||||||
|
'/api/works/:id',
|
||||||
|
'/api/works/:id/lock',
|
||||||
|
'/api/works/public',
|
||||||
|
'/api/staff',
|
||||||
|
'/api/staff/:id',
|
||||||
|
'/::rest',
|
||||||
|
]
|
||||||
|
|
||||||
|
function arrIncludes(data) {
|
||||||
|
let out = new Array()
|
||||||
|
for (let item of data) {
|
||||||
|
if (out.includes(item)) { break }
|
||||||
|
out.push(item)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAdd(data) {
|
||||||
|
let s = new Set()
|
||||||
|
for (let item of data) {
|
||||||
|
let size = s.size
|
||||||
|
if (s.add(item).size === size) { break }
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
let func = [arrIncludes, setAdd];
|
||||||
|
for (let fun of func) {
|
||||||
|
console.log(`--- warming up ${fun.name || 'mapl'} ---`)
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
fun(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(res => setTimeout(res, 3000))
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
for (let i = 20; i >= 0; i--) {
|
||||||
|
const dataSet = data.slice(0, data.length - i)
|
||||||
|
|
||||||
|
func.forEach(function(fun) {
|
||||||
|
bench(`${dataSet.length} items: ${fun.name}`, function() {
|
||||||
|
return fun(dataSet)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// console.log(tests, fun, tests.map(fun))
|
||||||
|
/*bench(fun.name, function() {
|
||||||
|
return fun(data)
|
||||||
|
})*/
|
||||||
|
})
|
||||||
|
|
||||||
|
run();
|
80
benchmark/string.js
Normal file
80
benchmark/string.js
Normal file
|
@ -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();
|
121
benchmark/strings.mjs
Normal file
121
benchmark/strings.mjs
Normal file
|
@ -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();
|
115
benchmark/strings_compare.mjs
Normal file
115
benchmark/strings_compare.mjs
Normal file
|
@ -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();
|
44
benchmark/strings_v2.mjs
Normal file
44
benchmark/strings_v2.mjs
Normal file
|
@ -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();
|
33
benchmark/test.mjs
Normal file
33
benchmark/test.mjs
Normal file
|
@ -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])
|
||||||
|
}
|
||||||
|
}
|
15
benchmark/utils.mjs
Normal file
15
benchmark/utils.mjs
Normal file
|
@ -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)
|
||||||
|
}
|
541
flaska.mjs
541
flaska.mjs
|
@ -37,16 +37,6 @@ export const MimeTypeDb = getDb()
|
||||||
* Router
|
* Router
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Branch {
|
|
||||||
constructor() {
|
|
||||||
this.children = new Map()
|
|
||||||
this.paramName = null
|
|
||||||
this.fullparamName = null
|
|
||||||
this.handler = null
|
|
||||||
this.middlewares = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ErrorCodes = {
|
export const ErrorCodes = {
|
||||||
ERR_CONNECTION_ABORTED: 'ERR_CON_ABORTED'
|
ERR_CONNECTION_ABORTED: 'ERR_CON_ABORTED'
|
||||||
}
|
}
|
||||||
|
@ -73,8 +63,6 @@ const statuses = {
|
||||||
304: true
|
304: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const __paramMapName = '__param'
|
|
||||||
const __fullParamMapName = '__fullparam'
|
|
||||||
|
|
||||||
function assertIsHandler(handler, name) {
|
function assertIsHandler(handler, name) {
|
||||||
if (typeof(handler) !== 'function') {
|
if (typeof(handler) !== 'function') {
|
||||||
|
@ -114,9 +102,9 @@ export function JsonHandler(org = {}) {
|
||||||
ctx.req.body = {}
|
ctx.req.body = {}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = Buffer.concat(buffers).toString();
|
const data = Buffer.concat(buffers).toString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ctx.req.body = JSON.parse(data)
|
ctx.req.body = JSON.parse(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -145,7 +133,7 @@ export function CorsHandler(opts = {}) {
|
||||||
// Always add vary header on origin. Prevent caches from
|
// Always add vary header on origin. Prevent caches from
|
||||||
// accidentally caching wrong preflight request
|
// accidentally caching wrong preflight request
|
||||||
ctx.headers['Vary'] = 'Origin'
|
ctx.headers['Vary'] = 'Origin'
|
||||||
|
|
||||||
// Set status to 204 if OPTIONS. Just handy for flaska and
|
// Set status to 204 if OPTIONS. Just handy for flaska and
|
||||||
// other checking.
|
// other checking.
|
||||||
if (ctx.method === 'OPTIONS') {
|
if (ctx.method === 'OPTIONS') {
|
||||||
|
@ -228,7 +216,7 @@ export function FormidableHandler(formidable, org = {}) {
|
||||||
|
|
||||||
// For testing/stubbing purposes
|
// For testing/stubbing purposes
|
||||||
let rename = formidable.fsRename || fs.rename
|
let rename = formidable.fsRename || fs.rename
|
||||||
|
|
||||||
return function(ctx) {
|
return function(ctx) {
|
||||||
let form = formidable.IncomingForm()
|
let form = formidable.IncomingForm()
|
||||||
form.uploadDir = opts.uploadDir
|
form.uploadDir = opts.uploadDir
|
||||||
|
@ -263,7 +251,7 @@ export function FormidableHandler(formidable, org = {}) {
|
||||||
keys.map(key => {
|
keys.map(key => {
|
||||||
let filename
|
let filename
|
||||||
let target
|
let target
|
||||||
|
|
||||||
try {
|
try {
|
||||||
filename = opts.filename(ctx.req.files[key]) || ctx.req.files[key].name
|
filename = opts.filename(ctx.req.files[key]) || ctx.req.files[key].name
|
||||||
target = path.join(opts.uploadDir, filename)
|
target = path.join(opts.uploadDir, filename)
|
||||||
|
@ -386,7 +374,7 @@ export class FileResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext = path.extname(this.filepath).slice(1)
|
let ext = path.extname(this.filepath).slice(1)
|
||||||
let found = MimeTypeDb[ext]
|
let found = MimeTypeDb[ext]
|
||||||
if (found) {
|
if (found) {
|
||||||
|
@ -403,190 +391,322 @@ export class FileResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* --- Router ---
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const regParamPrefix = /^::?/
|
||||||
|
const regCleanNonAschii = /(?![a-zA-Z_])./g
|
||||||
|
const regCleanRest = /_+/g
|
||||||
|
const regStarDoubleParam = /::[^:/]+/g
|
||||||
|
const regStarSingleParam = /:[^:/]+/g
|
||||||
|
const spaces = ' '
|
||||||
|
|
||||||
export class FlaskaRouter {
|
export class FlaskaRouter {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.root = new Branch()
|
this.paths = []
|
||||||
|
this.registeredPaths = new Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoute(route, orgMiddlewares, orgHandler) {
|
addRoute(path, middlewares, orgHandler) {
|
||||||
if (route[0] !== '/')
|
if (path[0] !== '/')
|
||||||
throw new Error(`route "${route}" must start with forward slash`)
|
throw new RouterError(null, null, `addRoute("${path}") path must start with forward slash`)
|
||||||
|
|
||||||
let middlewares = orgMiddlewares
|
let cleaned = path
|
||||||
let handler = orgHandler
|
if (cleaned.indexOf('/:') >= 0) {
|
||||||
if (!orgHandler) {
|
cleaned = cleaned.replace(regStarDoubleParam, '**').replace(regStarSingleParam, '*')
|
||||||
handler = orgMiddlewares
|
if (cleaned.indexOf(':') > 0) {
|
||||||
middlewares = []
|
throw new RouterError(null, null, `addRoute("${path}") path has missing name or word between two forward slashes`)
|
||||||
}
|
|
||||||
if (middlewares && typeof(middlewares) === 'function') {
|
|
||||||
middlewares = [middlewares]
|
|
||||||
}
|
|
||||||
assertIsHandler(handler, 'addRoute()')
|
|
||||||
|
|
||||||
let start = 1
|
|
||||||
let end = 1
|
|
||||||
let name = ''
|
|
||||||
let param = ''
|
|
||||||
let isParam = false
|
|
||||||
let isFullParam = false
|
|
||||||
let branch = this.root
|
|
||||||
|
|
||||||
if (route.indexOf(':') < 0) {
|
|
||||||
let name = route
|
|
||||||
if (name.length > 1 && name[name.length - 1] === '/') {
|
|
||||||
name = name.slice(0, -1)
|
|
||||||
}
|
}
|
||||||
let child = new Branch()
|
if (cleaned.indexOf('**/') > 0) {
|
||||||
branch.children.set(name, child)
|
throw new RouterError(null, null, `addRoute("${path}") cannot add anything after a full param route`)
|
||||||
child.handler = handler
|
|
||||||
child.middlewares = middlewares
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 1; i <= route.length; i++) {
|
|
||||||
if ((i === route.length || route[i] === '/') && end > start) {
|
|
||||||
if (branch.fullparamName) {
|
|
||||||
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
|
|
||||||
}
|
|
||||||
let child
|
|
||||||
name = route.substring(start, end)
|
|
||||||
if (isFullParam) {
|
|
||||||
param = name
|
|
||||||
name = __fullParamMapName
|
|
||||||
} else if (isParam) {
|
|
||||||
param = name
|
|
||||||
name = __paramMapName
|
|
||||||
}
|
|
||||||
if (branch.children.has(name)) {
|
|
||||||
child = branch.children.get(name)
|
|
||||||
}
|
|
||||||
else if (isParam && !isFullParam && branch.children.has(__fullParamMapName)) {
|
|
||||||
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
|
|
||||||
}
|
|
||||||
else if (isFullParam && branch.children.has(__paramMapName)) {
|
|
||||||
throw new Error(`route "${route}" conflicts with a sub-branch that has a partial param child`)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
child = new Branch()
|
|
||||||
branch.children.set(name, child)
|
|
||||||
}
|
|
||||||
branch = child
|
|
||||||
end = i
|
|
||||||
start = i
|
|
||||||
if (isParam) {
|
|
||||||
if (branch.paramName && branch.paramName !== param) {
|
|
||||||
throw new Error(`route "${route}" conflicts with pre-existing param name of ${branch.paramName} instead of ${param}`)
|
|
||||||
}
|
|
||||||
if (isFullParam) {
|
|
||||||
branch.fullparamName = param
|
|
||||||
} else {
|
|
||||||
branch.paramName = param
|
|
||||||
}
|
|
||||||
isParam = false
|
|
||||||
}
|
|
||||||
} else if (route[i] === '/' && end === start) {
|
|
||||||
throw new Error(`route "${route}" has missing path name inbetween slashes`)
|
|
||||||
}
|
|
||||||
if (i === route.length) {
|
|
||||||
branch.handler = handler
|
|
||||||
branch.middlewares = middlewares
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (route[i] === ':') {
|
|
||||||
if (isParam) {
|
|
||||||
isFullParam = true
|
|
||||||
}
|
|
||||||
isParam = true
|
|
||||||
end = start = i + 1
|
|
||||||
}
|
|
||||||
else if (route[i] === '/') {
|
|
||||||
end = start = i + 1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
end++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cleaned.indexOf('//') >= 0) {
|
||||||
|
throw new RouterError(null, null, `addRoute("${path}") path has missing name or word between two forward slashes`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = this.registeredPaths.size
|
||||||
|
if (this.registeredPaths.add(cleaned).size === size) {
|
||||||
|
throw new RouterError(null, null, `addRoute("${path}") found an existing route with same path of ${cleaned}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let handlers = []
|
||||||
|
if (Array.isArray(middlewares)) {
|
||||||
|
handlers.push(...middlewares)
|
||||||
|
if (typeof(orgHandler) !== 'function') {
|
||||||
|
throw new RouterError(orgHandler, null, `addRoute("${path}") was called with a handler that was not a function`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handlers.push(middlewares)
|
||||||
|
}
|
||||||
|
if (orgHandler) {
|
||||||
|
handlers.push(orgHandler)
|
||||||
|
}
|
||||||
|
for (let handler of handlers) {
|
||||||
|
if (typeof(handler) !== 'function') {
|
||||||
|
throw new RouterError(handler, null, `addRoute("${path}") was called with a handler that was not a function`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.paths.push({
|
||||||
|
path,
|
||||||
|
handlers
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
match(orgUrl) {
|
__buildChild(x, i, splitPaths) {
|
||||||
let url = orgUrl
|
let splitPath = splitPaths[0]
|
||||||
if (url.length > 1 && url[url.length - 1] === '/') {
|
let letter = new Child(splitPath.split, x, i)
|
||||||
url = url.slice(0, -1)
|
|
||||||
|
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]
|
||||||
}
|
}
|
||||||
let branch = this.root
|
|
||||||
let start = 1
|
for (let y = 1; y < splitPaths.length; y++) {
|
||||||
let end = 1
|
let checkPath = splitPaths[y]
|
||||||
let output
|
if (!checkPath.split[x]
|
||||||
let name
|
|| checkPath.split[x].isParams !== splitPath.split[x].isParams
|
||||||
let char
|
|| checkPath.split[x].isFullParams !== splitPath.split[x].isFullParams
|
||||||
let params = {}
|
|| !checkPath.split[x].isParams
|
||||||
if (output = branch.children.get(url)) {
|
&& !checkPath.split[x].isFullParams
|
||||||
return {
|
&& (checkPath.split[x].word[i] || '/') !== letter.char) break
|
||||||
handler: output.handler,
|
consume.push(checkPath)
|
||||||
middlewares: output.middlewares,
|
}
|
||||||
params: params,
|
|
||||||
|
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(this.__buildChild(x, i + 1, consume))
|
||||||
|
consume.splice(0, letter.children[letter.children.length - 1].count)
|
||||||
|
}
|
||||||
|
return letter
|
||||||
|
}
|
||||||
|
|
||||||
|
__buildTree(splitPaths) {
|
||||||
|
let builder = []
|
||||||
|
while (splitPaths.length) {
|
||||||
|
builder.push(this.__buildChild(0, 0, splitPaths))
|
||||||
|
splitPaths.splice(0, builder[builder.length - 1].count)
|
||||||
|
}
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
__splitAndSortPaths(paths, separateStatic = true) {
|
||||||
|
let staticPaths = new Map()
|
||||||
|
let paramsPaths = []
|
||||||
|
let collator = new Intl.Collator('en', { sensitivity: 'accent' });
|
||||||
|
let sealed = Object.seal({})
|
||||||
|
|
||||||
|
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: sealed,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (let i = 1; i <= url.length; i++) {
|
// Collect params path separately
|
||||||
char = url[i]
|
paramsPaths.push({
|
||||||
if ((i === url.length || char === '/') && end > start) {
|
split: entry.path.slice(1).split(/\//g).map(function(word) {
|
||||||
name = url.slice(start, end)
|
let actualWord = word.replace(regParamPrefix, '')
|
||||||
if (output = branch.children.get(name)) {
|
|
||||||
branch = output
|
|
||||||
}
|
|
||||||
else if (output = branch.children.get(__paramMapName)) {
|
|
||||||
branch = output
|
|
||||||
params[branch.paramName] = name
|
|
||||||
}
|
|
||||||
else if (output = branch.children.get(__fullParamMapName)) {
|
|
||||||
params[output.fullparamName] = url.slice(start)
|
|
||||||
return {
|
return {
|
||||||
handler: output.handler,
|
word: actualWord,
|
||||||
middlewares: output.middlewares,
|
isParams: word[0] === ':' && word[1] !== ':',
|
||||||
params: params,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__getIndex(offset, additions, params) {
|
||||||
|
return (offset + additions)
|
||||||
|
+ (params.length
|
||||||
|
? ' + ' + params.map(a => `offset${a[1]}`).join(' + ')
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
|
||||||
|
__treeIntoCompiledCodeReturnPath(indentString, paths, branch, params, pathParamMapIndex) {
|
||||||
|
let pathIndex = paths.indexOf(branch.path)
|
||||||
|
if (pathIndex < 0) {
|
||||||
|
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
|
||||||
|
}
|
||||||
|
let mapIndex = pathParamMapIndex.size + 1
|
||||||
|
pathParamMapIndex.set(mapIndex, pathIndex)
|
||||||
|
let output = '\n' + indentString + `return {`
|
||||||
|
output += '\n' + indentString + ` path: paths_${mapIndex},`
|
||||||
|
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 + ` {},`
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + `}`
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
__treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = [], pathParamMapIndex) {
|
||||||
|
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 {
|
} else {
|
||||||
if (output = this.root.children.get(__fullParamMapName)) {
|
// output += '} //'
|
||||||
params = {
|
output += '\n' + indentation
|
||||||
[output.fullparamName]: url.slice(1)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
handler: output.handler,
|
|
||||||
middlewares: output.middlewares,
|
|
||||||
params: params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
end = start = i
|
|
||||||
char = url[i]
|
|
||||||
}
|
|
||||||
// Check branch.handler. This can happen if route /::path is added
|
|
||||||
// and request is '/' it will attempt to match root which will fail
|
|
||||||
if (i >= url.length && branch.handler) {
|
|
||||||
return {
|
|
||||||
handler: branch.handler,
|
|
||||||
middlewares: branch.middlewares,
|
|
||||||
params: params,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (char === '/') {
|
|
||||||
end = start = i + 1
|
if (!branch.isParams && !branch.isFullParams) {
|
||||||
|
output += `if (str.charCodeAt(${this.__getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
|
||||||
|
|
||||||
|
if (branch.path) {
|
||||||
|
output += '\n' + indentation + ` if (strLength === ${this.__getIndex(indent, 1, params)}) {`
|
||||||
|
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params, pathParamMapIndex)
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
end++
|
addEndBracket = false
|
||||||
|
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
|
||||||
|
output += `let s${paramVarName} = str.slice(${this.__getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${this.__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 += this.__treeIntoCompiledCodeReturnPath(indentation, paths, branch, params, pathParamMapIndex)
|
||||||
|
} else if (branch.path) {
|
||||||
|
output += '\n' + indentation + `if (strLength === ${this.__getIndex(indent, 0, params)}) {`
|
||||||
|
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params, pathParamMapIndex)
|
||||||
|
output += '\n' + indentation + `}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch.children.length) {
|
||||||
|
if (branch.path) {
|
||||||
|
output += ' else '
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentation + ' '
|
||||||
|
}
|
||||||
|
output += this.__treeIntoCompiledCodeBranch(paths, branch.children, indent + 1, params.slice(), pathParamMapIndex)
|
||||||
|
}
|
||||||
|
if (addEndBracket) {
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (output = this.root.children.get(__fullParamMapName)) {
|
return output
|
||||||
params = {
|
}
|
||||||
[output.fullparamName]: url.slice(1)
|
|
||||||
}
|
__treeIntoCompiledCodeClosure(paths, tree, staticList) {
|
||||||
return {
|
let pathParamMapIndex = new Map()
|
||||||
handler: output.handler,
|
let output = ''
|
||||||
middlewares: output.middlewares,
|
let prefix = ''
|
||||||
params: params,
|
if (staticList.size > 0) {
|
||||||
}
|
output += '\n let checkStatic = staticList.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
}
|
}
|
||||||
return null
|
if (tree.length) {
|
||||||
|
output += '\n let strLength = str.length'
|
||||||
|
output += '\n ' + this.__treeIntoCompiledCodeBranch(paths, tree, 1, [], pathParamMapIndex)
|
||||||
|
|
||||||
|
}
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
pathParamMapIndex.forEach(function (val, key) {
|
||||||
|
prefix += `let paths_${key} = paths[${val}]\n`
|
||||||
|
})
|
||||||
|
output = prefix + 'return function flaskaFastRouter(str) {\n "use strict";' + output
|
||||||
|
|
||||||
|
return new Function('paths', 'staticList', output)(paths, staticList)
|
||||||
|
}
|
||||||
|
|
||||||
|
compile() {
|
||||||
|
let splitPaths = this.__splitAndSortPaths(this.paths)
|
||||||
|
let tree = this.__buildTree(splitPaths.paramsPaths.slice())
|
||||||
|
this.match = this.__treeIntoCompiledCodeClosure(this.paths, tree, splitPaths.staticPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
match(url) {
|
||||||
|
this.compile()
|
||||||
|
return this.match(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,7 +802,7 @@ export class Flaska {
|
||||||
for (let i = 0; i < this._nonces.length; i++) {
|
for (let i = 0; i < this._nonces.length; i++) {
|
||||||
this._nonces[i] = crypto.randomBytes(16).toString('base64')
|
this._nonces[i] = crypto.randomBytes(16).toString('base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFunction += `
|
constructFunction += `
|
||||||
let nonce = this._nonces[this._noncesIndex] || crypto.randomBytes(16).toString('base64');
|
let nonce = this._nonces[this._noncesIndex] || crypto.randomBytes(16).toString('base64');
|
||||||
this._noncesIndex--;
|
this._noncesIndex--;
|
||||||
|
@ -692,7 +812,7 @@ ctx.state.nonce = nonce;
|
||||||
|
|
||||||
constructFunction += 'ctx.headers = {'
|
constructFunction += 'ctx.headers = {'
|
||||||
constructFunction += `'Date': new Date().toUTCString(),`
|
constructFunction += `'Date': new Date().toUTCString(),`
|
||||||
for (let key of headerKeys) {
|
for (let key of headerKeys) {
|
||||||
if (key === 'Content-Security-Policy' && options.nonce.length) {
|
if (key === 'Content-Security-Policy' && options.nonce.length) {
|
||||||
let groups = options.defaultHeaders[key].split(';')
|
let groups = options.defaultHeaders[key].split(';')
|
||||||
for (let ni = 0; ni < options.nonce.length; ni++) {
|
for (let ni = 0; ni < options.nonce.length; ni++) {
|
||||||
|
@ -754,12 +874,6 @@ ctx.state.nonce = nonce;
|
||||||
this.patch = this.routers.PATCH.addRoute.bind(this.routers.PATCH)
|
this.patch = this.routers.PATCH.addRoute.bind(this.routers.PATCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
_assertIsHandler(handler, name) {
|
|
||||||
if (typeof(handler) !== 'function') {
|
|
||||||
throw new Error(`${name} was called with a handler that was not a function`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
devMode() {
|
devMode() {
|
||||||
this._backuperror = this._onerror = function(err, ctx) {
|
this._backuperror = this._onerror = function(err, ctx) {
|
||||||
ctx.log.error(err)
|
ctx.log.error(err)
|
||||||
|
@ -795,7 +909,7 @@ ctx.state.nonce = nonce;
|
||||||
assertIsHandler(handler, 'onreqerror()')
|
assertIsHandler(handler, 'onreqerror()')
|
||||||
this._onreqerror = handler
|
this._onreqerror = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
onreserror(handler) {
|
onreserror(handler) {
|
||||||
assertIsHandler(handler, 'onreserror()')
|
assertIsHandler(handler, 'onreserror()')
|
||||||
this._onreserror = handler
|
this._onreserror = handler
|
||||||
|
@ -810,7 +924,7 @@ ctx.state.nonce = nonce;
|
||||||
assertIsHandler(handler, 'beforeAsync()')
|
assertIsHandler(handler, 'beforeAsync()')
|
||||||
this._beforeAsync.push(handler)
|
this._beforeAsync.push(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
after(handler) {
|
after(handler) {
|
||||||
assertIsHandler(handler, 'after()')
|
assertIsHandler(handler, 'after()')
|
||||||
this._after.push(handler)
|
this._after.push(handler)
|
||||||
|
@ -820,7 +934,7 @@ ctx.state.nonce = nonce;
|
||||||
assertIsHandler(handler, 'afterAsync()')
|
assertIsHandler(handler, 'afterAsync()')
|
||||||
this._afterAsync.push(handler)
|
this._afterAsync.push(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestStart(req, res) {
|
requestStart(req, res) {
|
||||||
let url = req.url
|
let url = req.url
|
||||||
let search = ''
|
let search = ''
|
||||||
|
@ -894,37 +1008,25 @@ ctx.state.nonce = nonce;
|
||||||
|
|
||||||
ctx.params = route.params
|
ctx.params = route.params
|
||||||
|
|
||||||
if (route.middlewares.length) {
|
let handlers = this.runHandlers(ctx, route.path.handlers, 0)
|
||||||
let middle = this.handleMiddleware(ctx, route.middlewares, 0)
|
|
||||||
|
|
||||||
if (middle && middle.then) {
|
if (handlers && handlers.then) {
|
||||||
return middle.then(() => {
|
return handlers.then(() => {
|
||||||
return route.handler(ctx)
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.requestEnd(null, ctx)
|
|
||||||
}, err => {
|
|
||||||
this.requestEnd(err, ctx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let handler = route.handler(ctx)
|
|
||||||
if (handler && handler.then) {
|
|
||||||
return handler.then(() => {
|
|
||||||
this.requestEnd(null, ctx)
|
this.requestEnd(null, ctx)
|
||||||
}, err => {
|
}, err => {
|
||||||
this.requestEnd(err, ctx)
|
this.requestEnd(err, ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestEnd(null, ctx)
|
this.requestEnd(null, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMiddleware(ctx, middles, index) {
|
runHandlers(ctx, middles, index) {
|
||||||
for (let i = index; i < middles.length; i++) {
|
for (let i = index; i < middles.length; i++) {
|
||||||
let res = middles[i](ctx)
|
let res = middles[i](ctx)
|
||||||
if (res && res.then) {
|
if (res && res.then) {
|
||||||
return res.then(() => {
|
return res.then(() => {
|
||||||
return this.handleMiddleware(ctx, middles, i + 1)
|
return this.runHandlers(ctx, middles, i + 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -952,7 +1054,7 @@ ctx.state.nonce = nonce;
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.body === null && !handleUsed && ctx.status === 200) {
|
if (ctx.body == null && !handleUsed && ctx.status === 200) {
|
||||||
ctx.status = 204
|
ctx.status = 204
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -989,7 +1091,7 @@ ctx.state.nonce = nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = 0
|
let length = 0
|
||||||
|
|
||||||
if (body instanceof Buffer) {
|
if (body instanceof Buffer) {
|
||||||
length = body.byteLength
|
length = body.byteLength
|
||||||
ctx.type = ctx.type || 'application/octet-stream'
|
ctx.type = ctx.type || 'application/octet-stream'
|
||||||
|
@ -1060,6 +1162,12 @@ ctx.state.nonce = nonce;
|
||||||
this[`_${type}AsyncCompiled`] = func.bind(this, ...this[`_${type}Async`])
|
this[`_${type}AsyncCompiled`] = func.bind(this, ...this[`_${type}Async`])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.routers.GET.compile()
|
||||||
|
this.routers.POST.compile()
|
||||||
|
this.routers.PUT.compile()
|
||||||
|
this.routers.DELETE.compile()
|
||||||
|
this.routers.OPTIONS.compile()
|
||||||
|
this.routers.PATCH.compile()
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
@ -1069,7 +1177,7 @@ ctx.state.nonce = nonce;
|
||||||
this.server.on('connection', function (socket) {
|
this.server.on('connection', function (socket) {
|
||||||
// Set socket idle timeout in milliseconds
|
// Set socket idle timeout in milliseconds
|
||||||
socket.setTimeout(1000 * 60 * 5) // 5 minutes
|
socket.setTimeout(1000 * 60 * 5) // 5 minutes
|
||||||
|
|
||||||
// Wait for timeout event (socket will emit it when idle timeout elapses)
|
// Wait for timeout event (socket will emit it when idle timeout elapses)
|
||||||
socket.on('timeout', function () {
|
socket.on('timeout', function () {
|
||||||
// Call destroy again
|
// Call destroy again
|
||||||
|
@ -1090,7 +1198,7 @@ ctx.state.nonce = nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.create()
|
this.create()
|
||||||
|
|
||||||
this.server.listen(port, ip, cb)
|
this.server.listen(port, ip, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1121,8 +1229,9 @@ ctx.state.nonce = nonce;
|
||||||
if (err) { return rej(err) }
|
if (err) { return rej(err) }
|
||||||
|
|
||||||
// Waiting 0.1 second for it to close down
|
// Waiting 0.1 second for it to close down
|
||||||
setTimeout(function() { res() }, 100)
|
setTimeout(res, 100)
|
||||||
})
|
})
|
||||||
|
this.server.closeAllConnections()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
package.json
18
package.json
|
@ -1,21 +1,18 @@
|
||||||
{
|
{
|
||||||
"name": "flaska",
|
"name": "flaska",
|
||||||
"version": "1.3.5",
|
"version": "1.4.0",
|
||||||
"description": "Flaska is a micro web-framework for node. It is designed to be fast, simple and lightweight, and is distributed as a single file module with no dependencies.",
|
"description": "Flaska is a micro web-framework for node. It is designed to be fast, simple and lightweight, and is distributed as a single file module with no dependencies.",
|
||||||
"main": "flaska.mjs",
|
"main": "flaska.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "eltro",
|
"test": "eltro -r dot",
|
||||||
"test:watch": "npm-watch test"
|
"test:watch": "eltro -r dot -w test"
|
||||||
},
|
},
|
||||||
"watch": {
|
"watch": {
|
||||||
"test": {
|
"test": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
"test/*",
|
"./"
|
||||||
"flaska.mjs"
|
|
||||||
],
|
],
|
||||||
"extensions": "mjs",
|
"extensions": "mjs"
|
||||||
"quiet": true,
|
|
||||||
"inherit": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -42,6 +39,11 @@
|
||||||
"eltro": "^1.3.2",
|
"eltro": "^1.3.2",
|
||||||
"formidable": "^1.2.2"
|
"formidable": "^1.2.2"
|
||||||
},
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"allowedDeprecatedVersions": {
|
||||||
|
"formidable": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"flaska.mjs",
|
"flaska.mjs",
|
||||||
"README.md",
|
"README.md",
|
||||||
|
|
16
test.mjs
16
test.mjs
|
@ -1,16 +0,0 @@
|
||||||
import { Flaska } from './flaska.mjs'
|
|
||||||
|
|
||||||
const port = 51024
|
|
||||||
const flaska = new Flaska({}, )
|
|
||||||
|
|
||||||
flaska.get('/', function(ctx) {
|
|
||||||
ctx.body = { status: true }
|
|
||||||
})
|
|
||||||
|
|
||||||
flaska.get('/error', function(ctx) {
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
flaska.listen(port, function() {
|
|
||||||
console.log('listening on port', port)
|
|
||||||
})
|
|
|
@ -5,11 +5,11 @@ import { createCtx, fakeHttp } from './helper.mjs'
|
||||||
const faker = fakeHttp()
|
const faker = fakeHttp()
|
||||||
|
|
||||||
t.describe('#constructor', function() {
|
t.describe('#constructor', function() {
|
||||||
t.test('should be able to override the http', function() {
|
t.test('should be able to override the http', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
assert.strictEqual(flaska.http, faker)
|
assert.strictEqual(flaska.http, faker)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('it should have all the common verbs', function() {
|
t.test('it should have all the common verbs', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
assert.ok(flaska.get)
|
assert.ok(flaska.get)
|
||||||
|
@ -25,7 +25,7 @@ t.describe('#constructor', function() {
|
||||||
assert.ok(flaska.patch)
|
assert.ok(flaska.patch)
|
||||||
assert.strictEqual(typeof(flaska.patch), 'function')
|
assert.strictEqual(typeof(flaska.patch), 'function')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('the verbs GET and HEAD should be identical', function() {
|
t.test('the verbs GET and HEAD should be identical', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
assert.ok(flaska.get)
|
assert.ok(flaska.get)
|
||||||
|
@ -38,7 +38,7 @@ t.describe('#constructor', function() {
|
||||||
t.test('should have before default header generator', function() {
|
t.test('should have before default header generator', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
assert.strictEqual(flaska._before.length, 1)
|
assert.strictEqual(flaska._before.length, 1)
|
||||||
|
|
||||||
let ctx = {}
|
let ctx = {}
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
@ -78,7 +78,7 @@ t.describe('#constructor', function() {
|
||||||
let ctx = {}
|
let ctx = {}
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
let keys = Object.keys(defaultHeaders)
|
let keys = Object.keys(defaultHeaders)
|
||||||
|
|
||||||
assert.strictEqual(Object.keys(ctx.headers).length, keys.length + 1)
|
assert.strictEqual(Object.keys(ctx.headers).length, keys.length + 1)
|
||||||
|
@ -103,7 +103,7 @@ t.describe('#constructor', function() {
|
||||||
let ctx = {}
|
let ctx = {}
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
Object.keys(ctx.headers).sort(),
|
Object.keys(ctx.headers).sort(),
|
||||||
['Server', 'Herp', 'X-Content-Type-Options','Content-Security-Policy','Cross-Origin-Opener-Policy','Cross-Origin-Resource-Policy','Cross-Origin-Embedder-Policy','Date'].sort()
|
['Server', 'Herp', 'X-Content-Type-Options','Content-Security-Policy','Cross-Origin-Opener-Policy','Cross-Origin-Resource-Policy','Cross-Origin-Embedder-Policy','Date'].sort()
|
||||||
|
@ -140,7 +140,7 @@ t.describe('#_nonce', function() {
|
||||||
set.add(entry)
|
set.add(entry)
|
||||||
})
|
})
|
||||||
assert.strictEqual(set.size, flaska._nonces.length)
|
assert.strictEqual(set.size, flaska._nonces.length)
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
|
|
||||||
assert.notOk(ctx.state.nonce)
|
assert.notOk(ctx.state.nonce)
|
||||||
|
@ -148,12 +148,12 @@ t.describe('#_nonce', function() {
|
||||||
let oldIndex = flaska._noncesIndex
|
let oldIndex = flaska._noncesIndex
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
assert.ok(ctx.state.nonce)
|
assert.ok(ctx.state.nonce)
|
||||||
assert.strictEqual(flaska._noncesIndex, oldIndex - 1)
|
assert.strictEqual(flaska._noncesIndex, oldIndex - 1)
|
||||||
assert.strictEqual(flaska._nonces[oldIndex], ctx.state.nonce)
|
assert.strictEqual(flaska._nonces[oldIndex], ctx.state.nonce)
|
||||||
|
|
||||||
|
|
||||||
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
||||||
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
||||||
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline' 'nonce-${ctx.state.nonce}'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline' 'nonce-${ctx.state.nonce}'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
||||||
|
@ -162,7 +162,7 @@ t.describe('#_nonce', function() {
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
||||||
assert.ok(new Date(ctx.headers['Date']).getDate())
|
assert.ok(new Date(ctx.headers['Date']).getDate())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should always return nonce values even if it runs out in cache', function() {
|
t.test('should always return nonce values even if it runs out in cache', function() {
|
||||||
let flaska = new Flaska({
|
let flaska = new Flaska({
|
||||||
nonce: ['script-src'],
|
nonce: ['script-src'],
|
||||||
|
@ -178,9 +178,9 @@ t.describe('#_nonce', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.notOk(flaska._nonces[flaska._noncesIndex])
|
assert.notOk(flaska._nonces[flaska._noncesIndex])
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
assert.notOk(flaska._nonces[flaska._noncesIndex])
|
assert.notOk(flaska._nonces[flaska._noncesIndex])
|
||||||
assert.ok(ctx.state.nonce)
|
assert.ok(ctx.state.nonce)
|
||||||
|
|
||||||
|
@ -206,13 +206,13 @@ t.describe('#_nonce', function() {
|
||||||
set.add(entry)
|
set.add(entry)
|
||||||
})
|
})
|
||||||
assert.strictEqual(set.size, 5)
|
assert.strictEqual(set.size, 5)
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
assert.strictEqual(flaska._noncesIndex, 1)
|
assert.strictEqual(flaska._noncesIndex, 1)
|
||||||
|
|
||||||
flaska._after[0](ctx)
|
flaska._after[0](ctx)
|
||||||
assert.strictEqual(flaska._noncesIndex, 2)
|
assert.strictEqual(flaska._noncesIndex, 2)
|
||||||
set.add(flaska._nonces[flaska._noncesIndex])
|
set.add(flaska._nonces[flaska._noncesIndex])
|
||||||
|
@ -227,7 +227,7 @@ t.describe('#_nonce', function() {
|
||||||
assert.strictEqual(flaska._noncesIndex, 4)
|
assert.strictEqual(flaska._noncesIndex, 4)
|
||||||
set.add(flaska._nonces[flaska._noncesIndex])
|
set.add(flaska._nonces[flaska._noncesIndex])
|
||||||
assert.strictEqual(set.size, 8)
|
assert.strictEqual(set.size, 8)
|
||||||
|
|
||||||
flaska._after[0](ctx)
|
flaska._after[0](ctx)
|
||||||
assert.strictEqual(flaska._noncesIndex, 4)
|
assert.strictEqual(flaska._noncesIndex, 4)
|
||||||
set.add(flaska._nonces[flaska._noncesIndex])
|
set.add(flaska._nonces[flaska._noncesIndex])
|
||||||
|
@ -249,12 +249,12 @@ t.describe('#_nonce', function() {
|
||||||
set.add(entry)
|
set.add(entry)
|
||||||
})
|
})
|
||||||
assert.strictEqual(set.size, 2)
|
assert.strictEqual(set.size, 2)
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
assert.strictEqual(flaska._noncesIndex, -1)
|
assert.strictEqual(flaska._noncesIndex, -1)
|
||||||
|
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
assert.strictEqual(flaska._noncesIndex, -2)
|
assert.strictEqual(flaska._noncesIndex, -2)
|
||||||
set.add(ctx.state.nonce)
|
set.add(ctx.state.nonce)
|
||||||
|
@ -271,19 +271,19 @@ t.describe('#_nonce', function() {
|
||||||
assert.strictEqual(set.size, 5)
|
assert.strictEqual(set.size, 5)
|
||||||
|
|
||||||
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
|
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
|
||||||
|
|
||||||
flaska._after[0](ctx)
|
flaska._after[0](ctx)
|
||||||
assert.strictEqual(flaska._noncesIndex, 0)
|
assert.strictEqual(flaska._noncesIndex, 0)
|
||||||
set.add(flaska._nonces[flaska._noncesIndex])
|
set.add(flaska._nonces[flaska._noncesIndex])
|
||||||
assert.strictEqual(set.size, 6)
|
assert.strictEqual(set.size, 6)
|
||||||
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
|
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
|
||||||
|
|
||||||
flaska._after[0](ctx)
|
flaska._after[0](ctx)
|
||||||
assert.strictEqual(flaska._noncesIndex, 1)
|
assert.strictEqual(flaska._noncesIndex, 1)
|
||||||
set.add(flaska._nonces[flaska._noncesIndex])
|
set.add(flaska._nonces[flaska._noncesIndex])
|
||||||
assert.strictEqual(set.size, 7)
|
assert.strictEqual(set.size, 7)
|
||||||
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
|
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
|
||||||
|
|
||||||
flaska._after[0](ctx)
|
flaska._after[0](ctx)
|
||||||
assert.strictEqual(flaska._noncesIndex, 1)
|
assert.strictEqual(flaska._noncesIndex, 1)
|
||||||
set.add(flaska._nonces[flaska._noncesIndex])
|
set.add(flaska._nonces[flaska._noncesIndex])
|
||||||
|
@ -299,7 +299,7 @@ t.describe('#log', function() {
|
||||||
assert.strictEqual(typeof(flaska.log.info), 'function')
|
assert.strictEqual(typeof(flaska.log.info), 'function')
|
||||||
assert.strictEqual(typeof(flaska.log.warn), 'function')
|
assert.strictEqual(typeof(flaska.log.warn), 'function')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('allow overwriting in options', function() {
|
t.test('allow overwriting in options', function() {
|
||||||
const assertFunction = function() { return 1 }
|
const assertFunction = function() { return 1 }
|
||||||
let flaska = new Flaska({ log: {
|
let flaska = new Flaska({ log: {
|
||||||
|
@ -323,7 +323,7 @@ specialHandlers.forEach(function(type) {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
assert.strictEqual(typeof(flaska[type]), 'function')
|
assert.strictEqual(typeof(flaska[type]), 'function')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('validate handler', function() {
|
t.test('validate handler', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
assert.throws(function() { flaska[type]() }, /[Ff]unction/)
|
assert.throws(function() { flaska[type]() }, /[Ff]unction/)
|
||||||
|
@ -424,7 +424,7 @@ t.describe('_onerror', function() {
|
||||||
const assertStatus = 431
|
const assertStatus = 431
|
||||||
const assertBody = { a: 1 }
|
const assertBody = { a: 1 }
|
||||||
const assertError = new HttpError(assertStatus, 'should not be seen', assertBody)
|
const assertError = new HttpError(assertStatus, 'should not be seen', assertBody)
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska._onerror(assertError, ctx)
|
flaska._onerror(assertError, ctx)
|
||||||
|
@ -437,7 +437,7 @@ t.describe('_onerror', function() {
|
||||||
t.test('default valid handling of HttpError with no body', function() {
|
t.test('default valid handling of HttpError with no body', function() {
|
||||||
const assertStatus = 413
|
const assertStatus = 413
|
||||||
const assertError = new HttpError(assertStatus, 'should not be seen')
|
const assertError = new HttpError(assertStatus, 'should not be seen')
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska._onerror(assertError, ctx)
|
flaska._onerror(assertError, ctx)
|
||||||
|
@ -453,7 +453,7 @@ t.describe('_onerror', function() {
|
||||||
t.test('default valid handling of HttpError with missing status message', function() {
|
t.test('default valid handling of HttpError with missing status message', function() {
|
||||||
const assertStatus = 432
|
const assertStatus = 432
|
||||||
const assertError = new HttpError(assertStatus, 'should not be seen')
|
const assertError = new HttpError(assertStatus, 'should not be seen')
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska._onerror(assertError, ctx)
|
flaska._onerror(assertError, ctx)
|
||||||
|
@ -531,7 +531,7 @@ t.describe('#devMode()', function() {
|
||||||
const assertStatus = 431
|
const assertStatus = 431
|
||||||
const assertBody = { a: 1 }
|
const assertBody = { a: 1 }
|
||||||
const assertError = new HttpError(assertStatus, 'should not be seen', assertBody)
|
const assertError = new HttpError(assertStatus, 'should not be seen', assertBody)
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.devMode()
|
flaska.devMode()
|
||||||
|
@ -546,7 +546,7 @@ t.describe('#devMode()', function() {
|
||||||
const assertStatus = 413
|
const assertStatus = 413
|
||||||
const assertErrorMessage = 'A day'
|
const assertErrorMessage = 'A day'
|
||||||
const assertError = new HttpError(assertStatus, assertErrorMessage)
|
const assertError = new HttpError(assertStatus, assertErrorMessage)
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.devMode()
|
flaska.devMode()
|
||||||
|
@ -563,7 +563,7 @@ t.describe('#devMode()', function() {
|
||||||
const assertStatus = 432
|
const assertStatus = 432
|
||||||
const assertErrorMessage = 'Jet Coaster Ride'
|
const assertErrorMessage = 'Jet Coaster Ride'
|
||||||
const assertError = new HttpError(assertStatus, assertErrorMessage)
|
const assertError = new HttpError(assertStatus, assertErrorMessage)
|
||||||
|
|
||||||
let ctx = createCtx()
|
let ctx = createCtx()
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.devMode()
|
flaska.devMode()
|
||||||
|
@ -735,16 +735,16 @@ t.describe('#compile()', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('#handleMiddleware()', function() {
|
t.describe('#runHandlers()', function() {
|
||||||
t.test('should work with empty array', function() {
|
t.test('should work with empty array', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.handleMiddleware({}, [], 0)
|
flaska.runHandlers({}, [], 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should work with correct index', function() {
|
t.test('should work with correct index', function() {
|
||||||
let checkIsTrue = false
|
let checkIsTrue = false
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.handleMiddleware({}, [
|
flaska.runHandlers({}, [
|
||||||
function() { throw new Error('should not be thrown') },
|
function() { throw new Error('should not be thrown') },
|
||||||
function() { throw new Error('should not be thrown') },
|
function() { throw new Error('should not be thrown') },
|
||||||
function() { throw new Error('should not be thrown') },
|
function() { throw new Error('should not be thrown') },
|
||||||
|
@ -757,7 +757,7 @@ t.describe('#handleMiddleware()', function() {
|
||||||
const assertCtx = createCtx({ a: 1 })
|
const assertCtx = createCtx({ a: 1 })
|
||||||
let checkCounter = 0
|
let checkCounter = 0
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.handleMiddleware(assertCtx, [
|
flaska.runHandlers(assertCtx, [
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
||||||
|
@ -771,7 +771,7 @@ t.describe('#handleMiddleware()', function() {
|
||||||
const assertCtx = createCtx({ a: 1 })
|
const assertCtx = createCtx({ a: 1 })
|
||||||
let checkCounter = 0
|
let checkCounter = 0
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
let result = flaska.handleMiddleware(assertCtx, [
|
let result = flaska.runHandlers(assertCtx, [
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 0); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 0); checkCounter++ },
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 1); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 1); checkCounter++ },
|
||||||
function(ctx) { return new Promise(function(res) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 2); checkCounter++; res() }) },
|
function(ctx) { return new Promise(function(res) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 2); checkCounter++; res() }) },
|
||||||
|
@ -793,19 +793,19 @@ t.describe('#handleMiddleware()', function() {
|
||||||
const assertError = { a: 1 }
|
const assertError = { a: 1 }
|
||||||
let checkCounter = 0
|
let checkCounter = 0
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
let err = await assert.isRejected(flaska.handleMiddleware({}, [
|
let err = await assert.isRejected(flaska.runHandlers({}, [
|
||||||
function() { },
|
function() { },
|
||||||
function() { return new Promise(function(res, rej) { rej(assertError) }) },
|
function() { return new Promise(function(res, rej) { rej(assertError) }) },
|
||||||
function() { throw new Error('should not be seen') },
|
function() { throw new Error('should not be seen') },
|
||||||
], 0))
|
], 0))
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
err = await assert.isRejected(flaska.handleMiddleware({}, [
|
err = await assert.isRejected(flaska.runHandlers({}, [
|
||||||
function() { },
|
function() { },
|
||||||
function() { return Promise.reject(assertError) },
|
function() { return Promise.reject(assertError) },
|
||||||
function() { throw new Error('should not be seen') },
|
function() { throw new Error('should not be seen') },
|
||||||
], 0))
|
], 0))
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
err = await assert.isRejected(flaska.handleMiddleware({}, [
|
err = await assert.isRejected(flaska.runHandlers({}, [
|
||||||
function() { },
|
function() { },
|
||||||
function() { return Promise.resolve() },
|
function() { return Promise.resolve() },
|
||||||
function() { throw assertError },
|
function() { throw assertError },
|
||||||
|
@ -977,7 +977,7 @@ t.describe('#listenAsync()', function() {
|
||||||
assert.strictEqual(checkPort, assertPort)
|
assert.strictEqual(checkPort, assertPort)
|
||||||
assert.strictEqual(checkIp, '::')
|
assert.strictEqual(checkIp, '::')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('call http and listenAsync correctly if supported', async function() {
|
t.test('call http and listenAsync correctly if supported', async function() {
|
||||||
const assertPort = 4632
|
const assertPort = 4632
|
||||||
const assertIp = 'asdf'
|
const assertIp = 'asdf'
|
||||||
|
@ -1002,7 +1002,7 @@ t.describe('#listenAsync()', function() {
|
||||||
assert.strictEqual(stubListenAsync.firstCall[0], assertPort)
|
assert.strictEqual(stubListenAsync.firstCall[0], assertPort)
|
||||||
assert.strictEqual(stubListenAsync.firstCall[1], assertIp)
|
assert.strictEqual(stubListenAsync.firstCall[1], assertIp)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('call http and listenAsync correctly if supported and ip is null', async function() {
|
t.test('call http and listenAsync correctly if supported and ip is null', async function() {
|
||||||
const assertPort = 325897235
|
const assertPort = 325897235
|
||||||
const assertReturns = { a: 1 }
|
const assertReturns = { a: 1 }
|
||||||
|
@ -1072,10 +1072,23 @@ t.describe('#closeAsync()', function() {
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.test('it should call closeAllConnections', async function() {
|
||||||
|
const assertError = new Error('Pirate Fight')
|
||||||
|
let flaska = new Flaska()
|
||||||
|
flaska.server = {
|
||||||
|
close: stub(),
|
||||||
|
closeAllConnections: stub(),
|
||||||
|
}
|
||||||
|
flaska.server.closeAllConnections.throws(assertError)
|
||||||
|
let err = await assert.isRejected(flaska.closeAsync())
|
||||||
|
assert.strictEqual(err, assertError)
|
||||||
|
})
|
||||||
|
|
||||||
t.test('should otherwise work', async function() {
|
t.test('should otherwise work', async function() {
|
||||||
let flaska = new Flaska()
|
let flaska = new Flaska()
|
||||||
flaska.server = {
|
flaska.server = {
|
||||||
close: stub()
|
close: stub(),
|
||||||
|
closeAllConnections: stub(),
|
||||||
}
|
}
|
||||||
flaska.server.close.returnWith(function(cb) {
|
flaska.server.close.returnWith(function(cb) {
|
||||||
cb(null, { a: 1 })
|
cb(null, { a: 1 })
|
||||||
|
|
|
@ -30,7 +30,7 @@ t.describe('#requestStart()', function() {
|
||||||
assert.strictEqual(onResError.callCount, 1)
|
assert.strictEqual(onResError.callCount, 1)
|
||||||
assert.strictEqual(onResError.firstCall[0], assertErrorTwo)
|
assert.strictEqual(onResError.firstCall[0], assertErrorTwo)
|
||||||
assert.strictEqual(onResError.firstCall[1], ctx)
|
assert.strictEqual(onResError.firstCall[1], ctx)
|
||||||
|
|
||||||
assert.strictEqual(assertRes.on.secondCall[0], 'finish')
|
assert.strictEqual(assertRes.on.secondCall[0], 'finish')
|
||||||
assert.strictEqual(typeof(assertRes.on.secondCall[1]), 'function')
|
assert.strictEqual(typeof(assertRes.on.secondCall[1]), 'function')
|
||||||
assert.strictEqual(onEnded.callCount, 0)
|
assert.strictEqual(onEnded.callCount, 0)
|
||||||
|
@ -220,7 +220,7 @@ t.describe('#requestStart()', function() {
|
||||||
}), createRes())
|
}), createRes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('calls handleMiddleware correctly', function(cb) {
|
t.test('calls handlers correctly', function(cb) {
|
||||||
const assertError = new Error('test')
|
const assertError = new Error('test')
|
||||||
const assertMiddles = [1, 2]
|
const assertMiddles = [1, 2]
|
||||||
const assertParams = { a: 1, b: 2 }
|
const assertParams = { a: 1, b: 2 }
|
||||||
|
@ -231,12 +231,11 @@ t.describe('#requestStart()', function() {
|
||||||
|
|
||||||
flaska.routers.GET.match = function() {
|
flaska.routers.GET.match = function() {
|
||||||
return {
|
return {
|
||||||
handler: function() {},
|
path: { handlers: assertMiddles, },
|
||||||
middlewares: assertMiddles,
|
|
||||||
params: assertParams,
|
params: assertParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flaska.handleMiddleware = function(ctx, middles, index) {
|
flaska.runHandlers = function(ctx, middles, index) {
|
||||||
assert.strictEqual(index, 0)
|
assert.strictEqual(index, 0)
|
||||||
assert.strictEqual(middles, assertMiddles)
|
assert.strictEqual(middles, assertMiddles)
|
||||||
checkMiddleCtx = ctx
|
checkMiddleCtx = ctx
|
||||||
|
@ -265,7 +264,7 @@ t.describe('#requestStart()', function() {
|
||||||
checkHandlerCtx = ctx
|
checkHandlerCtx = ctx
|
||||||
throw assertError
|
throw assertError
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/:id', handler)
|
flaska.get('/:id', handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
@ -289,7 +288,7 @@ t.describe('#requestStart()', function() {
|
||||||
let handler = function() {
|
let handler = function() {
|
||||||
throw new Error('should not be called')
|
throw new Error('should not be called')
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.on404(on404Error)
|
flaska.on404(on404Error)
|
||||||
flaska.get('/test', function() { throw new Error('should not be called') })
|
flaska.get('/test', function() { throw new Error('should not be called') })
|
||||||
|
@ -307,7 +306,7 @@ t.describe('#requestStart()', function() {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}), createRes())
|
}), createRes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('calls 404 if route handler is not found and supports promise', function(cb) {
|
t.test('calls 404 if route handler is not found and supports promise', function(cb) {
|
||||||
let checkCtx = null
|
let checkCtx = null
|
||||||
const assertError = new Error('should be seen')
|
const assertError = new Error('should be seen')
|
||||||
|
@ -315,7 +314,7 @@ t.describe('#requestStart()', function() {
|
||||||
checkCtx = ctx
|
checkCtx = ctx
|
||||||
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.on404(on404Error)
|
flaska.on404(on404Error)
|
||||||
flaska.get('/test', function() { throw new Error('should not be called') })
|
flaska.get('/test', function() { throw new Error('should not be called') })
|
||||||
|
@ -336,7 +335,7 @@ t.describe('#requestStart()', function() {
|
||||||
t.test(`should handle unexpected errors in on404 correctly`, function(cb) {
|
t.test(`should handle unexpected errors in on404 correctly`, function(cb) {
|
||||||
const assertError = new Error('should be seen')
|
const assertError = new Error('should be seen')
|
||||||
let checkCtx = null
|
let checkCtx = null
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.on404(function(ctx) {
|
flaska.on404(function(ctx) {
|
||||||
checkCtx = ctx
|
checkCtx = ctx
|
||||||
|
@ -360,7 +359,7 @@ t.describe('#requestStart()', function() {
|
||||||
t.test(`should handle unexpected errors in middleware correctly`, function(cb) {
|
t.test(`should handle unexpected errors in middleware correctly`, function(cb) {
|
||||||
const assertError = new Error('should be seen')
|
const assertError = new Error('should be seen')
|
||||||
let checkCtx = null
|
let checkCtx = null
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
let middles = [function(ctx) {
|
let middles = [function(ctx) {
|
||||||
checkCtx = ctx
|
checkCtx = ctx
|
||||||
|
@ -387,13 +386,10 @@ t.describe('#requestStart()', function() {
|
||||||
assert.strictEqual(ctx.params.path, 'test/something/here')
|
assert.strictEqual(ctx.params.path, 'test/something/here')
|
||||||
ctx.body = assertBody
|
ctx.body = assertBody
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/::path', handler)
|
flaska.get('/::path', handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
flaska.handleMiddleware = function() {
|
|
||||||
throw new Error('should not be called')
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestEnd = cb.finish(function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
assert.notOk(err)
|
assert.notOk(err)
|
||||||
|
@ -407,7 +403,7 @@ t.describe('#requestStart()', function() {
|
||||||
}), createRes())
|
}), createRes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('calls handleMiddleware correctly if is promise', function(cb) {
|
t.test('calls runHandlers correctly if is promise', function(cb) {
|
||||||
const assertError = new Error('test')
|
const assertError = new Error('test')
|
||||||
const assertMiddles = [1]
|
const assertMiddles = [1]
|
||||||
|
|
||||||
|
@ -416,11 +412,11 @@ t.describe('#requestStart()', function() {
|
||||||
|
|
||||||
flaska.routers.GET.match = function() {
|
flaska.routers.GET.match = function() {
|
||||||
return {
|
return {
|
||||||
handler: function() {},
|
path: { handlers: function() {}, },
|
||||||
middlewares: assertMiddles,
|
middlewares: assertMiddles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flaska.handleMiddleware = function() {
|
flaska.runHandlers = function() {
|
||||||
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,7 +440,7 @@ t.describe('#requestStart()', function() {
|
||||||
checkHandlerCtx = ctx
|
checkHandlerCtx = ctx
|
||||||
throw assertError
|
throw assertError
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
@ -470,7 +466,7 @@ t.describe('#requestStart()', function() {
|
||||||
checkHandlerCtx = ctx
|
checkHandlerCtx = ctx
|
||||||
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
@ -504,7 +500,7 @@ t.describe('#requestStart()', function() {
|
||||||
ctx.body = assertBody
|
ctx.body = assertBody
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/::path', [middle], handler)
|
flaska.get('/::path', [middle], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
@ -521,7 +517,7 @@ t.describe('#requestStart()', function() {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}), createRes())
|
}), createRes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('calls route handler correctly if promise', function(cb) {
|
t.test('calls route handler correctly if promise', function(cb) {
|
||||||
const assertError = new Error('test')
|
const assertError = new Error('test')
|
||||||
let checkHandlerCtx = null
|
let checkHandlerCtx = null
|
||||||
|
@ -532,7 +528,7 @@ t.describe('#requestStart()', function() {
|
||||||
return Promise.reject(assertError)
|
return Promise.reject(assertError)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/:id', handler)
|
flaska.get('/:id', handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
@ -558,15 +554,11 @@ t.describe('#requestStart()', function() {
|
||||||
ctx.body = assertBody
|
ctx.body = assertBody
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/::path', [], handler)
|
flaska.get('/::path', [], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.handleMiddleware = function() {
|
|
||||||
throw new Error('should not be called')
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestEnd = cb.finish(function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
assert.notOk(err)
|
assert.notOk(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
|
|
128
test/http.body.json.test.mjs
Normal file
128
test/http.body.json.test.mjs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import { Eltro as t, assert, stub } from 'eltro'
|
||||||
|
import { setTimeout } from 'timers/promises'
|
||||||
|
import { Flaska, JsonHandler } from '../flaska.mjs'
|
||||||
|
import Client from './client.mjs'
|
||||||
|
|
||||||
|
const port = 51024
|
||||||
|
const log = {
|
||||||
|
fatal: stub(),
|
||||||
|
error: stub(),
|
||||||
|
warn: stub(),
|
||||||
|
info: stub(),
|
||||||
|
debug: stub(),
|
||||||
|
trace: stub(),
|
||||||
|
log: stub(),
|
||||||
|
}
|
||||||
|
const flaska = new Flaska({ log })
|
||||||
|
const client = new Client(port)
|
||||||
|
|
||||||
|
let reqBody = null
|
||||||
|
|
||||||
|
flaska.after(function(ctx) {
|
||||||
|
if (ctx.aborted) return
|
||||||
|
ctx.log.info(ctx.status)
|
||||||
|
})
|
||||||
|
|
||||||
|
flaska.post('/json', JsonHandler(), function(ctx) {
|
||||||
|
ctx.body = { success: true }
|
||||||
|
reqBody = ctx.req.body
|
||||||
|
})
|
||||||
|
flaska.post('/json/slow', JsonHandler(), async function(ctx) {
|
||||||
|
await setTimeout(300)
|
||||||
|
ctx.body = { success: true }
|
||||||
|
ctx.status = 201
|
||||||
|
reqBody = ctx.req.body
|
||||||
|
})
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
log.fatal.reset()
|
||||||
|
log.error.reset()
|
||||||
|
log.warn.reset()
|
||||||
|
log.info.reset()
|
||||||
|
log.debug.reset()
|
||||||
|
log.trace.reset()
|
||||||
|
log.log.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
return flaska.listenAsync(port)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('/json', function() {
|
||||||
|
t.beforeEach(function() {
|
||||||
|
log.info.reset()
|
||||||
|
})
|
||||||
|
t.test('should return success and store body', async function() {
|
||||||
|
const assertBody = { a: '' }
|
||||||
|
for (let i = 0; i < 1010; i++) {
|
||||||
|
assertBody.a += 'aaaaaaaaaa'
|
||||||
|
}
|
||||||
|
reqBody = null
|
||||||
|
let body = await client.post('/json', assertBody)
|
||||||
|
assert.deepEqual(body, { success: true })
|
||||||
|
assert.deepStrictEqual(reqBody, assertBody)
|
||||||
|
assert.strictEqual(log.info.callCount, 1)
|
||||||
|
assert.strictEqual(log.info.firstCall[0], 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should return and log correctly', async function() {
|
||||||
|
const assertBody = { a: '' }
|
||||||
|
for (let i = 0; i < 1010; i++) {
|
||||||
|
assertBody.a += 'aaaaaaaaaa'
|
||||||
|
}
|
||||||
|
reqBody = null
|
||||||
|
let body = await client.post('/json/slow', assertBody)
|
||||||
|
assert.deepEqual(body, { success: true })
|
||||||
|
assert.deepStrictEqual(reqBody, assertBody)
|
||||||
|
assert.strictEqual(log.info.callCount, 1)
|
||||||
|
assert.strictEqual(log.info.firstCall[0], 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should fail if body is too big', async function() {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
const assertBody = { a: '' }
|
||||||
|
for (let i = 0; i < 10300; i++) {
|
||||||
|
assertBody.a += 'aaaaaaaaaa'
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = await assert.isRejected(client.post('/json', assertBody))
|
||||||
|
|
||||||
|
assert.strictEqual(err.body.status, 413)
|
||||||
|
assert.ok(log.error.called)
|
||||||
|
assert.match(log.error.firstCall[0].message, /10240/)
|
||||||
|
assert.strictEqual(log.error.firstCall[0].status, 413)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should fail if not a valid json', async function() {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
let err = await assert.isRejected(client.customRequest('POST', '/json', 'XXXXX'))
|
||||||
|
|
||||||
|
assert.strictEqual(err.body.status, 400)
|
||||||
|
assert.match(err.body.message, /invalid json/i)
|
||||||
|
assert.match(err.body.message, /token[^X]+X/i)
|
||||||
|
assert.strictEqual(err.body.request, 'XXXXX')
|
||||||
|
assert.strictEqual(log.error.callCount, 1)
|
||||||
|
assert.match(log.error.firstCall[0].message, /invalid json/i)
|
||||||
|
assert.match(log.error.firstCall[0].message, /token[^X]+X/i)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should handle incomplete requests correctly', async function() {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
let req = await client.customRequest('POST', '/json', 'aaaa', { returnRequest: true })
|
||||||
|
|
||||||
|
req.write('part1')
|
||||||
|
|
||||||
|
await setTimeout(20)
|
||||||
|
|
||||||
|
req.destroy()
|
||||||
|
|
||||||
|
await setTimeout(20)
|
||||||
|
|
||||||
|
assert.strictEqual(log.error.callCount, 0)
|
||||||
|
assert.strictEqual(log.info.callCount, 0)
|
||||||
|
// assert.strictEqual(log.info.firstCall[0].message, 'aborted')
|
||||||
|
})
|
||||||
|
})
|
|
@ -4,10 +4,10 @@ import fs from 'fs/promises'
|
||||||
import formidable from 'formidable'
|
import formidable from 'formidable'
|
||||||
import { Eltro as t, assert, stub } from 'eltro'
|
import { Eltro as t, assert, stub } from 'eltro'
|
||||||
import { setTimeout } from 'timers/promises'
|
import { setTimeout } from 'timers/promises'
|
||||||
import { Flaska, JsonHandler, FormidableHandler, FileResponse } from '../flaska.mjs'
|
import { Flaska, FormidableHandler, FileResponse } from '../flaska.mjs'
|
||||||
import Client from './client.mjs'
|
import Client from './client.mjs'
|
||||||
|
|
||||||
const port = 51024
|
const port = 51025
|
||||||
const log = {
|
const log = {
|
||||||
fatal: stub(),
|
fatal: stub(),
|
||||||
error: stub(),
|
error: stub(),
|
||||||
|
@ -20,7 +20,6 @@ const log = {
|
||||||
const flaska = new Flaska({ log })
|
const flaska = new Flaska({ log })
|
||||||
const client = new Client(port)
|
const client = new Client(port)
|
||||||
|
|
||||||
let reqBody = null
|
|
||||||
let file = null
|
let file = null
|
||||||
let uploaded = []
|
let uploaded = []
|
||||||
|
|
||||||
|
@ -32,16 +31,6 @@ flaska.after(function(ctx) {
|
||||||
flaska.get('/', function(ctx) {
|
flaska.get('/', function(ctx) {
|
||||||
ctx.body = { status: true }
|
ctx.body = { status: true }
|
||||||
})
|
})
|
||||||
flaska.post('/json', JsonHandler(), function(ctx) {
|
|
||||||
ctx.body = { success: true }
|
|
||||||
reqBody = ctx.req.body
|
|
||||||
})
|
|
||||||
flaska.post('/json/slow', JsonHandler(), async function(ctx) {
|
|
||||||
await setTimeout(300)
|
|
||||||
ctx.body = { success: true }
|
|
||||||
ctx.status = 201
|
|
||||||
reqBody = ctx.req.body
|
|
||||||
})
|
|
||||||
flaska.get('/timeout', function(ctx) {
|
flaska.get('/timeout', function(ctx) {
|
||||||
return new Promise(function() {})
|
return new Promise(function() {})
|
||||||
})
|
})
|
||||||
|
@ -96,90 +85,11 @@ t.describe('/', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('/json', function() {
|
|
||||||
t.beforeEach(function() {
|
|
||||||
log.info.reset()
|
|
||||||
})
|
|
||||||
t.test('should return success and store body', async function() {
|
|
||||||
const assertBody = { a: '' }
|
|
||||||
for (let i = 0; i < 1010; i++) {
|
|
||||||
assertBody.a += 'aaaaaaaaaa'
|
|
||||||
}
|
|
||||||
reqBody = null
|
|
||||||
let body = await client.post('/json', assertBody)
|
|
||||||
assert.deepEqual(body, { success: true })
|
|
||||||
assert.deepStrictEqual(reqBody, assertBody)
|
|
||||||
assert.strictEqual(log.info.callCount, 1)
|
|
||||||
assert.strictEqual(log.info.firstCall[0], 200)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should return and log correctly', async function() {
|
|
||||||
const assertBody = { a: '' }
|
|
||||||
for (let i = 0; i < 1010; i++) {
|
|
||||||
assertBody.a += 'aaaaaaaaaa'
|
|
||||||
}
|
|
||||||
reqBody = null
|
|
||||||
let body = await client.post('/json/slow', assertBody)
|
|
||||||
assert.deepEqual(body, { success: true })
|
|
||||||
assert.deepStrictEqual(reqBody, assertBody)
|
|
||||||
assert.strictEqual(log.info.callCount, 1)
|
|
||||||
assert.strictEqual(log.info.firstCall[0], 201)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should fail if body is too big', async function() {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
const assertBody = { a: '' }
|
|
||||||
for (let i = 0; i < 10300; i++) {
|
|
||||||
assertBody.a += 'aaaaaaaaaa'
|
|
||||||
}
|
|
||||||
|
|
||||||
let err = await assert.isRejected(client.post('/json', assertBody))
|
|
||||||
|
|
||||||
assert.strictEqual(err.body.status, 413)
|
|
||||||
assert.ok(log.error.called)
|
|
||||||
assert.match(log.error.firstCall[0].message, /10240/)
|
|
||||||
assert.strictEqual(log.error.firstCall[0].status, 413)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should fail if not a valid json', async function() {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
let err = await assert.isRejected(client.customRequest('POST', '/json', 'XXXXX'))
|
|
||||||
|
|
||||||
assert.strictEqual(err.body.status, 400)
|
|
||||||
assert.match(err.body.message, /invalid json/i)
|
|
||||||
assert.match(err.body.message, /token[^X]+X/i)
|
|
||||||
assert.strictEqual(err.body.request, 'XXXXX')
|
|
||||||
assert.strictEqual(log.error.callCount, 1)
|
|
||||||
assert.match(log.error.firstCall[0].message, /invalid json/i)
|
|
||||||
assert.match(log.error.firstCall[0].message, /token[^X]+X/i)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should handle incomplete requests correctly', async function() {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
let req = await client.customRequest('POST', '/json', 'aaaa', { returnRequest: true })
|
|
||||||
|
|
||||||
req.write('part1')
|
|
||||||
|
|
||||||
await setTimeout(20)
|
|
||||||
|
|
||||||
req.destroy()
|
|
||||||
|
|
||||||
await setTimeout(20)
|
|
||||||
|
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
|
||||||
assert.strictEqual(log.info.callCount, 0)
|
|
||||||
// assert.strictEqual(log.info.firstCall[0].message, 'aborted')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.describe('/timeout', function() {
|
t.describe('/timeout', function() {
|
||||||
t.test('server should handle timeout', async function() {
|
t.test('server should handle timeout', async function() {
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
let err = await assert.isRejected(client.customRequest('GET', '/timeout', JSON.stringify({}), { timeout: 20 }))
|
let err = await assert.isRejected(client.customRequest('GET', '/timeout', null, { timeout: 20 }))
|
||||||
|
|
||||||
assert.match(err.message, /timed out/)
|
assert.match(err.message, /timed out/)
|
||||||
assert.notOk(log.error.called)
|
assert.notOk(log.error.called)
|
||||||
|
@ -201,7 +111,7 @@ t.describe('/file', function() {
|
||||||
|
|
||||||
assert.ok(target.closed)
|
assert.ok(target.closed)
|
||||||
assert.ok(file.closed)
|
assert.ok(file.closed)
|
||||||
|
|
||||||
let [statSource, statTarget] = await Promise.all([
|
let [statSource, statTarget] = await Promise.all([
|
||||||
fs.stat('./test/test.png'),
|
fs.stat('./test/test.png'),
|
||||||
fs.stat('./test_tmp.png'),
|
fs.stat('./test_tmp.png'),
|
||||||
|
@ -226,11 +136,11 @@ t.describe('/file', function() {
|
||||||
assert.notOk(file.closed)
|
assert.notOk(file.closed)
|
||||||
|
|
||||||
req.destroy()
|
req.destroy()
|
||||||
|
|
||||||
while (!file.closed) {
|
while (!file.closed) {
|
||||||
await setTimeout(10)
|
await setTimeout(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
assert.strictEqual(log.error.callCount, 0)
|
||||||
assert.strictEqual(log.info.callCount, 0)
|
assert.strictEqual(log.info.callCount, 0)
|
||||||
|
|
||||||
|
@ -285,7 +195,7 @@ t.describe('/filehandle', function() {
|
||||||
assert.strictEqual(res.data, 'llo ')
|
assert.strictEqual(res.data, 'llo ')
|
||||||
assert.strictEqual(res.headers['content-length'], '4')
|
assert.strictEqual(res.headers['content-length'], '4')
|
||||||
|
|
||||||
|
|
||||||
res = await client.customRequest('GET', '/filehandle', null, { getRaw: true, agent: agent,
|
res = await client.customRequest('GET', '/filehandle', null, { getRaw: true, agent: agent,
|
||||||
headers: {
|
headers: {
|
||||||
'Range': 'bytes=0-0'
|
'Range': 'bytes=0-0'
|
||||||
|
@ -401,15 +311,15 @@ t.describe('HEAD', function() {
|
||||||
t.describe('/file', function() {
|
t.describe('/file', function() {
|
||||||
t.test('server return HEAD for pipes', async function() {
|
t.test('server return HEAD for pipes', async function() {
|
||||||
log.error.reset()
|
log.error.reset()
|
||||||
|
|
||||||
let res = await client.customRequest('HEAD', '/file', null, { getRaw: true, agent: agent })
|
let res = await client.customRequest('HEAD', '/file', null, { getRaw: true, agent: agent })
|
||||||
|
|
||||||
while (!file.closed) {
|
while (!file.closed) {
|
||||||
await setTimeout(10)
|
await setTimeout(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(file.closed)
|
assert.ok(file.closed)
|
||||||
|
|
||||||
let statSource = await fs.stat('./test/test.png')
|
let statSource = await fs.stat('./test/test.png')
|
||||||
|
|
||||||
assert.strictEqual(res.data, '')
|
assert.strictEqual(res.data, '')
|
||||||
|
@ -418,37 +328,37 @@ t.describe('HEAD', function() {
|
||||||
})
|
})
|
||||||
t.test('server should autoclose body file handles on errors', async function() {
|
t.test('server should autoclose body file handles on errors', async function() {
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
file = null
|
file = null
|
||||||
|
|
||||||
let req = await client.customRequest('HEAD', '/file/leak', null, { returnRequest: true })
|
let req = await client.customRequest('HEAD', '/file/leak', null, { returnRequest: true })
|
||||||
|
|
||||||
req.end()
|
req.end()
|
||||||
|
|
||||||
while (!file) {
|
while (!file) {
|
||||||
await setTimeout(10)
|
await setTimeout(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(file)
|
assert.ok(file)
|
||||||
assert.notOk(file.closed)
|
assert.notOk(file.closed)
|
||||||
|
|
||||||
req.destroy()
|
req.destroy()
|
||||||
|
|
||||||
while (!file.closed) {
|
while (!file.closed) {
|
||||||
await setTimeout(10)
|
await setTimeout(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
assert.strictEqual(log.error.callCount, 0)
|
||||||
assert.strictEqual(log.info.callCount, 0)
|
assert.strictEqual(log.info.callCount, 0)
|
||||||
|
|
||||||
assert.ok(file.closed)
|
assert.ok(file.closed)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('/filehandle', function() {
|
t.describe('/filehandle', function() {
|
||||||
t.test('server should send correctly', async function() {
|
t.test('server should send correctly', async function() {
|
||||||
log.error.reset()
|
log.error.reset()
|
||||||
|
|
||||||
let res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent })
|
let res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent })
|
||||||
assert.strictEqual(res.status, 200)
|
assert.strictEqual(res.status, 200)
|
||||||
assert.strictEqual(res.headers['content-length'], '11')
|
assert.strictEqual(res.headers['content-length'], '11')
|
||||||
|
@ -459,7 +369,7 @@ t.describe('HEAD', function() {
|
||||||
let etag = res.headers['etag']
|
let etag = res.headers['etag']
|
||||||
assert.ok(d.getTime())
|
assert.ok(d.getTime())
|
||||||
assert.strictEqual(res.data, '')
|
assert.strictEqual(res.data, '')
|
||||||
|
|
||||||
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
||||||
headers: {
|
headers: {
|
||||||
'If-Modified-Since': d.toUTCString()
|
'If-Modified-Since': d.toUTCString()
|
||||||
|
@ -468,7 +378,7 @@ t.describe('HEAD', function() {
|
||||||
assert.strictEqual(res.status, 304)
|
assert.strictEqual(res.status, 304)
|
||||||
assert.strictEqual(res.data, '')
|
assert.strictEqual(res.data, '')
|
||||||
assert.strictEqual(res.headers['etag'], etag)
|
assert.strictEqual(res.headers['etag'], etag)
|
||||||
|
|
||||||
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
||||||
headers: {
|
headers: {
|
||||||
'If-None-Match': etag
|
'If-None-Match': etag
|
||||||
|
@ -477,7 +387,7 @@ t.describe('HEAD', function() {
|
||||||
assert.strictEqual(res.status, 304)
|
assert.strictEqual(res.status, 304)
|
||||||
assert.strictEqual(res.data, '')
|
assert.strictEqual(res.data, '')
|
||||||
assert.strictEqual(res.headers['etag'], etag)
|
assert.strictEqual(res.headers['etag'], etag)
|
||||||
|
|
||||||
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
||||||
headers: {
|
headers: {
|
||||||
'Range': 'bytes=2-5'
|
'Range': 'bytes=2-5'
|
||||||
|
@ -486,8 +396,8 @@ t.describe('HEAD', function() {
|
||||||
assert.strictEqual(res.status, 206)
|
assert.strictEqual(res.status, 206)
|
||||||
assert.strictEqual(res.data, '')
|
assert.strictEqual(res.data, '')
|
||||||
assert.strictEqual(res.headers['content-length'], '4')
|
assert.strictEqual(res.headers['content-length'], '4')
|
||||||
|
|
||||||
|
|
||||||
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
|
||||||
headers: {
|
headers: {
|
||||||
'Range': 'bytes=0-0'
|
'Range': 'bytes=0-0'
|
||||||
|
@ -497,7 +407,7 @@ t.describe('HEAD', function() {
|
||||||
assert.strictEqual(res.data, '')
|
assert.strictEqual(res.data, '')
|
||||||
assert.strictEqual(res.headers['content-length'], '1')
|
assert.strictEqual(res.headers['content-length'], '1')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.after(function() {
|
t.after(function() {
|
||||||
agent.destroy()
|
agent.destroy()
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,7 @@ t.describe('#addRoute()', function() {
|
||||||
assert.throws(function() { router.addRoute(':test') }, /forward slash/)
|
assert.throws(function() { router.addRoute(':test') }, /forward slash/)
|
||||||
assert.throws(function() { router.addRoute('test/test2') }, /forward slash/)
|
assert.throws(function() { router.addRoute('test/test2') }, /forward slash/)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('fail if missing handler', function() {
|
t.test('fail if missing handler', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
assert.throws(function() { router.addRoute('/') }, /handler/)
|
assert.throws(function() { router.addRoute('/') }, /handler/)
|
||||||
|
@ -45,8 +45,21 @@ t.describe('#addRoute()', function() {
|
||||||
router.addRoute('/:test/bla', function() {})
|
router.addRoute('/:test/bla', function() {})
|
||||||
router.addRoute('/bla/bla', function() {})
|
router.addRoute('/bla/bla', function() {})
|
||||||
router.addRoute('/bla/bla/bla', function() {})
|
router.addRoute('/bla/bla/bla', function() {})
|
||||||
assert.throws(function() { router.addRoute('/:asdf/', function() {}) }, /param/)
|
assert.throws(function() { router.addRoute('/:asdf', function() {}) }, /existing/)
|
||||||
assert.throws(function() { router.addRoute('/:test/asdf/:foobar', function() {}) }, /param/)
|
assert.throws(function() { router.addRoute('/:test/asdf/:foobar', function() {}) }, /existing/)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('fail if adding anything after a fullparam', function() {
|
||||||
|
let router = new FlaskaRouter()
|
||||||
|
assert.throws(function() { router.addRoute('/::bla/:bla', function() {}) }, /after/)
|
||||||
|
assert.throws(function() { router.addRoute('/:test/::bla/test', function() {}) }, /after/)
|
||||||
|
assert.throws(function() { router.addRoute('/::test/bla', function() {}) }, /after/)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should work with param and full param side by side', function() {
|
||||||
|
let router = new FlaskaRouter()
|
||||||
|
router.addRoute('/:bla', function() {})
|
||||||
|
router.addRoute('/::bla', function() {})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('fail if adding multiple fullparam', function() {
|
t.test('fail if adding multiple fullparam', function() {
|
||||||
|
@ -56,10 +69,8 @@ t.describe('#addRoute()', function() {
|
||||||
router.addRoute('/:test/bla', function() {})
|
router.addRoute('/:test/bla', function() {})
|
||||||
router.addRoute('/:test/::bla', function() {})
|
router.addRoute('/:test/::bla', function() {})
|
||||||
router.addRoute('/bla/bla/bla', function() {})
|
router.addRoute('/bla/bla/bla', function() {})
|
||||||
assert.throws(function() { router.addRoute('/:test/asdf/::bla/fail', function() {}) }, /full.+param/)
|
assert.throws(function() { router.addRoute('/:test/asdf/::bla', function() {}) }, /existing/)
|
||||||
assert.throws(function() { router.addRoute('/:test/::bla/test', function() {}) }, /full.+param/)
|
assert.throws(function() { router.addRoute('/:test/::bla', function() {}) }, /existing/)
|
||||||
assert.throws(function() { router.addRoute('/:test/:bla', function() {}) }, /full.+param/)
|
|
||||||
assert.throws(function() { router.addRoute('/::test', function() {}) }, /partial.+param/)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('add route correctly', function() {
|
t.test('add route correctly', function() {
|
||||||
|
@ -67,39 +78,7 @@ t.describe('#addRoute()', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/a/b/c', assertHandler)
|
router.addRoute('/a/b/c', assertHandler)
|
||||||
let result = router.match('/a/b/c')
|
let result = router.match('/a/b/c')
|
||||||
assert.strictEqual(result.handler, assertHandler)
|
assert.strictEqual(result.path.handlers[0], assertHandler)
|
||||||
})
|
|
||||||
|
|
||||||
t.test('add param route correctly', function() {
|
|
||||||
let assertHandler = function() { return 1 }
|
|
||||||
let router = new FlaskaRouter()
|
|
||||||
router.addRoute('/a/:b/c', assertHandler)
|
|
||||||
assert.ok(router.root.children.get('a'))
|
|
||||||
assert.ok(router.root.children.get('a').children.get('__param'))
|
|
||||||
assert.strictEqual(router.root.children.get('a').children.get('__param').paramName, 'b')
|
|
||||||
assert.ok(router.root.children.get('a').children.get('__param').children.get('c'))
|
|
||||||
assert.strictEqual(router.root.children.get('a').children.get('__param').children.get('c').handler, assertHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('add full param route correctly', function() {
|
|
||||||
let assertHandler = function() { return 1 }
|
|
||||||
let router = new FlaskaRouter()
|
|
||||||
router.addRoute('/a/::b', assertHandler)
|
|
||||||
assert.ok(router.root.children.get('a'))
|
|
||||||
assert.ok(router.root.children.get('a').children.get('__fullparam'))
|
|
||||||
assert.strictEqual(router.root.children.get('a').children.get('__fullparam').fullparamName, 'b')
|
|
||||||
assert.strictEqual(router.root.children.get('a').children.get('__fullparam').handler, assertHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('add param route correctly', function() {
|
|
||||||
let assertHandler = function() { return 1 }
|
|
||||||
let router = new FlaskaRouter()
|
|
||||||
router.addRoute('/a/:b/c', assertHandler)
|
|
||||||
assert.ok(router.root.children.get('a'))
|
|
||||||
assert.ok(router.root.children.get('a').children.get('__param'))
|
|
||||||
assert.strictEqual(router.root.children.get('a').children.get('__param').paramName, 'b')
|
|
||||||
assert.ok(router.root.children.get('a').children.get('__param').children.get('c'))
|
|
||||||
assert.strictEqual(router.root.children.get('a').children.get('__param').children.get('c').handler, assertHandler)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('support single middlewares correctly', function() {
|
t.test('support single middlewares correctly', function() {
|
||||||
|
@ -107,10 +86,10 @@ t.describe('#addRoute()', function() {
|
||||||
let assertMiddleware = function() { return 1 }
|
let assertMiddleware = function() { return 1 }
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/a', assertMiddleware, assertHandler)
|
router.addRoute('/a', assertMiddleware, assertHandler)
|
||||||
assert.ok(router.root.children.get('a'))
|
let result = router.match('/a')
|
||||||
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
|
assert.strictEqual(result.path.handlers.length, 2)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
|
assert.strictEqual(result.path.handlers[0], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
|
assert.strictEqual(result.path.handlers[1], assertHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('support multi middlewares correctly', function() {
|
t.test('support multi middlewares correctly', function() {
|
||||||
|
@ -119,15 +98,15 @@ t.describe('#addRoute()', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/a', [assertMiddleware], assertHandler)
|
router.addRoute('/a', [assertMiddleware], assertHandler)
|
||||||
router.addRoute('/b', [assertMiddleware, assertMiddleware], assertHandler)
|
router.addRoute('/b', [assertMiddleware, assertMiddleware], assertHandler)
|
||||||
assert.ok(router.root.children.get('a'))
|
let resultA = router.match('/a')
|
||||||
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
|
assert.strictEqual(resultA.path.handlers.length, 2)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
|
assert.strictEqual(resultA.path.handlers[0], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
|
assert.strictEqual(resultA.path.handlers[1], assertHandler)
|
||||||
assert.ok(router.root.children.get('b'))
|
let resultB = router.match('/b')
|
||||||
assert.strictEqual(router.root.children.get('b').handler, assertHandler)
|
assert.strictEqual(resultB.path.handlers.length, 3)
|
||||||
assert.strictEqual(router.root.children.get('b').middlewares.length, 2)
|
assert.strictEqual(resultB.path.handlers[0], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('b').middlewares[0], assertMiddleware)
|
assert.strictEqual(resultB.path.handlers[1], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('b').middlewares[1], assertMiddleware)
|
assert.strictEqual(resultB.path.handlers[2], assertHandler)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -136,94 +115,47 @@ t.describe('#match()', function() {
|
||||||
let assertMatched = false
|
let assertMatched = false
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test', function() { assertMatched = true })
|
router.addRoute('/test', function() { assertMatched = true })
|
||||||
let result = router.match('/test')
|
let result = router.match('/test').path
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.handlers[0]()
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
|
||||||
|
|
||||||
// Test with extra slash at the end
|
|
||||||
assertMatched = false
|
|
||||||
result = router.match('/test/')
|
|
||||||
assert.ok(result.handler)
|
|
||||||
assert.ok(result.middlewares)
|
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
assert.strictEqual(assertMatched, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.test('return middlewares in handlers', function() {
|
||||||
t.test('return middlewares', function() {
|
|
||||||
let assertMatched = false
|
let assertMatched = false
|
||||||
let assertMiddleware = function() { assertMatched = true }
|
let assertMiddleware = function() { assertMatched = true }
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test', assertMiddleware, function() { })
|
router.addRoute('/test', assertMiddleware, function() { })
|
||||||
let result = router.match('/test')
|
let result = router.match('/test').path
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.handlers.length, 2)
|
||||||
assert.ok(result.middlewares)
|
result.handlers[0]()
|
||||||
assert.strictEqual(result.middlewares.length, 1)
|
|
||||||
result.middlewares[0]()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
|
||||||
|
|
||||||
// Test with extra slash at the end
|
|
||||||
assertMatched = false
|
|
||||||
result = router.match('/test/')
|
|
||||||
assert.ok(result.handler)
|
|
||||||
assert.ok(result.middlewares)
|
|
||||||
assert.strictEqual(result.middlewares.length, 1)
|
|
||||||
result.middlewares[0]()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
assert.strictEqual(assertMatched, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('match variable paths', function() {
|
t.test('match variable paths', function() {
|
||||||
const assertParameter = 'bla'
|
const assertParameter = 'bla'
|
||||||
let assertMatched = false
|
let assertMatched = false
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test/:id', function() { assertMatched = true })
|
router.addRoute('/test/:id', function() { assertMatched = true })
|
||||||
let result = router.match('/test/' + assertParameter)
|
let result = router.match('/test/' + assertParameter)
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.path.handlers[0]()
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
|
||||||
assert.strictEqual(result.params.id, assertParameter)
|
|
||||||
|
|
||||||
// Test with extra slash at the end
|
|
||||||
assertMatched = false
|
|
||||||
result = router.match('/test/' + assertParameter + '/')
|
|
||||||
assert.ok(result.handler)
|
|
||||||
assert.ok(result.middlewares)
|
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
assert.strictEqual(assertMatched, true)
|
||||||
assert.strictEqual(result.params.id, assertParameter)
|
assert.strictEqual(result.params.id, assertParameter)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('match full path variable paths', function() {
|
t.test('match full path variable paths', function() {
|
||||||
const assertParameter = 'bla/bla/bla'
|
const assertParameter = 'bla/bla/bla'
|
||||||
let assertMatched = false
|
let assertMatched = false
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test/::id', function() { assertMatched = true })
|
router.addRoute('/test/::id', function() { assertMatched = true })
|
||||||
let result = router.match('/test/' + assertParameter)
|
let result = router.match('/test/' + assertParameter)
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.path.handlers[0]()
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
|
||||||
assert.strictEqual(result.params.id, assertParameter)
|
|
||||||
|
|
||||||
// Test with extra slash at the end
|
|
||||||
assertMatched = false
|
|
||||||
result = router.match('/test/' + assertParameter + '/')
|
|
||||||
assert.ok(result.handler)
|
|
||||||
assert.ok(result.middlewares)
|
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
assert.strictEqual(assertMatched, true)
|
||||||
assert.strictEqual(result.params.id, assertParameter)
|
assert.strictEqual(result.params.id, assertParameter)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('match full path root path properly', function() {
|
t.test('match full path root path properly', function() {
|
||||||
const assertParamFunc = function() { }
|
const assertParamFunc = function() { }
|
||||||
const assertFullFunc = function() { }
|
const assertFullFunc = function() { }
|
||||||
|
@ -231,15 +163,13 @@ t.describe('#match()', function() {
|
||||||
router.addRoute('/test/:bla', assertParamFunc)
|
router.addRoute('/test/:bla', assertParamFunc)
|
||||||
router.addRoute('/::id', assertFullFunc)
|
router.addRoute('/::id', assertFullFunc)
|
||||||
let result = router.match('/test/123')
|
let result = router.match('/test/123')
|
||||||
assert.strictEqual(result.handler, assertParamFunc)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
assert.strictEqual(result.path.handlers[0], assertParamFunc)
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
assert.strictEqual(result.params.bla, '123')
|
assert.strictEqual(result.params.bla, '123')
|
||||||
|
|
||||||
result = router.match('/test/123/asdf')
|
result = router.match('/test/123/asdf')
|
||||||
assert.strictEqual(result.handler, assertFullFunc)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
assert.strictEqual(result.path.handlers[0], assertFullFunc)
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
assert.strictEqual(result.params.id, 'test/123/asdf')
|
assert.strictEqual(result.params.id, 'test/123/asdf')
|
||||||
assert.notOk(result.params.bla)
|
assert.notOk(result.params.bla)
|
||||||
})
|
})
|
||||||
|
@ -250,19 +180,8 @@ t.describe('#match()', function() {
|
||||||
router.addRoute('/test/:id', function() { assertMatched = false })
|
router.addRoute('/test/:id', function() { assertMatched = false })
|
||||||
router.addRoute('/test/:id/test1', function() { })
|
router.addRoute('/test/:id/test1', function() { })
|
||||||
let result = router.match('/test/asdf/test1')
|
let result = router.match('/test/asdf/test1')
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.path.handlers[0]()
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
|
||||||
assert.strictEqual(result.params.id, 'asdf')
|
|
||||||
|
|
||||||
// Test with extra slash at the end
|
|
||||||
result = router.match('/test/asdf/test1/')
|
|
||||||
assert.ok(result.handler)
|
|
||||||
assert.ok(result.middlewares)
|
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
result.handler()
|
|
||||||
assert.strictEqual(assertMatched, true)
|
assert.strictEqual(assertMatched, true)
|
||||||
assert.strictEqual(result.params.id, 'asdf')
|
assert.strictEqual(result.params.id, 'asdf')
|
||||||
})
|
})
|
||||||
|
@ -277,24 +196,25 @@ t.describe('#match()', function() {
|
||||||
router.addRoute('/foo/::path', assertFunction)
|
router.addRoute('/foo/::path', assertFunction)
|
||||||
router.addRoute('/::path', assertFailFunction)
|
router.addRoute('/::path', assertFailFunction)
|
||||||
|
|
||||||
assert.strictEqual(router.match('/test/123').handler, assertFunction)
|
assert.strictEqual(router.match('/test/123').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/test/asdfasdg').handler, assertFunction)
|
assert.strictEqual(router.match('/test/asdfasdg').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/test/test/sdafsda').handler, assertFunction)
|
assert.strictEqual(router.match('/test/test/sdafsda').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').handler, assertFunction)
|
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/foo/123').handler, assertFunction)
|
assert.strictEqual(router.match('/foo/123').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/foo/bar/baz/test').handler, assertFunction)
|
assert.strictEqual(router.match('/foo/bar/baz/test').path.handlers[0], assertFunction)
|
||||||
assert.ok(router.match('/test/123/yweherher/reher/h34h34/'))
|
assert.ok(router.match('/test/123/yweherher/reher/h34h34/'))
|
||||||
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').handler, assertFailFunction)
|
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').path.handlers[0], assertFailFunction)
|
||||||
assert.ok(router.match('/test/foo/bar'))
|
assert.ok(router.match('/test/foo/bar'))
|
||||||
assert.strictEqual(router.match('/test/foo/bar').handler, assertFailFunction)
|
assert.strictEqual(router.match('/test/foo/bar').path.handlers[0], assertFailFunction)
|
||||||
assert.ok(router.match('/'))
|
assert.ok(router.match('/'))
|
||||||
assert.strictEqual(router.match('/').handler, assertFailFunction)
|
assert.strictEqual(router.match('/').path.handlers[0], assertFailFunction)
|
||||||
assert.ok(router.match('/something/else/goes/here'))
|
assert.ok(router.match('/something/else/goes/here'))
|
||||||
assert.strictEqual(router.match('/something/else/goes/here').handler, assertFailFunction)
|
assert.strictEqual(router.match('/something/else/goes/here').path.handlers[0], assertFailFunction)
|
||||||
|
|
||||||
router.addRoute('/', assertRootFunction)
|
router.addRoute('/', assertRootFunction)
|
||||||
|
router.compile()
|
||||||
assert.ok(router.match('/'))
|
assert.ok(router.match('/'))
|
||||||
assert.strictEqual(router.match('/').handler, assertRootFunction)
|
assert.strictEqual(router.match('/').path.handlers[0], assertRootFunction)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('return null when no match is found', function() {
|
t.test('return null when no match is found', function() {
|
||||||
|
|
Loading…
Reference in a new issue