@aromix/core 0.4.1 → 0.4.3
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 +25 -247
- package/dist/index.js +107 -363
- package/package.json +27 -14
package/dist/index.d.ts
CHANGED
|
@@ -1,254 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Db, InsertOneOptions, InsertOneResult, BulkWriteOptions, InsertManyResult, Filter, FindOptions, UpdateFilter, UpdateOptions, UpdateResult, ReplaceOptions, DeleteOptions, DeleteResult, FindOneAndUpdateOptions, FindOneAndDeleteOptions, FindOneAndReplaceOptions, CountDocumentsOptions, DistinctOptions, AggregateOptions, AggregationCursor, BulkWriteResult, CreateIndexesOptions, DropIndexesOptions } from 'mongodb';
|
|
3
|
-
import * as http from 'http';
|
|
4
|
-
import { IncomingMessage, ServerResponse } from 'http';
|
|
1
|
+
import { AxSchemaShape, AxObjectSchema } from '@aromix/validator';
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
type Phase = 'start' | 'stop';
|
|
4
|
+
interface Block {
|
|
7
5
|
name: string;
|
|
8
|
-
|
|
6
|
+
start(): void | Promise<void>;
|
|
7
|
+
stop?(): void | Promise<void>;
|
|
8
|
+
error?(err: Error, phase: Phase): void | Promise<void>;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
readonly state: KvEntityUserInput<Schema>;
|
|
12
|
-
private adapter;
|
|
13
|
-
constructor(userProvided: KvEntityUserInput<Schema>, internal: EntityBuilder.KvAdapter);
|
|
14
|
-
get(key: string): Promise<Schema['$select']>;
|
|
15
|
-
set(key: string, value: Schema['$insert']): Promise<void>;
|
|
16
|
-
delete(key: string): Promise<void>;
|
|
17
|
-
has(key: string): Promise<boolean>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface MongoEntityUserInput<Schema extends AnySchema> {
|
|
21
|
-
name: string;
|
|
22
|
-
model: Schema;
|
|
23
|
-
}
|
|
24
|
-
declare class MongoEntity<Schema extends AnySchema> {
|
|
25
|
-
readonly states: MongoEntityUserInput<Schema>;
|
|
26
|
-
private readonly collection;
|
|
27
|
-
constructor(userInput: MongoEntityUserInput<Schema>, db: Db);
|
|
28
|
-
insertOne(doc: Schema['$insert'], options?: InsertOneOptions): Promise<InsertOneResult>;
|
|
29
|
-
insertMany(docs: Schema['$insert'][], options?: BulkWriteOptions): Promise<InsertManyResult>;
|
|
30
|
-
findOne(filter: Filter<Schema['$select']>, options?: FindOptions): Promise<Schema['$select'] | null>;
|
|
31
|
-
find(filter: Filter<Schema['$select']>, options?: FindOptions): Promise<Schema['$select'][]>;
|
|
32
|
-
updateOne(filter: Filter<Schema['$select']>, update: UpdateFilter<Schema['$update']>, options?: UpdateOptions): Promise<UpdateResult>;
|
|
33
|
-
updateMany(filter: Filter<Schema['$select']>, update: UpdateFilter<Schema['$update']>, options?: UpdateOptions): Promise<UpdateResult>;
|
|
34
|
-
replaceOne(filter: Filter<Schema['$select']>, replacement: Schema['$select'], options?: ReplaceOptions): Promise<UpdateResult>;
|
|
35
|
-
deleteOne(filter: Filter<Schema['$select']>, options?: DeleteOptions): Promise<DeleteResult>;
|
|
36
|
-
deleteMany(filter: Filter<Schema['$select']>, options?: DeleteOptions): Promise<DeleteResult>;
|
|
37
|
-
findOneAndUpdate(filter: Filter<Schema['$select']>, update: UpdateFilter<Schema['$update']>, options?: FindOneAndUpdateOptions): Promise<Schema['$select'] | null>;
|
|
38
|
-
findOneAndDelete(filter: Filter<Schema['$select']>, options?: FindOneAndDeleteOptions): Promise<Schema['$select'] | null>;
|
|
39
|
-
findOneAndReplace(filter: Filter<Schema['$select']>, replacement: Schema['$select'], options?: FindOneAndReplaceOptions): Promise<Schema['$select'] | null>;
|
|
40
|
-
countDocuments(filter: Filter<Schema['$select']>, options?: CountDocumentsOptions): Promise<number>;
|
|
41
|
-
estimatedDocumentCount(): Promise<number>;
|
|
42
|
-
distinct<Field extends keyof Schema['$select'] & string>(field: Field, filter: Filter<Schema['$select']>, options?: DistinctOptions): Promise<Array<Schema['$select'][Field]>>;
|
|
43
|
-
aggregate(pipeline: any[], options?: AggregateOptions): AggregationCursor;
|
|
44
|
-
bulkWrite(operations: any[], options?: BulkWriteOptions): Promise<BulkWriteResult>;
|
|
45
|
-
createIndex(indexSpec: any, options?: CreateIndexesOptions): Promise<string>;
|
|
46
|
-
dropIndex(indexName: string, options?: DropIndexesOptions): Promise<void>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
declare namespace EntityBuilder {
|
|
50
|
-
interface KvAdapter {
|
|
51
|
-
get(key: string): Promise<unknown>;
|
|
52
|
-
set(key: string, value: unknown): Promise<void>;
|
|
53
|
-
delete(key: string): Promise<void>;
|
|
54
|
-
has(key: string): Promise<boolean>;
|
|
55
|
-
}
|
|
56
|
-
interface KvInput {
|
|
57
|
-
transport: 'http';
|
|
58
|
-
adapter(): KvAdapter;
|
|
59
|
-
}
|
|
60
|
-
function kv(builderInput: KvInput): {
|
|
61
|
-
entity<Schema extends AnySchema>(entityInput: KvEntityUserInput<Schema>): KvEntity<Schema>;
|
|
62
|
-
};
|
|
63
|
-
interface MongoInput {
|
|
64
|
-
adapter(): Db;
|
|
65
|
-
transport: 'http';
|
|
66
|
-
}
|
|
67
|
-
function mongo(builderInput: MongoInput): {
|
|
68
|
-
entity<Schema extends AnySchema>(entityInput: MongoEntityUserInput<Schema>): MongoEntity<Schema>;
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface ComposeInput {
|
|
73
|
-
entities: Array<MongoEntity<AnySchema> | KvEntity<AnySchema>>;
|
|
74
|
-
}
|
|
75
|
-
declare function compose(input: ComposeInput): {
|
|
76
|
-
readonly descriptor: {
|
|
77
|
-
entities: any[];
|
|
78
|
-
};
|
|
79
|
-
dispatch(route: string, payload: unknown): Promise<any>;
|
|
80
|
-
};
|
|
10
|
+
type ErrorHandler = (err: Error, phase: Phase | 'runtime') => void | Promise<void>;
|
|
81
11
|
|
|
82
|
-
declare class
|
|
83
|
-
private
|
|
84
|
-
private
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
output: any;
|
|
96
|
-
} | {
|
|
97
|
-
name: string;
|
|
98
|
-
route: string;
|
|
99
|
-
input: {
|
|
100
|
-
type: string;
|
|
101
|
-
shape: {
|
|
102
|
-
key: {
|
|
103
|
-
type: string;
|
|
104
|
-
};
|
|
105
|
-
value: any;
|
|
106
|
-
};
|
|
107
|
-
};
|
|
108
|
-
output: {
|
|
109
|
-
type: string;
|
|
110
|
-
};
|
|
111
|
-
})[];
|
|
112
|
-
};
|
|
113
|
-
private buildMongoMethod;
|
|
114
|
-
mongo(entity: MongoEntity<AnySchema>): {
|
|
115
|
-
name: string;
|
|
116
|
-
platform: string;
|
|
117
|
-
methods: {
|
|
118
|
-
name: string;
|
|
119
|
-
route: string;
|
|
120
|
-
input: any;
|
|
121
|
-
output: {
|
|
122
|
-
type: string;
|
|
123
|
-
shape: {
|
|
124
|
-
acknowledged: {
|
|
125
|
-
type: string;
|
|
126
|
-
};
|
|
127
|
-
matchedCount: {
|
|
128
|
-
type: string;
|
|
129
|
-
};
|
|
130
|
-
modifiedCount: {
|
|
131
|
-
type: string;
|
|
132
|
-
};
|
|
133
|
-
upsertedCount: {
|
|
134
|
-
type: string;
|
|
135
|
-
};
|
|
136
|
-
upsertedId: {
|
|
137
|
-
type: string;
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
} | {
|
|
141
|
-
type: string;
|
|
142
|
-
shape: {
|
|
143
|
-
acknowledged: {
|
|
144
|
-
type: string;
|
|
145
|
-
};
|
|
146
|
-
insertedId: {
|
|
147
|
-
type: string;
|
|
148
|
-
};
|
|
149
|
-
insertedCount?: undefined;
|
|
150
|
-
insertedIds?: undefined;
|
|
151
|
-
matchedCount?: undefined;
|
|
152
|
-
modifiedCount?: undefined;
|
|
153
|
-
upsertedCount?: undefined;
|
|
154
|
-
upsertedId?: undefined;
|
|
155
|
-
deletedCount?: undefined;
|
|
156
|
-
};
|
|
157
|
-
items?: undefined;
|
|
158
|
-
element?: undefined;
|
|
159
|
-
} | {
|
|
160
|
-
type: string;
|
|
161
|
-
shape: {
|
|
162
|
-
acknowledged: {
|
|
163
|
-
type: string;
|
|
164
|
-
};
|
|
165
|
-
insertedCount: {
|
|
166
|
-
type: string;
|
|
167
|
-
};
|
|
168
|
-
insertedIds: {
|
|
169
|
-
type: string;
|
|
170
|
-
};
|
|
171
|
-
insertedId?: undefined;
|
|
172
|
-
matchedCount?: undefined;
|
|
173
|
-
modifiedCount?: undefined;
|
|
174
|
-
upsertedCount?: undefined;
|
|
175
|
-
upsertedId?: undefined;
|
|
176
|
-
deletedCount?: undefined;
|
|
177
|
-
};
|
|
178
|
-
items?: undefined;
|
|
179
|
-
element?: undefined;
|
|
180
|
-
} | {
|
|
181
|
-
type: string;
|
|
182
|
-
items: any[];
|
|
183
|
-
shape?: undefined;
|
|
184
|
-
element?: undefined;
|
|
185
|
-
} | {
|
|
186
|
-
type: string;
|
|
187
|
-
element: any;
|
|
188
|
-
shape?: undefined;
|
|
189
|
-
items?: undefined;
|
|
190
|
-
} | {
|
|
191
|
-
type: string;
|
|
192
|
-
shape: {
|
|
193
|
-
acknowledged: {
|
|
194
|
-
type: string;
|
|
195
|
-
};
|
|
196
|
-
matchedCount: {
|
|
197
|
-
type: string;
|
|
198
|
-
};
|
|
199
|
-
modifiedCount: {
|
|
200
|
-
type: string;
|
|
201
|
-
};
|
|
202
|
-
upsertedCount: {
|
|
203
|
-
type: string;
|
|
204
|
-
};
|
|
205
|
-
upsertedId: {
|
|
206
|
-
type: string;
|
|
207
|
-
};
|
|
208
|
-
insertedId?: undefined;
|
|
209
|
-
insertedCount?: undefined;
|
|
210
|
-
insertedIds?: undefined;
|
|
211
|
-
deletedCount?: undefined;
|
|
212
|
-
};
|
|
213
|
-
items?: undefined;
|
|
214
|
-
element?: undefined;
|
|
215
|
-
} | {
|
|
216
|
-
type: string;
|
|
217
|
-
items: any[];
|
|
218
|
-
shape?: undefined;
|
|
219
|
-
element?: undefined;
|
|
220
|
-
} | {
|
|
221
|
-
type: string;
|
|
222
|
-
shape: {
|
|
223
|
-
acknowledged: {
|
|
224
|
-
type: string;
|
|
225
|
-
};
|
|
226
|
-
deletedCount: {
|
|
227
|
-
type: string;
|
|
228
|
-
};
|
|
229
|
-
insertedId?: undefined;
|
|
230
|
-
insertedCount?: undefined;
|
|
231
|
-
insertedIds?: undefined;
|
|
232
|
-
matchedCount?: undefined;
|
|
233
|
-
modifiedCount?: undefined;
|
|
234
|
-
upsertedCount?: undefined;
|
|
235
|
-
upsertedId?: undefined;
|
|
236
|
-
};
|
|
237
|
-
items?: undefined;
|
|
238
|
-
element?: undefined;
|
|
239
|
-
} | {
|
|
240
|
-
type: string;
|
|
241
|
-
shape?: undefined;
|
|
242
|
-
items?: undefined;
|
|
243
|
-
element?: undefined;
|
|
244
|
-
};
|
|
245
|
-
}[];
|
|
246
|
-
};
|
|
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;
|
|
247
24
|
}
|
|
248
25
|
|
|
249
|
-
type
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
26
|
+
type LoadEnvOptions<Shape extends AxSchemaShape> = Partial<{
|
|
27
|
+
path: string;
|
|
28
|
+
schema: AxObjectSchema<Shape>;
|
|
29
|
+
}>;
|
|
30
|
+
declare function loadEnv<Shape extends AxSchemaShape>(options: LoadEnvOptions<Shape>): Block;
|
|
253
31
|
|
|
254
|
-
export { type
|
|
32
|
+
export { App, type Block, type ErrorHandler, type LoadEnvOptions, type Phase, loadEnv };
|
package/dist/index.js
CHANGED
|
@@ -1,380 +1,124 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return this.collection.insertOne(doc, options);
|
|
38
|
-
}
|
|
39
|
-
async insertMany(docs, options) {
|
|
40
|
-
return this.collection.insertMany(docs, options);
|
|
41
|
-
}
|
|
42
|
-
async findOne(filter, options) {
|
|
43
|
-
return this.collection.findOne(filter, options);
|
|
44
|
-
}
|
|
45
|
-
async find(filter, options) {
|
|
46
|
-
return this.collection.find(filter, options).toArray();
|
|
47
|
-
}
|
|
48
|
-
async updateOne(filter, update, options) {
|
|
49
|
-
return this.collection.updateOne(filter, update, options);
|
|
50
|
-
}
|
|
51
|
-
async updateMany(filter, update, options) {
|
|
52
|
-
return this.collection.updateMany(filter, update, options);
|
|
53
|
-
}
|
|
54
|
-
async replaceOne(filter, replacement, options) {
|
|
55
|
-
return this.collection.replaceOne(filter, replacement, options);
|
|
56
|
-
}
|
|
57
|
-
async deleteOne(filter, options) {
|
|
58
|
-
return this.collection.deleteOne(filter, options);
|
|
59
|
-
}
|
|
60
|
-
async deleteMany(filter, options) {
|
|
61
|
-
return this.collection.deleteMany(filter, options);
|
|
62
|
-
}
|
|
63
|
-
async findOneAndUpdate(filter, update, options) {
|
|
64
|
-
return this.collection.findOneAndUpdate(filter, update, options);
|
|
65
|
-
}
|
|
66
|
-
async findOneAndDelete(filter, options) {
|
|
67
|
-
return this.collection.findOneAndDelete(filter, options);
|
|
68
|
-
}
|
|
69
|
-
async findOneAndReplace(filter, replacement, options) {
|
|
70
|
-
return this.collection.findOneAndReplace(filter, replacement, options);
|
|
71
|
-
}
|
|
72
|
-
async countDocuments(filter, options) {
|
|
73
|
-
return this.collection.countDocuments(filter, options);
|
|
74
|
-
}
|
|
75
|
-
async estimatedDocumentCount() {
|
|
76
|
-
return this.collection.estimatedDocumentCount();
|
|
77
|
-
}
|
|
78
|
-
async distinct(field, filter, options) {
|
|
79
|
-
return this.collection.distinct(field, filter, options);
|
|
80
|
-
}
|
|
81
|
-
aggregate(pipeline, options) {
|
|
82
|
-
return this.collection.aggregate(pipeline, options);
|
|
83
|
-
}
|
|
84
|
-
async bulkWrite(operations, options) {
|
|
85
|
-
return this.collection.bulkWrite(operations, options);
|
|
86
|
-
}
|
|
87
|
-
async createIndex(indexSpec, options) {
|
|
88
|
-
return this.collection.createIndex(indexSpec, options);
|
|
89
|
-
}
|
|
90
|
-
async dropIndex(indexName, options) {
|
|
91
|
-
return this.collection.dropIndex(indexName, options);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// src/entity/builder.ts
|
|
96
|
-
var EntityBuilder;
|
|
97
|
-
((EntityBuilder2) => {
|
|
98
|
-
function kv(builderInput) {
|
|
99
|
-
const adapter = builderInput.adapter();
|
|
100
|
-
const result = {
|
|
101
|
-
entity(entityInput) {
|
|
102
|
-
return new KvEntity(entityInput, adapter);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
return result;
|
|
106
|
-
}
|
|
107
|
-
EntityBuilder2.kv = kv;
|
|
108
|
-
function mongo(builderInput) {
|
|
109
|
-
const db = builderInput.adapter();
|
|
110
|
-
const result = {
|
|
111
|
-
entity(entityInput) {
|
|
112
|
-
return new MongoEntity(entityInput, db);
|
|
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.`);
|
|
13
|
+
}
|
|
14
|
+
this.blocks.push(block);
|
|
15
|
+
console.debug("[Aromix] Block registered", { block: block.name, phase: "register" });
|
|
16
|
+
}
|
|
17
|
+
onError(handler) {
|
|
18
|
+
this.errorHandlers.push(handler);
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
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();
|
|
29
|
+
try {
|
|
30
|
+
await block.start();
|
|
31
|
+
console.log("[Aromix] Block started", { block: block.name, phase: "start", durationMs: Date.now() - startedAt });
|
|
32
|
+
} 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);
|
|
113
37
|
}
|
|
114
|
-
};
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
EntityBuilder2.mongo = mongo;
|
|
118
|
-
})(EntityBuilder || (EntityBuilder = {}));
|
|
119
|
-
|
|
120
|
-
// src/organize/descriptor.ts
|
|
121
|
-
var Descriptor = class {
|
|
122
|
-
isExcluded(schema, slot) {
|
|
123
|
-
const accessors = schema.state.accessors ?? {};
|
|
124
|
-
if (slot === "select") return !!accessors.hidden;
|
|
125
|
-
if (slot === "insert") return accessors.readonlyValue !== void 0 || !!accessors.readonlyFn;
|
|
126
|
-
return accessors.readonlyValue !== void 0 || !!accessors.readonlyFn || !!accessors.locked;
|
|
127
|
-
}
|
|
128
|
-
parseSchema(schema, slot) {
|
|
129
|
-
const state = schema.state;
|
|
130
|
-
if (state.type === "object") {
|
|
131
|
-
const shape = state.typeMeta.objectShape ?? {};
|
|
132
|
-
const fields = {};
|
|
133
|
-
for (const key in shape) {
|
|
134
|
-
if (this.isExcluded(shape[key], slot)) continue;
|
|
135
|
-
fields[key] = this.parseSchema(shape[key], slot);
|
|
136
|
-
}
|
|
137
|
-
return { type: "object", shape: fields };
|
|
138
|
-
}
|
|
139
|
-
if (state.type === "array") {
|
|
140
|
-
return { type: "array", element: this.parseSchema(state.typeMeta.arrayElement, slot) };
|
|
141
|
-
}
|
|
142
|
-
if (state.type === "tuple") {
|
|
143
|
-
return { type: "tuple", items: (state.typeMeta.tupleItems ?? []).map((s) => this.parseSchema(s, slot)) };
|
|
144
|
-
}
|
|
145
|
-
if (state.type === "union") {
|
|
146
|
-
return { type: "union", items: (state.typeMeta.unionItems ?? []).map((s) => this.parseSchema(s, slot)) };
|
|
147
|
-
}
|
|
148
|
-
if (state.type === "record") {
|
|
149
|
-
return { type: "record", element: this.parseSchema(state.typeMeta.recordElement, slot) };
|
|
150
|
-
}
|
|
151
|
-
if (state.type === "literals") {
|
|
152
|
-
return { type: "literals", values: state.typeMeta.literalValues };
|
|
153
|
-
}
|
|
154
|
-
return { type: state.type };
|
|
155
|
-
}
|
|
156
|
-
kv(entity) {
|
|
157
|
-
const name = entity.state.name;
|
|
158
|
-
const model = entity.state.model;
|
|
159
|
-
return {
|
|
160
|
-
name,
|
|
161
|
-
platform: "kv",
|
|
162
|
-
methods: [
|
|
163
|
-
{ name: "get", route: `${name}.kv.get`, input: { type: "string" }, output: this.parseSchema(model, "select") },
|
|
164
|
-
{ name: "set", route: `${name}.kv.set`, input: { type: "object", shape: { key: { type: "string" }, value: this.parseSchema(model, "insert") } }, output: { type: "null" } },
|
|
165
|
-
{ name: "delete", route: `${name}.kv.delete`, input: { type: "string" }, output: { type: "null" } },
|
|
166
|
-
{ name: "has", route: `${name}.kv.has`, input: { type: "string" }, output: { type: "boolean" } }
|
|
167
|
-
]
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
buildMongoMethod(model, methodName) {
|
|
171
|
-
if (methodName === "insertOne") {
|
|
172
|
-
return { input: this.parseSchema(model, "insert"), output: { type: "object", shape: { acknowledged: { type: "boolean" }, insertedId: { type: "unknown" } } } };
|
|
173
38
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
if (methodName === "replaceOne" || methodName === "findOneAndReplace") {
|
|
202
|
-
const updateResultShape = {
|
|
203
|
-
type: "object",
|
|
204
|
-
shape: { acknowledged: { type: "boolean" }, matchedCount: { type: "number" }, modifiedCount: { type: "number" }, upsertedCount: { type: "number" }, upsertedId: { type: "unknown" } }
|
|
205
|
-
};
|
|
206
|
-
return {
|
|
207
|
-
input: { type: "object", shape: { filter: this.parseSchema(model, "select"), replacement: this.parseSchema(model, "select") } },
|
|
208
|
-
output: methodName === "replaceOne" ? updateResultShape : { type: "union", items: [this.parseSchema(model, "select"), { type: "null" }] }
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
if (methodName === "deleteOne" || methodName === "deleteMany") {
|
|
212
|
-
return { input: this.parseSchema(model, "select"), output: { type: "object", shape: { acknowledged: { type: "boolean" }, deletedCount: { type: "number" } } } };
|
|
213
|
-
}
|
|
214
|
-
if (methodName === "findOneAndUpdate") {
|
|
215
|
-
return {
|
|
216
|
-
input: { type: "object", shape: { filter: this.parseSchema(model, "select"), update: this.parseSchema(model, "update") } },
|
|
217
|
-
output: { type: "union", items: [this.parseSchema(model, "select"), { type: "null" }] }
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
if (methodName === "findOneAndDelete") {
|
|
221
|
-
return { input: this.parseSchema(model, "select"), output: { type: "union", items: [this.parseSchema(model, "select"), { type: "null" }] } };
|
|
222
|
-
}
|
|
223
|
-
if (methodName === "countDocuments") {
|
|
224
|
-
return { input: this.parseSchema(model, "select"), output: { type: "number" } };
|
|
225
|
-
}
|
|
226
|
-
if (methodName === "estimatedDocumentCount") {
|
|
227
|
-
return { input: { type: "null" }, output: { type: "number" } };
|
|
228
|
-
}
|
|
229
|
-
if (methodName === "distinct") {
|
|
230
|
-
return { input: { type: "object", shape: { field: { type: "string" }, filter: this.parseSchema(model, "select") } }, output: { type: "unknown" } };
|
|
231
|
-
}
|
|
232
|
-
if (methodName === "createIndex") {
|
|
233
|
-
return { input: { type: "unknown" }, output: { type: "string" } };
|
|
234
|
-
}
|
|
235
|
-
if (methodName === "dropIndex") {
|
|
236
|
-
return { input: { type: "string" }, output: { type: "null" } };
|
|
237
|
-
}
|
|
238
|
-
return { input: { type: "unknown" }, output: { type: "unknown" } };
|
|
239
|
-
}
|
|
240
|
-
mongo(entity) {
|
|
241
|
-
const name = entity.states.name;
|
|
242
|
-
const model = entity.states.model;
|
|
243
|
-
const proto = Object.getPrototypeOf(entity);
|
|
244
|
-
const methods = [];
|
|
245
|
-
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
246
|
-
if (key === "constructor") continue;
|
|
247
|
-
const shape = this.buildMongoMethod(model, key);
|
|
248
|
-
methods.push({ name: key, route: `${name}.mongo.${key}`, input: shape.input, output: shape.output });
|
|
249
|
-
}
|
|
250
|
-
return { name, platform: "mongo", methods };
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
// src/organize/compose.ts
|
|
255
|
-
function compose(input) {
|
|
256
|
-
const descriptors = [];
|
|
257
|
-
const routes = /* @__PURE__ */ new Map();
|
|
258
|
-
const des = new Descriptor();
|
|
259
|
-
for (const entity of input.entities) {
|
|
260
|
-
if (entity instanceof MongoEntity) {
|
|
261
|
-
const descriptor = des.mongo(entity);
|
|
262
|
-
descriptors.push(descriptor);
|
|
263
|
-
for (const method of descriptor.methods) {
|
|
264
|
-
routes.set(method.route, { entity, methodName: method.name });
|
|
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 });
|
|
51
|
+
}
|
|
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 });
|
|
265
62
|
}
|
|
266
63
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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 });
|
|
272
69
|
}
|
|
273
70
|
}
|
|
274
71
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
try {
|
|
81
|
+
await block.stop?.();
|
|
82
|
+
console.log("[Aromix] Block stopped", { block: block.name, phase: "stop", durationMs: Date.now() - startedAt });
|
|
83
|
+
} 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);
|
|
283
87
|
}
|
|
284
|
-
const fn = target.entity[target.methodName];
|
|
285
|
-
return fn.call(target.entity, payload);
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// src/organize/make.ts
|
|
291
|
-
import { createServer } from "http";
|
|
292
|
-
async function toWebRequest(req) {
|
|
293
|
-
const url = `http://${req.headers.host}${req.url}`;
|
|
294
|
-
const headers = new Headers();
|
|
295
|
-
for (const key in req.headers) {
|
|
296
|
-
const value = req.headers[key];
|
|
297
|
-
if (typeof value === "string") {
|
|
298
|
-
headers.set(key, value);
|
|
299
88
|
}
|
|
300
|
-
|
|
301
|
-
headers.set(key, value.join(", "));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (req.method === "GET" || req.method === "HEAD") {
|
|
305
|
-
return new Request(url, { method: req.method, headers });
|
|
306
|
-
}
|
|
307
|
-
const chunks = [];
|
|
308
|
-
for await (const chunk of req) {
|
|
309
|
-
chunks.push(chunk);
|
|
89
|
+
return allStoppedCleanly;
|
|
310
90
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
res.statusCode = webResponse.status;
|
|
316
|
-
webResponse.headers.forEach((value, key) => {
|
|
317
|
-
res.setHeader(key, value);
|
|
318
|
-
});
|
|
319
|
-
if (webResponse.body === null) {
|
|
320
|
-
res.end();
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const reader = webResponse.body.getReader();
|
|
324
|
-
while (true) {
|
|
325
|
-
const next = await reader.read();
|
|
326
|
-
if (next.done) {
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
res.write(next.value);
|
|
330
|
-
}
|
|
331
|
-
res.end();
|
|
332
|
-
}
|
|
333
|
-
async function handle(request, composed) {
|
|
334
|
-
const url = new URL(request.url);
|
|
335
|
-
if (url.pathname === "/meta") {
|
|
336
|
-
return Response.json(composed.descriptor);
|
|
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);
|
|
337
95
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} catch {
|
|
346
|
-
payload = void 0;
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
const data = await composed.dispatch(route, payload);
|
|
350
|
-
return Response.json({ data, errors: null });
|
|
351
|
-
} catch (error) {
|
|
352
|
-
let message = "unknown error";
|
|
353
|
-
if (error instanceof Error) {
|
|
354
|
-
message = error.message;
|
|
355
|
-
}
|
|
356
|
-
return Response.json({ data: null, errors: message }, { status: 400 });
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
function make(composed) {
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/common/load.env.ts
|
|
99
|
+
import { resolve } from "path";
|
|
100
|
+
import { existsSync } from "fs";
|
|
101
|
+
function loadEnv(options) {
|
|
102
|
+
const path = resolve(options.path ?? ".env");
|
|
360
103
|
return {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
+
}
|
|
370
118
|
}
|
|
371
119
|
};
|
|
372
120
|
}
|
|
373
121
|
export {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
KvEntity,
|
|
377
|
-
MongoEntity,
|
|
378
|
-
compose,
|
|
379
|
-
make
|
|
122
|
+
App,
|
|
123
|
+
loadEnv
|
|
380
124
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aromix/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "The Core Package For Aromix",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
"core",
|
|
19
19
|
"server",
|
|
20
20
|
"framework",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"node"
|
|
21
|
+
"node",
|
|
22
|
+
"rpc"
|
|
24
23
|
],
|
|
25
24
|
"files": [
|
|
26
25
|
"dist"
|
|
@@ -35,25 +34,39 @@
|
|
|
35
34
|
"access": "public",
|
|
36
35
|
"registry": "https://registry.npmjs.org"
|
|
37
36
|
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsup",
|
|
40
|
-
"dev": "tsup --watch",
|
|
41
|
-
"typecheck": "tsc --noEmit"
|
|
42
|
-
},
|
|
43
37
|
"peerDependencies": {
|
|
44
|
-
"@aromix/validator": "
|
|
45
|
-
"mongodb": "^7.3.0"
|
|
38
|
+
"@aromix/validator": "0.5.5",
|
|
39
|
+
"mongodb": "^7.3.0",
|
|
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",
|
|
48
|
+
"redis": "^6.0.1",
|
|
49
|
+
"rimraf": "^6.1.3",
|
|
51
50
|
"tsup": "^8.3.5",
|
|
52
|
-
"typescript": "^5.7.2"
|
|
51
|
+
"typescript": "^5.7.2",
|
|
52
|
+
"vitest": "^4.1.6"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"mongodb": {
|
|
56
56
|
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"redis": {
|
|
59
|
+
"optional": true
|
|
57
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"
|
|
58
71
|
}
|
|
59
|
-
}
|
|
72
|
+
}
|