97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
|
|
import type { OperationCategory } from '../types.js'
|
||
|
|
/**
|
||
|
|
* Category inference for route contracts.
|
||
|
|
* Pure functions, no side effects.
|
||
|
|
* Optimized: direct string comparison over Set for hot path.
|
||
|
|
*/
|
||
|
|
// Fast path: direct string comparison for most common utility paths
|
||
|
|
const isUtilityPath = (path: string): boolean => {
|
||
|
|
// Check exact matches first (fastest)
|
||
|
|
if (
|
||
|
|
path === '/reset' ||
|
||
|
|
path === '/health' ||
|
||
|
|
path === '/ping' ||
|
||
|
|
path === '/login' ||
|
||
|
|
path === '/logout' ||
|
||
|
|
path === '/auth' ||
|
||
|
|
path === '/callback' ||
|
||
|
|
path === '/purge' ||
|
||
|
|
path === '/clear' ||
|
||
|
|
path === '/initialize' ||
|
||
|
|
path === '/setup' ||
|
||
|
|
path === '/webhook'
|
||
|
|
) {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
// Check trailing slash variants
|
||
|
|
const last = path.charCodeAt(path.length - 1)
|
||
|
|
if (last === 47) {
|
||
|
|
const base = path.slice(0, -1)
|
||
|
|
return (
|
||
|
|
base === '/reset' ||
|
||
|
|
base === '/health' ||
|
||
|
|
base === '/ping' ||
|
||
|
|
base === '/login' ||
|
||
|
|
base === '/logout' ||
|
||
|
|
base === '/auth' ||
|
||
|
|
base === '/callback' ||
|
||
|
|
base === '/purge' ||
|
||
|
|
base === '/clear' ||
|
||
|
|
base === '/initialize' ||
|
||
|
|
base === '/setup' ||
|
||
|
|
base === '/webhook'
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
// Fast path: check last 7 chars for common suffixes
|
||
|
|
const isObserverSuffix = (path: string): boolean => {
|
||
|
|
const len = path.length
|
||
|
|
if (len >= 7) {
|
||
|
|
const end = path.slice(-7)
|
||
|
|
if (end === '/search') return true
|
||
|
|
}
|
||
|
|
if (len >= 6) {
|
||
|
|
const end = path.slice(-6)
|
||
|
|
if (end === '/count' || end === '/stats') return true
|
||
|
|
}
|
||
|
|
if (len >= 7) {
|
||
|
|
const end = path.slice(-7)
|
||
|
|
if (end === '/status') return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
const isCollectionPath = (path: string): boolean => {
|
||
|
|
const len = path.length
|
||
|
|
let i = len - 1
|
||
|
|
// Skip trailing slash
|
||
|
|
while (i > 0 && path.charCodeAt(i) === 47) i--
|
||
|
|
// Find last segment
|
||
|
|
const lastSlash = path.lastIndexOf('/', i)
|
||
|
|
const segment = path.slice(lastSlash + 1, i + 1)
|
||
|
|
return segment.length > 0 && segment.charCodeAt(0) !== 58 // ':'.charCodeAt(0) === 58
|
||
|
|
}
|
||
|
|
const hasPathParam = (path: string): boolean => path.includes(':')
|
||
|
|
export const inferCategory = (
|
||
|
|
path: string,
|
||
|
|
method: string,
|
||
|
|
override: string | undefined
|
||
|
|
): OperationCategory => {
|
||
|
|
if (override !== undefined && override !== '') {
|
||
|
|
return override as OperationCategory
|
||
|
|
}
|
||
|
|
if (isUtilityPath(path)) {
|
||
|
|
return 'utility'
|
||
|
|
}
|
||
|
|
const upperMethod = method.toUpperCase()
|
||
|
|
if (upperMethod === 'GET' || isObserverSuffix(path)) {
|
||
|
|
return 'observer'
|
||
|
|
}
|
||
|
|
if (upperMethod === 'POST' && isCollectionPath(path)) {
|
||
|
|
return 'constructor'
|
||
|
|
}
|
||
|
|
if (upperMethod === 'PUT' || upperMethod === 'PATCH' || upperMethod === 'DELETE' || (upperMethod === 'POST' && hasPathParam(path))) {
|
||
|
|
return 'mutator'
|
||
|
|
}
|
||
|
|
return 'observer'
|
||
|
|
}
|