mushroomdb-client 0.1.2

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/ws.js ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * WebSocket subscription over `GET /subscribe`.
3
+ *
4
+ * # Protocol (matches crates/server/src/subscribe.rs)
5
+ *
6
+ * 1. Connect to `ws[s]://<host>/subscribe`.
7
+ * 2. Server waits for one JSON subscribe message: `{rules?, writes?}`.
8
+ * 3. Server responds with `{"subscribed":true}`.
9
+ * 4. Server streams DbEvent JSON frames until the connection closes.
10
+ *
11
+ * # Reconnection
12
+ *
13
+ * Auto-reconnect is NOT implemented in v1. When the connection drops
14
+ * (network error, server restart), no further events are delivered. The
15
+ * caller is responsible for reconnecting if required.
16
+ *
17
+ * # Lagged events
18
+ *
19
+ * If the server's per-subscriber queue overflows, it emits a
20
+ * `{"type":"lagged","missed":N}` frame. This is passed to `onEvent` like any
21
+ * other event. For lossless consumers: on receiving a `lagged` event, re-read
22
+ * the affected graph state via a query.
23
+ *
24
+ * # Node.js usage
25
+ *
26
+ * The browser WebSocket global is not present in Node < 21. Pass the `ws`
27
+ * package's WebSocket class via `opts.wsConstructor`:
28
+ *
29
+ * ```ts
30
+ * import WS from 'ws';
31
+ * const handle = await subscribe(wsUrl, { writes: true, wsConstructor: WS as WsConstructor }, onEvent);
32
+ * ```
33
+ */
34
+ import { MushroomError } from "./types.js";
35
+ /** Coerce an unknown message `data` value to a UTF-8 string. */
36
+ function dataToString(data) {
37
+ if (typeof data === "string")
38
+ return data;
39
+ // Node.js ws package delivers Buffer objects for text frames.
40
+ if (data != null && typeof data.toString === "function") {
41
+ return data.toString();
42
+ }
43
+ return String(data);
44
+ }
45
+ /** Resolve the WebSocket constructor: explicit option → global. */
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ function resolveWsConstructor(opt) {
48
+ if (opt)
49
+ return opt;
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ const g = globalThis;
52
+ if (typeof g["WebSocket"] === "function")
53
+ return g["WebSocket"];
54
+ throw new Error("No WebSocket implementation available. " +
55
+ "In Node.js < 21, install the `ws` package and pass " +
56
+ "`wsConstructor: WS as WsConstructor` in the options.");
57
+ }
58
+ /**
59
+ * Open a `GET /subscribe` WebSocket and begin streaming {@link DbEvent}s.
60
+ *
61
+ * Resolves when the server acknowledges the subscribe message
62
+ * (`{"subscribed":true}`). Rejects on connection failure or if the server
63
+ * returns an error (e.g. unknown rule name).
64
+ *
65
+ * @param wsUrl Full WebSocket URL, e.g. `ws://127.0.0.1:8080/subscribe`.
66
+ * @param opts Subscribe options — rules, writes flag, optional wsConstructor.
67
+ * @param onEvent Callback invoked for each {@link DbEvent}, including `lagged`.
68
+ */
69
+ export async function subscribe(wsUrl, opts, onEvent) {
70
+ const WS = resolveWsConstructor(opts.wsConstructor);
71
+ const ws = new WS(wsUrl);
72
+ return new Promise((resolve, reject) => {
73
+ let subscribed = false;
74
+ let closeResolve = null;
75
+ const closePromise = new Promise((res) => {
76
+ closeResolve = res;
77
+ });
78
+ ws.onopen = () => {
79
+ const msg = {
80
+ rules: opts.rules ?? [],
81
+ writes: opts.writes ?? false,
82
+ };
83
+ ws.send(JSON.stringify(msg));
84
+ };
85
+ ws.onmessage = (ev) => {
86
+ let text;
87
+ try {
88
+ text = dataToString(ev.data);
89
+ }
90
+ catch {
91
+ return; // unreadable frame — skip
92
+ }
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(text);
96
+ }
97
+ catch {
98
+ return; // unparseable frame — skip
99
+ }
100
+ const frame = parsed;
101
+ if (!subscribed) {
102
+ if (frame["subscribed"] === true) {
103
+ subscribed = true;
104
+ resolve({
105
+ close() {
106
+ ws.close();
107
+ return closePromise;
108
+ },
109
+ });
110
+ }
111
+ else if (typeof frame["error"] === "string") {
112
+ reject(new MushroomError(frame["error"]));
113
+ ws.close();
114
+ }
115
+ else {
116
+ reject(new Error("Unexpected subscribe response: " + text));
117
+ ws.close();
118
+ }
119
+ }
120
+ else {
121
+ onEvent(frame);
122
+ }
123
+ };
124
+ ws.onerror = (err) => {
125
+ if (!subscribed) {
126
+ reject(err instanceof Error
127
+ ? err
128
+ : new Error("WebSocket error before subscribe ack"));
129
+ }
130
+ };
131
+ ws.onclose = () => {
132
+ if (!subscribed) {
133
+ reject(new Error("WebSocket closed before subscribe ack"));
134
+ }
135
+ closeResolve?.();
136
+ };
137
+ });
138
+ }
139
+ //# sourceMappingURL=ws.js.map
package/dist/ws.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "mushroomdb-client",
3
+ "version": "0.1.2",
4
+ "description": "TypeScript client for the mushroomdb graph database HTTP + WebSocket API",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/MatthewSherlin/mushroomdb"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.0.0",
34
+ "@types/ws": "^8.5.12",
35
+ "typescript": "^7.0.2",
36
+ "vitest": "^4.1.10",
37
+ "ws": "^8.18.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
package/src/client.ts ADDED
@@ -0,0 +1,364 @@
1
+ /**
2
+ * HTTP client for the mushroomdb server.
3
+ *
4
+ * Uses the browser-standard `fetch` API (built into Node 18+).
5
+ *
6
+ * ```ts
7
+ * import { MushroomClient } from 'mushroomdb-client';
8
+ *
9
+ * const client = new MushroomClient('http://127.0.0.1:8080');
10
+ * const result = await client.query('MATCH (n:Person) RETURN n.name LIMIT 10');
11
+ * console.log(result.columns, result.rows);
12
+ * ```
13
+ */
14
+
15
+ import {
16
+ MushroomError,
17
+ type AlgoReport,
18
+ type DegreeConfig,
19
+ type DegreeReport,
20
+ type Explanation,
21
+ type IngestReport,
22
+ type IngestRequest,
23
+ type Neighborhood,
24
+ type NodeInfo,
25
+ type PageRankConfig,
26
+ type PageRankReport,
27
+ type QueryResult,
28
+ type RuleDef,
29
+ type Stats,
30
+ type SuggestReport,
31
+ type WccConfig,
32
+ type WccReport,
33
+ } from "./types.js";
34
+ import {
35
+ subscribe as wsSubscribe,
36
+ type SubscribeHandle,
37
+ type SubscribeOptions,
38
+ type WsConstructor,
39
+ } from "./ws.js";
40
+
41
+ export type {
42
+ AlgoDir,
43
+ AlgoReport,
44
+ CellValue,
45
+ DegreeConfig,
46
+ DegreeReport,
47
+ Explanation,
48
+ IngestEdge,
49
+ IngestOptions,
50
+ IngestReport,
51
+ IngestRequest,
52
+ Neighborhood,
53
+ NodeInfo,
54
+ PageRankConfig,
55
+ PageRankReport,
56
+ PredicateKind,
57
+ PredicateSummary,
58
+ QueryResult,
59
+ RuleDef,
60
+ RulePredicate,
61
+ RuleStats,
62
+ RuleSuggestion,
63
+ Stats,
64
+ SuggestReport,
65
+ WccConfig,
66
+ WccReport,
67
+ } from "./types.js";
68
+ export { MushroomError };
69
+
70
+ /** Optional constructor flags for {@link MushroomClient}. */
71
+ export interface ClientOptions {
72
+ /**
73
+ * When set, sent as `Authorization: Bearer <token>` on every HTTP fetch.
74
+ * Cookie auth is a browser/explorer concern and is not implemented here.
75
+ */
76
+ token?: string;
77
+ }
78
+
79
+ /** Parameters for a Cypher query. Values must be JSON scalars. */
80
+ export type QueryParams = Record<string, string | number | boolean>;
81
+
82
+ /** Options accepted by {@link MushroomClient.query}. */
83
+ export interface QueryOptions {
84
+ /** Bound parameters. Values must be JSON scalars (string | number | boolean). */
85
+ params?: QueryParams;
86
+ }
87
+
88
+ /**
89
+ * HTTP + WebSocket client for mushroomdb.
90
+ *
91
+ * All methods use the browser-standard `fetch` API and are therefore
92
+ * compatible with both Node.js 18+ and modern browsers.
93
+ *
94
+ * **Node-only**: The `subscribe` method requires a WebSocket implementation.
95
+ * In Node < 21, install the `ws` package and pass `wsConstructor` in the
96
+ * subscribe options. See {@link SubscribeOptions}.
97
+ */
98
+ export class MushroomClient {
99
+ private readonly baseUrl: string;
100
+ private readonly wsBase: string;
101
+ private readonly token: string | undefined;
102
+
103
+ /**
104
+ * @param baseUrl HTTP base URL of the mushroomdb server, e.g.
105
+ * `"http://127.0.0.1:8080"`. Trailing slash is stripped.
106
+ * @param opts Optional `{ token }` — sent as `Authorization: Bearer`.
107
+ */
108
+ constructor(baseUrl: string, opts?: ClientOptions) {
109
+ this.baseUrl = baseUrl.replace(/\/$/, "");
110
+ this.wsBase = this.baseUrl.replace(/^http/, "ws");
111
+ this.token = opts?.token;
112
+ }
113
+
114
+ // -------------------------------------------------------------------------
115
+ // Internal helpers
116
+ // -------------------------------------------------------------------------
117
+
118
+ private url(path: string): string {
119
+ return `${this.baseUrl}${path}`;
120
+ }
121
+
122
+ private wsUrl(path: string): string {
123
+ return `${this.wsBase}${path}`;
124
+ }
125
+
126
+ private authHeaders(): Record<string, string> {
127
+ return this.token ? { Authorization: `Bearer ${this.token}` } : {};
128
+ }
129
+
130
+ /**
131
+ * Execute a fetch and decode the JSON body.
132
+ * Throws {@link MushroomError} on non-2xx responses.
133
+ */
134
+ private async fetchJson<T>(
135
+ path: string,
136
+ init?: RequestInit,
137
+ ): Promise<T> {
138
+ const resp = await fetch(this.url(path), {
139
+ ...init,
140
+ headers: {
141
+ "Content-Type": "application/json",
142
+ Accept: "application/json",
143
+ ...this.authHeaders(),
144
+ ...(init?.headers ?? {}),
145
+ },
146
+ });
147
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ const body: any = await resp.json();
149
+ if (!resp.ok) {
150
+ const detail: string =
151
+ typeof body?.error === "string" ? body.error : `HTTP ${resp.status}`;
152
+ throw new MushroomError(detail);
153
+ }
154
+ return body as T;
155
+ }
156
+
157
+ // -------------------------------------------------------------------------
158
+ // HTTP endpoints
159
+ // -------------------------------------------------------------------------
160
+
161
+ /**
162
+ * Run a Cypher query (read or write).
163
+ *
164
+ * The server auto-detects write statements (`CREATE`, `MERGE`, `SET`,
165
+ * `DELETE`) and acquires the appropriate lock. Both read and write queries
166
+ * go to `POST /query?format=json`.
167
+ *
168
+ * @param cypher Cypher query string.
169
+ * @param opts Optional bound parameters (JSON scalar values only).
170
+ * @returns Column names and a 2-D array of {@link CellValue} rows.
171
+ */
172
+ async query(cypher: string, opts?: QueryOptions): Promise<QueryResult> {
173
+ return this.fetchJson<QueryResult>("/query?format=json", {
174
+ method: "POST",
175
+ body: JSON.stringify({
176
+ cypher,
177
+ ...(opts?.params ? { params: opts.params } : {}),
178
+ }),
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Ingest nodes (and optional edges) into the database.
184
+ *
185
+ * Wraps `POST /ingest`. The server acquires the write lock, applies the
186
+ * rows to the WAL, and runs all rules incrementally.
187
+ *
188
+ * @param req Ingest payload — `label`, `rows`, optional `options` and `edges`.
189
+ * @returns Server ingest report (opaque; check for absence of errors).
190
+ */
191
+ async ingest(req: IngestRequest): Promise<IngestReport> {
192
+ return this.fetchJson<IngestReport>("/ingest", {
193
+ method: "POST",
194
+ body: JSON.stringify(req),
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Get database-wide statistics.
200
+ *
201
+ * Wraps `GET /stats`. Returns live node/edge counts and per-rule stats.
202
+ */
203
+ async stats(): Promise<Stats> {
204
+ return this.fetchJson<Stats>("/stats");
205
+ }
206
+
207
+ /**
208
+ * Profile the database and return rule suggestions.
209
+ *
210
+ * Wraps `GET /suggest`. CPU-intensive — runs in the server's blocking
211
+ * thread-pool with a 5-second global budget. The {@link SuggestReport}
212
+ * includes a `truncated` flag when the budget fires early.
213
+ *
214
+ * Suggestions are not auto-applied; call `POST /rules` to create a rule.
215
+ */
216
+ async suggest(): Promise<SuggestReport> {
217
+ return this.fetchJson<SuggestReport>("/suggest");
218
+ }
219
+
220
+ /**
221
+ * Run a graph algorithm.
222
+ *
223
+ * Wraps `POST /algo/{pagerank|wcc|degree}`.
224
+ *
225
+ * @param algo Algorithm name.
226
+ * @param config Optional algorithm-specific configuration.
227
+ * @returns Algorithm report — see {@link PageRankReport}, {@link WccReport},
228
+ * {@link DegreeReport}.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * const pr = await client.algo('pagerank') as PageRankReport;
233
+ * console.log(pr.scores.slice(0, 5));
234
+ * ```
235
+ */
236
+ async algo(algo: "pagerank", config?: PageRankConfig): Promise<PageRankReport>;
237
+ async algo(algo: "wcc", config?: WccConfig): Promise<WccReport>;
238
+ async algo(algo: "degree", config?: DegreeConfig): Promise<DegreeReport>;
239
+ async algo(
240
+ algo: "pagerank" | "wcc" | "degree",
241
+ config?: PageRankConfig | WccConfig | DegreeConfig,
242
+ ): Promise<AlgoReport> {
243
+ // The server structs carry #[serde(default)], so sending only the fields
244
+ // the caller explicitly set (or an empty body {}) is valid — the server
245
+ // fills in its own defaults for any missing fields.
246
+ return this.fetchJson<AlgoReport>(`/algo/${algo}`, {
247
+ method: "POST",
248
+ body: JSON.stringify(config ?? {}),
249
+ });
250
+ }
251
+
252
+ /**
253
+ * Explain rule-derived edges between two node keys.
254
+ *
255
+ * Wraps `GET /explain?a=&b=`.
256
+ */
257
+ async explain(a: string, b: string): Promise<Explanation[]> {
258
+ const qs = new URLSearchParams({ a, b });
259
+ return this.fetchJson<Explanation[]>(`/explain?${qs.toString()}`);
260
+ }
261
+
262
+ /**
263
+ * Create a derivation rule.
264
+ *
265
+ * Wraps `POST /rules`. The server acquires the write lock, validates the
266
+ * {@link RuleDef}, and backfills matching pairs.
267
+ */
268
+ async createRule(def: RuleDef): Promise<void> {
269
+ await this.fetchJson("/rules", {
270
+ method: "POST",
271
+ body: JSON.stringify(def),
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Fetch a node by key.
277
+ *
278
+ * Wraps `GET /node/{key}`. Returns `null` when the server answers 404
279
+ * (unknown key). Other HTTP errors throw {@link MushroomError}.
280
+ */
281
+ async node(key: string): Promise<NodeInfo | null> {
282
+ const resp = await fetch(this.url(`/node/${encodeURIComponent(key)}`), {
283
+ headers: {
284
+ Accept: "application/json",
285
+ ...this.authHeaders(),
286
+ },
287
+ });
288
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
+ const body: any = await resp.json();
290
+ if (resp.status === 404) {
291
+ return null;
292
+ }
293
+ if (!resp.ok) {
294
+ const detail: string =
295
+ typeof body?.error === "string" ? body.error : `HTTP ${resp.status}`;
296
+ throw new MushroomError(detail);
297
+ }
298
+ return body as NodeInfo;
299
+ }
300
+
301
+ /**
302
+ * Depth-N neighborhood of a node.
303
+ *
304
+ * Wraps `GET /node/{key}/neighborhood`. Default depth is the server's
305
+ * (1). Columns are `key`, `label`, `depth`.
306
+ */
307
+ async neighborhood(
308
+ key: string,
309
+ opts?: { depth?: number },
310
+ ): Promise<Neighborhood> {
311
+ const qs = new URLSearchParams();
312
+ if (opts?.depth !== undefined) {
313
+ qs.set("depth", String(opts.depth));
314
+ }
315
+ const query = qs.toString();
316
+ const path = `/node/${encodeURIComponent(key)}/neighborhood${
317
+ query ? `?${query}` : ""
318
+ }`;
319
+ return this.fetchJson<Neighborhood>(path);
320
+ }
321
+
322
+ // -------------------------------------------------------------------------
323
+ // WebSocket
324
+ // -------------------------------------------------------------------------
325
+
326
+ /**
327
+ * Subscribe to post-commit events over WebSocket (`GET /subscribe`).
328
+ *
329
+ * Returns a promise that resolves when the server acknowledges the
330
+ * subscription (`{"subscribed":true}`). After that, `onEvent` is called
331
+ * for each {@link DbEvent}, including {@link DbEvent.lagged} frames.
332
+ *
333
+ * **No auto-reconnect in v1.** When the connection drops, no further events
334
+ * are delivered. Reconnect manually if required.
335
+ *
336
+ * **Always await `handle.close()`** when done — an open WebSocket keeps the
337
+ * Node.js event loop alive and will cause test hangs.
338
+ *
339
+ * **Node.js < 21**: pass `wsConstructor` — see {@link SubscribeOptions}.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * import WS from 'ws';
344
+ * const handle = await client.subscribe(
345
+ * { writes: true, wsConstructor: WS as WsConstructor },
346
+ * (ev) => console.log(ev),
347
+ * );
348
+ * // ... do work ...
349
+ * await handle.close();
350
+ * ```
351
+ */
352
+ subscribe(
353
+ opts: SubscribeOptions,
354
+ onEvent: (event: import("./types.js").DbEvent) => void,
355
+ ): Promise<SubscribeHandle> {
356
+ let url = this.wsUrl("/subscribe");
357
+ if (this.token) {
358
+ url += `?token=${encodeURIComponent(this.token)}`;
359
+ }
360
+ return wsSubscribe(url, opts, onEvent);
361
+ }
362
+ }
363
+
364
+ export type { SubscribeHandle, SubscribeOptions, WsConstructor };
package/src/index.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * mushroomdb-client — TypeScript client for mushroomdb.
3
+ *
4
+ * Entry point re-exports everything callers need.
5
+ */
6
+
7
+ export { MushroomClient, MushroomError } from "./client.js";
8
+ export type {
9
+ AlgoDir,
10
+ AlgoReport,
11
+ CellValue,
12
+ ClientOptions,
13
+ DegreeConfig,
14
+ DegreeReport,
15
+ Explanation,
16
+ IngestEdge,
17
+ IngestOptions,
18
+ IngestReport,
19
+ IngestRequest,
20
+ Neighborhood,
21
+ NodeInfo,
22
+ PageRankConfig,
23
+ PageRankReport,
24
+ PredicateKind,
25
+ PredicateSummary,
26
+ QueryOptions,
27
+ QueryParams,
28
+ QueryResult,
29
+ RuleDef,
30
+ RulePredicate,
31
+ RuleStats,
32
+ RuleSuggestion,
33
+ Stats,
34
+ SuggestReport,
35
+ WccConfig,
36
+ WccReport,
37
+ } from "./client.js";
38
+ export type {
39
+ DbEvent,
40
+ SubscribeMessage,
41
+ } from "./types.js";
42
+ export type {
43
+ SubscribeHandle,
44
+ SubscribeOptions,
45
+ WsConstructor,
46
+ WsLike,
47
+ } from "./ws.js";