@intent-framework/router 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -0
- package/dist/router.d.ts +68 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mahyar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//#region src/router.ts
|
|
2
|
+
function normalizePath(path) {
|
|
3
|
+
let normalized = path;
|
|
4
|
+
if (!normalized.startsWith("/")) normalized = "/" + normalized;
|
|
5
|
+
if (normalized.length > 1 && normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
6
|
+
return normalized;
|
|
7
|
+
}
|
|
8
|
+
function compilePattern(path) {
|
|
9
|
+
const segments = path.split("/");
|
|
10
|
+
const paramNames = [];
|
|
11
|
+
const regexParts = [];
|
|
12
|
+
for (const segment of segments) if (segment.startsWith(":")) {
|
|
13
|
+
paramNames.push(segment.slice(1));
|
|
14
|
+
regexParts.push("([^/]+)");
|
|
15
|
+
} else if (segment.length > 0) regexParts.push(segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
16
|
+
const pattern = new RegExp(`^/${regexParts.join("/")}$`);
|
|
17
|
+
return {
|
|
18
|
+
pattern,
|
|
19
|
+
paramNames
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function createRouter() {
|
|
23
|
+
const build = (currentRoutes, currentByName, currentByPath) => {
|
|
24
|
+
const router = {
|
|
25
|
+
route(name, path, screen) {
|
|
26
|
+
if (currentByName.has(name)) throw new Error(`Route name "${name}" is already registered.`);
|
|
27
|
+
const normalized = normalizePath(path);
|
|
28
|
+
if (currentByPath.has(normalized)) throw new Error(`Route path "${normalized}" is already registered.`);
|
|
29
|
+
const { pattern, paramNames } = compilePattern(normalized);
|
|
30
|
+
const route = {
|
|
31
|
+
name,
|
|
32
|
+
path: normalized,
|
|
33
|
+
pattern,
|
|
34
|
+
paramNames,
|
|
35
|
+
screen
|
|
36
|
+
};
|
|
37
|
+
const newByName = new Map(currentByName).set(name, route);
|
|
38
|
+
const newByPath = new Map(currentByPath).set(normalized, true);
|
|
39
|
+
const newRoutes = [...currentRoutes, route];
|
|
40
|
+
return build(newRoutes, newByName, newByPath);
|
|
41
|
+
},
|
|
42
|
+
match(pathname) {
|
|
43
|
+
const normalized = normalizePath(pathname);
|
|
44
|
+
for (const route of currentRoutes) {
|
|
45
|
+
const m = normalized.match(route.pattern);
|
|
46
|
+
if (m) {
|
|
47
|
+
const params = {};
|
|
48
|
+
for (let i = 0; i < route.paramNames.length; i++) {
|
|
49
|
+
const value = m[i + 1];
|
|
50
|
+
if (value !== void 0) params[route.paramNames[i]] = value;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
found: true,
|
|
54
|
+
name: route.name,
|
|
55
|
+
path: route.path,
|
|
56
|
+
params,
|
|
57
|
+
screen: route.screen
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
found: false,
|
|
63
|
+
pathname: normalized
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
path(name, params = {}) {
|
|
67
|
+
const route = currentByName.get(name);
|
|
68
|
+
if (!route) throw new Error(`Route "${name}" not found`);
|
|
69
|
+
let result = route.path;
|
|
70
|
+
for (const [key, value] of Object.entries(params)) result = result.replace(`:${key}`, value);
|
|
71
|
+
const unresolved = result.match(/:(\w+)/g);
|
|
72
|
+
if (unresolved) throw new Error(`Missing params for route "${name}": ${unresolved.join(", ")}`);
|
|
73
|
+
return result;
|
|
74
|
+
},
|
|
75
|
+
routes() {
|
|
76
|
+
return currentRoutes.map((r) => ({
|
|
77
|
+
name: r.name,
|
|
78
|
+
path: r.path,
|
|
79
|
+
screen: r.screen
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return router;
|
|
84
|
+
};
|
|
85
|
+
return build([], /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { createRouter };
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ScreenDefinition, DefaultScreenServices } from "@intent-framework/core";
|
|
2
|
+
export type RouteDefinition<TServices extends object = DefaultScreenServices> = {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
screen: ScreenDefinition<TServices>;
|
|
6
|
+
};
|
|
7
|
+
export type RouteParamNames<Path extends string> = string extends Path ? string : Path extends `${string}/:${infer Param}/${infer Rest}` ? Param | RouteParamNames<`/${Rest}`> : Path extends `${string}/:${infer Param}` ? Param : never;
|
|
8
|
+
export type RouteParams<Path extends string> = [
|
|
9
|
+
RouteParamNames<Path>
|
|
10
|
+
] extends [never] ? {} : {
|
|
11
|
+
[Key in RouteParamNames<Path>]: string;
|
|
12
|
+
};
|
|
13
|
+
export type RoutePathArgs<Path extends string> = [
|
|
14
|
+
RouteParamNames<Path>
|
|
15
|
+
] extends [never] ? [] : [params: RouteParams<Path>];
|
|
16
|
+
export type RouteParamsFor<Routes extends Record<string, {
|
|
17
|
+
path: string;
|
|
18
|
+
}>, Name extends Extract<keyof Routes, string>> = RouteParams<Routes[Name]["path"]>;
|
|
19
|
+
export type RouteContextFor<Routes extends Record<string, {
|
|
20
|
+
path: string;
|
|
21
|
+
}>, Name extends Extract<keyof Routes, string>> = {
|
|
22
|
+
name: Name;
|
|
23
|
+
path: Routes[Name]["path"];
|
|
24
|
+
params: RouteParamsFor<Routes, Name>;
|
|
25
|
+
};
|
|
26
|
+
export type RouteContext<Routes extends Record<string, {
|
|
27
|
+
path: string;
|
|
28
|
+
}>> = {
|
|
29
|
+
[Name in Extract<keyof Routes, string>]: RouteContextFor<Routes, Name>;
|
|
30
|
+
}[Extract<keyof Routes, string>];
|
|
31
|
+
export type RouterNavigate<Routes extends Record<string, {
|
|
32
|
+
path: string;
|
|
33
|
+
}>> = <Name extends Extract<keyof Routes, string>>(name: Name, ...args: RoutePathArgs<Routes[Name]["path"]>) => void;
|
|
34
|
+
export type RouterServices<Routes extends Record<string, {
|
|
35
|
+
path: string;
|
|
36
|
+
}>, ExtraServices extends object = {}> = ExtraServices & {
|
|
37
|
+
navigate: RouterNavigate<Routes>;
|
|
38
|
+
};
|
|
39
|
+
export type RoutesFromPaths<Paths extends Record<string, string>> = {
|
|
40
|
+
[Name in Extract<keyof Paths, string>]: {
|
|
41
|
+
path: Paths[Name];
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export type RouteMatch<Routes extends Record<string, {
|
|
45
|
+
path: string;
|
|
46
|
+
}> = Record<string, {
|
|
47
|
+
path: string;
|
|
48
|
+
}>, TServices extends object = DefaultScreenServices> = {
|
|
49
|
+
found: true;
|
|
50
|
+
name: keyof Routes;
|
|
51
|
+
path: string;
|
|
52
|
+
params: Record<string, string>;
|
|
53
|
+
screen: ScreenDefinition<TServices>;
|
|
54
|
+
} | {
|
|
55
|
+
found: false;
|
|
56
|
+
pathname: string;
|
|
57
|
+
};
|
|
58
|
+
export type Router<Routes extends Record<string, {
|
|
59
|
+
path: string;
|
|
60
|
+
}> = {}, TServices extends object = DefaultScreenServices> = {
|
|
61
|
+
route<Name extends string, Path extends string>(name: Name, path: Path, screen: ScreenDefinition<TServices>): Router<Routes & Record<Name, {
|
|
62
|
+
path: Path;
|
|
63
|
+
}>, TServices>;
|
|
64
|
+
match(pathname: string): RouteMatch<Routes, TServices>;
|
|
65
|
+
path<Name extends Extract<keyof Routes, string>>(name: Name, ...args: RoutePathArgs<Routes[Name]["path"]>): string;
|
|
66
|
+
routes(): RouteDefinition<TServices>[];
|
|
67
|
+
};
|
|
68
|
+
export declare function createRouter<TServices extends object = DefaultScreenServices>(): Router<{}, TServices>;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intent-framework/router",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.1.0-alpha.0",
|
|
7
|
+
"description": "Typed route definitions and navigation for Intent",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/intent-framework/intent.git",
|
|
12
|
+
"directory": "packages/router"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@intent-framework/core": "0.1.0-alpha.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsdown": "^0.3.0",
|
|
33
|
+
"typescript": "^5.7.0",
|
|
34
|
+
"vitest": "^3.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"typecheck": "tsc -b --noEmit",
|
|
40
|
+
"lint": "tsc -b --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|