@aromix/core 0.4.4 → 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): void;
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,104 +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
11
  }
16
- onError(handler) {
17
- this.errorHandlers.push(handler);
12
+ function load(loader) {
13
+ assertIdle("add a config loader");
14
+ configLoaders.push(loader);
18
15
  }
19
- async start() {
20
- if (this.isStarted) {
21
- throw new Error("[Aromix] Process has already been started.");
22
- }
23
- this.isStarted = true;
24
- for (const block of this.blocks) {
25
- 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--) {
26
24
  try {
27
- await block.start();
25
+ await units[i].stop?.();
28
26
  } catch (err) {
29
- await this.notifyError(err, "start", block);
30
- await this.stopAll("start-failure");
31
- process.exit(1);
27
+ console.error(`[Aromix] "${units[i].name}" failed to stop`, err);
32
28
  }
33
29
  }
34
- process.on("uncaughtException", (err) => {
35
- this.notifyError(err, "runtime").finally(() => this.gracefulShutdown("uncaughtException"));
36
- });
37
- process.on("unhandledRejection", (reason) => {
38
- const err = reason instanceof Error ? reason : new Error(String(reason));
39
- this.notifyError(err, "runtime").finally(() => this.gracefulShutdown("unhandledRejection"));
40
- });
41
- process.once("SIGINT", () => this.gracefulShutdown("SIGINT"));
42
- process.once("SIGTERM", () => this.gracefulShutdown("SIGTERM"));
30
+ state = "stopped";
43
31
  }
44
- async stop() {
45
- await this.stopAll("manual");
46
- }
47
- async notifyError(err, phase, block) {
48
- const error = err instanceof Error ? err : new Error(String(err));
49
- if (block?.error && (phase === "start" || phase === "stop")) {
50
- await block.error(error, phase);
51
- }
52
- for (const handler of this.errorHandlers) {
53
- await handler(error, phase);
32
+ async function start() {
33
+ assertIdle("start");
34
+ state = "loading";
35
+ for (const loader of configLoaders) {
36
+ await loader();
54
37
  }
55
- }
56
- async stopAll(reason) {
57
- if (this.isStopping) return true;
58
- this.isStopping = true;
59
- let allStoppedCleanly = true;
60
- for (let i = this.blocks.length - 1; i >= 0; i--) {
61
- const block = this.blocks[i];
62
- const startedAt = Date.now();
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.`);
43
+ }
63
44
  try {
64
- await block.stop?.();
45
+ await unit.start();
46
+ units.push(unit);
65
47
  } catch (err) {
66
- allStoppedCleanly = false;
67
- await this.notifyError(err, "stop", block);
48
+ await stop();
49
+ throw new Error(`[Aromix] "${unit.name}" failed to start`, { cause: err });
68
50
  }
69
51
  }
70
- return allStoppedCleanly;
71
- }
72
- async gracefulShutdown(signal) {
73
- const clean = await this.stopAll(signal);
74
- process.exit(clean ? 0 : 1);
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);
75
59
  }
76
- };
60
+ return {
61
+ load,
62
+ register,
63
+ start,
64
+ stop
65
+ };
66
+ }
77
67
 
78
- // src/common/load.env.ts
79
- import { resolve } from "path";
68
+ // src/common/LoadEnv.ts
80
69
  import { existsSync } from "fs";
81
- function loadEnv(options) {
70
+ import { resolve } from "path";
71
+ function LoadEnv(options) {
82
72
  const path = resolve(options.path ?? ".env");
83
- return {
84
- name: "Load Env",
85
- async start() {
86
- if (!existsSync(path)) {
87
- throw new Error(`[Load Env] Environment file not found: ${path}`);
88
- }
89
- process.loadEnvFile(path);
90
- if (options.schema) {
91
- const [result, issues] = options.schema.parseBase(process.env);
92
- if (issues) {
93
- throw new Error("[Load Env] Schema Validation Failed", {
94
- cause: issues
95
- });
96
- }
97
- }
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) });
98
82
  }
99
- };
83
+ values = result;
84
+ }
85
+ function env(key) {
86
+ return values[key];
87
+ }
88
+ return { env };
100
89
  }
101
90
  export {
102
- App,
103
- loadEnv
91
+ LoadEnv,
92
+ system
104
93
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aromix/core",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "The Core Package For Aromix",
5
5
  "type": "module",
6
6
  "license": "MIT",