buffer and charCodeAt impl

This commit is contained in:
Jonatan Nilsson 2024-11-02 23:40:54 +00:00
parent 2e1dadbdea
commit f48d63038d
10 changed files with 2966 additions and 450 deletions

117
benchmark/set_arr.mjs Normal file
View 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();

View file

@ -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'
} }
@ -403,190 +393,312 @@ 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 SlashCode = '/'.charCodeAt(0)
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 && false) {
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' });
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: {}
})
} }
}
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) {
let pathIndex = paths.indexOf(branch.path)
if (pathIndex < 0) {
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
}
let output = '\n' + indentString + `return {`
output += '\n' + indentString + ` path: paths[${pathIndex}],`
if (params.length) {
output += '\n' + indentString + ` params: {`
for (let param of params) {
output += '\n' + indentString + ` ${param[0]}: s${param[1]},`
}
output += '\n' + indentString + ` },`
} else {
output += '\n' + indentString + ` params: {},`
}
output += '\n' + indentString + `}`
return output
}
__treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = []) {
let output = ''
let indentation = spaces.slice(0, (indent - params.length) * 2)
let addEndBracket = true
for (let i = 0; i < branches.length; i++) {
let branch = branches[i]
if (i > 0) {
if (!branch.isParams && !branch.isFullParams) {
output += ' else '
} else { } 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 (str.length === ${this.__getIndex(indent, 1, params)}) {`
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params)
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)
} else if (branch.path) {
output += '\n' + indentation + `if (str.length === ${this.__getIndex(indent, 0, params)}) {`
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params)
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())
}
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 output = 'return function RBufferStrSliceClosure(str) {'
handler: output.handler, if (staticList.size > 0) {
middlewares: output.middlewares, output += '\n let checkStatic = staticList.get(str)'
params: params, output += '\n if(checkStatic) {'
} output += '\n return checkStatic'
output += '\n }'
} }
return null if (tree.length) {
output += '\n ' + this.__treeIntoCompiledCodeBranch(paths, tree, 1, [])
}
output += '\n return null'
output += '\n}'
//console.log(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)
} }
} }
@ -754,12 +866,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)
@ -894,37 +1000,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 +1046,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
} }
@ -1060,6 +1154,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() {
@ -1121,7 +1221,7 @@ 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)
}) })
}) })
} }

1229
flaska_buffer.mjs Normal file

File diff suppressed because one or more lines are too long

1128
flaska_old.mjs Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,12 +1,22 @@
import { Flaska } from './flaska.mjs' import { Flaska } from './flaska.mjs'
const port = 51024 const port = 51026
const flaska = new Flaska({}, ) const flaska = new Flaska({}, )
flaska.devMode()
flaska.get('/', function(ctx) { flaska.get('/', function(ctx) {
ctx.body = { status: true } ctx.body = { status: true }
}) })
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/a', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/error', function(ctx) { flaska.get('/error', function(ctx) {
process.exit(1) process.exit(1)
}) })

View file

@ -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 },

View file

@ -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
@ -391,9 +390,6 @@ t.describe('#requestStart()', function() {
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) })
} }
@ -563,10 +559,6 @@ t.describe('#requestStart()', function() {
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)

View file

@ -7,7 +7,7 @@ import { setTimeout } from 'timers/promises'
import { Flaska, 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(),

View file

@ -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,43 +115,20 @@ 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)
}) })
@ -182,20 +138,8 @@ t.describe('#match()', function() {
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)
}) })
@ -206,20 +150,8 @@ t.describe('#match()', function() {
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)
}) })
@ -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() {

20
test_old.mjs Normal file
View file

@ -0,0 +1,20 @@
import { Flaska } from './flaska_buffer.mjs'
const port = 51027
const flaska = new Flaska({}, )
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/error', function(ctx) {
process.exit(1)
})
flaska.listen(port, function() {
console.log('listening on port', port)
})