mushroomdb-client 0.7.1 → 0.8.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/src/client.ts CHANGED
@@ -15,12 +15,21 @@
15
15
  import {
16
16
  MushroomError,
17
17
  type AlgoReport,
18
+ type BackupReport,
19
+ type CreateNodeResult,
18
20
  type DegreeConfig,
19
21
  type DegreeReport,
22
+ type EdgeHistoryResult,
20
23
  type Explanation,
24
+ type FindSimilarOptions,
25
+ type FindSimilarResult,
26
+ type HealthReport,
21
27
  type IngestReport,
22
28
  type IngestRequest,
29
+ type MetricsReport,
23
30
  type Neighborhood,
31
+ type NodeEdgesResult,
32
+ type NodeHistoryResult,
24
33
  type NodeInfo,
25
34
  type PageRankConfig,
26
35
  type PageRankReport,
@@ -28,40 +37,62 @@ import {
28
37
  type RuleDef,
29
38
  type Stats,
30
39
  type SuggestReport,
40
+ type WasLinkedResult,
31
41
  type WccConfig,
32
42
  type WccReport,
33
43
  } from "./types.js";
34
44
  import {
35
45
  subscribe as wsSubscribe,
46
+ watch as wsWatch,
47
+ type MutationEvent,
36
48
  type SubscribeHandle,
37
49
  type SubscribeOptions,
50
+ type WatchHandle,
51
+ type WatchOptions,
38
52
  type WsConstructor,
39
53
  } from "./ws.js";
40
54
 
41
55
  export type {
42
56
  AlgoDir,
43
57
  AlgoReport,
58
+ BackupReport,
44
59
  CellValue,
60
+ CreateNodeResult,
45
61
  DegreeConfig,
46
62
  DegreeReport,
63
+ EdgeHistoryEntry,
64
+ EdgeHistoryResult,
65
+ EdgeInfo,
47
66
  Explanation,
67
+ FindSimilarOptions,
68
+ FindSimilarResult,
69
+ HealthReport,
70
+ HistoryChange,
71
+ HistoryEntry,
48
72
  IngestEdge,
49
73
  IngestOptions,
50
74
  IngestReport,
51
75
  IngestRequest,
76
+ MetricsReport,
52
77
  Neighborhood,
78
+ NodeEdgesResult,
79
+ NodeHistoryResult,
53
80
  NodeInfo,
81
+ NodeKeyOrStub,
54
82
  PageRankConfig,
55
83
  PageRankReport,
56
84
  PredicateKind,
57
85
  PredicateSummary,
86
+ PropPredicate,
58
87
  QueryResult,
59
88
  RuleDef,
60
89
  RulePredicate,
61
90
  RuleStats,
62
91
  RuleSuggestion,
92
+ SlowQueryEntry,
63
93
  Stats,
64
94
  SuggestReport,
95
+ WasLinkedResult,
65
96
  WccConfig,
66
97
  WccReport,
67
98
  } from "./types.js";
@@ -83,6 +114,25 @@ export type QueryParams = Record<string, string | number | boolean>;
83
114
  export interface QueryOptions {
84
115
  /** Bound parameters. Values must be JSON scalars (string | number | boolean). */
85
116
  params?: QueryParams;
117
+ /**
118
+ * Time-travel: run the query against the graph as it existed at this WAL
119
+ * commit index (read-only). Valid values are `0` to `commit_seq − 1`
120
+ * inclusive; passing `commit_seq` itself yields a range error. Obtain a
121
+ * commit index from the `commit` field of a `nodeHistory`/`edgeHistory`
122
+ * entry, or compute `(await client.metrics()).commit_seq - 1` for the most
123
+ * recent committed state.
124
+ */
125
+ as_of?: number;
126
+ /**
127
+ * Node-key allow-list: restrict visible nodes to these keys. The query sees
128
+ * only nodes whose key is in this set. Makes the query read-only.
129
+ */
130
+ mask?: string[];
131
+ /**
132
+ * Namespace to restrict results to. Intersects with `mask` and any role.
133
+ * Makes the query read-only.
134
+ */
135
+ namespace?: string;
86
136
  }
87
137
 
88
138
  /**
@@ -127,8 +177,41 @@ export class MushroomClient {
127
177
  return this.token ? { Authorization: `Bearer ${this.token}` } : {};
128
178
  }
129
179
 
180
+ /**
181
+ * Validate a single URL path segment (a node key, edge type, or property
182
+ * field name that will be percent-encoded into a URL path).
183
+ *
184
+ * Rejects `.` and `..` because fetch normalises those path segments away
185
+ * (`/node/./edges` becomes `/node/edges`, silently querying the wrong route).
186
+ * Encoding them as `%2E` / `%2E%2E` does not help — fetch normalises those
187
+ * too. Also catches lone-surrogate strings, which would throw a raw
188
+ * `URIError` from `encodeURIComponent`; re-throws as {@link MushroomError}.
189
+ *
190
+ * @throws {@link MushroomError}
191
+ */
192
+ private checkPathSeg(seg: string): void {
193
+ if (seg === "." || seg === "..") {
194
+ throw new MushroomError(
195
+ `"${seg}" is not a valid path segment — fetch normalises it away`,
196
+ );
197
+ }
198
+ try {
199
+ encodeURIComponent(seg);
200
+ } catch {
201
+ // encodeURIComponent throws URIError for lone Unicode surrogates.
202
+ throw new MushroomError(
203
+ "key or field name contains an invalid Unicode character (lone surrogate)",
204
+ );
205
+ }
206
+ }
207
+
130
208
  /**
131
209
  * Execute a fetch and decode the JSON body.
210
+ *
211
+ * Reads the response as text first so that an empty or plain-text error body
212
+ * (e.g. HTTP 404 with `content-length: 0`, or a 408/413/415 plain-text body)
213
+ * produces `MushroomError("HTTP <status>")` instead of a raw `SyntaxError`.
214
+ *
132
215
  * Throws {@link MushroomError} on non-2xx responses.
133
216
  */
134
217
  private async fetchJson<T>(
@@ -144,8 +227,14 @@ export class MushroomClient {
144
227
  ...(init?.headers ?? {}),
145
228
  },
146
229
  });
