graphvault-studio 0.1.2 → 0.1.3

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/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', 'Click nodes or edges to inspect referenced records.'],
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,12 @@ OFFSET 0</textarea>
308
323
  }
309
324
  async function refreshKpis() {
310
325
  const summary = await requestJson('/api/summary?verify=false');
311
- kpis.innerHTML = kpi('Objects', summary.objectCount) + kpi('Transaction', summary.transactionId) + kpi('Verify', summary.verification ? (summary.verification.ok ? 'OK' : 'FAIL') : 'manual') + kpi('Snapshot', summary.currentSnapshot || '-');
326
+ const hardening = summary.hardening || {};
327
+ const ops = summary.operations || {};
328
+ const library = summary.library || {};
329
+ const walLabel = (hardening.transactionLog || '-') + (typeof ops.pendingWalCommits === 'number' ? ' / ' + ops.pendingWalCommits + ' pending' : '');
330
+ const libraryLabel = (library.installedVersion || '-') + (library.status === 'warning' ? ' warning' : '');
331
+ kpis.innerHTML = kpi('Objects', summary.objectCount) + kpi('Transaction', summary.transactionId) + kpi('Library', libraryLabel) + kpi('WAL', walLabel) + kpi('Lock', hardening.writerLock || '-') + kpi('Ops', ops.status || '-') + kpi('Snapshot', summary.currentSnapshot || '-');
312
332
  return summary;
313
333
  }
314
334
  async function showHierarchy() {
@@ -326,6 +346,7 @@ OFFSET 0</textarea>
326
346
  show({ root: root.rootObjectId, note: 'Only the visible branch is loaded. Click a row to drill into its children.' });
327
347
  }
328
348
  async function showObjectWithChildren(id, label, parentPath) {
349
+ document.getElementById('graphRoot').value = id;
329
350
  const record = await apiJson('/api/objects/' + encodeURIComponent(id));
330
351
  hierarchyPath = parentPath.concat([{ id, label, record }]);
331
352
  renderEditableFields(record);
@@ -345,6 +366,7 @@ OFFSET 0</textarea>
345
366
  setRows(rows, 'No children');
346
367
  }
347
368
  async function showObjectInHierarchyPath(id) {
369
+ document.getElementById('graphRoot').value = id;
348
370
  setView('hierarchy');
349
371
  listTitle.textContent = 'Object hierarchy';
350
372
  listHint.textContent = 'Search context';
@@ -396,7 +418,17 @@ OFFSET 0</textarea>
396
418
  listHint.textContent = 'Overview';
397
419
  const summary = await refreshKpis();
398
420
  show(summary);
399
- setRows(summary.latestTransaction ? [{ columns: ['#' + summary.latestTransaction.transactionId, summary.latestTransaction.mode, summary.latestTransaction.snapshotFile, summary.latestTransaction.objectIds.length + ' ids'], onclick: () => show(summary.latestTransaction) }] : [], 'No transactions yet');
421
+ const rows = [];
422
+ if (summary.operations) {
423
+ rows.push({ columns: [summary.operations.status, 'operations', summary.operations.pendingWalCommits + ' pending WAL commits', summary.operations.walCommitFiles + ' WAL commits'], onclick: () => show(summary.operations) });
424
+ }
425
+ if (summary.library) {
426
+ rows.push({ columns: [summary.library.installedVersion || '-', 'library', 'recommended ' + summary.library.recommendedVersion, summary.library.status], onclick: () => show(summary.library) });
427
+ }
428
+ if (summary.latestTransaction) {
429
+ rows.push({ columns: ['#' + summary.latestTransaction.transactionId, summary.latestTransaction.mode, summary.latestTransaction.snapshotFile, summary.latestTransaction.objectIds.length + ' ids'], onclick: () => show(summary.latestTransaction) });
430
+ }
431
+ setRows(rows, 'No transactions yet');
400
432
  }
401
433
  async function showObjects() {
402
434
  setView('objects');
@@ -427,6 +459,7 @@ OFFSET 0</textarea>
427
459
  list.appendChild(pager);
428
460
  }
429
461
  async function showObject(id) {
462
+ document.getElementById('graphRoot').value = id;
430
463
  document.getElementById('mid').value = id;
431
464
  document.getElementById('detailHint').textContent = 'Object #' + id;
432
465
  const record = await requestJson('/api/objects/' + encodeURIComponent(id));
@@ -565,11 +598,39 @@ OFFSET 0</textarea>
565
598
  }
