milkio 0.1.2 → 0.2.1

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/kernel/milkio.ts CHANGED
@@ -1,115 +1,124 @@
1
- /* eslint-disable no-console, @typescript-eslint/no-invalid-void-type, @typescript-eslint/await-thenable, @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any */
2
- import { type MiddlewareOptions, _middlewares, MiddlewareEvent } from "./middleware"
3
- import schema from "../../../generated/api-schema"
4
- import type { Context } from "../../../src/context"
5
- import { failCode } from "../../../src/fail-code"
6
- import type { MilkioContext } from "./context"
7
- import { headerToPlainObject } from "../utils/header-to-plain-object"
8
- import { type Mixin, type ExecuteId, type Fail, type FailEnumerates, loggerPushTags, loggerSubmit, runtime, TSON, type Logger, useLogger, reject } from ".."
9
- import { hanldeCatchError } from "../utils/handle-catch-error"
10
- import { createUlid } from "../utils/create-ulid"
11
- import { _validate } from "./validate"
12
- import { exit } from "node:process"
1
+ import { type MiddlewareOptions, _middlewares, MiddlewareEvent } from "./middleware";
2
+ import schema from "../../../generated/api-schema";
3
+ import type { Context } from "../../../src/context";
4
+ import { failCode } from "../../../src/fail-code";
5
+ import type { MilkioContext } from "./context";
6
+ import { headerToPlainObject } from "../utils/header-to-plain-object";
7
+ import { type Mixin, type ExecuteId, type Fail, type FailEnumerates, loggerPushTags, loggerSubmit, runtime, TSON, type Logger, useLogger, reject } from "..";
8
+ import { hanldeCatchError } from "../utils/handle-catch-error";
9
+ import { createUlid } from "../utils/create-ulid";
10
+ import { _validate } from "./validate";
13
11
 
14
12
  export type MilkioAppOptions = {
15
- /**
16
- * bootstraps
17
- * @description
18
- * When Milkio is launched, all methods in this array will run **in parallel**.
19
- */
20
- bootstraps?: () => Array</* This type is long, and its intention is to prevent someone from forgetting to add parentheses when adding bootstraps. Therefore, it allows all types except methods */ Promise<unknown> | void | string | number | boolean | null | undefined | Record<string | number | symbol, unknown> | Array<unknown>>;
21
- /**
22
- * middlewares
23
- * @description
24
- * When Milkio is launched, the closer it is to the front of the array, the more it is on the outer layer of the "onion".
25
- */
26
- middlewares?: () => Array<MiddlewareOptions>;
27
- /**
28
- * maxRunningTime (minutes)
29
- * @description
30
- * When the function runs for a long time, it is possible that the memory will continuously expand (not necessarily due to memory leaks, but also possibly due to having a large number of routes).
31
- * Set the maximum running time (in minutes). When milkio's running time reaches this value, terminate the process and automatically restart it from outside (K8S or other means).
32
- */
33
- enableMaxRunningTimeoutLimit?: number | null | undefined;
13
+ /**
14
+ * bootstraps
15
+ * @description
16
+ * When Milkio is launched, all methods in this array will run **in parallel**.
17
+ */
18
+ bootstraps?: () => Array<
19
+ /* This type is long, and its intention is to prevent someone from forgetting to add parentheses when adding bootstraps. Therefore, it allows all types except methods */ Promise<unknown> | void | string | number | boolean | null | undefined | Record<string | number | symbol, unknown> | Array<unknown>
20
+ >;
21
+ /**
22
+ * middlewares
23
+ * @description
24
+ * When Milkio is launched, the closer it is to the front of the array, the more it is on the outer layer of the "onion".
25
+ */
26
+ middlewares?: () => Array<MiddlewareOptions>;
27
+ /**
28
+ * maxRunningTime (minutes)
29
+ * @description
30
+ * When the function runs for a long time, it is possible that the memory will continuously expand (not necessarily due to memory leaks, but also possibly due to having a large number of routes).
31
+ * Set the maximum running time (in minutes). When milkio's running time reaches this value, terminate the process and automatically restart it from outside (K8S or other means).
32
+ */
33
+ enableMaxRunningTimeoutLimit?: number | null | undefined;
34
34
  };
