@tinyfx/runtime 0.1.8 → 0.1.9
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__/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/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 +76 -0
- package/dist/http/data.js +36 -0
- 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 +13 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/init.d.ts +12 -0
- package/dist/init.js +40 -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 +1 -0
- package/dist/router/index.js +1 -0
- package/dist/router/lifecycle.d.ts +26 -0
- package/dist/router/lifecycle.js +26 -0
- package/dist/router/navigate.d.ts +8 -0
- package/dist/router/navigate.js +8 -0
- package/dist/router/params.d.ts +19 -0
- package/dist/router/params.js +19 -0
- 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 +41 -0
- package/dist/signals.js +41 -0
- package/package.json +6 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { markMounted, isMounted } from "../mount-state";
|
|
3
|
+
describe("markMounted", () => {
|
|
4
|
+
it("returns true on first call", () => {
|
|
5
|
+
const el = document.createElement("div");
|
|
6
|
+
expect(markMounted(el)).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it("returns false on second call for same element", () => {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
|
+
markMounted(el);
|
|
11
|
+
expect(markMounted(el)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
it("returns true for different elements", () => {
|
|
14
|
+
const el1 = document.createElement("div");
|
|
15
|
+
const el2 = document.createElement("div");
|
|
16
|
+
markMounted(el1);
|
|
17
|
+
expect(markMounted(el2)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("isMounted", () => {
|
|
21
|
+
it("returns false for unmarked element", () => {
|
|
22
|
+
const el = document.createElement("div");
|
|
23
|
+
expect(isMounted(el)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
it("returns true after markMounted", () => {
|
|
26
|
+
const el = document.createElement("div");
|
|
27
|
+
markMounted(el);
|
|
28
|
+
expect(isMounted(el)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { registerPage, getPageModule, runPageInit } from "../page-registry";
|
|
3
|
+
describe("registerPage / getPageModule", () => {
|
|
4
|
+
it("returns undefined for unregistered page", () => {
|
|
5
|
+
expect(getPageModule("/unknown")).toBeUndefined();
|
|
6
|
+
});
|
|
7
|
+
it("returns module after registration", () => {
|
|
8
|
+
const mod = { init: () => { } };
|
|
9
|
+
registerPage("/test", mod);
|
|
10
|
+
expect(getPageModule("/test")).toBe(mod);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe("runPageInit", () => {
|
|
14
|
+
it("calls init on page module", () => {
|
|
15
|
+
let called = false;
|
|
16
|
+
let receivedEl;
|
|
17
|
+
let receivedCtx;
|
|
18
|
+
registerPage("/init-test", {
|
|
19
|
+
init: (el, ctx) => {
|
|
20
|
+
called = true;
|
|
21
|
+
receivedEl = el;
|
|
22
|
+
receivedCtx = ctx;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const ctx = { params: { id: "1" }, navigate: () => { } };
|
|
26
|
+
runPageInit("/init-test", ctx);
|
|
27
|
+
expect(called).toBe(true);
|
|
28
|
+
expect(receivedEl).toBe(document.body);
|
|
29
|
+
expect(receivedCtx).toBe(ctx);
|
|
30
|
+
});
|
|
31
|
+
it("does nothing for unregistered page", () => {
|
|
32
|
+
expect(() => runPageInit("/nonexistent", { params: {}, navigate: () => { } })).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
it("does nothing for page without init", () => {
|
|
35
|
+
registerPage("/no-init", {});
|
|
36
|
+
expect(() => runPageInit("/no-init", { params: {}, navigate: () => { } })).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { matchPath, splitPath } from "../router/path-matcher";
|
|
3
|
+
describe("splitPath", () => {
|
|
4
|
+
it("splits root path", () => {
|
|
5
|
+
expect(splitPath("/")).toEqual([]);
|
|
6
|
+
});
|
|
7
|
+
it("splits nested path", () => {
|
|
8
|
+
expect(splitPath("/blog/hello-world")).toEqual(["blog", "hello-world"]);
|
|
9
|
+
});
|
|
10
|
+
it("handles trailing slash", () => {
|
|
11
|
+
expect(splitPath("/blog/")).toEqual(["blog"]);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe("matchPath", () => {
|
|
15
|
+
it("matches static route exactly", () => {
|
|
16
|
+
const def = { staticSegments: ["blog"], paramNames: [] };
|
|
17
|
+
expect(matchPath(def, ["blog"])).toEqual({});
|
|
18
|
+
});
|
|
19
|
+
it("returns null for static mismatch", () => {
|
|
20
|
+
const def = { staticSegments: ["blog"], paramNames: [] };
|
|
21
|
+
expect(matchPath(def, ["about"])).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
it("returns null for length mismatch", () => {
|
|
24
|
+
const def = { staticSegments: ["blog"], paramNames: [] };
|
|
25
|
+
expect(matchPath(def, ["blog", "extra"])).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
it("extracts single param", () => {
|
|
28
|
+
const def = { staticSegments: ["blog", null], paramNames: ["slug"] };
|
|
29
|
+
expect(matchPath(def, ["blog", "hello-world"])).toEqual({ slug: "hello-world" });
|
|
30
|
+
});
|
|
31
|
+
it("extracts multiple params", () => {
|
|
32
|
+
const def = { staticSegments: [null, "posts", null], paramNames: ["user", "id"] };
|
|
33
|
+
expect(matchPath(def, ["alice", "posts", "42"])).toEqual({ user: "alice", id: "42" });
|
|
34
|
+
});
|
|
35
|
+
it("decodes URI params", () => {
|
|
36
|
+
const def = { staticSegments: ["blog", null], paramNames: ["slug"] };
|
|
37
|
+
expect(matchPath(def, ["blog", "hello%20world"])).toEqual({ slug: "hello world" });
|
|
38
|
+
});
|
|
39
|
+
it("matches root route", () => {
|
|
40
|
+
const def = { staticSegments: [], paramNames: [] };
|
|
41
|
+
expect(matchPath(def, [])).toEqual({});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { registerComponent, getComponentFactory, mountComponents } from "../registry";
|
|
3
|
+
describe("registerComponent / getComponentFactory", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
registerComponent("__test_cleanup__", () => { });
|
|
6
|
+
});
|
|
7
|
+
it("returns undefined for unregistered component", () => {
|
|
8
|
+
expect(getComponentFactory("NonExistent")).toBeUndefined();
|
|
9
|
+
});
|
|
10
|
+
it("returns factory after registration", () => {
|
|
11
|
+
const factory = (el) => ({ mounted: true });
|
|
12
|
+
registerComponent("TestComp", factory);
|
|
13
|
+
expect(getComponentFactory("TestComp")).toBe(factory);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe("mountComponents", () => {
|
|
17
|
+
it("mounts components found in root element", () => {
|
|
18
|
+
const root = document.createElement("div");
|
|
19
|
+
const el1 = document.createElement("div");
|
|
20
|
+
el1.setAttribute("data-component", "CompA");
|
|
21
|
+
const el2 = document.createElement("div");
|
|
22
|
+
el2.setAttribute("data-component", "CompB");
|
|
23
|
+
root.appendChild(el1);
|
|
24
|
+
root.appendChild(el2);
|
|
25
|
+
const results = [];
|
|
26
|
+
registerComponent("CompA", () => {
|
|
27
|
+
results.push("CompA");
|
|
28
|
+
return { name: "CompA" };
|
|
29
|
+
});
|
|
30
|
+
registerComponent("CompB", () => {
|
|
31
|
+
results.push("CompB");
|
|
32
|
+
return { name: "CompB" };
|
|
33
|
+
});
|
|
34
|
+
const ctx = { params: {}, navigate: () => { } };
|
|
35
|
+
const instances = mountComponents(ctx, root);
|
|
36
|
+
expect(results).toContain("CompA");
|
|
37
|
+
expect(results).toContain("CompB");
|
|
38
|
+
expect(instances).toHaveLength(2);
|
|
39
|
+
});
|
|
40
|
+
it("skips elements without data-component", () => {
|
|
41
|
+
const root = document.createElement("div");
|
|
42
|
+
const el = document.createElement("div");
|
|
43
|
+
root.appendChild(el);
|
|
44
|
+
const ctx = { params: {}, navigate: () => { } };
|
|
45
|
+
const instances = mountComponents(ctx, root);
|
|
46
|
+
expect(instances).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
it("skips unregistered component names", () => {
|
|
49
|
+
const root = document.createElement("div");
|
|
50
|
+
const el = document.createElement("div");
|
|
51
|
+
el.setAttribute("data-component", "Unknown");
|
|
52
|
+
root.appendChild(el);
|
|
53
|
+
const ctx = { params: {}, navigate: () => { } };
|
|
54
|
+
const instances = mountComponents(ctx, root);
|
|
55
|
+
expect(instances).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
it("does not include undefined returns in instances", () => {
|
|
58
|
+
const root = document.createElement("div");
|
|
59
|
+
const el = document.createElement("div");
|
|
60
|
+
el.setAttribute("data-component", "NoReturn");
|
|
61
|
+
root.appendChild(el);
|
|
62
|
+
registerComponent("NoReturn", () => undefined);
|
|
63
|
+
const ctx = { params: {}, navigate: () => { } };
|
|
64
|
+
const instances = mountComponents(ctx, root);
|
|
65
|
+
expect(instances).toHaveLength(0);
|
|
66
|
+
});
|
|
67
|
+
});
|
package/dist/context.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight page context passed to page and component behavior.
|
|
3
|
+
*
|
|
4
|
+
* TinyFX uses this to expose route params and navigation helpers without a
|
|
5
|
+
* dependency-injection container.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import type { TinyFxContext } from "@tinyfx/runtime";
|
|
9
|
+
*
|
|
10
|
+
* export function init(el: HTMLElement, ctx: TinyFxContext): void {
|
|
11
|
+
* console.log(ctx.params.slug);
|
|
12
|
+
* ctx.navigate("/");
|
|
13
|
+
* void el;
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
1
16
|
export interface TinyFxContext {
|
|
2
17
|
params: Record<string, string>;
|
|
3
18
|
navigate: (path: string) => void;
|
package/dist/context.js
CHANGED
package/dist/dom.d.ts
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binds an element's text content to a reactive getter.
|
|
3
|
+
*
|
|
4
|
+
* @param el - The DOM element whose text content should be updated
|
|
5
|
+
* @param get - A reactive getter that returns the next text value
|
|
6
|
+
* @returns Nothing
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const count = signal(0);
|
|
10
|
+
* bindText(span, () => `Count: ${count()}`);
|
|
11
|
+
*/
|
|
1
12
|
export declare function bindText(el: HTMLElement, get: () => any): void;
|
|
13
|
+
/**
|
|
14
|
+
* Binds an attribute to a reactive getter.
|
|
15
|
+
*
|
|
16
|
+
* @param el - The DOM element whose attribute should be updated
|
|
17
|
+
* @param attr - The attribute name to write
|
|
18
|
+
* @param get - A reactive getter that returns the attribute value
|
|
19
|
+
* @returns Nothing
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const isDark = signal(false);
|
|
23
|
+
* bindAttr(document.body, "data-theme", () => (isDark() ? "dark" : "light"));
|
|
24
|
+
*/
|
|
2
25
|
export declare function bindAttr(el: HTMLElement, attr: string, get: () => string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Toggles a CSS class from a reactive boolean getter.
|
|
28
|
+
*
|
|
29
|
+
* @param el - The DOM element whose class list should be updated
|
|
30
|
+
* @param cls - The class name to toggle
|
|
31
|
+
* @param get - A reactive getter that returns whether the class should exist
|
|
32
|
+
* @returns Nothing
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const open = signal(false);
|
|
36
|
+
* bindClass(panel, "is-open", () => open());
|
|
37
|
+
*/
|
|
3
38
|
export declare function bindClass(el: HTMLElement, cls: string, get: () => boolean): void;
|
package/dist/dom.js
CHANGED
|
@@ -1,16 +1,51 @@
|
|
|
1
1
|
// @tinyfx/runtime — DOM helpers
|
|
2
2
|
// Helpers for binding reactive state to the DOM.
|
|
3
3
|
import { effect } from "./signals";
|
|
4
|
+
/**
|
|
5
|
+
* Binds an element's text content to a reactive getter.
|
|
6
|
+
*
|
|
7
|
+
* @param el - The DOM element whose text content should be updated
|
|
8
|
+
* @param get - A reactive getter that returns the next text value
|
|
9
|
+
* @returns Nothing
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const count = signal(0);
|
|
13
|
+
* bindText(span, () => `Count: ${count()}`);
|
|
14
|
+
*/
|
|
4
15
|
export function bindText(el, get) {
|
|
5
16
|
effect(() => {
|
|
6
17
|
el.textContent = String(get());
|
|
7
18
|
});
|
|
8
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Binds an attribute to a reactive getter.
|
|
22
|
+
*
|
|
23
|
+
* @param el - The DOM element whose attribute should be updated
|
|
24
|
+
* @param attr - The attribute name to write
|
|
25
|
+
* @param get - A reactive getter that returns the attribute value
|
|
26
|
+
* @returns Nothing
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const isDark = signal(false);
|
|
30
|
+
* bindAttr(document.body, "data-theme", () => (isDark() ? "dark" : "light"));
|
|
31
|
+
*/
|
|
9
32
|
export function bindAttr(el, attr, get) {
|
|
10
33
|
effect(() => {
|
|
11
34
|
el.setAttribute(attr, get());
|
|
12
35
|
});
|
|
13
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Toggles a CSS class from a reactive boolean getter.
|
|
39
|
+
*
|
|
40
|
+
* @param el - The DOM element whose class list should be updated
|
|
41
|
+
* @param cls - The class name to toggle
|
|
42
|
+
* @param get - A reactive getter that returns whether the class should exist
|
|
43
|
+
* @returns Nothing
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const open = signal(false);
|
|
47
|
+
* bindClass(panel, "is-open", () => open());
|
|
48
|
+
*/
|
|
14
49
|
export function bindClass(el, cls, get) {
|
|
15
50
|
effect(() => {
|
|
16
51
|
el.classList.toggle(cls, get());
|
package/dist/each.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register a mounted component instance by root element.
|
|
3
|
+
*/
|
|
4
|
+
export declare function registerInstance(el: HTMLElement, instance: {
|
|
5
|
+
destroy?: () => void;
|
|
6
|
+
}): void;
|
|
7
|
+
/**
|
|
8
|
+
* Destroy a mounted component instance on a node and nested component nodes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function destroyNode(el: HTMLElement): void;
|
package/dist/each.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @tinyfx/runtime — each lifecycle helpers
|
|
2
|
+
const __componentInstances = new WeakMap();
|
|
3
|
+
/**
|
|
4
|
+
* Register a mounted component instance by root element.
|
|
5
|
+
*/
|
|
6
|
+
export function registerInstance(el, instance) {
|
|
7
|
+
__componentInstances.set(el, instance);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Destroy a mounted component instance on a node and nested component nodes.
|
|
11
|
+
*/
|
|
12
|
+
export function destroyNode(el) {
|
|
13
|
+
var _a;
|
|
14
|
+
const inst = __componentInstances.get(el);
|
|
15
|
+
(_a = inst === null || inst === void 0 ? void 0 : inst.destroy) === null || _a === void 0 ? void 0 : _a.call(inst);
|
|
16
|
+
__componentInstances.delete(el);
|
|
17
|
+
el.querySelectorAll("[data-component]").forEach((child) => {
|
|
18
|
+
var _a;
|
|
19
|
+
const childEl = child;
|
|
20
|
+
const childInst = __componentInstances.get(childEl);
|
|
21
|
+
(_a = childInst === null || childInst === void 0 ? void 0 : childInst.destroy) === null || _a === void 0 ? void 0 : _a.call(childInst);
|
|
22
|
+
__componentInstances.delete(childEl);
|
|
23
|
+
});
|
|
24
|
+
}
|
package/dist/http/data.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown for non-success HTTP responses.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* try {
|
|
6
|
+
* await http.get("/posts");
|
|
7
|
+
* } catch (error) {
|
|
8
|
+
* if (error instanceof HttpError) {
|
|
9
|
+
* console.error(error.status, error.url);
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
1
13
|
export declare class HttpError extends Error {
|
|
2
14
|
readonly status: number;
|
|
3
15
|
readonly statusText: string;
|
|
@@ -6,15 +18,51 @@ export declare class HttpError extends Error {
|
|
|
6
18
|
readonly response?: Response | undefined;
|
|
7
19
|
constructor(status: number, statusText: string, url: string, method: string, response?: Response | undefined);
|
|
8
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when a request times out.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* try {
|
|
26
|
+
* await http.get("/slow", { timeout: 100 });
|
|
27
|
+
* } catch (error) {
|
|
28
|
+
* if (error instanceof HttpTimeoutError) {
|
|
29
|
+
* console.error(error.timeout);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
9
33
|
export declare class HttpTimeoutError extends Error {
|
|
10
34
|
readonly url: string;
|
|
11
35
|
readonly timeout: number;
|
|
12
36
|
constructor(url: string, timeout: number);
|
|
13
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when a JSON response cannot be parsed.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* try {
|
|
43
|
+
* await http.get("/broken-json");
|
|
44
|
+
* } catch (error) {
|
|
45
|
+
* if (error instanceof HttpParseError) {
|
|
46
|
+
* console.error(error.url);
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
*/
|
|
14
50
|
export declare class HttpParseError extends Error {
|
|
15
51
|
readonly url: string;
|
|
16
52
|
constructor(message: string, url: string);
|
|
17
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Hook that can rewrite an outgoing request before `fetch` runs.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const addToken: RequestInterceptor = (url, options) => ({
|
|
59
|
+
* url,
|
|
60
|
+
* options: {
|
|
61
|
+
* ...options,
|
|
62
|
+
* headers: { ...options.headers, Authorization: "Bearer token" },
|
|
63
|
+
* },
|
|
64
|
+
* });
|
|
65
|
+
*/
|
|
18
66
|
export interface RequestInterceptor {
|
|
19
67
|
(url: string, options: RequestInit): Promise<{
|
|
20
68
|
url: string;
|
|
@@ -24,9 +72,28 @@ export interface RequestInterceptor {
|
|
|
24
72
|
options: RequestInit;
|
|
25
73
|
};
|
|
26
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Hook that can inspect or replace a response before TinyFX handles it.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* const logResponse: ResponseInterceptor = (response) => {
|
|
80
|
+
* console.log(response.status);
|
|
81
|
+
* return response;
|
|
82
|
+
* };
|
|
83
|
+
*/
|
|
27
84
|
export interface ResponseInterceptor {
|
|
28
85
|
(response: Response): Promise<Response> | Response;
|
|
29
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Default options for an HTTP client created by `createHttp()`.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* const config: HttpConfig = {
|
|
92
|
+
* baseUrl: "/api",
|
|
93
|
+
* timeout: 5000,
|
|
94
|
+
* retries: 1,
|
|
95
|
+
* };
|
|
96
|
+
*/
|
|
30
97
|
export interface HttpConfig {
|
|
31
98
|
baseUrl?: string;
|
|
32
99
|
headers?: Record<string, string>;
|
|
@@ -36,6 +103,15 @@ export interface HttpConfig {
|
|
|
36
103
|
requestInterceptors?: RequestInterceptor[];
|
|
37
104
|
responseInterceptors?: ResponseInterceptor[];
|
|
38
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Per-request overrides for an HTTP call.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* const options: RequestOptions = {
|
|
111
|
+
* params: { page: 2 },
|
|
112
|
+
* timeout: 1000,
|
|
113
|
+
* };
|
|
114
|
+
*/
|
|
39
115
|
export interface RequestOptions {
|
|
40
116
|
headers?: Record<string, string>;
|
|
41
117
|
timeout?: number;
|
package/dist/http/data.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown for non-success HTTP responses.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* try {
|
|
6
|
+
* await http.get("/posts");
|
|
7
|
+
* } catch (error) {
|
|
8
|
+
* if (error instanceof HttpError) {
|
|
9
|
+
* console.error(error.status, error.url);
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
1
13
|
export class HttpError extends Error {
|
|
2
14
|
constructor(status, statusText, url, method, response) {
|
|
3
15
|
super(`HTTP ${status} ${statusText}: ${method} ${url}`);
|
|
@@ -9,6 +21,18 @@ export class HttpError extends Error {
|
|
|
9
21
|
this.name = "HttpError";
|
|
10
22
|
}
|
|
11
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Error thrown when a request times out.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* try {
|
|
29
|
+
* await http.get("/slow", { timeout: 100 });
|
|
30
|
+
* } catch (error) {
|
|
31
|
+
* if (error instanceof HttpTimeoutError) {
|
|
32
|
+
* console.error(error.timeout);
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
12
36
|
export class HttpTimeoutError extends Error {
|
|
13
37
|
constructor(url, timeout) {
|
|
14
38
|
super(`Request timeout after ${timeout}ms: ${url}`);
|
|
@@ -17,6 +41,18 @@ export class HttpTimeoutError extends Error {
|
|
|
17
41
|
this.name = "HttpTimeoutError";
|
|
18
42
|
}
|
|
19
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Error thrown when a JSON response cannot be parsed.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* try {
|
|
49
|
+
* await http.get("/broken-json");
|
|
50
|
+
* } catch (error) {
|
|
51
|
+
* if (error instanceof HttpParseError) {
|
|
52
|
+
* console.error(error.url);
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
20
56
|
export class HttpParseError extends Error {
|
|
21
57
|
constructor(message, url) {
|
|
22
58
|
super(message);
|
package/dist/http/helper.d.ts
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a request URL by combining a base path with optional query params.
|
|
3
|
+
*
|
|
4
|
+
* @param url - The request path or URL fragment
|
|
5
|
+
* @param base - The configured base URL prefix
|
|
6
|
+
* @param params - Optional query parameters to append
|
|
7
|
+
* @returns The final request URL string
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* buildUrl("/posts", "/api", { page: 2, published: true });
|
|
11
|
+
*/
|
|
1
12
|
export declare function buildUrl(url: string, base: string, params?: Record<string, string | number | boolean>): string;
|
|
13
|
+
/**
|
|
14
|
+
* Waits for a number of milliseconds.
|
|
15
|
+
*
|
|
16
|
+
* @param ms - The delay duration in milliseconds
|
|
17
|
+
* @returns A promise that resolves after the delay completes
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* await sleep(250);
|
|
21
|
+
*/
|
|
2
22
|
export declare function sleep(ms: number): Promise<void>;
|
package/dist/http/helper.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a request URL by combining a base path with optional query params.
|
|
3
|
+
*
|
|
4
|
+
* @param url - The request path or URL fragment
|
|
5
|
+
* @param base - The configured base URL prefix
|
|
6
|
+
* @param params - Optional query parameters to append
|
|
7
|
+
* @returns The final request URL string
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* buildUrl("/posts", "/api", { page: 2, published: true });
|
|
11
|
+
*/
|
|
1
12
|
export function buildUrl(url, base, params) {
|
|
2
13
|
const fullUrl = base + url;
|
|
3
14
|
if (!params || Object.keys(params).length === 0)
|
|
@@ -8,6 +19,15 @@ export function buildUrl(url, base, params) {
|
|
|
8
19
|
});
|
|
9
20
|
return urlObj.toString().replace(urlObj.origin, '');
|
|
10
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Waits for a number of milliseconds.
|
|
24
|
+
*
|
|
25
|
+
* @param ms - The delay duration in milliseconds
|
|
26
|
+
* @returns A promise that resolves after the delay completes
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* await sleep(250);
|
|
30
|
+
*/
|
|
11
31
|
export async function sleep(ms) {
|
|
12
32
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
13
33
|
}
|
package/dist/http/http.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { HttpConfig, RequestOptions } from "./data";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a typed HTTP client around `fetch`.
|
|
4
|
+
*
|
|
5
|
+
* The client exposes `get`, `post`, `put`, `patch`, `del`, and `delete`
|
|
6
|
+
* helpers and applies the default configuration to every request.
|
|
7
|
+
*
|
|
8
|
+
* @param config - Default HTTP configuration such as base URL, headers, and retry policy
|
|
9
|
+
* @returns An object containing typed request methods
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const http = createHttp({ baseUrl: "/api", timeout: 5000 });
|
|
13
|
+
* const posts = await http.get<{ id: number; title: string }[]>("/posts");
|
|
14
|
+
*/
|
|
2
15
|
export declare function createHttp(config?: HttpConfig): {
|
|
3
16
|
get: <T>(url: string, options?: RequestOptions) => Promise<T>;
|
|
4
17
|
post: <T>(url: string, body?: unknown, options?: RequestOptions) => Promise<T>;
|
package/dist/http/http.js
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
// Thin, typed wrapper over fetch.
|
|
3
3
|
import { HttpError, HttpParseError, HttpTimeoutError } from "./data";
|
|
4
4
|
import { buildUrl, sleep } from "./helper";
|
|
5
|
+
/**
|
|
6
|
+
* Creates a typed HTTP client around `fetch`.
|
|
7
|
+
*
|
|
8
|
+
* The client exposes `get`, `post`, `put`, `patch`, `del`, and `delete`
|
|
9
|
+
* helpers and applies the default configuration to every request.
|
|
10
|
+
*
|
|
11
|
+
* @param config - Default HTTP configuration such as base URL, headers, and retry policy
|
|
12
|
+
* @returns An object containing typed request methods
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const http = createHttp({ baseUrl: "/api", timeout: 5000 });
|
|
16
|
+
* const posts = await http.get<{ id: number; title: string }[]>("/posts");
|
|
17
|
+
*/
|
|
5
18
|
export function createHttp(config = {}) {
|
|
6
19
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
7
20
|
const base = (_a = config.baseUrl) !== null && _a !== void 0 ? _a : "";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { signal, effect } from "./signals";
|
|
2
|
+
export { registerInstance, destroyNode } from "./each";
|
|
2
3
|
export { bindText, bindAttr, bindClass } from "./dom";
|
|
3
4
|
export { createHttp } from "./http";
|
|
5
|
+
export { registerComponent, getComponentFactory, mountComponents } from "./registry";
|
|
6
|
+
export { registerPage, getPageModule, runPageInit } from "./page-registry";
|
|
7
|
+
export { markMounted, isMounted } from "./mount-state";
|
|
8
|
+
export { matchPath, splitPath } from "./router/path-matcher";
|
|
9
|
+
export type { RouteDef } from "./router/path-matcher";
|
|
10
|
+
export { init } from "./init";
|
|
11
|
+
export type { InitConfig } from "./init";
|
|
4
12
|
export { initRouter, getParam, navigate, goBack } from "./router/index";
|
|
5
13
|
export { onMount, onDestroy } from "./router/lifecycle";
|
|
6
14
|
export type { TinyFxContext } from "./context";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
// @tinyfx/runtime — barrel export
|
|
2
2
|
export { signal, effect } from "./signals";
|
|
3
|
+
export { registerInstance, destroyNode } from "./each";
|
|
3
4
|
export { bindText, bindAttr, bindClass } from "./dom";
|
|
4
5
|
export { createHttp } from "./http";
|
|
6
|
+
// New decoupled APIs
|
|
7
|
+
export { registerComponent, getComponentFactory, mountComponents } from "./registry";
|
|
8
|
+
export { registerPage, getPageModule, runPageInit } from "./page-registry";
|
|
9
|
+
export { markMounted, isMounted } from "./mount-state";
|
|
10
|
+
export { matchPath, splitPath } from "./router/path-matcher";
|
|
11
|
+
export { init } from "./init";
|
|
12
|
+
// Keep existing router exports
|
|
5
13
|
export { initRouter, getParam, navigate, goBack } from "./router/index";
|
|
6
14
|
export { onMount, onDestroy } from "./router/lifecycle";
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TinyFxContext } from "./context";
|
|
2
|
+
import type { RouteDef } from "./router/path-matcher";
|
|
3
|
+
export interface InitConfig {
|
|
4
|
+
routes: Record<string, RouteDef>;
|
|
5
|
+
setupDirectives?: (ctx: TinyFxContext) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface InitResult {
|
|
8
|
+
matchedPath: string;
|
|
9
|
+
params: Record<string, string>;
|
|
10
|
+
instances: any[];
|
|
11
|
+
}
|
|
12
|
+
export declare function init(config: InitConfig): InitResult | null;
|