@aromix/core 0.4.2 → 0.4.4
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 +28 -80
- package/dist/index.js +87 -106
- package/package.json +19 -10
package/dist/index.d.ts
CHANGED
|
@@ -1,84 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Server } from 'http';
|
|
3
|
-
import { Db, MongoClient } from 'mongodb';
|
|
4
|
-
import { RedisClientType, RedisClusterType, RedisClientPoolType, RedisSentinelType } from 'redis';
|
|
1
|
+
import { AxSchemaShape, AxObjectSchema } from '@aromix/validator';
|
|
5
2
|
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
-
interface Builder {
|
|
3
|
+
type Phase = 'start' | 'stop';
|
|
4
|
+
interface Block {
|
|
9
5
|
name: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
start(): void | Promise<void>;
|
|
7
|
+
stop?(): void | Promise<void>;
|
|
8
|
+
error?(err: Error, phase: Phase): void | Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
type ErrorHandler = (err: Error, phase: Phase | 'runtime') => void | Promise<void>;
|
|
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
|
+
}
|
|
25
|
+
|
|
26
|
+
type LoadEnvOptions<Shape extends AxSchemaShape> = Partial<{
|
|
17
27
|
path: string;
|
|
18
|
-
schema:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
declare function ValidateEnv<const Schema extends Record<string, AnySchema>>(config: ValidateEnvConfig<Schema>): readonly [Builder, <Key extends keyof Schema>(key: Key, fallback?: Schema[Key]["$base"]) => Schema[Key]["$base"]];
|
|
22
|
-
|
|
23
|
-
interface EffectDef {
|
|
24
|
-
name: string;
|
|
25
|
-
run(): void;
|
|
26
|
-
}
|
|
27
|
-
declare function effect(def: EffectDef): EffectDef;
|
|
28
|
-
|
|
29
|
-
interface GuardDef {
|
|
30
|
-
name: string;
|
|
31
|
-
run(): void;
|
|
32
|
-
}
|
|
33
|
-
declare function guard(def: GuardDef): GuardDef;
|
|
34
|
-
|
|
35
|
-
declare class MongoDatabase {
|
|
36
|
-
db: Db;
|
|
37
|
-
constructor();
|
|
38
|
-
/** internal attach */
|
|
39
|
-
attach(db: Db): void;
|
|
40
|
-
entity<Schema extends AnySchema>(input: MongoEntityInput<Schema>): void;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
type ClusterResult<T extends readonly string[]> = {
|
|
44
|
-
builder: Builder;
|
|
45
|
-
client: MongoClient;
|
|
46
|
-
} & {
|
|
47
|
-
[K in T[number]]: MongoDatabase;
|
|
48
|
-
};
|
|
49
|
-
interface MongoClusterInput<Databases extends readonly string[]> {
|
|
50
|
-
name: string;
|
|
51
|
-
uri: string;
|
|
52
|
-
databases: Databases;
|
|
53
|
-
onConnect?(client: MongoClient): MaybePromise<void>;
|
|
54
|
-
onDisconnect?(client: MongoClient): MaybePromise<void>;
|
|
55
|
-
onError?(err: unknown): MaybePromise<void>;
|
|
56
|
-
}
|
|
57
|
-
interface MongoEntityInput<Schema extends AnySchema> {
|
|
58
|
-
name: string;
|
|
59
|
-
model: Schema;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
declare function MongoCluster<const Databases extends readonly string[]>(options: MongoClusterInput<Databases>): ClusterResult<Databases>;
|
|
63
|
-
|
|
64
|
-
type RedisAdapter = RedisClientType | RedisClusterType | RedisClientPoolType | RedisSentinelType;
|
|
65
|
-
interface RedisInput {
|
|
66
|
-
name: string;
|
|
67
|
-
client(): RedisAdapter;
|
|
68
|
-
onConnect?(client: RedisAdapter): MaybePromise<void>;
|
|
69
|
-
onDisconnect?(client: RedisAdapter): MaybePromise<void>;
|
|
70
|
-
onError?(err: unknown): void;
|
|
71
|
-
}
|
|
72
|
-
declare class Redis {
|
|
73
|
-
constructor(input: RedisInput);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
declare class Application {
|
|
77
|
-
private state;
|
|
78
|
-
private builders;
|
|
79
|
-
register(builder: Builder): void;
|
|
80
|
-
listen(port: number): Promise<void>;
|
|
81
|
-
}
|
|
82
|
-
declare function bootstrap(): Application;
|
|
28
|
+
schema: AxObjectSchema<Shape>;
|
|
29
|
+
}>;
|
|
30
|
+
declare function loadEnv<Shape extends AxSchemaShape>(options: LoadEnvOptions<Shape>): Block;
|
|
83
31
|
|
|
84
|
-
export {
|
|
32
|
+
export { App, type Block, type ErrorHandler, type LoadEnvOptions, type Phase, loadEnv };
|
package/dist/index.js
CHANGED
|
@@ -1,123 +1,104 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.log(state);
|
|
14
|
-
console.log(process.env);
|
|
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.`);
|
|
10
|
+
}
|
|
11
|
+
if (this.blocks.some((b) => b.name === block.name)) {
|
|
12
|
+
throw new Error(`[Aromix] Block with name "${block.name}" is already registered.`);
|
|
15
13
|
}
|
|
16
|
-
|
|
17
|
-
function env(key, fallback) {
|
|
18
|
-
return process.env[key] ?? fallback;
|
|
14
|
+
this.blocks.push(block);
|
|
19
15
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// src/layer/effect.ts
|
|
24
|
-
function effect(def) {
|
|
25
|
-
return def;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/layer/guard.ts
|
|
29
|
-
function guard(def) {
|
|
30
|
-
return def;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// src/mongo/cluster.ts
|
|
34
|
-
import { MongoClient } from "mongodb";
|
|
35
|
-
|
|
36
|
-
// src/mongo/database.ts
|
|
37
|
-
var MongoDatabase = class {
|
|
38
|
-
db;
|
|
39
|
-
constructor() {
|
|
16
|
+
onError(handler) {
|
|
17
|
+
this.errorHandlers.push(handler);
|
|
40
18
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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();
|
|
26
|
+
try {
|
|
27
|
+
await block.start();
|
|
28
|
+
} catch (err) {
|
|
29
|
+
await this.notifyError(err, "start", block);
|
|
30
|
+
await this.stopAll("start-failure");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
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"));
|
|
44
43
|
}
|
|
45
|
-
|
|
44
|
+
async stop() {
|
|
45
|
+
await this.stopAll("manual");
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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);
|
|
54
|
+
}
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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();
|
|
59
63
|
try {
|
|
60
|
-
await
|
|
61
|
-
for (const name of options.databases) {
|
|
62
|
-
const db = client.db(name);
|
|
63
|
-
databases[name].attach(db);
|
|
64
|
-
}
|
|
65
|
-
await options.onConnect?.(client);
|
|
64
|
+
await block.stop?.();
|
|
66
65
|
} catch (err) {
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
allStoppedCleanly = false;
|
|
67
|
+
await this.notifyError(err, "stop", block);
|
|
69
68
|
}
|
|
70
|
-
},
|
|
71
|
-
async onShutdown() {
|
|
72
|
-
await options.onDisconnect?.(client);
|
|
73
|
-
await client.close();
|
|
74
69
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// src/redis/redis.ts
|
|
84
|
-
var Redis = class {
|
|
85
|
-
constructor(input) {
|
|
70
|
+
return allStoppedCleanly;
|
|
71
|
+
}
|
|
72
|
+
async gracefulShutdown(signal) {
|
|
73
|
+
const clean = await this.stopAll(signal);
|
|
74
|
+
process.exit(clean ? 0 : 1);
|
|
86
75
|
}
|
|
87
76
|
};
|
|
88
77
|
|
|
89
|
-
// src/
|
|
90
|
-
import {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const server = createServer();
|
|
100
|
-
for (const builder of this.builders) {
|
|
101
|
-
await builder.onListen?.(server);
|
|
102
|
-
}
|
|
103
|
-
server.listen(port);
|
|
104
|
-
server.on("close", async () => {
|
|
105
|
-
for (const builder of this.builders) {
|
|
106
|
-
await builder.onShutdown?.();
|
|
78
|
+
// src/common/load.env.ts
|
|
79
|
+
import { resolve } from "path";
|
|
80
|
+
import { existsSync } from "fs";
|
|
81
|
+
function loadEnv(options) {
|
|
82
|
+
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}`);
|
|
107
88
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
113
100
|
}
|
|
114
101
|
export {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
MongoDatabase,
|
|
118
|
-
Redis,
|
|
119
|
-
ValidateEnv,
|
|
120
|
-
bootstrap,
|
|
121
|
-
effect,
|
|
122
|
-
guard
|
|
102
|
+
App,
|
|
103
|
+
loadEnv
|
|
123
104
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aromix/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "The Core Package For Aromix",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,23 +34,22 @@
|
|
|
34
34
|
"access": "public",
|
|
35
35
|
"registry": "https://registry.npmjs.org"
|
|
36
36
|
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"dev": "tsup --watch",
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
41
|
-
},
|
|
42
37
|
"peerDependencies": {
|
|
43
|
-
"@aromix/validator": "
|
|
38
|
+
"@aromix/validator": "0.5.5",
|
|
44
39
|
"mongodb": "^7.3.0",
|
|
45
40
|
"redis": "^6.0.1"
|
|
46
41
|
},
|
|
47
42
|
"devDependencies": {
|
|
48
|
-
"@aromix/validator": "
|
|
43
|
+
"@aromix/validator": "0.5.5",
|
|
44
|
+
"@biomejs/biome": "^1.9.4",
|
|
49
45
|
"@types/node": "^22.10.2",
|
|
46
|
+
"beachball": "^2.65.5",
|
|
50
47
|
"mongodb": "^7.3.0",
|
|
51
48
|
"redis": "^6.0.1",
|
|
49
|
+
"rimraf": "^6.1.3",
|
|
52
50
|
"tsup": "^8.3.5",
|
|
53
|
-
"typescript": "^5.7.2"
|
|
51
|
+
"typescript": "^5.7.2",
|
|
52
|
+
"vitest": "^4.1.6"
|
|
54
53
|
},
|
|
55
54
|
"peerDependenciesMeta": {
|
|
56
55
|
"mongodb": {
|
|
@@ -59,5 +58,15 @@
|
|
|
59
58
|
"redis": {
|
|
60
59
|
"optional": true
|
|
61
60
|
}
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"dev": "tsup --watch",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"format": "biome format --write .",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"change": "beachball change",
|
|
69
|
+
"bump": "beachball bump",
|
|
70
|
+
"release": "beachball publish --yes --access public"
|
|
62
71
|
}
|
|
63
|
-
}
|
|
72
|
+
}
|