@arki/dot 0.1.0 → 0.1.2
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 +36 -31
- package/dist/define-app.d.ts +58 -16
- package/dist/define-app.d.ts.map +1 -1
- package/dist/define-app.js +23 -13
- package/dist/define-app.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/kernel/app-instance.d.ts +3 -3
- package/dist/kernel/app-instance.d.ts.map +1 -1
- package/dist/kernel/app-instance.js +265 -159
- package/dist/kernel/app-instance.js.map +1 -1
- package/dist/lifecycle.d.ts +11 -9
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +15 -9
- package/dist/lifecycle.js.map +1 -1
- package/dist/pip-contract.d.ts +254 -149
- package/dist/pip-contract.d.ts.map +1 -1
- package/dist/pip-contract.js +185 -41
- package/dist/pip-contract.js.map +1 -1
- package/dist/pip.d.ts +9 -12
- package/dist/pip.d.ts.map +1 -1
- package/dist/pip.js +8 -11
- package/dist/pip.js.map +1 -1
- package/dist/test-harness.d.ts +13 -10
- package/dist/test-harness.d.ts.map +1 -1
- package/dist/test-harness.js +12 -12
- package/dist/test-harness.js.map +1 -1
- package/package.json +10 -4
- package/src/cli/discover.ts +223 -0
- package/src/cli/error-codes.ts +74 -0
- package/src/cli/files.ts +120 -0
- package/src/cli/index.ts +539 -0
- package/src/cli/json.ts +49 -0
- package/src/cli/new.ts +420 -0
- package/src/cli/observability-probe.ts +51 -0
- package/src/cli/render-doctor.ts +199 -0
- package/src/cli/render-explain.ts +161 -0
- package/src/define-app.ts +310 -0
- package/src/diagnostics.ts +91 -0
- package/src/index.ts +89 -0
- package/src/kernel/app-instance.ts +1341 -0
- package/src/kernel/otel.ts +265 -0
- package/src/lifecycle-observer.ts +100 -0
- package/src/lifecycle.ts +121 -0
- package/src/manifest.ts +94 -0
- package/src/pip-contract.ts +477 -0
- package/src/pip.ts +84 -0
- package/src/test-harness.ts +72 -0
- package/src/timeline.ts +137 -0
- package/templates/app-minimal/AGENTS.md.tmpl +2 -2
package/src/timeline.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII waterfall renderer for DOT lifecycle diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-pip
|
|
5
|
+
* duration chart suitable for printing in a terminal or embedding in a
|
|
6
|
+
* `dot doctor` text report. No colour, no Unicode beyond `█` so the
|
|
7
|
+
* output stays clean in log aggregators and CI.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```
|
|
11
|
+
* Timeline: my-app (state=booted)
|
|
12
|
+
* ─────────────────────────────────────────────────────────
|
|
13
|
+
* configure
|
|
14
|
+
* env 0.5ms █
|
|
15
|
+
* db 1.4ms █████
|
|
16
|
+
* kv 0.3ms █
|
|
17
|
+
*
|
|
18
|
+
* boot
|
|
19
|
+
* env 2.8ms █████████
|
|
20
|
+
* db 32.9ms ███████████████████████████████████████████
|
|
21
|
+
* kv 3.9ms ████████████
|
|
22
|
+
* ─────────────────────────────────────────────────────────
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @see packages/dot/docs/observability.md
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type { DotDiagnosticsSnapshot, LifecycleDiagnostic } from './diagnostics.js';
|
|
29
|
+
import type { DotLifecycleHook } from './lifecycle.js';
|
|
30
|
+
|
|
31
|
+
const PHASE_ORDER: readonly DotLifecycleHook[] = ['configure', 'boot', 'start', 'stop', 'dispose'];
|
|
32
|
+
|
|
33
|
+
const BAR_CHAR = '█';
|
|
34
|
+
const DEFAULT_BAR_WIDTH = 50;
|
|
35
|
+
const MIN_BAR_FOR_NONZERO = 1;
|
|
36
|
+
|
|
37
|
+
export type RenderTimelineOptions = {
|
|
38
|
+
/** Maximum bar width in characters (default `50`). */
|
|
39
|
+
readonly barWidth?: number;
|
|
40
|
+
/**
|
|
41
|
+
* When `true`, also emit any pip's diagnostic `issues[]` underneath
|
|
42
|
+
* the bar so failure reasons are visible in the timeline view.
|
|
43
|
+
* Default `true`.
|
|
44
|
+
*/
|
|
45
|
+
readonly showIssues?: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Render the snapshot's lifecycle records as a per-phase waterfall.
|
|
50
|
+
* Pure — no IO. Caller is responsible for printing the result.
|
|
51
|
+
*/
|
|
52
|
+
export function renderTimeline(snapshot: DotDiagnosticsSnapshot, opts: RenderTimelineOptions = {}): string {
|
|
53
|
+
const barWidth = opts.barWidth ?? DEFAULT_BAR_WIDTH;
|
|
54
|
+
const showIssues = opts.showIssues ?? true;
|
|
55
|
+
|
|
56
|
+
// Group lifecycle entries by phase, preserving execution order within
|
|
57
|
+
// each phase. The kernel pushes entries in execution order; we filter
|
|
58
|
+
// and re-order here to make the output deterministic when phases are
|
|
59
|
+
// interleaved (currently they aren't, but be defensive).
|
|
60
|
+
const byPhase = new Map<DotLifecycleHook, LifecycleDiagnostic[]>();
|
|
61
|
+
for (const entry of snapshot.lifecycle) {
|
|
62
|
+
const bucket = byPhase.get(entry.hook);
|
|
63
|
+
if (bucket) bucket.push(entry);
|
|
64
|
+
else byPhase.set(entry.hook, [entry]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Scale: the longest duration across all entries sets the full-width bar.
|
|
68
|
+
// Using a single global scale lets readers compare durations across
|
|
69
|
+
// phases at a glance ("boot.db is 10x configure.env").
|
|
70
|
+
let maxDuration = 0;
|
|
71
|
+
for (const entry of snapshot.lifecycle) {
|
|
72
|
+
const d = entry.durationMs ?? 0;
|
|
73
|
+
if (d > maxDuration) maxDuration = d;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines: string[] = [];
|
|
77
|
+
const title = `Timeline: ${snapshot.app.name} (state=${snapshot.app.state})`;
|
|
78
|
+
lines.push(title);
|
|
79
|
+
lines.push('─'.repeat(title.length));
|
|
80
|
+
|
|
81
|
+
// Layout: 2-space indent, 10-char pip column, 8-char duration column, then bar.
|
|
82
|
+
const PIP_COL = 12;
|
|
83
|
+
const DUR_COL = 8;
|
|
84
|
+
const indent = ' ';
|
|
85
|
+
|
|
86
|
+
let anyRendered = false;
|
|
87
|
+
for (const phase of PHASE_ORDER) {
|
|
88
|
+
const entries = byPhase.get(phase);
|
|
89
|
+
if (!entries || entries.length === 0) continue;
|
|
90
|
+
if (anyRendered) lines.push('');
|
|
91
|
+
anyRendered = true;
|
|
92
|
+
lines.push(phase);
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const durationMs = entry.durationMs ?? 0;
|
|
95
|
+
const bar = makeBar(durationMs, maxDuration, barWidth);
|
|
96
|
+
const failedMark = entry.state === 'failed' ? ' ✗' : '';
|
|
97
|
+
const pipCol = entry.pip.padEnd(PIP_COL);
|
|
98
|
+
const durCol = formatDuration(durationMs).padStart(DUR_COL);
|
|
99
|
+
lines.push(`${indent}${pipCol}${durCol} ${bar}${failedMark}`);
|
|
100
|
+
if (showIssues && entry.issues.length > 0) {
|
|
101
|
+
for (const issue of entry.issues) {
|
|
102
|
+
lines.push(`${indent} └─ ${issue.code}: ${issue.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!anyRendered) {
|
|
109
|
+
lines.push('(no lifecycle entries — app has not run any phase yet)');
|
|
110
|
+
} else {
|
|
111
|
+
lines.push('─'.repeat(title.length));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build the bar string. Zero-duration entries get a single block (so they
|
|
119
|
+
* are still visually present); otherwise width is proportional to the
|
|
120
|
+
* global max.
|
|
121
|
+
*/
|
|
122
|
+
function makeBar(duration: number, max: number, width: number): string {
|
|
123
|
+
if (max <= 0) return '';
|
|
124
|
+
if (duration <= 0) return BAR_CHAR.repeat(MIN_BAR_FOR_NONZERO);
|
|
125
|
+
const cells = Math.max(MIN_BAR_FOR_NONZERO, Math.round((duration / max) * width));
|
|
126
|
+
return BAR_CHAR.repeat(cells);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Format a duration in milliseconds with one decimal place. Caps the
|
|
131
|
+
* decimal at sub-second values; values ≥ 1000ms render as integer ms
|
|
132
|
+
* to keep the column tight.
|
|
133
|
+
*/
|
|
134
|
+
function formatDuration(ms: number): string {
|
|
135
|
+
if (ms >= 1000) return `${Math.round(ms)}ms`;
|
|
136
|
+
return `${ms.toFixed(1)}ms`;
|
|
137
|
+
}
|
|
@@ -36,8 +36,8 @@ command exits zero.
|
|
|
36
36
|
app reads belong here.
|
|
37
37
|
- Don't reach into `@arki/dot` internals (anything under
|
|
38
38
|
`@arki/dot/kernel`, `@arki/dot/legacy`, or `dist/` subpaths). Use the
|
|
39
|
-
public `defineApp` / `
|
|
40
|
-
`
|
|
39
|
+
public `defineApp` / `pip` API from `@arki/dot` and the
|
|
40
|
+
`pip` / `service` / `rename` symbols from `@arki/dot/pip`.
|
|
41
41
|
|
|
42
42
|
## DOT artifact locations
|
|
43
43
|
|