35
35
 
36
36
  export async function createMilkioApp(MilkioAppOptions: MilkioAppOptions = {}) {
37
- if (MilkioAppOptions.enableMaxRunningTimeoutLimit && MilkioAppOptions.enableMaxRunningTimeoutLimit >= 1) {
38
- setTimeout(() => {
39
- throw new Error('Milkio reached the limit of "maxRunningTimeout" in the options and automatically exited.')
40
- }, MilkioAppOptions.enableMaxRunningTimeoutLimit * 60 * 1000)
41
- runtime.maxRunningTimeout.enable = true
42
- }
43
-
44
- const MilkioApp = {
45
- execute: _execute,
46
- executeToJson: _executeToJson,
47
- _executeCore,
48
- _executeCoreToJson,
49
- randParams: _randParams
50
- }
51
-
52
- if (MilkioAppOptions.bootstraps) {
53
- await Promise.all(MilkioAppOptions.bootstraps())
54
- }
55
-
56
- if (MilkioAppOptions.middlewares) {
57
- MiddlewareEvent.define("bootstrap", (a, b) => a.index - b.index)
58
- MiddlewareEvent.define("beforeExecute", (a, b) => a.index - b.index)
59
- MiddlewareEvent.define("afterExecute", (a, b) => b.index - a.index)
60
- MiddlewareEvent.define("afterHTTPRequest", (a, b) => a.index - b.index)
61
- MiddlewareEvent.define("beforeHTTPResponse", (a, b) => b.index - a.index)
62
-
63
- const middlewares = MilkioAppOptions.middlewares()
64
-
65
- for (let index = 0; index < middlewares.length; index++) {
66
- const middlewareOptions = middlewares[index]
67
- for (const name in middlewareOptions) {
68
- let middleware = _middlewares.get(name)
69
- if (middleware === undefined) {
70
- middleware = []
71
- _middlewares.set(name, middleware)
72
- }
73
- const id = createUlid()
74
- middleware.push({ id, index, middleware: middlewareOptions[name] })
75
- }
76
- }
77
- MiddlewareEvent._sort()
78
-
79
- await MiddlewareEvent.handle("bootstrap", [MilkioApp])
80
- }
81
-
82
- return MilkioApp
37
+ if (MilkioAppOptions.enableMaxRunningTimeoutLimit && MilkioAppOptions.enableMaxRunningTimeoutLimit >= 1) {
38
+ setTimeout(
39
+ () => {
40
+ throw new Error('Milkio reached the limit of "maxRunningTimeout" in the options and automatically exited.');
41
+ },
42
+ MilkioAppOptions.enableMaxRunningTimeoutLimit * 60 * 1000,
43
+ );
44
+ runtime.maxRunningTimeout.enable = true;
45
+ }
46
+
47
+ const MilkioApp = {
48
+ execute: _execute,
49
+ executeToJson: _executeToJson,
50
+ _executeCore,
51
+ _executeCoreToJson,
52
+ randParams: _randParams,
53
+ };
54
+
55
+ if (MilkioAppOptions.bootstraps) {
56
+ await Promise.all(MilkioAppOptions.bootstraps());
57
+ }
58
+
59
+ if (MilkioAppOptions.middlewares) {
60
+ MiddlewareEvent.define("bootstrap", (a, b) => a.index - b.index);
61
+ MiddlewareEvent.define("beforeExecute", (a, b) => a.index - b.index);
62
+ MiddlewareEvent.define("afterExecute", (a, b) => b.index - a.index);
63
+ MiddlewareEvent.define("afterHttpRequest", (a, b) => a.index - b.index);
64
+ MiddlewareEvent.define("beforeHttpResponse", (a, b) => b.index - a.index);
65
+ MiddlewareEvent.define("httpNotFound", (a, b) => a.index - b.index);
66
+
67
+ const middlewares = MilkioAppOptions.middlewares();
68
+
69
+ for (let index = 0; index < middlewares.length; index++) {
70
+ const middlewareOptions = middlewares[index];
71
+ for (const name in middlewareOptions) {
72
+ let middleware = _middlewares.get(name);
73
+ if (middleware === undefined) {
74
+ middleware = [];
75
+ _middlewares.set(name, middleware);
76
+ }
77
+ const id = createUlid();
78
+ middleware.push({ id, index, middleware: middlewareOptions[name] });
79
+ }
80
+ }
81
+ MiddlewareEvent._sort();
82
+
83
+ await MiddlewareEvent.handle("bootstrap", [MilkioApp]);
84
+ }
85
+
86
+ return MilkioApp;
83
87
  }
