@aromix/core 0.4.3 → 0.4.5

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/index.d.ts CHANGED
@@ -1,32 +1,26 @@
1
1
  import { AxSchemaShape, AxObjectSchema } from '@aromix/validator';
2
2
 
3
- type Phase = 'start' | 'stop';
4
- interface Block {
3
+ type Loader<T> = () => Promise<{
4
+ default: T;
5
+ }>;
6
+ interface Unit {
5
7
  name: string;
6
8
  start(): void | Promise<void>;
7
9
  stop?(): void | Promise<void>;
8
- error?(err: Error, phase: Phase): void | Promise<void>;
9
10
  }
10
- type ErrorHandler = (err: Error, phase: Phase | 'runtime') => void | Promise<void>;
11
11
 
12
- declare class App {
13
- private isStarted;
14
- private isStopping;
15
- private blocks;
16
- private errorHandlers;
17
- use(block: Block): void;
18
- onError(handler: ErrorHandler): this;
19
- start(): Promise<void>;
20
- stop(): Promise<void>;
21
- private notifyError;
22
- private stopAll;
23
- private gracefulShutdown;
24
- }
12
+ declare function system(): {
13
+ load: (loader: Loader<unknown>) => void;
14
+ register: (loader: Loader<Unit>) => void;
15
+ start: () => Promise<void>;
16
+ stop: () => Promise<void>;
17
+ };
25
18
 
26
- type LoadEnvOptions<Shape extends AxSchemaShape> = Partial<{
19
+ declare function LoadEnv<Shape extends AxSchemaShape = AxSchemaShape>(options: Partial<{
27
20
  path: string;
28
21
  schema: AxObjectSchema<Shape>;
29
- }>;
30
- declare function loadEnv<Shape extends AxSchemaShape>(options: LoadEnvOptions<Shape>): Block;
22
+ }>): {
23
+ env: <Key extends keyof Shape["base"]>(key: Key) => Shape["base"][Key];
24
+ };
31
25
 
32
- export { App, type Block, type ErrorHandler, type LoadEnvOptions, type Phase, loadEnv };
26
+ export { LoadEnv, type Unit, system };
package/dist/index.js CHANGED
@@ -1,124 +1,93 @@
1
- // src/app/builder.ts
2
- var App = class {
3
- isStarted = false;
4
- isStopping = false;
5
- blocks = [];
6
- errorHandlers = [];
7
- use(block) {
8
- if (this.isStarted) {
9
- throw new Error(`[Aromix] Unable to register "${block.name}": process already started.`);
1
+ // src/system/def.ts
2
+ function system() {
3
+ const configLoaders = [];
4
+ const unitLoaders = [];
5
+ const units = [];
6
+ let state = "idle";
7
+ function assertIdle(action) {
8
+ if (state !== "idle") {
9
+ throw new Error(`[Aromix] Cannot ${action}: system is "${state}", expected "idle".`);
10
10
  }
11
- if (this.blocks.some((b) => b.name === block.name)) {
12
- throw new Error(`[Aromix] Block with name "${block.name}" is already registered.`);
13
- }
14
- this.blocks.push(block);
15
- console.debug("[Aromix] Block registered", { block: block.name, phase: "register" });
16
11
  }
17
- onError(handler) {
18
- this.errorHandlers.push(handler);
19
- return this;
12
+ function load(loader) {
13
+ assertIdle("add a config loader");
14
+ configLoaders.push(loader);
20
15
  }
21
- async start() {
22
- if (this.isStarted) {
23
- throw new Error("[Aromix] Process has already been started.");
24
- }
25
- this.isStarted = true;
26
- console.info("[Aromix] Starting application", { totalBlocks: this.blocks.length });
27
- for (const block of this.blocks) {
28
- const startedAt = Date.now();
16
+ function register(loader) {
17
+ assertIdle("register a unit");
18
+ unitLoaders.push(loader);
19
+ }
20
+ async function stop() {
21
+ if (state === "stopped" || state === "stopping") return;
22
+ state = "stopping";
23
+ for (let i = units.length - 1; i >= 0; i--) {
29
24
  try {
30
- await block.start();
31
- console.log("[Aromix] Block started", { block: block.name, phase: "start", durationMs: Date.now() - startedAt });
25
+ await units[i].stop?.();
32
26
  } catch (err) {
33
- console.error("[Aromix] Fatal: block failed to start. Rolling back...", err, { block: block.name, phase: "start" });
34
- await this.notifyError(err, "start", block);
35
- await this.stopAll("start-failure");
36
- process.exit(1);
27
+ console.error(`[Aromix] "${units[i].name}" failed to stop`, err);
37
28
  }
38
29
  }
39
- process.on("uncaughtException", (err) => {
40
- console.error("[Aromix] Uncaught exception. Shutting down...", err);
41
- this.notifyError(err, "runtime").finally(() => this.gracefulShutdown("uncaughtException"));
42
- });
43
- process.on("unhandledRejection", (reason) => {
44
- const err = reason instanceof Error ? reason : new Error(String(reason));
45
- console.error("[Aromix] Unhandled rejection. Shutting down...", err);
46
- this.notifyError(err, "runtime").finally(() => this.gracefulShutdown("unhandledRejection"));
47
- });
48
- process.once("SIGINT", () => this.gracefulShutdown("SIGINT"));
49
- process.once("SIGTERM", () => this.gracefulShutdown("SIGTERM"));
50
- console.log("[Aromix] Application started successfully", { totalBlocks: this.blocks.length });
30
+ state = "stopped";
51
31
  }
52
- async stop() {
53
- await this.stopAll("manual");
54
- }
55
- async notifyError(err, phase, block) {
56
- const error = err instanceof Error ? err : new Error(String(err));
57
- if (block?.error && (phase === "start" || phase === "stop")) {
58
- try {
59
- await block.error(error, phase);
60
- } catch (handlerErr) {
61
- console.error(`[Aromix] Block "${block.name}" error handler itself threw`, handlerErr, { block: block.name, phase });
62
- }
32
+ async function start() {
33
+ assertIdle("start");
34
+ state = "loading";
35
+ for (const loader of configLoaders) {
36
+ await loader();
63
37
  }
64
- for (const handler of this.errorHandlers) {
65
- try {
66
- await handler(error, phase);
67
- } catch (handlerErr) {
68
- console.error("[Aromix] Global error handler threw", handlerErr, { phase });
38
+ state = "starting";
39
+ for (const loader of unitLoaders) {
40
+ const { default: unit } = await loader();
41
+ if (units.some((u) => u.name === unit.name)) {
42
+ throw new Error(`[Aromix] Unit with name "${unit.name}" is already registered.`);
69
43
  }
70
- }
71
- }
72
- async stopAll(reason) {
73
- if (this.isStopping) return true;
74
- this.isStopping = true;
75
- console.info("[Aromix] Stopping all blocks", { phase: "stop", signal: reason, totalBlocks: this.blocks.length });
76
- let allStoppedCleanly = true;
77
- for (let i = this.blocks.length - 1; i >= 0; i--) {
78
- const block = this.blocks[i];
79
- const startedAt = Date.now();
80
44
  try {
81
- await block.stop?.();
82
- console.log("[Aromix] Block stopped", { block: block.name, phase: "stop", durationMs: Date.now() - startedAt });
45
+ await unit.start();
46
+ units.push(unit);
83
47
  } catch (err) {
84
- allStoppedCleanly = false;
85
- console.error("[Aromix] Block failed to stop", err, { block: block.name, phase: "stop" });
86
- await this.notifyError(err, "stop", block);
48
+ await stop();
49
+ throw new Error(`[Aromix] "${unit.name}" failed to start`, { cause: err });
87
50
  }
88
51
  }
89
- return allStoppedCleanly;
52
+ state = "running";
53
+ const onSignal = async () => {
54
+ await stop();
55
+ process.exit(0);
56
+ };
57
+ process.once("SIGINT", onSignal);
58
+ process.once("SIGTERM", onSignal);
90
59
  }
91
- async gracefulShutdown(signal) {
92
- console.info(`[Aromix] Received ${signal}. Starting graceful shutdown...`, { signal });
93
- const clean = await this.stopAll(signal);
94
- process.exit(clean ? 0 : 1);
95
- }
96
- };
60
+ return {
61
+ load,
62
+ register,
63
+ start,
64
+ stop
65
+ };
66
+ }
97
67
 
98
- // src/common/load.env.ts
99
- import { resolve } from "path";
68
+ // src/common/LoadEnv.ts
100
69
  import { existsSync } from "fs";
101
- function loadEnv(options) {
70
+ import { resolve } from "path";
71
+ function LoadEnv(options) {
102
72
  const path = resolve(options.path ?? ".env");
103
- return {
104
- name: "Load Env",
105
- async start() {
106
- if (!existsSync(path)) {
107
- throw new Error(`[Load Env] Environment file not found: ${path}`);
108
- }
109
- process.loadEnvFile(path);
110
- if (options.schema) {
111
- const [result, issues] = options.schema.parseBase(process.env);
112
- if (issues) {
113
- throw new Error("[Load Env] Schema Validation Failed", {
114
- cause: issues
115
- });
116
- }
117
- }
73
+ if (!existsSync(path)) {
74
+ throw new Error(`[LoadEnv] Environment file not found: ${path}`);
75
+ }
76
+ process.loadEnvFile(path);
77
+ let values = process.env;
78
+ if (options.schema) {
79
+ const [result, issues] = options.schema.parseBase(process.env);
80
+ if (issues) {
81
+ throw new Error("[LoadEnv] Schema validation failed", { cause: JSON.stringify(issues) });
118
82
  }
119
- };
83
+ values = result;
84
+ }
85
+ function env(key) {
86
+ return values[key];
87
+ }
88
+ return { env };
120
89
  }
121
90
  export {
122
- App,
123
- loadEnv
91
+ LoadEnv,
92
+ system
124
93
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aromix/core",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "The Core Package For Aromix",
5
5
  "type": "module",
6
6
  "license": "MIT",