milkio 0.1.2 → 0.2.0

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