Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 131 additions & 36 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@
"youch": "^4.1.1"
},
"dependencies": {
"@boringnode/route-matcher": "^0.1.1",
"@poppinss/macroable": "^1.1.2",
"@poppinss/matchit": "^3.2.0",
"@poppinss/middleware": "^3.2.7",
"@poppinss/qs": "^6.15.0",
"@poppinss/types": "^1.2.1",
Expand Down
4 changes: 2 additions & 2 deletions src/client/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/

import { type ClientRouteMatchItTokens, type ClientRouteJSON, type URLOptions } from './types.ts'
import { type ClientRouteToken, type ClientRouteJSON, type URLOptions } from './types.ts'

/**
* Finds a route by its identifier across domains.
Expand Down Expand Up @@ -99,7 +99,7 @@ export function findRoute<Route extends ClientRouteJSON>(
*/
export function createURL(
pattern: string,
tokens: Pick<ClientRouteMatchItTokens, 'val' | 'type' | 'end'>[],
tokens: Pick<ClientRouteToken, 'val' | 'type' | 'end'>[],
searchParamsStringifier: (qs: Record<string, any>) => string,
params?: any[] | { [param: string]: any },
options?: URLOptions
Expand Down
9 changes: 7 additions & 2 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* Types shared with the client. These should never import other types
*/

export type ClientRouteMatchItTokens = {
export type ClientRouteToken = {
/** Original token string */
old: string
/** Token type identifier (0=static, 1=param, 2=wildcard, 3=optional) */
Expand All @@ -22,6 +22,11 @@ export type ClientRouteMatchItTokens = {
end: string
}

/**
* @deprecated Use `ClientRouteToken` instead.
*/
export type ClientRouteMatchItTokens = ClientRouteToken

/**
* Complete route definition with all metadata, handlers, and execution context
*/
Expand All @@ -44,7 +49,7 @@ export type ClientRouteJSON = {
/**
* Tokens to be used to construct the route URL
*/
tokens: ClientRouteMatchItTokens[]
tokens: ClientRouteToken[]

/**
* HTTP methods, the route responds to.
Expand Down
31 changes: 16 additions & 15 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@
*/

import { serialize } from 'cookie-es'
// @ts-expect-error
import matchit from '@poppinss/matchit'
import string from '@poppinss/utils/string'
import { type Encryption } from '@boringnode/encryption'
import { parseBindingReference } from '@adonisjs/fold'
import {
parseRoute as parseRoutePattern,
matchRouteTokens,
type RouteToken,
type RouteMatchers,
} from '@boringnode/route-matcher'

import { type Qs } from './qs.ts'
import { safeDecodeURI } from './utils.ts'
import type { HttpRequest } from './request.ts'
import { createURL } from './client/helpers.ts'
import { type CookieOptions } from './types/response.ts'
import { type SignedURLOptions } from './types/url_builder.ts'
import type { RouteMatchers, RouteJSON, MatchItRouteToken } from './types/route.ts'
import type { RouteJSON } from './types/route.ts'
import {
type MiddlewareFn,
type RouteHandlerInfo,
Expand Down Expand Up @@ -156,11 +160,10 @@ export { default as mime } from 'mime-types'
*
* @param pattern - The route pattern to parse
* @param matchers - Optional route matchers
* @returns {MatchItRouteToken[]} Array of parsed route tokens
* @returns {RouteToken[]} Array of parsed route tokens
*/
export function parseRoute(pattern: string, matchers?: RouteMatchers): MatchItRouteToken[] {
const tokens = matchit.parse(pattern, matchers)
return tokens
export function parseRoute(pattern: string, matchers?: RouteMatchers): RouteToken[] {
return parseRoutePattern(pattern, matchers)
}

/**
Expand All @@ -177,7 +180,7 @@ export function parseRoute(pattern: string, matchers?: RouteMatchers): MatchItRo
*/
export function createSignedURL(
identifier: string,
tokens: MatchItRouteToken[],
tokens: RouteToken[],
searchParamsStringifier: (qs: Record<string, any>) => string,
encryption: Encryption,
params?: any[] | { [param: string]: any },
Expand Down Expand Up @@ -215,13 +218,11 @@ export function createSignedURL(
* @returns {null | Record<string, string>} Extracted parameters or null if no match
*/
export function matchRoute(url: string, patterns: string[]): null | Record<string, string> {
const tokensBucket = patterns.map((pattern) => parseRoute(pattern))
const match = matchit.match(url, tokensBucket)
if (!match.length) {
return null
}

return matchit.exec(url, match)
return matchRouteTokens(
url,
patterns.map((pattern) => parseRoute(pattern)),
false
)
}

/**
Expand Down
63 changes: 40 additions & 23 deletions src/router/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@
* file that was distributed with this source code.
*/

// @ts-expect-error
import matchit from '@poppinss/matchit'
import { RuntimeException } from '@poppinss/utils/exception'
import { RouteTable, extractRouteParams, type RouteToken } from '@boringnode/route-matcher'

import type {
RouteJSON,
MatchedRoute,
StoreDomainNode,
StoreMethodNode,
StoreRoutesTree,
MatchItRouteToken,
} from '../types/route.ts'
import debug from '../debug.ts'
import { parseRoute } from '../helpers.ts'
Expand All @@ -44,14 +42,21 @@ import { parseRoute } from '../helpers.ts'
* ```
*/
export class RoutesStore {
/**
* Lookup indexes are kept outside the public routes tree to avoid changing
* its observable shape.
*/
#methodRouteTables = new WeakMap<StoreMethodNode, RouteTable<RouteJSON>>()
#domainRouteTable = new RouteTable<RouteToken[]>()

/**
* A flag to know if routes for explicit domains
* have been registered
*/
usingDomains: boolean = false

/**
* Tree of registered routes and their matchit tokens
* Tree of registered routes and their parsed tokens
*/
tree: StoreRoutesTree = { tokens: [], domains: {} }

Expand All @@ -60,7 +65,9 @@ export class RoutesStore {
*/
#getDomainNode(domain: string): StoreDomainNode {
if (!this.tree.domains[domain]) {
this.tree.tokens.push(parseRoute(domain))
const tokens = parseRoute(domain)
this.tree.tokens.push(tokens)
this.#domainRouteTable.add(tokens, tokens)
this.tree.domains[domain] = {}
}

Expand All @@ -74,15 +81,33 @@ export class RoutesStore {
const domainNode = this.#getDomainNode(domain)
if (!domainNode[method]) {
domainNode[method] = { tokens: [], routes: {}, routeKeys: {} }
this.#methodRouteTables.set(domainNode[method], new RouteTable())
}

return domainNode[method]
}

/**
* Creates the public match result for a route and its collected params.
*/
#createMatchedRoute(
route: RouteJSON,
methodNode: StoreMethodNode,
params: Record<string, any>,
domain?: { tokens: RouteToken[]; hostname: string }
): MatchedRoute {
return {
route,
routeKey: methodNode.routeKeys[route.pattern],
params,
subdomains: domain?.hostname ? extractRouteParams(domain.tokens, domain.hostname, false) : {},
}
}

/**
* Collects route params
*/
#collectRouteParams(route: RouteJSON, tokens: MatchItRouteToken[]) {
#collectRouteParams(route: RouteJSON, tokens: RouteToken[]) {
const collectedParams: Set<string> = new Set()

for (let token of tokens) {
Expand All @@ -104,7 +129,7 @@ export class RoutesStore {
/**
* Register route for a given domain and method
*/
#registerRoute(domain: string, method: string, tokens: MatchItRouteToken[], route: RouteJSON) {
#registerRoute(domain: string, method: string, tokens: RouteToken[], route: RouteJSON) {
const methodRoutes = this.#getMethodNode(domain, method)

/*
Expand All @@ -121,6 +146,8 @@ export class RoutesStore {
debug('route middleware %O', route.middleware.all().entries())
}

this.#methodRouteTables.get(methodRoutes)!.add(tokens, route)

methodRoutes.tokens.push(tokens)
methodRoutes.routes[route.pattern] = route
methodRoutes.routeKeys[route.pattern] =
Expand Down Expand Up @@ -189,7 +216,7 @@ export class RoutesStore {
url: string,
method: string,
shouldDecodeParam: boolean,
domain?: { tokens: MatchItRouteToken[]; hostname: string }
domain?: { tokens: RouteToken[]; hostname: string }
): null | MatchedRoute {
const domainName = domain?.tokens[0]?.old || 'root'

Expand All @@ -208,34 +235,24 @@ export class RoutesStore {
return null
}

/*
* Next, match route for the given url inside the tokens list for the
* matchedMethod
*/
const matchedRoute = matchit.match(url, matchedMethod.tokens)
if (!matchedRoute.length) {
const matchedRoute = this.#methodRouteTables.get(matchedMethod)!.match(url, shouldDecodeParam)
if (!matchedRoute) {
return null
}

const route = matchedMethod.routes[matchedRoute[0].old]
return {
route: route,
routeKey: matchedMethod.routeKeys[route.pattern],
params: matchit.exec(url, matchedRoute, shouldDecodeParam),
subdomains: domain?.hostname ? matchit.exec(domain.hostname, domain.tokens) : {},
}
return this.#createMatchedRoute(matchedRoute.value, matchedMethod, matchedRoute.params, domain)
}

/**
* Match hostname against registered domains.
* @param hostname - The hostname to match
* @returns Array of matched domain tokens
*/
matchDomain(hostname?: string | null): MatchItRouteToken[] {
matchDomain(hostname?: string | null): RouteToken[] {
if (!hostname || !this.usingDomains) {
return []
}

return matchit.match(hostname, this.tree.tokens)
return this.#domainRouteTable.match(hostname, false)?.value ?? []
}
}
31 changes: 8 additions & 23 deletions src/types/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,19 @@
import type Middleware from '@poppinss/middleware'
import type { ContainerResolver } from '@adonisjs/fold'
import type { Constructor, LazyImport } from '@poppinss/utils/types'
import type { RouteMatchers, RouteToken } from '@boringnode/route-matcher'

import type { ServerErrorHandler } from './server.ts'
import type { HttpContext } from '../http_context/main.ts'
import type { MiddlewareFn, ParsedGlobalMiddleware } from './middleware.ts'
import { type ClientRouteJSON, type ClientRouteMatchItTokens } from '../client/types.ts'
import { type ClientRouteJSON } from '../client/types.ts'

/**
* Configuration for matching and casting route parameters
*/
export type RouteMatcher = {
/** Regular expression to match parameter values */
match?: RegExp
/** Function to cast string parameter values to specific types */
cast?: (value: string) => any
}
export type { RouteMatcher, RouteMatchers, RouteToken } from '@boringnode/route-matcher'

/**
* Route token structure used internally by the matchit routing library
* @deprecated Use `RouteToken` instead.
*/
export type MatchItRouteToken = RouteMatcher & ClientRouteMatchItTokens
export type MatchItRouteToken = RouteToken

/**
* Extracts method names from a controller class that accept HttpContext as first parameter
Expand Down Expand Up @@ -80,7 +73,7 @@ export type StoreRouteMiddleware =
*/
export type StoreMethodNode = {
/** Array of route tokens for pattern matching */
tokens: MatchItRouteToken[][]
tokens: RouteToken[][]
/** Mapping from route patterns to unique route keys */
routeKeys: {
[pattern: string]: string
Expand All @@ -104,7 +97,7 @@ export type StoreDomainNode = {
*/
export type StoreRoutesTree = {
/** Global route tokens for pattern matching */
tokens: MatchItRouteToken[][]
tokens: RouteToken[][]
/** Domain-based route organization */
domains: {
[domain: string]: StoreDomainNode
Expand Down Expand Up @@ -134,14 +127,6 @@ export type MatchedRoute = {
subdomains: Record<string, any>
}

/**
* Collection of parameter matchers indexed by parameter name
*/
export type RouteMatchers = {
/** Parameter name to matcher mapping */
[param: string]: RouteMatcher
}

/**
* Complete route definition with all metadata, handlers, and execution context
*/
Expand Down Expand Up @@ -175,7 +160,7 @@ export type RouteJSON = Pick<ClientRouteJSON, 'name' | 'methods' | 'domain' | 'p
/**
* Tokens to be used to construct the route URL
*/
tokens: MatchItRouteToken[]
tokens: RouteToken[]

/**
* Matchers for route params.
Expand Down
Loading
Loading