230
+ const text = await resp.text();
147
231
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
- const body: any = await resp.json();
232
+ let body: any;
233
+ try {
234
+ body = text ? JSON.parse(text) : null;
235
+ } catch {
236
+ body = null;
237
+ }
149
238
  if (!resp.ok) {
150
239
  const detail: string =
151
240
  typeof body?.error === "string" ? body.error : `HTTP ${resp.status}`;
@@ -175,6 +264,9 @@ export class MushroomClient {
175
264
  body: JSON.stringify({
176
265
  cypher,
177
266
  ...(opts?.params ? { params: opts.params } : {}),
267
+ ...(opts?.as_of !== undefined ? { as_of: opts.as_of } : {}),
268
+ ...(opts?.mask ? { mask: opts.mask } : {}),
269
+ ...(opts?.namespace ? { namespace: opts.namespace } : {}),
178
270
  }),
179
271
  });
180
272
  }
@@ -282,6 +374,7 @@ export class MushroomClient {
282
374
  * @throws {@link MushroomError} on `KeyNotFound` (old key) or `DuplicateKey` (new key taken).
283
375
  */
284
376
  async renameNode(oldKey: string, newKey: string): Promise<void> {
377
+ this.checkPathSeg(oldKey);
285
378
  await this.fetchJson(`/nodes/${encodeURIComponent(oldKey)}/rename`, {
286
379
  method: "POST",
287
380
  body: JSON.stringify({ new_key: newKey }),
@@ -327,17 +420,24 @@ export class MushroomClient {
327
420
  * (unknown key). Other HTTP errors throw {@link MushroomError}.
328
421
  */
329
422
  async node(key: string): Promise<NodeInfo | null> {
423
+ this.checkPathSeg(key);
330
424
  const resp = await fetch(this.url(`/node/${encodeURIComponent(key)}`), {
331
425
  headers: {
332
426
  Accept: "application/json",
333
427
  ...this.authHeaders(),
334
428
  },
335
429
  });
336
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
337
- const body: any = await resp.json();
338
430
  if (resp.status === 404) {
339
431
  return null;
340
432
  }
433
+ const text = await resp.text();
434
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
435
+ let body: any;
436
+ try {
437
+ body = text ? JSON.parse(text) : null;
438
+ } catch {
439
+ body = null;
440
+ }
341
441
  if (!resp.ok) {
342
442
  const detail: string =
343
443
  typeof body?.error === "string" ? body.error : `HTTP ${resp.status}`;
@@ -356,6 +456,7 @@ export class MushroomClient {
356
456
  key: string,
357
457
  opts?: { depth?: number },
358
458
  ): Promise<Neighborhood> {
459
+ this.checkPathSeg(key);
359
460
  const qs = new URLSearchParams();
360
461
  if (opts?.depth !== undefined) {
361
462
  qs.set("depth", String(opts.depth));
@@ -367,6 +468,313 @@ export class MushroomClient {
367
468
  return this.fetchJson<Neighborhood>(path);
368
469
  }
369
470
 
471
+ /**
472
+ * Get server liveness.
473
+ *
474
+ * Wraps `GET /health`. An unauthenticated caller receives only `{ ok: true }`.
475
+ * A full-access token also receives `nodes`, `edges` and `addr`.
476
+ */
477
+ async health(): Promise<HealthReport> {
478
+ return this.fetchJson<HealthReport>("/health");
479
+ }
480
+
481
+ /**
482
+ * Get server-wide runtime metrics.
483
+ *
484
+ * Wraps `GET /metrics`. Returns node/edge counts, WAL size, RSS, uptime
485
+ * and a slow-query ring buffer. Requires a full-access token; role-bound
486
+ * tokens receive 403.
487
+ */
488
+ async metrics(): Promise<MetricsReport> {
489
+ return this.fetchJson<MetricsReport>("/metrics");
490
+ }
491
+
492
+ /**
493
+ * Vector kNN search.
494
+ *
495
+ * Wraps `POST /find_similar`. Returns up to `k` nodes whose stored value for
496
+ * `field` has cosine similarity ≥ `min` to the query `vector`. Results are
497
+ * sorted by similarity descending.
498
+ *
499
+ * @param opts Query options — `field` and `vector` are required.
500
+ * @returns `{ hits: [node_key, similarity][] }`
501
+ */
502
+ async findSimilar(opts: FindSimilarOptions): Promise<FindSimilarResult> {
503
+ return this.fetchJson<FindSimilarResult>("/find_similar", {
504
+ method: "POST",
505
+ body: JSON.stringify(opts),
506
+ });
507
+ }
508
+
509
+ /**
510
+ * Create a new node.
511
+ *
512
+ * Wraps `POST /nodes`. This is an **insert**, not an upsert. If a node with
513
+ * the same `key` already exists the server responds with 400
514
+ * "duplicate node key: <key>" and this method throws {@link MushroomError}.
515
+ *
516
+ * @param label Node label.
517
+ * @param key Node key — must be unique in the store.
518
+ * @param props Optional property map. Null values are silently dropped.
519
+ * @param namespace Optional namespace to assign the node to.
520
+ */
521
+ async createNode(
522
+ label: string,
523
+ key: string,
524
+ props?: Record<string, import("./types.js").CellValue>,
525
+ namespace?: string,
526
+ ): Promise<CreateNodeResult> {
527
+ return this.fetchJson<CreateNodeResult>("/nodes", {
528
+ method: "POST",
529
+ body: JSON.stringify({
530
+ label,
531
+ key,
532
+ ...(props ? { props } : {}),
533
+ ...(namespace ? { namespace } : {}),
534
+ }),
535
+ });
536
+ }
537
+
538
+ /**
539
+ * Delete a node by key.
540
+ *
541
+ * Wraps `DELETE /node/{key}`. The dense id is freed; all edges attached to
542
+ * this node are also removed. Throws {@link MushroomError} when the key does
543
+ * not exist.
544
+ *
545
+ * @param key The node key to delete.
546
+ */
547
+ async deleteNode(key: string): Promise<void> {
548
+ this.checkPathSeg(key);
549
+ await this.fetchJson(`/node/${encodeURIComponent(key)}`, { method: "DELETE" });
550
+ }
551
+
552
+ /**
553
+ * List all edges attached to a node.
554
+ *
555
+ * Wraps `GET /node/{key}/edges`. Returns both asserted and derived edges in
556
+ * both directions. Throws {@link MushroomError} (404) when the key does not
557
+ * exist.
558
+ *
559
+ * @param key The node key.
560
+ * @param opts Optional filters:
561
+ * - `mask` — node-key allow-list; only edges whose **other** endpoint is
562
+ * in the list are returned (`key` itself need not be). Must be non-empty
563
+ * (an empty array throws {@link MushroomError}). A key must not contain
564
+ * a comma, be empty, or have surrounding whitespace: the server splits
565
+ * on commas and trims (use a `query()` with `mask` for such keys).
566
+ * - `stub_hidden` — when `true` and `mask` is set, edges to masked-out
567
+ * endpoints are **included** rather than omitted; those endpoints are
568
+ * returned as `{ key: string; restricted: true }` stubs rather than
569
+ * plain key strings. Without a mask this option is silently ignored by
570
+ * the server. Note that `as_of` (time-travel) does not compose with
571
+ * `stub_hidden`; the server rejects that combination.
572
+ */
573
+ async nodeEdges(
574
+ key: string,
575
+ opts?: { mask?: string[]; stub_hidden?: boolean },
576
+ ): Promise<NodeEdgesResult> {
577
+ this.checkPathSeg(key);
578
+ const qs = new URLSearchParams();
579
+ if (opts?.mask !== undefined) {
580
+ if (opts.mask.length === 0) {
581
+ throw new MushroomError(
582
+ "nodeEdges mask must not be empty — an empty allow-list is ambiguous; " +
583
+ "omit mask to return all edges, or use query() with mask:[] to hide all nodes",
584
+ );
585
+ }
586
+ for (const k of opts.mask) {
587
+ if (k === "" || k !== k.trim()) {
588
+ throw new MushroomError(
589
+ `nodeEdges mask key ${JSON.stringify(k)} is empty or has surrounding ` +
590
+ "whitespace — the server trims each key, so it would match nothing or " +
591
+ "remove the mask altogether",
592
+ );
593
+ }
594
+ if (k.includes(",")) {
595
+ throw new MushroomError(
596
+ `nodeEdges mask key "${k}" contains a comma — the server splits on commas ` +
597
+ "and cannot express a key with one; use query() with mask for such keys",
598
+ );
599
+ }
600
+ }
601
+ qs.set("mask", opts.mask.join(","));
602
+ }
603
+ if (opts?.stub_hidden !== undefined) {
604
+ qs.set("stub_hidden", String(opts.stub_hidden));
605
+ }
606
+ const query = qs.toString();
607
+ const path = `/node/${encodeURIComponent(key)}/edges${query ? `?${query}` : ""}`;
608
+ return this.fetchJson<NodeEdgesResult>(path);
609
+ }
610
+
611
+ /**
612
+ * Get WAL change history for a node.
613
+ *
614
+ * Wraps `GET /node/{key}/history`. Returns the sequence of WAL entries that
615
+ * touched this node, oldest first. Throws {@link MushroomError} (404) when
616
+ * the key does not exist.
617
+ *
618
+ * @param key The node key.
619
+ */
620
+ async nodeHistory(key: string): Promise<NodeHistoryResult> {
621
+ this.checkPathSeg(key);
622
+ return this.fetchJson<NodeHistoryResult>(
623
+ `/node/${encodeURIComponent(key)}/history`,
624
+ );
625
+ }
626
+
627
+ /**
628
+ * Get edge lifecycle between two nodes.
629
+ *
630
+ * Wraps `GET /history/edge?a=&b=`. Returns every time an edge was added or
631
+ * retracted between `a` and `b` (in either direction, any edge type).
632
+ *
633
+ * @param a Key of the first node.
634
+ * @param b Key of the second node.
635
+ */
636
+ async edgeHistory(a: string, b: string): Promise<EdgeHistoryResult> {
637
+ const qs = new URLSearchParams({ a, b });
638
+ return this.fetchJson<EdgeHistoryResult>(`/history/edge?${qs.toString()}`);
639
+ }
640
+
641
+ /**
642
+ * Point-in-time edge check.
643
+ *
644
+ * Wraps `GET /history/was_linked?a=&b=&edge_type=&at_commit=`. Returns
645
+ * whether any edge of `edgeType` existed between `a` and `b` at the given
646
+ * WAL commit index. Throws {@link MushroomError} (400) when `atCommit` is
647
+ * outside the visible horizon.
648
+ *
649
+ * @param a Key of the first node.
650
+ * @param b Key of the second node.
651
+ * @param edgeType The edge type to check.
652
+ * @param atCommit WAL commit index (integer) or an RFC 3339 date string
653
+ * (e.g. `"2026-01-01T00:00:00Z"`); the server resolves the
654
+ * date to the nearest commit at or before that time.
655
+ */
656
+ async wasLinked(
657
+ a: string,
658
+ b: string,
659
+ edgeType: string,
660
+ atCommit: number | string,
661
+ ): Promise<WasLinkedResult> {
662
+ const qs = new URLSearchParams({
663
+ a,
664
+ b,
665
+ edge_type: edgeType,
666
+ at_commit: String(atCommit),
667
+ });
668
+ return this.fetchJson<WasLinkedResult>(
669
+ `/history/was_linked?${qs.toString()}`,
670
+ );
671
+ }
672
+
673
+ /**
674
+ * Create an explicit edge.
675
+ *
676
+ * Wraps `POST /edges`. Both endpoint nodes must already exist; use
677
+ * {@link upsertEdge} to auto-create missing endpoints.
678
+ *
679
+ * @param type Edge type string.
680
+ * @param src Source node key.
681
+ * @param dst Destination node key.
682
+ */
683
+ async createEdge(type: string, src: string, dst: string): Promise<void> {
684
+ await this.fetchJson("/edges", {
685
+ method: "POST",
686
+ body: JSON.stringify({ type, src, dst }),
687
+ });
688
+ }
689
+
690
+ /**
691
+ * Delete an explicit edge.
692
+ *
693
+ * Wraps `DELETE /edges/{etype}/{src}/{dst}`. Returns silently when the edge
694
+ * does not exist (idempotent — the server answers 200). Throws
695
+ * {@link MushroomError} only when an endpoint node is missing
696
+ * (400 "node key not found: <key>").
697
+ *
698
+ * @param etype Edge type string.
699
+ * @param src Source node key.
700
+ * @param dst Destination node key.
701
+ */
702
+ async deleteEdge(etype: string, src: string, dst: string): Promise<void> {
703
+ this.checkPathSeg(etype);
704
+ this.checkPathSeg(src);
705
+ this.checkPathSeg(dst);
706
+ await this.fetchJson(
707
+ `/edges/${encodeURIComponent(etype)}/${encodeURIComponent(src)}/${encodeURIComponent(dst)}`,
708
+ { method: "DELETE" },
709
+ );
710
+ }
711
+
712
+ /**
713
+ * Set a property on a node.
714
+ *
715
+ * Wraps `PUT /node/{key}/prop/{field}`. Creates the property if absent,
716
+ * replaces its value if present. The value must be a non-null scalar, array
717
+ * or object; a null value is rejected with 400.
718
+ *
719
+ * @param key Node key.
720
+ * @param field Property field name.
721
+ * @param value New property value (non-null).
722
+ */
723
+ async setNodeProp(
724
+ key: string,
725
+ field: string,
726
+ value: import("./types.js").CellValue,
727
+ ): Promise<void> {
728
+ this.checkPathSeg(key);
729
+ this.checkPathSeg(field);
730
+ await this.fetchJson(
731
+ `/node/${encodeURIComponent(key)}/prop/${encodeURIComponent(field)}`,
732
+ {
733
+ method: "PUT",
734
+ body: JSON.stringify({ value }),
735
+ },
736
+ );
737
+ }
738
+
739
+ /**
740
+ * Remove a property from a node.
741
+ *
742
+ * Wraps `DELETE /node/{key}/prop/{field}`. Succeeds even when the property
743
+ * does not exist (the server is idempotent on prop removal).
744
+ *
745
+ * @param key Node key.
746
+ * @param field Property field name to remove.
747
+ */
748
+ async removeNodeProp(key: string, field: string): Promise<void> {
749
+ this.checkPathSeg(key);
750
+ this.checkPathSeg(field);
751
+ await this.fetchJson(
752
+ `/node/${encodeURIComponent(key)}/prop/${encodeURIComponent(field)}`,
753
+ { method: "DELETE" },
754
+ );
755
+ }
756
+
757
+ /**
758
+ * Trigger a consistent backup of the database.
759
+ *
760
+ * Wraps `POST /backup`. The server holds a read lock for the duration of
761
+ * the file copies. `dest` is a path relative to the backup root
762
+ * (`MUSHROOMDB_BACKUP_DIR`, or the server's working directory); `..`
763
+ * segments are rejected. Requires a full-access token.
764
+ *
765
+ * When `verified` is false in the response, the server answered 500 and
766
+ * throws {@link MushroomError}; the backup was written but the post-copy
767
+ * verification failed.
768
+ *
769
+ * @param dest Destination directory name or relative path.
770
+ */
771
+ async backup(dest: string): Promise<BackupReport> {
772
+ return this.fetchJson<BackupReport>("/backup", {
773
+ method: "POST",
774
+ body: JSON.stringify({ dest }),
775
+ });
776
+ }
777
+
370
778
  // -------------------------------------------------------------------------
371
779
  // WebSocket
372
780
  // -------------------------------------------------------------------------
@@ -407,6 +815,50 @@ export class MushroomClient {
407
815
  }
408
816
  return wsSubscribe(url, opts, onEvent);
409
817
  }
818
+
819
+ /**
820
+ * Subscribe to the raw mutation event stream over WebSocket (`GET /watch`).
821
+ *
822
+ * The server sends `{"subscribed":true}` immediately after the upgrade; no
823
+ * client subscribe message is required. After that, {@link MutationEvent}
824
+ * frames arrive for every write to the store.
825
+ *
826
+ * Unlike `/subscribe`, the `/watch` stream carries every mutation (nodes,
827
+ * edges, rules, batches, ingests) without filtering.
828
+ *
829
+ * **No auto-reconnect in v1.** Reconnect manually when the connection drops.
830
+ *
831
+ * **Always await `handle.close()`** to avoid dangling handles in tests.
832
+ *
833
+ * **Node.js < 21**: pass `wsConstructor` — see {@link WatchOptions}.
834
+ *
835
+ * @example
836
+ * ```ts
837
+ * import WS from 'ws';
838
+ * const handle = await client.watch(
839
+ * { wsConstructor: WS as WsConstructor },
840
+ * (ev) => console.log(ev),
841
+ * );
842
+ * await handle.close();
843
+ * ```
844
+ */
845
+ watch(
846
+ opts: WatchOptions,
847
+ onEvent: (event: MutationEvent) => void,
848
+ ): Promise<WatchHandle> {
849
+ let url = this.wsUrl("/watch");
850
+ if (this.token) {
851
+ url += `?token=${encodeURIComponent(this.token)}`;
852
+ }
853
+ return wsWatch(url, opts, onEvent);
854
+ }
410
855
  }
411
856
 
412
- export type { SubscribeHandle, SubscribeOptions, WsConstructor };
857
+ export type {
858
+ MutationEvent,
859
+ SubscribeHandle,
860
+ SubscribeOptions,
861
+ WatchHandle,
862
+ WatchOptions,
863
+ WsConstructor,
864
+ };
package/src/index.ts CHANGED
@@ -8,16 +8,30 @@ export { MushroomClient, MushroomError } from "./client.js";
8
8
  export type {
9
9
  AlgoDir,
10
10
  AlgoReport,
11
+ BackupReport,
11
12
  CellValue,
12
13
  ClientOptions,
14
+ CreateNodeResult,
13
15
  DegreeConfig,
14
16
  DegreeReport,
17
+ EdgeHistoryEntry,
18
+ EdgeHistoryResult,
19
+ EdgeInfo,
15
20
  Explanation,
21
+ FindSimilarOptions,
22
+ FindSimilarResult,
23
+ HealthReport,
24
+ HistoryChange,
25
+ HistoryEntry,
16
26
  IngestEdge,
17
27
  IngestOptions,
18
28
  IngestReport,
19
29
  IngestRequest,
30
+ MetricsReport,
31
+ MutationEvent,
20
32
  Neighborhood,
33
+ NodeEdgesResult,
34
+ NodeHistoryResult,
21
35
  NodeInfo,
22
36
  PageRankConfig,
23
37
  PageRankReport,
@@ -30,8 +44,10 @@ export type {
30
44
  RulePredicate,
31
45
  RuleStats,
32
46
  RuleSuggestion,
47
+ SlowQueryEntry,
33
48
  Stats,
34
49
  SuggestReport,
50
+ WasLinkedResult,
35
51
  WccConfig,
36
52
  WccReport,
37
53
  } from "./client.js";
@@ -42,6 +58,8 @@ export type {
42
58
  export type {
43
59
  SubscribeHandle,
44
60
  SubscribeOptions,
61
+ WatchHandle,
62
+ WatchOptions,
45
63
  WsConstructor,
46
64
  WsLike,
47
65
  } from "./ws.js";