@reddb-io/ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -0
- package/bin/red-ui.js +259 -0
- package/build/CNAME +1 -0
- package/build/_app/env.js +1 -0
- package/build/_app/immutable/assets/0.C63jY5jX.css +1 -0
- package/build/_app/immutable/assets/10.Dblyk0Kr.css +1 -0
- package/build/_app/immutable/assets/EmptyState.RXcH7ryE.css +1 -0
- package/build/_app/immutable/assets/Workspace.BCzG1DsH.css +1 -0
- package/build/_app/immutable/assets/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2 +0 -0
- package/build/_app/immutable/assets/inter-cyrillic-wght-normal.DqGufNeO.woff2 +0 -0
- package/build/_app/immutable/assets/inter-greek-ext-wght-normal.DlzME5K_.woff2 +0 -0
- package/build/_app/immutable/assets/inter-greek-wght-normal.CkhJZR-_.woff2 +0 -0
- package/build/_app/immutable/assets/inter-latin-ext-wght-normal.DO1Apj_S.woff2 +0 -0
- package/build/_app/immutable/assets/inter-latin-wght-normal.Dx4kXJAl.woff2 +0 -0
- package/build/_app/immutable/assets/inter-vietnamese-wght-normal.CBcvBZtf.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2 +0 -0
- package/build/_app/immutable/assets/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2 +0 -0
- package/build/_app/immutable/chunks/3j4cZ58b.js +1 -0
- package/build/_app/immutable/chunks/69_IOA4Y.js +1 -0
- package/build/_app/immutable/chunks/ALqaDrNQ.js +2 -0
- package/build/_app/immutable/chunks/BGcIe2ep.js +2 -0
- package/build/_app/immutable/chunks/BMHKbvs8.js +1 -0
- package/build/_app/immutable/chunks/BQHbwf54.js +916 -0
- package/build/_app/immutable/chunks/Bgso8cDH.js +311 -0
- package/build/_app/immutable/chunks/Bmgx1mfv.js +310 -0
- package/build/_app/immutable/chunks/BrvvOVaJ.js +1 -0
- package/build/_app/immutable/chunks/Bzak7iHL.js +1 -0
- package/build/_app/immutable/chunks/D9CTkMjK.js +1 -0
- package/build/_app/immutable/chunks/DIeogL5L.js +1 -0
- package/build/_app/immutable/chunks/DXY_Rxa7.js +113 -0
- package/build/_app/immutable/chunks/DhEqZVGG.js +1 -0
- package/build/_app/immutable/chunks/Ov8N-vxt.js +1 -0
- package/build/_app/immutable/chunks/oiO4RLqp.js +1 -0
- package/build/_app/immutable/entry/app.D_IVtpdv.js +2 -0
- package/build/_app/immutable/entry/start.3oC0UQ_Q.js +1 -0
- package/build/_app/immutable/nodes/0.CHPiFxDo.js +1 -0
- package/build/_app/immutable/nodes/1.BC_4YZ0h.js +1 -0
- package/build/_app/immutable/nodes/10.B7DMKSNc.js +60 -0
- package/build/_app/immutable/nodes/2.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/3.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/4.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/5.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/6.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/7.BZX9Iird.js +134 -0
- package/build/_app/immutable/nodes/8.CAG5_cGP.js +1 -0
- package/build/_app/immutable/nodes/9.CAG5_cGP.js +1 -0
- package/build/_app/version.json +1 -0
- package/build/favicon.png +0 -0
- package/build/index.html +59 -0
- package/dist/embed/RedUiEmbedRoot-BGoLxJf1.js +26562 -0
- package/dist/embed/RedUiEmbedRoot-BGoLxJf1.js.map +1 -0
- package/dist/embed/bundle-mjs-UuVRz3O4.js +3047 -0
- package/dist/embed/bundle-mjs-UuVRz3O4.js.map +1 -0
- package/dist/embed/core-QuRYvMt8.js +118 -0
- package/dist/embed/core-QuRYvMt8.js.map +1 -0
- package/dist/embed/index-B6ZQhCsu.js +577 -0
- package/dist/embed/index-B6ZQhCsu.js.map +1 -0
- package/dist/embed/index-client-ItCEU64C.js +3087 -0
- package/dist/embed/index-client-ItCEU64C.js.map +1 -0
- package/dist/embed/index-fad77a13.esm-BqPj0saH.js +1318 -0
- package/dist/embed/index-fad77a13.esm-BqPj0saH.js.map +1 -0
- package/dist/embed/index.js +11 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/embed/sigma-node-border.esm-CGtlgqmo.js +432 -0
- package/dist/embed/sigma-node-border.esm-CGtlgqmo.js.map +1 -0
- package/dist/embed/sigma.esm-y0qgY-OC.js +2355 -0
- package/dist/embed/sigma.esm-y0qgY-OC.js.map +1 -0
- package/dist/embed/ui.css +1 -0
- package/package.json +101 -0
- package/src/app.css +238 -0
- package/src/lib/ActivityIndicator.svelte +100 -0
- package/src/lib/ClusterNode.svelte +119 -0
- package/src/lib/ClusterView.svelte +541 -0
- package/src/lib/CollectionHistory.svelte +266 -0
- package/src/lib/CollectionsView.svelte +14 -0
- package/src/lib/CommandPalette.svelte +237 -0
- package/src/lib/ConnectDropdown.svelte +222 -0
- package/src/lib/EmptyState.svelte +27 -0
- package/src/lib/LiveChanges.svelte +195 -0
- package/src/lib/MasterPasswordDialog.svelte +168 -0
- package/src/lib/PageHeader.svelte +32 -0
- package/src/lib/PendingChangesPanel.svelte +153 -0
- package/src/lib/QueryEditor.svelte +122 -0
- package/src/lib/ResultsPane.svelte +747 -0
- package/src/lib/SchemaTree.svelte +193 -0
- package/src/lib/SecurityView.svelte +255 -0
- package/src/lib/ShortcutOverlay.svelte +104 -0
- package/src/lib/Splash.svelte +46 -0
- package/src/lib/StatusBar.svelte +163 -0
- package/src/lib/SubNav.svelte +62 -0
- package/src/lib/Topbar.svelte +119 -0
- package/src/lib/Workspace.svelte +247 -0
- package/src/lib/activity.svelte.ts +91 -0
- package/src/lib/build-info.ts +41 -0
- package/src/lib/capability.ts +221 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/collection-catalog.ts +116 -0
- package/src/lib/collection-pages.ts +33 -0
- package/src/lib/connection-history.ts +301 -0
- package/src/lib/connections.svelte.ts +493 -0
- package/src/lib/embed/RedUiEmbedRoot.svelte +31 -0
- package/src/lib/embed/index.ts +234 -0
- package/src/lib/graph-loader/contract.ts +336 -0
- package/src/lib/graph-loader/drilldown.ts +334 -0
- package/src/lib/graph-loader/index.ts +13 -0
- package/src/lib/index.ts +114 -0
- package/src/lib/pending-changes.svelte.ts +149 -0
- package/src/lib/query-tabs.svelte.ts +130 -0
- package/src/lib/reddb/boot-params.ts +254 -0
- package/src/lib/reddb/cdc-stream-client.ts +234 -0
- package/src/lib/reddb/client.ts +698 -0
- package/src/lib/reddb/connection-bootstrap.ts +260 -0
- package/src/lib/reddb/connection-provider.contract.ts +72 -0
- package/src/lib/reddb/connection-provider.ts +72 -0
- package/src/lib/reddb/index.ts +14 -0
- package/src/lib/reddb/injected-client-provider.ts +76 -0
- package/src/lib/reddb/local-url-provider.ts +245 -0
- package/src/lib/reddb/secure-store.ts +41 -0
- package/src/lib/reddb/server-capabilities.ts +71 -0
- package/src/lib/reddb/tauri-encrypted-store.ts +64 -0
- package/src/lib/reddb/transports.ts +89 -0
- package/src/lib/reddb/types.ts +32 -0
- package/src/lib/reddb/uri.ts +54 -0
- package/src/lib/reddb/web-encrypted-store.ts +201 -0
- package/src/lib/renderers/DiffRenderer.svelte +184 -0
- package/src/lib/renderers/DocumentRenderer.svelte +134 -0
- package/src/lib/renderers/GraphRenderer.svelte +1328 -0
- package/src/lib/renderers/HypertableRenderer.svelte +253 -0
- package/src/lib/renderers/JsonRenderer.svelte +30 -0
- package/src/lib/renderers/KvRenderer.svelte +150 -0
- package/src/lib/renderers/QueueRenderer.svelte +286 -0
- package/src/lib/renderers/StatsRenderer.svelte +123 -0
- package/src/lib/renderers/TableRenderer.svelte +440 -0
- package/src/lib/renderers/VectorRenderer.svelte +388 -0
- package/src/lib/renderers/diff-render.ts +122 -0
- package/src/lib/renderers/diff.ts +9 -0
- package/src/lib/renderers/document-render.ts +64 -0
- package/src/lib/renderers/document.ts +9 -0
- package/src/lib/renderers/graph-render.ts +631 -0
- package/src/lib/renderers/graph-sigma.ts +395 -0
- package/src/lib/renderers/graph.ts +9 -0
- package/src/lib/renderers/hypertable-render.ts +320 -0
- package/src/lib/renderers/hypertable.ts +9 -0
- package/src/lib/renderers/index.ts +42 -0
- package/src/lib/renderers/json-render.ts +89 -0
- package/src/lib/renderers/json.ts +10 -0
- package/src/lib/renderers/kv-render.ts +193 -0
- package/src/lib/renderers/kv.ts +9 -0
- package/src/lib/renderers/queue-render.ts +197 -0
- package/src/lib/renderers/queue.ts +9 -0
- package/src/lib/renderers/registry.ts +68 -0
- package/src/lib/renderers/stats-render.ts +73 -0
- package/src/lib/renderers/stats.ts +9 -0
- package/src/lib/renderers/table-render.ts +156 -0
- package/src/lib/renderers/table.ts +11 -0
- package/src/lib/renderers/types.ts +33 -0
- package/src/lib/renderers/vector-render.ts +154 -0
- package/src/lib/renderers/vector.ts +9 -0
- package/src/lib/router.svelte.ts +163 -0
- package/src/lib/secureStore.svelte.ts +148 -0
- package/src/lib/shadow-dom-spike/ShadowDomSpikeCore.svelte +198 -0
- package/src/lib/shadow-dom-spike/mount-shadow-dom-spike.ts +94 -0
- package/src/lib/shortcuts.ts +22 -0
- package/src/lib/surface.ts +80 -0
- package/src/lib/tabs.svelte.ts +115 -0
- package/src/lib/theme.svelte.ts +63 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import type { QueryResult } from "#reddb";
|
|
2
|
+
import type { LoadedGraph, LoadedGraphEdge, LoadedGraphNode } from "./contract";
|
|
3
|
+
|
|
4
|
+
type GraphModel = LoadedGraph["model"];
|
|
5
|
+
|
|
6
|
+
export interface DrilldownNodeSummary {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
type: string;
|
|
10
|
+
degree: number;
|
|
11
|
+
incoming: number;
|
|
12
|
+
outgoing: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DrilldownComponent {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
nodeCount: number;
|
|
19
|
+
connectionCount: number;
|
|
20
|
+
internalConnectionCount: number;
|
|
21
|
+
externalConnectionCount: number;
|
|
22
|
+
nodeIds: string[];
|
|
23
|
+
nodes: DrilldownNodeSummary[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DrilldownLayer {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
color: string;
|
|
30
|
+
nodeCount: number;
|
|
31
|
+
componentCount: number;
|
|
32
|
+
connectionCount: number;
|
|
33
|
+
internalConnectionCount: number;
|
|
34
|
+
externalConnectionCount: number;
|
|
35
|
+
nodeIds: string[];
|
|
36
|
+
components: DrilldownComponent[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GraphDrilldown {
|
|
40
|
+
layers: DrilldownLayer[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const LAYER_FIELDS = [
|
|
44
|
+
"layer",
|
|
45
|
+
"layer_name",
|
|
46
|
+
"layerName",
|
|
47
|
+
"layer_id",
|
|
48
|
+
"layerId",
|
|
49
|
+
"domain",
|
|
50
|
+
"namespace",
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const COMMUNITY_FIELDS = [
|
|
54
|
+
"community",
|
|
55
|
+
"community_name",
|
|
56
|
+
"communityName",
|
|
57
|
+
"community_id",
|
|
58
|
+
"communityId",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const COMPONENT_FIELDS = [
|
|
62
|
+
"component",
|
|
63
|
+
"component_name",
|
|
64
|
+
"componentName",
|
|
65
|
+
"component_id",
|
|
66
|
+
"componentId",
|
|
67
|
+
"package",
|
|
68
|
+
"module",
|
|
69
|
+
"service",
|
|
70
|
+
"collection",
|
|
71
|
+
"file",
|
|
72
|
+
"path",
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const FALLBACK_LAYER = {
|
|
76
|
+
id: "unassigned",
|
|
77
|
+
label: "Unassigned",
|
|
78
|
+
color: "#94a3b8",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const SAFE_COLOR_PATTERN =
|
|
82
|
+
/^(#[0-9a-f]{3,8}|rgba?\([\d\s.,%+-]+\)|hsla?\([\d\s.,%+-]+\)|oklch\([\d\s.%+-]+(?:\/\s*[\d.]+%?)?\))$/i;
|
|
83
|
+
|
|
84
|
+
function safeColor(value: string): string {
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
return SAFE_COLOR_PATTERN.test(trimmed) ? trimmed : FALLBACK_LAYER.color;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function firstPresentString(
|
|
90
|
+
data: Record<string, unknown>,
|
|
91
|
+
fields: string[]
|
|
92
|
+
): string | null {
|
|
93
|
+
for (const field of fields) {
|
|
94
|
+
const value = data[field];
|
|
95
|
+
if (value === null || value === undefined || value === "") continue;
|
|
96
|
+
if (typeof value === "string" || typeof value === "number")
|
|
97
|
+
return String(value);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function stableId(prefix: string, value: string): string {
|
|
103
|
+
return `${prefix}:${value.toLowerCase().replace(/[^a-z0-9._:/-]+/g, "-")}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function communityMap(
|
|
107
|
+
model: GraphModel
|
|
108
|
+
): Map<number, { label: string; color: string }> {
|
|
109
|
+
return new Map(
|
|
110
|
+
model.communities.map((community) => [
|
|
111
|
+
community.id,
|
|
112
|
+
{ label: community.label, color: safeColor(community.color) },
|
|
113
|
+
])
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function layerIdentity(
|
|
118
|
+
node: LoadedGraphNode,
|
|
119
|
+
communities: Map<number, { label: string; color: string }>
|
|
120
|
+
): { id: string; label: string; color: string } {
|
|
121
|
+
const explicitLayer = firstPresentString(node.data, LAYER_FIELDS);
|
|
122
|
+
if (explicitLayer) {
|
|
123
|
+
return {
|
|
124
|
+
id: stableId("layer", explicitLayer),
|
|
125
|
+
label: explicitLayer,
|
|
126
|
+
color: "#94a3b8",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof node.community === "number") {
|
|
131
|
+
const community = communities.get(node.community);
|
|
132
|
+
return {
|
|
133
|
+
id: `community:${node.community}`,
|
|
134
|
+
label: community?.label ?? `Community ${node.community}`,
|
|
135
|
+
color: community?.color ?? "#94a3b8",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const explicitCommunity = firstPresentString(node.data, COMMUNITY_FIELDS);
|
|
140
|
+
if (explicitCommunity) {
|
|
141
|
+
return {
|
|
142
|
+
id: stableId("community", explicitCommunity),
|
|
143
|
+
label: explicitCommunity,
|
|
144
|
+
color: "#94a3b8",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return FALLBACK_LAYER;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function componentIdentity(node: LoadedGraphNode): {
|
|
152
|
+
id: string;
|
|
153
|
+
label: string;
|
|
154
|
+
} {
|
|
155
|
+
const explicit = firstPresentString(node.data, COMPONENT_FIELDS);
|
|
156
|
+
if (explicit) return { id: stableId("component", explicit), label: explicit };
|
|
157
|
+
|
|
158
|
+
const type =
|
|
159
|
+
node.type ?? firstPresentString(node.data, ["node_type", "kind"]);
|
|
160
|
+
if (type) return { id: stableId("type", type), label: `${type} nodes` };
|
|
161
|
+
|
|
162
|
+
return { id: "component:unassigned", label: "Unassigned" };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function connectionStats(ids: Set<string>, edges: LoadedGraphEdge[]) {
|
|
166
|
+
let connectionCount = 0;
|
|
167
|
+
let internalConnectionCount = 0;
|
|
168
|
+
let externalConnectionCount = 0;
|
|
169
|
+
|
|
170
|
+
for (const edge of edges) {
|
|
171
|
+
const hasSource = ids.has(edge.source);
|
|
172
|
+
const hasTarget = ids.has(edge.target);
|
|
173
|
+
if (!hasSource && !hasTarget) continue;
|
|
174
|
+
|
|
175
|
+
connectionCount += 1;
|
|
176
|
+
if (hasSource && hasTarget) internalConnectionCount += 1;
|
|
177
|
+
else externalConnectionCount += 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { connectionCount, internalConnectionCount, externalConnectionCount };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function nodeSummary(
|
|
184
|
+
node: LoadedGraphNode,
|
|
185
|
+
edges: LoadedGraphEdge[]
|
|
186
|
+
): DrilldownNodeSummary {
|
|
187
|
+
let incoming = 0;
|
|
188
|
+
let outgoing = 0;
|
|
189
|
+
for (const edge of edges) {
|
|
190
|
+
if (edge.source === node.id) outgoing += 1;
|
|
191
|
+
if (edge.target === node.id) incoming += 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
id: node.id,
|
|
196
|
+
label: node.label,
|
|
197
|
+
type:
|
|
198
|
+
node.type ??
|
|
199
|
+
firstPresentString(node.data, ["node_type", "kind"]) ??
|
|
200
|
+
"node",
|
|
201
|
+
degree: incoming + outgoing,
|
|
202
|
+
incoming,
|
|
203
|
+
outgoing,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function byLargestThenLabel<
|
|
208
|
+
T extends {
|
|
209
|
+
nodeCount: number;
|
|
210
|
+
connectionCount: number;
|
|
211
|
+
label: string;
|
|
212
|
+
id: string;
|
|
213
|
+
},
|
|
214
|
+
>(a: T, b: T): number {
|
|
215
|
+
const nodes = b.nodeCount - a.nodeCount;
|
|
216
|
+
if (nodes !== 0) return nodes;
|
|
217
|
+
const connections = b.connectionCount - a.connectionCount;
|
|
218
|
+
if (connections !== 0) return connections;
|
|
219
|
+
const label = a.label.localeCompare(b.label);
|
|
220
|
+
if (label !== 0) return label;
|
|
221
|
+
return a.id.localeCompare(b.id);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function buildGraphDrilldown(model: GraphModel): GraphDrilldown {
|
|
225
|
+
const communities = communityMap(model);
|
|
226
|
+
const layerBuckets = new Map<
|
|
227
|
+
string,
|
|
228
|
+
{ id: string; label: string; color: string; nodes: LoadedGraphNode[] }
|
|
229
|
+
>();
|
|
230
|
+
|
|
231
|
+
for (const node of model.nodes) {
|
|
232
|
+
const layer = layerIdentity(node, communities);
|
|
233
|
+
const bucket = layerBuckets.get(layer.id);
|
|
234
|
+
if (bucket) bucket.nodes.push(node);
|
|
235
|
+
else layerBuckets.set(layer.id, { ...layer, nodes: [node] });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const layers = [...layerBuckets.values()].map((layer): DrilldownLayer => {
|
|
239
|
+
const nodeIds = layer.nodes.map((node) => node.id);
|
|
240
|
+
const idSet = new Set(nodeIds);
|
|
241
|
+
const stats = connectionStats(idSet, model.edges);
|
|
242
|
+
const componentBuckets = new Map<
|
|
243
|
+
string,
|
|
244
|
+
{ id: string; label: string; nodes: LoadedGraphNode[] }
|
|
245
|
+
>();
|
|
246
|
+
|
|
247
|
+
for (const node of layer.nodes) {
|
|
248
|
+
const component = componentIdentity(node);
|
|
249
|
+
const bucket = componentBuckets.get(component.id);
|
|
250
|
+
if (bucket) bucket.nodes.push(node);
|
|
251
|
+
else componentBuckets.set(component.id, { ...component, nodes: [node] });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const components = [...componentBuckets.values()]
|
|
255
|
+
.map((component): DrilldownComponent => {
|
|
256
|
+
const componentNodeIds = component.nodes.map((node) => node.id);
|
|
257
|
+
const componentStats = connectionStats(
|
|
258
|
+
new Set(componentNodeIds),
|
|
259
|
+
model.edges
|
|
260
|
+
);
|
|
261
|
+
return {
|
|
262
|
+
id: component.id,
|
|
263
|
+
label: component.label,
|
|
264
|
+
nodeCount: component.nodes.length,
|
|
265
|
+
nodeIds: componentNodeIds,
|
|
266
|
+
nodes: component.nodes
|
|
267
|
+
.map((node) => nodeSummary(node, model.edges))
|
|
268
|
+
.sort(
|
|
269
|
+
(a, b) => b.degree - a.degree || a.label.localeCompare(b.label)
|
|
270
|
+
),
|
|
271
|
+
...componentStats,
|
|
272
|
+
};
|
|
273
|
+
})
|
|
274
|
+
.sort(byLargestThenLabel);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
id: layer.id,
|
|
278
|
+
label: layer.label,
|
|
279
|
+
color: layer.color,
|
|
280
|
+
nodeCount: layer.nodes.length,
|
|
281
|
+
componentCount: components.length,
|
|
282
|
+
nodeIds,
|
|
283
|
+
components,
|
|
284
|
+
...stats,
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return { layers: layers.sort(byLargestThenLabel) };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function graphResultForNodeIds(
|
|
292
|
+
model: GraphModel,
|
|
293
|
+
nodeIds: Iterable<string> | null
|
|
294
|
+
): QueryResult {
|
|
295
|
+
const selectedIds = nodeIds === null ? null : new Set(nodeIds);
|
|
296
|
+
const nodes = selectedIds
|
|
297
|
+
? model.nodes.filter((node) => selectedIds.has(node.id))
|
|
298
|
+
: model.nodes;
|
|
299
|
+
const visibleIds = new Set(nodes.map((node) => node.id));
|
|
300
|
+
const edges = model.edges.filter(
|
|
301
|
+
(edge) => visibleIds.has(edge.source) && visibleIds.has(edge.target)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const nodeMap: Record<string, Record<string, unknown>> = {};
|
|
305
|
+
for (const node of nodes) {
|
|
306
|
+
nodeMap[node.id] = {
|
|
307
|
+
...node.data,
|
|
308
|
+
id: node.id,
|
|
309
|
+
label: node.label,
|
|
310
|
+
...(node.type !== undefined ? { node_type: node.type } : {}),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const edgeMap: Record<string, Record<string, unknown>> = {};
|
|
315
|
+
for (const edge of edges) {
|
|
316
|
+
edgeMap[edge.id] = {
|
|
317
|
+
...edge.data,
|
|
318
|
+
source: edge.source,
|
|
319
|
+
target: edge.target,
|
|
320
|
+
...(edge.type !== undefined ? { type: edge.type } : {}),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
query: "",
|
|
327
|
+
capability: "graph",
|
|
328
|
+
record_count: nodes.length + edges.length,
|
|
329
|
+
result: {
|
|
330
|
+
columns: ["nodes", "edges"],
|
|
331
|
+
records: [{ values: {}, nodes: nodeMap, edges: edgeMap }],
|
|
332
|
+
},
|
|
333
|
+
} as QueryResult;
|
|
334
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
parseGraphExport,
|
|
3
|
+
parseGraphExportJson,
|
|
4
|
+
loadGraphExport,
|
|
5
|
+
GraphContractError,
|
|
6
|
+
SUPPORTED_GRAPH_VERSION_MAJOR,
|
|
7
|
+
} from './contract'
|
|
8
|
+
export type {
|
|
9
|
+
LoadedGraph,
|
|
10
|
+
LoadedGraphNode,
|
|
11
|
+
LoadedGraphEdge,
|
|
12
|
+
LoadedGraphCommunity,
|
|
13
|
+
} from './contract'
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export { RED_UI_VERSION, readBuildInfo, type BuildInfo } from "./build-info";
|
|
2
|
+
export { default as ActivityIndicator } from "./ActivityIndicator.svelte";
|
|
3
|
+
export { default as ClusterNode } from "./ClusterNode.svelte";
|
|
4
|
+
export { default as ClusterView } from "./ClusterView.svelte";
|
|
5
|
+
export { default as CollectionsView } from "./CollectionsView.svelte";
|
|
6
|
+
export { default as CollectionHistory } from "./CollectionHistory.svelte";
|
|
7
|
+
export { default as CommandPalette } from "./CommandPalette.svelte";
|
|
8
|
+
export { default as ConnectDropdown } from "./ConnectDropdown.svelte";
|
|
9
|
+
export { default as EmptyState } from "./EmptyState.svelte";
|
|
10
|
+
export { default as LiveChanges } from "./LiveChanges.svelte";
|
|
11
|
+
export { default as MasterPasswordDialog } from "./MasterPasswordDialog.svelte";
|
|
12
|
+
export { default as PageHeader } from "./PageHeader.svelte";
|
|
13
|
+
export { default as PendingChangesPanel } from "./PendingChangesPanel.svelte";
|
|
14
|
+
export { default as QueryEditor } from "./QueryEditor.svelte";
|
|
15
|
+
export { default as ResultsPane } from "./ResultsPane.svelte";
|
|
16
|
+
export { default as SchemaTree } from "./SchemaTree.svelte";
|
|
17
|
+
export { default as SecurityView } from "./SecurityView.svelte";
|
|
18
|
+
export { default as ShortcutOverlay } from "./ShortcutOverlay.svelte";
|
|
19
|
+
export { default as Splash } from "./Splash.svelte";
|
|
20
|
+
export { default as StatusBar } from "./StatusBar.svelte";
|
|
21
|
+
export { default as SubNav } from "./SubNav.svelte";
|
|
22
|
+
export { default as Topbar } from "./Topbar.svelte";
|
|
23
|
+
export { default as Workspace } from "./Workspace.svelte";
|
|
24
|
+
|
|
25
|
+
export * from "./renderers";
|
|
26
|
+
export {
|
|
27
|
+
capabilityFromCatalogModel,
|
|
28
|
+
detectCapability,
|
|
29
|
+
pickCapability,
|
|
30
|
+
tagsFromCollectionMetadata,
|
|
31
|
+
} from "./capability";
|
|
32
|
+
export type { Capability as CollectionCapability } from "./capability";
|
|
33
|
+
export * from "./collection-catalog";
|
|
34
|
+
export * from "./collection-pages";
|
|
35
|
+
export * from "./router.svelte";
|
|
36
|
+
export * from "./cn";
|
|
37
|
+
export * from "./connections.svelte";
|
|
38
|
+
export * from "./shortcuts";
|
|
39
|
+
export * from "./tabs.svelte";
|
|
40
|
+
export * from "./query-tabs.svelte";
|
|
41
|
+
export * from "@reddb-io/ui-kit";
|
|
42
|
+
export {
|
|
43
|
+
BROWSER_TRANSPORTS,
|
|
44
|
+
CDCStreamClient,
|
|
45
|
+
DESKTOP_TRANSPORTS,
|
|
46
|
+
DEFAULT_PERSISTED_CONNECTION_KEY,
|
|
47
|
+
EMPTY_SERVER_CAPABILITIES,
|
|
48
|
+
InjectedClientProvider,
|
|
49
|
+
LocalUrlProvider,
|
|
50
|
+
NotConnectedError,
|
|
51
|
+
OpenContractError,
|
|
52
|
+
capabilitiesFromSignal,
|
|
53
|
+
encodeOpenContract,
|
|
54
|
+
hasBootEndpoint,
|
|
55
|
+
hasBootRoute,
|
|
56
|
+
isUrlReachable,
|
|
57
|
+
parseOpenContract,
|
|
58
|
+
parseBootParams,
|
|
59
|
+
readInjectedGlobalBootstrap,
|
|
60
|
+
readPersistedLocalBootstrap,
|
|
61
|
+
readTauriIpcBootstrap,
|
|
62
|
+
readUrlOpenContractBootstrap,
|
|
63
|
+
resolveConnectionBootstrap,
|
|
64
|
+
transportForUrl,
|
|
65
|
+
RedClient,
|
|
66
|
+
SecureStoreError,
|
|
67
|
+
TauriEncryptedStore,
|
|
68
|
+
UnknownConnectionError,
|
|
69
|
+
UnreachableConnectionError,
|
|
70
|
+
WebEncryptedStore,
|
|
71
|
+
localStorageHistory,
|
|
72
|
+
parseRedUri,
|
|
73
|
+
} from "#reddb";
|
|
74
|
+
export type {
|
|
75
|
+
ActiveConnection,
|
|
76
|
+
AuthPolicy,
|
|
77
|
+
AuthTenant,
|
|
78
|
+
AuthUser,
|
|
79
|
+
BootParams,
|
|
80
|
+
CDCStreamClientOptions,
|
|
81
|
+
ChangeEvent,
|
|
82
|
+
ClusterStatus,
|
|
83
|
+
CollectionMetadata,
|
|
84
|
+
Connection,
|
|
85
|
+
ConnectionBootstrap,
|
|
86
|
+
ConnectionBootstrapPayload,
|
|
87
|
+
ConnectionBootstrapResolverOptions,
|
|
88
|
+
ConnectionProvider,
|
|
89
|
+
ConnectionRole,
|
|
90
|
+
ConnectionTarget,
|
|
91
|
+
EncryptedStore,
|
|
92
|
+
EventSourceLike,
|
|
93
|
+
Identity,
|
|
94
|
+
InjectedClientOptions,
|
|
95
|
+
NodeRole,
|
|
96
|
+
NodeStats,
|
|
97
|
+
OpenContract,
|
|
98
|
+
OpenContractErrorCode,
|
|
99
|
+
QueryResult,
|
|
100
|
+
QueryRow,
|
|
101
|
+
RawCapabilities,
|
|
102
|
+
RedClientOptions,
|
|
103
|
+
ReplicationStatus,
|
|
104
|
+
ServerCapabilities,
|
|
105
|
+
SseFactory,
|
|
106
|
+
Stats,
|
|
107
|
+
SubscribeOpts,
|
|
108
|
+
Subscription,
|
|
109
|
+
Topology,
|
|
110
|
+
Transport,
|
|
111
|
+
VcsCommit,
|
|
112
|
+
VcsDiff,
|
|
113
|
+
VcsEnvelope,
|
|
114
|
+
} from "#reddb";
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// PendingChangesPanel — TablePlus-style batch staging for cell edits.
|
|
2
|
+
//
|
|
3
|
+
// Deep module: staging, preview SQL, and transactional commit live here.
|
|
4
|
+
// The Svelte component layer reads `changes`/`count` reactively and calls
|
|
5
|
+
// `stage`/`unstage`/`commitAll`/`discardAll`. SQL generation is naive on
|
|
6
|
+
// purpose — it assumes reddb's physical `rid` is the row identifier, which
|
|
7
|
+
// matches how the TableRenderer passes `row` in from the dataset index.
|
|
8
|
+
|
|
9
|
+
export interface StagedChange {
|
|
10
|
+
id: string
|
|
11
|
+
table: string
|
|
12
|
+
row: number
|
|
13
|
+
col: string
|
|
14
|
+
oldValue: unknown
|
|
15
|
+
newValue: string
|
|
16
|
+
error?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CommitOutcome {
|
|
20
|
+
id: string
|
|
21
|
+
ok: boolean
|
|
22
|
+
error?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CommitExecutor = (changes: StagedChange[]) => Promise<CommitOutcome[]>
|
|
26
|
+
|
|
27
|
+
function quote(v: string): string {
|
|
28
|
+
return `'${v.replace(/'/g, "''")}'`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function uid(): string {
|
|
32
|
+
return Math.random().toString(36).slice(2, 10)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildUpdateSql(change: StagedChange): string {
|
|
36
|
+
return `UPDATE ${change.table} SET ${change.col} = ${quote(change.newValue)} WHERE rid = ${change.row};`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class PendingChangesStore {
|
|
40
|
+
changes = $state<StagedChange[]>([])
|
|
41
|
+
// Reactive count derived from the staged list.
|
|
42
|
+
count = $derived(this.changes.length)
|
|
43
|
+
|
|
44
|
+
private executor: CommitExecutor | null = null
|
|
45
|
+
|
|
46
|
+
/** Inject the commit backend. Pass null to detach (e.g. on disconnect). */
|
|
47
|
+
setExecutor(fn: CommitExecutor | null): void {
|
|
48
|
+
this.executor = fn
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stage a cell change. If a stage already exists for the same
|
|
53
|
+
* (table, row, col) it is replaced in place (preserving its id and
|
|
54
|
+
* position), so re-editing the same cell never produces duplicates.
|
|
55
|
+
*/
|
|
56
|
+
stage(table: string, row: number, col: string, oldValue: unknown, newValue: string): string {
|
|
57
|
+
const idx = this.changes.findIndex(
|
|
58
|
+
(c) => c.table === table && c.row === row && c.col === col,
|
|
59
|
+
)
|
|
60
|
+
if (idx >= 0) {
|
|
61
|
+
const existing = this.changes[idx]
|
|
62
|
+
const updated: StagedChange = {
|
|
63
|
+
...existing,
|
|
64
|
+
oldValue: existing.oldValue,
|
|
65
|
+
newValue,
|
|
66
|
+
error: undefined,
|
|
67
|
+
}
|
|
68
|
+
const next = this.changes.slice()
|
|
69
|
+
next[idx] = updated
|
|
70
|
+
this.changes = next
|
|
71
|
+
return existing.id
|
|
72
|
+
}
|
|
73
|
+
const id = uid()
|
|
74
|
+
this.changes = [
|
|
75
|
+
...this.changes,
|
|
76
|
+
{ id, table, row, col, oldValue, newValue },
|
|
77
|
+
]
|
|
78
|
+
return id
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
unstage(id: string): void {
|
|
82
|
+
this.changes = this.changes.filter((c) => c.id !== id)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Look up an existing staged change for a given cell. */
|
|
86
|
+
find(table: string, row: number, col: string): StagedChange | undefined {
|
|
87
|
+
return this.changes.find(
|
|
88
|
+
(c) => c.table === table && c.row === row && c.col === col,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** SQL preview — one statement per staged change, in insertion order. */
|
|
93
|
+
previewSql(): string[] {
|
|
94
|
+
return this.changes.map(buildUpdateSql)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Apply every staged change via the configured executor. Successful
|
|
99
|
+
* changes are removed from the queue; failures stay staged with their
|
|
100
|
+
* error attached, so the user can retry or unstage them individually.
|
|
101
|
+
*/
|
|
102
|
+
async commitAll(): Promise<{ applied: number; failed: StagedChange[] }> {
|
|
103
|
+
if (this.changes.length === 0) return { applied: 0, failed: [] }
|
|
104
|
+
if (!this.executor) throw new Error('PendingChanges: no commit executor configured')
|
|
105
|
+
|
|
106
|
+
const snapshot = this.changes.slice()
|
|
107
|
+
const outcomes = await this.executor(snapshot)
|
|
108
|
+
const outcomeById = new Map(outcomes.map((o) => [o.id, o]))
|
|
109
|
+
|
|
110
|
+
const failed: StagedChange[] = []
|
|
111
|
+
let applied = 0
|
|
112
|
+
for (const change of snapshot) {
|
|
113
|
+
const outcome = outcomeById.get(change.id)
|
|
114
|
+
if (outcome?.ok) {
|
|
115
|
+
applied += 1
|
|
116
|
+
} else {
|
|
117
|
+
failed.push({ ...change, error: outcome?.error ?? 'commit failed' })
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Replace the queue: drop committed changes, keep the failures (with
|
|
122
|
+
// any concurrent stages that landed during commitAll preserved).
|
|
123
|
+
const survivorIds = new Set(failed.map((c) => c.id))
|
|
124
|
+
const concurrent = this.changes.filter(
|
|
125
|
+
(c) => !snapshot.some((s) => s.id === c.id),
|
|
126
|
+
)
|
|
127
|
+
const failedById = new Map(failed.map((c) => [c.id, c]))
|
|
128
|
+
this.changes = [
|
|
129
|
+
...this.changes
|
|
130
|
+
.filter((c) => survivorIds.has(c.id))
|
|
131
|
+
.map((c) => failedById.get(c.id) ?? c),
|
|
132
|
+
...concurrent,
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
return { applied, failed }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
discardAll(): void {
|
|
139
|
+
this.changes = []
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Test helper — reset everything between cases. */
|
|
143
|
+
clear(): void {
|
|
144
|
+
this.changes = []
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const pendingChanges = new PendingChangesStore()
|
|
149
|
+
export type PendingChangesPanel = PendingChangesStore
|