@ryanstark24/sfgraph-web 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/__tests__/search-bounds.test.d.ts +2 -0
- package/dist/__tests__/search-bounds.test.d.ts.map +1 -0
- package/dist/__tests__/search-bounds.test.js +45 -0
- package/dist/__tests__/search-bounds.test.js.map +1 -0
- package/dist/api.d.ts +74 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +355 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/public/app.js +1001 -0
- package/dist/public/index.html +224 -0
- package/dist/public/styles.css +1063 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +179 -0
- package/dist/server.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @ryanstark24/sfgraph-web
|
|
2
|
+
|
|
3
|
+
Local web visualiser for an ingested sfgraph org. Boots via `sfgraph serve` (or `node dist/index.js` from this package) and serves a 3D force-graph viewer plus a small REST surface against the per-org SQLite store.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Trace tab** — neighborhood graph from a focal node; pairs with the `trace_upstream` / `trace_downstream` MCP tools when you want a visual companion to the markdown output.
|
|
8
|
+
- **Overview tab** — top-level org topology: hubs by degree, layer counts, freshness signals.
|
|
9
|
+
- **Schema tab** — SObject/field topology with references resolved across layers.
|
|
10
|
+
- **Render entire org** button — hits `/api/full` for a full 3D force-graph of the ingested org. Obsidian-style auto-fading labels: top-N nearest nodes plus hubs stay labeled at any zoom.
|
|
11
|
+
|
|
12
|
+
## Security model
|
|
13
|
+
|
|
14
|
+
- Binds to **loopback only by default** (`127.0.0.1:7777`).
|
|
15
|
+
- Non-loopback binds require the explicit `--i-understand-public-bind` flag on `sfgraph serve`. Without that flag, attempts to set `--host 0.0.0.0` are rejected at startup.
|
|
16
|
+
- No authentication. The viewer is intended for local-machine use only. If you opt into a public bind, you are responsible for putting it behind your own auth/firewall.
|
|
17
|
+
- EADDRINUSE auto-recovery: if a stale `sfgraph serve` is already holding the port, it's terminated and the new one takes over.
|
|
18
|
+
|
|
19
|
+
## Tech stack
|
|
20
|
+
|
|
21
|
+
- Vanilla JS + [`3d-force-graph`](https://github.com/vasturiano/3d-force-graph) + [`three.js`](https://threejs.org/).
|
|
22
|
+
- **No React, no bundler.** The `public/` directory is served as-is.
|
|
23
|
+
- Server is a tiny Node HTTP listener that reads the per-org SQLite file. It does not write to the graph.
|
|
24
|
+
|
|
25
|
+
## Running in dev
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# From repo root
|
|
29
|
+
pnpm --filter @ryanstark24/sfgraph-web build
|
|
30
|
+
cd packages/web && node dist/index.js
|
|
31
|
+
|
|
32
|
+
# Or via the published CLI (after `pnpm -r build`)
|
|
33
|
+
sfgraph serve
|
|
34
|
+
sfgraph serve --no-open # don't open a browser
|
|
35
|
+
sfgraph serve --port 8123 # alternate port
|
|
36
|
+
sfgraph serve --i-understand-public-bind --host 0.0.0.0
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Keyboard shortcuts
|
|
40
|
+
|
|
41
|
+
- `L` — toggle always-show labels on every node
|
|
42
|
+
- `F` — fit the graph to the viewport
|
|
43
|
+
- `Esc` — close the open side panel / popover
|
|
44
|
+
|
|
45
|
+
## REST endpoints
|
|
46
|
+
|
|
47
|
+
All endpoints are read-only.
|
|
48
|
+
|
|
49
|
+
| Path | Purpose |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `GET /api/orgs` | List ingested orgs with last-sync timestamps |
|
|
52
|
+
| `GET /api/search?q=…&org=…` | Type-ahead node search by qname/label |
|
|
53
|
+
| `GET /api/neighborhood?org=…&qname=…&hops=N` | Subgraph around a focal node |
|
|
54
|
+
| `GET /api/overview?org=…` | Hubs, layer counts, freshness buckets |
|
|
55
|
+
| `GET /api/full?org=…` | Full org graph (nodes + edges) — backs "Render entire org" |
|
|
56
|
+
| `GET /api/schema?org=…` | SObject / field topology |
|
|
57
|
+
| `GET /api/rel-types?org=…` | Edge relationship-type catalogue |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-bounds.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/search-bounds.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { search } from "../api.js";
|
|
6
|
+
/**
|
|
7
|
+
* Verifies the bounds added to `/api/search` after the pen-test:
|
|
8
|
+
* 1. Queries shorter than `SEARCH_MIN_QUERY_LEN` return [] without scanning.
|
|
9
|
+
* 2. `listAllQnames` scan stops at `SEARCH_MAX_SCAN` rows even when the
|
|
10
|
+
* graph has more.
|
|
11
|
+
*
|
|
12
|
+
* Uses a tmp sfgraph data dir so the test is hermetic from the user's real
|
|
13
|
+
* data dir.
|
|
14
|
+
*/
|
|
15
|
+
let tmpHome;
|
|
16
|
+
let prevHome;
|
|
17
|
+
let prevXdg;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
tmpHome = mkdtempSync(path.join(tmpdir(), "sfg-web-search-"));
|
|
20
|
+
prevHome = process.env.SFGRAPH_HOME;
|
|
21
|
+
prevXdg = process.env.XDG_DATA_HOME;
|
|
22
|
+
process.env.SFGRAPH_HOME = tmpHome;
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
if (prevHome === undefined)
|
|
26
|
+
delete process.env.SFGRAPH_HOME;
|
|
27
|
+
else
|
|
28
|
+
process.env.SFGRAPH_HOME = prevHome;
|
|
29
|
+
if (prevXdg === undefined)
|
|
30
|
+
delete process.env.XDG_DATA_HOME;
|
|
31
|
+
else
|
|
32
|
+
process.env.XDG_DATA_HOME = prevXdg;
|
|
33
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
describe("web /api/search bounds", () => {
|
|
36
|
+
it("returns [] for empty query without opening a store", async () => {
|
|
37
|
+
const r = await search("00DfakeOrgIdAbc12", "");
|
|
38
|
+
expect(r).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
it("returns [] for single-char query (below SEARCH_MIN_QUERY_LEN)", async () => {
|
|
41
|
+
const r = await search("00DfakeOrgIdAbc12", "a");
|
|
42
|
+
expect(r).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=search-bounds.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-bounds.test.js","sourceRoot":"","sources":["../../src/__tests__/search-bounds.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC;;;;;;;;GAQG;AAEH,IAAI,OAAe,CAAC;AACpB,IAAI,QAA4B,CAAC;AACjC,IAAI,OAA2B,CAAC;AAEhC,UAAU,CAAC,GAAG,EAAE;IACd,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC9D,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACpC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;;QACvD,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,QAAQ,CAAC;IACzC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;;QACvD,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,OAAO,CAAC;IACzC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;QACjD,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare function closeAllStores(): Promise<void>;
|
|
2
|
+
export interface OrgListEntry {
|
|
3
|
+
orgId: string;
|
|
4
|
+
alias: string;
|
|
5
|
+
apiVersion: string | null;
|
|
6
|
+
lastSyncedAt: number | null;
|
|
7
|
+
nodeCount: number;
|
|
8
|
+
edgeCount: number;
|
|
9
|
+
}
|
|
10
|
+
/** Scan the data dir for `<orgId>.sqlite` files and pull metadata + counts.
|
|
11
|
+
* Reuses the per-org store cache so opening for the listing also primes the
|
|
12
|
+
* subsequent /api/search and /api/neighborhood calls.
|
|
13
|
+
*
|
|
14
|
+
* Failures are logged to stderr (visible in the `sfgraph serve` terminal)
|
|
15
|
+
* AND attached to the response under `_errors` so the UI can surface them.
|
|
16
|
+
* Previously this swallowed errors silently, which made common failure
|
|
17
|
+
* modes (better-sqlite3 ABI mismatch, locked DB) look like "no orgs". */
|
|
18
|
+
export declare function listOrgs(): Promise<{
|
|
19
|
+
orgs: OrgListEntry[];
|
|
20
|
+
errors: Array<{
|
|
21
|
+
orgId: string;
|
|
22
|
+
error: string;
|
|
23
|
+
}>;
|
|
24
|
+
}>;
|
|
25
|
+
export interface SearchHit {
|
|
26
|
+
qname: string;
|
|
27
|
+
label: string;
|
|
28
|
+
}
|
|
29
|
+
/** Substring-match autocomplete over qnames. Returns at most `limit` hits. */
|
|
30
|
+
export declare function search(orgId: string, q: string, limit?: number): Promise<SearchHit[]>;
|
|
31
|
+
export interface GraphNode {
|
|
32
|
+
id: string;
|
|
33
|
+
label: string;
|
|
34
|
+
attrs?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface GraphEdge {
|
|
37
|
+
source: string;
|
|
38
|
+
target: string;
|
|
39
|
+
relType: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GraphPayload {
|
|
42
|
+
nodes: GraphNode[];
|
|
43
|
+
edges: GraphEdge[];
|
|
44
|
+
truncated?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Bidirectional BFS around a center node up to `depth` hops. */
|
|
47
|
+
export declare function neighborhood(orgId: string, qname: string, depth: number, relTypes?: string[]): Promise<GraphPayload>;
|
|
48
|
+
/** Pull every node matching one of `labels`, plus the edges among them.
|
|
49
|
+
* Capped at `limit` nodes to keep the browser from melting. */
|
|
50
|
+
export declare function overview(orgId: string, labels: string[], limit?: number): Promise<GraphPayload>;
|
|
51
|
+
export declare function fullGraph(orgId: string, limit?: number): Promise<GraphPayload>;
|
|
52
|
+
/**
|
|
53
|
+
* Schema view: CustomObjects as the centre, plus every node that touches
|
|
54
|
+
* them — Apex that queries them, Flows that reference them, LWC bundles
|
|
55
|
+
* that consume them. Surfaced rel types are the ones that meaningfully
|
|
56
|
+
* connect to data:
|
|
57
|
+
*
|
|
58
|
+
* - REFERENCES_OBJECT (Flow / Apex / LWC → CustomObject)
|
|
59
|
+
* - EXECUTES_SOQL (Apex → CustomObject via SOQL)
|
|
60
|
+
* - EXECUTES_DML (Apex → CustomObject via insert/update/delete)
|
|
61
|
+
* - READS_FIELD / WRITES_FIELD (then we hop CustomField → its parent)
|
|
62
|
+
* - TRIGGER_FIRES_ON (ApexTrigger → CustomObject)
|
|
63
|
+
*
|
|
64
|
+
* This is way more useful than the "CustomObject + CustomField + edges
|
|
65
|
+
* between only those two" original — most orgs don't even store
|
|
66
|
+
* CustomField nodes, and the only outgoing schema-relevant edges are
|
|
67
|
+
* INCOMING to CustomObject from other layers.
|
|
68
|
+
*/
|
|
69
|
+
export declare function schema(orgId: string, limit?: number): Promise<GraphPayload>;
|
|
70
|
+
/** List the rel-types available in the current graph for filter UI. */
|
|
71
|
+
export declare const ALL_REL_TYPES: string[];
|
|
72
|
+
/** Validate that an orgId looks like a Salesforce id before we try to open. */
|
|
73
|
+
export declare function validOrgId(s: string): boolean;
|
|
74
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAsBA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CASpD;AAID,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;0EAO0E;AAC1E,wBAAsB,QAAQ,IAAI,OAAO,CAAC;IACxC,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD,CAAC,CA0CD;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAgBD,8EAA8E;AAC9E,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAkBvF;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AACD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,iEAAiE;AACjE,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,YAAY,CAAC,CA6DvB;AAED;gEACgE;AAChE,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,SAAO,GACX,OAAO,CAAC,YAAY,CAAC,CAsBvB;AAqCD,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBlF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA0D/E;AAED,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAE,MAAM,EAAyC,CAAC;AAE5E,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE7C"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { REL_TYPES, SqliteGraphStore } from "@ryanstark24/sfgraph-core";
|
|
4
|
+
import { asOrgId, asQualifiedName, getSfgraphPaths } from "@ryanstark24/sfgraph-shared";
|
|
5
|
+
/** Per-orgId open-store cache. Web server reuses stores across requests. */
|
|
6
|
+
const stores = new Map();
|
|
7
|
+
async function getStore(orgId) {
|
|
8
|
+
const cached = stores.get(orgId);
|
|
9
|
+
if (cached)
|
|
10
|
+
return cached;
|
|
11
|
+
const { data } = getSfgraphPaths();
|
|
12
|
+
const dbPath = path.join(data, `${orgId}.sqlite`);
|
|
13
|
+
if (!existsSync(dbPath)) {
|
|
14
|
+
throw new Error(`no ingested graph for org '${orgId}' (expected ${dbPath})`);
|
|
15
|
+
}
|
|
16
|
+
const store = new SqliteGraphStore({ dbPath });
|
|
17
|
+
await store.init();
|
|
18
|
+
stores.set(orgId, store);
|
|
19
|
+
return store;
|
|
20
|
+
}
|
|
21
|
+
export async function closeAllStores() {
|
|
22
|
+
for (const s of stores.values()) {
|
|
23
|
+
try {
|
|
24
|
+
await s.close();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* best effort */
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
stores.clear();
|
|
31
|
+
}
|
|
32
|
+
const SF_ORG_ID_RE = /^00D[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$/;
|
|
33
|
+
/** Scan the data dir for `<orgId>.sqlite` files and pull metadata + counts.
|
|
34
|
+
* Reuses the per-org store cache so opening for the listing also primes the
|
|
35
|
+
* subsequent /api/search and /api/neighborhood calls.
|
|
36
|
+
*
|
|
37
|
+
* Failures are logged to stderr (visible in the `sfgraph serve` terminal)
|
|
38
|
+
* AND attached to the response under `_errors` so the UI can surface them.
|
|
39
|
+
* Previously this swallowed errors silently, which made common failure
|
|
40
|
+
* modes (better-sqlite3 ABI mismatch, locked DB) look like "no orgs". */
|
|
41
|
+
export async function listOrgs() {
|
|
42
|
+
const { data } = getSfgraphPaths();
|
|
43
|
+
if (!existsSync(data)) {
|
|
44
|
+
console.error(`sfgraph-web: data dir does not exist: ${data}`);
|
|
45
|
+
return { orgs: [], errors: [{ orgId: "(data-dir)", error: `does not exist: ${data}` }] };
|
|
46
|
+
}
|
|
47
|
+
const files = readdirSync(data).filter((f) => f.endsWith(".sqlite") && SF_ORG_ID_RE.test(f.replace(/\.sqlite$/, "")));
|
|
48
|
+
if (files.length === 0) {
|
|
49
|
+
console.error(`sfgraph-web: no <orgId>.sqlite files in ${data}`);
|
|
50
|
+
return {
|
|
51
|
+
orgs: [],
|
|
52
|
+
errors: [{ orgId: "(data-dir)", error: `no ingested orgs in ${data}` }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const out = [];
|
|
56
|
+
const errors = [];
|
|
57
|
+
for (const f of files) {
|
|
58
|
+
const orgId = f.replace(/\.sqlite$/, "");
|
|
59
|
+
try {
|
|
60
|
+
const store = await getStore(orgId);
|
|
61
|
+
const oid = asOrgId(orgId);
|
|
62
|
+
const org = store.getOrg(oid);
|
|
63
|
+
out.push({
|
|
64
|
+
orgId,
|
|
65
|
+
alias: org?.alias ?? orgId,
|
|
66
|
+
apiVersion: org?.apiVersion ?? null,
|
|
67
|
+
lastSyncedAt: org?.lastSyncedAt ?? null,
|
|
68
|
+
nodeCount: store.countNodes(oid),
|
|
69
|
+
edgeCount: store.countEdges(oid),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
const msg = e?.message ?? String(e);
|
|
74
|
+
console.error(`sfgraph-web: failed to open ${orgId}.sqlite — ${msg}`);
|
|
75
|
+
errors.push({ orgId, error: msg });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
orgs: out.sort((a, b) => a.alias.localeCompare(b.alias)),
|
|
80
|
+
errors,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Minimum needle length. Single-character queries scan the whole graph and
|
|
85
|
+
* match thousands of qnames — useless for autocomplete and a free DoS vector
|
|
86
|
+
* on a large org.
|
|
87
|
+
*/
|
|
88
|
+
const SEARCH_MIN_QUERY_LEN = 2;
|
|
89
|
+
/**
|
|
90
|
+
* Cap on qnames scanned per request. `listAllQnames` is O(N) across every
|
|
91
|
+
* label table; on a 50k-node org this is multi-second per call. The cap
|
|
92
|
+
* lets us short-circuit instead of locking the event loop.
|
|
93
|
+
*/
|
|
94
|
+
const SEARCH_MAX_SCAN = 25000;
|
|
95
|
+
/** Substring-match autocomplete over qnames. Returns at most `limit` hits. */
|
|
96
|
+
export async function search(orgId, q, limit = 25) {
|
|
97
|
+
if (!q || q.length < SEARCH_MIN_QUERY_LEN)
|
|
98
|
+
return [];
|
|
99
|
+
const store = await getStore(orgId);
|
|
100
|
+
const oid = asOrgId(orgId);
|
|
101
|
+
const needle = q.toLowerCase();
|
|
102
|
+
const all = store.listAllQnames(oid);
|
|
103
|
+
const hits = [];
|
|
104
|
+
let scanned = 0;
|
|
105
|
+
for (const qn of all) {
|
|
106
|
+
scanned++;
|
|
107
|
+
if (scanned > SEARCH_MAX_SCAN)
|
|
108
|
+
break;
|
|
109
|
+
if (String(qn).toLowerCase().includes(needle)) {
|
|
110
|
+
const node = store.getNode(oid, qn);
|
|
111
|
+
hits.push({ qname: String(qn), label: node?.label ?? "Unknown" });
|
|
112
|
+
if (hits.length >= limit)
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return hits;
|
|
117
|
+
}
|
|
118
|
+
/** Bidirectional BFS around a center node up to `depth` hops. */
|
|
119
|
+
export async function neighborhood(orgId, qname, depth, relTypes) {
|
|
120
|
+
const store = await getStore(orgId);
|
|
121
|
+
const oid = asOrgId(orgId);
|
|
122
|
+
const center = asQualifiedName(qname);
|
|
123
|
+
const nodes = new Map();
|
|
124
|
+
const edges = [];
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
const allowedRels = relTypes && relTypes.length > 0 ? new Set(relTypes) : null;
|
|
127
|
+
const NODE_CAP = 250;
|
|
128
|
+
let truncated = false;
|
|
129
|
+
const addNode = (qn) => {
|
|
130
|
+
if (nodes.has(qn))
|
|
131
|
+
return;
|
|
132
|
+
const n = store.getNode(oid, asQualifiedName(qn));
|
|
133
|
+
nodes.set(qn, { id: qn, label: n?.label ?? "Unknown" });
|
|
134
|
+
};
|
|
135
|
+
addNode(qname);
|
|
136
|
+
let frontier = [qname];
|
|
137
|
+
seen.add(qname);
|
|
138
|
+
// Tight cap check: a single high-fan-out node (e.g. CustomObject:Account
|
|
139
|
+
// has 1000+ field/permission edges) can blow past the limit inside one
|
|
140
|
+
// frontier iteration if we only check at the top. Check after every
|
|
141
|
+
// node add and break early.
|
|
142
|
+
outer: for (let d = 0; d < depth; d++) {
|
|
143
|
+
const next = [];
|
|
144
|
+
for (const cur of frontier) {
|
|
145
|
+
const outE = store.listEdgesFrom(oid, asQualifiedName(cur));
|
|
146
|
+
const inE = store.listEdgesTo(oid, asQualifiedName(cur));
|
|
147
|
+
for (const e of outE) {
|
|
148
|
+
if (allowedRels && !allowedRels.has(String(e.relType)))
|
|
149
|
+
continue;
|
|
150
|
+
if (nodes.size >= NODE_CAP) {
|
|
151
|
+
truncated = true;
|
|
152
|
+
break outer;
|
|
153
|
+
}
|
|
154
|
+
const dst = String(e.dstQualifiedName);
|
|
155
|
+
addNode(dst);
|
|
156
|
+
edges.push({ source: cur, target: dst, relType: String(e.relType) });
|
|
157
|
+
if (!seen.has(dst)) {
|
|
158
|
+
seen.add(dst);
|
|
159
|
+
next.push(dst);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const e of inE) {
|
|
163
|
+
if (allowedRels && !allowedRels.has(String(e.relType)))
|
|
164
|
+
continue;
|
|
165
|
+
if (nodes.size >= NODE_CAP) {
|
|
166
|
+
truncated = true;
|
|
167
|
+
break outer;
|
|
168
|
+
}
|
|
169
|
+
const src = String(e.srcQualifiedName);
|
|
170
|
+
addNode(src);
|
|
171
|
+
edges.push({ source: src, target: cur, relType: String(e.relType) });
|
|
172
|
+
if (!seen.has(src)) {
|
|
173
|
+
seen.add(src);
|
|
174
|
+
next.push(src);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
frontier = next;
|
|
179
|
+
}
|
|
180
|
+
return { nodes: [...nodes.values()], edges, truncated };
|
|
181
|
+
}
|
|
182
|
+
/** Pull every node matching one of `labels`, plus the edges among them.
|
|
183
|
+
* Capped at `limit` nodes to keep the browser from melting. */
|
|
184
|
+
export async function overview(orgId, labels, limit = 1500) {
|
|
185
|
+
const store = await getStore(orgId);
|
|
186
|
+
const oid = asOrgId(orgId);
|
|
187
|
+
const nodes = new Map();
|
|
188
|
+
for (const lbl of labels) {
|
|
189
|
+
const ns = store.listNodesByLabel(oid, lbl, limit);
|
|
190
|
+
for (const n of ns) {
|
|
191
|
+
if (nodes.size >= limit)
|
|
192
|
+
break;
|
|
193
|
+
nodes.set(String(n.qualifiedName), { id: String(n.qualifiedName), label: n.label });
|
|
194
|
+
}
|
|
195
|
+
if (nodes.size >= limit)
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
const edges = [];
|
|
199
|
+
// For overview, only emit edges where BOTH endpoints are in the node set.
|
|
200
|
+
for (const qn of nodes.keys()) {
|
|
201
|
+
const outE = store.listEdgesFrom(oid, asQualifiedName(qn));
|
|
202
|
+
for (const e of outE) {
|
|
203
|
+
const dst = String(e.dstQualifiedName);
|
|
204
|
+
if (nodes.has(dst))
|
|
205
|
+
edges.push({ source: qn, target: dst, relType: String(e.relType) });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { nodes: [...nodes.values()], edges, truncated: nodes.size >= limit };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Full-graph view: every node in the org (capped at `limit`) plus every
|
|
212
|
+
* edge between visible nodes. Used by the "Render entire org" button in
|
|
213
|
+
* the web UI — gives the Obsidian-style global view instead of the
|
|
214
|
+
* per-label or per-trace subsets.
|
|
215
|
+
*
|
|
216
|
+
* We enumerate node-bearing labels rather than calling `listAllQnames`
|
|
217
|
+
* because we want a stable label assignment for colouring without doing
|
|
218
|
+
* one `getNode` per id.
|
|
219
|
+
*/
|
|
220
|
+
const ALL_KNOWN_LABELS = [
|
|
221
|
+
"ApexClass",
|
|
222
|
+
"ApexTrigger",
|
|
223
|
+
"ApexMethod",
|
|
224
|
+
"TestMethod",
|
|
225
|
+
"ApexPage",
|
|
226
|
+
"LightningComponentBundle",
|
|
227
|
+
"LWC",
|
|
228
|
+
"LWCBundle",
|
|
229
|
+
"AuraDefinitionBundle",
|
|
230
|
+
"Flow",
|
|
231
|
+
"FlowVersion",
|
|
232
|
+
"CustomObject",
|
|
233
|
+
"CustomField",
|
|
234
|
+
"Profile",
|
|
235
|
+
"PermissionSet",
|
|
236
|
+
"PermissionSetGroup",
|
|
237
|
+
"SharingRule",
|
|
238
|
+
"NamedCredential",
|
|
239
|
+
"ExternalServiceRegistration",
|
|
240
|
+
"StaticResource",
|
|
241
|
+
"CustomLabel",
|
|
242
|
+
"Workflow",
|
|
243
|
+
];
|
|
244
|
+
export async function fullGraph(orgId, limit = 5000) {
|
|
245
|
+
const store = await getStore(orgId);
|
|
246
|
+
const oid = asOrgId(orgId);
|
|
247
|
+
const nodes = new Map();
|
|
248
|
+
for (const lbl of ALL_KNOWN_LABELS) {
|
|
249
|
+
if (nodes.size >= limit)
|
|
250
|
+
break;
|
|
251
|
+
const ns = store.listNodesByLabel(oid, lbl, limit);
|
|
252
|
+
for (const n of ns) {
|
|
253
|
+
if (nodes.size >= limit)
|
|
254
|
+
break;
|
|
255
|
+
nodes.set(String(n.qualifiedName), { id: String(n.qualifiedName), label: n.label });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// All edges where both endpoints are in our node set.
|
|
259
|
+
const edges = [];
|
|
260
|
+
for (const qn of nodes.keys()) {
|
|
261
|
+
const outE = store.listEdgesFrom(oid, asQualifiedName(qn));
|
|
262
|
+
for (const e of outE) {
|
|
263
|
+
const dst = String(e.dstQualifiedName);
|
|
264
|
+
if (nodes.has(dst)) {
|
|
265
|
+
edges.push({ source: qn, target: dst, relType: String(e.relType) });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { nodes: [...nodes.values()], edges, truncated: nodes.size >= limit };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Schema view: CustomObjects as the centre, plus every node that touches
|
|
273
|
+
* them — Apex that queries them, Flows that reference them, LWC bundles
|
|
274
|
+
* that consume them. Surfaced rel types are the ones that meaningfully
|
|
275
|
+
* connect to data:
|
|
276
|
+
*
|
|
277
|
+
* - REFERENCES_OBJECT (Flow / Apex / LWC → CustomObject)
|
|
278
|
+
* - EXECUTES_SOQL (Apex → CustomObject via SOQL)
|
|
279
|
+
* - EXECUTES_DML (Apex → CustomObject via insert/update/delete)
|
|
280
|
+
* - READS_FIELD / WRITES_FIELD (then we hop CustomField → its parent)
|
|
281
|
+
* - TRIGGER_FIRES_ON (ApexTrigger → CustomObject)
|
|
282
|
+
*
|
|
283
|
+
* This is way more useful than the "CustomObject + CustomField + edges
|
|
284
|
+
* between only those two" original — most orgs don't even store
|
|
285
|
+
* CustomField nodes, and the only outgoing schema-relevant edges are
|
|
286
|
+
* INCOMING to CustomObject from other layers.
|
|
287
|
+
*/
|
|
288
|
+
export async function schema(orgId, limit = 1500) {
|
|
289
|
+
const store = await getStore(orgId);
|
|
290
|
+
const oid = asOrgId(orgId);
|
|
291
|
+
const nodes = new Map();
|
|
292
|
+
const edges = [];
|
|
293
|
+
const addNode = (qn, label) => {
|
|
294
|
+
if (nodes.size >= limit)
|
|
295
|
+
return;
|
|
296
|
+
if (!nodes.has(qn))
|
|
297
|
+
nodes.set(qn, { id: qn, label });
|
|
298
|
+
};
|
|
299
|
+
// Seed with every CustomObject in the org.
|
|
300
|
+
for (const n of store.listNodesByLabel(oid, "CustomObject", limit)) {
|
|
301
|
+
addNode(String(n.qualifiedName), n.label);
|
|
302
|
+
}
|
|
303
|
+
// Also include CustomField if the org has them (some extraction profiles
|
|
304
|
+
// produce field-level nodes, others don't).
|
|
305
|
+
for (const n of store.listNodesByLabel(oid, "CustomField", limit)) {
|
|
306
|
+
addNode(String(n.qualifiedName), n.label);
|
|
307
|
+
}
|
|
308
|
+
const wantedRels = new Set([
|
|
309
|
+
REL_TYPES.REFERENCES_OBJECT,
|
|
310
|
+
REL_TYPES.REFERENCES,
|
|
311
|
+
REL_TYPES.EXECUTES_SOQL,
|
|
312
|
+
REL_TYPES.EXECUTES_DML,
|
|
313
|
+
REL_TYPES.EXECUTES_SOSL,
|
|
314
|
+
REL_TYPES.READS_FIELD,
|
|
315
|
+
REL_TYPES.WRITES_FIELD,
|
|
316
|
+
REL_TYPES.TRIGGER_FIRES_ON,
|
|
317
|
+
REL_TYPES.DEFINES_FIELD,
|
|
318
|
+
REL_TYPES.CONTAINS,
|
|
319
|
+
]);
|
|
320
|
+
// For each schema-y node, follow INCOMING edges to find consumers. This is
|
|
321
|
+
// the load-bearing change — most schema relationships are stored as
|
|
322
|
+
// (consumer → CustomObject), so we have to listEdgesTo, not From.
|
|
323
|
+
const schemaSeeds = [...nodes.keys()];
|
|
324
|
+
for (const qn of schemaSeeds) {
|
|
325
|
+
if (nodes.size >= limit)
|
|
326
|
+
break;
|
|
327
|
+
const inE = store.listEdgesTo(oid, asQualifiedName(qn));
|
|
328
|
+
for (const e of inE) {
|
|
329
|
+
if (!wantedRels.has(String(e.relType)))
|
|
330
|
+
continue;
|
|
331
|
+
const src = String(e.srcQualifiedName);
|
|
332
|
+
const srcNode = store.getNode(oid, asQualifiedName(src));
|
|
333
|
+
addNode(src, srcNode?.label ?? "Unknown");
|
|
334
|
+
edges.push({ source: src, target: qn, relType: String(e.relType) });
|
|
335
|
+
}
|
|
336
|
+
// Outgoing too (CustomField → CustomObject lookups, when present).
|
|
337
|
+
const outE = store.listEdgesFrom(oid, asQualifiedName(qn));
|
|
338
|
+
for (const e of outE) {
|
|
339
|
+
if (!wantedRels.has(String(e.relType)))
|
|
340
|
+
continue;
|
|
341
|
+
const dst = String(e.dstQualifiedName);
|
|
342
|
+
const dstNode = store.getNode(oid, asQualifiedName(dst));
|
|
343
|
+
addNode(dst, dstNode?.label ?? "Unknown");
|
|
344
|
+
edges.push({ source: qn, target: dst, relType: String(e.relType) });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return { nodes: [...nodes.values()], edges, truncated: nodes.size >= limit };
|
|
348
|
+
}
|
|
349
|
+
/** List the rel-types available in the current graph for filter UI. */
|
|
350
|
+
export const ALL_REL_TYPES = Object.values(REL_TYPES);
|
|
351
|
+
/** Validate that an orgId looks like a Salesforce id before we try to open. */
|
|
352
|
+
export function validOrgId(s) {
|
|
353
|
+
return SF_ORG_ID_RE.test(s);
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoC,MAAM,aAAa,CAAC"}
|