Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow exporting/importing matchers #62

Merged
merged 7 commits into from
Aug 17, 2023
Merged
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ const matches = matcher.matchAll('/foo/bar/baz')
// ]
```

### Route Matcher Export

It is also possible to export and then rehydrate a matcher from pre-compiled rules.

```ts
import { exportMatcher, createMatcherFromExport } from 'radix3'

// Assuming you already have a matcher
// you can export this to a JSON-type object
const json = exportMatcher(matcher)

// and then rehydrate this later
const newMatcher = createMatcherFromExport(json)

const matches = newMatcher.matchAll('/foo/bar/baz')
```

## Performance

See [benchmark](./benchmark).
Expand Down
56 changes: 53 additions & 3 deletions src/matcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { RadixNode, RadixRouter, RadixNodeData, NODE_TYPES } from "./types";
import {
RadixNode,
RadixRouter,
RadixNodeData,
NODE_TYPES,
MatcherExport,
} from "./types";

export interface RouteTable {
static: Map<string, RadixNodeData>;
Expand All @@ -17,10 +23,10 @@ export function toRouteMatcher(router: RadixRouter): RouteMatcher {
}

function _createMatcher(table: RouteTable): RouteMatcher {
return <RouteMatcher>{
return {
ctx: { table },
matchAll: (path) => _matchRoutes(path, table),
};
} satisfies RouteMatcher;
}

function _createRouteTable(): RouteTable {
Expand All @@ -31,6 +37,50 @@ function _createRouteTable(): RouteTable {
};
}

function _exportMatcherFromTable(table: RouteTable): MatcherExport {
const obj = Object.create(null);

for (const property in table) {
obj[property] =
property === "dynamic"
? Object.fromEntries(
[...table[property].entries()].map(([key, value]) => [
key,
_exportMatcherFromTable(value),
])
)
: Object.fromEntries(table[property].entries());
}

return obj;
}

export function exportMatcher(matcher: RouteMatcher): MatcherExport {
return _exportMatcherFromTable(matcher.ctx.table);
}

function _createTableFromExport(matcherExport: MatcherExport): RouteTable {
const table: Partial<RouteTable> = {};
for (const property in matcherExport) {
table[property] =
property === "dynamic"
? new Map(
Object.entries(matcherExport[property]).map(([key, value]) => [
key,
_createTableFromExport(value as any),
])
)
: new Map(Object.entries(matcherExport[property]));
}
return table as RouteTable;
}

export function createMatcherFromExport(
matcherExport: MatcherExport
): RouteMatcher {
return _createMatcher(_createTableFromExport(matcherExport));
}

function _matchRoutes(path: string, table: RouteTable): RadixNodeData[] {
// Order should be from less specific to most specific
const matches = [];
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ export interface RadixRouter<T extends RadixNodeData = RadixNodeData> {
*/
remove(path: string): boolean;
}

export interface MatcherExport {
dynamic: Map<string, MatcherExport>;
wildcard: Map<string, { pattern: string }>;
static: Map<string, { pattern: string }>;
}
46 changes: 45 additions & 1 deletion tests/matcher.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { createRouter, toRouteMatcher } from "../src";
import { createRouter, exportMatcher, toRouteMatcher } from "../src";

export function createRoutes(paths) {
return Object.fromEntries(paths.map((path) => [path, { pattern: path }]));
Expand Down Expand Up @@ -141,4 +141,48 @@ describe("Route matcher", function () {
]
`);
});

it("can be exported", () => {
const jsonData = exportMatcher(matcher);
expect(jsonData).toMatchInlineSnapshot(`
{
"dynamic": {
"/foo": {
"dynamic": {},
"static": {
"/": {
"pattern": "/foo/*",
},
"/sub": {
"pattern": "/foo/*/sub",
},
},
"wildcard": {},
},
},
"static": {
"/": {
"pattern": "/",
},
"/foo": {
"pattern": "/foo",
},
"/foo/bar": {
"pattern": "/foo/bar",
},
"/foo/baz": {
"pattern": "/foo/baz",
},
},
"wildcard": {
"/foo": {
"pattern": "/foo/**",
},
"/foo/baz": {
"pattern": "/foo/baz/**",
},
},
}
`);
});
});