566
599
  async function showGraph() {
567
600
  setView('graph');
568
- listTitle.textContent = 'Graph edges';
569
- listHint.textContent = 'Follow references';
570
- const graph = await requestJson('/api/graph');
601
+ listTitle.textContent = 'Graph slice';
602
+ const depth = graphDepth();
603
+ const rootObjectId = document.getElementById('graphRoot').value.trim();
604
+ listHint.textContent = (rootObjectId ? 'Root #' + rootObjectId : 'Store root') + ', depth ' + depth;
605
+ const graph = await requestJson(graphSubtreeUrl(rootObjectId, depth));
571
606
  renderGraph(graph);
572
- setRows(graph.edges.map(e => ({ columns: [e.from + ' -> ' + e.to, 'edge', e.path, 'ref'], onclick: () => showObject(e.to) })), 'No edges found');
607
+ const rows = [
608
+ {
609
+ columns: [graph.complete ? 'complete' : 'partial', 'subtree', graph.objectIds.length + ' objects', graph.truncatedReferences.length + ' boundary refs'],
610
+ onclick: () => show(graph)
611
+ },
612
+ ...graph.nodes.map(node => ({
613
+ columns: ['#' + node.objectId, node.type || node.kind, node.objectId === graph.rootObjectId ? 'subtree root' : 'loaded node', 'node'],
614
+ onclick: () => showObject(node.objectId)
615
+ })),
616
+ ...graph.edges.map(e => ({
617
+ columns: [e.from + ' -> ' + e.to, 'edge', e.path, 'loaded'],
618
+ onclick: () => showObject(e.to)
619
+ })),
620
+ ...(graph.truncatedReferences || []).map(ref => ({
621
+ columns: [ref.fromObjectId + ' -> ' + ref.toObjectId, 'boundary', ref.path, 'depth ' + ref.depth],
622
+ onclick: () => showObjectInHierarchyPath(ref.toObjectId)
623
+ }))
624
+ ];
625
+ setRows(rows, 'No graph objects found');
626
+ }
627
+ function graphDepth() {
628
+ const value = Number.parseInt(document.getElementById('graphDepth').value || '2', 10);
629
+ return Number.isInteger(value) && value >= 0 ? Math.min(value, 12) : 2;
630
+ }
631
+ function graphSubtreeUrl(rootObjectId, depth) {
632
+ const query = '?depth=' + encodeURIComponent(String(depth));
633
+ return rootObjectId ? '/api/objects/' + encodeURIComponent(rootObjectId) + '/subtree' + query : '/api/subtree' + query;
573
634
  }
574
635
  function renderGraph(graph) {
575
636
  const width = 900;
@@ -607,8 +668,8 @@ OFFSET 0</textarea>
607
668
  circle.setAttribute('cx', pos.x);
608
669
  circle.setAttribute('cy', pos.y);
609
670
  circle.setAttribute('r', '26');
610
- circle.setAttribute('fill', node.objectId === 'root' ? '#f3c969' : '#d9f1ef');
611
- circle.setAttribute('stroke', node.objectId === 'root' ? '#ab7622' : '#0f8b8d');
671
+ circle.setAttribute('fill', node.objectId === graph.rootObjectId ? '#f3c969' : '#d9f1ef');
672
+ circle.setAttribute('stroke', node.objectId === graph.rootObjectId ? '#ab7622' : '#0f8b8d');
612
673
  circle.setAttribute('stroke-width', '2');
613
674
  const label = document.createElementNS(ns, 'text');
614
675
  label.setAttribute('x', pos.x);
@@ -624,6 +685,20 @@ OFFSET 0</textarea>
624
685
  }
625
686
  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
687
  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'); };
