milkio 0.2.12 → 0.2.14
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/defines/define-api.ts +1 -4
- package/defines/define-http-handler.ts +89 -24
- package/kernel/execute.ts +146 -0
- package/kernel/logger.ts +6 -6
- package/kernel/milkio.ts +4 -205
- package/package.json +1 -1
- package/types.ts +46 -5
- package/utils/handle-catch-error.ts +1 -1
package/defines/define-api.ts
CHANGED
|
@@ -2,10 +2,7 @@ import type { Meta } from "../../../src/meta";
|
|
|
2
2
|
import type { Context } from "../../../src/context";
|
|
3
3
|
|
|
4
4
|
export function defineApi<ApiT extends Api>(api: ApiT): ApiT & { isApi: true } {
|
|
5
|
-
return {
|
|
6
|
-
...api,
|
|
7
|
-
isApi: true,
|
|
8
|
-
};
|
|
5
|
+
return { ...api, isApi: true };
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
export type Api = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { loggerPushTags, loggerSubmit, useLogger, runtime, MiddlewareEvent } from "..";
|
|
1
|
+
import { loggerPushTags, loggerSubmit, useLogger, runtime, MiddlewareEvent, reject } from "..";
|
|
2
2
|
import type { ExecuteId, MilkioApp, Mixin } from "..";
|
|
3
|
-
import {
|
|
3
|
+
import { handleCatchError } from "../utils/handle-catch-error";
|
|
4
4
|
import { routerHandler } from "../../../src/router";
|
|
5
5
|
import schema from "../../../generated/api-schema";
|
|
6
6
|
import { failCode } from "../../../src/fail-code";
|
|
@@ -18,6 +18,7 @@ export type ExecuteHttpServerOptions = {
|
|
|
18
18
|
* @returns
|
|
19
19
|
*/
|
|
20
20
|
executeIdGenerator?: (request: Request) => string | Promise<string>;
|
|
21
|
+
getRealIp?: (request: Request) => string;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
export function defineHttpHandler(app: MilkioApp, options: ExecuteHttpServerOptions = {}) {
|
|
@@ -26,7 +27,7 @@ export function defineHttpHandler(app: MilkioApp, options: ExecuteHttpServerOpti
|
|
|
26
27
|
const executeId = (options?.executeIdGenerator ? await options.executeIdGenerator(request.request) : createUlid()) as ExecuteId;
|
|
27
28
|
runtime.execute.executeIds.add(executeId);
|
|
28
29
|
const logger = useLogger(executeId);
|
|
29
|
-
const ip = (request.request.headers.get("
|
|
30
|
+
const ip = options.getRealIp ? options.getRealIp(request.request) : (request.request.headers.get("X-Forwarded-For") as string | undefined)?.split(",")[0] ?? "0.0.0.0";
|
|
30
31
|
const headers = request.request.headers;
|
|
31
32
|
|
|
32
33
|
loggerPushTags(executeId, {
|
|
@@ -121,13 +122,13 @@ export function defineHttpHandler(app: MilkioApp, options: ExecuteHttpServerOpti
|
|
|
121
122
|
// execute api
|
|
122
123
|
// after request middleware
|
|
123
124
|
await MiddlewareEvent.handle("afterHttpRequest", [headers, detail]);
|
|
125
|
+
const mode = headers.get("Accept") === "text/event-stream" ? "stream" : "execute";
|
|
124
126
|
|
|
125
127
|
const rawbody = await request.request.text();
|
|
126
128
|
loggerPushTags(executeId, {
|
|
127
129
|
body: rawbody || "no body",
|
|
128
130
|
});
|
|
129
131
|
|
|
130
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
132
|
let params: any;
|
|
132
133
|
if (rawbody === "") {
|
|
133
134
|
params = undefined;
|
|
@@ -145,29 +146,94 @@ export function defineHttpHandler(app: MilkioApp, options: ExecuteHttpServerOpti
|
|
|
145
146
|
params,
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
// @ts-ignore
|
|
150
|
-
const result = await app._executeCoreToJson(pathstr, params, headers, {
|
|
151
|
-
executeId,
|
|
152
|
-
logger,
|
|
153
|
-
detail,
|
|
154
|
-
});
|
|
149
|
+
const resultsRaw = await app._call(mode, pathstr, params, headers, { executeId, logger, detail });
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
let fn: any;
|
|
152
|
+
try {
|
|
153
|
+
fn = await schema.apiValidator.validate[pathstr]();
|
|
154
|
+
} catch (error) {
|
|
155
|
+
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.");
|
|
156
|
+
}
|
|
159
157
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
if (mode === "execute") {
|
|
159
|
+
const result: string = await fn.validateResults(TSON.encode(resultsRaw.$result));
|
|
160
|
+
if (!detail.response.body) detail.response.body = result;
|
|
161
|
+
|
|
162
|
+
// before response middleware
|
|
163
|
+
const middlewareResponse = {
|
|
164
|
+
value: detail.response.body,
|
|
165
|
+
};
|
|
166
|
+
await MiddlewareEvent.handle("beforeHttpResponse", [middlewareResponse, detail]);
|
|
165
167
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
if (!detail.response.headers["Content-Type"]) detail.response.headers["Content-Type"] = "application/json";
|
|
169
|
+
if (!detail.response.headers["Cache-Control"]) detail.response.headers["Cache-Control"] = "no-cache";
|
|
170
|
+
if (!detail.response.body) detail.response.body = middlewareResponse.value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (mode === "stream") {
|
|
174
|
+
const generator = (resultsRaw as any).$generator as AsyncGenerator;
|
|
175
|
+
let stream: ReadableStream;
|
|
176
|
+
let control: ReadableStreamDirectController | ReadableStreamDefaultController;
|
|
177
|
+
// SSE has a default timeout, which helps prevent memory leaks, especially when you are writing code with a while (true) loop
|
|
178
|
+
|
|
179
|
+
if (global?.Bun) {
|
|
180
|
+
// bun
|
|
181
|
+
stream = new ReadableStream({
|
|
182
|
+
type: "direct",
|
|
183
|
+
async pull(controller: ReadableStreamDirectController) {
|
|
184
|
+
control = controller;
|
|
185
|
+
try {
|
|
186
|
+
for await (const value of generator) {
|
|
187
|
+
if (!request.request.signal.aborted) {
|
|
188
|
+
const result: string = JSON.stringify(TSON.encode(value));
|
|
189
|
+
controller.write(`data:${result}\n\n`);
|
|
190
|
+
} else {
|
|
191
|
+
generator.return(undefined);
|
|
192
|
+
controller.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
controller.close();
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
controller.close();
|
|
200
|
+
},
|
|
201
|
+
cancel() {
|
|
202
|
+
control.close();
|
|
203
|
+
},
|
|
204
|
+
} as unknown as UnderlyingByteSource);
|
|
205
|
+
} else {
|
|
206
|
+
// node.js or others
|
|
207
|
+
stream = new ReadableStream({
|
|
208
|
+
async pull(controller) {
|
|
209
|
+
control = controller;
|
|
210
|
+
try {
|
|
211
|
+
for await (const value of generator) {
|
|
212
|
+
if (!request.request.signal.aborted) {
|
|
213
|
+
const result: string = JSON.stringify(TSON.encode(value));
|
|
214
|
+
controller.enqueue(`data:${result}\n\n`);
|
|
215
|
+
} else {
|
|
216
|
+
generator.return(undefined);
|
|
217
|
+
controller.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
controller.close();
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
controller.close();
|
|
225
|
+
},
|
|
226
|
+
cancel() {
|
|
227
|
+
control.close();
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
detail.response.headers["Content-Type"] = "text/event-stream";
|
|
232
|
+
detail.response.headers["Cache-Control"] = "no-cache";
|
|
233
|
+
detail.response.body = stream;
|
|
234
|
+
}
|
|
169
235
|
} catch (error) {
|
|
170
|
-
const result =
|
|
236
|
+
const result = handleCatchError(error, executeId);
|
|
171
237
|
if (!response.headers["Content-Type"]) response.headers["Content-Type"] = "application/json";
|
|
172
238
|
if (!response.headers["Cache-Control"]) response.headers["Cache-Control"] = "no-cache";
|
|
173
239
|
if (!response.body) response.body = TSON.stringify(result);
|
|
@@ -176,7 +242,6 @@ export function defineHttpHandler(app: MilkioApp, options: ExecuteHttpServerOpti
|
|
|
176
242
|
loggerPushTags(executeId, {
|
|
177
243
|
status: response.status,
|
|
178
244
|
responseHeaders: response.headers,
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
180
245
|
body: response.body?.toString() ?? "",
|
|
181
246
|
timeout: new Date().getTime(),
|
|
182
247
|
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Context } from "../../../src/context";
|
|
2
|
+
import { failCode } from "../../../src/fail-code";
|
|
3
|
+
import schema from "../../../generated/api-schema";
|
|
4
|
+
import { type ExecuteId, type ExecuteOptions, type ExecuteResult, createUlid, useLogger, runtime, loggerPushTags, headerToPlainObject, loggerSubmit, type ExecuteCoreOptions, TSON, MiddlewareEvent, reject, _validate } from "..";
|
|
5
|
+
import { handleCatchError } from "../utils/handle-catch-error";
|
|
6
|
+
|
|
7
|
+
const apis = new Map<string, any>();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* call is a low-level API that is useful only when you want to execute the Milkio Api without using execute or httpServer.
|
|
11
|
+
* 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.
|
|
12
|
+
* Both execute and httpServer essentially call call.
|
|
13
|
+
*/
|
|
14
|
+
export async function _call(
|
|
15
|
+
mode: "execute" | "stream",
|
|
16
|
+
path: string,
|
|
17
|
+
params: unknown | string,
|
|
18
|
+
headersInit: Record<string, string> | Headers = {},
|
|
19
|
+
options: ExecuteCoreOptions,
|
|
20
|
+
): Promise<{ $type: "result"; $result: ExecuteResult<unknown> } | { $type: "stream"; $result: ExecuteResult<unknown>; $generator: AsyncGenerator }> {
|
|
21
|
+
const executeId = options.executeId as ExecuteId;
|
|
22
|
+
|
|
23
|
+
params = TSON.decode(params);
|
|
24
|
+
|
|
25
|
+
if (!(path in schema.apiMethodsSchema)) {
|
|
26
|
+
const result = {
|
|
27
|
+
executeId,
|
|
28
|
+
success: false,
|
|
29
|
+
fail: {
|
|
30
|
+
code: "NOT_FOUND",
|
|
31
|
+
message: failCode.NOT_FOUND(),
|
|
32
|
+
data: undefined,
|
|
33
|
+
},
|
|
34
|
+
} as ExecuteResult<unknown>;
|
|
35
|
+
|
|
36
|
+
return { $type: "result", $result: result };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let headers: Headers;
|
|
40
|
+
if (!(headersInit instanceof Headers)) {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
headers = new Headers({
|
|
43
|
+
...headersInit,
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
headers = headersInit;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options?.onAfterHeaders) {
|
|
50
|
+
await options.onAfterHeaders(headers);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const context: Context = {
|
|
54
|
+
executeId,
|
|
55
|
+
path: path as string,
|
|
56
|
+
headers,
|
|
57
|
+
logger: options.logger,
|
|
58
|
+
detail: options?.detail ?? {},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let result: { value: unknown };
|
|
62
|
+
try {
|
|
63
|
+
// before execute middleware
|
|
64
|
+
await MiddlewareEvent.handle("beforeExecute", [context]);
|
|
65
|
+
|
|
66
|
+
let fn: any;
|
|
67
|
+
// check type
|
|
68
|
+
try {
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
fn = await schema.apiValidator.validate[path]();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
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.");
|
|
73
|
+
}
|
|
74
|
+
params = _validate(await fn.validateParams(params));
|
|
75
|
+
|
|
76
|
+
// execute api
|
|
77
|
+
let api: any;
|
|
78
|
+
if (apis.has(path as string)) api = apis.get(path as string);
|
|
79
|
+
else {
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
api = schema.apiMethodsSchema[path as string]();
|
|
82
|
+
apis.set(path as string, api);
|
|
83
|
+
}
|
|
84
|
+
const apiModuleAwaited = await api.module;
|
|
85
|
+
const apiMethod = apiModuleAwaited.api.action;
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
result = { value: await apiMethod(params, context) };
|
|
88
|
+
// after execute middleware
|
|
89
|
+
await MiddlewareEvent.handle("afterExecute", [context, result]);
|
|
90
|
+
|
|
91
|
+
if (mode === "execute" && !(result.value as AsyncGenerator)[Symbol.asyncIterator]) return { $type: "result", $result: { executeId, success: true, data: result.value } };
|
|
92
|
+
if (mode === "stream" && (result.value as AsyncGenerator)[Symbol.asyncIterator]) return { $type: "stream", $result: { executeId, success: true, data: "$" }, $generator: result.value as AsyncGenerator };
|
|
93
|
+
throw reject("BUSINESS_FAIL", `It looks like you used the wrong syntax, for this API you should use "client.${mode}(...)"`);
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
const errorResult = handleCatchError(error, executeId);
|
|
96
|
+
|
|
97
|
+
return { $type: "result", $result: errorResult };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function _execute<Path extends keyof (typeof schema)["apiMethodsTypeSchema"], Result extends Awaited<ReturnType<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>>>(
|
|
102
|
+
path: Path,
|
|
103
|
+
params: Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0] | string,
|
|
104
|
+
headersInit: Record<string, string> | Headers = {},
|
|
105
|
+
options?: ExecuteOptions,
|
|
106
|
+
): Promise<ExecuteResult<Result>> {
|
|
107
|
+
const executeId = (options?.executeId ?? createUlid()) as ExecuteId;
|
|
108
|
+
const logger = useLogger(executeId);
|
|
109
|
+
runtime.execute.executeIds.add(executeId);
|
|
110
|
+
|
|
111
|
+
loggerPushTags(executeId, {
|
|
112
|
+
from: "execute",
|
|
113
|
+
executeId,
|
|
114
|
+
params,
|
|
115
|
+
path,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await _call("execute", path, params, headersInit, {
|
|
119
|
+
...options,
|
|
120
|
+
executeId,
|
|
121
|
+
logger,
|
|
122
|
+
onAfterHeaders: (headers) => {
|
|
123
|
+
loggerPushTags(executeId, {
|
|
124
|
+
headers: headerToPlainObject(headers),
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
loggerPushTags(executeId, { result: result.$result });
|
|
130
|
+
await loggerSubmit(executeId);
|
|
131
|
+
runtime.execute.executeIds.delete(executeId);
|
|
132
|
+
|
|
133
|
+
return result.$result as ExecuteResult<Result>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export 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> {
|
|
137
|
+
const resultsRaw = await _execute(path, params, headersInit, options);
|
|
138
|
+
let fn: any;
|
|
139
|
+
try {
|
|
140
|
+
fn = await schema.apiValidator.validate[path]();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
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.");
|
|
143
|
+
}
|
|
144
|
+
const results = await fn.validateResults(TSON.encode(resultsRaw));
|
|
145
|
+
return results;
|
|
146
|
+
}
|
package/kernel/logger.ts
CHANGED
|
@@ -23,10 +23,10 @@ export type Logger = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export const loggerController = (() => {
|
|
26
|
-
const logs = new Map<ExecuteId, {
|
|
26
|
+
const logs = new Map<ExecuteId, { __LOG_DETAIL__: Array<LoggerItem>; [key: string]: any }>();
|
|
27
27
|
|
|
28
28
|
const loggerPushTags = (executeId: ExecuteId, tags: Record<string, any>) => {
|
|
29
|
-
if (!logs.has(executeId)) logs.set(executeId, {
|
|
29
|
+
if (!logs.has(executeId)) logs.set(executeId, { __LOG_DETAIL__: [] });
|
|
30
30
|
const logItem = logs.get(executeId);
|
|
31
31
|
for (const key in tags) {
|
|
32
32
|
logItem![key] = tags[key];
|
|
@@ -41,11 +41,11 @@ export const loggerController = (() => {
|
|
|
41
41
|
};
|
|
42
42
|
const log = logs.get(executeId)!;
|
|
43
43
|
for (const key in log) {
|
|
44
|
-
if (key === "
|
|
44
|
+
if (key === "__LOG_DETAIL__") continue;
|
|
45
45
|
loggerSubmitOptions[key] = log[key];
|
|
46
46
|
}
|
|
47
47
|
logs.delete(executeId);
|
|
48
|
-
loggerOptions.onSubmit(loggerSubmitOptions, log.
|
|
48
|
+
loggerOptions.onSubmit(loggerSubmitOptions, log.__LOG_DETAIL__);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const loggerSubmitAll = async () => {
|
|
@@ -63,7 +63,7 @@ export const loggerController = (() => {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
for (const executeId of executeIds) {
|
|
66
|
-
if (!logs.has(executeId as ExecuteId)) logs.set(executeId as ExecuteId, {
|
|
66
|
+
if (!logs.has(executeId as ExecuteId)) logs.set(executeId as ExecuteId, { __LOG_DETAIL__: [] });
|
|
67
67
|
const loggerItem = {
|
|
68
68
|
executeId: executeId as ExecuteId,
|
|
69
69
|
loggerLevel: level,
|
|
@@ -71,7 +71,7 @@ export const loggerController = (() => {
|
|
|
71
71
|
params,
|
|
72
72
|
} satisfies LoggerItem;
|
|
73
73
|
if (!loggerOptions.onInsert(loggerItem)) return;
|
|
74
|
-
logs.get(executeId as ExecuteId)!.
|
|
74
|
+
logs.get(executeId as ExecuteId)!.__LOG_DETAIL__.push(loggerItem);
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
|
package/kernel/milkio.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { type MiddlewareOptions, _middlewares, MiddlewareEvent } from "./middleware";
|
|
2
2
|
import schema from "../../../generated/api-schema";
|
|
3
|
-
import
|
|
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";
|
|
3
|
+
import { runtime } from "..";
|
|
9
4
|
import { createUlid } from "../utils/create-ulid";
|
|
10
5
|
import { _validate } from "./validate";
|
|
6
|
+
import { _execute, _call, _executeToJson } from "./execute";
|
|
11
7
|
|
|
12
8
|
export type MilkioAppOptions = {
|
|
13
9
|
/**
|
|
@@ -45,11 +41,10 @@ export async function createMilkioApp(MilkioAppOptions: MilkioAppOptions = {}) {
|
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
const MilkioApp = {
|
|
44
|
+
randParams: _randParams,
|
|
48
45
|
execute: _execute,
|
|
49
46
|
executeToJson: _executeToJson,
|
|
50
|
-
|
|
51
|
-
_executeCoreToJson,
|
|
52
|
-
randParams: _randParams,
|
|
47
|
+
_call,
|
|
53
48
|
};
|
|
54
49
|
|
|
55
50
|
if (MilkioAppOptions.bootstraps) {
|
|
@@ -86,202 +81,6 @@ export async function createMilkioApp(MilkioAppOptions: MilkioAppOptions = {}) {
|
|
|
86
81
|
return MilkioApp;
|
|
87
82
|
}
|
|
88
83
|
|
|
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;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* executeCore is a low-level API that is useful only when you want to execute the Milkio Api without using execute or httpServer.
|
|
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.
|
|
127
|
-
* Both execute and httpServer essentially call executeCore.
|
|
128
|
-
*/
|
|
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
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
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> {
|
|
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;
|
|
232
|
-
}
|
|
233
|
-
|
|
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> {
|
|
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;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
84
|
export async function _randParams<Path extends keyof (typeof schema)["apiMethodsTypeSchema"]>(path: Path): Promise<Parameters<(typeof schema)["apiMethodsTypeSchema"][Path]["api"]["action"]>[0]> {
|
|
247
85
|
return await (await schema.apiValidator.validate[path]()).randParams();
|
|
248
86
|
}
|
|
249
|
-
|
|
250
|
-
const apis = new Map<string, any>();
|
|
251
|
-
|
|
252
|
-
export type ExecuteResult<Result> = ExecuteResultSuccess<Result> | ExecuteResultFail;
|
|
253
|
-
|
|
254
|
-
export type ExecuteResultSuccess<Result> = {
|
|
255
|
-
executeId: ExecuteId;
|
|
256
|
-
success: true;
|
|
257
|
-
data: Result;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
export type ExecuteResultFail<FailT extends Fail<keyof FailEnumerates> = Fail<keyof FailEnumerates>> = {
|
|
261
|
-
executeId: ExecuteId;
|
|
262
|
-
success: false;
|
|
263
|
-
fail: FailT;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
export type ExecuteOptions = {
|
|
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"];
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
export type ExecuteCoreOptions = Mixin<
|
|
281
|
-
ExecuteOptions,
|
|
282
|
-
{
|
|
283
|
-
executeId: string;
|
|
284
|
-
logger: Logger;
|
|
285
|
-
onAfterHeaders?: (headers: Headers) => void | Promise<void>;
|
|
286
|
-
}
|
|
287
|
-
>;
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import type { createMilkioApp } from ".";
|
|
1
|
+
import type { Logger, MilkioContext, createMilkioApp } from ".";
|
|
2
2
|
import type { failCode } from "../../src/fail-code";
|
|
3
3
|
|
|
4
|
+
export type Override<P, S> = Omit<P, keyof S> & S;
|
|
5
|
+
|
|
6
|
+
export type Mixin<T, U> = U & Omit<T, keyof U>;
|
|
7
|
+
|
|
8
|
+
export type GeneratorGeneric<T> = T extends AsyncGenerator<infer I> ? I : never;
|
|
9
|
+
|
|
4
10
|
export type MilkioApp = Awaited<ReturnType<typeof createMilkioApp>>;
|
|
5
11
|
|
|
6
12
|
export type ExecuteId = string | "global";
|
|
@@ -33,10 +39,6 @@ export type CookbookItem = {
|
|
|
33
39
|
}>;
|
|
34
40
|
};
|
|
35
41
|
|
|
36
|
-
export type Override<P, S> = Omit<P, keyof S> & S;
|
|
37
|
-
|
|
38
|
-
export type Mixin<T, U> = U & Omit<T, keyof U>;
|
|
39
|
-
|
|
40
42
|
export type MilkioConfig = {
|
|
41
43
|
generate?: {
|
|
42
44
|
significant?: Array<string>;
|
|
@@ -47,3 +49,42 @@ export type MilkioConfig = {
|
|
|
47
49
|
commands?: Array<{ name?: string; script?: string; icon?: string }>;
|
|
48
50
|
};
|
|
49
51
|
};
|
|
52
|
+
|
|
53
|
+
export type ExecuteResult<Result> = ExecuteResultSuccess<Result> | ExecuteResultFail;
|
|
54
|
+
|
|
55
|
+
export type ExecuteResultSuccess<Result> = {
|
|
56
|
+
executeId: ExecuteId;
|
|
57
|
+
success: true;
|
|
58
|
+
data: Result;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type ExecuteResultFail<FailT extends Fail<keyof FailEnumerates> = Fail<keyof FailEnumerates>> = {
|
|
62
|
+
executeId: ExecuteId;
|
|
63
|
+
success: false;
|
|
64
|
+
fail: FailT;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type ExecuteOptions = {
|
|
68
|
+
/**
|
|
69
|
+
* The executeId of the request
|
|
70
|
+
* executeId may be generated by the serverless provider, if not, a random string will be generated instead
|
|
71
|
+
*/
|
|
72
|
+
executeId?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Additional information about the request
|
|
75
|
+
* These are usually only fully implemented when called by an Http server
|
|
76
|
+
* During testing or when calling between microservices, some or all of the values may be undefined
|
|
77
|
+
*/
|
|
78
|
+
detail?: MilkioContext["detail"];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type ExecuteCoreOptions = Mixin<
|
|
82
|
+
ExecuteOptions,
|
|
83
|
+
{
|
|
84
|
+
executeId: string;
|
|
85
|
+
logger: Logger;
|
|
86
|
+
onAfterHeaders?: (headers: Headers) => void | Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
export type MilkioEvent<Result> = Awaited<GeneratorGeneric<ExecuteResultSuccess<Result>["data"] extends { $type: any } ? ExecuteResultSuccess<Result>["data"]["$type"] : never>>;
|
|
@@ -3,7 +3,7 @@ import { useLogger, type ExecuteId, type ExecuteResult } from "..";
|
|
|
3
3
|
import { configMilkio } from "../../../src/config/milkio";
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
-
export function
|
|
6
|
+
export function handleCatchError(error: any, executeId: ExecuteId): ExecuteResult<any> {
|
|
7
7
|
const logger = useLogger(executeId);
|
|
8
8
|
|
|
9
9
|
if (configMilkio.debug === true) {
|