@kici-dev/shared 0.0.0 → 0.1.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/LICENSE +202 -0
- package/README.md +1 -6
- package/dist/chunk-gOLHoazu.js +4 -0
- package/dist/cold-store/bucket.d.ts +26 -0
- package/dist/cold-store/bucket.js +40 -0
- package/dist/cold-store/bucket.test.d.ts +2 -0
- package/dist/cold-store/chunk-encoder.d.ts +63 -0
- package/dist/cold-store/chunk-encoder.js +94 -0
- package/dist/cold-store/chunk-encoder.test.d.ts +2 -0
- package/dist/cold-store/chunk-id.d.ts +10 -0
- package/dist/cold-store/chunk-id.js +29 -0
- package/dist/cold-store/chunk-id.test.d.ts +2 -0
- package/dist/cold-store/cold-store.d.ts +345 -0
- package/dist/cold-store/cold-store.js +1078 -0
- package/dist/cold-store/cold-store.test.d.ts +2 -0
- package/dist/cold-store/config.d.ts +62 -0
- package/dist/cold-store/config.js +28 -0
- package/dist/cold-store/index.d.ts +20 -0
- package/dist/cold-store/index.js +11 -0
- package/dist/cold-store/key.d.ts +89 -0
- package/dist/cold-store/key.js +88 -0
- package/dist/cold-store/key.test.d.ts +2 -0
- package/dist/cold-store/lru.d.ts +33 -0
- package/dist/cold-store/lru.js +59 -0
- package/dist/cold-store/lru.test.d.ts +2 -0
- package/dist/cold-store/manifest.d.ts +11 -0
- package/dist/cold-store/manifest.js +57 -0
- package/dist/cold-store/manifest.test.d.ts +2 -0
- package/dist/cold-store/metrics.d.ts +103 -0
- package/dist/cold-store/metrics.js +179 -0
- package/dist/cold-store/table-adapter.d.ts +229 -0
- package/dist/cold-store/table-adapter.js +2 -0
- package/dist/cold-store/types.d.ts +111 -0
- package/dist/cold-store/types.js +2 -0
- package/dist/crypto.d.ts +33 -0
- package/dist/crypto.js +67 -0
- package/dist/db-admin.d.ts +1422 -0
- package/dist/db-admin.js +2525 -0
- package/dist/db-admin.test.d.ts +2 -0
- package/dist/db.d.ts +14 -0
- package/dist/db.js +23 -0
- package/dist/env/allowlist.d.ts +79 -0
- package/dist/env/allowlist.js +86 -0
- package/dist/env/define-env.d.ts +172 -0
- package/dist/env/define-env.js +295 -0
- package/dist/env/define-env.test.d.ts +2 -0
- package/dist/env/env-rule-allowlist.test.d.ts +2 -0
- package/dist/env/index.d.ts +11 -0
- package/dist/env/index.js +4 -0
- package/dist/env/logger-env.d.ts +30 -0
- package/dist/env/logger-env.js +95 -0
- package/dist/error.d.ts +16 -0
- package/dist/error.js +58 -0
- package/dist/error.test.d.ts +2 -0
- package/dist/format-bytes.d.ts +5 -0
- package/dist/format-bytes.js +15 -0
- package/dist/format-bytes.test.d.ts +2 -0
- package/dist/format-duration.d.ts +11 -0
- package/dist/format-duration.js +32 -0
- package/dist/format-duration.test.d.ts +2 -0
- package/dist/graceful-shutdown.d.ts +58 -0
- package/dist/graceful-shutdown.js +71 -0
- package/dist/graceful-shutdown.test.d.ts +2 -0
- package/dist/idempotency-files.d.ts +113 -0
- package/dist/idempotency-files.js +190 -0
- package/dist/idempotency-files.test.d.ts +2 -0
- package/dist/idempotency.d.ts +68 -0
- package/dist/idempotency.js +49 -0
- package/dist/idempotency.test.d.ts +2 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +31 -0
- package/dist/logger.d.ts +57 -0
- package/dist/logger.js +175 -0
- package/dist/logger.test.d.ts +2 -0
- package/dist/reconnect-delay.d.ts +11 -0
- package/dist/reconnect-delay.js +22 -0
- package/dist/request-context.d.ts +42 -0
- package/dist/request-context.js +37 -0
- package/dist/ring-buffer.d.ts +35 -0
- package/dist/ring-buffer.js +62 -0
- package/dist/ring-buffer.test.d.ts +2 -0
- package/dist/routes/health.d.ts +26 -0
- package/dist/routes/health.js +45 -0
- package/dist/routes/health.test.d.ts +2 -0
- package/dist/routes/metrics.d.ts +20 -0
- package/dist/routes/metrics.js +38 -0
- package/dist/s3-client.d.ts +42 -0
- package/dist/s3-client.js +25 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/init.d.ts +20 -0
- package/dist/telemetry/init.js +42 -0
- package/dist/telemetry/init.test.d.ts +2 -0
- package/dist/telemetry/metrics.d.ts +8 -0
- package/dist/telemetry/metrics.js +16 -0
- package/dist/tool-check.d.ts +33 -0
- package/dist/tool-check.js +60 -0
- package/dist/tool-check.test.d.ts +2 -0
- package/dist/ts-loader-hook.d.ts +26 -0
- package/dist/ts-loader-hook.js +48 -0
- package/dist/zx.d.ts +8 -0
- package/dist/zx.js +78 -0
- package/package.json +69 -5
- package/sbom.spdx.json +8775 -0
- package/index.js +0 -3
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import "./chunk-gOLHoazu.js";
|
|
2
|
+
import { createTwoFilesPatch } from "diff";
|
|
3
|
+
//#region src/idempotency-files.ts
|
|
4
|
+
/**
|
|
5
|
+
* File-drift preview primitive for idempotent steps that copy or rsync
|
|
6
|
+
* files to a remote target. Companion to `idempotency.ts`: a `check()`
|
|
7
|
+
* that ships files can call into the helper layer (e.g. `previewRsync*`
|
|
8
|
+
* in `packages/ci/src/deploy-prod/remote.ts`) to get a typed list of
|
|
9
|
+
* per-file changes, then surface those entries inside the step's drift
|
|
10
|
+
* value so the confirm prompt shows the operator exactly which files
|
|
11
|
+
* would change and how.
|
|
12
|
+
*
|
|
13
|
+
* The source of truth for the categorisation is rsync's own
|
|
14
|
+
* `--itemize-changes` (`-i`) output, captured under `--dry-run` (`-n`).
|
|
15
|
+
* Running rsync with the same flags the apply path uses guarantees the
|
|
16
|
+
* preview and the actual transfer agree.
|
|
17
|
+
*
|
|
18
|
+
* The itemize code rsync prints is 11 characters: `YXcstpoguax`.
|
|
19
|
+
* Y = update type (`<` sent, `>` received, `.` no transfer, `*` message, `c`/`h`)
|
|
20
|
+
* X = file type (`f` file, `d` directory, `L` symlink, …)
|
|
21
|
+
* c = checksum mismatch (or `+` block when newly created)
|
|
22
|
+
* s = size mismatch
|
|
23
|
+
* t = mtime mismatch
|
|
24
|
+
* p = perms mismatch
|
|
25
|
+
* o = owner mismatch
|
|
26
|
+
* g = group mismatch
|
|
27
|
+
* u = (sub-second) mtime mismatch
|
|
28
|
+
* a = ACL mismatch
|
|
29
|
+
* x = xattr mismatch
|
|
30
|
+
* Newly-created files render as `<f+++++++++` (or `cf+++++++++` for local
|
|
31
|
+
* creates). See `rsync(1)` § "ITEMIZED OUTPUT".
|
|
32
|
+
*/
|
|
33
|
+
const ANSI_RED = "\x1B[31m";
|
|
34
|
+
const ANSI_GREEN = "\x1B[32m";
|
|
35
|
+
const ANSI_CYAN = "\x1B[36m";
|
|
36
|
+
const ANSI_BOLD = "\x1B[1m";
|
|
37
|
+
const ANSI_RESET = "\x1B[0m";
|
|
38
|
+
/**
|
|
39
|
+
* Parse one line of `rsync --itemize-changes` output. Returns null for
|
|
40
|
+
* lines that are not actionable file drift entries:
|
|
41
|
+
* - "sending incremental file list" header line
|
|
42
|
+
* - blank / whitespace-only lines
|
|
43
|
+
* - summary lines ("sent N bytes received M bytes …", "total size is X")
|
|
44
|
+
* - directory metadata entries (e.g. `.d..t...... ./`) — directories
|
|
45
|
+
* are created by rsync as a side-effect of file entries, no separate
|
|
46
|
+
* reporting needed
|
|
47
|
+
* - in-sync entries (rsync emits these only at -vv; we run at -v, so
|
|
48
|
+
* they normally don't appear — but we filter defensively)
|
|
49
|
+
*/
|
|
50
|
+
function parseItemizeLine(line) {
|
|
51
|
+
if (line.length < 13) return null;
|
|
52
|
+
const code = line.slice(0, 11);
|
|
53
|
+
const rest = line.slice(11);
|
|
54
|
+
if (!rest.startsWith(" ")) return null;
|
|
55
|
+
const relativePath = rest.slice(1).trimEnd();
|
|
56
|
+
if (relativePath.length === 0) return null;
|
|
57
|
+
if (!/^[<>.ch*]$/.test(code[0])) return null;
|
|
58
|
+
if (!/^[fdLDS]$/.test(code[1])) return null;
|
|
59
|
+
if (code[1] === "d") return null;
|
|
60
|
+
if (code[0] === "*") return null;
|
|
61
|
+
if (code.slice(2, 11) === "+++++++++") return {
|
|
62
|
+
itemizeCode: code,
|
|
63
|
+
relativePath,
|
|
64
|
+
category: "new"
|
|
65
|
+
};
|
|
66
|
+
const c = code[2];
|
|
67
|
+
const s = code[3];
|
|
68
|
+
const t = code[4];
|
|
69
|
+
const p = code[5];
|
|
70
|
+
const o = code[6];
|
|
71
|
+
const g = code[7];
|
|
72
|
+
const u = code[8];
|
|
73
|
+
const a = code[9];
|
|
74
|
+
const x = code[10];
|
|
75
|
+
if (c !== "." || s !== ".") return {
|
|
76
|
+
itemizeCode: code,
|
|
77
|
+
relativePath,
|
|
78
|
+
category: "content"
|
|
79
|
+
};
|
|
80
|
+
if (p !== "." || o !== "." || g !== "." || u !== "." || a !== "." || x !== ".") return {
|
|
81
|
+
itemizeCode: code,
|
|
82
|
+
relativePath,
|
|
83
|
+
category: "mode"
|
|
84
|
+
};
|
|
85
|
+
if (t !== ".") return {
|
|
86
|
+
itemizeCode: code,
|
|
87
|
+
relativePath,
|
|
88
|
+
category: "time-only"
|
|
89
|
+
};
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const CATEGORY_DISPLAY = {
|
|
93
|
+
new: "NEW",
|
|
94
|
+
content: "CONTENT",
|
|
95
|
+
mode: "MODE",
|
|
96
|
+
"time-only": "TIME"
|
|
97
|
+
};
|
|
98
|
+
const CATEGORY_ORDER = [
|
|
99
|
+
"new",
|
|
100
|
+
"content",
|
|
101
|
+
"mode",
|
|
102
|
+
"time-only"
|
|
103
|
+
];
|
|
104
|
+
const SKIP_REASON_DISPLAY = {
|
|
105
|
+
binary: "(binary file changed — no inline diff)",
|
|
106
|
+
"too-large": "(file too large for inline diff)",
|
|
107
|
+
"read-failed": "(content read failed — no inline diff)",
|
|
108
|
+
sensitive: "(sensitive content — diff suppressed)"
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Render the unified-diff block for a single FileDriftEntry. Returns an
|
|
112
|
+
* array of pre-indented lines (no surrounding indent applied by this
|
|
113
|
+
* function — the caller decides nesting). The first line is the diff
|
|
114
|
+
* header (`--- a/path` / `+++ b/path`), followed by hunk bodies.
|
|
115
|
+
*
|
|
116
|
+
* `mode` and `time-only` entries return an empty array — no diff to show.
|
|
117
|
+
* Entries with `contentSkipped` set return a single line explaining the
|
|
118
|
+
* skip reason. Entries with no content captured (older callers that
|
|
119
|
+
* didn't fill the fields) also return empty — the caller falls back to
|
|
120
|
+
* the label-only row.
|
|
121
|
+
*/
|
|
122
|
+
function renderFileDriftWithDiff(entry, opts = {}) {
|
|
123
|
+
if (entry.category === "mode" || entry.category === "time-only") return [];
|
|
124
|
+
if (entry.contentSkipped) return [SKIP_REASON_DISPLAY[entry.contentSkipped]];
|
|
125
|
+
const color = opts.color ?? false;
|
|
126
|
+
const maxLines = opts.maxLines ?? 200;
|
|
127
|
+
const before = entry.category === "new" ? "" : entry.remoteContent ?? "";
|
|
128
|
+
const after = entry.localContent ?? "";
|
|
129
|
+
if (before === "" && after === "" && entry.category !== "new") return [];
|
|
130
|
+
const rawLines = createTwoFilesPatch(`a/${entry.localPath}`, `b/${entry.localPath}`, before, after, void 0, void 0, { context: 3 }).split("\n");
|
|
131
|
+
const headerIdx = rawLines.findIndex((l) => l.startsWith("--- "));
|
|
132
|
+
const body = headerIdx >= 0 ? rawLines.slice(headerIdx) : rawLines;
|
|
133
|
+
const truncated = body.length > maxLines;
|
|
134
|
+
const lines = (truncated ? body.slice(0, maxLines) : body).map((line) => colorize(line, color));
|
|
135
|
+
if (truncated) lines.push(`… (${body.length - maxLines} more lines truncated; raw patch in log file)`);
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
function colorize(line, color) {
|
|
139
|
+
if (!color) return line;
|
|
140
|
+
if (line.startsWith("+++") || line.startsWith("---")) return `${ANSI_BOLD}${line}${ANSI_RESET}`;
|
|
141
|
+
if (line.startsWith("@@")) return `${ANSI_CYAN}${line}${ANSI_RESET}`;
|
|
142
|
+
if (line.startsWith("+")) return `${ANSI_GREEN}${line}${ANSI_RESET}`;
|
|
143
|
+
if (line.startsWith("-")) return `${ANSI_RED}${line}${ANSI_RESET}`;
|
|
144
|
+
return line;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Render a FileDriftEntry[] as an aligned multi-line block suitable for
|
|
148
|
+
* inclusion in a confirm-prompt summary.
|
|
149
|
+
*
|
|
150
|
+
* 3 file(s) drifted: 1 new, 1 content, 1 mode
|
|
151
|
+
* NEW placeholder.html
|
|
152
|
+
* CONTENT haproxy.cfg
|
|
153
|
+
* MODE scripts/certbot-pre-stop.sh
|
|
154
|
+
*
|
|
155
|
+
* When `opts.withContent` is true, each `new`/`content` row is followed
|
|
156
|
+
* by an indented unified-diff block (colored if `opts.color`).
|
|
157
|
+
*
|
|
158
|
+
* Returns the rendered lines (no surrounding indent applied). Caller
|
|
159
|
+
* decides how to nest the block in a wider prompt.
|
|
160
|
+
*/
|
|
161
|
+
function renderFileDrifts(entries, opts = {}) {
|
|
162
|
+
if (entries.length === 0) return [];
|
|
163
|
+
const counts = {};
|
|
164
|
+
for (const e of entries) counts[e.category] = (counts[e.category] ?? 0) + 1;
|
|
165
|
+
const summary = CATEGORY_ORDER.filter((c) => counts[c]).map((c) => `${counts[c]} ${c}`).join(", ");
|
|
166
|
+
const widest = Math.max(...entries.map((e) => CATEGORY_DISPLAY[e.category].length));
|
|
167
|
+
const sorted = [...entries].sort((a, b) => {
|
|
168
|
+
const ai = CATEGORY_ORDER.indexOf(a.category);
|
|
169
|
+
const bi = CATEGORY_ORDER.indexOf(b.category);
|
|
170
|
+
if (ai !== bi) return ai - bi;
|
|
171
|
+
return a.localPath.localeCompare(b.localPath);
|
|
172
|
+
});
|
|
173
|
+
const lines = [`${entries.length} file(s) drifted: ${summary}`];
|
|
174
|
+
for (const e of sorted) {
|
|
175
|
+
const label = CATEGORY_DISPLAY[e.category].padEnd(widest);
|
|
176
|
+
lines.push(` ${label} ${e.localPath}`);
|
|
177
|
+
if (opts.withContent) {
|
|
178
|
+
const diff = renderFileDriftWithDiff(e, {
|
|
179
|
+
color: opts.color,
|
|
180
|
+
maxLines: opts.maxLines
|
|
181
|
+
});
|
|
182
|
+
for (const dl of diff) lines.push(` ${dl}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
export { parseItemizeLine, renderFileDriftWithDiff, renderFileDrifts };
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=idempotency-files.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent-step primitive: a check / prompt / apply runner that is
|
|
3
|
+
* UI-agnostic and embeddable across the product.
|
|
4
|
+
*
|
|
5
|
+
* The pattern: every destructive operation on shared state (prod infra,
|
|
6
|
+
* npm, git remotes, DNS, TF state, workflow step side effects) is wrapped
|
|
7
|
+
* as an IdempotentStep whose check() returns a typed drift value or null.
|
|
8
|
+
* Null means the system is already in the desired state — the runner
|
|
9
|
+
* silently skips, optionally invoking whenInSync() to surface the
|
|
10
|
+
* already-satisfied resource (e.g. an existing resource id). A non-null
|
|
11
|
+
* drift means apply() would change state — the runner asks the caller's
|
|
12
|
+
* confirm() before invoking apply(), unless yes or dryRun overrides are
|
|
13
|
+
* set. apply() returns the typed result of the change for the caller.
|
|
14
|
+
*
|
|
15
|
+
* The runner has no UI dependency. CLI consumers pass an inquirer-backed
|
|
16
|
+
* confirm; future SDK / agent consumers pass their own policy function.
|
|
17
|
+
* See `.claude/rules/idempotency.md` for the full rule and adopters.
|
|
18
|
+
*/
|
|
19
|
+
export interface IdempotentStep<TDrift, TInSync = void, TApplied = void> {
|
|
20
|
+
/** Human-readable name; appears in logs and the confirm prompt. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Read-only inspection. Returns drift value if apply() would change
|
|
23
|
+
* state, or null if the system is already in the desired state. */
|
|
24
|
+
check: () => Promise<TDrift | null>;
|
|
25
|
+
/** Multi-line description of what apply() would do, given drift. */
|
|
26
|
+
summarize: (drift: TDrift) => string;
|
|
27
|
+
/** Destructive action that brings the system into the desired state.
|
|
28
|
+
* Its return value is surfaced in StepResult.result on the 'applied'
|
|
29
|
+
* outcome. */
|
|
30
|
+
apply: (drift: TDrift) => Promise<TApplied>;
|
|
31
|
+
/** Optional: runs when check() returns null. Use this to fetch the
|
|
32
|
+
* already-satisfied resource (e.g. read the existing id when a
|
|
33
|
+
* create-if-missing was already done). Return value is surfaced in
|
|
34
|
+
* StepResult.result on the 'skipped' outcome. */
|
|
35
|
+
whenInSync?: () => Promise<TInSync>;
|
|
36
|
+
}
|
|
37
|
+
export type ConfirmFn = (message: string) => Promise<boolean>;
|
|
38
|
+
export interface RunOptions {
|
|
39
|
+
/** Pluggable confirm. Required unless `yes` or `dryRun` is set. */
|
|
40
|
+
confirm?: ConfirmFn;
|
|
41
|
+
/** Breakglass: skip prompts, apply on drift. The CALLER prints any
|
|
42
|
+
* loud "auto-confirm" banner before invoking the runner. */
|
|
43
|
+
yes?: boolean;
|
|
44
|
+
/** Report only; bypass confirm; never call apply(). */
|
|
45
|
+
dryRun?: boolean;
|
|
46
|
+
/** Sink for `name`-prefixed status lines. Defaults to console.log. */
|
|
47
|
+
log?: (line: string) => void;
|
|
48
|
+
}
|
|
49
|
+
export type StepOutcome = 'skipped' | 'applied' | 'declined' | 'dry-run';
|
|
50
|
+
export type StepResult<TDrift, TInSync = void, TApplied = void> = {
|
|
51
|
+
outcome: 'skipped';
|
|
52
|
+
drift: null;
|
|
53
|
+
result: TInSync;
|
|
54
|
+
} | {
|
|
55
|
+
outcome: 'applied';
|
|
56
|
+
drift: TDrift;
|
|
57
|
+
result: TApplied;
|
|
58
|
+
} | {
|
|
59
|
+
outcome: 'declined';
|
|
60
|
+
drift: TDrift;
|
|
61
|
+
result: undefined;
|
|
62
|
+
} | {
|
|
63
|
+
outcome: 'dry-run';
|
|
64
|
+
drift: TDrift;
|
|
65
|
+
result: undefined;
|
|
66
|
+
};
|
|
67
|
+
export declare function runIdempotentStep<TDrift, TInSync = void, TApplied = void>(step: IdempotentStep<TDrift, TInSync, TApplied>, opts?: RunOptions): Promise<StepResult<TDrift, TInSync, TApplied>>;
|
|
68
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import "./chunk-gOLHoazu.js";
|
|
2
|
+
//#region src/idempotency.ts
|
|
3
|
+
async function runIdempotentStep(step, opts = {}) {
|
|
4
|
+
const log = opts.log ?? ((line) => console.log(line));
|
|
5
|
+
const drift = await step.check();
|
|
6
|
+
if (drift === null) {
|
|
7
|
+
log(`✓ ${step.name} — in sync, skipping`);
|
|
8
|
+
return {
|
|
9
|
+
outcome: "skipped",
|
|
10
|
+
drift: null,
|
|
11
|
+
result: step.whenInSync ? await step.whenInSync() : void 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
log(`! ${step.name} — drift detected:`);
|
|
15
|
+
for (const line of step.summarize(drift).split("\n")) log(` ${line}`);
|
|
16
|
+
if (opts.dryRun) {
|
|
17
|
+
log(` (dry-run; would apply)`);
|
|
18
|
+
return {
|
|
19
|
+
outcome: "dry-run",
|
|
20
|
+
drift,
|
|
21
|
+
result: void 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
let approved;
|
|
25
|
+
if (opts.yes) approved = true;
|
|
26
|
+
else {
|
|
27
|
+
if (!opts.confirm) throw new Error(`runIdempotentStep(${step.name}): drift detected but no confirm callback provided and yes/dryRun not set. Pass opts.confirm, opts.yes, or opts.dryRun.`);
|
|
28
|
+
approved = await opts.confirm(`Apply ${step.name}?`);
|
|
29
|
+
}
|
|
30
|
+
if (!approved) {
|
|
31
|
+
log(` declined; skipping`);
|
|
32
|
+
return {
|
|
33
|
+
outcome: "declined",
|
|
34
|
+
drift,
|
|
35
|
+
result: void 0
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const appliedResult = await step.apply(drift);
|
|
39
|
+
log(`✓ ${step.name} — applied`);
|
|
40
|
+
return {
|
|
41
|
+
outcome: "applied",
|
|
42
|
+
drift,
|
|
43
|
+
result: appliedResult
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { runIdempotentStep };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=idempotency.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { RingBuffer } from './ring-buffer.js';
|
|
2
|
+
export { toErrorMessage, serializeError } from './error.js';
|
|
3
|
+
export { createPool, createDb } from './db.js';
|
|
4
|
+
export { parseDatabaseUrl, maskDatabaseUrl, dropAndCreateDatabase, dropDatabaseDirect, ensureDatabase, createDbRole, createReadOnlyDbUser, computeMigrationsHash, storeMigrationContentHash, readStoredMigrationContentHash, isSchemaCurrent, clearDispatchQueueDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, purgeScopedSecretsDirect, seedEnvironmentDirect, seedEnvironmentBindingDirect, setEnvironmentPolicyDirect, listEnvironmentsDirect, showEnvironmentDirect, createEnvironmentTemplateDirect, setEnvironmentSecretDirect, listQueueDirect, showQueueEntryDirect, listExecutionRunsDirect, showExecutionRunDirect, listExecutionJobsDirect, listRegistrationsDirect, showRegistrationDirect, registerWorkflowManualDirect, resetRaftStateDirect, emitKiciEventDirect, seedGenericWebhookSourceDirect, purgeSecretBackendsDirect, apiKeyExistsDirect, seedApiKeyInlineDirect, platformConnectionExistsDirect, countWebhookSourcesByConnectionIdDirect, findAnyUserApiKeyIdDirect, seedSyntheticGithubSourceDirect, seedWebhookSecretDirect, seedSourcePrivateKeyDirect, bumpRegistryVersionDirect, pollKiciEventsDirect, waitForPostgresDirect, waitForRunCompletionDirect, cleanupExecutionRowsDirect, isSchemaCurrentFromFilesDirect, storeMigrationContentHashInTableDirect, createJoinTokenDirect, updateSourceRoutingKeyDirect, prunePeerCredentialsDirect, waitForPlatformRegistrationsDirect, seedUniversalGitSourceDirect, seedCiSecurityFixturesDirect, waitForExecutionRunStatusSinceDirect, latestExecutionRunByStatusDirect, waitForLatestExecutionJobStatusDirect, describeTableColumnsDirect, tableExistsDirect, insertKiciEventRawDirect, showKiciEventDirect, listKiciEventsDirect, deleteKiciEventsDirect, verifyKiciEventNotifyDirect, seedCrossRepoTrustDirect, listCrossRepoTrustBySourceRoutingKeyDirect, deleteCrossRepoTrustDirect, insertCrossRepoTrustStrictDirect, deleteWorkflowRegistrationsDirect, getWorkflowRegistrationByIdDirect, listRegistrationsByRoutingKeyDirect, waitForRegistrationsByRoutingKeyDirect, waitForRegistrationsUpdatedAtAdvanceDirect, updateWorkflowRegistrationCommitShaDirect, insertWorkflowRegistrationRawDirect, insertWorkflowRegistrationStrictDirect, getRegistryVersionDirect, bumpRegistryVersionSimpleDirect, upsertCronLastFiredDirect, countCronLastFiredDirect, insertCronLastFiredNowDirect, deleteCronLastFiredDirect, deleteExecutionRunsByWorkflowNameDirect, getGenericWebhookSourceByRoutingKeyDirect, listActiveGenericWebhookSourcesDirect, updateGenericWebhookVerificationConfigDirect, deleteGenericWebhookSourcesByNameDirect, restoreSoftDeletedGenericWebhookSourceDirect, upsertOrgSettingsGlobalWorkflowsDirect, updateOrgSettingsDeniedReposDirect, deleteOrgSettingsByCustomerIdDirect, getExecutionRunSecurityDirect, getHeldRunByIdDirect, countHeldRunsByRunIdDirect, waitForPlatformExecutionRunStatusDirect, waitForPlatformEventLogDistinctRoutedDirect, waitForEventLogRowByDeliveryIdDirect, resolvePlatformWebhookSourceRoutingKeyDirect, ensureOrgOwnerMemberDirect, deletePeerCredentialsByInstanceIdLikeDirect, insertPeerCredentialExpiredDirect, getPeerCredentialRevokedAtDirect, listActivePeerCredentialsExcludingDirect, clearPeerCredentialsRevokedAtByIdsDirect, countActivePeerCredentialsByInstanceDirect, type ColumnInfo, type KiciEventRow, type CrossRepoTrustRow, type WorkflowRegistrationFullRow, type RegistrationsScopedResult, type LatestExecutionRunResult, type WaitForLatestJobResult, type ExecutionRunSecurityRow, type HeldRunSecurityRow, type EventLogRow as PlatformEventLogRow, type UpsertOrgSettingsOpts, type OrgSettingsRepoPatternEntry, type InsertKiciEventRawOpts, type EmitKiciEventOpts, type SeedGenericWebhookSourceOpts, REGISTERABLE_TRIGGER_TYPES, MIGRATION_HASH_TABLE, type PurgeStaleExecutionResult, type PurgeStaleSourcesResult, type SeedEnvironmentOpts, type SeedEnvironmentResult, type SeedEnvironmentBindingOpts, type SetEnvironmentPolicyOpts, type EnvironmentRow, type EnvironmentVariableRow, type EnvironmentBindingRow, type ShowEnvironmentResult, type CreateEnvironmentTemplateOpts, type SetEnvironmentSecretOpts, type DispatchQueueRow, type ListQueueOpts, type ExecutionRunRow, type ExecutionJobRow, type ListExecutionRunsOpts, type WorkflowRegistrationRow, type ListRegistrationsOpts, type ListRegistrationsResult, type ShowRegistrationResult, type RegisterWorkflowManualOpts, type RegisterWorkflowManualResult, } from './db-admin.js';
|
|
5
|
+
export { createMetricsRoutes, type MetricsRoutesDeps } from './routes/metrics.js';
|
|
6
|
+
export { createHealthRoutes, type HealthRoutesDeps } from './routes/health.js';
|
|
7
|
+
export { initZx } from './zx.js';
|
|
8
|
+
export { createLogger, guardStartup, logger, setServiceName, type LogLevel, type Logger, } from './logger.js';
|
|
9
|
+
export { requestContext, getRequestContext, enrichRequestContext, type RequestContext, } from './request-context.js';
|
|
10
|
+
export { getReconnectDelay } from './reconnect-delay.js';
|
|
11
|
+
export { formatBytes } from './format-bytes.js';
|
|
12
|
+
export { formatDuration, formatUptime } from './format-duration.js';
|
|
13
|
+
export { sha256, sha256File, deriveSharedSecret, normalizeLineEndings } from './crypto.js';
|
|
14
|
+
export { initTelemetry, getPrometheusExporter, createMeter, type TelemetryConfig, } from './telemetry/index.js';
|
|
15
|
+
export { setupGracefulShutdown, type ShutdownStep, type ShutdownLogger, type ShutdownHandle, type GracefulShutdownOptions, } from './graceful-shutdown.js';
|
|
16
|
+
export { validateRequiredTools, type ToolRequirement } from './tool-check.js';
|
|
17
|
+
export { createS3Client, type CreateS3ClientOptions, type SharedS3Config } from './s3-client.js';
|
|
18
|
+
export { BaseColdStore, ChunkLru, COLD_BUCKET_NAMES, DEFAULT_TABLE_CONFIG, chunkObjectKey, coldDaysToBucket, coldStoreArchiveBytesTotal, coldStoreArchiveCyclesTotal, coldStoreArchiveDurationSeconds, coldStoreArchiveRowsTotal, coldStorePurgeBytesTotal, coldStorePurgeChunksTotal, coldStorePurgeDurationSeconds, coldStoreRehydrateBytesTotal, coldStoreRehydrateDurationSeconds, coldStoreRehydrateRequestsTotal, coldStoreReplayDurationSeconds, coldStoreReplayRowsTotal, coldStoreVerifyFailuresTotal, computeChunkId, decodeChunk, encodeChunk, encodeKeySegment, isLongerColdRetention, parseManifest, resolveTableConfig, serializeManifest, tablePrefix, tenantDayBucketPrefix, tenantDayPrefix, type ArchiveCycleSummary, type BaseColdStoreDeps, type ChunkCommitMetadata, type ChunkLruOptions, type ChunkManifest, type ColdBucketName, type ColdRetention, type ColdStore, type ColdStoreConfig, type ColdStoreFetchRangeArgs, type ColdStoreReplayChunkArgs, type ColdStoreReplayResult, type ColdStoreReplayRowArgs, type ColdStoreTableConfig, type DbKind, type DecodeChunkArgs, type EligiblePartition, type EncodeChunkArgs, type EncodedChunk, type PurgeableChunk, type PurgeChunkResult, type PurgeExpiredChunksOpts, type PurgeExpiredChunksSummary, type TableAdapter, } from './cold-store/index.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "./chunk-gOLHoazu.js";
|
|
2
|
+
import { deriveSharedSecret, normalizeLineEndings, sha256, sha256File } from "./crypto.js";
|
|
3
|
+
import { createDb, createPool } from "./db.js";
|
|
4
|
+
import { MIGRATION_HASH_TABLE, REGISTERABLE_TRIGGER_TYPES, apiKeyExistsDirect, bumpRegistryVersionDirect, bumpRegistryVersionSimpleDirect, cleanupExecutionRowsDirect, clearDispatchQueueDirect, clearPeerCredentialsRevokedAtByIdsDirect, computeMigrationsHash, countActivePeerCredentialsByInstanceDirect, countCronLastFiredDirect, countHeldRunsByRunIdDirect, countWebhookSourcesByConnectionIdDirect, createDbRole, createEnvironmentTemplateDirect, createJoinTokenDirect, createReadOnlyDbUser, deleteCronLastFiredDirect, deleteCrossRepoTrustDirect, deleteExecutionRunsByWorkflowNameDirect, deleteGenericWebhookSourcesByNameDirect, deleteKiciEventsDirect, deleteOrgSettingsByCustomerIdDirect, deletePeerCredentialsByInstanceIdLikeDirect, deleteWorkflowRegistrationsDirect, describeTableColumnsDirect, dropAndCreateDatabase, dropDatabaseDirect, emitKiciEventDirect, ensureDatabase, ensureOrgOwnerMemberDirect, findAnyUserApiKeyIdDirect, getExecutionRunSecurityDirect, getGenericWebhookSourceByRoutingKeyDirect, getHeldRunByIdDirect, getPeerCredentialRevokedAtDirect, getRegistryVersionDirect, getWorkflowRegistrationByIdDirect, insertCronLastFiredNowDirect, insertCrossRepoTrustStrictDirect, insertKiciEventRawDirect, insertPeerCredentialExpiredDirect, insertWorkflowRegistrationRawDirect, insertWorkflowRegistrationStrictDirect, isSchemaCurrent, isSchemaCurrentFromFilesDirect, latestExecutionRunByStatusDirect, listActiveGenericWebhookSourcesDirect, listActivePeerCredentialsExcludingDirect, listCrossRepoTrustBySourceRoutingKeyDirect, listEnvironmentsDirect, listExecutionJobsDirect, listExecutionRunsDirect, listKiciEventsDirect, listQueueDirect, listRegistrationsByRoutingKeyDirect, listRegistrationsDirect, maskDatabaseUrl, parseDatabaseUrl, platformConnectionExistsDirect, pollKiciEventsDirect, prunePeerCredentialsDirect, purgeScopedSecretsDirect, purgeSecretBackendsDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, readStoredMigrationContentHash, registerWorkflowManualDirect, resetRaftStateDirect, resolvePlatformWebhookSourceRoutingKeyDirect, restoreSoftDeletedGenericWebhookSourceDirect, seedApiKeyInlineDirect, seedCiSecurityFixturesDirect, seedCrossRepoTrustDirect, seedEnvironmentBindingDirect, seedEnvironmentDirect, seedGenericWebhookSourceDirect, seedSourcePrivateKeyDirect, seedSyntheticGithubSourceDirect, seedUniversalGitSourceDirect, seedWebhookSecretDirect, setEnvironmentPolicyDirect, setEnvironmentSecretDirect, showEnvironmentDirect, showExecutionRunDirect, showKiciEventDirect, showQueueEntryDirect, showRegistrationDirect, storeMigrationContentHash, storeMigrationContentHashInTableDirect, tableExistsDirect, updateGenericWebhookVerificationConfigDirect, updateOrgSettingsDeniedReposDirect, updateSourceRoutingKeyDirect, updateWorkflowRegistrationCommitShaDirect, upsertCronLastFiredDirect, upsertOrgSettingsGlobalWorkflowsDirect, verifyKiciEventNotifyDirect, waitForEventLogRowByDeliveryIdDirect, waitForExecutionRunStatusSinceDirect, waitForLatestExecutionJobStatusDirect, waitForPlatformEventLogDistinctRoutedDirect, waitForPlatformExecutionRunStatusDirect, waitForPlatformRegistrationsDirect, waitForPostgresDirect, waitForRegistrationsByRoutingKeyDirect, waitForRegistrationsUpdatedAtAdvanceDirect, waitForRunCompletionDirect } from "./db-admin.js";
|
|
5
|
+
import { serializeError, toErrorMessage } from "./error.js";
|
|
6
|
+
import { formatBytes } from "./format-bytes.js";
|
|
7
|
+
import { formatDuration, formatUptime } from "./format-duration.js";
|
|
8
|
+
import { setupGracefulShutdown } from "./graceful-shutdown.js";
|
|
9
|
+
import { RingBuffer } from "./ring-buffer.js";
|
|
10
|
+
import { createMetricsRoutes } from "./routes/metrics.js";
|
|
11
|
+
import { createHealthRoutes } from "./routes/health.js";
|
|
12
|
+
import { initZx } from "./zx.js";
|
|
13
|
+
import { enrichRequestContext, getRequestContext, requestContext } from "./request-context.js";
|
|
14
|
+
import { createLogger, guardStartup, logger, setServiceName } from "./logger.js";
|
|
15
|
+
import { getReconnectDelay } from "./reconnect-delay.js";
|
|
16
|
+
import { getPrometheusExporter, initTelemetry } from "./telemetry/init.js";
|
|
17
|
+
import { createMeter } from "./telemetry/metrics.js";
|
|
18
|
+
import "./telemetry/index.js";
|
|
19
|
+
import { validateRequiredTools } from "./tool-check.js";
|
|
20
|
+
import { createS3Client } from "./s3-client.js";
|
|
21
|
+
import { chunkObjectKey, encodeKeySegment, tablePrefix, tenantDayBucketPrefix, tenantDayPrefix } from "./cold-store/key.js";
|
|
22
|
+
import { COLD_BUCKET_NAMES, coldDaysToBucket, isLongerColdRetention } from "./cold-store/bucket.js";
|
|
23
|
+
import { computeChunkId } from "./cold-store/chunk-id.js";
|
|
24
|
+
import { decodeChunk, encodeChunk } from "./cold-store/chunk-encoder.js";
|
|
25
|
+
import { parseManifest, serializeManifest } from "./cold-store/manifest.js";
|
|
26
|
+
import { coldStoreArchiveBytesTotal, coldStoreArchiveCyclesTotal, coldStoreArchiveDurationSeconds, coldStoreArchiveRowsTotal, coldStorePurgeBytesTotal, coldStorePurgeChunksTotal, coldStorePurgeDurationSeconds, coldStoreRehydrateBytesTotal, coldStoreRehydrateDurationSeconds, coldStoreRehydrateRequestsTotal, coldStoreReplayDurationSeconds, coldStoreReplayRowsTotal, coldStoreVerifyFailuresTotal } from "./cold-store/metrics.js";
|
|
27
|
+
import { BaseColdStore } from "./cold-store/cold-store.js";
|
|
28
|
+
import { ChunkLru } from "./cold-store/lru.js";
|
|
29
|
+
import { DEFAULT_TABLE_CONFIG, resolveTableConfig } from "./cold-store/config.js";
|
|
30
|
+
import "./cold-store/index.js";
|
|
31
|
+
export { BaseColdStore, COLD_BUCKET_NAMES, ChunkLru, DEFAULT_TABLE_CONFIG, MIGRATION_HASH_TABLE, REGISTERABLE_TRIGGER_TYPES, RingBuffer, apiKeyExistsDirect, bumpRegistryVersionDirect, bumpRegistryVersionSimpleDirect, chunkObjectKey, cleanupExecutionRowsDirect, clearDispatchQueueDirect, clearPeerCredentialsRevokedAtByIdsDirect, coldDaysToBucket, coldStoreArchiveBytesTotal, coldStoreArchiveCyclesTotal, coldStoreArchiveDurationSeconds, coldStoreArchiveRowsTotal, coldStorePurgeBytesTotal, coldStorePurgeChunksTotal, coldStorePurgeDurationSeconds, coldStoreRehydrateBytesTotal, coldStoreRehydrateDurationSeconds, coldStoreRehydrateRequestsTotal, coldStoreReplayDurationSeconds, coldStoreReplayRowsTotal, coldStoreVerifyFailuresTotal, computeChunkId, computeMigrationsHash, countActivePeerCredentialsByInstanceDirect, countCronLastFiredDirect, countHeldRunsByRunIdDirect, countWebhookSourcesByConnectionIdDirect, createDb, createDbRole, createEnvironmentTemplateDirect, createHealthRoutes, createJoinTokenDirect, createLogger, createMeter, createMetricsRoutes, createPool, createReadOnlyDbUser, createS3Client, decodeChunk, deleteCronLastFiredDirect, deleteCrossRepoTrustDirect, deleteExecutionRunsByWorkflowNameDirect, deleteGenericWebhookSourcesByNameDirect, deleteKiciEventsDirect, deleteOrgSettingsByCustomerIdDirect, deletePeerCredentialsByInstanceIdLikeDirect, deleteWorkflowRegistrationsDirect, deriveSharedSecret, describeTableColumnsDirect, dropAndCreateDatabase, dropDatabaseDirect, emitKiciEventDirect, encodeChunk, encodeKeySegment, enrichRequestContext, ensureDatabase, ensureOrgOwnerMemberDirect, findAnyUserApiKeyIdDirect, formatBytes, formatDuration, formatUptime, getExecutionRunSecurityDirect, getGenericWebhookSourceByRoutingKeyDirect, getHeldRunByIdDirect, getPeerCredentialRevokedAtDirect, getPrometheusExporter, getReconnectDelay, getRegistryVersionDirect, getRequestContext, getWorkflowRegistrationByIdDirect, guardStartup, initTelemetry, initZx, insertCronLastFiredNowDirect, insertCrossRepoTrustStrictDirect, insertKiciEventRawDirect, insertPeerCredentialExpiredDirect, insertWorkflowRegistrationRawDirect, insertWorkflowRegistrationStrictDirect, isLongerColdRetention, isSchemaCurrent, isSchemaCurrentFromFilesDirect, latestExecutionRunByStatusDirect, listActiveGenericWebhookSourcesDirect, listActivePeerCredentialsExcludingDirect, listCrossRepoTrustBySourceRoutingKeyDirect, listEnvironmentsDirect, listExecutionJobsDirect, listExecutionRunsDirect, listKiciEventsDirect, listQueueDirect, listRegistrationsByRoutingKeyDirect, listRegistrationsDirect, logger, maskDatabaseUrl, normalizeLineEndings, parseDatabaseUrl, parseManifest, platformConnectionExistsDirect, pollKiciEventsDirect, prunePeerCredentialsDirect, purgeScopedSecretsDirect, purgeSecretBackendsDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, readStoredMigrationContentHash, registerWorkflowManualDirect, requestContext, resetRaftStateDirect, resolvePlatformWebhookSourceRoutingKeyDirect, resolveTableConfig, restoreSoftDeletedGenericWebhookSourceDirect, seedApiKeyInlineDirect, seedCiSecurityFixturesDirect, seedCrossRepoTrustDirect, seedEnvironmentBindingDirect, seedEnvironmentDirect, seedGenericWebhookSourceDirect, seedSourcePrivateKeyDirect, seedSyntheticGithubSourceDirect, seedUniversalGitSourceDirect, seedWebhookSecretDirect, serializeError, serializeManifest, setEnvironmentPolicyDirect, setEnvironmentSecretDirect, setServiceName, setupGracefulShutdown, sha256, sha256File, showEnvironmentDirect, showExecutionRunDirect, showKiciEventDirect, showQueueEntryDirect, showRegistrationDirect, storeMigrationContentHash, storeMigrationContentHashInTableDirect, tableExistsDirect, tablePrefix, tenantDayBucketPrefix, tenantDayPrefix, toErrorMessage, updateGenericWebhookVerificationConfigDirect, updateOrgSettingsDeniedReposDirect, updateSourceRoutingKeyDirect, updateWorkflowRegistrationCommitShaDirect, upsertCronLastFiredDirect, upsertOrgSettingsGlobalWorkflowsDirect, validateRequiredTools, verifyKiciEventNotifyDirect, waitForEventLogRowByDeliveryIdDirect, waitForExecutionRunStatusSinceDirect, waitForLatestExecutionJobStatusDirect, waitForPlatformEventLogDistinctRoutedDirect, waitForPlatformExecutionRunStatusDirect, waitForPlatformRegistrationsDirect, waitForPostgresDirect, waitForRegistrationsByRoutingKeyDirect, waitForRegistrationsUpdatedAtAdvanceDirect, waitForRunCompletionDirect };
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
/** Set the service name for all loggers in this process. Call once at startup. */
|
|
3
|
+
export declare function setServiceName(name: 'platform' | 'orchestrator' | 'agent'): void;
|
|
4
|
+
/** Get the current service name (for testing/inspection). */
|
|
5
|
+
export declare function getServiceName(): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Build the rotated log filename. When a stable instance ID is available in the
|
|
8
|
+
* environment, append it so multiple processes (e.g. several orchestrators or
|
|
9
|
+
* agents) can safely share one KICI_LOG_DIR without racing on the same file.
|
|
10
|
+
*
|
|
11
|
+
* Precedence matches the tier that owns each variable:
|
|
12
|
+
* orchestrator (KICI_CLUSTER_INSTANCE_ID) > agent (KICI_AGENT_ID) > platform
|
|
13
|
+
* (KICI_PLATFORM_INSTANCE_ID). Sanitize defensively to filesystem-safe characters.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildLogFilename(serviceName: string | undefined): string;
|
|
16
|
+
/** Log level type */
|
|
17
|
+
export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
18
|
+
/** Minimal logger interface for dependency injection (avoids coupling to winston). */
|
|
19
|
+
export interface Logger {
|
|
20
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
21
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
22
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
23
|
+
}
|
|
24
|
+
/** Logger creation options */
|
|
25
|
+
interface LoggerOptions {
|
|
26
|
+
/** Use JSON format (default: auto-detect based on TTY) */
|
|
27
|
+
json?: boolean;
|
|
28
|
+
/** Log level (default: 'info') */
|
|
29
|
+
level?: LogLevel;
|
|
30
|
+
/** Optional prefix for all messages */
|
|
31
|
+
prefix?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a winston logger instance.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Logger configuration options
|
|
37
|
+
* @returns Configured winston logger
|
|
38
|
+
*/
|
|
39
|
+
export declare function createLogger(options?: LoggerOptions): winston.Logger;
|
|
40
|
+
/**
|
|
41
|
+
* Default singleton logger instance for simple use cases. If KICI_LOG_DIR is
|
|
42
|
+
* set but setServiceName() hasn't been called yet (CLI tools, scripts),
|
|
43
|
+
* the file transport stays deferred and is attached only once a service
|
|
44
|
+
* name is known — protecting against writing to `kici-<instanceId>-*.log`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const logger: winston.Logger;
|
|
47
|
+
/**
|
|
48
|
+
* Wrap an async startup function so that any thrown error is logged
|
|
49
|
+
* through the structured (JSON-aware) logger before the process exits.
|
|
50
|
+
*
|
|
51
|
+
* Without this guard, a top-level `await` rejection in an ESM entry
|
|
52
|
+
* point is printed by Node.js's default handler (multi-line, not JSON),
|
|
53
|
+
* which breaks log aggregators like ELK.
|
|
54
|
+
*/
|
|
55
|
+
export declare function guardStartup(log: winston.Logger, fn: () => Promise<void>): Promise<void>;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import "./chunk-gOLHoazu.js";
|
|
2
|
+
import { toErrorMessage } from "./error.js";
|
|
3
|
+
import { getRequestContext } from "./request-context.js";
|
|
4
|
+
import winston from "winston";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import DailyRotateFile from "winston-daily-rotate-file";
|
|
7
|
+
//#region src/logger.ts
|
|
8
|
+
let _serviceName;
|
|
9
|
+
/**
|
|
10
|
+
* Tracked set of loggers still waiting for the service name so they can
|
|
11
|
+
* add their rotated-file transport with the right filename. Module-level
|
|
12
|
+
* `createLogger()` calls resolve before the service's `setServiceName()`
|
|
13
|
+
* runs; if we built the file transport eagerly, every such logger would
|
|
14
|
+
* write to `kici-<instanceId>-*.log` (undefined service name) instead of
|
|
15
|
+
* `<service>-<instanceId>-*.log`. Holding them here lets setServiceName
|
|
16
|
+
* attach the correct transport once, in one place.
|
|
17
|
+
*/
|
|
18
|
+
const _pendingFileTransportLoggers = /* @__PURE__ */ new Set();
|
|
19
|
+
function buildFileTransport() {
|
|
20
|
+
if (!process.env.KICI_LOG_DIR) return void 0;
|
|
21
|
+
return new DailyRotateFile({
|
|
22
|
+
dirname: process.env.KICI_LOG_DIR,
|
|
23
|
+
filename: buildLogFilename(_serviceName),
|
|
24
|
+
datePattern: "YYYY-MM-DD",
|
|
25
|
+
maxSize: process.env.KICI_LOG_MAX_SIZE ?? "500m",
|
|
26
|
+
maxFiles: `${process.env.KICI_LOG_RETENTION_DAYS ?? "7"}d`,
|
|
27
|
+
format: winston.format.combine(winston.format.timestamp(), winston.format((info) => {
|
|
28
|
+
if (_serviceName) info["service"] = _serviceName;
|
|
29
|
+
const ctx = getRequestContext();
|
|
30
|
+
if (ctx.requestId) info["requestId"] = ctx.requestId;
|
|
31
|
+
if (ctx.runId) info["runId"] = ctx.runId;
|
|
32
|
+
if (ctx.jobId) info["jobId"] = ctx.jobId;
|
|
33
|
+
if (ctx.routingKey) info["routingKey"] = ctx.routingKey;
|
|
34
|
+
if (ctx.traceId) info["traceId"] = ctx.traceId;
|
|
35
|
+
if (ctx.spanId) info["spanId"] = ctx.spanId;
|
|
36
|
+
return info;
|
|
37
|
+
})(), winston.format.json()),
|
|
38
|
+
zippedArchive: true
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Set the service name for all loggers in this process. Call once at startup. */
|
|
42
|
+
function setServiceName(name) {
|
|
43
|
+
_serviceName = name;
|
|
44
|
+
for (const logger of _pendingFileTransportLoggers) {
|
|
45
|
+
const transport = buildFileTransport();
|
|
46
|
+
if (transport) logger.add(transport);
|
|
47
|
+
}
|
|
48
|
+
_pendingFileTransportLoggers.clear();
|
|
49
|
+
}
|
|
50
|
+
/** Get the current service name (for testing/inspection). */
|
|
51
|
+
function getServiceName() {
|
|
52
|
+
return _serviceName;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the rotated log filename. When a stable instance ID is available in the
|
|
56
|
+
* environment, append it so multiple processes (e.g. several orchestrators or
|
|
57
|
+
* agents) can safely share one KICI_LOG_DIR without racing on the same file.
|
|
58
|
+
*
|
|
59
|
+
* Precedence matches the tier that owns each variable:
|
|
60
|
+
* orchestrator (KICI_CLUSTER_INSTANCE_ID) > agent (KICI_AGENT_ID) > platform
|
|
61
|
+
* (KICI_PLATFORM_INSTANCE_ID). Sanitize defensively to filesystem-safe characters.
|
|
62
|
+
*/
|
|
63
|
+
function buildLogFilename(serviceName) {
|
|
64
|
+
const base = serviceName ?? "kici";
|
|
65
|
+
const suffix = (process.env.KICI_CLUSTER_INSTANCE_ID || process.env.KICI_AGENT_ID || process.env.KICI_PLATFORM_INSTANCE_ID || "").replace(/[^A-Za-z0-9_.-]+/g, "_");
|
|
66
|
+
return suffix ? `${base}-${suffix}-%DATE%.log` : `${base}-%DATE%.log`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a winston logger instance.
|
|
70
|
+
*
|
|
71
|
+
* @param options - Logger configuration options
|
|
72
|
+
* @returns Configured winston logger
|
|
73
|
+
*/
|
|
74
|
+
function createLogger(options = {}) {
|
|
75
|
+
const { json = !process.stdout.isTTY, level = "info", prefix } = options;
|
|
76
|
+
const TOKEN_MASK_RE = /kat_[0-9a-f]{64}/gi;
|
|
77
|
+
const maskTokens = (value) => {
|
|
78
|
+
if (typeof value === "string") return value.replace(TOKEN_MASK_RE, "kat_***");
|
|
79
|
+
if (Array.isArray(value)) return value.map(maskTokens);
|
|
80
|
+
if (value !== null && typeof value === "object") {
|
|
81
|
+
const masked = {};
|
|
82
|
+
for (const [k, v] of Object.entries(value)) masked[k] = maskTokens(v);
|
|
83
|
+
return masked;
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
};
|
|
87
|
+
const tokenMaskFormat = winston.format((info) => {
|
|
88
|
+
if (typeof info.message === "string") info.message = info.message.replace(TOKEN_MASK_RE, "kat_***");
|
|
89
|
+
for (const key of Object.keys(info)) {
|
|
90
|
+
if (key === "level" || key === "message") continue;
|
|
91
|
+
info[key] = maskTokens(info[key]);
|
|
92
|
+
}
|
|
93
|
+
return info;
|
|
94
|
+
});
|
|
95
|
+
const traceContextFormat = winston.format((info) => {
|
|
96
|
+
if (_serviceName) info["service"] = _serviceName;
|
|
97
|
+
const ctx = getRequestContext();
|
|
98
|
+
if (ctx.requestId) info["requestId"] = ctx.requestId;
|
|
99
|
+
if (ctx.runId) info["runId"] = ctx.runId;
|
|
100
|
+
if (ctx.jobId) info["jobId"] = ctx.jobId;
|
|
101
|
+
if (ctx.routingKey) info["routingKey"] = ctx.routingKey;
|
|
102
|
+
if (ctx.traceId) info["traceId"] = ctx.traceId;
|
|
103
|
+
if (ctx.spanId) info["spanId"] = ctx.spanId;
|
|
104
|
+
return info;
|
|
105
|
+
});
|
|
106
|
+
const prettyFormat = winston.format.printf(({ level, message, timestamp, requestId }) => {
|
|
107
|
+
const time = new Date(timestamp).toLocaleTimeString("en-US", {
|
|
108
|
+
hour12: false,
|
|
109
|
+
hour: "2-digit",
|
|
110
|
+
minute: "2-digit",
|
|
111
|
+
second: "2-digit"
|
|
112
|
+
});
|
|
113
|
+
let coloredLevel;
|
|
114
|
+
switch (level) {
|
|
115
|
+
case "error":
|
|
116
|
+
coloredLevel = pc.red(level);
|
|
117
|
+
break;
|
|
118
|
+
case "warn":
|
|
119
|
+
coloredLevel = pc.yellow(level);
|
|
120
|
+
break;
|
|
121
|
+
case "info":
|
|
122
|
+
coloredLevel = pc.cyan(level);
|
|
123
|
+
break;
|
|
124
|
+
case "debug":
|
|
125
|
+
coloredLevel = pc.gray(level);
|
|
126
|
+
break;
|
|
127
|
+
default: coloredLevel = level;
|
|
128
|
+
}
|
|
129
|
+
const traceStr = typeof requestId === "string" ? ` ${pc.dim(`[${requestId.slice(0, 8)}]`)}` : "";
|
|
130
|
+
const prefixStr = prefix ? `${prefix} ` : "";
|
|
131
|
+
return `${pc.dim(`[${time}]`)}${traceStr} ${coloredLevel}: ${prefixStr}${message}`;
|
|
132
|
+
});
|
|
133
|
+
const jsonFormat = winston.format.combine(winston.format.timestamp(), traceContextFormat(), tokenMaskFormat(), winston.format.json());
|
|
134
|
+
const prettyPipeline = winston.format.combine(winston.format.timestamp(), traceContextFormat(), tokenMaskFormat(), prettyFormat);
|
|
135
|
+
const loggerInstance = winston.createLogger({
|
|
136
|
+
level,
|
|
137
|
+
format: json ? jsonFormat : prettyPipeline,
|
|
138
|
+
transports: [new winston.transports.Console()]
|
|
139
|
+
});
|
|
140
|
+
if (process.env.KICI_LOG_DIR) if (_serviceName) {
|
|
141
|
+
const transport = buildFileTransport();
|
|
142
|
+
if (transport) loggerInstance.add(transport);
|
|
143
|
+
} else _pendingFileTransportLoggers.add(loggerInstance);
|
|
144
|
+
return loggerInstance;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Default singleton logger instance for simple use cases. If KICI_LOG_DIR is
|
|
148
|
+
* set but setServiceName() hasn't been called yet (CLI tools, scripts),
|
|
149
|
+
* the file transport stays deferred and is attached only once a service
|
|
150
|
+
* name is known — protecting against writing to `kici-<instanceId>-*.log`.
|
|
151
|
+
*/
|
|
152
|
+
const logger = createLogger();
|
|
153
|
+
/**
|
|
154
|
+
* Wrap an async startup function so that any thrown error is logged
|
|
155
|
+
* through the structured (JSON-aware) logger before the process exits.
|
|
156
|
+
*
|
|
157
|
+
* Without this guard, a top-level `await` rejection in an ESM entry
|
|
158
|
+
* point is printed by Node.js's default handler (multi-line, not JSON),
|
|
159
|
+
* which breaks log aggregators like ELK.
|
|
160
|
+
*/
|
|
161
|
+
async function guardStartup(log, fn) {
|
|
162
|
+
try {
|
|
163
|
+
await fn();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
log.error("Fatal startup error", {
|
|
166
|
+
error: toErrorMessage(error),
|
|
167
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
168
|
+
});
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { buildLogFilename, createLogger, getServiceName, guardStartup, logger, setServiceName };
|
|
174
|
+
|
|
175
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate reconnection delay with exponential backoff and jitter.
|
|
3
|
+
*
|
|
4
|
+
* Formula: baseDelay * multiplier^attempts * (1.0 + random * 0.5)
|
|
5
|
+
* - Initial delay: 1000ms
|
|
6
|
+
* - Multiplier: 1.5x per attempt
|
|
7
|
+
* - Jitter: 0-50% additional randomness
|
|
8
|
+
* - Max delay: capped at maxDelayMs
|
|
9
|
+
*/
|
|
10
|
+
export declare function getReconnectDelay(attempts: number, maxDelayMs: number): number;
|
|
11
|
+
//# sourceMappingURL=reconnect-delay.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import "./chunk-gOLHoazu.js";
|
|
2
|
+
//#region src/reconnect-delay.ts
|
|
3
|
+
/**
|
|
4
|
+
* Calculate reconnection delay with exponential backoff and jitter.
|
|
5
|
+
*
|
|
6
|
+
* Formula: baseDelay * multiplier^attempts * (1.0 + random * 0.5)
|
|
7
|
+
* - Initial delay: 1000ms
|
|
8
|
+
* - Multiplier: 1.5x per attempt
|
|
9
|
+
* - Jitter: 0-50% additional randomness
|
|
10
|
+
* - Max delay: capped at maxDelayMs
|
|
11
|
+
*/
|
|
12
|
+
function getReconnectDelay(attempts, maxDelayMs) {
|
|
13
|
+
const baseDelay = 1e3;
|
|
14
|
+
const multiplier = 1.5;
|
|
15
|
+
const jitterFactor = 1 + Math.random() * .5;
|
|
16
|
+
const delay = baseDelay * Math.pow(multiplier, attempts) * jitterFactor;
|
|
17
|
+
return Math.min(delay, maxDelayMs);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { getReconnectDelay };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=reconnect-delay.js.map
|