@nmvuong92/fluxe 0.3.0 → 0.4.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/lib/core/cli.js +4 -0
- package/lib/core/config.d.ts +98 -0
- package/lib/core/config.js +66 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/server_factory.d.ts +2 -1
- package/lib/server_factory.js +5 -3
- package/package.json +1 -1
package/lib/core/cli.js
CHANGED
|
@@ -17,6 +17,10 @@ export const COMMANDS = {
|
|
|
17
17
|
desc: "Auto-discovery: quét app/cells/* → app/app.ts (dev/resolve tự gọi)",
|
|
18
18
|
shell: () => SYNC,
|
|
19
19
|
},
|
|
20
|
+
config: {
|
|
21
|
+
desc: "In config đã giải (default ← ENV FLUXE_* ← override)",
|
|
22
|
+
shell: () => `tsx scripts/config.ts`,
|
|
23
|
+
},
|
|
20
24
|
gen: {
|
|
21
25
|
desc: "Codegen contract → types TS/Go/Rust (.fluxe/gen)",
|
|
22
26
|
shell: () => `tsx scripts/codegen.ts`,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const Schema: z.ZodObject<{
|
|
3
|
+
env: z.ZodEnum<["development", "production", "test"]>;
|
|
4
|
+
secret: z.ZodString;
|
|
5
|
+
port: z.ZodNumber;
|
|
6
|
+
defaultBackend: z.ZodEnum<["memory", "go", "rust"]>;
|
|
7
|
+
rateLimit: z.ZodObject<{
|
|
8
|
+
capacity: z.ZodNumber;
|
|
9
|
+
refillPerSec: z.ZodNumber;
|
|
10
|
+
maxKeys: z.ZodNumber;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
capacity: number;
|
|
13
|
+
refillPerSec: number;
|
|
14
|
+
maxKeys: number;
|
|
15
|
+
}, {
|
|
16
|
+
capacity: number;
|
|
17
|
+
refillPerSec: number;
|
|
18
|
+
maxKeys: number;
|
|
19
|
+
}>;
|
|
20
|
+
renderCache: z.ZodObject<{
|
|
21
|
+
maxKeys: z.ZodNumber;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
maxKeys: number;
|
|
24
|
+
}, {
|
|
25
|
+
maxKeys: number;
|
|
26
|
+
}>;
|
|
27
|
+
upload: z.ZodObject<{
|
|
28
|
+
maxBytes: z.ZodNumber;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
maxBytes: number;
|
|
31
|
+
}, {
|
|
32
|
+
maxBytes: number;
|
|
33
|
+
}>;
|
|
34
|
+
i18n: z.ZodObject<{
|
|
35
|
+
defaultLocale: z.ZodString;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
defaultLocale: string;
|
|
38
|
+
}, {
|
|
39
|
+
defaultLocale: string;
|
|
40
|
+
}>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
env: "development" | "production" | "test";
|
|
43
|
+
secret: string;
|
|
44
|
+
port: number;
|
|
45
|
+
defaultBackend: "memory" | "go" | "rust";
|
|
46
|
+
rateLimit: {
|
|
47
|
+
capacity: number;
|
|
48
|
+
refillPerSec: number;
|
|
49
|
+
maxKeys: number;
|
|
50
|
+
};
|
|
51
|
+
renderCache: {
|
|
52
|
+
maxKeys: number;
|
|
53
|
+
};
|
|
54
|
+
upload: {
|
|
55
|
+
maxBytes: number;
|
|
56
|
+
};
|
|
57
|
+
i18n: {
|
|
58
|
+
defaultLocale: string;
|
|
59
|
+
};
|
|
60
|
+
}, {
|
|
61
|
+
env: "development" | "production" | "test";
|
|
62
|
+
secret: string;
|
|
63
|
+
port: number;
|
|
64
|
+
defaultBackend: "memory" | "go" | "rust";
|
|
65
|
+
rateLimit: {
|
|
66
|
+
capacity: number;
|
|
67
|
+
refillPerSec: number;
|
|
68
|
+
maxKeys: number;
|
|
69
|
+
};
|
|
70
|
+
renderCache: {
|
|
71
|
+
maxKeys: number;
|
|
72
|
+
};
|
|
73
|
+
upload: {
|
|
74
|
+
maxBytes: number;
|
|
75
|
+
};
|
|
76
|
+
i18n: {
|
|
77
|
+
defaultLocale: string;
|
|
78
|
+
};
|
|
79
|
+
}>;
|
|
80
|
+
export type FluxeConfig = z.infer<typeof Schema>;
|
|
81
|
+
type Src = Record<string, string | undefined>;
|
|
82
|
+
export declare const ENV_KEYS: {
|
|
83
|
+
readonly NODE_ENV: "env";
|
|
84
|
+
readonly FLUXE_SECRET: "secret";
|
|
85
|
+
readonly "PORT (ho\u1EB7c FLUXE_PORT)": "port";
|
|
86
|
+
readonly FLUXE_BACKEND: "defaultBackend";
|
|
87
|
+
readonly FLUXE_RATELIMIT_CAPACITY: "rateLimit.capacity";
|
|
88
|
+
readonly FLUXE_RATELIMIT_REFILL: "rateLimit.refillPerSec";
|
|
89
|
+
readonly FLUXE_RATELIMIT_MAX_KEYS: "rateLimit.maxKeys";
|
|
90
|
+
readonly FLUXE_RENDERCACHE_MAX_KEYS: "renderCache.maxKeys";
|
|
91
|
+
readonly FLUXE_UPLOAD_MAX_BYTES: "upload.maxBytes";
|
|
92
|
+
readonly FLUXE_LOCALE_DEFAULT: "i18n.defaultLocale";
|
|
93
|
+
};
|
|
94
|
+
type DeepPartial<T> = {
|
|
95
|
+
[K in keyof T]?: T[K] extends object ? Partial<T[K]> : T[K];
|
|
96
|
+
};
|
|
97
|
+
export declare function loadConfig(source?: Src, overrides?: DeepPartial<FluxeConfig>): FluxeConfig;
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright (c) 2026 nmvuong92
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/* Config core — DEFAULT + override qua ENV (quy ước FLUXE_*), kiểu Laravel config().
|
|
4
|
+
* Một nguồn sự thật cho mọi tham số tinh chỉnh của engine. Thứ tự ưu tiên:
|
|
5
|
+
* default < ENV (FLUXE_*) < override truyền tay.
|
|
6
|
+
* Mọi giá trị quan trọng ĐỀU expose ra ENV + tài liệu (xem docs/reference/configuration). */
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
const Schema = z.object({
|
|
9
|
+
env: z.enum(["development", "production", "test"]),
|
|
10
|
+
secret: z.string().min(8),
|
|
11
|
+
port: z.coerce.number().int().positive(),
|
|
12
|
+
defaultBackend: z.enum(["memory", "go", "rust"]),
|
|
13
|
+
rateLimit: z.object({
|
|
14
|
+
capacity: z.coerce.number().int().positive(),
|
|
15
|
+
refillPerSec: z.coerce.number().positive(),
|
|
16
|
+
maxKeys: z.coerce.number().int().positive(),
|
|
17
|
+
}),
|
|
18
|
+
renderCache: z.object({ maxKeys: z.coerce.number().int().positive() }),
|
|
19
|
+
upload: z.object({ maxBytes: z.coerce.number().int().positive() }),
|
|
20
|
+
i18n: z.object({ defaultLocale: z.string().min(2) }),
|
|
21
|
+
});
|
|
22
|
+
/* Bảng ENV — TÊN biến ↔ field. Dùng cho loadConfig + sinh tài liệu. */
|
|
23
|
+
export const ENV_KEYS = {
|
|
24
|
+
"NODE_ENV": "env",
|
|
25
|
+
"FLUXE_SECRET": "secret",
|
|
26
|
+
"PORT (hoặc FLUXE_PORT)": "port",
|
|
27
|
+
"FLUXE_BACKEND": "defaultBackend",
|
|
28
|
+
"FLUXE_RATELIMIT_CAPACITY": "rateLimit.capacity",
|
|
29
|
+
"FLUXE_RATELIMIT_REFILL": "rateLimit.refillPerSec",
|
|
30
|
+
"FLUXE_RATELIMIT_MAX_KEYS": "rateLimit.maxKeys",
|
|
31
|
+
"FLUXE_RENDERCACHE_MAX_KEYS": "renderCache.maxKeys",
|
|
32
|
+
"FLUXE_UPLOAD_MAX_BYTES": "upload.maxBytes",
|
|
33
|
+
"FLUXE_LOCALE_DEFAULT": "i18n.defaultLocale",
|
|
34
|
+
};
|
|
35
|
+
const num = (s, k, d) => {
|
|
36
|
+
const v = s[k];
|
|
37
|
+
return v == null || v === "" ? d : Number(v);
|
|
38
|
+
};
|
|
39
|
+
function merge(base, o) {
|
|
40
|
+
return {
|
|
41
|
+
...base,
|
|
42
|
+
...o,
|
|
43
|
+
rateLimit: { ...base.rateLimit, ...o.rateLimit },
|
|
44
|
+
renderCache: { ...base.renderCache, ...o.renderCache },
|
|
45
|
+
upload: { ...base.upload, ...o.upload },
|
|
46
|
+
i18n: { ...base.i18n, ...o.i18n },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/* Giải config: default ← ENV (FLUXE_*) ← override. Validate Zod (sai → ném ngay, fail-fast). */
|
|
50
|
+
export function loadConfig(source = process.env, overrides = {}) {
|
|
51
|
+
const fromEnv = {
|
|
52
|
+
env: source.NODE_ENV || "development",
|
|
53
|
+
secret: source.FLUXE_SECRET || "dev-secret-change-me",
|
|
54
|
+
port: num(source, "PORT", num(source, "FLUXE_PORT", 5180)),
|
|
55
|
+
defaultBackend: source.FLUXE_BACKEND || "memory",
|
|
56
|
+
rateLimit: {
|
|
57
|
+
capacity: num(source, "FLUXE_RATELIMIT_CAPACITY", 30),
|
|
58
|
+
refillPerSec: num(source, "FLUXE_RATELIMIT_REFILL", 10),
|
|
59
|
+
maxKeys: num(source, "FLUXE_RATELIMIT_MAX_KEYS", 5000),
|
|
60
|
+
},
|
|
61
|
+
renderCache: { maxKeys: num(source, "FLUXE_RENDERCACHE_MAX_KEYS", 256) },
|
|
62
|
+
upload: { maxBytes: num(source, "FLUXE_UPLOAD_MAX_BYTES", 10 * 1024 * 1024) },
|
|
63
|
+
i18n: { defaultLocale: source.FLUXE_LOCALE_DEFAULT || "en" },
|
|
64
|
+
};
|
|
65
|
+
return Schema.parse(merge(fromEnv, overrides));
|
|
66
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./core/resolver.ts";
|
|
|
5
5
|
export * from "./core/wiring.ts";
|
|
6
6
|
export * from "./core/auth.ts";
|
|
7
7
|
export * from "./core/env.ts";
|
|
8
|
+
export * from "./core/config.ts";
|
|
8
9
|
export * from "./core/i18n.ts";
|
|
9
10
|
export * from "./core/seo.ts";
|
|
10
11
|
export * from "./core/broker.ts";
|
package/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./core/resolver.js"; // resolve, ResolutionProfile/Manifest, Cell
|
|
|
9
9
|
export * from "./core/wiring.js"; // backendFromManifest, backendsFromManifest
|
|
10
10
|
export * from "./core/auth.js"; // session HMAC, scrypt password, CSRF, RBAC
|
|
11
11
|
export * from "./core/env.js"; // loadEnv
|
|
12
|
+
export * from "./core/config.js"; // FluxeConfig, loadConfig (default ← ENV FLUXE_* ← override)
|
|
12
13
|
export * from "./core/i18n.js"; // createI18n, resolveLocale, translate, makeT, t(key, vars)
|
|
13
14
|
export * from "./core/seo.js"; // renderHead, renderSitemap, renderRobots, HeadMeta
|
|
14
15
|
export * from "./core/broker.js"; // pub/sub
|
package/lib/server_factory.d.ts
CHANGED
|
@@ -10,9 +10,10 @@ type LayoutEntry = LayoutMeta & {
|
|
|
10
10
|
type LayoutMap = Record<string, LayoutEntry>;
|
|
11
11
|
import { type I18n } from "./core/i18n.ts";
|
|
12
12
|
import { type Storage } from "./storage/types.ts";
|
|
13
|
+
import { type FluxeConfig } from "./core/config.ts";
|
|
13
14
|
export declare function makeServer(manifest: ResolutionManifest, cells: CellDef<any, any>[], layouts?: LayoutMap, opts?: {
|
|
14
15
|
i18n?: I18n;
|
|
15
16
|
storage?: Storage;
|
|
16
|
-
|
|
17
|
+
config?: FluxeConfig;
|
|
17
18
|
}): http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
18
19
|
export {};
|
package/lib/server_factory.js
CHANGED
|
@@ -23,6 +23,7 @@ import { parseChaos } from "./core/chaos.js";
|
|
|
23
23
|
import { resolveLocale, makeT } from "./core/i18n.js";
|
|
24
24
|
import { parseMultipart, boundaryFromContentType } from "./core/multipart.js";
|
|
25
25
|
import { makeKey } from "./storage/types.js";
|
|
26
|
+
import { loadConfig } from "./core/config.js";
|
|
26
27
|
import { createMemoryBackend } from "./backends/memory.js";
|
|
27
28
|
import { createHttpBackend } from "./backends/http.js";
|
|
28
29
|
// Build backend theo ngôn ngữ (cho live swap trong devtools).
|
|
@@ -92,7 +93,8 @@ function renderBodyToString(node) {
|
|
|
92
93
|
export function makeServer(manifest, cells, layouts = {}, opts = {}) {
|
|
93
94
|
const i18n = opts.i18n;
|
|
94
95
|
const storage = opts.storage;
|
|
95
|
-
const
|
|
96
|
+
const config = opts.config ?? loadConfig(); // default ← ENV (FLUXE_*) ← override
|
|
97
|
+
const MAX_UPLOAD = config.upload.maxBytes;
|
|
96
98
|
const readBodyBuffer = (req) => new Promise((resolve, reject) => {
|
|
97
99
|
const chunks = [];
|
|
98
100
|
let size = 0;
|
|
@@ -115,10 +117,10 @@ export function makeServer(manifest, cells, layouts = {}, opts = {}) {
|
|
|
115
117
|
const backends = backendsFromManifest(manifest);
|
|
116
118
|
const backendFor = (id) => backends.byCell.get(id) ?? backends.default;
|
|
117
119
|
const broker = createBroker(); // realtime pub/sub (Trục 4g, bản 1-node)
|
|
118
|
-
const actionLimit = createRateLimiter(
|
|
120
|
+
const actionLimit = createRateLimiter(config.rateLimit); // per-IP cho action (FLUXE_RATELIMIT_*)
|
|
119
121
|
const recorder = createRecorder(); // request log (observability)
|
|
120
122
|
const presence = createPresence(); // ai đang online per topic (Trục 4g)
|
|
121
|
-
const renderCache = createRenderCache({ maxKeys:
|
|
123
|
+
const renderCache = createRenderCache({ maxKeys: config.renderCache.maxKeys }); // FLUXE_RENDERCACHE_MAX_KEYS
|
|
122
124
|
let clientJs; // ý A: đọc dist/client.js 1 lần (zero-copy: tái dùng buffer)
|
|
123
125
|
return http.createServer(async (req, res) => {
|
|
124
126
|
const url = new URL(req.url, "http://localhost");
|