84
88
 
85
- async function _execute<Path extends keyof (typeof schema)["apiMethodsTypeSchema"], Result extends Awaited<ReturnType<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>>>(path: Path, params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string, headersInit: Record<string, string> | Headers = {}, options?: ExecuteOptions): Promise<ExecuteResult<Result>> {
86
- const executeId = (options?.executeId ?? createUlid()) as ExecuteId
87
- const logger = useLogger(executeId)
88
- runtime.execute.executeIds.add(executeId)
89
-
90
- loggerPushTags(executeId, {
91
- from: "execute",
92
- executeId,
93
- params,
94
- path
95
- })
96
-
97
- const result: any = await _executeCore(path, params, headersInit, {
98
- ...options,
99
- executeId,
100
- logger,
101
- onAfterHeaders: (headers) => {
102
- loggerPushTags(executeId, {
103
- headers: headerToPlainObject(headers)
104
- })
105
- }
106
- })
107
-
108
- loggerPushTags(executeId, { result })
109
- await loggerSubmit(executeId)
110
- runtime.execute.executeIds.delete(executeId)
111
-
112
- return result
89
+ async function _execute<Path extends keyof (typeof schema)["apiMethodsTypeSchema"], Result extends Awaited<ReturnType<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>>>(
90
+ path: Path,
91
+ params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string,
92
+ headersInit: Record<string, string> | Headers = {},
93
+ options?: ExecuteOptions,
94
+ ): Promise<ExecuteResult<Result>> {
95
+ const executeId = (options?.executeId ?? createUlid()) as ExecuteId;
96
+ const logger = useLogger(executeId);
97
+ runtime.execute.executeIds.add(executeId);
98
+
99
+ loggerPushTags(executeId, {
100
+ from: "execute",
101
+ executeId,
102
+ params,
103
+ path,
104
+ });
105
+
106
+ const result: any = await _executeCore(path, params, headersInit, {
107
+ ...options,
108
+ executeId,
109
+ logger,
110
+ onAfterHeaders: (headers) => {
111
+ loggerPushTags(executeId, {
112
+ headers: headerToPlainObject(headers),
113
+ });
114
+ },
115
+ });
116
+
117
+ loggerPushTags(executeId, { result });
118
+ await loggerSubmit(executeId);
119
+ runtime.execute.executeIds.delete(executeId);
120
+
121
+ return result;
113
122
  }
114
123
 
115
124
  /**
@@ -117,146 +126,162 @@ async function _execute<Path extends keyof (typeof schema)["apiMethodsTypeSchema
117
126
  * It only does the most basic thing internally, which is calling the API. The external handling of functions such as making executeId, logging, middleware, etc., are all handled externally.
118
127
  * Both execute and httpServer essentially call executeCore.
119
128
  */
120
- async function _executeCore<Path extends keyof (typeof schema)["apiMethodsTypeSchema"], Result extends Awaited<ReturnType<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>>>(path: Path, params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string, headersInit: Record<string, string> | Headers = {}, options: ExecuteCoreOptions): Promise<ExecuteResult<Result>> {
121
- const executeId = options.executeId as ExecuteId
122
-
123
- params = TSON.decode(params)
124
-
125
- if (!(path in schema.apiMethodsSchema)) {
126
- const result = {
127
- executeId,
128
- success: false,
129
- fail: {
130
- code: "NOT_FOUND",
131
- message: failCode.NOT_FOUND(),
132
- data: undefined
133
- }
134
- } satisfies ExecuteResult<Result>
135
-
136
- return result
137
- }
138
-
139
- let headers: Headers
140
- if (!(headersInit instanceof Headers)) {
141
- // @ts-ignore
142
- headers = new Headers({
143
- ...headersInit
144
- })
145
- } else {
146
- headers = headersInit
147
- }
148
-
149
- if (options?.onAfterHeaders) {
150
- await options.onAfterHeaders(headers)
151
- }
152
-
153
- const context: Context = {
154
- executeId,
155
- path,
156
- headers,
157
- logger: options.logger,
158
- detail: options?.detail ?? {}
159
- }
160
-
161
- let result: { value: Result }
162
- try {
163
- // before execute middleware
164
- await MiddlewareEvent.handle("beforeExecute", [context])
165
-
166
- let fn: any
167
- // check type
168
- // @ts-ignore
169
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
170
- try { fn = await schema.apiValidator.validate[path]() } catch (error) { throw reject('BUSINESS_FAIL', 'This is the new API, which takes effect after restarting the server or saving any changes.') }
171
-
172
- params = _validate(await fn.validateParams(params))
173
-
174
- // execute api
175
- let api: any
176
- if (apis.has(path)) api = apis.get(path)
177
- else {
178
- // @ts-ignore
179
- api = schema.apiMethodsSchema[path]()
180
- apis.set(path, api)
181
- }
182
- const apiModuleAwaited = await api.module
183
-
184
- const apiMethod = apiModuleAwaited.api.action
185
-
186
- // @ts-ignore
187
- result = { value: await apiMethod(params, context) }
188
-
189
- // after execute middleware
190
- await MiddlewareEvent.handle("afterExecute", [context, result])
191
- } catch (error: any) {
192
- const errorResult = hanldeCatchError(error, executeId)
193
-
194
- return errorResult
195
- }
196
-
197
- return {
198
- executeId,
199
- success: true,
200
- data: result.value
201
- }
129
+ async function _executeCore<Path extends keyof (typeof schema)["apiMethodsTypeSchema"], Result extends Awaited<ReturnType<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>>>(
130
+ path: Path,
131
+ params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string,
132
+ headersInit: Record<string, string> | Headers = {},
133
+ options: ExecuteCoreOptions,
134
+ ): Promise<ExecuteResult<Result>> {
135
+ const executeId = options.executeId as ExecuteId;
136
+
137
+ params = TSON.decode(params);
138
+
139
+ if (!(path in schema.apiMethodsSchema)) {
140
+ const result = {
141
+ executeId,
142
+ success: false,
143
+ fail: {
144
+ code: "NOT_FOUND",
145
+ message: failCode.NOT_FOUND(),
146
+ data: undefined,
147
+ },
148
+ } satisfies ExecuteResult<Result>;
149
+
150
+ return result;
151
+ }
152
+
153
+ let headers: Headers;
154
+ if (!(headersInit instanceof Headers)) {
155
+ // @ts-ignore
156
+ headers = new Headers({
157
+ ...headersInit,
158
+ });
159
+ } else {
160
+ headers = headersInit;
161
+ }
162
+
163
+ if (options?.onAfterHeaders) {
164
+ await options.onAfterHeaders(headers);
165
+ }
166
+
167
+ const context: Context = {
168
+ executeId,
169
+ path,
170
+ headers,
171
+ logger: options.logger,
172
+ detail: options?.detail ?? {},
173
+ };
174
+
175
+ let result: { value: Result };
176
+ try {
177
+ // before execute middleware
178
+ await MiddlewareEvent.handle("beforeExecute", [context]);
179
+
180
+ let fn: any;
181
+ // check type
182
+ // @ts-ignore
183
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
184
+ try {
185
+ fn = await schema.apiValidator.validate[path]();
186
+ } catch (error) {
187
+ throw reject("BUSINESS_FAIL", "This is the new API, which takes effect after restarting the server or saving any changes. It will be fixed in the future.");
188
+ }
189
+
190
+ params = _validate(await fn.validateParams(params));
191
+
192
+ // execute api
193
+ let api: any;
194
+ if (apis.has(path)) api = apis.get(path);
195
+ else {
196
+ // @ts-ignore
197
+ api = schema.apiMethodsSchema[path]();
198
+ apis.set(path, api);
199
+ }
200
+ const apiModuleAwaited = await api.module;
201
+
202
+ const apiMethod = apiModuleAwaited.api.action;
203
+
204
+ // @ts-ignore
205
+ result = { value: await apiMethod(params, context) };
206
+
207
+ // after execute middleware
208
+ await MiddlewareEvent.handle("afterExecute", [context, result]);
209
+ } catch (error: any) {
210
+ const errorResult = hanldeCatchError(error, executeId);
211
+
212
+ return errorResult;
213
+ }
214
+
215
+ return {
216
+ executeId,
217
+ success: true,
218
+ data: result.value,
219
+ };
202
220
  }
203
221
 
204
222
  async function _executeToJson<Path extends keyof (typeof schema)["apiMethodsTypeSchema"]>(path: Path, params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string, headersInit: Record<string, string> | Headers = {}, options?: ExecuteOptions): Promise<string> {
205
- const resultsRaw = await _execute(path, params, headersInit, options)
206
- let fn: any
207
- try { fn = await schema.apiValidator.validate[path]() } catch (error) { throw reject('BUSINESS_FAIL', 'This is the new API, which takes effect after restarting the server or saving any changes.') }
208
- const results = await fn.validateResults(TSON.encode(resultsRaw))
209
- return results
223
+ const resultsRaw = await _execute(path, params, headersInit, options);
224
+ let fn: any;
225
+ try {
226
+ fn = await schema.apiValidator.validate[path]();
227
+ } catch (error) {
228
+ throw reject("BUSINESS_FAIL", "This is the new API, which takes effect after restarting the server or saving any changes. It will be fixed in the future.");
229
+ }
230
+ const results = await fn.validateResults(TSON.encode(resultsRaw));
231
+ return results;
210
232
  }
211
233
 
212
234
  async function _executeCoreToJson<Path extends keyof (typeof schema)["apiMethodsTypeSchema"]>(path: Path, params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string, headersInit: Record<string, string> | Headers = {}, options: ExecuteCoreOptions): Promise<string> {
213
- const resultsRaw = await _executeCore(path, params, headersInit, options)
214
- let fn: any
215
- try { fn = await schema.apiValidator.validate[path]() } catch (error) { throw reject('BUSINESS_FAIL', 'This is the new API, which takes effect after restarting the server or saving any changes.') }
216
- const results = await fn.validateResults(TSON.encode(resultsRaw))
217
- return results
235
+ const resultsRaw = await _executeCore(path, params, headersInit, options);
236
+ let fn: any;
237
+ try {
238
+ fn = await schema.apiValidator.validate[path]();
239
+ } catch (error) {
240
+ throw reject("BUSINESS_FAIL", "This is the new API, which takes effect after restarting the server or saving any changes. It will be fixed in the future.");
241
+ }
242
+ const results = await fn.validateResults(TSON.encode(resultsRaw));
243
+ return results;
218
244
  }
219
245
 
220
-
221
- export async function _randParams<Path extends keyof (typeof schema)["apiMethodsTypeSchema"]>(path: Path): Promise<Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]['api']['action']>[0]> {
222
- return await (await schema.apiValidator.validate[path]()).randParams()
246
+ export async function _randParams<Path extends keyof (typeof schema)["apiMethodsTypeSchema"]>(path: Path): Promise<Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0]> {
247
+ return await (await schema.apiValidator.validate[path]()).randParams();
223
248
  }
224
249
 
225
- const apis = new Map<string, any>()
250
+ const apis = new Map<string, any>();
226
251
 
227
252
  export type ExecuteResult<Result> = ExecuteResultSuccess<Result> | ExecuteResultFail;
228
253
 
229
254
  export type ExecuteResultSuccess<Result> = {
230
- executeId: ExecuteId;
231
- success: true;
232
- data: Result;
255
+ executeId: ExecuteId;
256
+ success: true;
257
+ data: Result;
233
258
  };
234
259
 
235
260
  export type ExecuteResultFail<FailT extends Fail<keyof FailEnumerates> = Fail<keyof FailEnumerates>> = {
236
- executeId: ExecuteId;
237
- success: false;
238
- fail: FailT;
261
+ executeId: ExecuteId;
262
+ success: false;
263
+ fail: FailT;
239
264
  };
240
265
 
241
266
  export type ExecuteOptions = {
242
- /**
243
- * The executeId of the request
244
- * executeId may be generated by the serverless provider, if not, a random string will be generated instead
245
- */
246
- executeId?: string;
247
- /**
248
- * Additional information about the request
249
- * These are usually only fully implemented when called by an HTTP server
250
- * During testing or when calling between microservices, some or all of the values may be undefined
251
- */
252
- detail?: MilkioContext["detail"];
267
+ /**
268
+ * The executeId of the request
269
+ * executeId may be generated by the serverless provider, if not, a random string will be generated instead
270
+ */
271
+ executeId?: string;
272
+ /**
273
+ * Additional information about the request
274
+ * These are usually only fully implemented when called by an Http server
275
+ * During testing or when calling between microservices, some or all of the values may be undefined
276
+ */
277
+ detail?: MilkioContext["detail"];
253
278
  };
254
279
 
255
280
  export type ExecuteCoreOptions = Mixin<
256
- ExecuteOptions,
257
- {
258
- executeId: string;
259
- logger: Logger;
260
- onAfterHeaders?: (headers: Headers) => void | Promise<void>;
261
- }
262
- >;
281
+ ExecuteOptions,
282
+ {
283
+ executeId: string;
284
+ logger: Logger;
285
+ onAfterHeaders?: (headers: Headers) => void | Promise<void>;
286
+ }
287
+ >;
package/kernel/runtime.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { type ExecuteId } from ".."
1
+ import type { ExecuteId } from "..";
2
2
 
3
3
  export const runtime = {
4
- execute: {
5
- executeIds: new Set<ExecuteId>()
6
- },
7
- maxRunningTimeout: {
8
- enable: false,
9
- expectedEndedAt: 0
10
- }
11
- }
4
+ execute: {
5
+ executeIds: new Set<ExecuteId>(),
6
+ },
7
+ maxRunningTimeout: {
8
+ enable: false,
9
+ expectedEndedAt: 0,
10
+ },
11
+ };
@@ -1,15 +1,13 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- import type { IValidation } from "typia"
4
- import { reject } from "../kernel/fail"
1
+ import type { IValidation } from "typia";
2
+ import { reject } from "../kernel/fail";
5
3
 
6
4
  export function _validate(validator: IValidation.IFailure | IValidation.ISuccess): any {
7
- if (validator.success) return validator.data
8
- const error = validator.errors[0]
5
+ if (validator.success) return validator.data;
6
+ const error = validator.errors[0];
9
7
 
10
- throw reject("TYPE_SAFE_ERROR", {
11
- path: error.path,
12
- expected: error.expected,
13
- value: error.value
14
- })
8
+ throw reject("TYPE_SAFE_ERROR", {
9
+ path: error.path,
10
+ expected: error.expected,
11
+ value: error.value,
12
+ });
15
13
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "milkio",
3
3
  "type": "module",
4
4
  "module": "index.ts",
5
- "version": "0.1.2",
5
+ "version": "0.2.1",
6
6
  "peerDependencies": {
7
7
  "typescript": "^5.4.2"
8
8
  },