@tinyfx/runtime 0.1.8 → 0.2.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/dist/__tests__/http-errors.test.d.ts +1 -0
- package/dist/__tests__/http-errors.test.js +65 -0
- package/dist/__tests__/mount-state.test.d.ts +1 -0
- package/dist/__tests__/mount-state.test.js +30 -0
- package/dist/__tests__/page-registry.test.d.ts +1 -0
- package/dist/__tests__/page-registry.test.js +38 -0
- package/dist/__tests__/path-matcher.test.d.ts +1 -0
- package/dist/__tests__/path-matcher.test.js +43 -0
- package/dist/__tests__/registry.test.d.ts +1 -0
- package/dist/__tests__/registry.test.js +67 -0
- package/dist/__tests__/router-merged.test.d.ts +1 -0
- package/dist/__tests__/router-merged.test.js +33 -0
- package/dist/__tests__/signals.test.d.ts +1 -0
- package/dist/__tests__/signals.test.js +93 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.js +0 -2
- package/dist/dom.d.ts +35 -0
- package/dist/dom.js +35 -0
- package/dist/each.d.ts +10 -0
- package/dist/each.js +24 -0
- package/dist/http/data.d.ts +84 -17
- package/dist/http/data.js +15 -23
- package/dist/http/helper.d.ts +20 -0
- package/dist/http/helper.js +20 -0
- package/dist/http/http.d.ts +13 -0
- package/dist/http/http.js +19 -6
- package/dist/index.d.ts +10 -2
- package/dist/index.js +9 -1
- package/dist/init.d.ts +12 -0
- package/dist/init.js +42 -0
- package/dist/mount-state.d.ts +17 -0
- package/dist/mount-state.js +27 -0
- package/dist/page-registry.d.ts +31 -0
- package/dist/page-registry.js +38 -0
- package/dist/registry.d.ts +31 -0
- package/dist/registry.js +49 -0
- package/dist/router/active-links.d.ts +3 -0
- package/dist/router/active-links.js +3 -0
- package/dist/router/index.d.ts +21 -22
- package/dist/router/index.js +12 -35
- package/dist/router/lifecycle.d.ts +28 -2
- package/dist/router/lifecycle.js +28 -2
- package/dist/router/navigate.d.ts +8 -0
- package/dist/router/navigate.js +8 -0
- package/dist/router/params.d.ts +15 -7
- package/dist/router/params.js +16 -25
- package/dist/router/path-matcher.d.ts +30 -0
- package/dist/router/path-matcher.js +45 -0
- package/dist/router/types.d.ts +15 -2
- package/dist/signals.d.ts +28 -7
- package/dist/signals.js +41 -21
- package/package.json +6 -2
package/dist/router/lifecycle.js
CHANGED
|
@@ -6,6 +6,14 @@ const destroyCallbacks = [];
|
|
|
6
6
|
*
|
|
7
7
|
* - If the document is still loading, the callback runs after DOMContentLoaded.
|
|
8
8
|
* - If the document is already loaded, the callback runs immediately.
|
|
9
|
+
*
|
|
10
|
+
* @param fn - The callback to run on mount
|
|
11
|
+
* @returns Nothing
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* onMount(() => {
|
|
15
|
+
* console.log("page ready");
|
|
16
|
+
* });
|
|
9
17
|
*/
|
|
10
18
|
export function onMount(fn) {
|
|
11
19
|
if (document.readyState === "loading") {
|
|
@@ -17,13 +25,26 @@ export function onMount(fn) {
|
|
|
17
25
|
/**
|
|
18
26
|
* Register a callback to run when the user navigates away from this page.
|
|
19
27
|
* Fires on the browser's `pagehide` event.
|
|
28
|
+
*
|
|
29
|
+
* @param fn - The callback to run during page teardown
|
|
30
|
+
* @returns Nothing
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* onDestroy(() => {
|
|
34
|
+
* console.log("page leaving");
|
|
35
|
+
* });
|
|
20
36
|
*/
|
|
21
37
|
export function onDestroy(fn) {
|
|
22
38
|
destroyCallbacks.push(fn);
|
|
23
39
|
}
|
|
24
40
|
/**
|
|
25
41
|
* Trigger all registered `onMount` callbacks.
|
|
26
|
-
* Called by `
|
|
42
|
+
* Called by `init()` after matching the current route.
|
|
43
|
+
*
|
|
44
|
+
* @returns Nothing
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* flushMount();
|
|
27
48
|
*/
|
|
28
49
|
export function flushMount() {
|
|
29
50
|
const run = () => mountCallbacks.forEach((fn) => fn());
|
|
@@ -36,7 +57,12 @@ export function flushMount() {
|
|
|
36
57
|
}
|
|
37
58
|
/**
|
|
38
59
|
* Wire up registered `onDestroy` callbacks to the `pagehide` event.
|
|
39
|
-
* Called by `
|
|
60
|
+
* Called by `init()` after matching the current route.
|
|
61
|
+
*
|
|
62
|
+
* @returns Nothing
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* initLifecycle();
|
|
40
66
|
*/
|
|
41
67
|
export function initLifecycle() {
|
|
42
68
|
flushMount();
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
* Navigate to the given path.
|
|
3
3
|
* This is a full browser navigation — not a DOM swap.
|
|
4
4
|
*
|
|
5
|
+
* @param path - The destination pathname or URL
|
|
6
|
+
* @returns Nothing
|
|
7
|
+
*
|
|
5
8
|
* @example
|
|
6
9
|
* navigate("/blog/hello-world");
|
|
7
10
|
*/
|
|
8
11
|
export declare function navigate(path: string): void;
|
|
9
12
|
/**
|
|
10
13
|
* Go back one step in the browser history.
|
|
14
|
+
*
|
|
15
|
+
* @returns Nothing
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* goBack();
|
|
11
19
|
*/
|
|
12
20
|
export declare function goBack(): void;
|
package/dist/router/navigate.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Navigate to the given path.
|
|
5
5
|
* This is a full browser navigation — not a DOM swap.
|
|
6
6
|
*
|
|
7
|
+
* @param path - The destination pathname or URL
|
|
8
|
+
* @returns Nothing
|
|
9
|
+
*
|
|
7
10
|
* @example
|
|
8
11
|
* navigate("/blog/hello-world");
|
|
9
12
|
*/
|
|
@@ -12,6 +15,11 @@ export function navigate(path) {
|
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Go back one step in the browser history.
|
|
18
|
+
*
|
|
19
|
+
* @returns Nothing
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* goBack();
|
|
15
23
|
*/
|
|
16
24
|
export function goBack() {
|
|
17
25
|
window.history.go(-1);
|
package/dist/router/params.d.ts
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Set resolved route params (called internally by init()).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* module-level store so `getParam()` can retrieve them.
|
|
6
|
-
*
|
|
7
|
-
* Static segments must match exactly; `:param` segments match any non-empty
|
|
8
|
-
* path segment.
|
|
4
|
+
* @param params - The resolved URL parameters
|
|
9
5
|
*/
|
|
10
|
-
export declare function
|
|
6
|
+
export declare function setParams(params: Record<string, string>): void;
|
|
11
7
|
/**
|
|
12
8
|
* Retrieve a URL parameter resolved for the current page.
|
|
13
9
|
*
|
|
10
|
+
* @param key - The route parameter name to read
|
|
11
|
+
* @returns The decoded parameter value, or `undefined` when it is missing
|
|
12
|
+
*
|
|
14
13
|
* @example
|
|
15
14
|
* // On the page with route "/blog/:slug" and URL "/blog/hello-world"
|
|
16
15
|
* getParam("slug"); // → "hello-world"
|
|
17
16
|
*/
|
|
18
17
|
export declare function getParam(key: string): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Returns a shallow copy of all currently resolved route params.
|
|
20
|
+
*
|
|
21
|
+
* @returns An object containing the current route parameters
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const params = getParams();
|
|
25
|
+
* console.log(params.slug);
|
|
26
|
+
*/
|
|
19
27
|
export declare function getParams(): Record<string, string>;
|
package/dist/router/params.js
CHANGED
|
@@ -2,37 +2,19 @@
|
|
|
2
2
|
/** Module-level store for the current page's resolved params. */
|
|
3
3
|
let currentParams = {};
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Set resolved route params (called internally by init()).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* module-level store so `getParam()` can retrieve them.
|
|
9
|
-
*
|
|
10
|
-
* Static segments must match exactly; `:param` segments match any non-empty
|
|
11
|
-
* path segment.
|
|
7
|
+
* @param params - The resolved URL parameters
|
|
12
8
|
*/
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
const pathParts = pathname.split("/").filter(Boolean);
|
|
16
|
-
if (patternParts.length !== pathParts.length)
|
|
17
|
-
return false;
|
|
18
|
-
const captured = {};
|
|
19
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
20
|
-
const seg = patternParts[i];
|
|
21
|
-
if (seg.startsWith(":")) {
|
|
22
|
-
// Dynamic segment — capture the value
|
|
23
|
-
captured[seg.slice(1)] = decodeURIComponent(pathParts[i]);
|
|
24
|
-
}
|
|
25
|
-
else if (seg !== pathParts[i]) {
|
|
26
|
-
// Static segment mismatch
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
currentParams = captured;
|
|
31
|
-
return true;
|
|
9
|
+
export function setParams(params) {
|
|
10
|
+
currentParams = params;
|
|
32
11
|
}
|
|
33
12
|
/**
|
|
34
13
|
* Retrieve a URL parameter resolved for the current page.
|
|
35
14
|
*
|
|
15
|
+
* @param key - The route parameter name to read
|
|
16
|
+
* @returns The decoded parameter value, or `undefined` when it is missing
|
|
17
|
+
*
|
|
36
18
|
* @example
|
|
37
19
|
* // On the page with route "/blog/:slug" and URL "/blog/hello-world"
|
|
38
20
|
* getParam("slug"); // → "hello-world"
|
|
@@ -40,6 +22,15 @@ export function resolveParams(pattern, pathname) {
|
|
|
40
22
|
export function getParam(key) {
|
|
41
23
|
return currentParams[key];
|
|
42
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns a shallow copy of all currently resolved route params.
|
|
27
|
+
*
|
|
28
|
+
* @returns An object containing the current route parameters
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const params = getParams();
|
|
32
|
+
* console.log(params.slug);
|
|
33
|
+
*/
|
|
43
34
|
export function getParams() {
|
|
44
35
|
return Object.assign({}, currentParams);
|
|
45
36
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface RouteDef {
|
|
2
|
+
/** Static segment values (null for dynamic segments) */
|
|
3
|
+
staticSegments: (string | null)[];
|
|
4
|
+
/** Param names at dynamic positions */
|
|
5
|
+
paramNames: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Match a pre-compiled route definition against path segments.
|
|
9
|
+
*
|
|
10
|
+
* @param def - The route definition from the compiler
|
|
11
|
+
* @param pathSegments - Pre-split path segments from splitPath()
|
|
12
|
+
* @returns Captured params on match, null on mismatch
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const def = { staticSegments: ["blog", null], paramNames: ["slug"] };
|
|
16
|
+
* matchPath(def, ["blog", "hello-world"]);
|
|
17
|
+
* // Returns: { slug: "hello-world" }
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchPath(def: RouteDef, pathSegments: string[]): Record<string, string> | null;
|
|
20
|
+
/**
|
|
21
|
+
* Split a pathname into segments for matching.
|
|
22
|
+
*
|
|
23
|
+
* @param pathname - The browser pathname (e.g., "/blog/hello")
|
|
24
|
+
* @returns Array of non-empty segments
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* splitPath("/blog/hello-world");
|
|
28
|
+
* // Returns: ["blog", "hello-world"]
|
|
29
|
+
*/
|
|
30
|
+
export declare function splitPath(pathname: string): string[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @tinyfx/runtime — path matcher
|
|
2
|
+
// Unified path resolution with pre-split array support for compiler optimization.
|
|
3
|
+
/**
|
|
4
|
+
* Match a pre-compiled route definition against path segments.
|
|
5
|
+
*
|
|
6
|
+
* @param def - The route definition from the compiler
|
|
7
|
+
* @param pathSegments - Pre-split path segments from splitPath()
|
|
8
|
+
* @returns Captured params on match, null on mismatch
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const def = { staticSegments: ["blog", null], paramNames: ["slug"] };
|
|
12
|
+
* matchPath(def, ["blog", "hello-world"]);
|
|
13
|
+
* // Returns: { slug: "hello-world" }
|
|
14
|
+
*/
|
|
15
|
+
export function matchPath(def, pathSegments) {
|
|
16
|
+
const { staticSegments, paramNames } = def;
|
|
17
|
+
if (staticSegments.length !== pathSegments.length)
|
|
18
|
+
return null;
|
|
19
|
+
const params = {};
|
|
20
|
+
let paramIdx = 0;
|
|
21
|
+
for (let i = 0; i < staticSegments.length; i++) {
|
|
22
|
+
const expected = staticSegments[i];
|
|
23
|
+
const actual = pathSegments[i];
|
|
24
|
+
if (expected === null) {
|
|
25
|
+
params[paramNames[paramIdx++]] = decodeURIComponent(actual);
|
|
26
|
+
}
|
|
27
|
+
else if (expected !== actual) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return params;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Split a pathname into segments for matching.
|
|
35
|
+
*
|
|
36
|
+
* @param pathname - The browser pathname (e.g., "/blog/hello")
|
|
37
|
+
* @returns Array of non-empty segments
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* splitPath("/blog/hello-world");
|
|
41
|
+
* // Returns: ["blog", "hello-world"]
|
|
42
|
+
*/
|
|
43
|
+
export function splitPath(pathname) {
|
|
44
|
+
return pathname.split("/").filter(Boolean);
|
|
45
|
+
}
|
package/dist/router/types.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Metadata for a single route produced by the compiler.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const route: RouteMeta = { page: "blog-post", path: "/blog/:slug" };
|
|
6
|
+
*/
|
|
2
7
|
export interface RouteMeta {
|
|
3
8
|
/** The page name, derived from the source filename. */
|
|
4
9
|
page: string;
|
|
5
10
|
/** The declared URL pattern, e.g. "/blog/:slug". */
|
|
6
11
|
path: string;
|
|
7
12
|
}
|
|
8
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The full route map emitted by `tinyfx build` into `tinyfx.gen.ts`.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const routes: RouteMap = {
|
|
18
|
+
* "/": { page: "index", path: "/" },
|
|
19
|
+
* "/blog/:slug": { page: "blog-post", path: "/blog/:slug" },
|
|
20
|
+
* };
|
|
21
|
+
*/
|
|
9
22
|
export type RouteMap = Record<string, RouteMeta>;
|
package/dist/signals.d.ts
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Creates a reactive signal function.
|
|
3
|
+
*
|
|
4
|
+
* @param v - The initial value stored by the signal
|
|
5
|
+
* @returns A callable signal that reads the current value and exposes `.set()`
|
|
6
|
+
* to update it
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const count = signal(0);
|
|
10
|
+
* console.log(count());
|
|
11
|
+
* count.set(1);
|
|
12
|
+
*/
|
|
8
13
|
export declare function signal<T>(v: T): (() => T) & {
|
|
9
14
|
set(v: T): void;
|
|
10
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Registers and runs a reactive effect.
|
|
18
|
+
*
|
|
19
|
+
* Every signal read while `fn` runs is tracked, and the effect is re-run when
|
|
20
|
+
* any of those signal values change.
|
|
21
|
+
*
|
|
22
|
+
* @param fn - The reactive callback to execute and subscribe
|
|
23
|
+
* @returns Nothing
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const count = signal(0);
|
|
27
|
+
* effect(() => {
|
|
28
|
+
* console.log(`count = ${count()}`);
|
|
29
|
+
* });
|
|
30
|
+
* count.set(1);
|
|
31
|
+
*/
|
|
11
32
|
export declare function effect(fn: () => void): void;
|
package/dist/signals.js
CHANGED
|
@@ -1,31 +1,51 @@
|
|
|
1
1
|
// @tinyfx/runtime — signals
|
|
2
2
|
// Minimal, explicit reactivity — no proxies, no magic.
|
|
3
3
|
const effectStack = [];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Creates a reactive signal function.
|
|
6
|
+
*
|
|
7
|
+
* @param v - The initial value stored by the signal
|
|
8
|
+
* @returns A callable signal that reads the current value and exposes `.set()`
|
|
9
|
+
* to update it
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const count = signal(0);
|
|
13
|
+
* console.log(count());
|
|
14
|
+
* count.set(1);
|
|
15
|
+
*/
|
|
16
|
+
export function signal(v) {
|
|
17
|
+
let value = v;
|
|
18
|
+
const subs = new Set();
|
|
19
|
+
const fn = () => {
|
|
10
20
|
const running = effectStack[effectStack.length - 1];
|
|
11
|
-
if (running)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (Object.is(v, this.value))
|
|
21
|
+
if (running)
|
|
22
|
+
subs.add(running);
|
|
23
|
+
return value;
|
|
24
|
+
};
|
|
25
|
+
fn.set = (next) => {
|
|
26
|
+
if (Object.is(next, value))
|
|
18
27
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export function signal(v) {
|
|
24
|
-
const s = new Signal(v);
|
|
25
|
-
const fn = () => s.get();
|
|
26
|
-
fn.set = (v) => s.set(v);
|
|
28
|
+
value = next;
|
|
29
|
+
subs.forEach((s) => s());
|
|
30
|
+
};
|
|
27
31
|
return fn;
|
|
28
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Registers and runs a reactive effect.
|
|
35
|
+
*
|
|
36
|
+
* Every signal read while `fn` runs is tracked, and the effect is re-run when
|
|
37
|
+
* any of those signal values change.
|
|
38
|
+
*
|
|
39
|
+
* @param fn - The reactive callback to execute and subscribe
|
|
40
|
+
* @returns Nothing
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const count = signal(0);
|
|
44
|
+
* effect(() => {
|
|
45
|
+
* console.log(`count = ${count()}`);
|
|
46
|
+
* });
|
|
47
|
+
* count.set(1);
|
|
48
|
+
*/
|
|
29
49
|
export function effect(fn) {
|
|
30
50
|
const run = () => {
|
|
31
51
|
effectStack.push(run);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinyfx/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Minimal frontend runtime — signals, DOM helpers, typed HTTP, DTO mapping",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
|
+
"test": "vitest run",
|
|
20
21
|
"prepublishOnly": "npm run build"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
|
-
"
|
|
24
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
25
|
+
"jsdom": "^29.0.1",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^4.1.2"
|
|
24
28
|
},
|
|
25
29
|
"license": "MIT"
|
|
26
30
|
}
|