First commit
This commit is contained in:
170
node_modules/find-my-way/lib/constrainer.js
generated
vendored
Normal file
170
node_modules/find-my-way/lib/constrainer.js
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
'use strict'
|
||||
|
||||
const acceptVersionStrategy = require('./strategies/accept-version')
|
||||
const acceptHostStrategy = require('./strategies/accept-host')
|
||||
const assert = require('node:assert')
|
||||
|
||||
class Constrainer {
|
||||
constructor (customStrategies) {
|
||||
this.strategies = {
|
||||
version: acceptVersionStrategy,
|
||||
host: acceptHostStrategy
|
||||
}
|
||||
|
||||
this.strategiesInUse = new Set()
|
||||
this.asyncStrategiesInUse = new Set()
|
||||
|
||||
// validate and optimize prototypes of given custom strategies
|
||||
if (customStrategies) {
|
||||
for (const strategy of Object.values(customStrategies)) {
|
||||
this.addConstraintStrategy(strategy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isStrategyUsed (strategyName) {
|
||||
return this.strategiesInUse.has(strategyName) ||
|
||||
this.asyncStrategiesInUse.has(strategyName)
|
||||
}
|
||||
|
||||
hasConstraintStrategy (strategyName) {
|
||||
const customConstraintStrategy = this.strategies[strategyName]
|
||||
if (customConstraintStrategy !== undefined) {
|
||||
return customConstraintStrategy.isCustom ||
|
||||
this.isStrategyUsed(strategyName)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
addConstraintStrategy (strategy) {
|
||||
assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.')
|
||||
assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.')
|
||||
assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.')
|
||||
|
||||
if (this.strategies[strategy.name] && this.strategies[strategy.name].isCustom) {
|
||||
throw new Error(`There already exists a custom constraint with the name ${strategy.name}.`)
|
||||
}
|
||||
|
||||
if (this.isStrategyUsed(strategy.name)) {
|
||||
throw new Error(`There already exists a route with ${strategy.name} constraint.`)
|
||||
}
|
||||
|
||||
strategy.isCustom = true
|
||||
strategy.isAsync = strategy.deriveConstraint.length === 3
|
||||
this.strategies[strategy.name] = strategy
|
||||
|
||||
if (strategy.mustMatchWhenDerived) {
|
||||
this.noteUsage({ [strategy.name]: strategy })
|
||||
}
|
||||
}
|
||||
|
||||
deriveConstraints (req, ctx, done) {
|
||||
const constraints = this.deriveSyncConstraints(req, ctx)
|
||||
|
||||
if (done === undefined) {
|
||||
return constraints
|
||||
}
|
||||
|
||||
this.deriveAsyncConstraints(constraints, req, ctx, done)
|
||||
}
|
||||
|
||||
deriveSyncConstraints (req, ctx) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// When new constraints start getting used, we need to rebuild the deriver to derive them. Do so if we see novel constraints used.
|
||||
noteUsage (constraints) {
|
||||
if (constraints) {
|
||||
const beforeSize = this.strategiesInUse.size
|
||||
for (const key in constraints) {
|
||||
const strategy = this.strategies[key]
|
||||
if (strategy.isAsync) {
|
||||
this.asyncStrategiesInUse.add(key)
|
||||
} else {
|
||||
this.strategiesInUse.add(key)
|
||||
}
|
||||
}
|
||||
if (beforeSize !== this.strategiesInUse.size) {
|
||||
this._buildDeriveConstraints()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newStoreForConstraint (constraint) {
|
||||
if (!this.strategies[constraint]) {
|
||||
throw new Error(`No strategy registered for constraint key ${constraint}`)
|
||||
}
|
||||
return this.strategies[constraint].storage()
|
||||
}
|
||||
|
||||
validateConstraints (constraints) {
|
||||
for (const key in constraints) {
|
||||
const value = constraints[key]
|
||||
if (typeof value === 'undefined') {
|
||||
throw new Error('Can\'t pass an undefined constraint value, must pass null or no key at all')
|
||||
}
|
||||
const strategy = this.strategies[key]
|
||||
if (!strategy) {
|
||||
throw new Error(`No strategy registered for constraint key ${key}`)
|
||||
}
|
||||
if (strategy.validate) {
|
||||
strategy.validate(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deriveAsyncConstraints (constraints, req, ctx, done) {
|
||||
let asyncConstraintsCount = this.asyncStrategiesInUse.size
|
||||
|
||||
if (asyncConstraintsCount === 0) {
|
||||
done(null, constraints)
|
||||
return
|
||||
}
|
||||
|
||||
constraints = constraints || {}
|
||||
for (const key of this.asyncStrategiesInUse) {
|
||||
const strategy = this.strategies[key]
|
||||
strategy.deriveConstraint(req, ctx, (err, constraintValue) => {
|
||||
if (err !== null) {
|
||||
done(err)
|
||||
return
|
||||
}
|
||||
|
||||
constraints[key] = constraintValue
|
||||
|
||||
if (--asyncConstraintsCount === 0) {
|
||||
done(null, constraints)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization: build a fast function for deriving the constraints for all the strategies at once. We inline the definitions of the version constraint and the host constraint for performance.
|
||||
// If no constraining strategies are in use (no routes constrain on host, or version, or any custom strategies) then we don't need to derive constraints for each route match, so don't do anything special, and just return undefined
|
||||
// This allows us to not allocate an object to hold constraint values if no constraints are defined.
|
||||
_buildDeriveConstraints () {
|
||||
if (this.strategiesInUse.size === 0) return
|
||||
|
||||
const lines = ['return {']
|
||||
|
||||
for (const key of this.strategiesInUse) {
|
||||
const strategy = this.strategies[key]
|
||||
// Optimization: inline the derivation for the common built in constraints
|
||||
if (!strategy.isCustom) {
|
||||
if (key === 'version') {
|
||||
lines.push(' version: req.headers[\'accept-version\'],')
|
||||
} else {
|
||||
lines.push(' host: req.headers.host || req.headers[\':authority\'],')
|
||||
}
|
||||
} else {
|
||||
lines.push(` ${strategy.name}: this.strategies.${key}.deriveConstraint(req, ctx),`)
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('}')
|
||||
|
||||
this.deriveSyncConstraints = new Function('req', 'ctx', lines.join('\n')).bind(this) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Constrainer
|
||||
164
node_modules/find-my-way/lib/handler-storage.js
generated
vendored
Normal file
164
node_modules/find-my-way/lib/handler-storage.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
'use strict'
|
||||
|
||||
const httpMethodStrategy = require('./strategies/http-method')
|
||||
|
||||
class HandlerStorage {
|
||||
constructor () {
|
||||
this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time
|
||||
this.constraints = []
|
||||
this.handlers = [] // unoptimized list of handler objects for which the fast matcher function will be compiled
|
||||
this.constrainedHandlerStores = null
|
||||
}
|
||||
|
||||
// This is the hot path for node handler finding -- change with care!
|
||||
getMatchingHandler (derivedConstraints) {
|
||||
if (derivedConstraints === undefined) {
|
||||
return this.unconstrainedHandler
|
||||
}
|
||||
return this._getHandlerMatchingConstraints(derivedConstraints)
|
||||
}
|
||||
|
||||
addHandler (constrainer, route) {
|
||||
const params = route.params
|
||||
const constraints = route.opts.constraints || {}
|
||||
|
||||
const handlerObject = {
|
||||
params,
|
||||
constraints,
|
||||
handler: route.handler,
|
||||
store: route.store || null,
|
||||
_createParamsObject: this._compileCreateParamsObject(params)
|
||||
}
|
||||
|
||||
const constraintsNames = Object.keys(constraints)
|
||||
if (constraintsNames.length === 0) {
|
||||
this.unconstrainedHandler = handlerObject
|
||||
}
|
||||
|
||||
for (const constraint of constraintsNames) {
|
||||
if (!this.constraints.includes(constraint)) {
|
||||
if (constraint === 'version') {
|
||||
// always check the version constraint first as it is the most selective
|
||||
this.constraints.unshift(constraint)
|
||||
} else {
|
||||
this.constraints.push(constraint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isMergedTree = constraintsNames.includes(httpMethodStrategy.name)
|
||||
if (!isMergedTree && this.handlers.length >= 31) {
|
||||
throw new Error('find-my-way supports a maximum of 31 route handlers per node when there are constraints, limit reached')
|
||||
}
|
||||
|
||||
this.handlers.push(handlerObject)
|
||||
// Sort the most constrained handlers to the front of the list of handlers so they are tested first.
|
||||
this.handlers.sort((a, b) => Object.keys(a.constraints).length - Object.keys(b.constraints).length)
|
||||
|
||||
if (!isMergedTree) {
|
||||
this._compileGetHandlerMatchingConstraints(constrainer, constraints)
|
||||
}
|
||||
}
|
||||
|
||||
_compileCreateParamsObject (params) {
|
||||
const lines = []
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
lines.push(`'${params[i]}': paramsArray[${i}]`)
|
||||
}
|
||||
return new Function('paramsArray', `return {${lines.join(',')}}`) // eslint-disable-line
|
||||
}
|
||||
|
||||
_getHandlerMatchingConstraints () {
|
||||
return null
|
||||
}
|
||||
|
||||
// Builds a store object that maps from constraint values to a bitmap of handler indexes which pass the constraint for a value
|
||||
// So for a host constraint, this might look like { "fastify.io": 0b0010, "google.ca": 0b0101 }, meaning the 3rd handler is constrainted to fastify.io, and the 2nd and 4th handlers are constrained to google.ca.
|
||||
// The store's implementation comes from the strategies provided to the Router.
|
||||
_buildConstraintStore (store, constraint) {
|
||||
for (let i = 0; i < this.handlers.length; i++) {
|
||||
const handler = this.handlers[i]
|
||||
const constraintValue = handler.constraints[constraint]
|
||||
if (constraintValue !== undefined) {
|
||||
let indexes = store.get(constraintValue) || 0
|
||||
indexes |= 1 << i // set the i-th bit for the mask because this handler is constrained by this value https://stackoverflow.com/questions/1436438/how-do-you-set-clear-and-toggle-a-single-bit-in-javascrip
|
||||
store.set(constraintValue, indexes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a bitmask for a given constraint that has a bit for each handler index that is 0 when that handler *is* constrained and 1 when the handler *isnt* constrainted. This is opposite to what might be obvious, but is just for convienience when doing the bitwise operations.
|
||||
_constrainedIndexBitmask (constraint) {
|
||||
let mask = 0
|
||||
for (let i = 0; i < this.handlers.length; i++) {
|
||||
const handler = this.handlers[i]
|
||||
const constraintValue = handler.constraints[constraint]
|
||||
if (constraintValue !== undefined) {
|
||||
mask |= 1 << i
|
||||
}
|
||||
}
|
||||
return ~mask
|
||||
}
|
||||
|
||||
// Compile a fast function to match the handlers for this node
|
||||
// The function implements a general case multi-constraint matching algorithm.
|
||||
// The general idea is this: we have a bunch of handlers, each with a potentially different set of constraints, and sometimes none at all. We're given a list of constraint values and we have to use the constraint-value-comparison strategies to see which handlers match the constraint values passed in.
|
||||
// We do this by asking each constraint store which handler indexes match the given constraint value for each store. Trickily, the handlers that a store says match are the handlers constrained by that store, but handlers that aren't constrained at all by that store could still match just fine. So, each constraint store can only describe matches for it, and it won't have any bearing on the handlers it doesn't care about. For this reason, we have to ask each stores which handlers match and track which have been matched (or not cared about) by all of them.
|
||||
// We use bitmaps to represent these lists of matches so we can use bitwise operations to implement this efficiently. Bitmaps are cheap to allocate, let us implement this masking behaviour in one CPU instruction, and are quite compact in memory. We start with a bitmap set to all 1s representing every handler that is a match candidate, and then for each constraint, see which handlers match using the store, and then mask the result by the mask of handlers that that store applies to, and bitwise AND with the candidate list. Phew.
|
||||
// We consider all this compiling function complexity to be worth it, because the naive implementation that just loops over the handlers asking which stores match is quite a bit slower.
|
||||
_compileGetHandlerMatchingConstraints (constrainer) {
|
||||
this.constrainedHandlerStores = {}
|
||||
|
||||
for (const constraint of this.constraints) {
|
||||
const store = constrainer.newStoreForConstraint(constraint)
|
||||
this.constrainedHandlerStores[constraint] = store
|
||||
|
||||
this._buildConstraintStore(store, constraint)
|
||||
}
|
||||
|
||||
const lines = []
|
||||
lines.push(`
|
||||
let candidates = ${(1 << this.handlers.length) - 1}
|
||||
let mask, matches
|
||||
`)
|
||||
for (const constraint of this.constraints) {
|
||||
// Setup the mask for indexes this constraint applies to. The mask bits are set to 1 for each position if the constraint applies.
|
||||
lines.push(`
|
||||
mask = ${this._constrainedIndexBitmask(constraint)}
|
||||
value = derivedConstraints.${constraint}
|
||||
`)
|
||||
|
||||
// If there's no constraint value, none of the handlers constrained by this constraint can match. Remove them from the candidates.
|
||||
// If there is a constraint value, get the matching indexes bitmap from the store, and mask it down to only the indexes this constraint applies to, and then bitwise and with the candidates list to leave only matching candidates left.
|
||||
const strategy = constrainer.strategies[constraint]
|
||||
const matchMask = strategy.mustMatchWhenDerived ? 'matches' : '(matches | mask)'
|
||||
|
||||
lines.push(`
|
||||
if (value === undefined) {
|
||||
candidates &= mask
|
||||
} else {
|
||||
matches = this.constrainedHandlerStores.${constraint}.get(value) || 0
|
||||
candidates &= ${matchMask}
|
||||
}
|
||||
if (candidates === 0) return null;
|
||||
`)
|
||||
}
|
||||
|
||||
// There are some constraints that can be derived and marked as "must match", where if they are derived, they only match routes that actually have a constraint on the value, like the SemVer version constraint.
|
||||
// An example: a request comes in for version 1.x, and this node has a handler that matches the path, but there's no version constraint. For SemVer, the find-my-way semantics do not match this handler to that request.
|
||||
// This function is used by Nodes with handlers to match when they don't have any constrained routes to exclude request that do have must match derived constraints present.
|
||||
for (const constraint in constrainer.strategies) {
|
||||
const strategy = constrainer.strategies[constraint]
|
||||
if (strategy.mustMatchWhenDerived && !this.constraints.includes(constraint)) {
|
||||
lines.push(`if (derivedConstraints.${constraint} !== undefined) return null`)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first handler who's bit is set in the candidates https://stackoverflow.com/questions/18134985/how-to-find-index-of-first-set-bit
|
||||
lines.push('return this.handlers[Math.floor(Math.log2(candidates))]')
|
||||
|
||||
this._getHandlerMatchingConstraints = new Function('derivedConstraints', lines.join('\n')) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HandlerStorage
|
||||
13
node_modules/find-my-way/lib/http-methods.js
generated
vendored
Normal file
13
node_modules/find-my-way/lib/http-methods.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
// defined by Node.js http module, a snapshot from Node.js 18.12.0
|
||||
const httpMethods = [
|
||||
'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE',
|
||||
'GET', 'HEAD', 'LINK', 'LOCK', 'M-SEARCH', 'MERGE',
|
||||
'MKACTIVITY', 'MKCALENDAR', 'MKCOL', 'MOVE', 'NOTIFY', 'OPTIONS',
|
||||
'PATCH', 'POST', 'PROPFIND', 'PROPPATCH', 'PURGE', 'PUT',
|
||||
'REBIND', 'REPORT', 'SEARCH', 'SOURCE', 'SUBSCRIBE', 'TRACE',
|
||||
'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE'
|
||||
]
|
||||
|
||||
module.exports = httpMethods
|
||||
228
node_modules/find-my-way/lib/node.js
generated
vendored
Normal file
228
node_modules/find-my-way/lib/node.js
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
'use strict'
|
||||
|
||||
const HandlerStorage = require('./handler-storage')
|
||||
|
||||
const NODE_TYPES = {
|
||||
STATIC: 0,
|
||||
PARAMETRIC: 1,
|
||||
WILDCARD: 2
|
||||
}
|
||||
|
||||
class Node {
|
||||
constructor () {
|
||||
this.isLeafNode = false
|
||||
this.routes = null
|
||||
this.handlerStorage = null
|
||||
}
|
||||
|
||||
addRoute (route, constrainer) {
|
||||
if (this.routes === null) {
|
||||
this.routes = []
|
||||
}
|
||||
if (this.handlerStorage === null) {
|
||||
this.handlerStorage = new HandlerStorage()
|
||||
}
|
||||
this.isLeafNode = true
|
||||
this.routes.push(route)
|
||||
this.handlerStorage.addHandler(constrainer, route)
|
||||
}
|
||||
}
|
||||
|
||||
class ParentNode extends Node {
|
||||
constructor () {
|
||||
super()
|
||||
this.staticChildren = {}
|
||||
}
|
||||
|
||||
findStaticMatchingChild (path, pathIndex) {
|
||||
const staticChild = this.staticChildren[path.charAt(pathIndex)]
|
||||
if (staticChild === undefined || !staticChild.matchPrefix(path, pathIndex)) {
|
||||
return null
|
||||
}
|
||||
return staticChild
|
||||
}
|
||||
|
||||
getStaticChild (path, pathIndex = 0) {
|
||||
if (path.length === pathIndex) {
|
||||
return this
|
||||
}
|
||||
|
||||
const staticChild = this.findStaticMatchingChild(path, pathIndex)
|
||||
if (staticChild) {
|
||||
return staticChild.getStaticChild(path, pathIndex + staticChild.prefix.length)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
createStaticChild (path) {
|
||||
if (path.length === 0) {
|
||||
return this
|
||||
}
|
||||
|
||||
let staticChild = this.staticChildren[path.charAt(0)]
|
||||
if (staticChild) {
|
||||
let i = 1
|
||||
for (; i < staticChild.prefix.length; i++) {
|
||||
if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) {
|
||||
staticChild = staticChild.split(this, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
return staticChild.createStaticChild(path.slice(i))
|
||||
}
|
||||
|
||||
const label = path.charAt(0)
|
||||
this.staticChildren[label] = new StaticNode(path)
|
||||
return this.staticChildren[label]
|
||||
}
|
||||
}
|
||||
|
||||
class StaticNode extends ParentNode {
|
||||
constructor (prefix) {
|
||||
super()
|
||||
this.prefix = prefix
|
||||
this.wildcardChild = null
|
||||
this.parametricChildren = []
|
||||
this.kind = NODE_TYPES.STATIC
|
||||
this._compilePrefixMatch()
|
||||
}
|
||||
|
||||
getParametricChild (regex) {
|
||||
const regexpSource = regex && regex.source
|
||||
|
||||
const parametricChild = this.parametricChildren.find(child => {
|
||||
const childRegexSource = child.regex && child.regex.source
|
||||
return childRegexSource === regexpSource
|
||||
})
|
||||
|
||||
if (parametricChild) {
|
||||
return parametricChild
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
createParametricChild (regex, staticSuffix, nodePath) {
|
||||
let parametricChild = this.getParametricChild(regex)
|
||||
if (parametricChild) {
|
||||
parametricChild.nodePaths.add(nodePath)
|
||||
return parametricChild
|
||||
}
|
||||
|
||||
parametricChild = new ParametricNode(regex, staticSuffix, nodePath)
|
||||
this.parametricChildren.push(parametricChild)
|
||||
this.parametricChildren.sort((child1, child2) => {
|
||||
if (!child1.isRegex) return 1
|
||||
if (!child2.isRegex) return -1
|
||||
|
||||
if (child1.staticSuffix === null) return 1
|
||||
if (child2.staticSuffix === null) return -1
|
||||
|
||||
if (child2.staticSuffix.endsWith(child1.staticSuffix)) return 1
|
||||
if (child1.staticSuffix.endsWith(child2.staticSuffix)) return -1
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
return parametricChild
|
||||
}
|
||||
|
||||
getWildcardChild () {
|
||||
return this.wildcardChild
|
||||
}
|
||||
|
||||
createWildcardChild () {
|
||||
this.wildcardChild = this.getWildcardChild() || new WildcardNode()
|
||||
return this.wildcardChild
|
||||
}
|
||||
|
||||
split (parentNode, length) {
|
||||
const parentPrefix = this.prefix.slice(0, length)
|
||||
const childPrefix = this.prefix.slice(length)
|
||||
|
||||
this.prefix = childPrefix
|
||||
this._compilePrefixMatch()
|
||||
|
||||
const staticNode = new StaticNode(parentPrefix)
|
||||
staticNode.staticChildren[childPrefix.charAt(0)] = this
|
||||
parentNode.staticChildren[parentPrefix.charAt(0)] = staticNode
|
||||
|
||||
return staticNode
|
||||
}
|
||||
|
||||
getNextNode (path, pathIndex, nodeStack, paramsCount) {
|
||||
let node = this.findStaticMatchingChild(path, pathIndex)
|
||||
let parametricBrotherNodeIndex = 0
|
||||
|
||||
if (node === null) {
|
||||
if (this.parametricChildren.length === 0) {
|
||||
return this.wildcardChild
|
||||
}
|
||||
|
||||
node = this.parametricChildren[0]
|
||||
parametricBrotherNodeIndex = 1
|
||||
}
|
||||
|
||||
if (this.wildcardChild !== null) {
|
||||
nodeStack.push({
|
||||
paramsCount,
|
||||
brotherPathIndex: pathIndex,
|
||||
brotherNode: this.wildcardChild
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = this.parametricChildren.length - 1; i >= parametricBrotherNodeIndex; i--) {
|
||||
nodeStack.push({
|
||||
paramsCount,
|
||||
brotherPathIndex: pathIndex,
|
||||
brotherNode: this.parametricChildren[i]
|
||||
})
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
_compilePrefixMatch () {
|
||||
if (this.prefix.length === 1) {
|
||||
this.matchPrefix = () => true
|
||||
return
|
||||
}
|
||||
|
||||
const lines = []
|
||||
for (let i = 1; i < this.prefix.length; i++) {
|
||||
const charCode = this.prefix.charCodeAt(i)
|
||||
lines.push(`path.charCodeAt(i + ${i}) === ${charCode}`)
|
||||
}
|
||||
this.matchPrefix = new Function('path', 'i', `return ${lines.join(' && ')}`) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
|
||||
class ParametricNode extends ParentNode {
|
||||
constructor (regex, staticSuffix, nodePath) {
|
||||
super()
|
||||
this.isRegex = !!regex
|
||||
this.regex = regex || null
|
||||
this.staticSuffix = staticSuffix || null
|
||||
this.kind = NODE_TYPES.PARAMETRIC
|
||||
|
||||
this.nodePaths = new Set([nodePath])
|
||||
}
|
||||
|
||||
getNextNode (path, pathIndex) {
|
||||
return this.findStaticMatchingChild(path, pathIndex)
|
||||
}
|
||||
}
|
||||
|
||||
class WildcardNode extends Node {
|
||||
constructor () {
|
||||
super()
|
||||
this.kind = NODE_TYPES.WILDCARD
|
||||
}
|
||||
|
||||
getNextNode () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { StaticNode, ParametricNode, WildcardNode, NODE_TYPES }
|
||||
168
node_modules/find-my-way/lib/pretty-print.js
generated
vendored
Normal file
168
node_modules/find-my-way/lib/pretty-print.js
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
'use strict'
|
||||
|
||||
const deepEqual = require('fast-deep-equal')
|
||||
|
||||
const httpMethodStrategy = require('./strategies/http-method')
|
||||
const treeDataSymbol = Symbol('treeData')
|
||||
|
||||
function printObjectTree (obj, parentPrefix = '') {
|
||||
let tree = ''
|
||||
const keys = Object.keys(obj)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const value = obj[key]
|
||||
const isLast = i === keys.length - 1
|
||||
|
||||
const nodePrefix = isLast ? '└── ' : '├── '
|
||||
const childPrefix = isLast ? ' ' : '│ '
|
||||
|
||||
const nodeData = value[treeDataSymbol] || ''
|
||||
const prefixedNodeData = nodeData.split('\n').join('\n' + parentPrefix + childPrefix)
|
||||
|
||||
tree += parentPrefix + nodePrefix + key + prefixedNodeData + '\n'
|
||||
tree += printObjectTree(value, parentPrefix + childPrefix)
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
function parseFunctionName (fn) {
|
||||
let fName = fn.name || ''
|
||||
|
||||
fName = fName.replace('bound', '').trim()
|
||||
fName = (fName || 'anonymous') + '()'
|
||||
return fName
|
||||
}
|
||||
|
||||
function parseMeta (meta) {
|
||||
if (Array.isArray(meta)) return meta.map(m => parseMeta(m))
|
||||
if (typeof meta === 'symbol') return meta.toString()
|
||||
if (typeof meta === 'function') return parseFunctionName(meta)
|
||||
return meta
|
||||
}
|
||||
|
||||
function getRouteMetaData (route, options) {
|
||||
if (!options.includeMeta) return {}
|
||||
|
||||
const metaDataObject = options.buildPrettyMeta(route)
|
||||
const filteredMetaData = {}
|
||||
|
||||
let includeMetaKeys = options.includeMeta
|
||||
if (!Array.isArray(includeMetaKeys)) {
|
||||
includeMetaKeys = Reflect.ownKeys(metaDataObject)
|
||||
}
|
||||
|
||||
for (const metaKey of includeMetaKeys) {
|
||||
if (!Object.prototype.hasOwnProperty.call(metaDataObject, metaKey)) continue
|
||||
|
||||
const serializedKey = metaKey.toString()
|
||||
const metaValue = metaDataObject[metaKey]
|
||||
|
||||
if (metaValue !== undefined && metaValue !== null) {
|
||||
const serializedValue = JSON.stringify(parseMeta(metaValue))
|
||||
filteredMetaData[serializedKey] = serializedValue
|
||||
}
|
||||
}
|
||||
|
||||
return filteredMetaData
|
||||
}
|
||||
|
||||
function serializeMetaData (metaData) {
|
||||
let serializedMetaData = ''
|
||||
for (const [key, value] of Object.entries(metaData)) {
|
||||
serializedMetaData += `\n• (${key}) ${value}`
|
||||
}
|
||||
return serializedMetaData
|
||||
}
|
||||
|
||||
// get original merged tree node route
|
||||
function normalizeRoute (route) {
|
||||
const constraints = { ...route.opts.constraints }
|
||||
const method = constraints[httpMethodStrategy.name]
|
||||
delete constraints[httpMethodStrategy.name]
|
||||
return { ...route, method, opts: { constraints } }
|
||||
}
|
||||
|
||||
function serializeRoute (route) {
|
||||
let serializedRoute = ` (${route.method})`
|
||||
|
||||
const constraints = route.opts.constraints || {}
|
||||
if (Object.keys(constraints).length !== 0) {
|
||||
serializedRoute += ' ' + JSON.stringify(constraints)
|
||||
}
|
||||
|
||||
serializedRoute += serializeMetaData(route.metaData)
|
||||
return serializedRoute
|
||||
}
|
||||
|
||||
function mergeSimilarRoutes (routes) {
|
||||
return routes.reduce((mergedRoutes, route) => {
|
||||
for (const nodeRoute of mergedRoutes) {
|
||||
if (
|
||||
deepEqual(route.opts.constraints, nodeRoute.opts.constraints) &&
|
||||
deepEqual(route.metaData, nodeRoute.metaData)
|
||||
) {
|
||||
nodeRoute.method += ', ' + route.method
|
||||
return mergedRoutes
|
||||
}
|
||||
}
|
||||
mergedRoutes.push(route)
|
||||
return mergedRoutes
|
||||
}, [])
|
||||
}
|
||||
|
||||
function serializeNode (node, prefix, options) {
|
||||
let routes = node.routes
|
||||
|
||||
if (options.method === undefined) {
|
||||
routes = routes.map(normalizeRoute)
|
||||
}
|
||||
|
||||
routes = routes.map(route => {
|
||||
route.metaData = getRouteMetaData(route, options)
|
||||
return route
|
||||
})
|
||||
|
||||
if (options.method === undefined) {
|
||||
routes = mergeSimilarRoutes(routes)
|
||||
}
|
||||
|
||||
return routes.map(serializeRoute).join(`\n${prefix}`)
|
||||
}
|
||||
|
||||
function buildObjectTree (node, tree, prefix, options) {
|
||||
if (node.isLeafNode || options.commonPrefix !== false) {
|
||||
prefix = prefix || '(empty root node)'
|
||||
tree = tree[prefix] = {}
|
||||
|
||||
if (node.isLeafNode) {
|
||||
tree[treeDataSymbol] = serializeNode(node, prefix, options)
|
||||
}
|
||||
|
||||
prefix = ''
|
||||
}
|
||||
|
||||
if (node.staticChildren) {
|
||||
for (const child of Object.values(node.staticChildren)) {
|
||||
buildObjectTree(child, tree, prefix + child.prefix, options)
|
||||
}
|
||||
}
|
||||
|
||||
if (node.parametricChildren) {
|
||||
for (const child of Object.values(node.parametricChildren)) {
|
||||
const childPrefix = Array.from(child.nodePaths).join('|')
|
||||
buildObjectTree(child, tree, prefix + childPrefix, options)
|
||||
}
|
||||
}
|
||||
|
||||
if (node.wildcardChild) {
|
||||
buildObjectTree(node.wildcardChild, tree, '*', options)
|
||||
}
|
||||
}
|
||||
|
||||
function prettyPrintTree (root, options) {
|
||||
const objectTree = {}
|
||||
buildObjectTree(root, objectTree, root.prefix, options)
|
||||
return printObjectTree(objectTree)
|
||||
}
|
||||
|
||||
module.exports = { prettyPrintTree }
|
||||
36
node_modules/find-my-way/lib/strategies/accept-host.js
generated
vendored
Normal file
36
node_modules/find-my-way/lib/strategies/accept-host.js
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
const assert = require('node:assert')
|
||||
|
||||
function HostStorage () {
|
||||
const hosts = {}
|
||||
const regexHosts = []
|
||||
return {
|
||||
get: (host) => {
|
||||
const exact = hosts[host]
|
||||
if (exact) {
|
||||
return exact
|
||||
}
|
||||
for (const regex of regexHosts) {
|
||||
if (regex.host.test(host)) {
|
||||
return regex.value
|
||||
}
|
||||
}
|
||||
},
|
||||
set: (host, value) => {
|
||||
if (host instanceof RegExp) {
|
||||
regexHosts.push({ host, value })
|
||||
} else {
|
||||
hosts[host] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'host',
|
||||
mustMatchWhenDerived: false,
|
||||
storage: HostStorage,
|
||||
validate (value) {
|
||||
assert(typeof value === 'string' || Object.prototype.toString.call(value) === '[object RegExp]', 'Host should be a string or a RegExp')
|
||||
}
|
||||
}
|
||||
65
node_modules/find-my-way/lib/strategies/accept-version.js
generated
vendored
Normal file
65
node_modules/find-my-way/lib/strategies/accept-version.js
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('node:assert')
|
||||
|
||||
function SemVerStore () {
|
||||
if (!(this instanceof SemVerStore)) {
|
||||
return new SemVerStore()
|
||||
}
|
||||
|
||||
this.store = {}
|
||||
|
||||
this.maxMajor = 0
|
||||
this.maxMinors = {}
|
||||
this.maxPatches = {}
|
||||
}
|
||||
|
||||
SemVerStore.prototype.set = function (version, store) {
|
||||
if (typeof version !== 'string') {
|
||||
throw new TypeError('Version should be a string')
|
||||
}
|
||||
let [major, minor, patch] = version.split('.')
|
||||
|
||||
if (isNaN(major)) {
|
||||
throw new TypeError('Major version must be a numeric value')
|
||||
}
|
||||
|
||||
major = Number(major)
|
||||
minor = Number(minor) || 0
|
||||
patch = Number(patch) || 0
|
||||
|
||||
if (major >= this.maxMajor) {
|
||||
this.maxMajor = major
|
||||
this.store.x = store
|
||||
this.store['*'] = store
|
||||
this.store['x.x'] = store
|
||||
this.store['x.x.x'] = store
|
||||
}
|
||||
|
||||
if (minor >= (this.maxMinors[major] || 0)) {
|
||||
this.maxMinors[major] = minor
|
||||
this.store[`${major}.x`] = store
|
||||
this.store[`${major}.x.x`] = store
|
||||
}
|
||||
|
||||
if (patch >= (this.maxPatches[`${major}.${minor}`] || 0)) {
|
||||
this.maxPatches[`${major}.${minor}`] = patch
|
||||
this.store[`${major}.${minor}.x`] = store
|
||||
}
|
||||
|
||||
this.store[`${major}.${minor}.${patch}`] = store
|
||||
return this
|
||||
}
|
||||
|
||||
SemVerStore.prototype.get = function (version) {
|
||||
return this.store[version]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'version',
|
||||
mustMatchWhenDerived: true,
|
||||
storage: SemVerStore,
|
||||
validate (value) {
|
||||
assert(typeof value === 'string', 'Version should be a string')
|
||||
}
|
||||
}
|
||||
14
node_modules/find-my-way/lib/strategies/http-method.js
generated
vendored
Normal file
14
node_modules/find-my-way/lib/strategies/http-method.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
name: '__fmw_internal_strategy_merged_tree_http_method__',
|
||||
storage: function () {
|
||||
const handlers = {}
|
||||
return {
|
||||
get: (type) => { return handlers[type] || null },
|
||||
set: (type, store) => { handlers[type] = store }
|
||||
}
|
||||
},
|
||||
deriveConstraint: /* istanbul ignore next */ (req) => req.method,
|
||||
mustMatchWhenDerived: true
|
||||
}
|
||||
96
node_modules/find-my-way/lib/url-sanitizer.js
generated
vendored
Normal file
96
node_modules/find-my-way/lib/url-sanitizer.js
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
'use strict'
|
||||
|
||||
// It must spot all the chars where decodeURIComponent(x) !== decodeURI(x)
|
||||
// The chars are: # $ & + , / : ; = ? @
|
||||
function decodeComponentChar (highCharCode, lowCharCode) {
|
||||
if (highCharCode === 50) {
|
||||
if (lowCharCode === 53) return '%'
|
||||
|
||||
if (lowCharCode === 51) return '#'
|
||||
if (lowCharCode === 52) return '$'
|
||||
if (lowCharCode === 54) return '&'
|
||||
if (lowCharCode === 66) return '+'
|
||||
if (lowCharCode === 98) return '+'
|
||||
if (lowCharCode === 67) return ','
|
||||
if (lowCharCode === 99) return ','
|
||||
if (lowCharCode === 70) return '/'
|
||||
if (lowCharCode === 102) return '/'
|
||||
return null
|
||||
}
|
||||
if (highCharCode === 51) {
|
||||
if (lowCharCode === 65) return ':'
|
||||
if (lowCharCode === 97) return ':'
|
||||
if (lowCharCode === 66) return ';'
|
||||
if (lowCharCode === 98) return ';'
|
||||
if (lowCharCode === 68) return '='
|
||||
if (lowCharCode === 100) return '='
|
||||
if (lowCharCode === 70) return '?'
|
||||
if (lowCharCode === 102) return '?'
|
||||
return null
|
||||
}
|
||||
if (highCharCode === 52 && lowCharCode === 48) {
|
||||
return '@'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function safeDecodeURI (path, useSemicolonDelimiter) {
|
||||
let shouldDecode = false
|
||||
let shouldDecodeParam = false
|
||||
|
||||
let querystring = ''
|
||||
|
||||
for (let i = 1; i < path.length; i++) {
|
||||
const charCode = path.charCodeAt(i)
|
||||
|
||||
if (charCode === 37) {
|
||||
const highCharCode = path.charCodeAt(i + 1)
|
||||
const lowCharCode = path.charCodeAt(i + 2)
|
||||
|
||||
if (decodeComponentChar(highCharCode, lowCharCode) === null) {
|
||||
shouldDecode = true
|
||||
} else {
|
||||
shouldDecodeParam = true
|
||||
// %25 - encoded % char. We need to encode one more time to prevent double decoding
|
||||
if (highCharCode === 50 && lowCharCode === 53) {
|
||||
shouldDecode = true
|
||||
path = path.slice(0, i + 1) + '25' + path.slice(i + 1)
|
||||
i += 2
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
// Some systems do not follow RFC and separate the path and query
|
||||
// string with a `;` character (code 59), e.g. `/foo;jsessionid=123456`.
|
||||
// Thus, we need to split on `;` as well as `?` and `#` if the useSemicolonDelimiter option is enabled.
|
||||
} else if (charCode === 63 || charCode === 35 || (charCode === 59 && useSemicolonDelimiter)) {
|
||||
querystring = path.slice(i + 1)
|
||||
path = path.slice(0, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
const decodedPath = shouldDecode ? decodeURI(path) : path
|
||||
return { path: decodedPath, querystring, shouldDecodeParam }
|
||||
}
|
||||
|
||||
function safeDecodeURIComponent (uriComponent) {
|
||||
const startIndex = uriComponent.indexOf('%')
|
||||
if (startIndex === -1) return uriComponent
|
||||
|
||||
let decoded = ''
|
||||
let lastIndex = startIndex
|
||||
|
||||
for (let i = startIndex; i < uriComponent.length; i++) {
|
||||
if (uriComponent.charCodeAt(i) === 37) {
|
||||
const highCharCode = uriComponent.charCodeAt(i + 1)
|
||||
const lowCharCode = uriComponent.charCodeAt(i + 2)
|
||||
|
||||
const decodedChar = decodeComponentChar(highCharCode, lowCharCode)
|
||||
decoded += uriComponent.slice(lastIndex, i) + decodedChar
|
||||
|
||||
lastIndex = i + 3
|
||||
}
|
||||
}
|
||||
return uriComponent.slice(0, startIndex) + decoded + uriComponent.slice(lastIndex)
|
||||
}
|
||||
|
||||
module.exports = { safeDecodeURI, safeDecodeURIComponent }
|
||||
Reference in New Issue
Block a user