milkio 0.0.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/.co.toml ADDED
@@ -0,0 +1,2 @@
1
+ ["general"]
2
+ includes = ["co:bun"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 southern-aurora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # bao
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.0.3. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
package/c.ts ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /* eslint-disable @typescript-eslint/no-misused-promises, no-console, @typescript-eslint/no-explicit-any */
4
+
5
+ import { argv, cwd, env, exit } from "node:process";
6
+ import { exec } from "./utils/exec";
7
+ import { join } from "node:path";
8
+ import { $ } from "bun";
9
+ import { readFile } from "node:fs/promises";
10
+
11
+ const rootPath = cwd();
12
+ const method = argv[2] as keyof typeof commands;
13
+ const params = argv.slice(3) as Parameters<(typeof commands)[keyof typeof commands]>;
14
+
15
+ const commands = {
16
+ async gen() {
17
+ await exec(rootPath, ["bun", "./node_modules/loongbao/scripts/generate.ts"]);
18
+ },
19
+ async "gen:database"() {
20
+ await exec(rootPath, ["bun", "./node_modules/loongbao/scripts/generate-database.ts"]);
21
+ },
22
+ async "build:cookbook"() {
23
+ await exec(rootPath, ["bun", "./node_modules/loongbao/scripts/build-cookbook.ts"]);
24
+ },
25
+ async "build:dto"() {
26
+ await exec(join(rootPath, "packages", "dto"), ["bun", "i"]);
27
+ await exec(rootPath, ["bun", "./node_modules/loongbao/scripts/generate.ts"]);
28
+ await exec(rootPath, ["bun", "./node_modules/loongbao/scripts/build-dto.ts"]);
29
+ },
30
+ async test(files?: string, reserved?: string) {
31
+ const run = async () => {
32
+ const packageJson = await JSON.parse((await readFile(join(rootPath, "package.json"))).toString());
33
+ // A bug has appeared in the new version of bun: https://github.com/oven-sh/bun/issues/9428
34
+ // Therefore, temporarily switch to another implementation.
35
+ // await $`${{ raw: packageJson.scripts.start }}`.env({
36
+ // ...env,
37
+ // LOONGBAO_RUN_MODE: "API_TEST",
38
+ // LOONGBAO_TEST: files ?? "1"
39
+ // });
40
+ //
41
+ await exec(rootPath, ["bash", "-c", packageJson.scripts.start as string], {
42
+ env: {
43
+ ...env,
44
+ LOONGBAO_RUN_MODE: "API_TEST",
45
+ LOONGBAO_TEST: files ?? "1"
46
+ }
47
+ });
48
+ };
49
+ if (!env.LOONGBAO_TEST_RESERVED && reserved !== "1") {
50
+ // Normal test
51
+ await run();
52
+ } else {
53
+ // Keep after testing, the terminal never exits and is usually used for various IDE extensions.
54
+ try {
55
+ await run();
56
+ } catch (error) {}
57
+ while (true) {
58
+ const result = await new Promise((resolve) => {
59
+ const wasRaw = process.stdin.isRaw;
60
+ process.stdin.setRawMode(true);
61
+ process.stdin.resume();
62
+ process.stdin.once("data", (data) => {
63
+ process.stdin.pause();
64
+ process.stdin.setRawMode(wasRaw);
65
+ resolve(data.toString());
66
+ });
67
+ });
68
+ // No exit function is set
69
+ // if (result === "q") exit(0);
70
+ }
71
+ }
72
+ }
73
+ };
74
+
75
+ if (method === undefined || !(method in commands)) {
76
+ console.log("Command does not exist, Supported commands are:");
77
+ console.log(" " + Object.keys(commands).join(", "));
78
+ exit(1);
79
+ }
80
+
81
+ await commands[method](...params);
82
+
83
+ exit(0);
@@ -0,0 +1,70 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-argument, no-console, @typescript-eslint/no-explicit-any */
2
+ import { exit } from "node:process";
3
+ import schema from "../../../generate/api-schema";
4
+ import { type LoongbaoApp } from "..";
5
+
6
+ export const defineApiTestHandler = async <Paths extends Array<keyof (typeof schema)["apiTestsSchema"]>>(app: LoongbaoApp, paths: Paths | string | 1 | undefined) => {
7
+ console.log(`🧊 Loongbao Api Testing..\n`);
8
+
9
+ if (!paths) {
10
+ console.log("🧊 No paths specified.");
11
+ exit(1);
12
+ }
13
+
14
+ if (paths === "1" || paths === 1) {
15
+ paths = Object.keys(schema.apiTestsSchema) as unknown as Paths;
16
+ } else if (typeof paths === "string") {
17
+ if (!paths.startsWith("[")) {
18
+ paths = [paths] as Paths;
19
+ } else {
20
+ paths = JSON.parse(paths) as Paths;
21
+ }
22
+ }
23
+
24
+ const tests = [];
25
+ const startedAt = new Date().getTime();
26
+
27
+ for (let path of paths) {
28
+ if (path.startsWith("/")) path = path.slice(1) as Paths[number];
29
+ tests.push(
30
+ // @ts-ignore
31
+ (async () => {
32
+ // @ts-ignore
33
+ const module = await schema.apiTestsSchema[path]().module;
34
+ const cases = module.test.getCases();
35
+ let i = 0;
36
+ for (const cs of cases) {
37
+ ++i;
38
+ const csStartedAt = new Date().getTime();
39
+ const clear = setTimeout(() => {
40
+ console.error(`------`);
41
+ console.error(`❌ TIMEOUT -- More than ${cs.timeout ?? 6000}ms`);
42
+ console.error(` ${cs.name} | Path: src/apps/${path as string}.ts | Case: ${i}`);
43
+ console.error(`------`);
44
+ exit(1);
45
+ }, cs.timeout ?? 6000);
46
+ await cs.handler({
47
+ execute: async (params: any, headers?: any, options?: any) => app.execute(path, params, headers ?? {}, options),
48
+ reject: (message?: string) => {
49
+ console.error(`------`);
50
+ console.error(`❌ REJECT -- ${message ?? "Test not satisfied"}`);
51
+ console.error(` ${cs.name} | Path: src/apps/${path as string}.ts | Case: ${i} | Time: ${new Date().getTime() - csStartedAt}ms`);
52
+ console.error(`------`);
53
+ exit(1);
54
+ }
55
+ });
56
+ clearTimeout(clear);
57
+ console.log(`✅ DIRECT -- ${cs.name} | Path: src/apps/${path as string}.ts | Case: ${i} | Time: ${new Date().getTime() - csStartedAt}ms`);
58
+ }
59
+ })()
60
+ );
61
+ }
62
+
63
+ await Promise.all(tests);
64
+
65
+ const endedAt = new Date().getTime();
66
+
67
+ console.log(`\n✅ All tests passed.`);
68
+ console.log(`🧊 Loongbao Api Testing took ${((endedAt - startedAt) / 1000).toFixed(2)}s\n`);
69
+ exit(0);
70
+ };
@@ -0,0 +1,14 @@
1
+ import { type Api, type ExecuteResult, type ExecuteOptions } from "..";
2
+
3
+ export function defineApiTest<ApiT extends Api>(_api: ApiT, cases: Array<ApiTestCases<ApiT>>) {
4
+ return {
5
+ getCases: () => cases,
6
+ isApiTest: true
7
+ };
8
+ }
9
+
10
+ export type ApiTestCases<ApiT extends Api> = {
11
+ handler: (test: { execute: (params: Parameters<ApiT["action"]>[0], headers?: Record<string, string>, options?: ExecuteOptions) => Promise<ExecuteResult<Awaited<ReturnType<ApiT["action"]>>>>; reject: (message?: string) => void }) => Promise<void> | void;
12
+ name: string;
13
+ timeout?: number;
14
+ };
@@ -0,0 +1,15 @@
1
+ import type { Meta } from "../../../src/meta";
2
+ import type { Context } from "../../../src/context";
3
+
4
+ export function defineApi<ApiT extends Api>(api: ApiT): ApiT & { isApi: true } {
5
+ return {
6
+ ...api,
7
+ isApi: true
8
+ };
9
+ }
10
+
11
+ export type Api = {
12
+ meta: Meta;
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ action: (params: any, context: Context) => Promise<unknown> | unknown;
15
+ };
@@ -0,0 +1,203 @@
1
+ /* eslint-disable no-console */
2
+ import { configFramework, loggerPushTags, loggerSubmit, useLogger, loggerSubmitAll, runtime, MiddlewareEvent } from "..";
3
+ import type { ExecuteId, LoongbaoApp, Mixin } from "..";
4
+ import { hanldeCatchError } from "../utils/handle-catch-error";
5
+ import { routerHandler } from "../../../src/router";
6
+ import schema from "../../../generate/api-schema";
7
+ import { failCode } from "../../../src/fail-code";
8
+ import process, { exit } from "node:process";
9
+ import { TSON } from "@southern-aurora/tson";
10
+ import { createUlid } from "../utils/create-ulid";
11
+
12
+ export type ExecuteHttpServerOptions = {
13
+ /**
14
+ * The execution ID generator
15
+ * If you have enabled this option, the executeId will be generated each time by calling this method. Otherwise, it will be generated using the built-in method.
16
+ *
17
+ * @param request
18
+ * @returns
19
+ */
20
+ executeIdGenerator?: (request: Request) => string | Promise<string>;
21
+ };
22
+
23
+ export function defineHttpHandler(app: LoongbaoApp, options: ExecuteHttpServerOptions = {}) {
24
+ // If an unexpected error occurs, exit the process.
25
+ // For modern production environments such as Serverless, Kubernetes, or Docker Compose:
26
+ // The process will automatically restart after exiting.
27
+ // This helps prevent unexpected errors from contaminating the entire application and causing subsequent requests to fail intermittently.
28
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
29
+ process.on("uncaughtException", async (error) => {
30
+ const logger = useLogger("global");
31
+ logger.error("ErrorCaughtInUncaughtExceptionEvent:", error);
32
+ await loggerSubmitAll();
33
+ exit(1);
34
+ });
35
+
36
+ const fetch = async (request: LoongbaoHTTPRequest) => {
37
+ const fullurl = new URL(request.request.url, `http://${request.request.headers.get("host") ?? "localhost"}`);
38
+ const executeId = (options?.executeIdGenerator ? await options.executeIdGenerator(request.request) : createUlid()) as ExecuteId;
39
+ runtime.execute.executeIds.add(executeId);
40
+ const logger = useLogger(executeId);
41
+ const ip = (request.request.headers.get("x-forwarded-for") as string | undefined)?.split(",")[0] ?? "0.0.0.0";
42
+ const headers = request.request.headers;
43
+
44
+ loggerPushTags(executeId, {
45
+ from: "http-server",
46
+ fullUrl: fullurl.pathname,
47
+ ip,
48
+ method: request.request.method,
49
+ requestHeaders: request.request.headers.toJSON(),
50
+ timein: new Date().getTime()
51
+ });
52
+
53
+ const response: LoongbaoHTTPResponse = {
54
+ body: "",
55
+ status: 200,
56
+ headers: {
57
+ "Content-Type": "application/json",
58
+ "Access-Control-Allow-Methods": configFramework.corsAllowMethods ?? "*",
59
+ "Access-Control-Allow-Headers": configFramework.corsAllowHeaders ?? "*",
60
+ "Access-Control-Allow-Origin": configFramework.corsAllowOrigin ?? "*"
61
+ }
62
+ };
63
+
64
+ try {
65
+ // Process OPTIONS pre inspection requests
66
+ if (request.request.method === "OPTIONS") {
67
+ await loggerSubmit(executeId);
68
+ runtime.execute.executeIds.delete(executeId);
69
+
70
+ return new Response("", {
71
+ headers: {
72
+ "Access-Control-Allow-Methods": configFramework.corsAllowMethods ?? "*",
73
+ "Access-Control-Allow-Headers": configFramework.corsAllowHeaders ?? "*",
74
+ "Access-Control-Allow-Origin": configFramework.corsAllowOrigin ?? "*"
75
+ }
76
+ });
77
+ }
78
+
79
+ let path = fullurl.pathname.substring(1).split("/");
80
+
81
+ // Compatible with API gateway's ability to differentiate versions by path
82
+ // see: /src/config/ConfigProgram.ts in "ignorePathLevel"
83
+ if (configFramework.ignorePathLevel !== 0) path = path.slice(configFramework.ignorePathLevel);
84
+
85
+ let pathstr = path.join("/") as keyof (typeof schema)["apiMethodsSchema"];
86
+
87
+ // Special processing: do not run middleware when encountering 404 and return quickly
88
+ if (!(pathstr in schema.apiMethodsSchema)) {
89
+ const redirectPath = await routerHandler(pathstr, fullurl);
90
+ if (!redirectPath) {
91
+ const rawbody = await request.request.text();
92
+ loggerPushTags(executeId, {
93
+ body: rawbody || "no body"
94
+ });
95
+ response.body = `{"executeId":"${executeId}","success":false,"fail":{"code":"not-found","message":${JSON.stringify(failCode["not-found"]())}}}`;
96
+
97
+ loggerPushTags(executeId, {
98
+ status: response.status,
99
+ responseHeaders: response.headers,
100
+ timeout: new Date().getTime()
101
+ });
102
+
103
+ await loggerSubmit(executeId);
104
+ runtime.execute.executeIds.delete(executeId);
105
+
106
+ return new Response(response.body, response);
107
+ }
108
+ pathstr = redirectPath as typeof pathstr;
109
+ }
110
+
111
+ loggerPushTags(executeId, {
112
+ path: pathstr
113
+ });
114
+
115
+ const detail = {
116
+ path: pathstr,
117
+ ip,
118
+ executeId,
119
+ fullurl,
120
+ request: request.request,
121
+ response
122
+ };
123
+
124
+ // execute api
125
+ // after request middleware
126
+ await MiddlewareEvent.handle("afterHTTPRequest", [headers, detail]);
127
+
128
+ const rawbody = await request.request.text();
129
+ loggerPushTags(executeId, {
130
+ body: rawbody || "no body"
131
+ });
132
+
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ let params: any;
135
+ if (rawbody === "") {
136
+ params = undefined;
137
+ } else {
138
+ try {
139
+ params = JSON.parse(rawbody);
140
+ } catch (error) {
141
+ const logger = useLogger(executeId);
142
+ logger.log("TIP: body is not json, the content is not empty, but the content is not in a valid JSON format. The original content value can be retrieved via request.request.text()");
143
+ params = undefined;
144
+ }
145
+ }
146
+
147
+ loggerPushTags(executeId, {
148
+ params
149
+ });
150
+
151
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
152
+ const result = await app._executeCoreToJson(pathstr, params, headers, {
153
+ executeId,
154
+ logger,
155
+ detail
156
+ });
157
+
158
+ // @ts-ignore
159
+ // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression, @typescript-eslint/no-explicit-any
160
+ response.body = `${response.body ?? ""}${result}`;
161
+
162
+ // before response middleware
163
+ const middlewareResponse = {
164
+ value: response.body
165
+ };
166
+ await MiddlewareEvent.handle("beforeHTTPResponse", [middlewareResponse, detail]);
167
+
168
+ response.body = middlewareResponse.value;
169
+ } catch (error) {
170
+ const result = hanldeCatchError(error, executeId);
171
+ response.body = TSON.stringify(result);
172
+ }
173
+
174
+ loggerPushTags(executeId, {
175
+ status: response.status,
176
+ responseHeaders: response.headers,
177
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
178
+ body: response.body || "",
179
+ timeout: new Date().getTime()
180
+ });
181
+
182
+ await loggerSubmit(executeId);
183
+ runtime.execute.executeIds.delete(executeId);
184
+
185
+ return new Response(response.body, response);
186
+ };
187
+
188
+ return fetch;
189
+ }
190
+
191
+ export type LoongbaoHTTPRequest = {
192
+ request: Request;
193
+ };
194
+
195
+ export type LoongbaoHTTPResponse = Mixin<
196
+ ResponseInit,
197
+ {
198
+ //
199
+ body: string;
200
+ status: number;
201
+ headers: Record<string, string>;
202
+ }
203
+ >;
@@ -0,0 +1,9 @@
1
+ import { type MiddlewareOptions } from "..";
2
+
3
+ export function defineMiddleware(options: MiddlewareOptions): () => MiddlewareOptions {
4
+ return () => ({
5
+ ...options,
6
+ // @ts-ignore
7
+ isMiddleware: true
8
+ });
9
+ }
@@ -0,0 +1,13 @@
1
+ export const defineUse = <CreatorFn extends () => Promise<unknown> | unknown>(creatorFn: CreatorFn): (() => Promise<Awaited<ReturnType<CreatorFn>>>) => {
2
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+ let use: any | undefined;
4
+
5
+ const getUse = async () => {
6
+ if (use === undefined) {
7
+ use = await creatorFn();
8
+ }
9
+ return use;
10
+ };
11
+
12
+ return getUse;
13
+ };
package/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ // types
2
+ export * from "./types";
3
+
4
+ // utils
5
+ export * from "./utils/tson";
6
+ export * from "./utils/create-ulid";
7
+ export * from "./utils/env-to-string";
8
+ export * from "./utils/env-to-number";
9
+ export * from "./utils/env-to-boolean";
10
+ export * from "./utils/create-template";
11
+
12
+ // defines
13
+ export * from "./defines/define-use";
14
+ export * from "./defines/define-api";
15
+ export * from "./defines/define-api-test";
16
+ export * from "./defines/define-middleware";
17
+
18
+ // kernel
19
+ export * from "./kernel/runtime";
20
+ export * from "./kernel/logger";
21
+ export * from "./kernel/fail";
22
+ export * from "./kernel/meta";
23
+ export * from "./kernel/config";
24
+ export * from "./kernel/context";
25
+ export * from "./kernel/validate";
26
+ export * from "./kernel/middleware";
27
+
28
+ // handler
29
+ export * from "./defines/define-http-handler";
30
+ export * from "./defines/define-api-test-handler";
31
+
32
+ // loongbao
33
+ export * from "./kernel/loongbao";
@@ -0,0 +1,15 @@
1
+ import { cwd, env } from "node:process";
2
+ import { envToBoolean, envToNumber, envToString } from "..";
3
+
4
+ export const configFramework = {
5
+ cwd: cwd(),
6
+ port: envToNumber(env.PORT, 9000),
7
+ loongbaoRunMode: envToString(env.LOONGBAO_RUN_MODE, "DEFAULT"),
8
+ loongbaoTest: envToString(env.LOONGBAO_TEST, ""),
9
+ debug: envToBoolean(env.DEBUG, false),
10
+ redisUrl: envToString(env.REDIS_URL, "redis://:123456@your-not-redis-url:6379"),
11
+ ignorePathLevel: envToNumber(env.IGNORE_PATH_LEVEL, 0),
12
+ corsAllowMethods: envToString(env.CORS_ALLOW_METHODS, "*"),
13
+ corsAllowHeaders: envToString(env.CORS_ALLOW_HEADERS, "*"),
14
+ corsAllowOrigin: envToString(env.CORS_ALLOW_ORIGIN, "*")
15
+ };
@@ -0,0 +1,24 @@
1
+ import type { URL } from "node:url";
2
+ import type { ExecuteId, Logger, LoongbaoHTTPResponse } from "..";
3
+
4
+ export type FrameworkContext = {
5
+ path: string;
6
+ executeId: ExecuteId;
7
+ headers: Headers;
8
+ logger: Logger;
9
+ /**
10
+ * Additional information about the request
11
+ * These are usually only fully implemented when called by an HTTP server
12
+ * During testing or when calling between microservices, some or all of the values may be undefined
13
+ */
14
+ detail: Partial<FrameworkHTTPDetail>;
15
+ };
16
+
17
+ export type FrameworkHTTPDetail = {
18
+ path: string;
19
+ executeId: ExecuteId;
20
+ fullurl: URL;
21
+ ip: string;
22
+ request: Request;
23
+ response: LoongbaoHTTPResponse;
24
+ };
package/kernel/fail.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { failCode } from "../../../src/fail-code";
2
+
3
+ export function reject<Code extends keyof typeof failCode, FailData extends (typeof failCode)[Code]>(code: Code, data: Parameters<FailData>[0]) {
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5
+ const message = failCode[code]?.(data as any) ?? "";
6
+ const error = {
7
+ name: "LoongbaoReject",
8
+ code,
9
+ message,
10
+ data,
11
+ stack: ""
12
+ };
13
+ Error.captureStackTrace(error);
14
+ error.stack = error.stack.replace(/\n.*\n/, "\n");
15
+
16
+ return error;
17
+ }
18
+
19
+ export type LoongbaoReject = ReturnType<typeof reject>;
20
+
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ export type LoongbaoFailCode = Record<string, (arg: any) => string>;
@@ -0,0 +1,113 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { runtime, type ExecuteId } from "..";
4
+ import { loggerOptions } from "../../../src/logger";
5
+
6
+ export type LoggerItem = {
7
+ executeId: ExecuteId;
8
+ loggerLevel: LoggerLevel;
9
+ description: string;
10
+ params: Array<unknown>;
11
+ };
12
+
13
+ export type LoggerTags = Record<string, any>;
14
+
15
+ export type LoggerOptions = {
16
+ onInsert: (options: LoggerItem) => boolean;
17
+ onSubmit: (tags: LoggerTags, logs: Array<LoggerItem>) => Promise<void> | void;
18
+ };
19
+
20
+ export type Logger = {
21
+ debug: (description: string, ...params: Array<unknown>) => void;
22
+ log: (description: string, ...params: Array<unknown>) => void;
23
+ warn: (description: string, ...params: Array<unknown>) => void;
24
+ error: (description: string, ...params: Array<unknown>) => void;
25
+ };
26
+
27
+ export const loggerController = (() => {
28
+ const logs = new Map<ExecuteId, { __LOG_DEATIL__: Array<LoggerItem>; [key: string]: any }>();
29
+
30
+ const loggerPushTags = (executeId: ExecuteId, tags: Record<string, any>) => {
31
+ if (!logs.has(executeId)) logs.set(executeId, { __LOG_DEATIL__: [] });
32
+ const logItem = logs.get(executeId);
33
+ for (const key in tags) {
34
+ logItem![key] = tags[key];
35
+ }
36
+ };
37
+
38
+ const loggerSubmit = async (executeId: ExecuteId) => {
39
+ if (!logs.has(executeId)) return;
40
+ if (executeId === "global") return;
41
+ const loggerSubmitOptions: Record<string, string> = {
42
+ executeId
43
+ };
44
+ const log = logs.get(executeId)!;
45
+ for (const key in log) {
46
+ if (key === "__LOG_DEATIL__") continue;
47
+ loggerSubmitOptions[key] = log[key];
48
+ }
49
+ logs.delete(executeId);
50
+ loggerOptions.onSubmit(loggerSubmitOptions, log.__LOG_DEATIL__);
51
+ };
52
+
53
+ const loggerSubmitAll = async () => {
54
+ for (const executeId of logs.keys()) {
55
+ await loggerSubmit(executeId);
56
+ }
57
+ };
58
+
59
+ const insertItem = (executeId: ExecuteId, level: LoggerLevel, description: string, params: Array<unknown>): void => {
60
+ let executeIds: Array<string> = [];
61
+ if (executeId === "global") {
62
+ executeIds = Array.from(new Set([...Array.from(runtime.execute.executeIds), ...Array.from(runtime.execute.executeIds)]));
63
+ } else {
64
+ executeIds = [executeId];
65
+ }
66
+
67
+ for (const executeId of executeIds) {
68
+ if (!logs.has(executeId as ExecuteId)) logs.set(executeId as ExecuteId, { __LOG_DEATIL__: [] });
69
+ const loggerItem = {
70
+ executeId: executeId as ExecuteId,
71
+ loggerLevel: level,
72
+ description,
73
+ params
74
+ } satisfies LoggerItem;
75
+ if (!loggerOptions.onInsert(loggerItem)) return;
76
+ logs.get(executeId as ExecuteId)!.__LOG_DEATIL__.push(loggerItem);
77
+ }
78
+ };
79
+
80
+ const useLogger = (executeId: ExecuteId) => {
81
+ return {
82
+ debug(description: string, ...params: Array<unknown>) {
83
+ insertItem(executeId, "debug", description, params);
84
+ },
85
+ log(description: string, ...params: Array<unknown>) {
86
+ insertItem(executeId, "log", description, params);
87
+ },
88
+ warn(description: string, ...params: Array<unknown>) {
89
+ insertItem(executeId, "warn", description, params);
90
+ },
91
+ error(description: string, ...params: Array<unknown>) {
92
+ insertItem(executeId, "error", description, params);
93
+ }
94
+ } satisfies Logger;
95
+ };
96
+
97
+ return {
98
+ loggerPushTags,
99
+ loggerSubmit,
100
+ loggerSubmitAll,
101
+ useLogger
102
+ };
103
+ })();
104
+
105
+ export const useLogger = loggerController.useLogger;
106
+
107
+ export const loggerPushTags = loggerController.loggerPushTags;
108
+
109
+ export const loggerSubmit = loggerController.loggerSubmit;
110
+
111
+ export const loggerSubmitAll = loggerController.loggerSubmitAll;
112
+
113
+ export type LoggerLevel = "debug" | "log" | "warn" | "error";