target-supabase-sdk 0.1.8 → 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/README.md +74 -6
- package/dist/auth/auth.api.d.ts.map +1 -1
- package/dist/auth/auth.api.js +2 -1
- package/dist/auth/auth.api.js.map +1 -1
- package/dist/browser.d.ts +52 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +45 -0
- package/dist/browser.js.map +1 -0
- package/dist/command/command.api.d.ts +43 -0
- package/dist/command/command.api.d.ts.map +1 -0
- package/dist/command/command.api.js +58 -0
- package/dist/command/command.api.js.map +1 -0
- package/dist/command/command.interface.d.ts +14 -0
- package/dist/command/command.interface.d.ts.map +1 -0
- package/dist/command/command.interface.js +9 -0
- package/dist/command/command.interface.js.map +1 -0
- package/dist/core.api.d.ts +70 -6
- package/dist/core.api.d.ts.map +1 -1
- package/dist/core.api.js +191 -13
- package/dist/core.api.js.map +1 -1
- package/dist/file/file-manager.d.ts +1 -1
- package/dist/file/file-manager.d.ts.map +1 -1
- package/dist/file/file-manager.js +1 -1
- package/dist/file/file-manager.js.map +1 -1
- package/dist/index.d.ts +2 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -34
- package/dist/index.js.map +1 -1
- package/dist/link/link.interface.d.ts +1 -1
- package/dist/link/link.interface.d.ts.map +1 -1
- package/dist/node/node-log-scope.d.ts +11 -0
- package/dist/node/node-log-scope.d.ts.map +1 -0
- package/dist/node/node-log-scope.js +7 -0
- package/dist/node/node-log-scope.js.map +1 -0
- package/dist/node/node-runtime.base.d.ts +42 -0
- package/dist/node/node-runtime.base.d.ts.map +1 -0
- package/dist/node/node-runtime.base.js +397 -0
- package/dist/node/node-runtime.base.js.map +1 -0
- package/dist/node/node-task.preset.d.ts +1 -0
- package/dist/node/node-task.preset.d.ts.map +1 -0
- package/dist/node/node-task.preset.js +14 -0
- package/dist/node/node-task.preset.js.map +1 -0
- package/dist/node/node.api.d.ts +67 -31
- package/dist/node/node.api.d.ts.map +1 -1
- package/dist/node/node.api.js +65 -102
- package/dist/node/node.api.js.map +1 -1
- package/dist/node/node.interface.d.ts +7 -0
- package/dist/node/node.interface.d.ts.map +1 -1
- package/dist/node/node.interface.js +2 -0
- package/dist/node/node.interface.js.map +1 -1
- package/dist/node/node.utils.d.ts +5 -0
- package/dist/node/node.utils.d.ts.map +1 -0
- package/dist/node/node.utils.js +36 -0
- package/dist/node/node.utils.js.map +1 -0
- package/dist/node/task-node.d.ts +20 -0
- package/dist/node/task-node.d.ts.map +1 -0
- package/dist/node/task-node.js +245 -0
- package/dist/node/task-node.js.map +1 -0
- package/dist/node/trigger-node.d.ts +18 -0
- package/dist/node/trigger-node.d.ts.map +1 -0
- package/dist/node/trigger-node.js +150 -0
- package/dist/node/trigger-node.js.map +1 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +19 -0
- package/dist/node.js.map +1 -0
- package/dist/parcel/parcel-manager.d.ts +1 -1
- package/dist/parcel/parcel-manager.d.ts.map +1 -1
- package/dist/parcel/parcel-manager.js +1 -1
- package/dist/parcel/parcel-manager.js.map +1 -1
- package/dist/repo/repo-context.utils.d.ts +24 -0
- package/dist/repo/repo-context.utils.d.ts.map +1 -0
- package/dist/repo/repo-context.utils.js +81 -0
- package/dist/repo/repo-context.utils.js.map +1 -0
- package/dist/repo/repo-manager.d.ts +42 -0
- package/dist/repo/repo-manager.d.ts.map +1 -0
- package/dist/repo/repo-manager.js +166 -0
- package/dist/repo/repo-manager.js.map +1 -0
- package/dist/repo/repo.api.d.ts +16 -0
- package/dist/repo/repo.api.d.ts.map +1 -0
- package/dist/repo/repo.api.js +40 -0
- package/dist/repo/repo.api.js.map +1 -0
- package/dist/repo/repo.interface.d.ts +12 -5
- package/dist/repo/repo.interface.d.ts.map +1 -1
- package/dist/repo/repo.interface.js +2 -2
- package/dist/repo/repo.interface.js.map +1 -1
- package/dist/repo/repo.script-loader.d.ts +19 -0
- package/dist/repo/repo.script-loader.d.ts.map +1 -0
- package/dist/repo/repo.script-loader.js +108 -0
- package/dist/repo/repo.script-loader.js.map +1 -0
- package/dist/shared/log/create-api-logger.d.ts +21 -0
- package/dist/shared/log/create-api-logger.d.ts.map +1 -0
- package/dist/shared/log/create-api-logger.js +19 -0
- package/dist/shared/log/create-api-logger.js.map +1 -0
- package/dist/shared/log/index.d.ts +10 -0
- package/dist/shared/log/index.d.ts.map +1 -0
- package/dist/shared/log/index.js +10 -0
- package/dist/shared/log/index.js.map +1 -0
- package/dist/shared/log/log-manager.d.ts +93 -0
- package/dist/shared/log/log-manager.d.ts.map +1 -0
- package/dist/shared/log/log-manager.js +237 -0
- package/dist/shared/log/log-manager.js.map +1 -0
- package/dist/shared/log/log-scope.d.ts +49 -0
- package/dist/shared/log/log-scope.d.ts.map +1 -0
- package/dist/shared/log/log-scope.js +70 -0
- package/dist/shared/log/log-scope.js.map +1 -0
- package/dist/shared/utils/config-path.utils.d.ts +38 -0
- package/dist/shared/utils/config-path.utils.d.ts.map +1 -0
- package/dist/shared/utils/config-path.utils.js +85 -0
- package/dist/shared/utils/config-path.utils.js.map +1 -0
- package/dist/shared/utils/error.utils.d.ts +11 -0
- package/dist/shared/utils/error.utils.d.ts.map +1 -0
- package/dist/shared/utils/error.utils.js +15 -0
- package/dist/shared/utils/error.utils.js.map +1 -0
- package/dist/shared/utils/id.utils.d.ts +3 -0
- package/dist/shared/utils/id.utils.d.ts.map +1 -0
- package/dist/shared/utils/id.utils.js +5 -0
- package/dist/shared/utils/id.utils.js.map +1 -0
- package/dist/supabase.js +1 -1
- package/dist/supabase.js.map +1 -1
- package/dist/task/index.d.ts +15 -0
- package/dist/task/index.d.ts.map +1 -0
- package/dist/task/index.js +14 -0
- package/dist/task/index.js.map +1 -0
- package/dist/task/local-task-registry.d.ts +37 -0
- package/dist/task/local-task-registry.d.ts.map +1 -0
- package/dist/task/local-task-registry.js +214 -0
- package/dist/task/local-task-registry.js.map +1 -0
- package/dist/task/task-manager.d.ts +64 -0
- package/dist/task/task-manager.d.ts.map +1 -0
- package/dist/task/task-manager.js +196 -0
- package/dist/task/task-manager.js.map +1 -0
- package/dist/task/task-post.api.d.ts +49 -0
- package/dist/task/task-post.api.d.ts.map +1 -0
- package/dist/task/task-post.api.js +91 -0
- package/dist/task/task-post.api.js.map +1 -0
- package/dist/task/task-repo-context.d.ts +97 -0
- package/dist/task/task-repo-context.d.ts.map +1 -0
- package/dist/task/task-repo-context.js +12 -0
- package/dist/task/task-repo-context.js.map +1 -0
- package/dist/task/task-repo-validation.d.ts +46 -0
- package/dist/task/task-repo-validation.d.ts.map +1 -0
- package/dist/task/task-repo-validation.js +96 -0
- package/dist/task/task-repo-validation.js.map +1 -0
- package/dist/task/task.api.d.ts +198 -0
- package/dist/task/task.api.d.ts.map +1 -1
- package/dist/task/task.api.js +302 -1
- package/dist/task/task.api.js.map +1 -1
- package/dist/task/task.interface.d.ts +11 -2
- package/dist/task/task.interface.d.ts.map +1 -1
- package/dist/task/task.interface.js +10 -0
- package/dist/task/task.interface.js.map +1 -1
- package/dist/trigger/trigger.api.d.ts +140 -0
- package/dist/trigger/trigger.api.d.ts.map +1 -0
- package/dist/trigger/trigger.api.js +125 -0
- package/dist/trigger/trigger.api.js.map +1 -0
- package/dist/trigger/trigger.interface.d.ts +43 -0
- package/dist/trigger/trigger.interface.d.ts.map +1 -0
- package/dist/trigger/trigger.interface.js +11 -0
- package/dist/trigger/trigger.interface.js.map +1 -0
- package/dist/trigger/trigger.utils.d.ts +6 -0
- package/dist/trigger/trigger.utils.d.ts.map +1 -0
- package/dist/trigger/trigger.utils.js +39 -0
- package/dist/trigger/trigger.utils.js.map +1 -0
- package/package.json +30 -8
- package/src/auth/auth.api.ts +3 -1
- package/src/browser.ts +100 -0
- package/src/command/command.api.ts +88 -0
- package/src/command/command.interface.ts +17 -0
- package/src/core.api.ts +305 -26
- package/src/file/file-manager.ts +1 -1
- package/src/index.ts +2 -64
- package/src/link/link.interface.ts +1 -1
- package/src/node/node-log-scope.ts +15 -0
- package/src/node/node-runtime.base.ts +450 -0
- package/src/node/node-task.preset.ts +13 -0
- package/src/node/node.api.ts +95 -117
- package/src/node/node.interface.ts +8 -0
- package/src/node/node.utils.ts +36 -0
- package/src/node/task-node.ts +284 -0
- package/src/node/trigger-node.ts +180 -0
- package/src/node.ts +62 -0
- package/src/parcel/parcel-manager.ts +1 -1
- package/src/repo/repo-context.utils.ts +111 -0
- package/src/repo/repo-manager.ts +245 -0
- package/src/repo/repo.api.ts +50 -0
- package/src/repo/repo.interface.ts +13 -5
- package/src/repo/repo.script-loader.ts +140 -0
- package/src/shared/log/create-api-logger.ts +36 -0
- package/src/shared/log/index.ts +23 -0
- package/src/shared/log/log-manager.ts +335 -0
- package/src/shared/log/log-scope.ts +123 -0
- package/src/shared/utils/config-path.utils.ts +104 -0
- package/src/shared/utils/error.utils.ts +15 -0
- package/src/shared/utils/id.utils.ts +4 -0
- package/src/supabase.ts +1 -1
- package/src/task/index.ts +48 -0
- package/src/task/local-task-registry.ts +282 -0
- package/src/task/task-manager.ts +301 -0
- package/src/task/task-post.api.ts +116 -0
- package/src/task/task-repo-context.ts +105 -0
- package/src/task/task-repo-validation.ts +171 -0
- package/src/task/task.api.ts +369 -0
- package/src/task/task.interface.ts +12 -2
- package/src/trigger/trigger.api.ts +166 -0
- package/src/trigger/trigger.interface.ts +50 -0
- package/src/trigger/trigger.utils.ts +56 -0
- package/dist/task/task-node-manager.d.ts +0 -1
- package/dist/task/task-node-manager.d.ts.map +0 -1
- package/dist/task/task-node-manager.js +0 -2
- package/dist/task/task-node-manager.js.map +0 -1
- package/src/task/task-node-manager.ts +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
|
|
6
|
+
import { LoggerWithScope } from "../shared/log";
|
|
7
|
+
import { resolvePathFromCwd, toFileImportHref } from "../shared/utils/config-path.utils";
|
|
8
|
+
import { TaskRepoScriptRecord } from "../task/task-repo-context";
|
|
9
|
+
import {
|
|
10
|
+
getScriptLoadKey,
|
|
11
|
+
LoadedRepoContext,
|
|
12
|
+
normalizeRepoContextModule,
|
|
13
|
+
} from "./repo-context.utils";
|
|
14
|
+
|
|
15
|
+
const importedModuleCache = new Map<string, LoadedRepoContext>();
|
|
16
|
+
|
|
17
|
+
async function importFromFilePath(modulePath: string): Promise<unknown> {
|
|
18
|
+
return import(toFileImportHref(modulePath));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Resolve repo `details.url` to a value suitable for dynamic `import()`. */
|
|
22
|
+
export function resolveRepoEntryHref(url: string, cwd = process.cwd()): string {
|
|
23
|
+
const trimmed = url.trim();
|
|
24
|
+
if (trimmed.includes("://")) {
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
return toFileImportHref(resolvePathFromCwd(cwd, trimmed));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function importFromSource(source: string, cacheKey: string): Promise<unknown> {
|
|
31
|
+
const hash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
32
|
+
const dir = join(tmpdir(), "target-supabase-sdk", "repo-scripts");
|
|
33
|
+
await mkdir(dir, { recursive: true });
|
|
34
|
+
const filePath = join(dir, `${cacheKey}-${hash}.mjs`);
|
|
35
|
+
await writeFile(filePath, source, "utf8");
|
|
36
|
+
return importFromFilePath(filePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function loadRepoContextFromScript({
|
|
40
|
+
logger,
|
|
41
|
+
script,
|
|
42
|
+
taskTypeKey,
|
|
43
|
+
}: {
|
|
44
|
+
logger: LoggerWithScope;
|
|
45
|
+
script: TaskRepoScriptRecord;
|
|
46
|
+
taskTypeKey: string;
|
|
47
|
+
}): Promise<LoadedRepoContext | null> {
|
|
48
|
+
const loadKey = getScriptLoadKey(script);
|
|
49
|
+
const cacheKey = `${taskTypeKey}@${loadKey}`;
|
|
50
|
+
const cached = importedModuleCache.get(cacheKey);
|
|
51
|
+
if (cached != null) {
|
|
52
|
+
logger.info("命中 Repo 脚本模块缓存", { topic: "repo", data: { cacheKey } });
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { source, modulePath, exportName } = script.details;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
let moduleExports: unknown;
|
|
60
|
+
if (source != null && source.trim() !== "") {
|
|
61
|
+
logger.info("从 source 动态加载 Repo 脚本", { topic: "repo", data: { scriptId: script.id } });
|
|
62
|
+
moduleExports = await importFromSource(source, cacheKey);
|
|
63
|
+
} else if (modulePath != null && modulePath.trim() !== "") {
|
|
64
|
+
logger.info("从 modulePath 动态加载 Repo 脚本", {
|
|
65
|
+
topic: "repo",
|
|
66
|
+
data: { scriptId: script.id, modulePath },
|
|
67
|
+
});
|
|
68
|
+
moduleExports = await importFromFilePath(modulePath);
|
|
69
|
+
} else {
|
|
70
|
+
logger.warn("脚本缺少 source 或 modulePath", { topic: "repo", data: { scriptId: script.id } });
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const context = normalizeRepoContextModule(moduleExports, taskTypeKey, exportName);
|
|
75
|
+
if (context == null) {
|
|
76
|
+
logger.warn("脚本模块未导出合法的 TaskRepoContext", {
|
|
77
|
+
topic: "repo",
|
|
78
|
+
data: { scriptId: script.id, exportName: exportName ?? "default" },
|
|
79
|
+
});
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const loaded: LoadedRepoContext = { context, contentHash: loadKey };
|
|
84
|
+
importedModuleCache.set(cacheKey, loaded);
|
|
85
|
+
return loaded;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.error("动态加载 Repo 脚本失败", {
|
|
88
|
+
topic: "repo",
|
|
89
|
+
data: { scriptId: script.id, error: error instanceof Error ? error.message : error },
|
|
90
|
+
});
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function loadRepoContextFromUrl({
|
|
96
|
+
logger,
|
|
97
|
+
url,
|
|
98
|
+
taskTypeKey,
|
|
99
|
+
exportName,
|
|
100
|
+
}: {
|
|
101
|
+
logger: LoggerWithScope;
|
|
102
|
+
url: string;
|
|
103
|
+
taskTypeKey: string;
|
|
104
|
+
exportName?: string;
|
|
105
|
+
}): Promise<LoadedRepoContext | null> {
|
|
106
|
+
const href = resolveRepoEntryHref(url);
|
|
107
|
+
const cacheKey = `${taskTypeKey}@url:${href}`;
|
|
108
|
+
const cached = importedModuleCache.get(cacheKey);
|
|
109
|
+
if (cached != null) {
|
|
110
|
+
logger.info("命中 Repo URL 模块缓存", { topic: "repo", data: { cacheKey } });
|
|
111
|
+
return cached;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
logger.info("从 Repo URL 动态加载脚本", { topic: "repo", data: { url, href } });
|
|
116
|
+
const moduleExports = await import(href);
|
|
117
|
+
const context = normalizeRepoContextModule(moduleExports, taskTypeKey, exportName);
|
|
118
|
+
if (context == null) {
|
|
119
|
+
logger.warn("Repo URL 模块未导出合法的 TaskRepoContext", {
|
|
120
|
+
topic: "repo",
|
|
121
|
+
data: { url, exportName: exportName ?? "default" },
|
|
122
|
+
});
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const loaded: LoadedRepoContext = { context, contentHash: cacheKey };
|
|
126
|
+
importedModuleCache.set(cacheKey, loaded);
|
|
127
|
+
return loaded;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.error("从 Repo URL 动态加载失败", {
|
|
130
|
+
topic: "repo",
|
|
131
|
+
data: { url, error: error instanceof Error ? error.message : error },
|
|
132
|
+
});
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Clear in-memory imported module cache (tests / hot reload). */
|
|
138
|
+
export function clearRepoScriptModuleCache(): void {
|
|
139
|
+
importedModuleCache.clear();
|
|
140
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { logManager, type LoggerWithScope } from "./log-manager";
|
|
2
|
+
import { normalizeScope, type LogScope, type LogScopePatch } from "./log-scope";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link createApiLogger}.
|
|
6
|
+
*
|
|
7
|
+
* - **Trace:** optional `traceId` / `traceParentId` (generated root when traceId omitted)
|
|
8
|
+
* - **Labels:** optional `labels` (e.g. `{ nodeId }`)
|
|
9
|
+
*/
|
|
10
|
+
export type CreateApiLoggerOptions = {
|
|
11
|
+
traceId?: string;
|
|
12
|
+
traceParentId?: string | null;
|
|
13
|
+
labels?: Record<string, string>;
|
|
14
|
+
} & Partial<Omit<LogScope, "module" | "traceId" | "traceParentId" | "labels">>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Logger for one SDK API invocation.
|
|
18
|
+
*
|
|
19
|
+
* Binds `module` + normalized {@link LogScope}.
|
|
20
|
+
*/
|
|
21
|
+
export function createApiLogger(module: string, options?: CreateApiLoggerOptions): LoggerWithScope {
|
|
22
|
+
const { traceId, traceParentId, labels, ...rest } = options ?? {};
|
|
23
|
+
const scope = normalizeScope(
|
|
24
|
+
{
|
|
25
|
+
module,
|
|
26
|
+
traceId,
|
|
27
|
+
traceParentId: traceParentId ?? null,
|
|
28
|
+
labels,
|
|
29
|
+
...rest,
|
|
30
|
+
},
|
|
31
|
+
{ generateTraceId: true }
|
|
32
|
+
);
|
|
33
|
+
return logManager.withScope(scope).logger;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type { LogScopePatch };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log domain public API — curated re-exports only.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// log-manager
|
|
6
|
+
export { logManager, LogManager, LogLevel } from "./log-manager";
|
|
7
|
+
export type { LogEntry, LogOptions, LoggerWithScope, LogRestParams } from "./log-manager";
|
|
8
|
+
|
|
9
|
+
// create-api-logger
|
|
10
|
+
export { createApiLogger } from "./create-api-logger";
|
|
11
|
+
export type { CreateApiLoggerOptions } from "./create-api-logger";
|
|
12
|
+
|
|
13
|
+
// log-scope
|
|
14
|
+
export {
|
|
15
|
+
applyScopePatch,
|
|
16
|
+
createChildScope,
|
|
17
|
+
createRootScope,
|
|
18
|
+
formatScopeLabels,
|
|
19
|
+
normalizeScope,
|
|
20
|
+
resolveLogData,
|
|
21
|
+
withModule,
|
|
22
|
+
} from "./log-scope";
|
|
23
|
+
export type { LogScope, LogScopeInput, LogScopePatch, CreateRootScopeInput, CreateChildScopeInput } from "./log-scope";
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { isDevEnvironment } from "../../core.utils";
|
|
2
|
+
import { generateUniqueId } from "../utils/id.utils";
|
|
3
|
+
import {
|
|
4
|
+
applyScopePatch,
|
|
5
|
+
formatScopeLabels,
|
|
6
|
+
resolveLogData,
|
|
7
|
+
type LogScope,
|
|
8
|
+
type LogScopePatch,
|
|
9
|
+
} from "./log-scope";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Log severity and semantics (low → high). `minLevel` filters by {@link LOG_LEVEL_RANK}.
|
|
13
|
+
*
|
|
14
|
+
* | Level | Rank | When to use |
|
|
15
|
+
* |----------|------|-------------|
|
|
16
|
+
* | DEBUG | 0 | Diagnostic detail for dev/troubleshooting; noisy, safe to drop in prod. |
|
|
17
|
+
* | INFO | 1 | Normal operational flow (startup, polling, state transitions). |
|
|
18
|
+
* | SUCCESS | 2 | Positive milestone within a flow (not a separate severity axis — same band as INFO). |
|
|
19
|
+
* | WARN | 3 | Recoverable anomaly or degradation; work continues but worth watching. |
|
|
20
|
+
* | ERROR | 4 | A concrete operation failed (API, task step, I/O); investigate and retry/fix locally. |
|
|
21
|
+
* | CRITICAL | 5 | Invariant violated — design flaw, data-integrity risk, or business rule broken; escalate and fix urgently. |
|
|
22
|
+
*/
|
|
23
|
+
enum LogLevel {
|
|
24
|
+
DEBUG = "DEBUG",
|
|
25
|
+
INFO = "INFO",
|
|
26
|
+
SUCCESS = "SUCCESS",
|
|
27
|
+
WARN = "WARN",
|
|
28
|
+
ERROR = "ERROR",
|
|
29
|
+
CRITICAL = "CRITICAL",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const LOG_LEVEL_RANK: Record<LogLevel, number> = {
|
|
33
|
+
[LogLevel.DEBUG]: 0,
|
|
34
|
+
[LogLevel.INFO]: 1,
|
|
35
|
+
[LogLevel.SUCCESS]: 2,
|
|
36
|
+
[LogLevel.WARN]: 3,
|
|
37
|
+
[LogLevel.ERROR]: 4,
|
|
38
|
+
[LogLevel.CRITICAL]: 5,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
interface LogEntry extends LogScope {
|
|
42
|
+
timestamp: number;
|
|
43
|
+
level: LogLevel;
|
|
44
|
+
message: string;
|
|
45
|
+
topic: string | null;
|
|
46
|
+
extra?: string;
|
|
47
|
+
/** Per-line structured business payload. */
|
|
48
|
+
data?: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type LogRestParams = {
|
|
52
|
+
topic?: LogEntry["topic"];
|
|
53
|
+
extra?: LogEntry["extra"];
|
|
54
|
+
data?: unknown;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type LogParams = { message: LogEntry["message"] } & LogRestParams;
|
|
58
|
+
|
|
59
|
+
interface LogEntryPayload extends LogScope {
|
|
60
|
+
message: LogEntry["message"];
|
|
61
|
+
topic?: LogEntry["topic"];
|
|
62
|
+
extra?: LogEntry["extra"];
|
|
63
|
+
data?: LogEntry["data"];
|
|
64
|
+
level: LogEntry["level"];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface LogOptions {
|
|
68
|
+
defaultTopic: LogEntry["topic"];
|
|
69
|
+
formatTimestamp: boolean;
|
|
70
|
+
formatEmoji: boolean;
|
|
71
|
+
formatPrefix: string;
|
|
72
|
+
maxHistoryLength: number;
|
|
73
|
+
/** Logs below this level are skipped. Defaults to DEBUG in dev, INFO in production. */
|
|
74
|
+
minLevel?: LogLevel;
|
|
75
|
+
onLog?: (entry: LogEntry) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const DEFAULT_OPTIONS: LogOptions = {
|
|
79
|
+
defaultTopic: null,
|
|
80
|
+
formatTimestamp: true,
|
|
81
|
+
formatEmoji: true,
|
|
82
|
+
formatPrefix: "",
|
|
83
|
+
maxHistoryLength: 4096,
|
|
84
|
+
minLevel: isDevEnvironment() ? LogLevel.DEBUG : LogLevel.INFO,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
interface LoggerWithScope {
|
|
88
|
+
debug: (message: LogParams["message"], restParams?: LogRestParams) => void;
|
|
89
|
+
info: (message: LogParams["message"], restParams?: LogRestParams) => void;
|
|
90
|
+
success: (message: LogParams["message"], restParams?: LogRestParams) => void;
|
|
91
|
+
warn: (message: LogParams["message"], restParams?: LogRestParams) => void;
|
|
92
|
+
error: (message: LogParams["message"], restParams?: LogRestParams) => void;
|
|
93
|
+
critical: (message: LogParams["message"], restParams?: LogRestParams) => void;
|
|
94
|
+
/** Merge partial fields into this logger's bound scope. */
|
|
95
|
+
resetScope: (fields: LogScopePatch) => void;
|
|
96
|
+
/** @deprecated use {@link LoggerWithScope.resetScope} */
|
|
97
|
+
resetContext: (fields: LogScopePatch) => void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function mergeLogOptions(options?: Partial<LogOptions>): LogOptions {
|
|
101
|
+
return { ...DEFAULT_OPTIONS, ...options };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatDataValue(value: unknown): string {
|
|
105
|
+
if (value === undefined) {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
if (typeof value === "string") {
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
return JSON.stringify(value);
|
|
113
|
+
} catch {
|
|
114
|
+
return String(value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateLogScope(logScope: LogScope): void {
|
|
119
|
+
const missing: string[] = [];
|
|
120
|
+
if (!logScope.module?.trim()) {
|
|
121
|
+
missing.push("module");
|
|
122
|
+
}
|
|
123
|
+
if (!logScope.traceId?.trim()) {
|
|
124
|
+
missing.push("traceId");
|
|
125
|
+
}
|
|
126
|
+
if (logScope.traceParentId !== null && typeof logScope.traceParentId !== "string") {
|
|
127
|
+
missing.push("traceParentId");
|
|
128
|
+
}
|
|
129
|
+
if (missing.length > 0) {
|
|
130
|
+
console.warn(`[LogManager] LogScope missing or invalid: ${missing.join(", ")}`, logScope);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class LogManager {
|
|
135
|
+
private static instance: LogManager;
|
|
136
|
+
private options: LogOptions;
|
|
137
|
+
private history: LogEntry[] = [];
|
|
138
|
+
private readonly levelEmojis = {
|
|
139
|
+
[LogLevel.DEBUG]: "🔍",
|
|
140
|
+
[LogLevel.INFO]: "ℹ️",
|
|
141
|
+
[LogLevel.SUCCESS]: "✅",
|
|
142
|
+
[LogLevel.WARN]: "⚠️",
|
|
143
|
+
[LogLevel.ERROR]: "❌",
|
|
144
|
+
[LogLevel.CRITICAL]: "💀",
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
private constructor(options?: Partial<LogOptions>) {
|
|
148
|
+
this.options = mergeLogOptions(options);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public static getInstance(options?: Partial<LogOptions>): LogManager {
|
|
152
|
+
if (!LogManager.instance) {
|
|
153
|
+
LogManager.instance = new LogManager(options);
|
|
154
|
+
} else if (options != null && Object.keys(options).length > 0) {
|
|
155
|
+
console.warn("[LogManager] getInstance(options) ignored — singleton already created. Use setOptions() instead.");
|
|
156
|
+
}
|
|
157
|
+
return LogManager.instance;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public setOptions(options: Partial<LogOptions>): void {
|
|
161
|
+
this.options = mergeLogOptions({ ...this.options, ...options });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public getHistory(): LogEntry[] {
|
|
165
|
+
return [...this.history];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public clearHistory(): void {
|
|
169
|
+
this.history = [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private getMinLevel(): LogLevel {
|
|
173
|
+
return this.options.minLevel ?? DEFAULT_OPTIONS.minLevel ?? LogLevel.INFO;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private shouldLog(level: LogLevel): boolean {
|
|
177
|
+
return LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[this.getMinLevel()];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private createLogEntry(payload: LogEntryPayload): LogEntry {
|
|
181
|
+
const { level, message, topic, extra, data, module, traceId, traceParentId, labels } = payload;
|
|
182
|
+
return {
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
level,
|
|
185
|
+
message,
|
|
186
|
+
topic: topic === undefined ? this.options.defaultTopic : topic,
|
|
187
|
+
module,
|
|
188
|
+
traceId,
|
|
189
|
+
traceParentId,
|
|
190
|
+
labels,
|
|
191
|
+
extra,
|
|
192
|
+
data,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private formatLevelTag(entry: LogEntry): string {
|
|
197
|
+
const emoji = this.options.formatEmoji ? `${this.levelEmojis[entry.level]} ` : "";
|
|
198
|
+
if (entry.level === LogLevel.CRITICAL) {
|
|
199
|
+
return `[${emoji}*** ${entry.level} ***]`;
|
|
200
|
+
}
|
|
201
|
+
return `[${emoji}${entry.level}]`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private formatLog(entry: LogEntry): string {
|
|
205
|
+
const parts: string[] = [];
|
|
206
|
+
|
|
207
|
+
if (this.options.formatTimestamp) {
|
|
208
|
+
parts.push(`[${new Date(entry.timestamp).toISOString()}]`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
parts.push(this.formatLevelTag(entry));
|
|
212
|
+
parts.push(
|
|
213
|
+
`traceId=${entry.traceId} parent=${entry.traceParentId ?? "null"} labels=${formatScopeLabels(entry.labels)} module=${entry.module} topic=${entry.topic ?? "--"}`
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (this.options.formatPrefix) {
|
|
217
|
+
parts.push(`prefix=${this.options.formatPrefix}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const message =
|
|
221
|
+
entry.level === LogLevel.CRITICAL ? `>>> ${entry.message} <<<` : entry.message;
|
|
222
|
+
parts.push(`| ${message}`);
|
|
223
|
+
|
|
224
|
+
if (entry.extra) {
|
|
225
|
+
parts.push(`| extra=${entry.extra}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const dataText = formatDataValue(entry.data);
|
|
229
|
+
if (dataText) {
|
|
230
|
+
parts.push(`| data=${dataText}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return parts.join(" ");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private emitLog(entry: LogEntry, formatted: string): void {
|
|
237
|
+
switch (entry.level) {
|
|
238
|
+
case LogLevel.WARN:
|
|
239
|
+
console.warn(formatted);
|
|
240
|
+
break;
|
|
241
|
+
case LogLevel.ERROR:
|
|
242
|
+
case LogLevel.CRITICAL:
|
|
243
|
+
console.error(formatted);
|
|
244
|
+
break;
|
|
245
|
+
default:
|
|
246
|
+
console.log(formatted);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private log(level: LogLevel, message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
251
|
+
if (!this.shouldLog(level)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
validateLogScope(logScope);
|
|
256
|
+
|
|
257
|
+
const { topic, extra } = restParams ?? {};
|
|
258
|
+
const data = resolveLogData(restParams);
|
|
259
|
+
const entry = this.createLogEntry({
|
|
260
|
+
level,
|
|
261
|
+
message,
|
|
262
|
+
topic,
|
|
263
|
+
extra,
|
|
264
|
+
data,
|
|
265
|
+
...logScope,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
this.history.push(entry);
|
|
269
|
+
if (this.history.length > this.options.maxHistoryLength) {
|
|
270
|
+
this.history.shift();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const formatted = this.formatLog(entry);
|
|
274
|
+
this.emitLog(entry, formatted);
|
|
275
|
+
this.options.onLog?.(entry);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public generateTraceId(): string {
|
|
279
|
+
return generateUniqueId();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public debug(message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
283
|
+
this.log(LogLevel.DEBUG, message, logScope, restParams);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public info(message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
287
|
+
this.log(LogLevel.INFO, message, logScope, restParams);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public success(message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
291
|
+
this.log(LogLevel.SUCCESS, message, logScope, restParams);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public warn(message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
295
|
+
this.log(LogLevel.WARN, message, logScope, restParams);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
public error(message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
299
|
+
this.log(LogLevel.ERROR, message, logScope, restParams);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public critical(message: LogParams["message"], logScope: LogScope, restParams?: LogRestParams) {
|
|
303
|
+
this.log(LogLevel.CRITICAL, message, logScope, restParams);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public withScope(scope: LogScope): { logger: LoggerWithScope; scope: LogScope } {
|
|
307
|
+
const boundScope: LogScope = { ...scope, labels: scope.labels ? { ...scope.labels } : undefined };
|
|
308
|
+
|
|
309
|
+
const logger: LoggerWithScope = {
|
|
310
|
+
debug: (message, restParams) => this.debug(message, boundScope, restParams),
|
|
311
|
+
info: (message, restParams) => this.info(message, boundScope, restParams),
|
|
312
|
+
success: (message, restParams) => this.success(message, boundScope, restParams),
|
|
313
|
+
warn: (message, restParams) => this.warn(message, boundScope, restParams),
|
|
314
|
+
error: (message, restParams) => this.error(message, boundScope, restParams),
|
|
315
|
+
critical: (message, restParams) => this.critical(message, boundScope, restParams),
|
|
316
|
+
resetScope: (fields) => {
|
|
317
|
+
const next = applyScopePatch(boundScope, fields);
|
|
318
|
+
boundScope.module = next.module;
|
|
319
|
+
boundScope.traceId = next.traceId;
|
|
320
|
+
boundScope.traceParentId = next.traceParentId;
|
|
321
|
+
boundScope.labels = next.labels;
|
|
322
|
+
},
|
|
323
|
+
resetContext: (fields) => {
|
|
324
|
+
logger.resetScope(fields);
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
return { logger, scope: boundScope };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export const logManager = LogManager.getInstance();
|
|
334
|
+
export { LogManager, LogLevel };
|
|
335
|
+
export type { LogOptions, LogEntry, LoggerWithScope, LogRestParams };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { generateUniqueId } from "../utils/id.utils";
|
|
2
|
+
|
|
3
|
+
/** Logger-bound scope: module + trace chain + optional labels. */
|
|
4
|
+
export interface LogScope {
|
|
5
|
+
module: string;
|
|
6
|
+
traceId: string;
|
|
7
|
+
traceParentId: string | null;
|
|
8
|
+
labels?: Readonly<Record<string, string>>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Partial update for {@link LogScope} / {@link applyScopePatch}. */
|
|
12
|
+
export type LogScopePatch = Partial<Omit<LogScope, "labels">> & {
|
|
13
|
+
labels?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type LogScopeInput = LogScopePatch & Pick<LogScope, "module">;
|
|
17
|
+
|
|
18
|
+
interface NormalizeScopeOptions {
|
|
19
|
+
/** When true and traceId is missing, generate a new id. */
|
|
20
|
+
generateTraceId?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mergeLabels(
|
|
24
|
+
base?: Readonly<Record<string, string>>,
|
|
25
|
+
patch?: Record<string, string>
|
|
26
|
+
): Record<string, string> | undefined {
|
|
27
|
+
const merged: Record<string, string> = { ...base, ...patch };
|
|
28
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Coerce partial input into a {@link LogScope}. */
|
|
32
|
+
export function normalizeScope(input: LogScopeInput, options?: NormalizeScopeOptions): LogScope {
|
|
33
|
+
const generateTraceId = options?.generateTraceId ?? false;
|
|
34
|
+
const traceId = input.traceId?.trim() || (generateTraceId ? generateUniqueId() : "");
|
|
35
|
+
if (!traceId) {
|
|
36
|
+
throw new Error("[LogScope] traceId is required (or enable generateTraceId)");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
module: input.module,
|
|
41
|
+
traceId,
|
|
42
|
+
traceParentId: input.traceParentId ?? null,
|
|
43
|
+
labels: mergeLabels(undefined, input.labels),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Input for {@link createRootScope}. */
|
|
48
|
+
export type CreateRootScopeInput = {
|
|
49
|
+
module: string;
|
|
50
|
+
traceId?: string;
|
|
51
|
+
labels?: Record<string, string>;
|
|
52
|
+
traceParentId?: string | null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Root scope: traceParentId defaults to null. */
|
|
56
|
+
export function createRootScope(input: CreateRootScopeInput): LogScope {
|
|
57
|
+
return normalizeScope(
|
|
58
|
+
{
|
|
59
|
+
module: input.module,
|
|
60
|
+
traceId: input.traceId,
|
|
61
|
+
traceParentId: input.traceParentId ?? null,
|
|
62
|
+
labels: input.labels,
|
|
63
|
+
},
|
|
64
|
+
{ generateTraceId: true }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Input for {@link createChildScope}. */
|
|
69
|
+
export type CreateChildScopeInput = {
|
|
70
|
+
module: string;
|
|
71
|
+
parent: LogScope;
|
|
72
|
+
traceId?: string;
|
|
73
|
+
traceParentId?: string | null;
|
|
74
|
+
labels?: Record<string, string>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Child scope: parent link defaults to parent.traceId; labels merge with parent. */
|
|
78
|
+
export function createChildScope(input: CreateChildScopeInput): LogScope {
|
|
79
|
+
const { module, parent, traceId, traceParentId, labels } = input;
|
|
80
|
+
return normalizeScope(
|
|
81
|
+
{
|
|
82
|
+
module,
|
|
83
|
+
traceId,
|
|
84
|
+
traceParentId: traceParentId ?? parent.traceId,
|
|
85
|
+
labels: mergeLabels(parent.labels, labels),
|
|
86
|
+
},
|
|
87
|
+
{ generateTraceId: true }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Same trace chain, different module (e.g. prepareTask vs executeTask). */
|
|
92
|
+
export function withModule(scope: LogScope, module: string): LogScope {
|
|
93
|
+
return { ...scope, module };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Merge patch into scope (labels deep-merge). */
|
|
97
|
+
export function applyScopePatch(scope: LogScope, patch: LogScopePatch): LogScope {
|
|
98
|
+
return normalizeScope({
|
|
99
|
+
module: patch.module ?? scope.module,
|
|
100
|
+
traceId: patch.traceId ?? scope.traceId,
|
|
101
|
+
traceParentId: patch.traceParentId !== undefined ? patch.traceParentId : scope.traceParentId,
|
|
102
|
+
labels: mergeLabels(scope.labels, patch.labels),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function formatScopeLabels(labels?: Readonly<Record<string, string>>): string {
|
|
106
|
+
if (labels == null || Object.keys(labels).length === 0) {
|
|
107
|
+
return "--";
|
|
108
|
+
}
|
|
109
|
+
return Object.entries(labels)
|
|
110
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
111
|
+
.join(",");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Prefer `data`; fall back to deprecated `context` on log rest params. */
|
|
115
|
+
export function resolveLogData(rest?: { data?: unknown; context?: unknown }): unknown | undefined {
|
|
116
|
+
if (rest?.data !== undefined) {
|
|
117
|
+
return rest.data;
|
|
118
|
+
}
|
|
119
|
+
if (rest?.context !== undefined) {
|
|
120
|
+
return rest.context;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|