688
+ const showOperations = async () => {
689
+ setView('operations');
690
+ listTitle.textContent = 'Storage operations';
691
+ listHint.textContent = 'WAL and hardening';
692
+ const ops = await requestJson('/api/operations');
693
+ setRows([
694
+ { columns: [ops.status, 'health', ops.pendingWalCommits + ' pending WAL commits', ops.latestWalTransactionId ? 'wal tx ' + ops.latestWalTransactionId : 'no wal'], onclick: () => show(ops) },
695
+ { columns: [ops.transactionLog, 'wal', ops.walPrepareFiles + ' prepares', ops.walCommitFiles + ' commits'], onclick: () => show(ops) },
696
+ { columns: [String(ops.checkedIntegrityHashes || 0), 'integrity hashes', 'transaction chain', ops.checkedIntegrityHashes ? 'present' : 'not recorded'], onclick: () => show(ops) },
697
+ { columns: [ops.mutationsAllowed ? 'enabled' : 'disabled', 'mutations', 'lock timeout ' + ops.lockTimeoutMs + 'ms', ops.staleLockTimeoutMs ? 'stale ' + ops.staleLockTimeoutMs + 'ms' : 'no stale recovery'], onclick: () => show(ops) },
698
+ { columns: ['#' + ops.publishedTransactionId, 'published', ops.latestJournalTransactionId ? 'journal tx ' + ops.latestJournalTransactionId : 'no journal', 'manifest'], onclick: () => show(ops) }
699
+ ], 'No operations status');
700
+ return ops;
701
+ };
627
702
  const showJournal = async () => {
628
703
  setView('journal');
629
704
  listTitle.textContent = 'Journal';
@@ -637,7 +712,8 @@ OFFSET 0</textarea>
637
712
  listHint.textContent = 'Integrity';
638
713
  const result = await requestJson('/api/verify');
639
714
  setRows([
640
- { columns: [result.ok ? 'OK' : 'FAIL', 'health', result.errors.length ? result.errors.join('; ') : 'No errors', result.checkedObjects + ' objects'], onclick: () => show(result) }
715
+ { columns: [result.ok ? 'OK' : 'FAIL', 'health', result.errors.length ? result.errors.join('; ') : 'No errors', result.checkedObjects + ' objects'], onclick: () => show(result) },
716
+ { columns: [String(result.checkedIntegrityHashes || 0), 'integrity hashes', result.checkedWalRecords + ' WAL checks', result.pendingWalCommits + ' pending WAL'], onclick: () => show(result) }
641
717
  ], 'Verification has not run');
642
718
  return result;
643
719
  };
@@ -821,6 +897,7 @@ OFFSET 0</textarea>
821
897
  dryRun
822
898
  };
823
899
  if (!dryRun && confirmRequired) payload.confirmToken = document.getElementById('gvqlConfirmToken').value;
900
+ if (!dryRun) payload.metadata = readAuditMetadata('gvql');
824
901
  const result = await postJson('/api/gvql', payload);
825
902
  const rows = [];
826
903
  if (result.plan) {
@@ -872,8 +949,19 @@ OFFSET 0</textarea>
872
949
  }
873
950
  const confirmRequired = __CONFIRM_REQUIRED__;
874
951
  if (!confirmRequired) document.getElementById('confirmToken').style.display = 'none';
952
+ if (!confirmRequired) document.getElementById('gvqlConfirmToken').style.display = 'none';
953
+ function readAuditMetadata(prefix) {
954
+ const actor = document.getElementById(prefix === 'gvql' ? 'gvqlAuditActor' : 'auditActor')?.value.trim();
955
+ const reason = document.getElementById(prefix === 'gvql' ? 'gvqlAuditReason' : 'auditReason')?.value.trim();
956
+ const traceId = prefix === 'gvql' ? '' : document.getElementById('auditTraceId')?.value.trim();
957
+ const metadata = { source: prefix === 'gvql' ? 'graphvault-studio:gvql' : 'graphvault-studio:direct-edit' };
958
+ if (actor) metadata.actor = actor;
959
+ if (reason) metadata.reason = reason;
960
+ if (traceId) metadata.traceId = traceId;
961
+ return metadata;
962
+ }
875
963
  function mutationPayload(includeConfirm) {
876
- const payload = { objectId: document.getElementById('mid').value, path: document.getElementById('mpath').value, value: JSON.parse(document.getElementById('mvalue').value) };
964
+ const payload = { objectId: document.getElementById('mid').value, path: document.getElementById('mpath').value, value: JSON.parse(document.getElementById('mvalue').value), metadata: readAuditMetadata('direct') };
877
965
  if (includeConfirm) payload.confirmToken = document.getElementById('confirmToken').value;
878
966
  return payload;
879
967
  }
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAqNC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8pBvD,CAAC"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwuBvD,CAAC"}
@@ -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
- 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.
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.
@@ -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.2",
3
+ "version": "0.1.3",
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
- "test": "npm run build && node tests/smoke.mjs",
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",