@nmvuong92/fluxe 0.5.0 → 0.6.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 +26 -21
- package/lib/core/broker.js +2 -2
- package/lib/core/cli.js +1 -1
- package/lib/core/client.d.ts +0 -2
- package/lib/core/client.js +1 -6
- package/lib/core/codegen.d.ts +0 -2
- package/lib/core/codegen.js +1 -20
- package/lib/core/config.d.ts +3 -3
- package/lib/core/config.js +1 -1
- package/lib/core/panel.js +3 -5
- package/lib/core/resolver.d.ts +1 -7
- package/lib/core/resolver.js +4 -10
- package/lib/core/wiring.js +8 -6
- package/lib/index.d.ts +1 -1
- package/lib/index.js +3 -3
- package/lib/react/DebugBar.js +3 -4
- package/lib/server_factory.js +3 -24
- package/package.json +2 -2
- package/lib/backends/http.d.ts +0 -2
- package/lib/backends/http.js +0 -32
- package/lib/hot/search.d.ts +0 -9
- package/lib/hot/search.js +0 -16
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@nmvuong92/fluxe)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
Khung fullstack **
|
|
8
|
-
*logic chỉ phụ thuộc HỢP ĐỒNG; mọi quyết định
|
|
9
|
-
|
|
7
|
+
Khung fullstack tối giản — **một runtime TypeScript** (chạy trên `node:http` zero-dep) — dựa
|
|
8
|
+
trên triết lý **RCA — Resolved Cell Architecture**: *logic chỉ phụ thuộc HỢP ĐỒNG; mọi quyết định
|
|
9
|
+
vận hành (render, backend data) là **kết quả được GIẢI** bởi engine, không viết tay.*
|
|
10
10
|
|
|
11
|
-
> Đổi backend
|
|
12
|
-
> app per-cell — tất cả chỉ sửa `app/profiles.ts`, **cell & frontend không đổi một dòng**.
|
|
11
|
+
> Đổi backend data `memory ↔ sqlite ↔ postgres`, đổi render `static ↔ island`, gộp nhiều backend
|
|
12
|
+
> trong một app per-cell — tất cả chỉ sửa `app/profiles.ts`, **cell & frontend không đổi một dòng**.
|
|
13
13
|
|
|
14
14
|
## Cài & dùng (npm)
|
|
15
15
|
|
|
@@ -34,7 +34,7 @@ import { rpc } from "@nmvuong92/fluxe/client";
|
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
npm install
|
|
37
|
-
npm run fx -- build # resolve + prerender + bundle (1 schema → TS
|
|
37
|
+
npm run fx -- build # resolve + prerender + bundle (1 schema → types TS qua fx gen)
|
|
38
38
|
npm run fx -- dev # http://localhost:5180
|
|
39
39
|
npm run test:all # typecheck + 107 unit + 28 integration — TẤT CẢ XANH
|
|
40
40
|
```
|
|
@@ -47,10 +47,9 @@ npm run test:all # typecheck + 107 unit + 28 integration — TẤT C
|
|
|
47
47
|
app/ ← DEV sở hữu (sửa thoải mái) — Contract Plane
|
|
48
48
|
cells/ trang/feature (route + loader + view + action/head/layout/guard)
|
|
49
49
|
layouts/ layout dùng chung (nested)
|
|
50
|
-
profiles.ts CHỌN backend per môi trường + per-cell (memory/
|
|
51
|
-
contract.ts schema → codegen TS
|
|
50
|
+
profiles.ts CHỌN backend data per môi trường + per-cell (memory/sqlite/postgres)
|
|
51
|
+
contract.ts schema → codegen types TS
|
|
52
52
|
env.ts env có kiểu, validate fail-fast lúc boot
|
|
53
|
-
native/ service Go/Rust CỦA DEV (backend/host/hot/actor)
|
|
54
53
|
|
|
55
54
|
src/ ← ENGINE (không đụng) — Resolution Plane
|
|
56
55
|
core/ resolver · router · errors · auth · validate · codegen · layouts ·
|
|
@@ -63,28 +62,34 @@ folder. Engine không bao giờ import ngược vào `app/`.
|
|
|
63
62
|
|
|
64
63
|
## Tính năng (tất cả TDD + chạy thật)
|
|
65
64
|
|
|
66
|
-
- **Render** — static (0 JS) · island hydrate · SPA nav (Inertia) · static-prerender
|
|
65
|
+
- **Render** — static (0 JS) · island hydrate · SPA nav (Inertia) · static-prerender · API mode `?json=1`
|
|
67
66
|
- **Routing** — động `[param]` → `ctx.input` · **nested layouts** · SEO (head/canonical/OG/JSON-LD per cell, `/sitemap.xml`, `/robots.txt`)
|
|
68
67
|
- **Bảo mật (đầy đủ)** — input validation (Zod) · auth password **scrypt** · **RBAC** · **CSRF** double-submit · **rate-limit** token-bucket · error handling không-leak + structured
|
|
69
|
-
- **Data** — backend
|
|
68
|
+
- **Data** — backend TS per-cell (`memory`/`sqlite`/`postgres`) · DB **SQLite thật** (`node:sqlite`) + **Postgres** (inject client `pg`) · **codegen contract** 1 schema → types TS
|
|
70
69
|
- **Mutations DX** — `RpcError` có cấu trúc · `mutate()` optimistic + rollback · lỗi validation field-level
|
|
71
|
-
- **Realtime (Trục 4g)** — **SSE channel** + pub/sub broker · live-update on action · **presence** (multi-tab)
|
|
70
|
+
- **Realtime (Trục 4g)** — **SSE channel** + pub/sub broker · live-update on action · **presence** (multi-tab)
|
|
72
71
|
- **Async** — **job queue bền** (SQLite, retry → dead-letter)
|
|
73
72
|
- **Observability** — request log + dashboard `/_fluxe` (RCA Resolution + Recent requests)
|
|
74
73
|
- **Config** — env có kiểu, fail-fast lúc boot
|
|
75
74
|
- **DX** — `fx` CLI · mock `Backend` test cực dễ · typecheck gate
|
|
76
75
|
|
|
77
|
-
##
|
|
76
|
+
## Backend data (driver TS in-process)
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|------|----------|------|
|
|
81
|
-
| **Backend** (Todo CRUD) | Go · Rust | `./run-native.sh` |
|
|
82
|
-
| **Host/edge** (proxy + static) | Go | `./run-host.sh` |
|
|
83
|
-
| **Hot compute** (search) | Rust | `./run-hot.sh` |
|
|
84
|
-
| **Actor/realtime** (room+supervisor) | Go | `./run-actor.sh` |
|
|
78
|
+
Backend chỉ là **driver data TypeScript in-process** — chọn qua `app/profiles.ts`, cell không đổi:
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
| Driver | Ghi chú |
|
|
81
|
+
|--------|---------|
|
|
82
|
+
| `memory` | in-process, mặc định dev |
|
|
83
|
+
| `sqlite` | `node:sqlite` built-in, 0 dep, persist ra file (engine tự dựng) |
|
|
84
|
+
| `postgres` | production — **bạn tự inject client `pg`** (`npm i pg` + `DATABASE_URL`) |
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
export const profiles = {
|
|
88
|
+
dev: { name: "dev", backend: "memory" },
|
|
89
|
+
sqlite: { name: "sqlite", backend: "sqlite" },
|
|
90
|
+
mixed: { name: "mixed", backend: "memory", cellBackends: { todos: "sqlite" } },
|
|
91
|
+
};
|
|
92
|
+
```
|
|
88
93
|
|
|
89
94
|
## Triết lý
|
|
90
95
|
|
package/lib/core/broker.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Copyright (c) 2026 nmvuong92
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/* Broker pub/sub in-memory — nền của realtime channel (Trục 4g), bản 1-node.
|
|
4
|
-
* Bản distributed: thay bằng NATS/Redis fan-out (cùng interface).
|
|
5
|
-
*
|
|
4
|
+
* Bản distributed: thay bằng NATS/Redis fan-out (cùng interface). Đây là lõi
|
|
5
|
+
* đơn giản nhất, dependency-free. */
|
|
6
6
|
export function createBroker() {
|
|
7
7
|
const subs = new Map();
|
|
8
8
|
return {
|
package/lib/core/cli.js
CHANGED
package/lib/core/client.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ export declare class RpcError extends Error {
|
|
|
7
7
|
export declare function parseRpcError(status: number, body: string): RpcError;
|
|
8
8
|
export declare const setChaos: (v: string) => void;
|
|
9
9
|
export declare const getChaos: () => string;
|
|
10
|
-
export declare const setDevBackend: (v: string) => void;
|
|
11
|
-
export declare const getDevBackend: () => string;
|
|
12
10
|
export interface RpcMeta {
|
|
13
11
|
resolution?: string;
|
|
14
12
|
serverMs?: number;
|
package/lib/core/client.js
CHANGED
|
@@ -36,13 +36,10 @@ function cookie(name) {
|
|
|
36
36
|
const m = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
|
|
37
37
|
return m ? decodeURIComponent(m[1]) : "";
|
|
38
38
|
}
|
|
39
|
-
/* DevTools config (chỉ ảnh hưởng dev): chaos injection
|
|
39
|
+
/* DevTools config (chỉ ảnh hưởng dev): chaos injection. DebugBar set. */
|
|
40
40
|
let _chaos = ""; // vd "delay=600;fail=0.3"
|
|
41
|
-
let _devBackend = ""; // vd "go" | "rust" | "memory"
|
|
42
41
|
export const setChaos = (v) => { _chaos = v; };
|
|
43
42
|
export const getChaos = () => _chaos;
|
|
44
|
-
export const setDevBackend = (v) => { _devBackend = v; };
|
|
45
|
-
export const getDevBackend = () => _devBackend;
|
|
46
43
|
let _lastMeta = {};
|
|
47
44
|
export const lastRpcMeta = () => _lastMeta;
|
|
48
45
|
export async function rpc(cell, action, input) {
|
|
@@ -50,8 +47,6 @@ export async function rpc(cell, action, input) {
|
|
|
50
47
|
const headers = { "content-type": "application/json", "x-csrf-token": cookie("csrf") };
|
|
51
48
|
if (_chaos)
|
|
52
49
|
headers["x-fluxe-chaos"] = _chaos; // #1 chaos
|
|
53
|
-
if (_devBackend)
|
|
54
|
-
headers["x-fluxe-backend"] = _devBackend; // #5 live swap
|
|
55
50
|
let res;
|
|
56
51
|
try {
|
|
57
52
|
res = await fetch(`/__action/${cell}/${action}`, { method: "POST", headers, body: JSON.stringify(input) });
|
package/lib/core/codegen.d.ts
CHANGED
package/lib/core/codegen.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2026 nmvuong92
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/* Codegen contract
|
|
4
|
-
* type-safe xuyên ngôn ngữ). Thuần (string-in/string-out), dễ test. */
|
|
5
|
-
const pascal = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
3
|
+
/* Codegen contract — MỘT schema → types TS. Thuần (string-in/string-out), dễ test. */
|
|
6
4
|
const TS = { string: "string", bool: "boolean", int: "number" };
|
|
7
|
-
const GO = { string: "string", bool: "bool", int: "int" };
|
|
8
|
-
const RS = { string: "String", bool: "bool", int: "i64" };
|
|
9
5
|
export function genTS(s) {
|
|
10
6
|
return Object.entries(s.types)
|
|
11
7
|
.map(([name, fields]) => `export interface ${name} {\n` +
|
|
@@ -13,18 +9,3 @@ export function genTS(s) {
|
|
|
13
9
|
`\n}`)
|
|
14
10
|
.join("\n\n") + "\n";
|
|
15
11
|
}
|
|
16
|
-
export function genGo(s, pkg = "contract") {
|
|
17
|
-
return `package ${pkg}\n\n` +
|
|
18
|
-
Object.entries(s.types)
|
|
19
|
-
.map(([name, fields]) => `type ${name} struct {\n` +
|
|
20
|
-
Object.entries(fields).map(([f, t]) => `\t${pascal(f)} ${GO[t]} \`json:"${f}"\``).join("\n") +
|
|
21
|
-
`\n}`)
|
|
22
|
-
.join("\n\n") + "\n";
|
|
23
|
-
}
|
|
24
|
-
export function genRust(s) {
|
|
25
|
-
return Object.entries(s.types)
|
|
26
|
-
.map(([name, fields]) => `#[derive(Clone, Debug)]\npub struct ${name} {\n` +
|
|
27
|
-
Object.entries(fields).map(([f, t]) => ` pub ${f}: ${RS[t]},`).join("\n") +
|
|
28
|
-
`\n}`)
|
|
29
|
-
.join("\n\n") + "\n";
|
|
30
|
-
}
|
package/lib/core/config.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ declare const Schema: z.ZodObject<{
|
|
|
3
3
|
env: z.ZodEnum<["development", "production", "test"]>;
|
|
4
4
|
secret: z.ZodString;
|
|
5
5
|
port: z.ZodNumber;
|
|
6
|
-
defaultBackend: z.ZodEnum<["memory", "
|
|
6
|
+
defaultBackend: z.ZodEnum<["memory", "sqlite", "postgres"]>;
|
|
7
7
|
rateLimit: z.ZodObject<{
|
|
8
8
|
capacity: z.ZodNumber;
|
|
9
9
|
refillPerSec: z.ZodNumber;
|
|
@@ -42,7 +42,7 @@ declare const Schema: z.ZodObject<{
|
|
|
42
42
|
env: "development" | "production" | "test";
|
|
43
43
|
secret: string;
|
|
44
44
|
port: number;
|
|
45
|
-
defaultBackend: "memory" | "
|
|
45
|
+
defaultBackend: "memory" | "sqlite" | "postgres";
|
|
46
46
|
rateLimit: {
|
|
47
47
|
capacity: number;
|
|
48
48
|
refillPerSec: number;
|
|
@@ -61,7 +61,7 @@ declare const Schema: z.ZodObject<{
|
|
|
61
61
|
env: "development" | "production" | "test";
|
|
62
62
|
secret: string;
|
|
63
63
|
port: number;
|
|
64
|
-
defaultBackend: "memory" | "
|
|
64
|
+
defaultBackend: "memory" | "sqlite" | "postgres";
|
|
65
65
|
rateLimit: {
|
|
66
66
|
capacity: number;
|
|
67
67
|
refillPerSec: number;
|
package/lib/core/config.js
CHANGED
|
@@ -9,7 +9,7 @@ const Schema = z.object({
|
|
|
9
9
|
env: z.enum(["development", "production", "test"]),
|
|
10
10
|
secret: z.string().min(8),
|
|
11
11
|
port: z.coerce.number().int().positive(),
|
|
12
|
-
defaultBackend: z.enum(["memory", "
|
|
12
|
+
defaultBackend: z.enum(["memory", "sqlite", "postgres"]),
|
|
13
13
|
rateLimit: z.object({
|
|
14
14
|
capacity: z.coerce.number().int().positive(),
|
|
15
15
|
refillPerSec: z.coerce.number().positive(),
|
package/lib/core/panel.js
CHANGED
|
@@ -9,8 +9,6 @@ export function renderResolutionPanel(m, requests = []) {
|
|
|
9
9
|
<td>${c.render.mode}</td>
|
|
10
10
|
<td>${c.render.shipClientJs ? "✓ JS" : "0 JS"}</td>
|
|
11
11
|
<td><span class="badge ${c.backend.language}">${c.backend.language}</span></td>
|
|
12
|
-
<td>${c.backend.transport}</td>
|
|
13
|
-
<td>${c.backend.endpoint ?? "—"}</td>
|
|
14
12
|
</tr>`).join("");
|
|
15
13
|
return `<!doctype html><html lang="vi"><head><meta charset="utf-8"><title>fluxe — RCA Resolution</title>
|
|
16
14
|
<style>
|
|
@@ -22,13 +20,13 @@ export function renderResolutionPanel(m, requests = []) {
|
|
|
22
20
|
th{color:#8a8aa0;font-weight:600;font-size:.78rem;text-transform:uppercase;letter-spacing:.04em}
|
|
23
21
|
code{background:#1a1a2e;padding:.1rem .35rem;border-radius:4px;color:#a0a0ff}
|
|
24
22
|
.badge{display:inline-block;padding:.1rem .55rem;border-radius:99px;font-size:.78rem}
|
|
25
|
-
.
|
|
23
|
+
.memory{background:#7c7cff33;color:#a0a0ff}.sqlite{background:#00add833;color:#5fd3f0}.postgres{background:#dea58433;color:#e8b48f}
|
|
26
24
|
</style></head>
|
|
27
25
|
<body>
|
|
28
26
|
<h1>RCA Resolution</h1>
|
|
29
|
-
<div class="sub">profile <b>${m.profile}</b> · default backend <b>${m.backend.language}</b> (
|
|
27
|
+
<div class="sub">profile <b>${m.profile}</b> · default backend <b>${m.backend.language}</b> (in-process)</div>
|
|
30
28
|
<table>
|
|
31
|
-
<thead><tr><th>cell</th><th>route</th><th>render</th><th>JS</th><th>backend</th
|
|
29
|
+
<thead><tr><th>cell</th><th>route</th><th>render</th><th>JS</th><th>backend</th></tr></thead>
|
|
32
30
|
<tbody>${rows}
|
|
33
31
|
</tbody>
|
|
34
32
|
</table>
|
package/lib/core/resolver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type RenderMode = "static" | "island";
|
|
2
|
-
export type BackendKind = "memory" | "
|
|
2
|
+
export type BackendKind = "memory" | "sqlite" | "postgres";
|
|
3
3
|
export interface CellDecl {
|
|
4
4
|
id: string;
|
|
5
5
|
route: string;
|
|
@@ -8,16 +8,10 @@ export interface CellDecl {
|
|
|
8
8
|
export interface ResolutionProfile {
|
|
9
9
|
name: string;
|
|
10
10
|
backend: BackendKind;
|
|
11
|
-
endpoints?: {
|
|
12
|
-
go?: string;
|
|
13
|
-
rust?: string;
|
|
14
|
-
};
|
|
15
11
|
cellBackends?: Record<string, BackendKind>;
|
|
16
12
|
}
|
|
17
13
|
export interface BackendResolution {
|
|
18
14
|
language: BackendKind;
|
|
19
|
-
transport: "in-process" | "http";
|
|
20
|
-
endpoint?: string;
|
|
21
15
|
}
|
|
22
16
|
export interface CellResolution {
|
|
23
17
|
id: string;
|
package/lib/core/resolver.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
const ALLOWED = ["memory", "
|
|
2
|
-
// Giải một BackendKind → BackendResolution
|
|
3
|
-
// cho default app-level lẫn override per-cell.
|
|
1
|
+
const ALLOWED = ["memory", "sqlite", "postgres"];
|
|
2
|
+
// Giải một BackendKind → BackendResolution. Mọi driver TS đều in-process (0 roundtrip).
|
|
3
|
+
// Dùng chung cho default app-level lẫn override per-cell.
|
|
4
4
|
function resolveBackend(kind, profile) {
|
|
5
5
|
if (!ALLOWED.includes(kind)) {
|
|
6
6
|
throw new Error(`profile "${profile.name}": backend không hợp lệ: ${kind}`);
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
return { language: "memory", transport: "in-process" };
|
|
10
|
-
const endpoint = profile.endpoints?.[kind];
|
|
11
|
-
if (!endpoint) {
|
|
12
|
-
throw new Error(`profile "${profile.name}": backend "${kind}" cần endpoints.${kind}`);
|
|
13
|
-
}
|
|
14
|
-
return { language: kind, transport: "http", endpoint };
|
|
8
|
+
return { language: kind };
|
|
15
9
|
}
|
|
16
10
|
export function resolve(cells, profile) {
|
|
17
11
|
const ids = new Set(cells.map((c) => c.id));
|
package/lib/core/wiring.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { createMemoryBackend } from "../backends/memory.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createSqliteBackend } from "../backends/sqlite.js";
|
|
3
|
+
// Driver TS in-process, engine dựng được zero-dep. (postgres cần client `pg` → user tự inject,
|
|
4
|
+
// không phải kind auto-resolve.)
|
|
3
5
|
function buildBackend(b) {
|
|
4
6
|
if (b.language === "memory")
|
|
5
7
|
return createMemoryBackend();
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
if (b.language === "sqlite")
|
|
9
|
+
return createSqliteBackend(process.env.FLUXE_SQLITE_PATH ?? ":memory:");
|
|
10
|
+
throw new Error(`manifest backend "${b.language}" không dựng được tự động (chỉ memory | sqlite)`);
|
|
9
11
|
}
|
|
10
12
|
// Backend app-level (default) — giữ cho code cũ/đơn giản.
|
|
11
13
|
export function backendFromManifest(m) {
|
|
12
14
|
return buildBackend(m.backend);
|
|
13
15
|
}
|
|
14
|
-
// Dựng backend per-cell, DEDUP theo
|
|
16
|
+
// Dựng backend per-cell, DEDUP theo `language` → cells cùng resolution
|
|
15
17
|
// chia sẻ MỘT instance (vd memory dùng chung một store).
|
|
16
18
|
export function backendsFromManifest(m) {
|
|
17
19
|
const cache = new Map();
|
|
18
20
|
const make = (b) => {
|
|
19
|
-
const key =
|
|
21
|
+
const key = b.language;
|
|
20
22
|
let inst = cache.get(key);
|
|
21
23
|
if (!inst) {
|
|
22
24
|
inst = buildBackend(b);
|
package/lib/index.d.ts
CHANGED
|
@@ -22,6 +22,6 @@ export { createMemoryStorage } from "./storage/memory.ts";
|
|
|
22
22
|
export { createLocalStorage } from "./storage/local.ts";
|
|
23
23
|
export { createS3Storage } from "./storage/s3.ts";
|
|
24
24
|
export { createMemoryBackend } from "./backends/memory.ts";
|
|
25
|
-
export {
|
|
25
|
+
export { createSqliteBackend } from "./backends/sqlite.ts";
|
|
26
26
|
export { createPostgresBackend } from "./backends/postgres.ts";
|
|
27
27
|
export { makeServer } from "./server_factory.ts";
|
package/lib/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export * from "./core/seo.js"; // renderHead, renderSitemap, renderRobots, HeadM
|
|
|
16
16
|
export * from "./core/broker.js"; // pub/sub
|
|
17
17
|
export * from "./core/presence.js"; // ai online theo topic
|
|
18
18
|
export * from "./core/ratelimit.js"; // token-bucket + LRU
|
|
19
|
-
export * from "./core/codegen.js"; // genTS
|
|
19
|
+
export * from "./core/codegen.js"; // genTS
|
|
20
20
|
export * from "./core/layouts.js"; // layoutChain, LayoutMeta
|
|
21
21
|
export * from "./core/router.js"; // makeRouter
|
|
22
22
|
export * from "./core/testing.js"; // createTestBackend
|
|
@@ -26,6 +26,6 @@ export { createMemoryStorage } from "./storage/memory.js";
|
|
|
26
26
|
export { createLocalStorage } from "./storage/local.js";
|
|
27
27
|
export { createS3Storage } from "./storage/s3.js"; // adapter tham chiếu (cần @aws-sdk/client-s3)
|
|
28
28
|
export { createMemoryBackend } from "./backends/memory.js";
|
|
29
|
-
export {
|
|
30
|
-
export { createPostgresBackend } from "./backends/postgres.js";
|
|
29
|
+
export { createSqliteBackend } from "./backends/sqlite.js";
|
|
30
|
+
export { createPostgresBackend } from "./backends/postgres.js"; // user tự inject client `pg`
|
|
31
31
|
export { makeServer } from "./server_factory.js";
|
package/lib/react/DebugBar.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { createElement as h, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { debug } from "./store";
|
|
5
|
-
import { setChaos, getChaos
|
|
5
|
+
import { setChaos, getChaos } from "../core/client";
|
|
6
6
|
import { reproTest } from "./repro";
|
|
7
7
|
const EMPTY = [];
|
|
8
8
|
const DOT = { pending: "#e3b341", ok: "#3fb950", error: "#f85149" };
|
|
@@ -29,9 +29,8 @@ export function DebugBar() {
|
|
|
29
29
|
}, `⚡ fluxe · ${events.length}${errs ? ` · ${errs}✗` : ""}`));
|
|
30
30
|
}
|
|
31
31
|
const chaosOn = getChaos() !== "";
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const controls = h("div", { style: { display: "flex", gap: 6, alignItems: "center", padding: "6px 10px", background: "#0d1117", borderBottom: "1px solid #21262d", flexWrap: "wrap" } }, h("button", { onClick: () => { setChaos(chaosOn ? "" : CHAOS); force((x) => x + 1); }, style: ctrlBtn(chaosOn), title: CHAOS }, chaosOn ? "🔥 Chaos ON" : "Chaos"), h("span", { style: { color: "#7d8590" } }, "backend:"), ...["", "memory", "go", "rust"].map((v) => h("button", { key: v || "auto", onClick: () => { setDevBackend(v); force((x) => x + 1); }, style: ctrlBtn(be === v) }, v || "auto")));
|
|
32
|
+
// #1 Chaos toggle
|
|
33
|
+
const controls = h("div", { style: { display: "flex", gap: 6, alignItems: "center", padding: "6px 10px", background: "#0d1117", borderBottom: "1px solid #21262d", flexWrap: "wrap" } }, h("button", { onClick: () => { setChaos(chaosOn ? "" : CHAOS); force((x) => x + 1); }, style: ctrlBtn(chaosOn), title: CHAOS }, chaosOn ? "🔥 Chaos ON" : "Chaos"));
|
|
35
34
|
const rows = events.length === 0
|
|
36
35
|
? [h("div", { key: "e", style: { color: "#7d8590", padding: 8 } }, "Tương tác đi — query/mutation sẽ hiện ở đây.")]
|
|
37
36
|
: events.map((e) => h("div", { key: e.id, onClick: () => { setSel(sel === e.id ? null : e.id); setCopied(false); },
|
package/lib/server_factory.js
CHANGED
|
@@ -25,22 +25,6 @@ import { resolveLocale, makeT } from "./core/i18n.js";
|
|
|
25
25
|
import { parseMultipart, boundaryFromContentType } from "./core/multipart.js";
|
|
26
26
|
import { makeKey } from "./storage/types.js";
|
|
27
27
|
import { loadConfig } from "./core/config.js";
|
|
28
|
-
import { createMemoryBackend } from "./backends/memory.js";
|
|
29
|
-
import { createHttpBackend } from "./backends/http.js";
|
|
30
|
-
// Build backend theo ngôn ngữ (cho live swap trong devtools).
|
|
31
|
-
// Map ngôn ngữ → service HTTP demo (cùng hợp đồng list/add/toggle). Override bằng <LANG>_URL.
|
|
32
|
-
const DEV_BACKENDS = {
|
|
33
|
-
go: "http://127.0.0.1:8081",
|
|
34
|
-
rust: "http://127.0.0.1:8082",
|
|
35
|
-
python: "http://127.0.0.1:8083",
|
|
36
|
-
hono: "http://127.0.0.1:8084",
|
|
37
|
-
dotnet: "http://127.0.0.1:8085",
|
|
38
|
-
java: "http://127.0.0.1:8086",
|
|
39
|
-
};
|
|
40
|
-
function devBackend(lang) {
|
|
41
|
-
const url = process.env[`${lang.toUpperCase()}_URL`] ?? DEV_BACKENDS[lang];
|
|
42
|
-
return url ? createHttpBackend(lang, url) : createMemoryBackend();
|
|
43
|
-
}
|
|
44
28
|
import { randomUUID, randomBytes } from "node:crypto";
|
|
45
29
|
const DEV = process.env.NODE_ENV !== "production";
|
|
46
30
|
const SECRET = process.env.FLUXE_SECRET ?? "dev-secret-change-me";
|
|
@@ -286,15 +270,10 @@ export function makeServer(manifest, cells, layouts = {}, opts = {}) {
|
|
|
286
270
|
return res.end("no action");
|
|
287
271
|
}
|
|
288
272
|
const t0 = Date.now();
|
|
289
|
-
// DevTools (DEV): #
|
|
290
|
-
|
|
273
|
+
// DevTools (DEV): #3 resolution (backend TS in-process do manifest giải).
|
|
274
|
+
const backend = backendFor(cellId);
|
|
291
275
|
const r = manifest.cells[cellId]?.backend;
|
|
292
|
-
|
|
293
|
-
if (DEV && req.headers["x-fluxe-backend"]) {
|
|
294
|
-
const lang = String(req.headers["x-fluxe-backend"]);
|
|
295
|
-
backend = devBackend(lang);
|
|
296
|
-
resolution = `${lang}/${lang === "memory" ? "in-process" : "http"} (swap)`;
|
|
297
|
-
}
|
|
276
|
+
const resolution = r ? `${r.language}/in-process` : "memory/in-process";
|
|
298
277
|
// #1 Chaos (DEV): inject delay + lỗi giả lập để test UX.
|
|
299
278
|
if (DEV && req.headers["x-fluxe-chaos"]) {
|
|
300
279
|
const c = parseChaos(String(req.headers["x-fluxe-chaos"]));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmvuong92/fluxe",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "fluxe — khung fullstack tối giản
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "fluxe — khung fullstack tối giản, một runtime TS (RCA: Resolved Cell Architecture).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "nmvuong92",
|
|
7
7
|
"type": "module",
|
package/lib/backends/http.d.ts
DELETED
package/lib/backends/http.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/* Backend #3/#4: gọi một service THẬT qua HTTP (Go hoặc Rust).
|
|
2
|
-
* Cùng interface Backend → cell + frontend KHÔNG đổi một dòng.
|
|
3
|
-
* Service chỉ cần tôn trọng "hợp đồng" 3 endpoint:
|
|
4
|
-
* GET /todos → Todo[]
|
|
5
|
-
* POST /todos {title} → Todo
|
|
6
|
-
* POST /todos/{id}/toggle → Todo[]
|
|
7
|
-
*/
|
|
8
|
-
export function createHttpBackend(name, baseUrl) {
|
|
9
|
-
const base = baseUrl.replace(/\/$/, "");
|
|
10
|
-
async function j(path, init) {
|
|
11
|
-
const r = await fetch(base + path, init);
|
|
12
|
-
if (!r.ok)
|
|
13
|
-
throw new Error(`${name} backend ${path} → HTTP ${r.status}`);
|
|
14
|
-
return (await r.json());
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
name,
|
|
18
|
-
listTodos() {
|
|
19
|
-
return j("/todos");
|
|
20
|
-
},
|
|
21
|
-
addTodo(title) {
|
|
22
|
-
return j("/todos", {
|
|
23
|
-
method: "POST",
|
|
24
|
-
headers: { "content-type": "application/json" },
|
|
25
|
-
body: JSON.stringify({ title }),
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
toggleTodo(id) {
|
|
29
|
-
return j(`/todos/${encodeURIComponent(id)}/toggle`, { method: "POST" });
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
package/lib/hot/search.d.ts
DELETED
package/lib/hot/search.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function createRustSearch(baseUrl) {
|
|
2
|
-
const base = baseUrl.replace(/\/$/, "");
|
|
3
|
-
return {
|
|
4
|
-
name: "rust-hot",
|
|
5
|
-
async search(items, query) {
|
|
6
|
-
const r = await fetch(`${base}/search?q=${encodeURIComponent(query)}`, {
|
|
7
|
-
method: "POST",
|
|
8
|
-
headers: { "content-type": "text/plain" },
|
|
9
|
-
body: items.join("\n"),
|
|
10
|
-
});
|
|
11
|
-
if (!r.ok)
|
|
12
|
-
throw new Error(`hot search → HTTP ${r.status}`);
|
|
13
|
-
return (await r.json());
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
}
|