@doxajs/theoria 0.1.0-alpha.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 +201 -0
- package/NOTICE +4 -0
- package/README.md +13 -0
- package/dist/index 2.js +5 -0
- package/dist/index.d 2.ts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts 2.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js 2.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/migration 2.js +16 -0
- package/dist/migration.d 2.ts +5 -0
- package/dist/migration.d.ts +5 -0
- package/dist/migration.d.ts 2.map +1 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +16 -0
- package/dist/migration.js 2.map +1 -0
- package/dist/migration.js.map +1 -0
- package/dist/postgres-theoria.d.ts +22 -0
- package/dist/postgres-theoria.d.ts.map +1 -0
- package/dist/postgres-theoria.js +138 -0
- package/dist/postgres-theoria.js.map +1 -0
- package/dist/server 2.js +131 -0
- package/dist/server.d 2.ts +11 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.d.ts 2.map +1 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +131 -0
- package/dist/server.js 2.map +1 -0
- package/dist/server.js.map +1 -0
- package/dist/store 2.js +164 -0
- package/dist/store.d 2.ts +40 -0
- package/dist/store.d.ts +40 -0
- package/dist/store.d.ts 2.map +1 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +164 -0
- package/dist/store.js 2.map +1 -0
- package/dist/store.js.map +1 -0
- package/migrations/0001_doxa_theoria.sql +34 -0
- package/migrations/0002_doxa_theoria_sequence.sql +7 -0
- package/package.json +49 -0
package/dist/server 2.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { UndergrowthStore } from './store.js';
|
|
3
|
+
export async function listenUndergrowth(options) {
|
|
4
|
+
const host = options.host ?? '127.0.0.1';
|
|
5
|
+
if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
|
|
6
|
+
throw new Error('Undergrowth binds to loopback only. Use a local tunnel deliberately if remote access is required.');
|
|
7
|
+
}
|
|
8
|
+
const port = options.port ?? 4_400;
|
|
9
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535)
|
|
10
|
+
throw new TypeError('Undergrowth port must be between 0 and 65535.');
|
|
11
|
+
const store = new UndergrowthStore(options.connectionString);
|
|
12
|
+
const server = createServer(async (request, response) => {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(request.url ?? '/', `http://${host}`);
|
|
15
|
+
if (request.method !== 'GET')
|
|
16
|
+
return json(response, 405, {
|
|
17
|
+
ok: false,
|
|
18
|
+
code: 'method_not_allowed',
|
|
19
|
+
message: 'Undergrowth is read-only.',
|
|
20
|
+
data: null,
|
|
21
|
+
});
|
|
22
|
+
if (url.pathname === '/api/executions') {
|
|
23
|
+
const data = await store.executions({
|
|
24
|
+
...(url.searchParams.get('kind') ? { kind: url.searchParams.get('kind') } : {}),
|
|
25
|
+
...(url.searchParams.get('phase') ? { phase: url.searchParams.get('phase') } : {}),
|
|
26
|
+
...(url.searchParams.get('search') ? { search: url.searchParams.get('search') } : {}),
|
|
27
|
+
});
|
|
28
|
+
return json(response, 200, { ok: true, data });
|
|
29
|
+
}
|
|
30
|
+
if (url.pathname === '/api/entries') {
|
|
31
|
+
const kind = url.searchParams.get('kind');
|
|
32
|
+
const data = await store.entries({
|
|
33
|
+
...(kind ? { kind } : {}),
|
|
34
|
+
...(url.searchParams.get('phase') ? { phase: url.searchParams.get('phase') } : {}),
|
|
35
|
+
...(url.searchParams.get('search') ? { search: url.searchParams.get('search') } : {}),
|
|
36
|
+
});
|
|
37
|
+
return json(response, 200, { ok: true, data });
|
|
38
|
+
}
|
|
39
|
+
if (url.pathname.startsWith('/api/timeline/')) {
|
|
40
|
+
const id = decodeURIComponent(url.pathname.slice('/api/timeline/'.length));
|
|
41
|
+
if (!/^[0-9a-f-]{36}$/i.test(id))
|
|
42
|
+
return json(response, 400, {
|
|
43
|
+
ok: false,
|
|
44
|
+
code: 'invalid_execution',
|
|
45
|
+
message: 'Execution ID is invalid.',
|
|
46
|
+
data: null,
|
|
47
|
+
});
|
|
48
|
+
return json(response, 200, { ok: true, data: await store.timeline(id) });
|
|
49
|
+
}
|
|
50
|
+
if (url.pathname === '/api/health')
|
|
51
|
+
return json(response, 200, { ok: true, data: { service: 'undergrowth' } });
|
|
52
|
+
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
53
|
+
response.writeHead(200, {
|
|
54
|
+
'content-type': 'text/html; charset=utf-8',
|
|
55
|
+
'cache-control': 'no-store',
|
|
56
|
+
});
|
|
57
|
+
response.end(UNDERGROWTH_HTML);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
return json(response, 404, {
|
|
61
|
+
ok: false,
|
|
62
|
+
code: 'not_found',
|
|
63
|
+
message: 'Not found.',
|
|
64
|
+
data: null,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return json(response, 500, {
|
|
69
|
+
ok: false,
|
|
70
|
+
code: 'undergrowth_error',
|
|
71
|
+
message: error instanceof Error ? error.message : 'Undergrowth failed.',
|
|
72
|
+
data: null,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
await new Promise((resolve, reject) => {
|
|
77
|
+
server.once('error', reject);
|
|
78
|
+
server.listen(port, host, () => {
|
|
79
|
+
server.off('error', reject);
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
const address = server.address();
|
|
84
|
+
if (!address || typeof address === 'string')
|
|
85
|
+
throw new Error('Undergrowth did not expose a TCP address.');
|
|
86
|
+
return {
|
|
87
|
+
url: new URL(`http://${host === '::1' ? '[::1]' : host}:${address.port}/`),
|
|
88
|
+
shutdown: async () => {
|
|
89
|
+
await closeServer(server);
|
|
90
|
+
await store.close();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function json(response, status, body) {
|
|
95
|
+
response.writeHead(status, {
|
|
96
|
+
'content-type': 'application/json; charset=utf-8',
|
|
97
|
+
'cache-control': 'no-store',
|
|
98
|
+
});
|
|
99
|
+
response.end(JSON.stringify(body));
|
|
100
|
+
}
|
|
101
|
+
async function closeServer(server) {
|
|
102
|
+
if (!server.listening)
|
|
103
|
+
return;
|
|
104
|
+
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
|
105
|
+
}
|
|
106
|
+
const UNDERGROWTH_HTML = String.raw `<!doctype html>
|
|
107
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
108
|
+
<title>Undergrowth · Doxa.js</title><style>
|
|
109
|
+
:root{color-scheme:dark;--bg:#06100e;--panel:#091512;--panel2:#0d1c18;--line:#21342e;--muted:#82958f;--text:#edf4ef;--green:#6ed690;--blue:#57a8ff;--red:#ff665e;--amber:#e9aa43;--mono:ui-monospace,SFMono-Regular,Menlo,monospace;--sans:Inter,ui-sans-serif,system-ui,sans-serif}
|
|
110
|
+
*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 60% -20%,#123327 0,transparent 42%),var(--bg);color:var(--text);font:13px/1.45 var(--sans);overflow:hidden}button,input,select{font:inherit;color:inherit}.shell{height:100vh;display:grid;grid-template-rows:68px 1fr 28px}.top{display:grid;grid-template-columns:390px 1fr 260px;align-items:center;border-bottom:1px solid var(--line);padding:0 20px;gap:20px}.brand{display:flex;gap:13px;align-items:center}.mark{width:34px;height:34px;border:1px solid #2e5d46;background:#102b20;display:grid;place-items:center;color:var(--green);font-size:20px;border-radius:9px}.brand strong{display:block;font-size:19px;letter-spacing:-.02em}.brand small{color:var(--muted)}.search{height:40px;border:1px solid #31483f;border-radius:9px;background:#07100e;display:flex;align-items:center;padding:0 13px;gap:10px}.search input{border:0;outline:0;background:transparent;width:100%;font-family:var(--mono)}.local{justify-self:end;border:1px solid #30433c;background:#0b1714;border-radius:8px;padding:8px 13px}.dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--green);margin-right:8px;box-shadow:0 0 10px #6ed69088}.workspace{min-height:0;display:grid;grid-template-columns:minmax(300px,25%) minmax(500px,1fr) minmax(330px,27%)}.pane{min-width:0;min-height:0;border-right:1px solid var(--line);display:flex;flex-direction:column}.pane:last-child{border-right:0}.pane-head{height:52px;flex:none;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 18px}.pane-head h2{font-size:14px;margin:0}.filters{padding:10px 12px;border-bottom:1px solid var(--line);display:flex;gap:6px;overflow:auto}.chip{border:1px solid var(--line);background:transparent;border-radius:6px;padding:5px 9px;color:var(--muted);cursor:pointer}.chip.active{background:#182822;color:var(--text);border-color:#385447}.scroll{overflow:auto;min-height:0}.execution{width:calc(100% - 20px);margin:8px 10px;padding:13px;border:1px solid transparent;background:transparent;text-align:left;display:grid;gap:8px;cursor:pointer;border-radius:7px}.execution:hover{background:#0d1b17}.execution.active{background:#121d1a;border-color:#4f8d68}.execution.failed{border-left:2px solid var(--red)}.execution-top,.execution-meta{display:flex;align-items:center;gap:8px}.execution-name{font:600 13px var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.execution-meta{color:var(--muted);font:11px var(--mono)}.execution-meta .duration{margin-left:auto}.status{width:8px;height:8px;border-radius:50%;background:var(--green)}.status.failed{background:var(--red)}.kind{font:10px var(--mono);text-transform:uppercase;color:var(--green);background:#173322;padding:3px 5px;border-radius:4px}.empty{margin:auto;text-align:center;color:var(--muted);max-width:280px;padding:30px}.empty strong{display:block;color:var(--text);font-size:15px;margin-bottom:6px}.timeline{padding:22px 20px 60px}.observation{position:relative;display:grid;grid-template-columns:72px 26px minmax(0,1fr) auto;gap:10px;min-height:86px}.observation:before{content:"";position:absolute;left:84px;top:23px;bottom:-10px;width:1px;background:var(--blue)}.observation:last-child:before{display:none}.time{color:var(--muted);font:11px var(--mono);padding-top:6px;text-align:right}.node{width:12px;height:12px;margin:5px auto;border:2px solid var(--blue);background:var(--bg);border-radius:50%;z-index:1}.node.failed{border-color:var(--red);box-shadow:0 0 0 5px #ff665e19}.obs-card{padding:1px 10px 15px;cursor:pointer;min-width:0}.obs-card:hover .obs-name{color:var(--green)}.obs-kind{font:10px var(--mono);text-transform:uppercase;color:var(--muted)}.obs-name{font:600 14px var(--mono);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.obs-role{font:11px var(--mono);color:var(--muted);margin-top:4px}.obs-duration{font:11px var(--mono);color:var(--muted);padding-top:6px}.inspector{padding:18px;display:grid;gap:20px}.failure{color:var(--red);font-weight:650}.section h3{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:0 0 9px}.kv{display:grid;grid-template-columns:110px 1fr;gap:7px;font:11px var(--mono)}.kv dt{color:var(--muted)}.kv dd{margin:0;overflow-wrap:anywhere}.code{margin:0;background:#06100e;border:1px solid var(--line);border-radius:7px;padding:12px;white-space:pre-wrap;overflow:auto;max-height:280px;color:#b9d8cb;font:11px/1.55 var(--mono)}.footer{border-top:1px solid var(--line);display:flex;align-items:center;padding:0 14px;color:var(--muted);font:10px var(--mono)}.footer span:last-child{margin-left:auto}.error-banner{padding:9px 14px;background:#3b1715;color:#ffaaa5;border-bottom:1px solid #6a2823;display:none}.loading{opacity:.55;pointer-events:none}@media(max-width:1050px){.workspace{grid-template-columns:320px 1fr}.inspector-pane{position:fixed;right:0;top:68px;bottom:28px;width:390px;background:var(--panel);box-shadow:-20px 0 60px #0008}.top{grid-template-columns:280px 1fr}.local{display:none}}@media(max-width:720px){body{overflow:auto}.shell{height:auto;min-height:100vh}.top{grid-template-columns:1fr;padding:12px;height:auto}.search{display:none}.workspace{display:block}.pane{height:55vh;border-bottom:1px solid var(--line)}.inspector-pane{position:static;width:auto}.footer{display:none}}
|
|
111
|
+
</style><style>
|
|
112
|
+
.filters{flex:0 0 auto;overflow-x:auto;overflow-y:hidden}.chip{flex:0 0 auto}.scroll{flex:1 1 auto}
|
|
113
|
+
.kv{grid-template-columns:110px minmax(0,1fr)}
|
|
114
|
+
@media(max-width:720px){body{overflow-x:hidden;overflow-y:auto}.shell{width:100%;height:auto;min-height:100vh}.workspace{display:block;width:100%;min-width:0;max-width:100%;overflow:hidden}.pane{width:100%;min-width:0;max-width:100%;height:55vh}.timeline{padding:18px 10px 45px}.observation{grid-template-columns:44px 20px minmax(0,1fr) auto;gap:6px}.observation:before{left:55px}.inspector-pane{width:100%}.kv{grid-template-columns:88px minmax(0,1fr)}}
|
|
115
|
+
</style></head><body><div class="shell">
|
|
116
|
+
<header class="top"><div class="brand"><div class="mark">⌁</div><div><strong>Undergrowth</strong><small>Everything beneath the surface</small></div></div><label class="search"><span>⌕</span><input id="search" placeholder="Search executions, routes, jobs, events, roles…"></label><button class="local"><span class="dot"></span>Local</button></header>
|
|
117
|
+
<main class="workspace"><section class="pane"><div class="pane-head"><h2 id="rail-title">Executions</h2><span id="count">0</span></div><div class="filters" id="filters"><button class="chip active" data-kind="" data-phase="">All</button><button class="chip" data-kind="http">HTTP</button><button class="chip" data-kind="job">Queue</button><button class="chip" data-kind="event">Events</button><button class="chip" data-kind="schedule">Schedules</button><button class="chip" data-phase="failed">Failed</button></div><div class="error-banner" id="error"></div><div class="scroll" id="executions"><div class="empty"><strong>Waiting beneath the surface</strong>Run your Doxa application and executions will appear here.</div></div></section>
|
|
118
|
+
<section class="pane"><div class="pane-head"><h2>Timeline</h2><span id="correlation"></span></div><div class="scroll timeline" id="timeline"><div class="empty"><strong>Choose an execution</strong>Follow every action, query, transaction, model, event, listener, job and exception in causal order.</div></div></section>
|
|
119
|
+
<aside class="pane inspector-pane"><div class="pane-head"><h2>Inspector</h2><span id="selected-kind">—</span></div><div class="scroll inspector" id="inspector"><div class="empty"><strong>No observation selected</strong>Select a point on the timeline to inspect its safe, redacted evidence.</div></div></aside></main>
|
|
120
|
+
<footer class="footer"><span><span class="dot"></span>Watching PostgreSQL</span><span>Read-only · secrets redacted</span></footer></div>
|
|
121
|
+
<script type="module">
|
|
122
|
+
const state={executions:[],timeline:[],execution:null,entry:null,observation:null,kind:'',phase:'',search:''};const el=id=>document.getElementById(id);const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const fmt=v=>v==null?'—':v<1?v.toFixed(2)+' ms':v<1000?Math.round(v)+' ms':(v/1000).toFixed(2)+' s';
|
|
123
|
+
async function api(url){const r=await fetch(url);const body=await r.json();if(!r.ok||!body.ok)throw new Error(body.message||'Undergrowth request failed');return body.data}
|
|
124
|
+
async function loadExecutions(){try{el('error').style.display='none';const p=new URLSearchParams();if(state.kind)p.set('kind',state.kind);if(state.phase)p.set('phase',state.phase);if(state.search)p.set('search',state.search);state.executions=await api('/api/entries?'+p);const selected=state.executions.find(x=>(x.entryId||x.executionId)===(state.entry||state.execution));if(!selected){state.execution=null;state.entry=null}renderExecutions();if(!selected&&state.executions[0])await chooseExecution(state.executions[0].executionId,state.executions[0].entryId)}catch(e){el('error').textContent=e.message;el('error').style.display='block'}}
|
|
125
|
+
function renderExecutions(){const titles={http:'HTTP',job:'Queue',event:'Events',schedule:'Schedules'};el('rail-title').textContent=titles[state.kind]||'Activity';el('count').textContent=state.executions.length;el('executions').innerHTML=state.executions.length?state.executions.map(x=>'<button class="execution '+((x.entryId||x.executionId)===(state.entry||state.execution)?'active ':'')+(x.phase==='failed'?'failed':'')+'" data-id="'+esc(x.executionId)+'" data-entry="'+esc(x.entryId||'')+'"><div class="execution-top"><span class="status '+(x.phase==='failed'?'failed':'')+'"></span><span class="kind">'+esc(x.kind||x.transport||'run')+'</span><span class="execution-name">'+esc(x.name)+'</span></div><div class="execution-meta"><span>'+new Date(x.occurredAt).toLocaleTimeString()+'</span><span>· '+esc(x.phase)+'</span><span class="duration">'+fmt(x.durationMilliseconds)+'</span></div></button>').join(''):'<div class="empty"><strong>No '+esc((titles[state.kind]||'activity').toLowerCase())+' recorded</strong>Run your Doxa application and matching evidence will appear here.</div>';document.querySelectorAll('.execution').forEach(b=>b.onclick=()=>chooseExecution(b.dataset.id,b.dataset.entry||undefined))}
|
|
126
|
+
async function chooseExecution(id,entryId){state.execution=id;state.entry=entryId||null;renderExecutions();state.timeline=await api('/api/timeline/'+encodeURIComponent(id));state.observation=(entryId?state.timeline.find(x=>x.id===entryId):null)||state.timeline.findLast(x=>x.phase==='failed')||state.timeline.at(-1)||null;el('correlation').textContent=state.timeline[0]?.context?.correlationId?'correlation '+state.timeline[0].context.correlationId.slice(0,8):'';renderTimeline();renderInspector()}
|
|
127
|
+
function renderTimeline(){if(!state.timeline.length){el('timeline').innerHTML='<div class="empty"><strong>No evidence recorded</strong>This execution has no observations.</div>';return}const base=Date.parse(state.timeline[0].occurredAt);el('timeline').innerHTML=state.timeline.map(x=>'<div class="observation"><div class="time">+'+(Date.parse(x.occurredAt)-base)+' ms</div><div class="node '+(x.phase==='failed'?'failed':'')+'"></div><div class="obs-card" data-id="'+esc(x.id)+'"><div class="obs-kind">'+esc(x.kind)+' · '+esc(x.phase)+'</div><div class="obs-name">'+esc(x.name)+'</div><div class="obs-role">'+esc(x.roleId||x.context.transportName||'framework boundary')+'</div></div><div class="obs-duration">'+fmt(x.durationMilliseconds)+'</div></div>').join('');document.querySelectorAll('.obs-card').forEach(b=>b.onclick=()=>{state.observation=state.timeline.find(x=>x.id===b.dataset.id);renderInspector()})}
|
|
128
|
+
function renderInspector(){const x=state.observation;if(!x)return;el('selected-kind').textContent=x.kind;const context=Object.entries(x.context||{}).map(([k,v])=>'<dt>'+esc(k)+'</dt><dd>'+esc(v)+'</dd>').join('');el('inspector').innerHTML='<section class="section"><h3>Observation</h3><div class="'+(x.phase==='failed'?'failure':'')+'">'+esc(x.name)+' · '+esc(x.phase)+'</div></section>'+(x.error?'<section class="section"><h3>Exception</h3><div class="kv"><dt>class</dt><dd>'+esc(x.error.name)+'</dd><dt>message</dt><dd>'+esc(x.error.message)+'</dd></div></section>':'')+'<section class="section"><h3>Context</h3><dl class="kv"><dt>occurred</dt><dd>'+esc(new Date(x.occurredAt).toLocaleString())+'</dd><dt>duration</dt><dd>'+fmt(x.durationMilliseconds)+'</dd>'+context+'</dl></section><section class="section"><h3>Safe attributes</h3><pre class="code">'+esc(JSON.stringify(x.attributes,null,2))+'</pre></section>'+(x.error?.stack?'<section class="section"><h3>Stack</h3><pre class="code">'+esc(x.error.stack)+'</pre></section>':'')}
|
|
129
|
+
el('filters').onclick=e=>{const b=e.target.closest('[data-kind],[data-phase]');if(!b)return;state.kind=b.dataset.kind??'';state.phase=b.dataset.phase??'';document.querySelectorAll('.chip').forEach(x=>x.classList.toggle('active',x===b));loadExecutions()};let timer;el('search').oninput=e=>{clearTimeout(timer);timer=setTimeout(()=>{state.search=e.target.value;loadExecutions()},180)};loadExecutions();setInterval(loadExecutions,3000);
|
|
130
|
+
</script></body></html>`;
|
|
131
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UndergrowthServerOptions {
|
|
2
|
+
readonly connectionString: string;
|
|
3
|
+
readonly host?: string;
|
|
4
|
+
readonly port?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface UndergrowthHost {
|
|
7
|
+
readonly url: URL;
|
|
8
|
+
readonly shutdown: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare function listenUndergrowth(options: UndergrowthServerOptions): Promise<UndergrowthHost>;
|
|
11
|
+
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TheoriaServerOptions {
|
|
2
|
+
readonly connectionString: string;
|
|
3
|
+
readonly host?: string;
|
|
4
|
+
readonly port?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TheoriaHost {
|
|
7
|
+
readonly url: URL;
|
|
8
|
+
readonly shutdown: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare function listenTheoria(options: TheoriaServerOptions): Promise<TheoriaHost>;
|
|
11
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACvC;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,eAAe,CAAC,CA2F1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACvC;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CA2FvF"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { TheoriaStore } from './store.js';
|
|
3
|
+
export async function listenTheoria(options) {
|
|
4
|
+
const host = options.host ?? '127.0.0.1';
|
|
5
|
+
if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
|
|
6
|
+
throw new Error('Theoria binds to loopback only. Use a local tunnel deliberately if remote access is required.');
|
|
7
|
+
}
|
|
8
|
+
const port = options.port ?? 4_400;
|
|
9
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535)
|
|
10
|
+
throw new TypeError('Theoria port must be between 0 and 65535.');
|
|
11
|
+
const store = new TheoriaStore(options.connectionString);
|
|
12
|
+
const server = createServer(async (request, response) => {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(request.url ?? '/', `http://${host}`);
|
|
15
|
+
if (request.method !== 'GET')
|
|
16
|
+
return json(response, 405, {
|
|
17
|
+
ok: false,
|
|
18
|
+
code: 'method_not_allowed',
|
|
19
|
+
message: 'Theoria is read-only.',
|
|
20
|
+
data: null,
|
|
21
|
+
});
|
|
22
|
+
if (url.pathname === '/api/executions') {
|
|
23
|
+
const data = await store.executions({
|
|
24
|
+
...(url.searchParams.get('kind') ? { kind: url.searchParams.get('kind') } : {}),
|
|
25
|
+
...(url.searchParams.get('phase') ? { phase: url.searchParams.get('phase') } : {}),
|
|
26
|
+
...(url.searchParams.get('search') ? { search: url.searchParams.get('search') } : {}),
|
|
27
|
+
});
|
|
28
|
+
return json(response, 200, { ok: true, data });
|
|
29
|
+
}
|
|
30
|
+
if (url.pathname === '/api/entries') {
|
|
31
|
+
const kind = url.searchParams.get('kind');
|
|
32
|
+
const data = await store.entries({
|
|
33
|
+
...(kind ? { kind } : {}),
|
|
34
|
+
...(url.searchParams.get('phase') ? { phase: url.searchParams.get('phase') } : {}),
|
|
35
|
+
...(url.searchParams.get('search') ? { search: url.searchParams.get('search') } : {}),
|
|
36
|
+
});
|
|
37
|
+
return json(response, 200, { ok: true, data });
|
|
38
|
+
}
|
|
39
|
+
if (url.pathname.startsWith('/api/timeline/')) {
|
|
40
|
+
const id = decodeURIComponent(url.pathname.slice('/api/timeline/'.length));
|
|
41
|
+
if (!/^[0-9a-f-]{36}$/i.test(id))
|
|
42
|
+
return json(response, 400, {
|
|
43
|
+
ok: false,
|
|
44
|
+
code: 'invalid_execution',
|
|
45
|
+
message: 'Execution ID is invalid.',
|
|
46
|
+
data: null,
|
|
47
|
+
});
|
|
48
|
+
return json(response, 200, { ok: true, data: await store.timeline(id) });
|
|
49
|
+
}
|
|
50
|
+
if (url.pathname === '/api/health')
|
|
51
|
+
return json(response, 200, { ok: true, data: { service: 'theoria' } });
|
|
52
|
+
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
53
|
+
response.writeHead(200, {
|
|
54
|
+
'content-type': 'text/html; charset=utf-8',
|
|
55
|
+
'cache-control': 'no-store',
|
|
56
|
+
});
|
|
57
|
+
response.end(THEORIA_HTML);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
return json(response, 404, {
|
|
61
|
+
ok: false,
|
|
62
|
+
code: 'not_found',
|
|
63
|
+
message: 'Not found.',
|
|
64
|
+
data: null,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return json(response, 500, {
|
|
69
|
+
ok: false,
|
|
70
|
+
code: 'theoria_error',
|
|
71
|
+
message: error instanceof Error ? error.message : 'Theoria failed.',
|
|
72
|
+
data: null,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
await new Promise((resolve, reject) => {
|
|
77
|
+
server.once('error', reject);
|
|
78
|
+
server.listen(port, host, () => {
|
|
79
|
+
server.off('error', reject);
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
const address = server.address();
|
|
84
|
+
if (!address || typeof address === 'string')
|
|
85
|
+
throw new Error('Theoria did not expose a TCP address.');
|
|
86
|
+
return {
|
|
87
|
+
url: new URL(`http://${host === '::1' ? '[::1]' : host}:${address.port}/`),
|
|
88
|
+
shutdown: async () => {
|
|
89
|
+
await closeServer(server);
|
|
90
|
+
await store.close();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function json(response, status, body) {
|
|
95
|
+
response.writeHead(status, {
|
|
96
|
+
'content-type': 'application/json; charset=utf-8',
|
|
97
|
+
'cache-control': 'no-store',
|
|
98
|
+
});
|
|
99
|
+
response.end(JSON.stringify(body));
|
|
100
|
+
}
|
|
101
|
+
async function closeServer(server) {
|
|
102
|
+
if (!server.listening)
|
|
103
|
+
return;
|
|
104
|
+
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
|
105
|
+
}
|
|
106
|
+
const THEORIA_HTML = String.raw `<!doctype html>
|
|
107
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
108
|
+
<title>Theoria · Doxa.js</title><style>
|
|
109
|
+
:root{color-scheme:dark;--bg:#070d1a;--panel:#0b1220;--panel2:#111a2d;--line:#263451;--muted:#91a0b8;--text:#eff6ff;--primary:#3b82f6;--tertiary:#f97316;--danger:#ef4444;--mono:ui-monospace,SFMono-Regular,Menlo,monospace;--sans:Inter,ui-sans-serif,system-ui,sans-serif}
|
|
110
|
+
*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 60% -20%,#172554 0,transparent 44%),var(--bg);color:var(--text);font:13px/1.45 var(--sans);overflow:hidden}button,input,select{font:inherit;color:inherit}.shell{height:100vh;display:grid;grid-template-rows:68px 1fr 28px}.top{display:grid;grid-template-columns:390px 1fr 260px;align-items:center;border-bottom:1px solid var(--line);padding:0 20px;gap:20px}.brand{display:flex;gap:13px;align-items:center}.mark{width:34px;height:34px;border:1px solid #2563eb;background:#172554;display:grid;place-items:center;color:var(--primary);font-size:20px;border-radius:9px}.brand strong{display:block;font-size:19px;letter-spacing:-.02em}.brand small{color:var(--muted)}.search{height:40px;border:1px solid #334155;border-radius:9px;background:#070d1a;display:flex;align-items:center;padding:0 13px;gap:10px}.search input{border:0;outline:0;background:transparent;width:100%;font-family:var(--mono)}.local{justify-self:end;border:1px solid #334155;background:#0f172a;border-radius:8px;padding:8px 13px}.dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--primary);margin-right:8px;box-shadow:0 0 10px #3b82f688}.workspace{min-height:0;display:grid;grid-template-columns:minmax(300px,25%) minmax(500px,1fr) minmax(330px,27%)}.pane{min-width:0;min-height:0;border-right:1px solid var(--line);display:flex;flex-direction:column}.pane:last-child{border-right:0}.pane-head{height:52px;flex:none;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 18px}.pane-head h2{font-size:14px;margin:0}.filters{padding:10px 12px;border-bottom:1px solid var(--line);display:flex;gap:6px;overflow:auto}.chip{border:1px solid var(--line);background:transparent;border-radius:6px;padding:5px 9px;color:var(--muted);cursor:pointer}.chip.active{background:#172554;color:var(--text);border-color:var(--primary)}.scroll{overflow:auto;min-height:0}.execution{width:calc(100% - 20px);margin:8px 10px;padding:13px;border:1px solid transparent;background:transparent;text-align:left;display:grid;gap:8px;cursor:pointer;border-radius:7px}.execution:hover{background:#0f172a}.execution.active{background:#111c35;border-color:var(--primary)}.execution.failed{border-left:2px solid var(--danger)}.execution-top,.execution-meta{display:flex;align-items:center;gap:8px}.execution-name{font:600 13px var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.execution-meta{color:var(--muted);font:11px var(--mono)}.execution-meta .duration{margin-left:auto}.status{width:8px;height:8px;border-radius:50%;background:var(--primary)}.status.failed{background:var(--danger)}.kind{font:10px var(--mono);text-transform:uppercase;color:var(--tertiary);background:#431407;padding:3px 5px;border-radius:4px}.empty{margin:auto;text-align:center;color:var(--muted);max-width:280px;padding:30px}.empty strong{display:block;color:var(--text);font-size:15px;margin-bottom:6px}.timeline{padding:22px 20px 60px}.observation{position:relative;display:grid;grid-template-columns:72px 26px minmax(0,1fr) auto;gap:10px;min-height:86px}.observation:before{content:"";position:absolute;left:84px;top:23px;bottom:-10px;width:1px;background:var(--primary)}.observation:last-child:before{display:none}.time{color:var(--muted);font:11px var(--mono);padding-top:6px;text-align:right}.node{width:12px;height:12px;margin:5px auto;border:2px solid var(--primary);background:var(--bg);border-radius:50%;z-index:1}.node.failed{border-color:var(--danger);box-shadow:0 0 0 5px #ef444419}.obs-card{padding:1px 10px 15px;cursor:pointer;min-width:0}.obs-card:hover .obs-name{color:var(--primary)}.obs-kind{font:10px var(--mono);text-transform:uppercase;color:var(--muted)}.obs-name{font:600 14px var(--mono);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.obs-role{font:11px var(--mono);color:var(--muted);margin-top:4px}.obs-duration{font:11px var(--mono);color:var(--muted);padding-top:6px}.inspector{padding:18px;display:grid;gap:20px}.failure{color:var(--danger);font-weight:650}.section h3{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:0 0 9px}.kv{display:grid;grid-template-columns:110px 1fr;gap:7px;font:11px var(--mono)}.kv dt{color:var(--muted)}.kv dd{margin:0;overflow-wrap:anywhere}.code{margin:0;background:#070d1a;border:1px solid var(--line);border-radius:7px;padding:12px;white-space:pre-wrap;overflow:auto;max-height:280px;color:#bfdbfe;font:11px/1.55 var(--mono)}.footer{border-top:1px solid var(--line);display:flex;align-items:center;padding:0 14px;color:var(--muted);font:10px var(--mono)}.footer span:last-child{margin-left:auto}.error-banner{padding:9px 14px;background:#3b1715;color:#fecaca;border-bottom:1px solid #7f1d1d;display:none}.loading{opacity:.55;pointer-events:none}@media(max-width:1050px){.workspace{grid-template-columns:320px 1fr}.inspector-pane{position:fixed;right:0;top:68px;bottom:28px;width:390px;background:var(--panel);box-shadow:-20px 0 60px #0008}.top{grid-template-columns:280px 1fr}.local{display:none}}@media(max-width:720px){body{overflow:auto}.shell{height:auto;min-height:100vh}.top{grid-template-columns:1fr;padding:12px;height:auto}.search{display:none}.workspace{display:block}.pane{height:55vh;border-bottom:1px solid var(--line)}.inspector-pane{position:static;width:auto}.footer{display:none}}
|
|
111
|
+
</style><style>
|
|
112
|
+
.filters{flex:0 0 auto;overflow-x:auto;overflow-y:hidden}.chip{flex:0 0 auto}.scroll{flex:1 1 auto}
|
|
113
|
+
.kv{grid-template-columns:110px minmax(0,1fr)}
|
|
114
|
+
@media(max-width:720px){body{overflow-x:hidden;overflow-y:auto}.shell{width:100%;height:auto;min-height:100vh}.workspace{display:block;width:100%;min-width:0;max-width:100%;overflow:hidden}.pane{width:100%;min-width:0;max-width:100%;height:55vh}.timeline{padding:18px 10px 45px}.observation{grid-template-columns:44px 20px minmax(0,1fr) auto;gap:6px}.observation:before{left:55px}.inspector-pane{width:100%}.kv{grid-template-columns:88px minmax(0,1fr)}}
|
|
115
|
+
</style></head><body><div class="shell">
|
|
116
|
+
<header class="top"><div class="brand"><div class="mark">⌁</div><div><strong>Theoria</strong><small>Everything beneath the surface</small></div></div><label class="search"><span>⌕</span><input id="search" placeholder="Search executions, routes, jobs, events, roles…"></label><button class="local"><span class="dot"></span>Local</button></header>
|
|
117
|
+
<main class="workspace"><section class="pane"><div class="pane-head"><h2 id="rail-title">Executions</h2><span id="count">0</span></div><div class="filters" id="filters"><button class="chip active" data-kind="" data-phase="">All</button><button class="chip" data-kind="http">HTTP</button><button class="chip" data-kind="job">Queue</button><button class="chip" data-kind="event">Events</button><button class="chip" data-kind="schedule">Schedules</button><button class="chip" data-phase="failed">Failed</button></div><div class="error-banner" id="error"></div><div class="scroll" id="executions"><div class="empty"><strong>Waiting beneath the surface</strong>Run your Doxa application and executions will appear here.</div></div></section>
|
|
118
|
+
<section class="pane"><div class="pane-head"><h2>Timeline</h2><span id="correlation"></span></div><div class="scroll timeline" id="timeline"><div class="empty"><strong>Choose an execution</strong>Follow every action, query, transaction, model, event, listener, job and exception in causal order.</div></div></section>
|
|
119
|
+
<aside class="pane inspector-pane"><div class="pane-head"><h2>Inspector</h2><span id="selected-kind">—</span></div><div class="scroll inspector" id="inspector"><div class="empty"><strong>No observation selected</strong>Select a point on the timeline to inspect its safe, redacted evidence.</div></div></aside></main>
|
|
120
|
+
<footer class="footer"><span><span class="dot"></span>Watching PostgreSQL</span><span>Read-only · secrets redacted</span></footer></div>
|
|
121
|
+
<script type="module">
|
|
122
|
+
const state={executions:[],timeline:[],execution:null,entry:null,observation:null,kind:'',phase:'',search:''};const el=id=>document.getElementById(id);const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const fmt=v=>v==null?'—':v<1?v.toFixed(2)+' ms':v<1000?Math.round(v)+' ms':(v/1000).toFixed(2)+' s';
|
|
123
|
+
async function api(url){const r=await fetch(url);const body=await r.json();if(!r.ok||!body.ok)throw new Error(body.message||'Theoria request failed');return body.data}
|
|
124
|
+
async function loadExecutions(){try{el('error').style.display='none';const p=new URLSearchParams();if(state.kind)p.set('kind',state.kind);if(state.phase)p.set('phase',state.phase);if(state.search)p.set('search',state.search);state.executions=await api('/api/entries?'+p);const selected=state.executions.find(x=>(x.entryId||x.executionId)===(state.entry||state.execution));if(!selected){state.execution=null;state.entry=null}renderExecutions();if(!selected&&state.executions[0])await chooseExecution(state.executions[0].executionId,state.executions[0].entryId)}catch(e){el('error').textContent=e.message;el('error').style.display='block'}}
|
|
125
|
+
function renderExecutions(){const titles={http:'HTTP',job:'Queue',event:'Events',schedule:'Schedules'};el('rail-title').textContent=titles[state.kind]||'Activity';el('count').textContent=state.executions.length;el('executions').innerHTML=state.executions.length?state.executions.map(x=>'<button class="execution '+((x.entryId||x.executionId)===(state.entry||state.execution)?'active ':'')+(x.phase==='failed'?'failed':'')+'" data-id="'+esc(x.executionId)+'" data-entry="'+esc(x.entryId||'')+'"><div class="execution-top"><span class="status '+(x.phase==='failed'?'failed':'')+'"></span><span class="kind">'+esc(x.kind||x.transport||'run')+'</span><span class="execution-name">'+esc(x.name)+'</span></div><div class="execution-meta"><span>'+new Date(x.occurredAt).toLocaleTimeString()+'</span><span>· '+esc(x.phase)+'</span><span class="duration">'+fmt(x.durationMilliseconds)+'</span></div></button>').join(''):'<div class="empty"><strong>No '+esc((titles[state.kind]||'activity').toLowerCase())+' recorded</strong>Run your Doxa application and matching evidence will appear here.</div>';document.querySelectorAll('.execution').forEach(b=>b.onclick=()=>chooseExecution(b.dataset.id,b.dataset.entry||undefined))}
|
|
126
|
+
async function chooseExecution(id,entryId){state.execution=id;state.entry=entryId||null;renderExecutions();state.timeline=await api('/api/timeline/'+encodeURIComponent(id));state.observation=(entryId?state.timeline.find(x=>x.id===entryId):null)||state.timeline.findLast(x=>x.phase==='failed')||state.timeline.at(-1)||null;el('correlation').textContent=state.timeline[0]?.context?.correlationId?'correlation '+state.timeline[0].context.correlationId.slice(0,8):'';renderTimeline();renderInspector()}
|
|
127
|
+
function renderTimeline(){if(!state.timeline.length){el('timeline').innerHTML='<div class="empty"><strong>No evidence recorded</strong>This execution has no observations.</div>';return}const base=Date.parse(state.timeline[0].occurredAt);el('timeline').innerHTML=state.timeline.map(x=>'<div class="observation"><div class="time">+'+(Date.parse(x.occurredAt)-base)+' ms</div><div class="node '+(x.phase==='failed'?'failed':'')+'"></div><div class="obs-card" data-id="'+esc(x.id)+'"><div class="obs-kind">'+esc(x.kind)+' · '+esc(x.phase)+'</div><div class="obs-name">'+esc(x.name)+'</div><div class="obs-role">'+esc(x.roleId||x.context.transportName||'framework boundary')+'</div></div><div class="obs-duration">'+fmt(x.durationMilliseconds)+'</div></div>').join('');document.querySelectorAll('.obs-card').forEach(b=>b.onclick=()=>{state.observation=state.timeline.find(x=>x.id===b.dataset.id);renderInspector()})}
|
|
128
|
+
function renderInspector(){const x=state.observation;if(!x)return;el('selected-kind').textContent=x.kind;const context=Object.entries(x.context||{}).map(([k,v])=>'<dt>'+esc(k)+'</dt><dd>'+esc(v)+'</dd>').join('');el('inspector').innerHTML='<section class="section"><h3>Observation</h3><div class="'+(x.phase==='failed'?'failure':'')+'">'+esc(x.name)+' · '+esc(x.phase)+'</div></section>'+(x.error?'<section class="section"><h3>Exception</h3><div class="kv"><dt>class</dt><dd>'+esc(x.error.name)+'</dd><dt>message</dt><dd>'+esc(x.error.message)+'</dd></div></section>':'')+'<section class="section"><h3>Context</h3><dl class="kv"><dt>occurred</dt><dd>'+esc(new Date(x.occurredAt).toLocaleString())+'</dd><dt>duration</dt><dd>'+fmt(x.durationMilliseconds)+'</dd>'+context+'</dl></section><section class="section"><h3>Safe attributes</h3><pre class="code">'+esc(JSON.stringify(x.attributes,null,2))+'</pre></section>'+(x.error?.stack?'<section class="section"><h3>Stack</h3><pre class="code">'+esc(x.error.stack)+'</pre></section>':'')}
|
|
129
|
+
el('filters').onclick=e=>{const b=e.target.closest('[data-kind],[data-phase]');if(!b)return;state.kind=b.dataset.kind??'';state.phase=b.dataset.phase??'';document.querySelectorAll('.chip').forEach(x=>x.classList.toggle('active',x===b));loadExecutions()};let timer;el('search').oninput=e=>{clearTimeout(timer);timer=setTimeout(()=>{state.search=e.target.value;loadExecutions()},180)};loadExecutions();setInterval(loadExecutions,3000);
|
|
130
|
+
</script></body></html>`;
|
|
131
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/store 2.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
export class UndergrowthStore {
|
|
3
|
+
#pool;
|
|
4
|
+
constructor(connectionString) {
|
|
5
|
+
this.#pool = new Pool({ connectionString, application_name: 'doxa-undergrowth-ui' });
|
|
6
|
+
}
|
|
7
|
+
async executions(query = {}) {
|
|
8
|
+
const limit = Math.min(Math.max(query.limit ?? 100, 1), 500);
|
|
9
|
+
const values = [];
|
|
10
|
+
const conditions = ['candidate.execution_id IS NOT NULL'];
|
|
11
|
+
if (query.kind) {
|
|
12
|
+
values.push(query.kind);
|
|
13
|
+
conditions.push(`candidate.kind = $${values.length}`);
|
|
14
|
+
}
|
|
15
|
+
if (query.phase) {
|
|
16
|
+
values.push(query.phase);
|
|
17
|
+
conditions.push(`candidate.phase = $${values.length}`);
|
|
18
|
+
}
|
|
19
|
+
if (query.search) {
|
|
20
|
+
values.push(`%${query.search}%`);
|
|
21
|
+
conditions.push(`(candidate.name ILIKE $${values.length} OR candidate.role_id ILIKE $${values.length} OR candidate.actor_id ILIKE $${values.length} OR candidate.execution_id::text ILIKE $${values.length} OR candidate.correlation_id::text ILIKE $${values.length})`);
|
|
22
|
+
}
|
|
23
|
+
values.push(limit);
|
|
24
|
+
const result = await this.#pool.query(`
|
|
25
|
+
WITH eligible AS (
|
|
26
|
+
SELECT DISTINCT candidate.execution_id
|
|
27
|
+
FROM doxa_undergrowth_observations candidate
|
|
28
|
+
WHERE ${conditions.join(' AND ')}
|
|
29
|
+
), ranked AS (
|
|
30
|
+
SELECT observation.execution_id, observation.correlation_id, observation.source_execution_id,
|
|
31
|
+
observation.name, observation.transport, observation.phase, observation.occurred_at,
|
|
32
|
+
observation.duration_ms,
|
|
33
|
+
count(*) OVER (PARTITION BY observation.execution_id) AS observation_count,
|
|
34
|
+
row_number() OVER (
|
|
35
|
+
PARTITION BY observation.execution_id
|
|
36
|
+
ORDER BY
|
|
37
|
+
CASE
|
|
38
|
+
WHEN observation.kind = 'execution' AND observation.phase IN ('completed', 'failed') THEN 0
|
|
39
|
+
WHEN observation.kind = 'execution' THEN 1
|
|
40
|
+
ELSE 2
|
|
41
|
+
END,
|
|
42
|
+
observation.sequence DESC NULLS LAST,
|
|
43
|
+
observation.occurred_at DESC,
|
|
44
|
+
observation.id DESC
|
|
45
|
+
) AS position
|
|
46
|
+
FROM doxa_undergrowth_observations observation
|
|
47
|
+
JOIN eligible ON eligible.execution_id = observation.execution_id
|
|
48
|
+
)
|
|
49
|
+
SELECT execution_id, correlation_id, source_execution_id, name, transport, phase,
|
|
50
|
+
occurred_at, duration_ms, observation_count
|
|
51
|
+
FROM ranked WHERE position = 1
|
|
52
|
+
ORDER BY occurred_at DESC, execution_id
|
|
53
|
+
LIMIT $${values.length}
|
|
54
|
+
`, values);
|
|
55
|
+
return result.rows.map((row) => ({
|
|
56
|
+
executionId: row.execution_id,
|
|
57
|
+
...(row.correlation_id ? { correlationId: row.correlation_id } : {}),
|
|
58
|
+
...(row.source_execution_id ? { sourceExecutionId: row.source_execution_id } : {}),
|
|
59
|
+
name: row.name,
|
|
60
|
+
...(row.transport ? { transport: row.transport } : {}),
|
|
61
|
+
phase: row.phase,
|
|
62
|
+
occurredAt: row.occurred_at.toISOString(),
|
|
63
|
+
...(row.duration_ms === null ? {} : { durationMilliseconds: row.duration_ms }),
|
|
64
|
+
observationCount: Number(row.observation_count),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
async entries(query) {
|
|
68
|
+
const limit = Math.min(Math.max(query.limit ?? 100, 1), 500);
|
|
69
|
+
const values = [];
|
|
70
|
+
const conditions = ['execution_id IS NOT NULL', "phase IN ('occurred', 'completed', 'failed')"];
|
|
71
|
+
if (query.kind) {
|
|
72
|
+
values.push(query.kind);
|
|
73
|
+
conditions.push(`kind = $${values.length}`);
|
|
74
|
+
}
|
|
75
|
+
if (query.phase) {
|
|
76
|
+
values.push(query.phase);
|
|
77
|
+
conditions.push(`phase = $${values.length}`);
|
|
78
|
+
}
|
|
79
|
+
if (query.search) {
|
|
80
|
+
values.push(`%${query.search}%`);
|
|
81
|
+
conditions.push(`(name ILIKE $${values.length} OR role_id ILIKE $${values.length} OR actor_id ILIKE $${values.length} OR execution_id::text ILIKE $${values.length} OR correlation_id::text ILIKE $${values.length})`);
|
|
82
|
+
}
|
|
83
|
+
values.push(limit);
|
|
84
|
+
const result = await this.#pool.query(`
|
|
85
|
+
SELECT id, execution_id, correlation_id, source_execution_id, name, kind, role_id,
|
|
86
|
+
transport, phase, occurred_at, duration_ms
|
|
87
|
+
FROM doxa_undergrowth_observations
|
|
88
|
+
WHERE ${conditions.join(' AND ')}
|
|
89
|
+
ORDER BY occurred_at DESC, id DESC
|
|
90
|
+
LIMIT $${values.length}
|
|
91
|
+
`, values);
|
|
92
|
+
return result.rows.map((row) => ({
|
|
93
|
+
entryId: row.id,
|
|
94
|
+
executionId: row.execution_id,
|
|
95
|
+
...(row.correlation_id ? { correlationId: row.correlation_id } : {}),
|
|
96
|
+
...(row.source_execution_id ? { sourceExecutionId: row.source_execution_id } : {}),
|
|
97
|
+
name: row.name,
|
|
98
|
+
kind: row.kind,
|
|
99
|
+
...(row.role_id ? { roleId: row.role_id } : {}),
|
|
100
|
+
...(row.transport ? { transport: row.transport } : {}),
|
|
101
|
+
phase: row.phase,
|
|
102
|
+
occurredAt: row.occurred_at.toISOString(),
|
|
103
|
+
...(row.duration_ms === null ? {} : { durationMilliseconds: row.duration_ms }),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
async timeline(executionId) {
|
|
107
|
+
const correlation = await this.#pool.query(`
|
|
108
|
+
SELECT correlation_id FROM doxa_undergrowth_observations
|
|
109
|
+
WHERE execution_id = $1 ORDER BY sequence NULLS LAST, occurred_at, id LIMIT 1
|
|
110
|
+
`, [executionId]);
|
|
111
|
+
const correlationId = correlation.rows[0]?.correlation_id;
|
|
112
|
+
const result = await this.#pool.query(`
|
|
113
|
+
SELECT * FROM doxa_undergrowth_observations
|
|
114
|
+
WHERE execution_id = $1 OR ($2::uuid IS NOT NULL AND correlation_id = $2::uuid)
|
|
115
|
+
ORDER BY sequence NULLS LAST, occurred_at, id
|
|
116
|
+
`, [executionId, correlationId ?? null]);
|
|
117
|
+
return result.rows.map(toObservation);
|
|
118
|
+
}
|
|
119
|
+
async close() {
|
|
120
|
+
await this.#pool.end();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function toObservation(row) {
|
|
124
|
+
const optional = (key) => row[key] === null || row[key] === undefined ? undefined : row[key];
|
|
125
|
+
return {
|
|
126
|
+
id: String(row.id),
|
|
127
|
+
occurredAt: row.occurred_at.toISOString(),
|
|
128
|
+
kind: row.kind,
|
|
129
|
+
name: String(row.name),
|
|
130
|
+
phase: row.phase,
|
|
131
|
+
...(optional('role_id') ? { roleId: optional('role_id') } : {}),
|
|
132
|
+
...(optional('duration_ms') === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { durationMilliseconds: optional('duration_ms') }),
|
|
135
|
+
context: {
|
|
136
|
+
...(optional('execution_id')
|
|
137
|
+
? { executionId: optional('execution_id') }
|
|
138
|
+
: {}),
|
|
139
|
+
...(optional('source_execution_id')
|
|
140
|
+
? { sourceExecutionId: optional('source_execution_id') }
|
|
141
|
+
: {}),
|
|
142
|
+
...(optional('correlation_id')
|
|
143
|
+
? { correlationId: optional('correlation_id') }
|
|
144
|
+
: {}),
|
|
145
|
+
...(optional('causation_id')
|
|
146
|
+
? { causationId: optional('causation_id') }
|
|
147
|
+
: {}),
|
|
148
|
+
...(optional('trace_id') ? { traceId: optional('trace_id') } : {}),
|
|
149
|
+
...(optional('span_id') ? { spanId: optional('span_id') } : {}),
|
|
150
|
+
...(optional('actor_kind')
|
|
151
|
+
? { actorKind: optional('actor_kind') }
|
|
152
|
+
: {}),
|
|
153
|
+
...(optional('actor_id') ? { actorId: optional('actor_id') } : {}),
|
|
154
|
+
...(optional('tenant_id') ? { tenantId: optional('tenant_id') } : {}),
|
|
155
|
+
...(optional('transport') ? { transport: optional('transport') } : {}),
|
|
156
|
+
...(optional('transport_name')
|
|
157
|
+
? { transportName: optional('transport_name') }
|
|
158
|
+
: {}),
|
|
159
|
+
},
|
|
160
|
+
attributes: row.attributes,
|
|
161
|
+
...(row.error ? { error: row.error } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Observation } from '@doxajs/core';
|
|
2
|
+
export interface UndergrowthQuery {
|
|
3
|
+
readonly kind?: string;
|
|
4
|
+
readonly phase?: string;
|
|
5
|
+
readonly search?: string;
|
|
6
|
+
readonly limit?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface UndergrowthExecution {
|
|
9
|
+
readonly executionId: string;
|
|
10
|
+
readonly correlationId?: string;
|
|
11
|
+
readonly sourceExecutionId?: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly transport?: string;
|
|
14
|
+
readonly phase: string;
|
|
15
|
+
readonly occurredAt: string;
|
|
16
|
+
readonly durationMilliseconds?: number;
|
|
17
|
+
readonly observationCount: number;
|
|
18
|
+
}
|
|
19
|
+
export interface UndergrowthEntry {
|
|
20
|
+
readonly entryId: string;
|
|
21
|
+
readonly executionId: string;
|
|
22
|
+
readonly correlationId?: string;
|
|
23
|
+
readonly sourceExecutionId?: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly kind: string;
|
|
26
|
+
readonly roleId?: string;
|
|
27
|
+
readonly transport?: string;
|
|
28
|
+
readonly phase: string;
|
|
29
|
+
readonly occurredAt: string;
|
|
30
|
+
readonly durationMilliseconds?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class UndergrowthStore {
|
|
33
|
+
#private;
|
|
34
|
+
constructor(connectionString: string);
|
|
35
|
+
executions(query?: UndergrowthQuery): Promise<readonly UndergrowthExecution[]>;
|
|
36
|
+
entries(query: UndergrowthQuery): Promise<readonly UndergrowthEntry[]>;
|
|
37
|
+
timeline(executionId: string): Promise<readonly Observation[]>;
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=store.d.ts.map
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Observation } from '@doxajs/core';
|
|
2
|
+
export interface TheoriaQuery {
|
|
3
|
+
readonly kind?: string;
|
|
4
|
+
readonly phase?: string;
|
|
5
|
+
readonly search?: string;
|
|
6
|
+
readonly limit?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TheoriaExecution {
|
|
9
|
+
readonly executionId: string;
|
|
10
|
+
readonly correlationId?: string;
|
|
11
|
+
readonly sourceExecutionId?: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly transport?: string;
|
|
14
|
+
readonly phase: string;
|
|
15
|
+
readonly occurredAt: string;
|
|
16
|
+
readonly durationMilliseconds?: number;
|
|
17
|
+
readonly observationCount: number;
|
|
18
|
+
}
|
|
19
|
+
export interface TheoriaEntry {
|
|
20
|
+
readonly entryId: string;
|
|
21
|
+
readonly executionId: string;
|
|
22
|
+
readonly correlationId?: string;
|
|
23
|
+
readonly sourceExecutionId?: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly kind: string;
|
|
26
|
+
readonly roleId?: string;
|
|
27
|
+
readonly transport?: string;
|
|
28
|
+
readonly phase: string;
|
|
29
|
+
readonly occurredAt: string;
|
|
30
|
+
readonly durationMilliseconds?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class TheoriaStore {
|
|
33
|
+
#private;
|
|
34
|
+
constructor(connectionString: string);
|
|
35
|
+
executions(query?: TheoriaQuery): Promise<readonly TheoriaExecution[]>;
|
|
36
|
+
entries(query: TheoriaQuery): Promise<readonly TheoriaEntry[]>;
|
|
37
|
+
timeline(executionId: string): Promise<readonly Observation[]>;
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CACvC;AAED,qBAAa,gBAAgB;;gBAGf,gBAAgB,EAAE,MAAM;IAI9B,UAAU,CAAC,KAAK,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC;IA4ElF,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC;IAyDtE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAoB9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CACvC;AAED,qBAAa,YAAY;;gBAGX,gBAAgB,EAAE,MAAM;IAI9B,UAAU,CAAC,KAAK,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC;IA4E1E,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAyD9D,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAoB9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|