@tinyfx/runtime 0.1.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/README.md +99 -0
- package/dist/dom.d.ts +3 -0
- package/dist/dom.js +18 -0
- package/dist/http.d.ts +10 -0
- package/dist/http.js +38 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/signals.d.ts +11 -0
- package/dist/signals.js +40 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @tinyfx/runtime
|
|
2
|
+
|
|
3
|
+
> The minimal, browser-side runtime for tinyfx.
|
|
4
|
+
|
|
5
|
+
A lightweight collection of helpers for reactivity, DOM manipulation, and typed HTTP communication. Designed to be used standalone or as part of the `tinyfx` build pipeline.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Signals** — Explicit reactivity without Proxies.
|
|
10
|
+
- **DOM Helpers** — One-way bindings for text, attributes, and classes.
|
|
11
|
+
- **Typed HTTP** — Minimal wrapper over `fetch` with response typing.
|
|
12
|
+
- **Tiny** — No dependencies, tree-shakeable, and extremely fast.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install @tinyfx/runtime
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Signals (Reactivity)
|
|
23
|
+
|
|
24
|
+
Signals provide a simple way to manage state that triggers effects when changed.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { signal, effect } from "@tinyfx/runtime";
|
|
28
|
+
|
|
29
|
+
const count = signal(0);
|
|
30
|
+
|
|
31
|
+
// Runs immediately, and re-runs whenever count() changes
|
|
32
|
+
effect(() => {
|
|
33
|
+
console.log("The count is:", count());
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
count.set(1); // logs: "The count is: 1"
|
|
37
|
+
count.set(count() + 1); // logs: "The count is: 2"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### DOM Bindings
|
|
41
|
+
|
|
42
|
+
Keep your JS decoupled from the DOM while maintaining reactive updates.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { signal, bindText, bindClass, bindAttr } from "@tinyfx/runtime";
|
|
46
|
+
|
|
47
|
+
const count = signal(0);
|
|
48
|
+
const isActive = signal(false);
|
|
49
|
+
|
|
50
|
+
const el = document.getElementById("counter");
|
|
51
|
+
const btn = document.querySelector("button");
|
|
52
|
+
|
|
53
|
+
bindText(el, () => `Count: ${count()}`);
|
|
54
|
+
bindClass(btn, "active", isActive);
|
|
55
|
+
bindAttr(btn, "disabled", () => count() > 10);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Typed HTTP Client
|
|
59
|
+
|
|
60
|
+
A thin wrapper over `fetch` that returns typed responses.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { createHttp } from "@tinyfx/runtime";
|
|
64
|
+
|
|
65
|
+
interface User {
|
|
66
|
+
id: number;
|
|
67
|
+
name: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const api = createHttp({ baseUrl: "/api" });
|
|
71
|
+
|
|
72
|
+
// Fully typed response
|
|
73
|
+
const users = await api.get<User[]>("/users");
|
|
74
|
+
|
|
75
|
+
// POST with data
|
|
76
|
+
await api.post("/users", { name: "Alice" });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API Overview
|
|
80
|
+
|
|
81
|
+
### Signals
|
|
82
|
+
- `signal<T>(value: T): Signal<T>`
|
|
83
|
+
- `effect(fn: () => void): void`
|
|
84
|
+
|
|
85
|
+
### DOM
|
|
86
|
+
- `bindText(el: HTMLElement, source: Signal<any> | (() => any)): void`
|
|
87
|
+
- `bindAttr(el: HTMLElement, attr: string, source: Signal<any> | (() => any)): void`
|
|
88
|
+
- `bindClass(el: HTMLElement, className: string, source: Signal<boolean> | (() => boolean)): void`
|
|
89
|
+
|
|
90
|
+
### HTTP
|
|
91
|
+
- `createHttp(config: HttpConfig): HttpClient`
|
|
92
|
+
- `client.get<T>(url: string): Promise<T>`
|
|
93
|
+
- `client.post<T>(url: string, data?: any): Promise<T>`
|
|
94
|
+
- `client.put<T>(url: string, data?: any): Promise<T>`
|
|
95
|
+
- `client.del<T>(url: string): Promise<T>`
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/dist/dom.d.ts
ADDED
package/dist/dom.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @tinyfx/runtime — DOM helpers
|
|
2
|
+
// Helpers for binding reactive state to the DOM.
|
|
3
|
+
import { effect } from "./signals";
|
|
4
|
+
export function bindText(el, get) {
|
|
5
|
+
effect(() => {
|
|
6
|
+
el.textContent = String(get());
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function bindAttr(el, attr, get) {
|
|
10
|
+
effect(() => {
|
|
11
|
+
el.setAttribute(attr, get());
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function bindClass(el, cls, get) {
|
|
15
|
+
effect(() => {
|
|
16
|
+
el.classList.toggle(cls, get());
|
|
17
|
+
});
|
|
18
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface HttpConfig {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
headers?: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export declare function createHttp(config?: HttpConfig): {
|
|
6
|
+
get: <T>(url: string) => Promise<T>;
|
|
7
|
+
post: <T>(url: string, body?: unknown) => Promise<T>;
|
|
8
|
+
put: <T>(url: string, body?: unknown) => Promise<T>;
|
|
9
|
+
del: <T>(url: string) => Promise<T>;
|
|
10
|
+
};
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @tinyfx/runtime — HTTP client
|
|
2
|
+
// Thin, typed wrapper over fetch.
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
export function createHttp(config = {}) {
|
|
13
|
+
var _a, _b;
|
|
14
|
+
const base = (_a = config.baseUrl) !== null && _a !== void 0 ? _a : "";
|
|
15
|
+
const defaultHeaders = (_b = config.headers) !== null && _b !== void 0 ? _b : {};
|
|
16
|
+
function request(method, url, body) {
|
|
17
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
const headers = Object.assign({}, defaultHeaders);
|
|
19
|
+
if (body !== undefined) {
|
|
20
|
+
headers["Content-Type"] = "application/json";
|
|
21
|
+
}
|
|
22
|
+
const res = yield fetch(base + url, {
|
|
23
|
+
method,
|
|
24
|
+
headers,
|
|
25
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
throw new Error(`${method} ${url}: ${res.status} ${res.statusText}`);
|
|
29
|
+
return res.json();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
get: (url) => request("GET", url),
|
|
34
|
+
post: (url, body) => request("POST", url, body),
|
|
35
|
+
put: (url, body) => request("PUT", url, body),
|
|
36
|
+
del: (url) => request("DELETE", url),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class Signal<T> {
|
|
2
|
+
private value;
|
|
3
|
+
private subs;
|
|
4
|
+
constructor(v: T);
|
|
5
|
+
get(): T;
|
|
6
|
+
set(v: T): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function signal<T>(v: T): (() => T) & {
|
|
9
|
+
set(v: T): void;
|
|
10
|
+
};
|
|
11
|
+
export declare function effect(fn: () => void): void;
|
package/dist/signals.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @tinyfx/runtime — signals
|
|
2
|
+
// Minimal, explicit reactivity — no proxies, no magic.
|
|
3
|
+
const effectStack = [];
|
|
4
|
+
export class Signal {
|
|
5
|
+
constructor(v) {
|
|
6
|
+
this.subs = new Set();
|
|
7
|
+
this.value = v;
|
|
8
|
+
}
|
|
9
|
+
get() {
|
|
10
|
+
const running = effectStack[effectStack.length - 1];
|
|
11
|
+
if (running) {
|
|
12
|
+
this.subs.add(running);
|
|
13
|
+
}
|
|
14
|
+
return this.value;
|
|
15
|
+
}
|
|
16
|
+
set(v) {
|
|
17
|
+
if (Object.is(v, this.value))
|
|
18
|
+
return;
|
|
19
|
+
this.value = v;
|
|
20
|
+
this.subs.forEach((fn) => fn());
|
|
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);
|
|
27
|
+
return fn;
|
|
28
|
+
}
|
|
29
|
+
export function effect(fn) {
|
|
30
|
+
const run = () => {
|
|
31
|
+
effectStack.push(run);
|
|
32
|
+
try {
|
|
33
|
+
fn();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
effectStack.pop();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
run();
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tinyfx/runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Minimal frontend runtime — signals, DOM helpers, typed HTTP, DTO mapping",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./signals": "./dist/signals.js",
|
|
11
|
+
"./dom": "./dist/dom.js",
|
|
12
|
+
"./http": "./dist/http.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.9.3"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT"
|
|
25
|
+
}
|