@wiscale/velesdb-sdk 1.5.1 → 1.7.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/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AgentMemoryClient: () => AgentMemoryClient,
33
34
  BackpressureError: () => BackpressureError,
34
35
  ConnectionError: () => ConnectionError,
35
36
  NotFoundError: () => NotFoundError,
@@ -77,6 +78,38 @@ var BackpressureError = class extends VelesDBError {
77
78
  }
78
79
  };
79
80
 
81
+ // src/backends/shared.ts
82
+ function throwOnError(response, resourceLabel) {
83
+ if (!response.error) {
84
+ return;
85
+ }
86
+ if (response.error.code === "NOT_FOUND" && resourceLabel !== void 0) {
87
+ throw new NotFoundError(resourceLabel);
88
+ }
89
+ throw new VelesDBError(response.error.message, response.error.code);
90
+ }
91
+ function returnNullOnNotFound(response) {
92
+ if (!response.error) {
93
+ return void 0;
94
+ }
95
+ if (response.error.code === "NOT_FOUND") {
96
+ return true;
97
+ }
98
+ throw new VelesDBError(response.error.message, response.error.code);
99
+ }
100
+ function collectionPath(collection) {
101
+ return `/collections/${encodeURIComponent(collection)}`;
102
+ }
103
+ function toNumberArray(v) {
104
+ return v instanceof Float32Array ? Array.from(v) : v;
105
+ }
106
+ function wasmNotSupported(feature) {
107
+ throw new VelesDBError(
108
+ `${feature}: not supported in WASM backend. Use REST backend.`,
109
+ "NOT_SUPPORTED"
110
+ );
111
+ }
112
+
80
113
  // src/backends/wasm.ts
81
114
  var WasmBackend = class {
82
115
  constructor() {
@@ -243,13 +276,13 @@ var WasmBackend = class {
243
276
  }
244
277
  }
245
278
  }
