@nmvuong92/fluxe 0.4.0 → 0.5.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.
@@ -0,0 +1,9 @@
1
+ export type Factory<T> = (c: Container) => T;
2
+ export interface Container {
3
+ register<T>(token: string, factory: Factory<T>): Container;
4
+ override<T>(token: string, factory: Factory<T>): Container;
5
+ has(token: string): boolean;
6
+ get<T>(token: string): T;
7
+ resolved(): string[];
8
+ }
9
+ export declare function createContainer(): Container;
@@ -0,0 +1,46 @@
1
+ // Copyright (c) 2026 nmvuong92
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /* Resolved Container — DI lười (lazy singleton). Register factory = O(1), KHÔNG instantiate;
4
+ * get() tạo lần đầu rồi memoize. Factory tự c.get(dep) → DI + thứ tự init tự nhiên (DFS) +
5
+ * phát hiện vòng (cycle). resolved() liệt kê token đã tạo → "chỉ module dùng mới bootstrap".
6
+ * DSA: Map provider + Map instance (O(1)); Set "đang giải" (cycle, O(depth)). */
7
+ export function createContainer() {
8
+ const providers = new Map();
9
+ const instances = new Map();
10
+ const resolving = new Set(); // DFS đang giải → bắt cycle
11
+ const c = {
12
+ register(token, factory) {
13
+ if (providers.has(token))
14
+ throw new Error(`Container: token '${token}' đã đăng ký (dùng override để ghi đè)`);
15
+ providers.set(token, factory);
16
+ return c;
17
+ },
18
+ override(token, factory) {
19
+ providers.set(token, factory);
20
+ instances.delete(token); // buộc tạo lại lần get sau
21
+ return c;
22
+ },
23
+ has: (token) => providers.has(token),
24
+ get(token) {
25
+ if (instances.has(token))
26
+ return instances.get(token); // memoized singleton
27
+ const f = providers.get(token);
28
+ if (!f)
29
+ throw new Error(`Container: chưa đăng ký token '${token}'`);
30
+ if (resolving.has(token)) {
31
+ throw new Error(`Container: phụ thuộc vòng (cycle) tại '${token}' — chuỗi: ${[...resolving, token].join(" → ")}`);
32
+ }
33
+ resolving.add(token);
34
+ try {
35
+ const inst = f(c); // factory có thể c.get(dep) → DI lười, thứ tự tự nhiên
36
+ instances.set(token, inst);
37
+ return inst;
38
+ }
39
+ finally {
40
+ resolving.delete(token);
41
+ }
42
+ },
43
+ resolved: () => [...instances.keys()],
44
+ };
45
+ return c;
46
+ }
package/lib/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./core/wiring.ts";
6
6
  export * from "./core/auth.ts";
7
7
  export * from "./core/env.ts";
8
8
  export * from "./core/config.ts";
9
+ export * from "./core/container.ts";
9
10
  export * from "./core/i18n.ts";
10
11
  export * from "./core/seo.ts";
11
12
  export * from "./core/broker.ts";
package/lib/index.js CHANGED
@@ -10,6 +10,7 @@ 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
12
  export * from "./core/config.js"; // FluxeConfig, loadConfig (default ← ENV FLUXE_* ← override)
13
+ export * from "./core/container.js"; // createContainer — Resolved Container (DI lười, chỉ-used-bootstrap)
13
14
  export * from "./core/i18n.js"; // createI18n, resolveLocale, translate, makeT, t(key, vars)
14
15
  export * from "./core/seo.js"; // renderHead, renderSitemap, renderRobots, HeadMeta
15
16
  export * from "./core/broker.js"; // pub/sub
@@ -14,6 +14,7 @@ import { FluxeError, toErrorPayload, renderErrorPage } from "./core/errors.js";
14
14
  import { signSession, verifySession, parseCookie, hasRole, hashPassword, verifyPassword, newCsrfToken } from "./core/auth.js";
15
15
  import { validateInput } from "./core/validate.js";
16
16
  import { createBroker } from "./core/broker.js";
17
+ import { createContainer } from "./core/container.js";
17
18
  import { createRateLimiter } from "./core/ratelimit.js";
