graphvault-studio 0.1.2 → 0.1.4
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/CHANGELOG.md +19 -0
- package/README.md +39 -2
- package/dist/admin-cli.d.ts +3 -0
- package/dist/admin-cli.js +52 -3
- package/dist/admin-cli.js.map +1 -1
- package/dist/admin-client.d.ts +21 -3
- package/dist/admin-client.js +308 -55
- package/dist/admin-client.js.map +1 -1
- package/dist/admin-compatibility.d.ts +2 -0
- package/dist/admin-compatibility.js +53 -0
- package/dist/admin-compatibility.js.map +1 -0
- package/dist/admin-integrity.d.ts +26 -0
- package/dist/admin-integrity.js +76 -0
- package/dist/admin-integrity.js.map +1 -0
- package/dist/admin-server.d.ts +6 -0
- package/dist/admin-server.js +102 -6
- package/dist/admin-server.js.map +1 -1
- package/dist/admin-storage-io.d.ts +10 -0
- package/dist/admin-storage-io.js +80 -0
- package/dist/admin-storage-io.js.map +1 -0
- package/dist/admin-subtree.d.ts +8 -0
- package/dist/admin-subtree.js +92 -0
- package/dist/admin-subtree.js.map +1 -0
- package/dist/admin-types.d.ts +91 -0
- package/dist/admin-ui.js +108 -12
- package/dist/admin-ui.js.map +1 -1
- package/docs/PUBLISHING.md +5 -2
- package/docs/RELEASE_NOTES_0.1.0.md +2 -0
- package/docs/REMOTE_STORAGE.md +19 -1
- package/package.json +5 -3
package/dist/admin-types.d.ts
CHANGED
|
@@ -5,17 +5,71 @@ export interface StorageAdminClientOptions {
|
|
|
5
5
|
storageTarget?: StorageTarget;
|
|
6
6
|
channelCount?: number;
|
|
7
7
|
allowMutations?: boolean;
|
|
8
|
+
lockTimeoutMs?: number;
|
|
9
|
+
staleLockTimeoutMs?: number;
|
|
10
|
+
transactionLog?: "full" | "off";
|
|
11
|
+
}
|
|
12
|
+
export interface AdminStorageHardening {
|
|
13
|
+
mutationsAllowed: boolean;
|
|
14
|
+
transactionLog: "full" | "off";
|
|
15
|
+
writerLock: "enabled";
|
|
16
|
+
fencingTokens: "used-when-supported";
|
|
17
|
+
staleLockRecovery: boolean;
|
|
8
18
|
}
|
|
9
19
|
export interface AdminSummary {
|
|
10
20
|
storageDirectory: string;
|
|
11
21
|
transactionId: number;
|
|
12
22
|
currentSnapshot?: string;
|
|
13
23
|
objectCount: number;
|
|
24
|
+
library: AdminLibraryCompatibility;
|
|
14
25
|
latestTransaction?: TransactionRecord;
|
|
15
26
|
typeDictionary?: TypeDictionary;
|
|
27
|
+
hardening: AdminStorageHardening;
|
|
28
|
+
operations: AdminOperationalStatus;
|
|
29
|
+
productionSafety: AdminProductionSafety;
|
|
16
30
|
verification?: VerificationResult;
|
|
17
31
|
verificationSkipped?: boolean;
|
|
18
32
|
}
|
|
33
|
+
export interface AdminOperationalStatus {
|
|
34
|
+
transactionLog: "full" | "off";
|
|
35
|
+
mutationsAllowed: boolean;
|
|
36
|
+
lockTimeoutMs: number;
|
|
37
|
+
staleLockTimeoutMs?: number;
|
|
38
|
+
walPrepareFiles: number;
|
|
39
|
+
walCommitFiles: number;
|
|
40
|
+
latestWalTransactionId: number;
|
|
41
|
+
latestJournalTransactionId: number;
|
|
42
|
+
publishedTransactionId: number;
|
|
43
|
+
pendingWalCommits: number;
|
|
44
|
+
checkedIntegrityHashes?: number;
|
|
45
|
+
status: "healthy" | "recovery-pending";
|
|
46
|
+
}
|
|
47
|
+
export interface AdminLibraryCompatibility {
|
|
48
|
+
packageName: "@sprengmeister/graphvault";
|
|
49
|
+
installedVersion?: string;
|
|
50
|
+
recommendedVersion: string;
|
|
51
|
+
status: "ok" | "warning";
|
|
52
|
+
warnings: string[];
|
|
53
|
+
}
|
|
54
|
+
export type AdminProductionSafetyStatus = "production-ready" | "warning" | "unsafe";
|
|
55
|
+
export type AdminProductionSafetySeverity = "info" | "warning" | "critical";
|
|
56
|
+
export interface AdminProductionSafetyIssue {
|
|
57
|
+
code: string;
|
|
58
|
+
severity: AdminProductionSafetySeverity;
|
|
59
|
+
message: string;
|
|
60
|
+
recommendation: string;
|
|
61
|
+
}
|
|
62
|
+
export interface AdminProductionSafety {
|
|
63
|
+
status: AdminProductionSafetyStatus;
|
|
64
|
+
score: number;
|
|
65
|
+
summary: string;
|
|
66
|
+
transactionLog: "full" | "off";
|
|
67
|
+
mutationsAllowed: boolean;
|
|
68
|
+
staleLockRecovery: boolean;
|
|
69
|
+
pendingRecovery: boolean;
|
|
70
|
+
hashChain: "present" | "missing";
|
|
71
|
+
issues: AdminProductionSafetyIssue[];
|
|
72
|
+
}
|
|
19
73
|
export interface AdminObjectListItem {
|
|
20
74
|
objectId: string;
|
|
21
75
|
kind: EncodedNode["kind"];
|
|
@@ -70,8 +124,12 @@ export interface AdminSearchResult {
|
|
|
70
124
|
}
|
|
71
125
|
export interface AdminGraph {
|
|
72
126
|
root: EncodedValue;
|
|
127
|
+
depth?: number;
|
|
128
|
+
complete?: boolean;
|
|
129
|
+
rootObjectId?: string;
|
|
73
130
|
nodes: AdminGraphNode[];
|
|
74
131
|
edges: AdminGraphEdge[];
|
|
132
|
+
truncatedReferences?: AdminSubtreeReference[];
|
|
75
133
|
}
|
|
76
134
|
export interface AdminGraphNode {
|
|
77
135
|
objectId: string;
|
|
@@ -83,10 +141,43 @@ export interface AdminGraphEdge {
|
|
|
83
141
|
to: string;
|
|
84
142
|
path: string;
|
|
85
143
|
}
|
|
144
|
+
export interface AdminSubtreeReference {
|
|
145
|
+
fromObjectId: string;
|
|
146
|
+
toObjectId: string;
|
|
147
|
+
path: string;
|
|
148
|
+
depth: number;
|
|
149
|
+
}
|
|
150
|
+
export interface AdminSubtree {
|
|
151
|
+
root: EncodedValue;
|
|
152
|
+
rootObjectId?: string;
|
|
153
|
+
transactionId: number;
|
|
154
|
+
depth: number;
|
|
155
|
+
complete: boolean;
|
|
156
|
+
objectIds: string[];
|
|
157
|
+
nodes: AdminGraphNode[];
|
|
158
|
+
edges: AdminGraphEdge[];
|
|
159
|
+
truncatedReferences: AdminSubtreeReference[];
|
|
160
|
+
envelope: {
|
|
161
|
+
format: "graphvault";
|
|
162
|
+
version: 1;
|
|
163
|
+
createdAt: string;
|
|
164
|
+
root: EncodedValue;
|
|
165
|
+
nodes: Record<string, EncodedNode>;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
86
168
|
export interface AdminMutation {
|
|
87
169
|
objectId: string;
|
|
88
170
|
path: string;
|
|
89
171
|
value: unknown;
|
|
172
|
+
metadata?: AdminTransactionMetadata;
|
|
173
|
+
}
|
|
174
|
+
export interface AdminTransactionMetadata {
|
|
175
|
+
actor?: string;
|
|
176
|
+
reason?: string;
|
|
177
|
+
source?: string;
|
|
178
|
+
traceId?: string;
|
|
179
|
+
tags?: string[];
|
|
180
|
+
attributes?: Record<string, string | number | boolean | null>;
|
|
90
181
|
}
|
|
91
182
|
export interface AdminMutationPreview {
|
|
92
183
|
objectId: string;
|
package/dist/admin-ui.js
CHANGED
|
@@ -45,6 +45,8 @@ export const ADMIN_HTML = `<!doctype html>
|
|
|
45
45
|
.sidebar-card { display: grid; align-content: start; gap: 8px; padding: 10px; border-radius: 8px; background: rgba(255, 255, 255, 0.06); }
|
|
46
46
|
nav .hint { align-self: end; }
|
|
47
47
|
.row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }
|
|
48
|
+
.row-split { grid-template-columns: minmax(0, 1fr) 82px; }
|
|
49
|
+
.mini-labels { color: #9fb1b9; font-size: 12px; padding: 2px 2px 0; }
|
|
48
50
|
.topbar { min-height: 74px; padding: 16px 24px; display: grid; grid-template-columns: 1fr auto auto; gap: 14px; align-items: center; background: linear-gradient(180deg, #ffffff 0%, #fbfdfd 100%); border-bottom: 1px solid var(--line); }
|
|
49
51
|
.topbar h1 { margin: 0; font-family: "Avenir Next", "SF Pro Display", "Aptos Display", "Inter", ui-sans-serif, system-ui, sans-serif; font-size: 24px; letter-spacing: 0; font-weight: 600; }
|
|
50
52
|
.auth { display: none; grid-template-columns: minmax(160px, 280px) auto; gap: 8px; }
|
|
@@ -62,6 +64,7 @@ export const ADMIN_HTML = `<!doctype html>
|
|
|
62
64
|
.mutation summary { cursor: pointer; font-weight: 600; }
|
|
63
65
|
.mutation[open] { display: grid; gap: 8px; }
|
|
64
66
|
.mutation-grid { display: grid; grid-template-columns: 90px 1fr 1fr 1fr; gap: 8px; }
|
|
67
|
+
.audit-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
65
68
|
.gvql-box { display: none; padding: 12px 14px; border-bottom: 1px solid var(--line); background: #f8fafb; gap: 8px; }
|
|
66
69
|
.gvql-box.active { display: grid; }
|
|
67
70
|
.gvql-examples { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
@@ -115,7 +118,7 @@ export const ADMIN_HTML = `<!doctype html>
|
|
|
115
118
|
.field-actions { grid-area: actions; justify-content: flex-start; }
|
|
116
119
|
}
|
|
117
120
|
@media (max-width: 980px) {
|
|
118
|
-
.shell, .kpis, .mutation-grid, .topbar, .fields-head { grid-template-columns: 1fr; }
|
|
121
|
+
.shell, .kpis, .mutation-grid, .audit-grid, .topbar, .fields-head { grid-template-columns: 1fr; }
|
|
119
122
|
.gvql-actions, .gvql-parameter-row { grid-template-columns: 1fr; }
|
|
120
123
|
nav { grid-template-rows: auto; gap: 10px; padding: 12px; }
|
|
121
124
|
.brand { grid-template-columns: 42px 1fr; padding-bottom: 2px; }
|
|
@@ -141,6 +144,7 @@ export const ADMIN_HTML = `<!doctype html>
|
|
|
141
144
|
<button class="nav-btn" data-view="objects" onclick="showObjects()">Objects</button>
|
|
142
145
|
<button class="nav-btn" data-view="graph" onclick="showGraph()">Graph</button>
|
|
143
146
|
<button class="nav-btn" data-view="gvql" onclick="showGvql()">GVQL</button>
|
|
147
|
+
<button class="nav-btn" data-view="operations" onclick="showOperations()">Operations</button>
|
|
144
148
|
<button class="nav-btn" data-view="types" onclick="showTypes()">Type Dictionary</button>
|
|
145
149
|
<button class="nav-btn" data-view="transactions" onclick="showTransactions()">Transactions</button>
|
|
146
150
|
<button class="nav-btn" data-view="journal" onclick="showJournal()">Journal</button>
|
|
@@ -148,6 +152,9 @@ export const ADMIN_HTML = `<!doctype html>
|
|
|
148
152
|
</div>
|
|
149
153
|
<div class="sidebar-card">
|
|
150
154
|
<div class="row"><input id="q" placeholder="Search paths, values, types, IDs" /><button onclick="showSearch()">Search</button></div>
|
|
155
|
+
<div class="row row-split mini-labels"><span>Graph root ID</span><span>Depth</span></div>
|
|
156
|
+
<div class="row row-split"><input id="graphRoot" placeholder="Graph root object ID" /><input id="graphDepth" type="number" min="0" max="12" value="2" /></div>
|
|
157
|
+
<button onclick="showGraph()">Load Graph Slice</button>
|
|
151
158
|
<div class="row"><input id="backupPath" placeholder="Backup destination" /><button onclick="runBackup()">Backup</button></div>
|
|
152
159
|
<button onclick="runMaintenance()">Run Maintenance</button>
|
|
153
160
|
</div>
|
|
@@ -172,6 +179,11 @@ export const ADMIN_HTML = `<!doctype html>
|
|
|
172
179
|
<input id="mvalue" placeholder='New JSON value, e.g. "active"' />
|
|
173
180
|
<input id="confirmToken" placeholder="Confirm token" />
|
|
174
181
|
</div>
|
|
182
|
+
<div class="audit-grid">
|
|
183
|
+
<input id="auditActor" placeholder="Actor, e.g. ops@example.com" />
|
|
184
|
+
<input id="auditReason" placeholder="Reason for change" />
|
|
185
|
+
<input id="auditTraceId" placeholder="Trace ID" />
|
|
186
|
+
</div>
|
|
175
187
|
<div class="actions"><button onclick="preview()">Preview</button><button class="danger" onclick="mutate()">Commit Change</button></div>
|
|
176
188
|
</details>
|
|
177
189
|
<div class="gvql-box" id="gvqlPanel">
|
|
@@ -185,6 +197,8 @@ OFFSET 0</textarea>
|
|
|
185
197
|
<input id="gvqlParams" type="hidden" />
|
|
186
198
|
<div class="gvql-actions">
|
|
187
199
|
<input id="gvqlConfirmToken" placeholder="Confirm token" />
|
|
200
|
+
<input id="gvqlAuditActor" placeholder="Actor" />
|
|
201
|
+
<input id="gvqlAuditReason" placeholder="Reason" />
|
|
188
202
|
<button onclick="runGvql(true)">Run / Preview</button>
|
|
189
203
|
<button class="danger" onclick="runGvql(false)">Commit GVQL</button>
|
|
190
204
|
</div>
|
|
@@ -223,8 +237,9 @@ OFFSET 0</textarea>
|
|
|
223
237
|
hierarchy: ['Object Hierarchy', 'Root-first graph view with references you can follow.'],
|
|
224
238
|
overview: ['Storage Overview', 'Health, object count, latest transaction, and current snapshot.'],
|
|
225
239
|
objects: ['Objects', 'Browse graph records with type, preview, and transaction metadata.'],
|
|
226
|
-
graph: ['Object Graph', '
|
|
240
|
+
graph: ['Object Graph', 'Depth-limited graph slice for large stores and API-style inspection.'],
|
|
227
241
|
gvql: ['GVQL Query', 'Run graph pattern queries and preview batch updates.'],
|
|
242
|
+
operations: ['Operations', 'Storage hardening, WAL state, and recovery readiness.'],
|
|
228
243
|
types: ['Type Dictionary', 'Registered runtime types and schema metadata.'],
|
|
229
244
|
transactions: ['Transactions', 'Newest commits first.'],
|
|
230
245
|
journal: ['Journal', 'Append-only storage activity log.'],
|
|
@@ -308,7 +323,14 @@ OFFSET 0</textarea>
|
|
|
308
323
|
}
|
|
309
324
|
async function refreshKpis() {
|
|
310
325
|
const summary = await requestJson('/api/summary?verify=false');
|
|
311
|
-
|
|
326
|
+
const hardening = summary.hardening || {};
|
|
327
|
+
const ops = summary.operations || {};
|
|
328
|
+
const library = summary.library || {};
|
|
329
|
+
const safety = summary.productionSafety || {};
|
|
330
|
+
const walLabel = (hardening.transactionLog || '-') + (typeof ops.pendingWalCommits === 'number' ? ' / ' + ops.pendingWalCommits + ' pending' : '');
|
|
331
|
+
const libraryLabel = (library.installedVersion || '-') + (library.status === 'warning' ? ' warning' : '');
|
|
332
|
+
const safetyLabel = (safety.status || '-') + (typeof safety.score === 'number' ? ' / ' + safety.score : '');
|
|
333
|
+
kpis.innerHTML = kpi('Objects', summary.objectCount) + kpi('Transaction', summary.transactionId) + kpi('Safety', safetyLabel) + kpi('Library', libraryLabel) + kpi('WAL', walLabel) + kpi('Lock', hardening.writerLock || '-') + kpi('Ops', ops.status || '-') + kpi('Snapshot', summary.currentSnapshot || '-');
|
|
312
334
|
return summary;
|
|
313
335
|
}
|
|
314
336
|
async function showHierarchy() {
|
|
@@ -326,6 +348,7 @@ OFFSET 0</textarea>
|
|
|
326
348
|
show({ root: root.rootObjectId, note: 'Only the visible branch is loaded. Click a row to drill into its children.' });
|
|
327
349
|
}
|
|
328
350
|
async function showObjectWithChildren(id, label, parentPath) {
|
|
351
|
+
document.getElementById('graphRoot').value = id;
|
|
329
352
|
const record = await apiJson('/api/objects/' + encodeURIComponent(id));
|
|
330
353
|
hierarchyPath = parentPath.concat([{ id, label, record }]);
|
|
331
354
|
renderEditableFields(record);
|
|
@@ -345,6 +368,7 @@ OFFSET 0</textarea>
|
|
|
345
368
|
setRows(rows, 'No children');
|
|
346
369
|
}
|
|
347
370
|
async function showObjectInHierarchyPath(id) {
|
|
371
|
+
document.getElementById('graphRoot').value = id;
|
|
348
372
|
setView('hierarchy');
|
|
349
373
|
listTitle.textContent = 'Object hierarchy';
|
|
350
374
|
listHint.textContent = 'Search context';
|
|
@@ -396,7 +420,20 @@ OFFSET 0</textarea>
|
|
|
396
420
|
listHint.textContent = 'Overview';
|
|
397
421
|
const summary = await refreshKpis();
|
|
398
422
|
show(summary);
|
|
399
|
-
|
|
423
|
+
const rows = [];
|
|
424
|
+
if (summary.operations) {
|
|
425
|
+
rows.push({ columns: [summary.operations.status, 'operations', summary.operations.pendingWalCommits + ' pending WAL commits', summary.operations.walCommitFiles + ' WAL commits'], onclick: () => show(summary.operations) });
|
|
426
|
+
}
|
|
427
|
+
if (summary.productionSafety) {
|
|
428
|
+
rows.push({ columns: [summary.productionSafety.status, 'production safety', summary.productionSafety.score + ' score', summary.productionSafety.issues.length + ' issues'], onclick: () => show(summary.productionSafety) });
|
|
429
|
+
}
|
|
430
|
+
if (summary.library) {
|
|
431
|
+
rows.push({ columns: [summary.library.installedVersion || '-', 'library', 'recommended ' + summary.library.recommendedVersion, summary.library.status], onclick: () => show(summary.library) });
|
|
432
|
+
}
|
|
433
|
+
if (summary.latestTransaction) {
|
|
434
|
+
rows.push({ columns: ['#' + summary.latestTransaction.transactionId, summary.latestTransaction.mode, summary.latestTransaction.snapshotFile, summary.latestTransaction.objectIds.length + ' ids'], onclick: () => show(summary.latestTransaction) });
|
|
435
|
+
}
|
|
436
|
+
setRows(rows, 'No transactions yet');
|
|
400
437
|
}
|
|
401
438
|
async function showObjects() {
|
|
402
439
|
setView('objects');
|
|
@@ -427,6 +464,7 @@ OFFSET 0</textarea>
|
|
|
427
464
|
list.appendChild(pager);
|
|
428
465
|
}
|
|
429
466
|
async function showObject(id) {
|
|
467
|
+
document.getElementById('graphRoot').value = id;
|
|
430
468
|
document.getElementById('mid').value = id;
|
|
431
469
|
document.getElementById('detailHint').textContent = 'Object #' + id;
|
|
432
470
|
const record = await requestJson('/api/objects/' + encodeURIComponent(id));
|
|
@@ -565,11 +603,39 @@ OFFSET 0</textarea>
|
|
|
565
603
|
}
|
|
566
604
|
async function showGraph() {
|
|
567
605
|
setView('graph');
|
|
568
|
-
listTitle.textContent = 'Graph
|
|
569
|
-
|
|
570
|
-
const
|
|
606
|
+
listTitle.textContent = 'Graph slice';
|
|
607
|
+
const depth = graphDepth();
|
|
608
|
+
const rootObjectId = document.getElementById('graphRoot').value.trim();
|
|
609
|
+
listHint.textContent = (rootObjectId ? 'Root #' + rootObjectId : 'Store root') + ', depth ' + depth;
|
|
610
|
+
const graph = await requestJson(graphSubtreeUrl(rootObjectId, depth));
|
|
571
611
|
renderGraph(graph);
|
|
572
|
-
|
|
612
|
+
const rows = [
|
|
613
|
+
{
|
|
614
|
+
columns: [graph.complete ? 'complete' : 'partial', 'subtree', graph.objectIds.length + ' objects', graph.truncatedReferences.length + ' boundary refs'],
|
|
615
|
+
onclick: () => show(graph)
|
|
616
|
+
},
|
|
617
|
+
...graph.nodes.map(node => ({
|
|
618
|
+
columns: ['#' + node.objectId, node.type || node.kind, node.objectId === graph.rootObjectId ? 'subtree root' : 'loaded node', 'node'],
|
|
619
|
+
onclick: () => showObject(node.objectId)
|
|
620
|
+
})),
|
|
621
|
+
...graph.edges.map(e => ({
|
|
622
|
+
columns: [e.from + ' -> ' + e.to, 'edge', e.path, 'loaded'],
|
|
623
|
+
onclick: () => showObject(e.to)
|
|
624
|
+
})),
|
|
625
|
+
...(graph.truncatedReferences || []).map(ref => ({
|
|
626
|
+
columns: [ref.fromObjectId + ' -> ' + ref.toObjectId, 'boundary', ref.path, 'depth ' + ref.depth],
|
|
627
|
+
onclick: () => showObjectInHierarchyPath(ref.toObjectId)
|
|
628
|
+
}))
|
|
629
|
+
];
|
|
630
|
+
setRows(rows, 'No graph objects found');
|
|
631
|
+
}
|
|
632
|
+
function graphDepth() {
|
|
633
|
+
const value = Number.parseInt(document.getElementById('graphDepth').value || '2', 10);
|
|
634
|
+
return Number.isInteger(value) && value >= 0 ? Math.min(value, 12) : 2;
|
|
635
|
+
}
|
|
636
|
+
function graphSubtreeUrl(rootObjectId, depth) {
|
|
637
|
+
const query = '?depth=' + encodeURIComponent(String(depth));
|
|
638
|
+
return rootObjectId ? '/api/objects/' + encodeURIComponent(rootObjectId) + '/subtree' + query : '/api/subtree' + query;
|
|
573
639
|
}
|
|
574
640
|
function renderGraph(graph) {
|
|
575
641
|
const width = 900;
|
|
@@ -607,8 +673,8 @@ OFFSET 0</textarea>
|
|
|
607
673
|
circle.setAttribute('cx', pos.x);
|
|
608
674
|
circle.setAttribute('cy', pos.y);
|
|
609
675
|
circle.setAttribute('r', '26');
|
|
610
|
-
circle.setAttribute('fill', node.objectId ===
|
|
611
|
-
circle.setAttribute('stroke', node.objectId ===
|
|
676
|
+
circle.setAttribute('fill', node.objectId === graph.rootObjectId ? '#f3c969' : '#d9f1ef');
|
|
677
|
+
circle.setAttribute('stroke', node.objectId === graph.rootObjectId ? '#ab7622' : '#0f8b8d');
|
|
612
678
|
circle.setAttribute('stroke-width', '2');
|
|
613
679
|
const label = document.createElementNS(ns, 'text');
|
|
614
680
|
label.setAttribute('x', pos.x);
|
|
@@ -624,6 +690,23 @@ OFFSET 0</textarea>
|
|
|
624
690
|
}
|
|
625
691
|
const showTypes = async () => { setView('types'); listTitle.textContent = 'Types'; listHint.textContent = 'Dictionary'; const value = await requestJson('/api/types'); setRows((value && value.entries || []).map(e => ({ columns: [String(e.id || '-'), e.name || 'type', e.handler || e.name || '-', 'type'], onclick: () => show(e) })), 'No type dictionary found'); };
|
|
626
692
|
const showTransactions = async () => { setView('transactions'); listTitle.textContent = 'Transactions'; listHint.textContent = 'Newest first'; const rows = await requestJson('/api/transactions'); setRows(rows.map(t => ({ columns: ['#' + t.transactionId, t.mode, t.snapshotFile, t.objectIds.length + ' ids'], onclick: () => show(t) })), 'No transactions found'); };
|
|
693
|
+
const showOperations = async () => {
|
|
694
|
+
setView('operations');
|
|
695
|
+
listTitle.textContent = 'Storage operations';
|
|
696
|
+
listHint.textContent = 'WAL, hardening, and production safety';
|
|
697
|
+
const summary = await requestJson('/api/summary?verify=false');
|
|
698
|
+
const ops = summary.operations || {};
|
|
699
|
+
const safety = summary.productionSafety || {};
|
|
700
|
+
setRows([
|
|
701
|
+
{ columns: [safety.status || '-', 'production safety', typeof safety.score === 'number' ? safety.score + ' score' : '-', (safety.issues || []).length + ' issues'], onclick: () => show(safety) },
|
|
702
|
+
{ columns: [ops.status, 'health', ops.pendingWalCommits + ' pending WAL commits', ops.latestWalTransactionId ? 'wal tx ' + ops.latestWalTransactionId : 'no wal'], onclick: () => show(ops) },
|
|
703
|
+
{ columns: [ops.transactionLog, 'wal', ops.walPrepareFiles + ' prepares', ops.walCommitFiles + ' commits'], onclick: () => show(ops) },
|
|
704
|
+
{ columns: [String(ops.checkedIntegrityHashes || 0), 'integrity hashes', 'transaction chain', ops.checkedIntegrityHashes ? 'present' : 'not recorded'], onclick: () => show(ops) },
|
|
705
|
+
{ columns: [ops.mutationsAllowed ? 'enabled' : 'disabled', 'mutations', 'lock timeout ' + ops.lockTimeoutMs + 'ms', ops.staleLockTimeoutMs ? 'stale ' + ops.staleLockTimeoutMs + 'ms' : 'no stale recovery'], onclick: () => show(ops) },
|
|
706
|
+
{ columns: ['#' + ops.publishedTransactionId, 'published', ops.latestJournalTransactionId ? 'journal tx ' + ops.latestJournalTransactionId : 'no journal', 'manifest'], onclick: () => show(ops) }
|
|
707
|
+
], 'No operations status');
|
|
708
|
+
return ops;
|
|
709
|
+
};
|
|
627
710
|
const showJournal = async () => {
|
|
628
711
|
setView('journal');
|
|
629
712
|
listTitle.textContent = 'Journal';
|
|
@@ -637,7 +720,8 @@ OFFSET 0</textarea>
|
|
|
637
720
|
listHint.textContent = 'Integrity';
|
|
638
721
|
const result = await requestJson('/api/verify');
|
|
639
722
|
setRows([
|
|
640
|
-
{ columns: [result.ok ? 'OK' : 'FAIL', 'health', result.errors.length ? result.errors.join('; ') : 'No errors', result.checkedObjects + ' objects'], onclick: () => show(result) }
|
|
723
|
+
{ columns: [result.ok ? 'OK' : 'FAIL', 'health', result.errors.length ? result.errors.join('; ') : 'No errors', result.checkedObjects + ' objects'], onclick: () => show(result) },
|
|
724
|
+
{ columns: [String(result.checkedIntegrityHashes || 0), 'integrity hashes', result.checkedWalRecords + ' WAL checks', result.pendingWalCommits + ' pending WAL'], onclick: () => show(result) }
|
|
641
725
|
], 'Verification has not run');
|
|
642
726
|
return result;
|
|
643
727
|
};
|
|
@@ -821,6 +905,7 @@ OFFSET 0</textarea>
|
|
|
821
905
|
dryRun
|
|
822
906
|
};
|
|
823
907
|
if (!dryRun && confirmRequired) payload.confirmToken = document.getElementById('gvqlConfirmToken').value;
|
|
908
|
+
if (!dryRun) payload.metadata = readAuditMetadata('gvql');
|
|
824
909
|
const result = await postJson('/api/gvql', payload);
|
|
825
910
|
const rows = [];
|
|
826
911
|
if (result.plan) {
|
|
@@ -872,8 +957,19 @@ OFFSET 0</textarea>
|
|
|
872
957
|
}
|
|
873
958
|
const confirmRequired = __CONFIRM_REQUIRED__;
|
|
874
959
|
if (!confirmRequired) document.getElementById('confirmToken').style.display = 'none';
|
|
960
|
+
if (!confirmRequired) document.getElementById('gvqlConfirmToken').style.display = 'none';
|
|
961
|
+
function readAuditMetadata(prefix) {
|
|
962
|
+
const actor = document.getElementById(prefix === 'gvql' ? 'gvqlAuditActor' : 'auditActor')?.value.trim();
|
|
963
|
+
const reason = document.getElementById(prefix === 'gvql' ? 'gvqlAuditReason' : 'auditReason')?.value.trim();
|
|
964
|
+
const traceId = prefix === 'gvql' ? '' : document.getElementById('auditTraceId')?.value.trim();
|
|
965
|
+
const metadata = { source: prefix === 'gvql' ? 'graphvault-studio:gvql' : 'graphvault-studio:direct-edit' };
|
|
966
|
+
if (actor) metadata.actor = actor;
|
|
967
|
+
if (reason) metadata.reason = reason;
|
|
968
|
+
if (traceId) metadata.traceId = traceId;
|
|
969
|
+
return metadata;
|
|
970
|
+
}
|
|
875
971
|
function mutationPayload(includeConfirm) {
|
|
876
|
-
const payload = { objectId: document.getElementById('mid').value, path: document.getElementById('mpath').value, value: JSON.parse(document.getElementById('mvalue').value) };
|
|
972
|
+
const payload = { objectId: document.getElementById('mid').value, path: document.getElementById('mpath').value, value: JSON.parse(document.getElementById('mvalue').value), metadata: readAuditMetadata('direct') };
|
|
877
973
|
if (includeConfirm) payload.confirmToken = document.getElementById('confirmToken').value;
|
|
878
974
|
return payload;
|
|
879
975
|
}
|
package/dist/admin-ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-ui.js","sourceRoot":"","sources":["../src/admin-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"admin-ui.js","sourceRoot":"","sources":["../src/admin-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAmOC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgvBvD,CAAC"}
|
package/docs/PUBLISHING.md
CHANGED
|
@@ -18,9 +18,12 @@ npm ci
|
|
|
18
18
|
npm test
|
|
19
19
|
npm run demo:store
|
|
20
20
|
npm run pack:dry-run
|
|
21
|
+
npm run package:smoke
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
`npm test` includes `npm run compat:check`, which verifies that Studio's `@sprengmeister/graphvault` dependency range accepts the runtime version recommended by the current Studio build. If the check warns that the lockfile resolves an older GraphVault Library, publish or install the newer library before a production Studio release.
|
|
25
|
+
|
|
26
|
+
Inspect the dry-run file list and confirm it contains `README.md`, `LICENSE`, `CHANGELOG.md`, `CONTRIBUTING.md`, `docs`, `dist`, `examples`, logo/screenshot assets, and the `graphvault-studio` CLI entry point. `npm run package:smoke` installs the generated tarball into a fresh temporary project, verifies the CLI help output, imports the public programmatic API, creates a real GraphVault store, and exercises search plus bounded subtree loading through the installed package.
|
|
24
27
|
|
|
25
28
|
## Tagging
|
|
26
29
|
|
|
@@ -35,7 +38,7 @@ git push origin v0.1.0
|
|
|
35
38
|
|
|
36
39
|
Use the GitHub Actions `Release` workflow with the matching tag input, for example `v0.1.0`.
|
|
37
40
|
|
|
38
|
-
The workflow checks out the tag, installs with `npm ci`, runs tests, creates the demo store, validates the npm tarball with `npm run pack:dry-run`, and publishes with npm provenance.
|
|
41
|
+
The workflow checks out the tag, installs with `npm ci`, runs tests, creates the demo store, validates the npm tarball with `npm run pack:dry-run`, performs the fresh-install package smoke test, and publishes with npm provenance.
|
|
39
42
|
|
|
40
43
|
## Repository Visibility
|
|
41
44
|
|
|
@@ -8,8 +8,10 @@ GraphVault Studio 0.1.0 is the first public graphical admin client for GraphVaul
|
|
|
8
8
|
- Search across paths, values, ids, types, references, and encoded object data.
|
|
9
9
|
- Parent-path lookup from an object back toward the root, including multiple direct parents.
|
|
10
10
|
- Paged object browser for large stores.
|
|
11
|
+
- Depth-limited subtree API and graph slice view for large stores.
|
|
11
12
|
- GVQL console for graph queries and dry-run batch mutation previews.
|
|
12
13
|
- Controlled primitive-field editing with confirmation-token safety.
|
|
14
|
+
- Visible GraphVault Library compatibility status.
|
|
13
15
|
- Verification, maintenance, backup, transaction, journal, graph, type dictionary, and object detail views.
|
|
14
16
|
- CLI and programmatic server startup.
|
|
15
17
|
- Local filesystem, custom, remote, S3-compatible, HTTP, and SQL-backed storage through GraphVault storage targets.
|
package/docs/REMOTE_STORAGE.md
CHANGED
|
@@ -53,4 +53,22 @@ await startAdminServer({
|
|
|
53
53
|
});
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
For mutation endpoints, always set `allowMutations: true` and a `mutationConfirmToken`. For exposed or shared environments, also set `authToken
|
|
56
|
+
For mutation endpoints, always set `allowMutations: true` and a `mutationConfirmToken`. For exposed or shared environments, also set authentication. The legacy `authToken` option grants admin access. Prefer role tokens when several people or tools use Studio:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
await startAdminServer({
|
|
60
|
+
storageDirectory: "main",
|
|
61
|
+
storageTarget,
|
|
62
|
+
allowMutations: true,
|
|
63
|
+
mutationConfirmToken: process.env.GRAPHVAULT_ADMIN_CONFIRM_TOKEN,
|
|
64
|
+
accessTokens: [
|
|
65
|
+
{ token: process.env.GRAPHVAULT_VIEWER_TOKEN!, role: "viewer" },
|
|
66
|
+
{ token: process.env.GRAPHVAULT_OPERATOR_TOKEN!, role: "operator" },
|
|
67
|
+
{ token: process.env.GRAPHVAULT_ADMIN_ROLE_TOKEN!, role: "admin" },
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `viewer`: read-only inspection, search, verification, transactions, journal, and GVQL dry-runs.
|
|
73
|
+
- `operator`: viewer plus maintenance and backup.
|
|
74
|
+
- `admin`: operator plus committed direct edits and GVQL mutations. Admin writes should still use a confirmation token.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphvault-studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Graphical admin client for GraphVault object graph stores.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,9 +37,11 @@
|
|
|
37
37
|
],
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsc -p tsconfig.json",
|
|
40
|
-
"
|
|
40
|
+
"compat:check": "node scripts/check-compatibility.mjs",
|
|
41
|
+
"test": "npm run compat:check && npm run build && node tests/smoke.mjs",
|
|
41
42
|
"demo:store": "npm run build && node examples/create-demo-store.mjs",
|
|
42
43
|
"pack:dry-run": "npm pack --dry-run --cache ./.npm-cache",
|
|
44
|
+
"package:smoke": "node scripts/package-smoke.mjs",
|
|
43
45
|
"prepack": "npm run build"
|
|
44
46
|
},
|
|
45
47
|
"keywords": [
|
|
@@ -58,7 +60,7 @@
|
|
|
58
60
|
"node": ">=20"
|
|
59
61
|
},
|
|
60
62
|
"dependencies": {
|
|
61
|
-
"@sprengmeister/graphvault": "0.1.0"
|
|
63
|
+
"@sprengmeister/graphvault": ">=0.1.0 <0.3.0"
|
|
62
64
|
},
|
|
63
65
|
"devDependencies": {
|
|
64
66
|
"@types/node": "^22.15.3",
|