246
- async search(collectionName, query, options) {
279
+ async search(collectionName, query2, options) {
247
280
  this.ensureInitialized();
248
281
  const collection = this.collections.get(collectionName);
249
282
  if (!collection) {
250
283
  throw new NotFoundError(`Collection '${collectionName}'`);
251
284
  }
252
- const queryVector = query instanceof Float32Array ? query : new Float32Array(query);
285
+ const queryVector = query2 instanceof Float32Array ? query2 : new Float32Array(query2);
253
286
  if (queryVector.length !== collection.config.dimension) {
254
287
  throw new VelesDBError(
255
288
  `Query dimension mismatch: expected ${collection.config.dimension}, got ${queryVector.length}`,
@@ -406,14 +439,7 @@ var WasmBackend = class {
406
439
  k
407
440
  );
408
441
  return {
409
- results: raw.map((r) => ({
410
- nodeId: r.nodeId ?? r.node_id,
411
- vectorScore: r.vectorScore ?? r.vector_score ?? null,
412
- graphScore: r.graphScore ?? r.graph_score ?? null,
413
- fusedScore: r.fusedScore ?? r.fused_score ?? 0,
414
- bindings: r.bindings ?? {},
415
- columnData: r.columnData ?? r.column_data ?? null
416
- })),
442
+ results: raw,
417
443
  stats: {
418
444
  executionTimeMs: 0,
419
445
  strategy: "wasm-query",
@@ -462,17 +488,11 @@ var WasmBackend = class {
462
488
  }
463
489
  async queryExplain(_queryString, _params) {
464
490
  this.ensureInitialized();
465
- throw new VelesDBError(
466
- "Query explain is not supported in WASM backend. Use REST backend for EXPLAIN support.",
467
- "NOT_SUPPORTED"
468
- );
491
+ wasmNotSupported("Query explain");
469
492
  }
470
493
  async collectionSanity(_collection) {
471
494
  this.ensureInitialized();
472
- throw new VelesDBError(
473
- "Collection sanity endpoint is not supported in WASM backend. Use REST backend for diagnostics.",
474
- "NOT_SUPPORTED"
475
- );
495
+ wasmNotSupported("Collection sanity endpoint");
476
496
  }
477
497
  async isEmpty(collectionName) {
478
498
  this.ensureInitialized();
@@ -552,51 +572,715 @@ var WasmBackend = class {
552
572
  // ========================================================================
553
573
  async addEdge(_collection, _edge) {
554
574
  this.ensureInitialized();
555
- throw new VelesDBError(
556
- "Knowledge Graph operations are not supported in WASM backend. Use REST backend for graph features.",
557
- "NOT_SUPPORTED"
558
- );
575
+ wasmNotSupported("Knowledge Graph operations");
559
576
  }
560
577
  async getEdges(_collection, _options) {
561
578
  this.ensureInitialized();
562
- throw new VelesDBError(
563
- "Knowledge Graph operations are not supported in WASM backend. Use REST backend for graph features.",
564
- "NOT_SUPPORTED"
565
- );
579
+ wasmNotSupported("Knowledge Graph operations");
566
580
  }
567
581
  async traverseGraph(_collection, _request) {
568
582
  this.ensureInitialized();
569
- throw new VelesDBError(
570
- "Graph traversal is not supported in WASM backend. Use REST backend for graph features.",
571
- "NOT_SUPPORTED"
572
- );
583
+ wasmNotSupported("Graph traversal");
573
584
  }
574
585
  async getNodeDegree(_collection, _nodeId) {
575
586
  this.ensureInitialized();
576
- throw new VelesDBError(
577
- "Graph degree query is not supported in WASM backend. Use REST backend for graph features.",
578
- "NOT_SUPPORTED"
579
- );
587
+ wasmNotSupported("Graph degree query");
580
588
  }
581
589
  // ========================================================================
582
590
  // Sparse / PQ / Streaming (v1.5)
583
591
  // ========================================================================
584
592
  async trainPq(_collection, _options) {
585
593
  this.ensureInitialized();
586
- throw new VelesDBError(
587
- "PQ training is not available in WASM mode. Use REST backend for PQ training.",
588
- "NOT_SUPPORTED"
589
- );
594
+ wasmNotSupported("PQ training");
590
595
  }
591
596
  async streamInsert(_collection, _docs) {
592
597
  this.ensureInitialized();
593
- throw new VelesDBError(
594
- "Streaming insert is not available in WASM mode. Use REST backend for streaming.",
595
- "NOT_SUPPORTED"
596
- );
598
+ wasmNotSupported("Streaming insert");
599
+ }
600
+ // ========================================================================
601
+ // Graph Collection / Stats / Agent Memory (Phase 8) - WASM stubs
602
+ // ========================================================================
603
+ async createGraphCollection(_name, _config) {
604
+ this.ensureInitialized();
605
+ wasmNotSupported("Graph collections");
606
+ }
607
+ async getCollectionStats(_collection) {
608
+ this.ensureInitialized();
609
+ wasmNotSupported("Collection stats");
610
+ }
611
+ async analyzeCollection(_collection) {
612
+ this.ensureInitialized();
613
+ wasmNotSupported("Collection analyze");
614
+ }
615
+ async getCollectionConfig(_collection) {
616
+ this.ensureInitialized();
617
+ wasmNotSupported("Collection config");
618
+ }
619
+ async searchIds(_collection, _query, _options) {
620
+ this.ensureInitialized();
621
+ wasmNotSupported("searchIds");
622
+ }
623
+ async storeSemanticFact(_collection, _entry) {
624
+ this.ensureInitialized();
625
+ wasmNotSupported("Agent memory");
626
+ }
627
+ async searchSemanticMemory(_collection, _embedding, _k) {
628
+ this.ensureInitialized();
629
+ wasmNotSupported("Agent memory");
630
+ }
631
+ async recordEpisodicEvent(_collection, _event) {
632
+ this.ensureInitialized();
633
+ wasmNotSupported("Agent memory");
634
+ }
635
+ async recallEpisodicEvents(_collection, _embedding, _k) {
636
+ this.ensureInitialized();
637
+ wasmNotSupported("Agent memory");
638
+ }
639
+ async storeProceduralPattern(_collection, _pattern) {
640
+ this.ensureInitialized();
641
+ wasmNotSupported("Agent memory");
642
+ }
643
+ async matchProceduralPatterns(_collection, _embedding, _k) {
644
+ this.ensureInitialized();
645
+ wasmNotSupported("Agent memory");
597
646
  }
598
647
  };
599
648
 
649
+ // src/backends/agent-memory-backend.ts
650
+ var _idCounter = 0;
651
+ var _lastTimestamp = 0;
652
+ function generateUniqueId() {
653
+ let now = Date.now();
654
+ if (now <= _lastTimestamp) {
655
+ _idCounter++;
656
+ if (_idCounter >= 1e3) {
657
+ _lastTimestamp++;
658
+ _idCounter = 0;
659
+ }
660
+ } else {
661
+ _lastTimestamp = now;
662
+ _idCounter = 0;
663
+ }
664
+ return _lastTimestamp * 1e3 + _idCounter;
665
+ }
666
+ async function storeSemanticFact(transport, collection, entry) {
667
+ const response = await transport.requestJson(
668
+ "POST",
669
+ `${collectionPath(collection)}/points`,
670
+ {
671
+ points: [{
672
+ id: entry.id,
673
+ vector: entry.embedding,
674
+ payload: {
675
+ _memory_type: "semantic",
676
+ text: entry.text,
677
+ ...entry.metadata
678
+ }
679
+ }]
680
+ }
681
+ );
682
+ throwOnError(response);
683
+ }
684
+ async function searchSemanticMemory(transport, collection, embedding, k = 5) {
685
+ return transport.searchVectors(collection, embedding, k, { _memory_type: "semantic" });
686
+ }
687
+ async function recordEpisodicEvent(transport, collection, event) {
688
+ const id = generateUniqueId();
689
+ const response = await transport.requestJson(
690
+ "POST",
691
+ `${collectionPath(collection)}/points`,
692
+ {
693
+ points: [{
694
+ id,
695
+ vector: event.embedding,
696
+ payload: {
697
+ _memory_type: "episodic",
698
+ event_type: event.eventType,
699
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
700
+ ...event.data,
701
+ ...event.metadata
702
+ }
703
+ }]
704
+ }
705
+ );
706
+ throwOnError(response);
707
+ }
708
+ async function recallEpisodicEvents(transport, collection, embedding, k = 5) {
709
+ return transport.searchVectors(collection, embedding, k, { _memory_type: "episodic" });
710
+ }
711
+ async function storeProceduralPattern(transport, collection, pattern) {
712
+ const id = generateUniqueId();
713
+ const response = await transport.requestJson(
714
+ "POST",
715
+ `${collectionPath(collection)}/points`,
716
+ {
717
+ points: [{
718
+ id,
719
+ payload: {
720
+ _memory_type: "procedural",
721
+ name: pattern.name,
722
+ steps: pattern.steps,
723
+ ...pattern.metadata
724
+ }
725
+ }]
726
+ }
727
+ );
728
+ throwOnError(response);
729
+ }
730
+ async function matchProceduralPatterns(transport, collection, embedding, k = 5) {
731
+ return transport.searchVectors(collection, embedding, k, { _memory_type: "procedural" });
732
+ }
733
+
734
+ // src/backends/search-backend.ts
735
+ async function search(transport, collection, query2, options) {
736
+ const queryVector = toNumberArray(query2);
737
+ const body = {
738
+ vector: queryVector,
739
+ top_k: options?.k ?? 10,
740
+ filter: options?.filter,
741
+ include_vectors: options?.includeVectors ?? false
742
+ };
743
+ if (options?.sparseVector) {
744
+ body.sparse_vector = transport.sparseToRest(options.sparseVector);
745
+ }
746
+ const response = await transport.requestJson(
747
+ "POST",
748
+ `${collectionPath(collection)}/search`,
749
+ body
750
+ );
751
+ throwOnError(response, `Collection '${collection}'`);
752
+ return response.data?.results ?? [];
753
+ }
754
+ async function searchBatch(transport, collection, searches) {
755
+ const formattedSearches = searches.map((s) => ({
756
+ vector: toNumberArray(s.vector),
757
+ top_k: s.k ?? 10,
758
+ filter: s.filter
759
+ }));
760
+ const response = await transport.requestJson(
761
+ "POST",
762
+ `${collectionPath(collection)}/search/batch`,
763
+ { searches: formattedSearches }
764
+ );
765
+ throwOnError(response, `Collection '${collection}'`);
766
+ return response.data?.results.map((r) => r.results) ?? [];
767
+ }
768
+ async function textSearch(transport, collection, query2, options) {
769
+ const response = await transport.requestJson(
770
+ "POST",
771
+ `${collectionPath(collection)}/search/text`,
772
+ {
773
+ query: query2,
774
+ top_k: options?.k ?? 10,
775
+ filter: options?.filter
776
+ }
777
+ );
778
+ throwOnError(response, `Collection '${collection}'`);
779
+ return response.data?.results ?? [];
780
+ }
781
+ async function hybridSearch(transport, collection, vector, textQuery, options) {
782
+ const queryVector = toNumberArray(vector);
783
+ const response = await transport.requestJson(
784
+ "POST",
785
+ `${collectionPath(collection)}/search/hybrid`,
786
+ {
787
+ vector: queryVector,
788
+ query: textQuery,
789
+ top_k: options?.k ?? 10,
790
+ vector_weight: options?.vectorWeight ?? 0.5,
791
+ filter: options?.filter
792
+ }
793
+ );
794
+ throwOnError(response, `Collection '${collection}'`);
795
+ return response.data?.results ?? [];
796
+ }
797
+ async function multiQuerySearch(transport, collection, vectors, options) {
798
+ const formattedVectors = vectors.map(toNumberArray);
799
+ const response = await transport.requestJson(
800
+ "POST",
801
+ `${collectionPath(collection)}/search/multi`,
802
+ {
803
+ vectors: formattedVectors,
804
+ top_k: options?.k ?? 10,
805
+ strategy: options?.fusion ?? "rrf",
806
+ rrf_k: options?.fusionParams?.k ?? 60,
807
+ avg_weight: options?.fusionParams?.avgWeight,
808
+ max_weight: options?.fusionParams?.maxWeight,
809
+ hit_weight: options?.fusionParams?.hitWeight,
810
+ filter: options?.filter
811
+ }
812
+ );
813
+ throwOnError(response, `Collection '${collection}'`);
814
+ return response.data?.results ?? [];
815
+ }
816
+ async function searchIds(transport, collection, query2, options) {
817
+ const queryVector = toNumberArray(query2);
818
+ const response = await transport.requestJson(
819
+ "POST",
820
+ `${collectionPath(collection)}/search/ids`,
821
+ {
822
+ vector: queryVector,
823
+ top_k: options?.k ?? 10,
824
+ filter: options?.filter
825
+ }
826
+ );
827
+ throwOnError(response, `Collection '${collection}'`);
828
+ return response.data?.results ?? [];
829
+ }
830
+
831
+ // src/backends/graph-backend.ts
832
+ async function addEdge(transport, collection, edge) {
833
+ const response = await transport.requestJson(
834
+ "POST",
835
+ `${collectionPath(collection)}/graph/edges`,
836
+ {
837
+ id: edge.id,
838
+ source: edge.source,
839
+ target: edge.target,
840
+ label: edge.label,
841
+ properties: edge.properties ?? {}
842
+ }
843
+ );
844
+ throwOnError(response, `Collection '${collection}'`);
845
+ }
846
+ async function getEdges(transport, collection, options) {
847
+ const queryParams = options?.label ? `?label=${encodeURIComponent(options.label)}` : "";
848
+ const response = await transport.requestJson(
849
+ "GET",
850
+ `${collectionPath(collection)}/graph/edges${queryParams}`
851
+ );
852
+ throwOnError(response, `Collection '${collection}'`);
853
+ return response.data?.edges ?? [];
854
+ }
855
+ async function traverseGraph(transport, collection, request) {
856
+ const response = await transport.requestJson(
857
+ "POST",
858
+ `${collectionPath(collection)}/graph/traverse`,
859
+ {
860
+ source: request.source,
861
+ strategy: request.strategy ?? "bfs",
862
+ max_depth: request.maxDepth ?? 3,
863
+ limit: request.limit ?? 100,
864
+ cursor: request.cursor,
865
+ rel_types: request.relTypes ?? []
866
+ }
867
+ );
868
+ throwOnError(response, `Collection '${collection}'`);
869
+ const data = response.data;
870
+ return {
871
+ results: data.results.map((r) => ({
872
+ targetId: r.target_id,
873
+ depth: r.depth,
874
+ path: r.path
875
+ })),
876
+ nextCursor: data.next_cursor ?? void 0,
877
+ hasMore: data.has_more,
878
+ stats: {
879
+ visited: data.stats.visited,
880
+ depthReached: data.stats.depth_reached
881
+ }
882
+ };
883
+ }
884
+ async function getNodeDegree(transport, collection, nodeId) {
885
+ const response = await transport.requestJson(
886
+ "GET",
887
+ `${collectionPath(collection)}/graph/nodes/${nodeId}/degree`
888
+ );
889
+ throwOnError(response, `Collection '${collection}'`);
890
+ return {
891
+ inDegree: response.data?.in_degree ?? 0,
892
+ outDegree: response.data?.out_degree ?? 0
893
+ };
894
+ }
895
+ async function createGraphCollection(transport, name, config) {
896
+ const response = await transport.requestJson("POST", "/collections", {
897
+ name,
898
+ collection_type: "graph",
899
+ dimension: config?.dimension,
900
+ metric: config?.metric ?? "cosine",
901
+ schema_mode: config?.schemaMode ?? "schemaless"
902
+ });
903
+ throwOnError(response);
904
+ }
905
+
906
+ // src/backends/query-backend.ts
907
+ function isLikelyAggregationQuery(queryString) {
908
+ return /\bGROUP\s+BY\b|\bHAVING\b|\bCOUNT\s*\(|\bSUM\s*\(|\bAVG\s*\(|\bMIN\s*\(|\bMAX\s*\(/i.test(
909
+ queryString
910
+ );
911
+ }
912
+ async function query(transport, collection, queryString, params, options) {
913
+ const endpoint = isLikelyAggregationQuery(queryString) ? "/aggregate" : "/query";
914
+ const response = await transport.requestJson(
915
+ "POST",
916
+ endpoint,
917
+ {
918
+ query: queryString,
919
+ params: params ?? {},
920
+ collection,
921
+ timeout_ms: options?.timeoutMs,
922
+ stream: options?.stream ?? false
923
+ }
924
+ );
925
+ throwOnError(response, `Collection '${collection}'`);
926
+ const rawData = response.data;
927
+ if (rawData && Object.prototype.hasOwnProperty.call(rawData, "result")) {
928
+ return {
929
+ result: rawData.result,
930
+ stats: {
931
+ executionTimeMs: rawData.timing_ms ?? 0,
932
+ strategy: "aggregation",
933
+ scannedNodes: 0
934
+ }
935
+ };
936
+ }
937
+ return {
938
+ results: rawData?.results ?? [],
939
+ stats: {
940
+ executionTimeMs: rawData?.timing_ms ?? 0,
941
+ strategy: "select",
942
+ scannedNodes: rawData?.rows_returned ?? 0
943
+ }
944
+ };
945
+ }
946
+ async function queryExplain(transport, queryString, params) {
947
+ const response = await transport.requestJson(
948
+ "POST",
949
+ "/query/explain",
950
+ {
951
+ query: queryString,
952
+ params: params ?? {}
953
+ }
954
+ );
955
+ throwOnError(response);
956
+ const data = response.data;
957
+ return {
958
+ query: data.query,
959
+ queryType: data.query_type,
960
+ collection: data.collection,
961
+ plan: data.plan.map((step) => ({
962
+ step: step.step,
963
+ operation: step.operation,
964
+ description: step.description,
965
+ estimatedRows: step.estimated_rows
966
+ })),
967
+ estimatedCost: {
968
+ usesIndex: data.estimated_cost.uses_index,
969
+ indexName: data.estimated_cost.index_name,
970
+ selectivity: data.estimated_cost.selectivity,
971
+ complexity: data.estimated_cost.complexity
972
+ },
973
+ features: {
974
+ hasVectorSearch: data.features.has_vector_search,
975
+ hasFilter: data.features.has_filter,
976
+ hasOrderBy: data.features.has_order_by,
977
+ hasGroupBy: data.features.has_group_by,
978
+ hasAggregation: data.features.has_aggregation,
979
+ hasJoin: data.features.has_join,
980
+ hasFusion: data.features.has_fusion,
981
+ limit: data.features.limit,
982
+ offset: data.features.offset
983
+ }
984
+ };
985
+ }
986
+ async function collectionSanity(transport, collection) {
987
+ const response = await transport.requestJson(
988
+ "GET",
989
+ `${collectionPath(collection)}/sanity`
990
+ );
991
+ throwOnError(response, `Collection '${collection}'`);
992
+ const data = response.data;
993
+ return {
994
+ collection: data.collection,
995
+ dimension: data.dimension,
996
+ metric: data.metric,
997
+ pointCount: data.point_count,
998
+ isEmpty: data.is_empty,
999
+ checks: {
1000
+ hasVectors: data.checks.has_vectors,
1001
+ searchReady: data.checks.search_ready,
1002
+ dimensionConfigured: data.checks.dimension_configured
1003
+ },
1004
+ diagnostics: {
1005
+ searchRequestsTotal: data.diagnostics.search_requests_total,
1006
+ dimensionMismatchTotal: data.diagnostics.dimension_mismatch_total,
1007
+ emptySearchResultsTotal: data.diagnostics.empty_search_results_total,
1008
+ filterParseErrorsTotal: data.diagnostics.filter_parse_errors_total
1009
+ },
1010
+ hints: data.hints ?? []
1011
+ };
1012
+ }
1013
+
1014
+ // src/backends/admin-backend.ts
1015
+ function mapStatsResponse(data) {
1016
+ return {
1017
+ totalPoints: data.total_points,
1018
+ totalSizeBytes: data.total_size_bytes,
1019
+ rowCount: data.row_count,
1020
+ deletedCount: data.deleted_count,
1021
+ avgRowSizeBytes: data.avg_row_size_bytes,
1022
+ payloadSizeBytes: data.payload_size_bytes,
1023
+ lastAnalyzedEpochMs: data.last_analyzed_epoch_ms
1024
+ };
1025
+ }
1026
+ async function getCollectionStats(transport, collection) {
1027
+ const response = await transport.requestJson(
1028
+ "GET",
1029
+ `${collectionPath(collection)}/stats`
1030
+ );
1031
+ if (returnNullOnNotFound(response)) {
1032
+ return null;
1033
+ }
1034
+ return mapStatsResponse(response.data);
1035
+ }
1036
+ async function analyzeCollection(transport, collection) {
1037
+ const response = await transport.requestJson(
1038
+ "POST",
1039
+ `${collectionPath(collection)}/analyze`
1040
+ );
1041
+ throwOnError(response, `Collection '${collection}'`);
1042
+ return mapStatsResponse(response.data);
1043
+ }
1044
+ async function getCollectionConfig(transport, collection) {
1045
+ const response = await transport.requestJson("GET", `${collectionPath(collection)}/config`);
1046
+ throwOnError(response, `Collection '${collection}'`);
1047
+ const data = response.data;
1048
+ return {
1049
+ name: data.name,
1050
+ dimension: data.dimension,
1051
+ metric: data.metric,
1052
+ storageMode: data.storage_mode,
1053
+ pointCount: data.point_count,
1054
+ metadataOnly: data.metadata_only,
1055
+ graphSchema: data.graph_schema,
1056
+ embeddingDimension: data.embedding_dimension
1057
+ };
1058
+ }
1059
+
1060
+ // src/backends/index-backend.ts
1061
+ async function createIndex(transport, collection, options) {
1062
+ const response = await transport.requestJson(
1063
+ "POST",
1064
+ `${collectionPath(collection)}/indexes`,
1065
+ {
1066
+ label: options.label,
1067
+ property: options.property,
1068
+ index_type: options.indexType ?? "hash"
1069
+ }
1070
+ );
1071
+ throwOnError(response, `Collection '${collection}'`);
1072
+ }
1073
+ async function listIndexes(transport, collection) {
1074
+ const response = await transport.requestJson(
1075
+ "GET",
1076
+ `${collectionPath(collection)}/indexes`
1077
+ );
1078
+ throwOnError(response, `Collection '${collection}'`);
1079
+ return (response.data?.indexes ?? []).map((idx) => ({
1080
+ label: idx.label,
1081
+ property: idx.property,
1082
+ indexType: idx.index_type,
1083
+ cardinality: idx.cardinality,
1084
+ memoryBytes: idx.memory_bytes
1085
+ }));
1086
+ }
1087
+ async function hasIndex(transport, collection, label, property) {
1088
+ const indexes = await listIndexes(transport, collection);
1089
+ return indexes.some((idx) => idx.label === label && idx.property === property);
1090
+ }
1091
+ async function dropIndex(transport, collection, label, property) {
1092
+ const response = await transport.requestJson(
1093
+ "DELETE",
1094
+ `${collectionPath(collection)}/indexes/${encodeURIComponent(label)}/${encodeURIComponent(property)}`
1095
+ );
1096
+ if (returnNullOnNotFound(response)) {
1097
+ return false;
1098
+ }
1099
+ return response.data?.dropped ?? true;
1100
+ }
1101
+
1102
+ // src/backends/streaming-backend.ts
1103
+ async function trainPq(transport, collection, options) {
1104
+ const m = options?.m ?? 8;
1105
+ const k = options?.k ?? 256;
1106
+ const withClause = options?.opq ? `WITH (m=${m}, k=${k}, opq=true)` : `WITH (m=${m}, k=${k})`;
1107
+ const queryString = `TRAIN QUANTIZER ON ${collection} ${withClause}`;
1108
+ const response = await transport.requestJson(
1109
+ "POST",
1110
+ "/query",
1111
+ { query: queryString }
1112
+ );
1113
+ throwOnError(response);
1114
+ return response.data?.message ?? "PQ training initiated";
1115
+ }
1116
+ async function streamInsert(transport, collection, docs) {
1117
+ for (const doc of docs) {
1118
+ const restId = transport.parseRestPointId(doc.id);
1119
+ const vector = toNumberArray(doc.vector);
1120
+ const body = {
1121
+ id: restId,
1122
+ vector,
1123
+ payload: doc.payload
1124
+ };
1125
+ if (doc.sparseVector) {
1126
+ body.sparse_vector = transport.sparseVectorToRestFormat(doc.sparseVector);
1127
+ }
1128
+ const url = `${transport.baseUrl}${collectionPath(collection)}/stream/insert`;
1129
+ const headers = {
1130
+ "Content-Type": "application/json"
1131
+ };
1132
+ if (transport.apiKey) {
1133
+ headers["Authorization"] = `Bearer ${transport.apiKey}`;
1134
+ }
1135
+ const controller = new AbortController();
1136
+ const timeoutId = setTimeout(() => controller.abort(), transport.timeout);
1137
+ try {
1138
+ const response = await fetch(url, {
1139
+ method: "POST",
1140
+ headers,
1141
+ body: JSON.stringify(body),
1142
+ signal: controller.signal
1143
+ });
1144
+ clearTimeout(timeoutId);
1145
+ if (response.status === 429) {
1146
+ throw new BackpressureError();
1147
+ }
1148
+ if (!response.ok && response.status !== 202) {
1149
+ const data = await response.json().catch(() => ({}));
1150
+ const errorPayload = transport.extractErrorPayload(data);
1151
+ throw new VelesDBError(
1152
+ errorPayload.message ?? `HTTP ${response.status}`,
1153
+ errorPayload.code ?? transport.mapStatusToErrorCode(response.status)
1154
+ );
1155
+ }
1156
+ } catch (error) {
1157
+ clearTimeout(timeoutId);
1158
+ if (error instanceof BackpressureError || error instanceof VelesDBError) {
1159
+ throw error;
1160
+ }
1161
+ if (error instanceof Error && error.name === "AbortError") {
1162
+ throw new ConnectionError("Request timeout");
1163
+ }
1164
+ throw new ConnectionError(
1165
+ `Stream insert failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1166
+ error instanceof Error ? error : void 0
1167
+ );
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ // src/backends/crud-backend.ts
1173
+ function parseRestPointId(id) {
1174
+ if (typeof id !== "number" || !Number.isFinite(id) || id < 0 || !Number.isInteger(id) || id > Number.MAX_SAFE_INTEGER) {
1175
+ throw new ValidationError(
1176
+ `REST backend requires numeric u64-compatible IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER}). Received: ${String(id)}`
1177
+ );
1178
+ }
1179
+ return id;
1180
+ }
1181
+ function sparseVectorToRestFormat(sv) {
1182
+ const result = {};
1183
+ for (const [k, v] of Object.entries(sv)) {
1184
+ result[String(k)] = v;
1185
+ }
1186
+ return result;
1187
+ }
1188
+ async function createCollection(transport, name, config) {
1189
+ const response = await transport.requestJson("POST", "/collections", {
1190
+ name,
1191
+ dimension: config.dimension,
1192
+ metric: config.metric ?? "cosine",
1193
+ storage_mode: config.storageMode ?? "full",
1194
+ collection_type: config.collectionType ?? "vector",
1195
+ description: config.description,
1196
+ hnsw_m: config.hnsw?.m,
1197
+ hnsw_ef_construction: config.hnsw?.efConstruction
1198
+ });
1199
+ throwOnError(response);
1200
+ }
1201
+ async function deleteCollection(transport, name) {
1202
+ const response = await transport.requestJson(
1203
+ "DELETE",
1204
+ collectionPath(name)
1205
+ );
1206
+ throwOnError(response, `Collection '${name}'`);
1207
+ }
1208
+ async function getCollection(transport, name) {
1209
+ const response = await transport.requestJson(
1210
+ "GET",
1211
+ collectionPath(name)
1212
+ );
1213
+ if (returnNullOnNotFound(response)) {
1214
+ return null;
1215
+ }
1216
+ return response.data ?? null;
1217
+ }
1218
+ async function listCollections(transport) {
1219
+ const response = await transport.requestJson("GET", "/collections");
1220
+ throwOnError(response);
1221
+ return response.data ?? [];
1222
+ }
1223
+ async function insert(transport, collection, doc) {
1224
+ const restId = parseRestPointId(doc.id);
1225
+ const vector = toNumberArray(doc.vector);
1226
+ const response = await transport.requestJson(
1227
+ "POST",
1228
+ `${collectionPath(collection)}/points`,
1229
+ { points: [{ id: restId, vector, payload: doc.payload }] }
1230
+ );
1231
+ throwOnError(response, `Collection '${collection}'`);
1232
+ }
1233
+ async function insertBatch(transport, collection, docs) {
1234
+ const vectors = docs.map((doc) => ({
1235
+ id: parseRestPointId(doc.id),
1236
+ vector: toNumberArray(doc.vector),
1237
+ payload: doc.payload
1238
+ }));
1239
+ const response = await transport.requestJson(
1240
+ "POST",
1241
+ `${collectionPath(collection)}/points`,
1242
+ { points: vectors }
1243
+ );
1244
+ throwOnError(response, `Collection '${collection}'`);
1245
+ }
1246
+ async function deletePoint(transport, collection, id) {
1247
+ const restId = parseRestPointId(id);
1248
+ const response = await transport.requestJson(
1249
+ "DELETE",
1250
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(restId))}`
1251
+ );
1252
+ if (returnNullOnNotFound(response)) {
1253
+ return false;
1254
+ }
1255
+ return response.data?.deleted ?? false;
1256
+ }
1257
+ async function get(transport, collection, id) {
1258
+ const restId = parseRestPointId(id);
1259
+ const response = await transport.requestJson(
1260
+ "GET",
1261
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(restId))}`
1262
+ );
1263
+ if (returnNullOnNotFound(response)) {
1264
+ return null;
1265
+ }
1266
+ return response.data ?? null;
1267
+ }
1268
+ async function isEmpty(transport, collection) {
1269
+ const response = await transport.requestJson(
1270
+ "GET",
1271
+ `${collectionPath(collection)}/empty`
1272
+ );
1273
+ throwOnError(response, `Collection '${collection}'`);
1274
+ return response.data?.is_empty ?? true;
1275
+ }
1276
+ async function flush(transport, collection) {
1277
+ const response = await transport.requestJson(
1278
+ "POST",
1279
+ `${collectionPath(collection)}/flush`
1280
+ );
1281
+ throwOnError(response, `Collection '${collection}'`);
1282
+ }
1283
+
600
1284
  // src/backends/rest.ts
601
1285
  var RestBackend = class {
602
1286
  constructor(url, apiKey, timeout = 3e4) {
@@ -664,10 +1348,6 @@ var RestBackend = class {
664
1348
  const message = typeof messageField === "string" ? messageField : void 0;
665
1349
  return { code, message };
666
1350
  }
667
- /**
668
- * Parse node ID safely to handle u64 values above Number.MAX_SAFE_INTEGER.
669
- * Returns bigint for large values, number for safe values.
670
- */
671
1351
  parseNodeId(value) {
672
1352
  if (value === null || value === void 0) {
673
1353
  return 0;
@@ -677,37 +1357,16 @@ var RestBackend = class {
677
1357
  }
678
1358
  if (typeof value === "string") {
679
1359
  const num = Number(value);
680
- if (num > Number.MAX_SAFE_INTEGER) {
681
- return BigInt(value);
682
- }
683
- return num;
1360
+ return num > Number.MAX_SAFE_INTEGER ? BigInt(value) : num;
684
1361
  }
685
1362
  if (typeof value === "number") {
686
- if (value > Number.MAX_SAFE_INTEGER) {
687
- return value;
688
- }
689
1363
  return value;
690
1364
  }
691
1365
  return 0;
692
1366
  }
693
- parseRestPointId(id) {
694
- if (typeof id !== "number" || !Number.isFinite(id) || id < 0 || !Number.isInteger(id) || id > Number.MAX_SAFE_INTEGER) {
695
- throw new ValidationError(
696
- `REST backend requires numeric u64-compatible IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER}). Received: ${String(id)}`
697
- );
698
- }
699
- return id;
700
- }
701
- isLikelyAggregationQuery(query) {
702
- return /\bGROUP\s+BY\b|\bHAVING\b|\bCOUNT\s*\(|\bSUM\s*\(|\bAVG\s*\(|\bMIN\s*\(|\bMAX\s*\(/i.test(
703
- query
704
- );
705
- }
706
1367
  async request(method, path, body) {
707
1368
  const url = `${this.baseUrl}${path}`;
708
- const headers = {
709
- "Content-Type": "application/json"
710
- };
1369
+ const headers = { "Content-Type": "application/json" };
711
1370
  if (this.apiKey) {
712
1371
  headers["Authorization"] = `Bearer ${this.apiKey}`;
713
1372
  }
@@ -743,631 +1402,281 @@ var RestBackend = class {
743
1402
  );
744
1403
  }
745
1404
  }
1405
+ // ==========================================================================
1406
+ // Transport adapters
1407
+ // ==========================================================================
1408
+ asCrudTransport() {
1409
+ return {
1410
+ requestJson: (m, p, b) => this.request(m, p, b)
1411
+ };
1412
+ }
1413
+ asSearchTransport() {
1414
+ return {
1415
+ requestJson: (m, p, b) => this.request(m, p, b),
1416
+ sparseToRest: (sv) => sparseVectorToRestFormat(sv)
1417
+ };
1418
+ }
1419
+ asAgentMemoryTransport() {
1420
+ return {
1421
+ requestJson: (m, p, b) => this.request(m, p, b),
1422
+ searchVectors: (c, e, k, f) => this.search(c, e, { k, filter: f })
1423
+ };
1424
+ }
1425
+ asQueryTransport() {
1426
+ return {
1427
+ requestJson: (m, p, b) => this.request(m, p, b),
1428
+ parseNodeId: (v) => this.parseNodeId(v)
1429
+ };
1430
+ }
1431
+ asStreamingTransport() {
1432
+ return {
1433
+ requestJson: (m, p, b) => this.request(m, p, b),
1434
+ baseUrl: this.baseUrl,
1435
+ apiKey: this.apiKey,
1436
+ timeout: this.timeout,
1437
+ parseRestPointId,
1438
+ sparseVectorToRestFormat,
1439
+ mapStatusToErrorCode: (s) => this.mapStatusToErrorCode(s),
1440
+ extractErrorPayload: (d) => this.extractErrorPayload(d)
1441
+ };
1442
+ }
1443
+ // ==========================================================================
1444
+ // Collection CRUD — delegates to crud-backend.ts
1445
+ // ==========================================================================
746
1446
  async createCollection(name, config) {
747
1447
  this.ensureInitialized();
748
- const response = await this.request("POST", "/collections", {
749
- name,
750
- dimension: config.dimension,
751
- metric: config.metric ?? "cosine",
752
- storage_mode: config.storageMode ?? "full",
753
- collection_type: config.collectionType ?? "vector",
754
- description: config.description
755
- });
756
- if (response.error) {
757
- throw new VelesDBError(response.error.message, response.error.code);
758
- }
1448
+ return createCollection(this.asCrudTransport(), name, config);
759
1449
  }
760
1450
  async deleteCollection(name) {
761
1451
  this.ensureInitialized();
762
- const response = await this.request("DELETE", `/collections/${encodeURIComponent(name)}`);
763
- if (response.error) {
764
- if (response.error.code === "NOT_FOUND") {
765
- throw new NotFoundError(`Collection '${name}'`);
766
- }
767
- throw new VelesDBError(response.error.message, response.error.code);
768
- }
1452
+ return deleteCollection(this.asCrudTransport(), name);
769
1453
  }
770
1454
  async getCollection(name) {
771
1455
  this.ensureInitialized();
772
- const response = await this.request(
773
- "GET",
774
- `/collections/${encodeURIComponent(name)}`
775
- );
776
- if (response.error) {
777
- if (response.error.code === "NOT_FOUND") {
778
- return null;
779
- }
780
- throw new VelesDBError(response.error.message, response.error.code);
781
- }
782
- return response.data ?? null;
1456
+ return getCollection(this.asCrudTransport(), name);
783
1457
  }
784
1458
  async listCollections() {
785
1459
  this.ensureInitialized();
786
- const response = await this.request("GET", "/collections");
787
- if (response.error) {
788
- throw new VelesDBError(response.error.message, response.error.code);
789
- }
790
- return response.data ?? [];
1460
+ return listCollections(this.asCrudTransport());
791
1461
  }
792
1462
  async insert(collection, doc) {
793
1463
  this.ensureInitialized();
794
- const restId = this.parseRestPointId(doc.id);
795
- const vector = doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector;
796
- const response = await this.request(
797
- "POST",
798
- `/collections/${encodeURIComponent(collection)}/points`,
799
- {
800
- points: [{
801
- id: restId,
802
- vector,
803
- payload: doc.payload
804
- }]
805
- }
806
- );
807
- if (response.error) {
808
- if (response.error.code === "NOT_FOUND") {
809
- throw new NotFoundError(`Collection '${collection}'`);
810
- }
811
- throw new VelesDBError(response.error.message, response.error.code);
812
- }
1464
+ return insert(this.asCrudTransport(), collection, doc);
813
1465
  }
814
1466
  async insertBatch(collection, docs) {
815
1467
  this.ensureInitialized();
816
- const vectors = docs.map((doc) => ({
817
- id: this.parseRestPointId(doc.id),
818
- vector: doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector,
819
- payload: doc.payload
820
- }));
821
- const response = await this.request(
822
- "POST",
823
- `/collections/${encodeURIComponent(collection)}/points`,
824
- { points: vectors }
825
- );
826
- if (response.error) {
827
- if (response.error.code === "NOT_FOUND") {
828
- throw new NotFoundError(`Collection '${collection}'`);
829
- }
830
- throw new VelesDBError(response.error.message, response.error.code);
831
- }
1468
+ return insertBatch(this.asCrudTransport(), collection, docs);
832
1469
  }
833
- sparseVectorToRestFormat(sv) {
834
- const result = {};
835
- for (const [k, v] of Object.entries(sv)) {
836
- result[String(k)] = v;
837
- }
838
- return result;
1470
+ async delete(collection, id) {
1471
+ this.ensureInitialized();
1472
+ return deletePoint(this.asCrudTransport(), collection, id);
839
1473
  }
840
- async search(collection, query, options) {
1474
+ async get(collection, id) {
841
1475
  this.ensureInitialized();
842
- const queryVector = query instanceof Float32Array ? Array.from(query) : query;
843
- const body = {
844
- vector: queryVector,
845
- top_k: options?.k ?? 10,
846
- filter: options?.filter,
847
- include_vectors: options?.includeVectors ?? false
848
- };
849
- if (options?.sparseVector) {
850
- body.sparse_vector = this.sparseVectorToRestFormat(options.sparseVector);
851
- }
852
- const response = await this.request(
853
- "POST",
854
- `/collections/${encodeURIComponent(collection)}/search`,
855
- body
856
- );
857
- if (response.error) {
858
- if (response.error.code === "NOT_FOUND") {
859
- throw new NotFoundError(`Collection '${collection}'`);
860
- }
861
- throw new VelesDBError(response.error.message, response.error.code);
862
- }
863
- return response.data?.results ?? [];
1476
+ return get(this.asCrudTransport(), collection, id);
1477
+ }
1478
+ async isEmpty(collection) {
1479
+ this.ensureInitialized();
1480
+ return isEmpty(this.asCrudTransport(), collection);
1481
+ }
1482
+ async flush(collection) {
1483
+ this.ensureInitialized();
1484
+ return flush(this.asCrudTransport(), collection);
1485
+ }
1486
+ async close() {
1487
+ this._initialized = false;
1488
+ }
1489
+ // ==========================================================================
1490
+ // Search — delegates to search-backend.ts
1491
+ // ==========================================================================
1492
+ async search(c, q, o) {
1493
+ this.ensureInitialized();
1494
+ return search(this.asSearchTransport(), c, q, o);
864
1495
  }
865
1496
  async searchBatch(collection, searches) {
866
1497
  this.ensureInitialized();
867
- const formattedSearches = searches.map((s) => ({
868
- vector: s.vector instanceof Float32Array ? Array.from(s.vector) : s.vector,
869
- top_k: s.k ?? 10,
870
- filter: s.filter
871
- }));
872
- const response = await this.request(
873
- "POST",
874
- `/collections/${encodeURIComponent(collection)}/search/batch`,
875
- { searches: formattedSearches }
876
- );
877
- if (response.error) {
878
- if (response.error.code === "NOT_FOUND") {
879
- throw new NotFoundError(`Collection '${collection}'`);
880
- }
881
- throw new VelesDBError(response.error.message, response.error.code);
882
- }
883
- return response.data?.results.map((r) => r.results) ?? [];
1498
+ return searchBatch(this.asSearchTransport(), collection, searches);
884
1499
  }
885
- async delete(collection, id) {
1500
+ async textSearch(c, q, o) {
886
1501
  this.ensureInitialized();
887
- const restId = this.parseRestPointId(id);
888
- const response = await this.request(
889
- "DELETE",
890
- `/collections/${encodeURIComponent(collection)}/points/${encodeURIComponent(String(restId))}`
891
- );
892
- if (response.error) {
893
- if (response.error.code === "NOT_FOUND") {
894
- return false;
895
- }
896
- throw new VelesDBError(response.error.message, response.error.code);
897
- }
898
- return response.data?.deleted ?? false;
1502
+ return textSearch(this.asSearchTransport(), c, q, o);
899
1503
  }
900
- async get(collection, id) {
1504
+ async hybridSearch(c, v, t, o) {
901
1505
  this.ensureInitialized();
902
- const restId = this.parseRestPointId(id);
903
- const response = await this.request(
904
- "GET",
905
- `/collections/${encodeURIComponent(collection)}/points/${encodeURIComponent(String(restId))}`
906
- );
907
- if (response.error) {
908
- if (response.error.code === "NOT_FOUND") {
909
- return null;
910
- }
911
- throw new VelesDBError(response.error.message, response.error.code);
912
- }
913
- return response.data ?? null;
1506
+ return hybridSearch(this.asSearchTransport(), c, v, t, o);
914
1507
  }
915
- async textSearch(collection, query, options) {
1508
+ async multiQuerySearch(c, v, o) {
916
1509
  this.ensureInitialized();
917
- const response = await this.request(
918
- "POST",
919
- `/collections/${encodeURIComponent(collection)}/search/text`,
920
- {
921
- query,
922
- top_k: options?.k ?? 10,
923
- filter: options?.filter
924
- }
925
- );
926
- if (response.error) {
927
- if (response.error.code === "NOT_FOUND") {
928
- throw new NotFoundError(`Collection '${collection}'`);
929
- }
930
- throw new VelesDBError(response.error.message, response.error.code);
931
- }
932
- return response.data?.results ?? [];
1510
+ return multiQuerySearch(this.asSearchTransport(), c, v, o);
933
1511
  }
934
- async hybridSearch(collection, vector, textQuery, options) {
1512
+ async searchIds(c, q, o) {
935
1513
  this.ensureInitialized();
936
- const queryVector = vector instanceof Float32Array ? Array.from(vector) : vector;
937
- const response = await this.request(
938
- "POST",
939
- `/collections/${encodeURIComponent(collection)}/search/hybrid`,
940
- {
941
- vector: queryVector,
942
- query: textQuery,
943
- top_k: options?.k ?? 10,
944
- vector_weight: options?.vectorWeight ?? 0.5,
945
- filter: options?.filter
946
- }
947
- );
948
- if (response.error) {
949
- if (response.error.code === "NOT_FOUND") {
950
- throw new NotFoundError(`Collection '${collection}'`);
951
- }
952
- throw new VelesDBError(response.error.message, response.error.code);
953
- }
954
- return response.data?.results ?? [];
1514
+ return searchIds(this.asSearchTransport(), c, q, o);
955
1515
  }
956
- async query(collection, queryString, params, options) {
1516
+ // ==========================================================================
1517
+ // Query — delegates to query-backend.ts
1518
+ // ==========================================================================
1519
+ async query(c, q, p, o) {
957
1520
  this.ensureInitialized();
958
- const endpoint = this.isLikelyAggregationQuery(queryString) ? "/aggregate" : "/query";
959
- const response = await this.request(
960
- "POST",
961
- endpoint,
962
- {
963
- query: queryString,
964
- params: params ?? {},
965
- collection,
966
- timeout_ms: options?.timeoutMs,
967
- stream: options?.stream ?? false
968
- }
969
- );
970
- if (response.error) {
971
- if (response.error.code === "NOT_FOUND") {
972
- throw new NotFoundError(`Collection '${collection}'`);
973
- }
974
- throw new VelesDBError(response.error.message, response.error.code);
975
- }
976
- const rawData = response.data;
977
- if (rawData && Object.prototype.hasOwnProperty.call(rawData, "result")) {
978
- return {
979
- result: rawData.result,
980
- stats: {
981
- executionTimeMs: rawData.timing_ms ?? 0,
982
- strategy: "aggregation",
983
- scannedNodes: 0
984
- }
985
- };
986
- }
987
- return {
988
- results: (rawData?.results ?? []).map((r) => ({
989
- // Server returns `id` (u64), map to nodeId with precision handling
990
- nodeId: this.parseNodeId(r.id ?? r.node_id ?? r.nodeId),
991
- // Server returns `score`, map to vectorScore (primary score for SELECT queries)
992
- vectorScore: r.score ?? r.vector_score ?? r.vectorScore,
993
- // graph_score not returned by SELECT queries, only by future MATCH queries
994
- graphScore: r.graph_score ?? r.graphScore,
995
- // Use score as fusedScore for compatibility
996
- fusedScore: r.score ?? r.fused_score ?? r.fusedScore ?? 0,
997
- // payload maps to bindings for compatibility
998
- bindings: r.payload ?? r.bindings ?? {},
999
- columnData: r.column_data ?? r.columnData
1000
- })),
1001
- stats: {
1002
- executionTimeMs: rawData?.timing_ms ?? 0,
1003
- strategy: "select",
1004
- scannedNodes: rawData?.rows_returned ?? 0
1005
- }
1006
- };
1521
+ return query(this.asQueryTransport(), c, q, p, o);
1007
1522
  }
1008
- async queryExplain(queryString, params) {
1523
+ async queryExplain(q, p) {
1009
1524
  this.ensureInitialized();
1010
- const response = await this.request(
1011
- "POST",
1012
- "/query/explain",
1013
- {
1014
- query: queryString,
1015
- params: params ?? {}
1016
- }
1017
- );
1018
- if (response.error) {
1019
- throw new VelesDBError(response.error.message, response.error.code);
1020
- }
1021
- const data = response.data;
1022
- return {
1023
- query: data.query,
1024
- queryType: data.query_type,
1025
- collection: data.collection,
1026
- plan: data.plan.map((step) => ({
1027
- step: step.step,
1028
- operation: step.operation,
1029
- description: step.description,
1030
- estimatedRows: step.estimated_rows
1031
- })),
1032
- estimatedCost: {
1033
- usesIndex: data.estimated_cost.uses_index,
1034
- indexName: data.estimated_cost.index_name,
1035
- selectivity: data.estimated_cost.selectivity,
1036
- complexity: data.estimated_cost.complexity
1037
- },
1038
- features: {
1039
- hasVectorSearch: data.features.has_vector_search,
1040
- hasFilter: data.features.has_filter,
1041
- hasOrderBy: data.features.has_order_by,
1042
- hasGroupBy: data.features.has_group_by,
1043
- hasAggregation: data.features.has_aggregation,
1044
- hasJoin: data.features.has_join,
1045
- hasFusion: data.features.has_fusion,
1046
- limit: data.features.limit,
1047
- offset: data.features.offset
1048
- }
1049
- };
1525
+ return queryExplain(this.asQueryTransport(), q, p);
1050
1526
  }
1051
1527
  async collectionSanity(collection) {
1052
1528
  this.ensureInitialized();
1053
- const response = await this.request(
1054
- "GET",
1055
- `/collections/${encodeURIComponent(collection)}/sanity`
1056
- );
1057
- if (response.error) {
1058
- if (response.error.code === "NOT_FOUND") {
1059
- throw new NotFoundError(`Collection '${collection}'`);
1060
- }
1061
- throw new VelesDBError(response.error.message, response.error.code);
1062
- }
1063
- const data = response.data;
1064
- return {
1065
- collection: data.collection,
1066
- dimension: data.dimension,
1067
- metric: data.metric,
1068
- pointCount: data.point_count,
1069
- isEmpty: data.is_empty,
1070
- checks: {
1071
- hasVectors: data.checks.has_vectors,
1072
- searchReady: data.checks.search_ready,
1073
- dimensionConfigured: data.checks.dimension_configured
1074
- },
1075
- diagnostics: {
1076
- searchRequestsTotal: data.diagnostics.search_requests_total,
1077
- dimensionMismatchTotal: data.diagnostics.dimension_mismatch_total,
1078
- emptySearchResultsTotal: data.diagnostics.empty_search_results_total,
1079
- filterParseErrorsTotal: data.diagnostics.filter_parse_errors_total
1080
- },
1081
- hints: data.hints ?? []
1082
- };
1529
+ return collectionSanity(this.asQueryTransport(), collection);
1083
1530
  }
1084
- async multiQuerySearch(collection, vectors, options) {
1531
+ // ==========================================================================
1532
+ // Graph — delegates to graph-backend.ts
1533
+ // ==========================================================================
1534
+ async addEdge(collection, edge) {
1085
1535
  this.ensureInitialized();
1086
- const formattedVectors = vectors.map(
1087
- (v) => v instanceof Float32Array ? Array.from(v) : v
1088
- );
1089
- const response = await this.request(
1090
- "POST",
1091
- `/collections/${encodeURIComponent(collection)}/search/multi`,
1092
- {
1093
- vectors: formattedVectors,
1094
- top_k: options?.k ?? 10,
1095
- strategy: options?.fusion ?? "rrf",
1096
- rrf_k: options?.fusionParams?.k ?? 60,
1097
- avg_weight: options?.fusionParams?.avgWeight,
1098
- max_weight: options?.fusionParams?.maxWeight,
1099
- hit_weight: options?.fusionParams?.hitWeight,
1100
- filter: options?.filter
1101
- }
1102
- );
1103
- if (response.error) {
1104
- if (response.error.code === "NOT_FOUND") {
1105
- throw new NotFoundError(`Collection '${collection}'`);
1106
- }
1107
- throw new VelesDBError(response.error.message, response.error.code);
1108
- }
1109
- return response.data?.results ?? [];
1536
+ return addEdge(this.asCrudTransport(), collection, edge);
1110
1537
  }
1111
- async isEmpty(collection) {
1538
+ async getEdges(collection, options) {
1112
1539
  this.ensureInitialized();
1113
- const response = await this.request(
1114
- "GET",
1115
- `/collections/${encodeURIComponent(collection)}/empty`
1116
- );
1117
- if (response.error) {
1118
- if (response.error.code === "NOT_FOUND") {
1119
- throw new NotFoundError(`Collection '${collection}'`);
1120
- }
1121
- throw new VelesDBError(response.error.message, response.error.code);
1122
- }
1123
- return response.data?.is_empty ?? true;
1540
+ return getEdges(this.asCrudTransport(), collection, options);
1124
1541
  }
1125
- async flush(collection) {
1542
+ async traverseGraph(collection, req) {
1126
1543
  this.ensureInitialized();
1127
- const response = await this.request(
1128
- "POST",
1129
- `/collections/${encodeURIComponent(collection)}/flush`
1130
- );
1131
- if (response.error) {
1132
- if (response.error.code === "NOT_FOUND") {
1133
- throw new NotFoundError(`Collection '${collection}'`);
1134
- }
1135
- throw new VelesDBError(response.error.message, response.error.code);
1136
- }
1544
+ return traverseGraph(this.asCrudTransport(), collection, req);
1137
1545
  }
1138
- // ========================================================================
1139
- // Sparse / PQ / Streaming (v1.5)
1140
- // ========================================================================
1141
- async trainPq(collection, options) {
1546
+ async getNodeDegree(collection, nodeId) {
1142
1547
  this.ensureInitialized();
1143
- const m = options?.m ?? 8;
1144
- const k = options?.k ?? 256;
1145
- const withClause = options?.opq ? `WITH (m=${m}, k=${k}, opq=true)` : `WITH (m=${m}, k=${k})`;
1146
- const queryString = `TRAIN QUANTIZER ON ${collection} ${withClause}`;
1147
- const response = await this.request(
1148
- "POST",
1149
- "/query",
1150
- { query: queryString }
1151
- );
1152
- if (response.error) {
1153
- throw new VelesDBError(response.error.message, response.error.code);
1154
- }
1155
- return response.data?.message ?? "PQ training initiated";
1548
+ return getNodeDegree(this.asCrudTransport(), collection, nodeId);
1156
1549
  }
1157
- async streamInsert(collection, docs) {
1550
+ async createGraphCollection(name, config) {
1158
1551
  this.ensureInitialized();
1159
- for (const doc of docs) {
1160
- const restId = this.parseRestPointId(doc.id);
1161
- const vector = doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector;
1162
- const body = {
1163
- id: restId,
1164
- vector,
1165
- payload: doc.payload
1166
- };
1167
- if (doc.sparseVector) {
1168
- body.sparse_vector = this.sparseVectorToRestFormat(doc.sparseVector);
1169
- }
1170
- const url = `${this.baseUrl}/collections/${encodeURIComponent(collection)}/stream/insert`;
1171
- const headers = {
1172
- "Content-Type": "application/json"
1173
- };
1174
- if (this.apiKey) {
1175
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1176
- }
1177
- const controller = new AbortController();
1178
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1179
- try {
1180
- const response = await fetch(url, {
1181
- method: "POST",
1182
- headers,
1183
- body: JSON.stringify(body),
1184
- signal: controller.signal
1185
- });
1186
- clearTimeout(timeoutId);
1187
- if (response.status === 429) {
1188
- throw new BackpressureError();
1189
- }
1190
- if (!response.ok && response.status !== 202) {
1191
- const data = await response.json().catch(() => ({}));
1192
- const errorPayload = this.extractErrorPayload(data);
1193
- throw new VelesDBError(
1194
- errorPayload.message ?? `HTTP ${response.status}`,
1195
- errorPayload.code ?? this.mapStatusToErrorCode(response.status)
1196
- );
1197
- }
1198
- } catch (error) {
1199
- clearTimeout(timeoutId);
1200
- if (error instanceof BackpressureError || error instanceof VelesDBError) {
1201
- throw error;
1202
- }
1203
- if (error instanceof Error && error.name === "AbortError") {
1204
- throw new ConnectionError("Request timeout");
1205
- }
1206
- throw new ConnectionError(
1207
- `Stream insert failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1208
- error instanceof Error ? error : void 0
1209
- );
1210
- }
1211
- }
1552
+ return createGraphCollection(this.asCrudTransport(), name, config);
1212
1553
  }
1213
- async close() {
1214
- this._initialized = false;
1215
- }
1216
- // ========================================================================
1217
- // Index Management (EPIC-009)
1218
- // ========================================================================
1554
+ // ==========================================================================
1555
+ // Index — delegates to index-backend.ts
1556
+ // ==========================================================================
1219
1557
  async createIndex(collection, options) {
1220
1558
  this.ensureInitialized();
1221
- const response = await this.request(
1222
- "POST",
1223
- `/collections/${encodeURIComponent(collection)}/indexes`,
1224
- {
1225
- label: options.label,
1226
- property: options.property,
1227
- index_type: options.indexType ?? "hash"
1228
- }
1229
- );
1230
- if (response.error) {
1231
- if (response.error.code === "NOT_FOUND") {
1232
- throw new NotFoundError(`Collection '${collection}'`);
1233
- }
1234
- throw new VelesDBError(response.error.message, response.error.code);
1235
- }
1559
+ return createIndex(this.asCrudTransport(), collection, options);
1236
1560
  }
1237
1561
  async listIndexes(collection) {
1238
1562
  this.ensureInitialized();
1239
- const response = await this.request(
1240
- "GET",
1241
- `/collections/${encodeURIComponent(collection)}/indexes`
1242
- );
1243
- if (response.error) {
1244
- if (response.error.code === "NOT_FOUND") {
1245
- throw new NotFoundError(`Collection '${collection}'`);
1246
- }
1247
- throw new VelesDBError(response.error.message, response.error.code);
1248
- }
1249
- return (response.data?.indexes ?? []).map((idx) => ({
1250
- label: idx.label,
1251
- property: idx.property,
1252
- indexType: idx.index_type,
1253
- cardinality: idx.cardinality,
1254
- memoryBytes: idx.memory_bytes
1255
- }));
1563
+ return listIndexes(this.asCrudTransport(), collection);
1256
1564
  }
1257
1565
  async hasIndex(collection, label, property) {
1258
- const indexes = await this.listIndexes(collection);
1259
- return indexes.some((idx) => idx.label === label && idx.property === property);
1566
+ this.ensureInitialized();
1567
+ return hasIndex(this.asCrudTransport(), collection, label, property);
1260
1568
  }
1261
1569
  async dropIndex(collection, label, property) {
1262
1570
  this.ensureInitialized();
1263
- const response = await this.request(
1264
- "DELETE",
1265
- `/collections/${encodeURIComponent(collection)}/indexes/${encodeURIComponent(label)}/${encodeURIComponent(property)}`
1266
- );
1267
- if (response.error) {
1268
- if (response.error.code === "NOT_FOUND") {
1269
- return false;
1270
- }
1271
- throw new VelesDBError(response.error.message, response.error.code);
1272
- }
1273
- return response.data?.dropped ?? true;
1571
+ return dropIndex(this.asCrudTransport(), collection, label, property);
1274
1572
  }
1275
- // ========================================================================
1276
- // Knowledge Graph (EPIC-016 US-041)
1277
- // ========================================================================
1278
- async addEdge(collection, edge) {
1573
+ // ==========================================================================
1574
+ // Admin delegates to admin-backend.ts
1575
+ // ==========================================================================
1576
+ async getCollectionStats(collection) {
1279
1577
  this.ensureInitialized();
1280
- const response = await this.request(
1281
- "POST",
1282
- `/collections/${encodeURIComponent(collection)}/graph/edges`,
1283
- {
1284
- id: edge.id,
1285
- source: edge.source,
1286
- target: edge.target,
1287
- label: edge.label,
1288
- properties: edge.properties ?? {}
1289
- }
1290
- );
1291
- if (response.error) {
1292
- if (response.error.code === "NOT_FOUND") {
1293
- throw new NotFoundError(`Collection '${collection}'`);
1294
- }
1295
- throw new VelesDBError(response.error.message, response.error.code);
1296
- }
1578
+ return getCollectionStats(this.asCrudTransport(), collection);
1297
1579
  }
1298
- async getEdges(collection, options) {
1580
+ async analyzeCollection(collection) {
1299
1581
  this.ensureInitialized();
1300
- const queryParams = options?.label ? `?label=${encodeURIComponent(options.label)}` : "";
1301
- const response = await this.request(
1302
- "GET",
1303
- `/collections/${encodeURIComponent(collection)}/graph/edges${queryParams}`
1304
- );
1305
- if (response.error) {
1306
- if (response.error.code === "NOT_FOUND") {
1307
- throw new NotFoundError(`Collection '${collection}'`);
1308
- }
1309
- throw new VelesDBError(response.error.message, response.error.code);
1310
- }
1311
- return response.data?.edges ?? [];
1582
+ return analyzeCollection(this.asCrudTransport(), collection);
1312
1583
  }
1313
- // ========================================================================
1314
- // Graph Traversal (EPIC-016 US-050)
1315
- // ========================================================================
1316
- async traverseGraph(collection, request) {
1584
+ async getCollectionConfig(collection) {
1317
1585
  this.ensureInitialized();
1318
- const response = await this.request(
1319
- "POST",
1320
- `/collections/${encodeURIComponent(collection)}/graph/traverse`,
1321
- {
1322
- source: request.source,
1323
- strategy: request.strategy ?? "bfs",
1324
- max_depth: request.maxDepth ?? 3,
1325
- limit: request.limit ?? 100,
1326
- cursor: request.cursor,
1327
- rel_types: request.relTypes ?? []
1328
- }
1329
- );
1330
- if (response.error) {
1331
- if (response.error.code === "NOT_FOUND") {
1332
- throw new NotFoundError(`Collection '${collection}'`);
1333
- }
1334
- throw new VelesDBError(response.error.message, response.error.code);
1335
- }
1336
- const data = response.data;
1337
- return {
1338
- results: data.results.map((r) => ({
1339
- targetId: r.target_id,
1340
- depth: r.depth,
1341
- path: r.path
1342
- })),
1343
- nextCursor: data.next_cursor ?? void 0,
1344
- hasMore: data.has_more,
1345
- stats: {
1346
- visited: data.stats.visited,
1347
- depthReached: data.stats.depth_reached
1348
- }
1349
- };
1586
+ return getCollectionConfig(this.asCrudTransport(), collection);
1350
1587
  }
1351
- async getNodeDegree(collection, nodeId) {
1588
+ // ==========================================================================
1589
+ // Streaming / PQ — delegates to streaming-backend.ts
1590
+ // ==========================================================================
1591
+ async trainPq(collection, options) {
1352
1592
  this.ensureInitialized();
1353
- const response = await this.request(
1354
- "GET",
1355
- `/collections/${encodeURIComponent(collection)}/graph/nodes/${nodeId}/degree`
1356
- );
1357
- if (response.error) {
1358
- if (response.error.code === "NOT_FOUND") {
1359
- throw new NotFoundError(`Collection '${collection}'`);
1360
- }
1361
- throw new VelesDBError(response.error.message, response.error.code);
1362
- }
1363
- return {
1364
- inDegree: response.data?.in_degree ?? 0,
1365
- outDegree: response.data?.out_degree ?? 0
1366
- };
1593
+ return trainPq(this.asStreamingTransport(), collection, options);
1594
+ }
1595
+ async streamInsert(collection, docs) {
1596
+ this.ensureInitialized();
1597
+ return streamInsert(this.asStreamingTransport(), collection, docs);
1598
+ }
1599
+ // ==========================================================================
1600
+ // Agent Memory — delegates to agent-memory-backend.ts
1601
+ // ==========================================================================
1602
+ async storeSemanticFact(collection, entry) {
1603
+ this.ensureInitialized();
1604
+ return storeSemanticFact(this.asAgentMemoryTransport(), collection, entry);
1605
+ }
1606
+ async searchSemanticMemory(collection, embedding, k = 5) {
1607
+ return searchSemanticMemory(this.asAgentMemoryTransport(), collection, embedding, k);
1608
+ }
1609
+ async recordEpisodicEvent(collection, event) {
1610
+ this.ensureInitialized();
1611
+ return recordEpisodicEvent(this.asAgentMemoryTransport(), collection, event);
1612
+ }
1613
+ async recallEpisodicEvents(collection, embedding, k = 5) {
1614
+ return recallEpisodicEvents(this.asAgentMemoryTransport(), collection, embedding, k);
1615
+ }
1616
+ async storeProceduralPattern(collection, pattern) {
1617
+ this.ensureInitialized();
1618
+ return storeProceduralPattern(this.asAgentMemoryTransport(), collection, pattern);
1619
+ }
1620
+ async matchProceduralPatterns(collection, embedding, k = 5) {
1621
+ return matchProceduralPatterns(this.asAgentMemoryTransport(), collection, embedding, k);
1622
+ }
1623
+ };
1624
+
1625
+ // src/agent-memory.ts
1626
+ var AgentMemoryClient = class {
1627
+ constructor(backend, config) {
1628
+ this.backend = backend;
1629
+ this.config = config;
1630
+ }
1631
+ /** Configured embedding dimension (default: 384) */
1632
+ get dimension() {
1633
+ return this.config?.dimension ?? 384;
1634
+ }
1635
+ /** Store a semantic fact */
1636
+ async storeFact(collection, entry) {
1637
+ return this.backend.storeSemanticFact(collection, entry);
1638
+ }
1639
+ /** Search semantic memory */
1640
+ async searchFacts(collection, embedding, k = 5) {
1641
+ return this.backend.searchSemanticMemory(collection, embedding, k);
1642
+ }
1643
+ /** Record an episodic event */
1644
+ async recordEvent(collection, event) {
1645
+ return this.backend.recordEpisodicEvent(collection, event);
1646
+ }
1647
+ /** Recall episodic events */
1648
+ async recallEvents(collection, embedding, k = 5) {
1649
+ return this.backend.recallEpisodicEvents(collection, embedding, k);
1650
+ }
1651
+ /** Store a procedural pattern */
1652
+ async learnProcedure(collection, pattern) {
1653
+ return this.backend.storeProceduralPattern(collection, pattern);
1654
+ }
1655
+ /** Match procedural patterns */
1656
+ async recallProcedures(collection, embedding, k = 5) {
1657
+ return this.backend.matchProceduralPatterns(collection, embedding, k);
1367
1658
  }
1368
1659
  };
1369
1660
 
1370
1661
  // src/client.ts
1662
+ function requireNonEmptyString(value, label) {
1663
+ if (!value || typeof value !== "string") {
1664
+ throw new ValidationError(`${label} must be a non-empty string`);
1665
+ }
1666
+ }
1667
+ function requireVector(value, label) {
1668
+ if (!value || !Array.isArray(value) && !(value instanceof Float32Array)) {
1669
+ throw new ValidationError(`${label} must be an array or Float32Array`);
1670
+ }
1671
+ }
1672
+ function validateDocsBatch(docs, validateDoc) {
1673
+ if (!Array.isArray(docs)) {
1674
+ throw new ValidationError("Documents must be an array");
1675
+ }
1676
+ for (const doc of docs) {
1677
+ validateDoc(doc);
1678
+ }
1679
+ }
1371
1680
  var VelesDB = class {
1372
1681
  /**
1373
1682
  * Create a new VelesDB client
@@ -1432,9 +1741,7 @@ var VelesDB = class {
1432
1741
  */
1433
1742
  async createCollection(name, config) {
1434
1743
  this.ensureInitialized();
1435
- if (!name || typeof name !== "string") {
1436
- throw new ValidationError("Collection name must be a non-empty string");
1437
- }
1744
+ requireNonEmptyString(name, "Collection name");
1438
1745
  const isMetadataOnly = config.collectionType === "metadata_only";
1439
1746
  if (!isMetadataOnly && (!config.dimension || config.dimension <= 0)) {
1440
1747
  throw new ValidationError("Dimension must be a positive integer for vector collections");
@@ -1456,9 +1763,7 @@ var VelesDB = class {
1456
1763
  */
1457
1764
  async createMetadataCollection(name) {
1458
1765
  this.ensureInitialized();
1459
- if (!name || typeof name !== "string") {
1460
- throw new ValidationError("Collection name must be a non-empty string");
1461
- }
1766
+ requireNonEmptyString(name, "Collection name");
1462
1767
  await this.backend.createCollection(name, { collectionType: "metadata_only" });
1463
1768
  }
1464
1769
  /**
@@ -1508,29 +1813,15 @@ var VelesDB = class {
1508
1813
  */
1509
1814
  async insertBatch(collection, docs) {
1510
1815
  this.ensureInitialized();
1511
- if (!Array.isArray(docs)) {
1512
- throw new ValidationError("Documents must be an array");
1513
- }
1514
- for (const doc of docs) {
1515
- this.validateDocument(doc);
1516
- }
1816
+ validateDocsBatch(docs, (doc) => this.validateDocument(doc));
1517
1817
  await this.backend.insertBatch(collection, docs);
1518
1818
  }
1519
1819
  validateDocument(doc) {
1520
1820
  if (doc.id === void 0 || doc.id === null) {
1521
1821
  throw new ValidationError("Document ID is required");
1522
1822
  }
1523
- if (!doc.vector) {
1524
- throw new ValidationError("Document vector is required");
1525
- }
1526
- if (!Array.isArray(doc.vector) && !(doc.vector instanceof Float32Array)) {
1527
- throw new ValidationError("Vector must be an array or Float32Array");
1528
- }
1529
- if (this.config.backend === "rest" && (typeof doc.id !== "number" || !Number.isInteger(doc.id) || doc.id < 0 || doc.id > Number.MAX_SAFE_INTEGER)) {
1530
- throw new ValidationError(
1531
- `REST backend requires numeric u64-compatible document IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER})`
1532
- );
1533
- }
1823
+ requireVector(doc.vector, "Vector");
1824
+ this.validateRestPointId(doc.id);
1534
1825
  }
1535
1826
  validateRestPointId(id) {
1536
1827
  if (this.config.backend === "rest" && (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > Number.MAX_SAFE_INTEGER)) {
@@ -1547,12 +1838,10 @@ var VelesDB = class {
1547
1838
  * @param options - Search options
1548
1839
  * @returns Search results sorted by relevance
1549
1840
  */
1550
- async search(collection, query, options) {
1841
+ async search(collection, query2, options) {
1551
1842
  this.ensureInitialized();
1552
- if (!query || !Array.isArray(query) && !(query instanceof Float32Array)) {
1553
- throw new ValidationError("Query must be an array or Float32Array");
1554
- }
1555
- return this.backend.search(collection, query, options);
1843
+ requireVector(query2, "Query");
1844
+ return this.backend.search(collection, query2, options);
1556
1845
  }
1557
1846
  /**
1558
1847
  * Search for multiple vectors in parallel
@@ -1567,9 +1856,7 @@ var VelesDB = class {
1567
1856
  throw new ValidationError("Searches must be an array");
1568
1857
  }
1569
1858
  for (const s of searches) {
1570
- if (!s.vector || !Array.isArray(s.vector) && !(s.vector instanceof Float32Array)) {
1571
- throw new ValidationError("Each search must have a vector (array or Float32Array)");
1572
- }
1859
+ requireVector(s.vector, "Each search vector");
1573
1860
  }
1574
1861
  return this.backend.searchBatch(collection, searches);
1575
1862
  }
@@ -1605,12 +1892,10 @@ var VelesDB = class {
1605
1892
  * @param options - Search options (k, filter)
1606
1893
  * @returns Search results sorted by BM25 score
1607
1894
  */
1608
- async textSearch(collection, query, options) {
1895
+ async textSearch(collection, query2, options) {
1609
1896
  this.ensureInitialized();
1610
- if (!query || typeof query !== "string") {
1611
- throw new ValidationError("Query must be a non-empty string");
1612
- }
1613
- return this.backend.textSearch(collection, query, options);
1897
+ requireNonEmptyString(query2, "Query");
1898
+ return this.backend.textSearch(collection, query2, options);
1614
1899
  }
1615
1900
  /**
1616
1901
  * Perform hybrid search combining vector similarity and BM25 text search
@@ -1623,12 +1908,8 @@ var VelesDB = class {
1623
1908
  */
1624
1909
  async hybridSearch(collection, vector, textQuery, options) {
1625
1910
  this.ensureInitialized();
1626
- if (!vector || !Array.isArray(vector) && !(vector instanceof Float32Array)) {
1627
- throw new ValidationError("Vector must be an array or Float32Array");
1628
- }
1629
- if (!textQuery || typeof textQuery !== "string") {
1630
- throw new ValidationError("Text query must be a non-empty string");
1631
- }
1911
+ requireVector(vector, "Vector");
1912
+ requireNonEmptyString(textQuery, "Text query");
1632
1913
  return this.backend.hybridSearch(collection, vector, textQuery, options);
1633
1914
  }
1634
1915
  /**
@@ -1649,30 +1930,43 @@ var VelesDB = class {
1649
1930
  * `, { q: queryVector });
1650
1931
  *
1651
1932
  * for (const r of response.results) {
1652
- * console.log(`Node ${r.nodeId}: ${r.fusedScore}`);
1933
+ * console.log(`ID ${r.id}, title: ${r.title}`);
1653
1934
  * }
1654
1935
  * ```
1655
1936
  */
1656
1937
  async query(collection, queryString, params, options) {
1657
1938
  this.ensureInitialized();
1658
- if (!collection || typeof collection !== "string") {
1659
- throw new ValidationError("Collection name must be a non-empty string");
1660
- }
1661
- if (!queryString || typeof queryString !== "string") {
1662
- throw new ValidationError("Query string must be a non-empty string");
1663
- }
1939
+ requireNonEmptyString(collection, "Collection name");
1940
+ requireNonEmptyString(queryString, "Query string");
1664
1941
  return this.backend.query(collection, queryString, params, options);
1665
1942
  }
1943
+ /**
1944
+ * Explain the execution plan for a VelesQL query without running it
1945
+ *
1946
+ * @param queryString - VelesQL query string to explain
1947
+ * @param params - Optional query parameters (vectors, scalars)
1948
+ * @returns Explain response with the query execution plan
1949
+ */
1950
+ async queryExplain(queryString, params) {
1951
+ this.ensureInitialized();
1952
+ requireNonEmptyString(queryString, "Query string");
1953
+ return this.backend.queryExplain(queryString, params);
1954
+ }
1955
+ async collectionSanity(collection) {
1956
+ this.ensureInitialized();
1957
+ requireNonEmptyString(collection, "Collection name");
1958
+ return this.backend.collectionSanity(collection);
1959
+ }
1666
1960
  /**
1667
1961
  * Multi-query fusion search combining results from multiple query vectors
1668
- *
1962
+ *
1669
1963
  * Ideal for RAG pipelines using Multiple Query Generation (MQG).
1670
- *
1964
+ *
1671
1965
  * @param collection - Collection name
1672
1966
  * @param vectors - Array of query vectors
1673
1967
  * @param options - Search options (k, fusion strategy, fusionParams, filter)
1674
1968
  * @returns Fused search results
1675
- *
1969
+ *
1676
1970
  * @example
1677
1971
  * ```typescript
1678
1972
  * // RRF fusion (default)
@@ -1681,7 +1975,7 @@ var VelesDB = class {
1681
1975
  * fusion: 'rrf',
1682
1976
  * fusionParams: { k: 60 }
1683
1977
  * });
1684
- *
1978
+ *
1685
1979
  * // Weighted fusion
1686
1980
  * const results = await db.multiQuerySearch('docs', [emb1, emb2], {
1687
1981
  * k: 10,
@@ -1690,29 +1984,13 @@ var VelesDB = class {
1690
1984
  * });
1691
1985
  * ```
1692
1986
  */
1693
- async queryExplain(queryString, params) {
1694
- this.ensureInitialized();
1695
- if (!queryString || typeof queryString !== "string") {
1696
- throw new ValidationError("Query string must be a non-empty string");
1697
- }
1698
- return this.backend.queryExplain(queryString, params);
1699
- }
1700
- async collectionSanity(collection) {
1701
- this.ensureInitialized();
1702
- if (!collection || typeof collection !== "string") {
1703
- throw new ValidationError("Collection name must be a non-empty string");
1704
- }
1705
- return this.backend.collectionSanity(collection);
1706
- }
1707
1987
  async multiQuerySearch(collection, vectors, options) {
1708
1988
  this.ensureInitialized();
1709
1989
  if (!Array.isArray(vectors) || vectors.length === 0) {
1710
1990
  throw new ValidationError("Vectors must be a non-empty array");
1711
1991
  }
1712
1992
  for (const v of vectors) {
1713
- if (!Array.isArray(v) && !(v instanceof Float32Array)) {
1714
- throw new ValidationError("Each vector must be an array or Float32Array");
1715
- }
1993
+ requireVector(v, "Each vector");
1716
1994
  }
1717
1995
  return this.backend.multiQuerySearch(collection, vectors, options);
1718
1996
  }
@@ -1738,12 +2016,7 @@ var VelesDB = class {
1738
2016
  */
1739
2017
  async streamInsert(collection, docs) {
1740
2018
  this.ensureInitialized();
1741
- if (!Array.isArray(docs)) {
1742
- throw new ValidationError("Documents must be an array");
1743
- }
1744
- for (const doc of docs) {
1745
- this.validateDocument(doc);
1746
- }
2019
+ validateDocsBatch(docs, (doc) => this.validateDocument(doc));
1747
2020
  await this.backend.streamInsert(collection, docs);
1748
2021
  }
1749
2022
  /**
@@ -1942,6 +2215,75 @@ var VelesDB = class {
1942
2215
  }
1943
2216
  return this.backend.getNodeDegree(collection, nodeId);
1944
2217
  }
2218
+ // ========================================================================
2219
+ // Graph Collection Management (Phase 8)
2220
+ // ========================================================================
2221
+ /**
2222
+ * Create a graph collection
2223
+ *
2224
+ * @param name - Collection name
2225
+ * @param config - Optional graph collection configuration
2226
+ */
2227
+ async createGraphCollection(name, config) {
2228
+ this.ensureInitialized();
2229
+ requireNonEmptyString(name, "Collection name");
2230
+ await this.backend.createGraphCollection(name, config);
2231
+ }
2232
+ /**
2233
+ * Get collection statistics (requires prior analyze)
2234
+ *
2235
+ * @param collection - Collection name
2236
+ * @returns Statistics or null if not yet analyzed
2237
+ */
2238
+ async getCollectionStats(collection) {
2239
+ this.ensureInitialized();
2240
+ return this.backend.getCollectionStats(collection);
2241
+ }
2242
+ /**
2243
+ * Analyze a collection to compute statistics
2244
+ *
2245
+ * @param collection - Collection name
2246
+ * @returns Computed statistics
2247
+ */
2248
+ async analyzeCollection(collection) {
2249
+ this.ensureInitialized();
2250
+ return this.backend.analyzeCollection(collection);
2251
+ }
2252
+ /**
2253
+ * Get collection configuration
2254
+ *
2255
+ * @param collection - Collection name
2256
+ * @returns Collection configuration details
2257
+ */
2258
+ async getCollectionConfig(collection) {
2259
+ this.ensureInitialized();
2260
+ return this.backend.getCollectionConfig(collection);
2261
+ }
2262
+ /**
2263
+ * Search returning only IDs and scores (lightweight)
2264
+ *
2265
+ * @param collection - Collection name
2266
+ * @param query - Query vector
2267
+ * @param options - Search options
2268
+ * @returns Array of id/score pairs
2269
+ */
2270
+ async searchIds(collection, query2, options) {
2271
+ this.ensureInitialized();
2272
+ return this.backend.searchIds(collection, query2, options);
2273
+ }
2274
+ // ========================================================================
2275
+ // Agent Memory (Phase 8)
2276
+ // ========================================================================
2277
+ /**
2278
+ * Create an agent memory interface
2279
+ *
2280
+ * @param config - Optional agent memory configuration
2281
+ * @returns AgentMemoryClient instance
2282
+ */
2283
+ agentMemory(config) {
2284
+ this.ensureInitialized();
2285
+ return new AgentMemoryClient(this.backend, config);
2286
+ }
1945
2287
  };
1946
2288
 
1947
2289
  // src/query-builder.ts
@@ -2247,6 +2589,7 @@ function velesql() {
2247
2589
  }
2248
2590
  // Annotate the CommonJS export names for ESM import in node:
2249
2591
  0 && (module.exports = {
2592
+ AgentMemoryClient,
2250
2593
  BackpressureError,
2251
2594
  ConnectionError,
2252
2595
  NotFoundError,