18
19
  import { createRecorder } from "./core/observe.js";
19
20
  import { createPresence } from "./core/presence.js";
@@ -116,10 +117,13 @@ export function makeServer(manifest, cells, layouts = {}, opts = {}) {
116
117
  // Backend GIẢI per-cell từ manifest (Resolution Plane) — cell/frontend giữ nguyên.
117
118
  const backends = backendsFromManifest(manifest);
118
119
  const backendFor = (id) => backends.byCell.get(id) ?? backends.default;
119
- const broker = createBroker(); // realtime pub/sub (Trục 4g, bản 1-node)
120
+ // Resolved Container: service realtime đăng LƯỜI chỉ tạo khi thật sự dùng (SSE/action).
121
+ // App không realtime → broker/presence KHÔNG bao giờ bootstrap. resolved() ở /_fluxe/stats.
122
+ const container = createContainer();
123
+ container.register("broker", () => createBroker());
124
+ container.register("presence", () => createPresence());
120
125
  const actionLimit = createRateLimiter(config.rateLimit); // per-IP cho action (FLUXE_RATELIMIT_*)
121
- const recorder = createRecorder(); // request log (observability)
122
- const presence = createPresence(); // ai đang online per topic (Trục 4g)
126
+ const recorder = createRecorder(); // request log — chạy mỗi request → eager (luôn dùng)
123
127
  const renderCache = createRenderCache({ maxKeys: config.renderCache.maxKeys }); // FLUXE_RENDERCACHE_MAX_KEYS
124
128
  let clientJs; // ý A: đọc dist/client.js 1 lần (zero-copy: tái dùng buffer)
125
129
  return http.createServer(async (req, res) => {
@@ -160,7 +164,7 @@ export function makeServer(manifest, cells, layouts = {}, opts = {}) {
160
164
  const m = process.memoryUsage();
161
165
  const c = process.cpuUsage();
162
166
  res.writeHead(200, { "content-type": "application/json" });
163
- return res.end(JSON.stringify({ rss: m.rss, heapUsed: m.heapUsed, cpuUser: c.user, cpuSystem: c.system, uptimeMs: Math.round(process.uptime() * 1000) }));
167
+ return res.end(JSON.stringify({ rss: m.rss, heapUsed: m.heapUsed, cpuUser: c.user, cpuSystem: c.system, uptimeMs: Math.round(process.uptime() * 1000), bootstrapped: container.resolved() }));
164
168
  }
165
169
  if (url.pathname === "/_fluxe/requests") {
166
170
  // Observability: log request gần đây (timing/status). Prod: gate sau auth.
@@ -246,6 +250,9 @@ export function makeServer(manifest, cells, layouts = {}, opts = {}) {
246
250
  const id = url.searchParams.get("id");
247
251
  res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
248
252
  res.write(`event: ready\ndata: {"topic":"${topic}"}\n\n`);
253
+ // Lần đầu có client SSE → broker + presence mới bootstrap (lazy qua container).
254
+ const broker = container.get("broker");
255
+ const presence = container.get("presence");
249
256
  const offBroker = broker.subscribe(topic, (data) => res.write(`data: ${JSON.stringify(data)}\n\n`));
250
257
  let offPresence = () => { };
251
258
  if (id) {
@@ -301,7 +308,7 @@ export function makeServer(manifest, cells, layouts = {}, opts = {}) {
301
308
  if (schema)
302
309
  input = validateInput(schema, input); // sai → FluxeError 400 (caught)
303
310
  const out = await fn({ input, backend, session });
304
- broker.publish(cellId, { action: name, out }); // realtime: báo client khác
311
+ container.get("broker").publish(cellId, { action: name, out }); // realtime: báo client khác (broker lazy)
305
312
  res.writeHead(200, { "content-type": "application/json", "x-fluxe-resolution": resolution, "x-fluxe-server-ms": String(Date.now() - t0) });
306
313
  return res.end(JSON.stringify(out));
307
314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmvuong92/fluxe",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "fluxe — khung fullstack tối giản polyglot (RCA: Resolved Cell Architecture).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "nmvuong92",