@wiscale/velesdb-sdk 1.18.0 → 3.0.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.mjs CHANGED
@@ -43,6 +43,7 @@ var REST_CAPABILITIES = Object.freeze({
43
43
  graphTraversal: true,
44
44
  secondaryIndexes: true,
45
45
  agentMemory: true,
46
+ enableStreaming: true,
46
47
  streamInsert: true,
47
48
  pqTraining: true,
48
49
  velesqlQuery: true,
@@ -58,9 +59,10 @@ var WASM_CAPABILITIES = Object.freeze({
58
59
  graphTraversal: false,
59
60
  secondaryIndexes: false,
60
61
  agentMemory: false,
62
+ enableStreaming: false,
61
63
  streamInsert: false,
62
64
  pqTraining: false,
63
- velesqlQuery: true,
65
+ velesqlQuery: false,
64
66
  collectionIntrospection: false
65
67
  });
66
68
 
@@ -298,20 +300,47 @@ async function wasmMultiQuerySearch(ctx, collectionName, vectors, options) {
298
300
  );
299
301
  return raw.map((r) => mapWasmResult(ctx, collection, r));
300
302
  }
301
- async function wasmQuery(ctx, collectionName, _queryString, params, _options) {
303
+ var PURE_NEAR_QUERY = /^\s*select\s+\*\s+from\s+([a-z_]\w*)\s+where\s+vector\s+near\s+\$([a-z_]\w*)\s*(?:limit\s+(\d+))?\s*;?\s*$/i;
304
+ function parsePureNearQuery(queryString) {
305
+ const match = PURE_NEAR_QUERY.exec(queryString);
306
+ if (!match) {
307
+ throw new VelesDBError(
308
+ `The WASM backend only executes pure top-k NEAR queries of the form "SELECT * FROM <collection> WHERE vector NEAR $param [LIMIT n]". WHERE predicates, JOIN, GROUP BY, MATCH, set operations and FUSION are not evaluated in WASM \u2014 use the REST backend (velesdb-server) for full VelesQL. Received: ${queryString}`,
309
+ "NOT_SUPPORTED"
310
+ );
311
+ }
312
+ const parsed = { from: match[1], param: match[2] };
313
+ if (match[3] !== void 0) {
314
+ parsed.limit = Number(match[3]);
315
+ }
316
+ return parsed;
317
+ }
318
+ function resolveQueryK(limit, requestedK) {
319
+ if (limit !== void 0) {
320
+ return limit;
321
+ }
322
+ return typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
323
+ }
324
+ async function wasmQuery(ctx, collectionName, queryString, params, _options) {
302
325
  const collection = ctx.getCollection(collectionName);
303
326
  if (!collection) {
304
327
  throw new NotFoundError(`Collection '${collectionName}'`);
305
328
  }
306
- const paramsVector = params?.q;
329
+ const parsed = parsePureNearQuery(queryString);
330
+ if (parsed.from !== collectionName) {
331
+ throw new VelesDBError(
332
+ `Query targets collection '${parsed.from}' but was executed against '${collectionName}'.`,
333
+ "BAD_REQUEST"
334
+ );
335
+ }
336
+ const paramsVector = params?.[parsed.param];
307
337
  if (!Array.isArray(paramsVector) && !(paramsVector instanceof Float32Array)) {
308
338
  throw new VelesDBError(
309
- "WASM query() expects params.q to contain the query embedding vector.",
339
+ `WASM query() expects params.${parsed.param} to contain the query embedding vector.`,
310
340
  "BAD_REQUEST"
311
341
  );
312
342
  }
313
- const requestedK = params?.k;
314
- const k = typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
343
+ const k = resolveQueryK(parsed.limit, params?.k);
315
344
  const raw = collection.store.query(
316
345
  paramsVector instanceof Float32Array ? paramsVector : new Float32Array(paramsVector),
317
346
  k
@@ -725,6 +754,9 @@ function validateCollectionName(name) {
725
754
  );
726
755
  }
727
756
  }
757
+ async function safeJsonParse(response) {
758
+ return response.json().catch(() => ({}));
759
+ }
728
760
  function toNumberArray(v) {
729
761
  return v instanceof Float32Array ? Array.from(v) : v;
730
762
  }
@@ -778,12 +810,18 @@ async function wasmScroll(_collection, _request) {
778
810
  async function wasmTrainPq(_collection, _options) {
779
811
  wasmNotSupported("PQ training");
780
812
  }
813
+ async function wasmEnableStreaming(_collection, _config) {
814
+ wasmNotSupported("Streaming enable");
815
+ }
781
816
  async function wasmStreamInsert(_collection, _docs) {
782
817
  wasmNotSupported("Streaming insert");
783
818
  }
784
819
  async function wasmStreamUpsertPoints(_collection, _docs) {
785
820
  wasmNotSupported("Streaming batch upsert");
786
821
  }
822
+ async function wasmUpsertBatchRaw(_collection, _docs) {
823
+ wasmNotSupported("Binary bulk upsert (upsertBatchRaw)");
824
+ }
787
825
  async function wasmCreateGraphCollection(_name, _config) {
788
826
  wasmNotSupported("Graph collections");
789
827
  }
@@ -799,6 +837,9 @@ async function wasmGetCollectionConfig(_collection) {
799
837
  async function wasmSearchIds(_collection, _query, _options) {
800
838
  wasmNotSupported("searchIds");
801
839
  }
840
+ async function wasmMultiQuerySearchIds(_collection, _vectors, _options) {
841
+ wasmNotSupported("multiQuerySearchIds");
842
+ }
802
843
  async function wasmStoreSemanticFact(_collection, _entry) {
803
844
  wasmNotSupported("Agent memory");
804
845
  }
@@ -811,6 +852,12 @@ async function wasmRecordEpisodicEvent(_collection, _event) {
811
852
  async function wasmRecallEpisodicEvents(_collection, _embedding, _k) {
812
853
  wasmNotSupported("Agent memory");
813
854
  }
855
+ async function wasmRecallRecentEvents(_collection, _since) {
856
+ wasmNotSupported("Agent memory");
857
+ }
858
+ async function wasmRecallOlderThanEvents(_collection, _before) {
859
+ wasmNotSupported("Agent memory");
860
+ }
814
861
  async function wasmStoreProceduralPattern(_collection, _pattern) {
815
862
  wasmNotSupported("Agent memory");
816
863
  }
@@ -858,6 +905,18 @@ function wasmUpsertNodePayload(_c, _id, _p) {
858
905
  function wasmGraphSearch(_c, _r) {
859
906
  return Promise.resolve(wasmNotSupported("Graph search"));
860
907
  }
908
+ function wasmRelate(_c, _req) {
909
+ return Promise.resolve(wasmNotSupported("Relation edges"));
910
+ }
911
+ function wasmUnrelate(_c, _id) {
912
+ return Promise.resolve(wasmNotSupported("Relation edge removal"));
913
+ }
914
+ function wasmGetRelations(_c, _id) {
915
+ return Promise.resolve(wasmNotSupported("Relation edges"));
916
+ }
917
+ function wasmSetTtlDurable(_c, _id, _ttl) {
918
+ return Promise.resolve(wasmNotSupported("Durable TTL"));
919
+ }
861
920
 
862
921
  // src/backends/wasm.ts
863
922
  var WasmBackend = class {
@@ -1037,6 +1096,9 @@ var WasmBackend = class {
1037
1096
  }
1038
1097
  }
1039
1098
  }
1099
+ async upsertBatchRaw(c, d) {
1100
+ return wasmUpsertBatchRaw(c, d);
1101
+ }
1040
1102
  async delete(collectionName, id) {
1041
1103
  this.ensureInitialized();
1042
1104
  const collection = this.collections.get(collectionName);
@@ -1168,6 +1230,10 @@ var WasmBackend = class {
1168
1230
  this.ensureInitialized();
1169
1231
  return wasmTrainPq(c, o);
1170
1232
  }
1233
+ async enableStreaming(c, cfg) {
1234
+ this.ensureInitialized();
1235
+ return wasmEnableStreaming(c, cfg);
1236
+ }
1171
1237
  async streamInsert(c, d) {
1172
1238
  this.ensureInitialized();
1173
1239
  return wasmStreamInsert(c, d);
@@ -1196,6 +1262,10 @@ var WasmBackend = class {
1196
1262
  this.ensureInitialized();
1197
1263
  return wasmSearchIds(c, q, o);
1198
1264
  }
1265
+ async multiQuerySearchIds(c, v, o) {
1266
+ this.ensureInitialized();
1267
+ return wasmMultiQuerySearchIds(c, v, o);
1268
+ }
1199
1269
  async storeSemanticFact(c, e) {
1200
1270
  this.ensureInitialized();
1201
1271
  return wasmStoreSemanticFact(c, e);
@@ -1212,6 +1282,14 @@ var WasmBackend = class {
1212
1282
  this.ensureInitialized();
1213
1283
  return wasmRecallEpisodicEvents(c, e, k);
1214
1284
  }
1285
+ async recallRecentEvents(c, since) {
1286
+ this.ensureInitialized();
1287
+ return wasmRecallRecentEvents(c, since);
1288
+ }
1289
+ async recallOlderThanEvents(c, before) {
1290
+ this.ensureInitialized();
1291
+ return wasmRecallOlderThanEvents(c, before);
1292
+ }
1215
1293
  async storeProceduralPattern(c, p) {
1216
1294
  this.ensureInitialized();
1217
1295
  return wasmStoreProceduralPattern(c, p);
@@ -1273,16 +1351,46 @@ var WasmBackend = class {
1273
1351
  this.ensureInitialized();
1274
1352
  return wasmSparseSearchNamed(c, q, idx, o);
1275
1353
  }
1354
+ async relate(c, req) {
1355
+ this.ensureInitialized();
1356
+ return wasmRelate(c, req);
1357
+ }
1358
+ async unrelate(c, edgeId) {
1359
+ this.ensureInitialized();
1360
+ return wasmUnrelate(c, edgeId);
1361
+ }
1362
+ async getRelations(c, pointId) {
1363
+ this.ensureInitialized();
1364
+ return wasmGetRelations(c, pointId);
1365
+ }
1366
+ async setTtlDurable(c, pointId, ttlSeconds) {
1367
+ this.ensureInitialized();
1368
+ return wasmSetTtlDurable(c, pointId, ttlSeconds);
1369
+ }
1276
1370
  };
1277
1371
 
1278
1372
  // src/backends/crud-backend.ts
1373
+ var RAW_BULK_HEADER_LEN = 16;
1374
+ var RAW_BULK_MAGIC = [86, 82, 66, 49];
1375
+ var RAW_BULK_ID_WIDTH = 8;
1376
+ var U64_MAX = 18446744073709551615n;
1377
+ function coerceDecimalStringId(id) {
1378
+ if (!/^\d+$/.test(id)) return NaN;
1379
+ const big = BigInt(id);
1380
+ if (big > U64_MAX) return NaN;
1381
+ return big > BigInt(Number.MAX_SAFE_INTEGER) ? id : Number(id);
1382
+ }
1279
1383
  function parseRestPointId(id) {
1280
- if (typeof id !== "number" || !Number.isFinite(id) || id < 0 || !Number.isInteger(id) || id > Number.MAX_SAFE_INTEGER) {
1384
+ const coerced = typeof id === "string" ? coerceDecimalStringId(id) : id;
1385
+ if (typeof coerced === "string") {
1386
+ return coerced;
1387
+ }
1388
+ if (!Number.isFinite(coerced) || coerced < 0 || !Number.isInteger(coerced) || coerced > Number.MAX_SAFE_INTEGER) {
1281
1389
  throw new ValidationError(
1282
- `REST backend requires numeric u64-compatible IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER}). Received: ${String(id)}`
1390
+ `REST backend requires numeric u64-compatible IDs: a non-negative integer in the JS safe integer range (0..${Number.MAX_SAFE_INTEGER}) or a decimal string up to u64::MAX (${U64_MAX}). Received: ${String(id)}`
1283
1391
  );
1284
1392
  }
1285
- return id;
1393
+ return coerced;
1286
1394
  }
1287
1395
  function sparseVectorToRestFormat(sv) {
1288
1396
  const result = {};
@@ -1415,6 +1523,105 @@ async function flush(transport, collection) {
1415
1523
  );
1416
1524
  throwOnError(response, `Collection '${collection}'`);
1417
1525
  }
1526
+ function encodeRawBulk(ids, vectors, dim) {
1527
+ const count = ids.length;
1528
+ if (vectors.length !== count) {
1529
+ throw new ValidationError(
1530
+ `encodeRawBulk: ids length (${count}) must match vectors length (${vectors.length})`
1531
+ );
1532
+ }
1533
+ const buf = new Uint8Array(RAW_BULK_HEADER_LEN + count * 8 + count * dim * 4);
1534
+ const view = new DataView(buf.buffer);
1535
+ buf.set(RAW_BULK_MAGIC, 0);
1536
+ view.setUint32(4, count, true);
1537
+ view.setUint32(8, dim, true);
1538
+ buf[12] = RAW_BULK_ID_WIDTH;
1539
+ writeIds(view, ids);
1540
+ writeVectors(view, vectors, dim, count);
1541
+ return buf;
1542
+ }
1543
+ function writeIds(view, ids) {
1544
+ let off = RAW_BULK_HEADER_LEN;
1545
+ for (const id of ids) {
1546
+ view.setBigUint64(off, BigInt(id), true);
1547
+ off += 8;
1548
+ }
1549
+ }
1550
+ function writeVectors(view, vectors, dim, count) {
1551
+ let off = RAW_BULK_HEADER_LEN + count * 8;
1552
+ for (const vec of vectors) {
1553
+ if (vec.length !== dim) {
1554
+ throw new ValidationError(
1555
+ `encodeRawBulk: vector length (${vec.length}) must match dim (${dim})`
1556
+ );
1557
+ }
1558
+ for (let i = 0; i < dim; i++) {
1559
+ view.setFloat32(off, vec[i] ?? 0, true);
1560
+ off += 4;
1561
+ }
1562
+ }
1563
+ }
1564
+ async function upsertBatchRaw(transport, collection, docs, dim) {
1565
+ const ids = docs.map((d) => coerceNumericId(d.id));
1566
+ const vectors = docs.map((d) => d.vector);
1567
+ const body = encodeRawBulk(ids, vectors, dim);
1568
+ return sendRawBulk(transport, collection, body);
1569
+ }
1570
+ function coerceNumericId(id) {
1571
+ const parsed = parseRestPointId(id);
1572
+ if (typeof parsed === "string") {
1573
+ throw new ValidationError(
1574
+ `upsertBatchRaw requires ids in the JS safe integer range; received: ${parsed}`
1575
+ );
1576
+ }
1577
+ return parsed;
1578
+ }
1579
+ async function sendRawBulk(transport, collection, body) {
1580
+ const url = `${transport.baseUrl}${collectionPath(collection)}/points/raw`;
1581
+ const headers = {
1582
+ "Content-Type": "application/octet-stream"
1583
+ };
1584
+ if (transport.apiKey) {
1585
+ headers["Authorization"] = `Bearer ${transport.apiKey}`;
1586
+ }
1587
+ const controller = new AbortController();
1588
+ const timeoutId = setTimeout(() => controller.abort(), transport.timeout);
1589
+ try {
1590
+ const response = await fetch(url, {
1591
+ method: "POST",
1592
+ headers,
1593
+ body,
1594
+ signal: controller.signal
1595
+ });
1596
+ clearTimeout(timeoutId);
1597
+ return await parseRawBulkResponse(response);
1598
+ } catch (error) {
1599
+ clearTimeout(timeoutId);
1600
+ throw wrapRawBulkError(error);
1601
+ }
1602
+ }
1603
+ async function parseRawBulkResponse(response) {
1604
+ const data = await safeJsonParse(response);
1605
+ if (!response.ok) {
1606
+ const code = typeof data.code === "string" ? data.code : `HTTP_${response.status}`;
1607
+ const message = typeof data.error === "string" ? data.error : `HTTP ${response.status}`;
1608
+ throw new VelesDBError(message, code);
1609
+ }
1610
+ return typeof data.count === "number" ? data.count : 0;
1611
+ }
1612
+ function wrapRawBulkError(error) {
1613
+ if (error instanceof VelesDBError) {
1614
+ return error;
1615
+ }
1616
+ if (error instanceof Error && error.name === "AbortError") {
1617
+ return new ConnectionError("Request timeout");
1618
+ }
1619
+ const message = error instanceof Error ? error.message : "Unknown error";
1620
+ return new ConnectionError(
1621
+ `Raw bulk upsert failed: ${message}`,
1622
+ error instanceof Error ? error : void 0
1623
+ );
1624
+ }
1418
1625
 
1419
1626
  // src/backends/rest-http.ts
1420
1627
  var STATUS_ERROR_CODES = {
@@ -1485,7 +1692,7 @@ async function request(config, method, path, body) {
1485
1692
  signal: controller.signal
1486
1693
  });
1487
1694
  clearTimeout(timeoutId);
1488
- const data = await response.json().catch(() => ({}));
1695
+ const data = await safeJsonParse(response);
1489
1696
  if (!response.ok) {
1490
1697
  const ep = extractErrorPayload(data);
1491
1698
  return { error: {
@@ -1509,6 +1716,13 @@ function buildCrudTransport(config) {
1509
1716
  requestJson: (m, p, b) => request(config, m, p, b)
1510
1717
  };
1511
1718
  }
1719
+ function buildRawBulkTransport(config) {
1720
+ return {
1721
+ baseUrl: config.baseUrl,
1722
+ apiKey: config.apiKey,
1723
+ timeout: config.timeout
1724
+ };
1725
+ }
1512
1726
  function buildSearchTransport(config) {
1513
1727
  return {
1514
1728
  requestJson: (m, p, b) => request(config, m, p, b),
@@ -1737,6 +1951,31 @@ async function graphSearch(transport, collection, request2) {
1737
1951
  return { results: items };
1738
1952
  }
1739
1953
 
1954
+ // src/backends/scroll-backend.ts
1955
+ async function scroll(transport, collection, request2) {
1956
+ const body = {};
1957
+ if (request2?.cursor !== void 0) {
1958
+ body.cursor = request2.cursor;
1959
+ }
1960
+ if (request2?.batchSize !== void 0) {
1961
+ body.batch_size = request2.batchSize;
1962
+ }
1963
+ if (request2?.filter !== void 0) {
1964
+ body.filter = request2.filter;
1965
+ }
1966
+ const response = await transport.requestJson(
1967
+ "POST",
1968
+ `${collectionPath(collection)}/points/scroll`,
1969
+ body
1970
+ );
1971
+ throwOnError(response, `Collection '${collection}'`);
1972
+ const data = response.data;
1973
+ return {
1974
+ points: data.points,
1975
+ nextCursor: data.next_cursor
1976
+ };
1977
+ }
1978
+
1740
1979
  // src/backends/agent-memory-backend.ts
1741
1980
  var _idCounter = 0;
1742
1981
  var _lastTimestamp = 0;
@@ -1754,20 +1993,28 @@ function generateUniqueId() {
1754
1993
  }
1755
1994
  return _lastTimestamp * 1e3 + _idCounter;
1756
1995
  }
1996
+ function memoryIdToString(id) {
1997
+ return String(id);
1998
+ }
1999
+ function nowUnixSeconds() {
2000
+ return Math.floor(Date.now() / 1e3);
2001
+ }
1757
2002
  async function storeSemanticFact(transport, collection, entry) {
1758
2003
  const response = await transport.requestJson(
1759
2004
  "POST",
1760
2005
  `${collectionPath(collection)}/points`,
1761
2006
  {
1762
2007
  points: [{
1763
- id: entry.id,
2008
+ id: parseRestPointId(entry.id),
1764
2009
  vector: entry.embedding,
1765
2010
  payload: {
1766
2011
  // Caller metadata is spread first so the reserved keys below
1767
- // (`_memory_type`, `text`) always win and cannot be clobbered.
2012
+ // (`_memory_type`, `content`) always win and cannot be clobbered.
1768
2013
  ...entry.metadata,
1769
2014
  _memory_type: "semantic",
1770
- text: entry.text
2015
+ // `content` matches the core semantic store and the server/Python
2016
+ // payload field (BREAKING: was `text` before this change).
2017
+ content: entry.text
1771
2018
  }
1772
2019
  }]
1773
2020
  }
@@ -1778,7 +2025,8 @@ async function searchSemanticMemory(transport, collection, embedding, k = 5) {
1778
2025
  return transport.searchVectors(collection, embedding, k, { _memory_type: "semantic" });
1779
2026
  }
1780
2027
  async function recordEpisodicEvent(transport, collection, event) {
1781
- const id = generateUniqueId();
2028
+ const id = event.id !== void 0 ? parseRestPointId(event.id) : generateUniqueId();
2029
+ const timestamp = event.timestamp ?? nowUnixSeconds();
1782
2030
  const response = await transport.requestJson(
1783
2031
  "POST",
1784
2032
  `${collectionPath(collection)}/points`,
@@ -1794,19 +2042,21 @@ async function recordEpisodicEvent(transport, collection, event) {
1794
2042
  ...event.metadata,
1795
2043
  _memory_type: "episodic",
1796
2044
  event_type: event.eventType,
1797
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2045
+ // NUMERIC unix-seconds, mirroring the core episodic store so
2046
+ // recallRecent/recallOlderThan can range-filter on it.
2047
+ timestamp
1798
2048
  }
1799
2049
  }]
1800
2050
  }
1801
2051
  );
1802
2052
  throwOnError(response);
1803
- return id;
2053
+ return memoryIdToString(id);
1804
2054
  }
1805
2055
  async function recallEpisodicEvents(transport, collection, embedding, k = 5) {
1806
2056
  return transport.searchVectors(collection, embedding, k, { _memory_type: "episodic" });
1807
2057
  }
1808
2058
  async function storeProceduralPattern(transport, collection, pattern) {
1809
- const id = generateUniqueId();
2059
+ const id = pattern.id !== void 0 ? parseRestPointId(pattern.id) : generateUniqueId();
1810
2060
  const response = await transport.requestJson(
1811
2061
  "POST",
1812
2062
  `${collectionPath(collection)}/points`,
@@ -1827,11 +2077,41 @@ async function storeProceduralPattern(transport, collection, pattern) {
1827
2077
  }
1828
2078
  );
1829
2079
  throwOnError(response);
1830
- return id;
2080
+ return memoryIdToString(id);
1831
2081
  }
1832
2082
  async function matchProceduralPatterns(transport, collection, embedding, k = 5) {
1833
2083
  return transport.searchVectors(collection, embedding, k, { _memory_type: "procedural" });
1834
2084
  }
2085
+ function toEpisodicRecord(point) {
2086
+ const payload = point.payload ?? {};
2087
+ if (payload._memory_type !== "episodic") return void 0;
2088
+ if (typeof payload.timestamp !== "number") return void 0;
2089
+ return { id: String(point.id), timestamp: payload.timestamp, payload };
2090
+ }
2091
+ async function scrollEpisodicRecords(transport, collection) {
2092
+ const records = [];
2093
+ let cursor = null;
2094
+ do {
2095
+ const page = await scroll(transport, collection, {
2096
+ cursor: cursor ?? void 0,
2097
+ filter: { _memory_type: "episodic" }
2098
+ });
2099
+ for (const point of page.points) {
2100
+ const record = toEpisodicRecord(point);
2101
+ if (record !== void 0) records.push(record);
2102
+ }
2103
+ cursor = page.nextCursor;
2104
+ } while (cursor !== null && cursor !== void 0);
2105
+ return records;
2106
+ }
2107
+ async function recallRecentEvents(transport, collection, since) {
2108
+ const records = await scrollEpisodicRecords(transport, collection);
2109
+ return records.filter((r) => since === void 0 || r.timestamp >= since).sort((a, b) => b.timestamp - a.timestamp);
2110
+ }
2111
+ async function recallOlderThanEvents(transport, collection, before) {
2112
+ const records = await scrollEpisodicRecords(transport, collection);
2113
+ return records.filter((r) => r.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
2114
+ }
1835
2115
 
1836
2116
  // src/search-quality.ts
1837
2117
  function searchQualityToMode(quality) {
@@ -1930,6 +2210,22 @@ async function multiQuerySearch(transport, collection, vectors, options) {
1930
2210
  throwOnError(response, `Collection '${collection}'`);
1931
2211
  return response.data?.results ?? [];
1932
2212
  }
2213
+ async function multiQuerySearchIds(transport, collection, vectors, options) {
2214
+ const formattedVectors = vectors.map(toNumberArray);
2215
+ const response = await transport.requestJson("POST", `${collectionPath(collection)}/search/multi/ids`, {
2216
+ vectors: formattedVectors,
2217
+ top_k: options?.k ?? 10,
2218
+ strategy: options?.fusion ?? "rrf",
2219
+ rrf_k: options?.fusionParams?.k ?? 60,
2220
+ avg_weight: options?.fusionParams?.avgWeight,
2221
+ max_weight: options?.fusionParams?.maxWeight,
2222
+ hit_weight: options?.fusionParams?.hitWeight,
2223
+ dense_weight: options?.fusionParams?.denseWeight,
2224
+ sparse_weight: options?.fusionParams?.sparseWeight
2225
+ });
2226
+ throwOnError(response, `Collection '${collection}'`);
2227
+ return response.data?.results ?? [];
2228
+ }
1933
2229
  async function sparseSearchNamed(transport, collection, query3, indexName, options) {
1934
2230
  const body = {
1935
2231
  sparse_vectors: { [indexName]: transport.sparseToRest(query3) },
@@ -2064,6 +2360,64 @@ async function traverseParallel(transport, collection, request2) {
2064
2360
  throwOnError(response, `Collection '${collection}'`);
2065
2361
  return toTraverseResponse(response.data);
2066
2362
  }
2363
+ async function relate(transport, collection, req) {
2364
+ const response = await transport.requestJson(
2365
+ "POST",
2366
+ `${collectionPath(collection)}/relations`,
2367
+ {
2368
+ source: req.source,
2369
+ target: req.target,
2370
+ rel_type: req.relType,
2371
+ properties: req.properties ?? {}
2372
+ }
2373
+ );
2374
+ throwOnError(response, `Collection '${collection}'`);
2375
+ return { edgeId: response.data.edge_id };
2376
+ }
2377
+ async function unrelate(transport, collection, edgeId) {
2378
+ const response = await transport.requestJson(
2379
+ "DELETE",
2380
+ `${collectionPath(collection)}/relations/${encodeURIComponent(String(edgeId))}`
2381
+ );
2382
+ if (response.error !== void 0) {
2383
+ const { code, message } = response.error;
2384
+ const err = parseVelesError(code, message);
2385
+ if (err instanceof EdgeNotFoundError) {
2386
+ return false;
2387
+ }
2388
+ if (code === "NOT_FOUND") {
2389
+ return false;
2390
+ }
2391
+ throwOnError(response, `Collection '${collection}'`);
2392
+ }
2393
+ return true;
2394
+ }
2395
+ async function getRelations(transport, collection, pointId) {
2396
+ const response = await transport.requestJson(
2397
+ "GET",
2398
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(pointId))}/relations`
2399
+ );
2400
+ throwOnError(response, `Collection '${collection}'`);
2401
+ const raw = response.data;
2402
+ return {
2403
+ edges: raw.edges.map((e) => ({
2404
+ id: e.id,
2405
+ source: e.source,
2406
+ target: e.target,
2407
+ relType: e.rel_type,
2408
+ properties: e.properties
2409
+ })),
2410
+ count: raw.count
2411
+ };
2412
+ }
2413
+ async function setTtlDurable(transport, collection, pointId, ttlSeconds) {
2414
+ const response = await transport.requestJson(
2415
+ "PATCH",
2416
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(pointId))}/ttl`,
2417
+ { ttl_seconds: ttlSeconds }
2418
+ );
2419
+ throwOnError(response, `Collection '${collection}'`);
2420
+ }
2067
2421
 
2068
2422
  // src/backends/query-backend.ts
2069
2423
  function isLikelyAggregationQuery(queryString) {
@@ -2089,7 +2443,7 @@ async function query(transport, collection, queryString, params, options) {
2089
2443
  );
2090
2444
  throwOnError(response, `Collection '${collection}'`);
2091
2445
  const rawData = response.data;
2092
- if (rawData && Object.prototype.hasOwnProperty.call(rawData, "result")) {
2446
+ if (rawData != null && Object.prototype.hasOwnProperty.call(rawData, "result")) {
2093
2447
  return {
2094
2448
  result: rawData.result,
2095
2449
  stats: {
@@ -2196,31 +2550,6 @@ async function collectionSanity(transport, collection) {
2196
2550
  };
2197
2551
  }
2198
2552
 
2199
- // src/backends/scroll-backend.ts
2200
- async function scroll(transport, collection, request2) {
2201
- const body = {};
2202
- if (request2?.cursor !== void 0) {
2203
- body.cursor = request2.cursor;
2204
- }
2205
- if (request2?.batchSize !== void 0) {
2206
- body.batch_size = request2.batchSize;
2207
- }
2208
- if (request2?.filter !== void 0) {
2209
- body.filter = request2.filter;
2210
- }
2211
- const response = await transport.requestJson(
2212
- "POST",
2213
- `${collectionPath(collection)}/points/scroll`,
2214
- body
2215
- );
2216
- throwOnError(response, `Collection '${collection}'`);
2217
- const data = response.data;
2218
- return {
2219
- points: data.points,
2220
- nextCursor: data.next_cursor
2221
- };
2222
- }
2223
-
2224
2553
  // src/backends/admin-backend.ts
2225
2554
  function mapStatsResponse(data) {
2226
2555
  let columnStats;
@@ -2384,7 +2713,7 @@ async function streamInsert(transport, collection, docs) {
2384
2713
  throw new BackpressureError();
2385
2714
  }
2386
2715
  if (!response.ok && response.status !== 202) {
2387
- const data = await response.json().catch(() => ({}));
2716
+ const data = await safeJsonParse(response);
2388
2717
  const errorPayload = transport.extractErrorPayload(data);
2389
2718
  throw new VelesDBError(
2390
2719
  errorPayload.message ?? `HTTP ${response.status}`,
@@ -2406,9 +2735,35 @@ async function streamInsert(transport, collection, docs) {
2406
2735
  }
2407
2736
  }
2408
2737
  }
2738
+ async function enableStreaming(transport, collection, config) {
2739
+ const body = {};
2740
+ if (config?.bufferSize !== void 0) {
2741
+ body.buffer_size = config.bufferSize;
2742
+ }
2743
+ if (config?.batchSize !== void 0) {
2744
+ body.batch_size = config.batchSize;
2745
+ }
2746
+ if (config?.flushIntervalMs !== void 0) {
2747
+ body.flush_interval_ms = config.flushIntervalMs;
2748
+ }
2749
+ const response = await transport.requestJson(
2750
+ "POST",
2751
+ `${collectionPath(collection)}/stream/enable`,
2752
+ body
2753
+ );
2754
+ throwOnError(response);
2755
+ }
2756
+ function requireSafeRangeId(restId) {
2757
+ if (typeof restId === "string") {
2758
+ throw new ValidationError(
2759
+ `streamUpsertPoints requires ids in the JS safe integer range (0..${Number.MAX_SAFE_INTEGER}); use upsert/upsertBatch for string ids above it. Received: ${restId}`
2760
+ );
2761
+ }
2762
+ return restId;
2763
+ }
2409
2764
  async function streamUpsertPoints(transport, collection, docs) {
2410
2765
  const ndjsonLines = docs.map((doc) => {
2411
- const restId = transport.parseRestPointId(doc.id);
2766
+ const restId = requireSafeRangeId(transport.parseRestPointId(doc.id));
2412
2767
  const vector = toNumberArray(doc.vector);
2413
2768
  const point = {
2414
2769
  id: restId,
@@ -2442,14 +2797,14 @@ async function streamUpsertPoints(transport, collection, docs) {
2442
2797
  throw new BackpressureError();
2443
2798
  }
2444
2799
  if (!response.ok) {
2445
- const data2 = await response.json().catch(() => ({}));
2800
+ const data2 = await safeJsonParse(response);
2446
2801
  const errorPayload = transport.extractErrorPayload(data2);
2447
2802
  throw new VelesDBError(
2448
2803
  errorPayload.message ?? `HTTP ${response.status}`,
2449
2804
  errorPayload.code ?? transport.mapStatusToErrorCode(response.status)
2450
2805
  );
2451
2806
  }
2452
- const data = await response.json().catch(() => ({}));
2807
+ const data = await safeJsonParse(response);
2453
2808
  return {
2454
2809
  message: typeof data.message === "string" ? data.message : "Stream processed",
2455
2810
  inserted: typeof data.inserted === "number" ? data.inserted : 0,
@@ -2534,6 +2889,10 @@ var RestBackend = class {
2534
2889
  this.ensureInitialized();
2535
2890
  return upsertBatch(buildCrudTransport(this.httpConfig), c, d);
2536
2891
  }
2892
+ async upsertBatchRaw(c, d) {
2893
+ this.ensureInitialized();
2894
+ return upsertBatchRaw(buildRawBulkTransport(this.httpConfig), c, d, d[0]?.vector.length ?? 0);
2895
+ }
2537
2896
  async delete(c, id) {
2538
2897
  this.ensureInitialized();
2539
2898
  return deletePoint(buildCrudTransport(this.httpConfig), c, id);
@@ -2599,6 +2958,22 @@ var RestBackend = class {
2599
2958
  this.ensureInitialized();
2600
2959
  return graphSearch(buildBaseTransport(this.httpConfig), c, r);
2601
2960
  }
2961
+ async relate(c, req) {
2962
+ this.ensureInitialized();
2963
+ return relate(buildCrudTransport(this.httpConfig), c, req);
2964
+ }
2965
+ async unrelate(c, edgeId) {
2966
+ this.ensureInitialized();
2967
+ return unrelate(buildCrudTransport(this.httpConfig), c, edgeId);
2968
+ }
2969
+ async getRelations(c, pointId) {
2970
+ this.ensureInitialized();
2971
+ return getRelations(buildCrudTransport(this.httpConfig), c, pointId);
2972
+ }
2973
+ async setTtlDurable(c, pointId, ttlSeconds) {
2974
+ this.ensureInitialized();
2975
+ return setTtlDurable(buildCrudTransport(this.httpConfig), c, pointId, ttlSeconds);
2976
+ }
2602
2977
  // Search
2603
2978
  async search(c, q, o) {
2604
2979
  this.ensureInitialized();
@@ -2620,6 +2995,10 @@ var RestBackend = class {
2620
2995
  this.ensureInitialized();
2621
2996
  return multiQuerySearch(buildSearchTransport(this.httpConfig), c, v, o);
2622
2997
  }
2998
+ async multiQuerySearchIds(c, v, o) {
2999
+ this.ensureInitialized();
3000
+ return multiQuerySearchIds(buildSearchTransport(this.httpConfig), c, v, o);
3001
+ }
2623
3002
  async searchIds(c, q, o) {
2624
3003
  this.ensureInitialized();
2625
3004
  return searchIds(buildSearchTransport(this.httpConfig), c, q, o);
@@ -2706,6 +3085,10 @@ var RestBackend = class {
2706
3085
  this.ensureInitialized();
2707
3086
  return trainPq(buildStreamingTransport(this.httpConfig), c, o);
2708
3087
  }
3088
+ async enableStreaming(c, cfg) {
3089
+ this.ensureInitialized();
3090
+ return enableStreaming(buildStreamingTransport(this.httpConfig), c, cfg);
3091
+ }
2709
3092
  async streamInsert(c, d) {
2710
3093
  this.ensureInitialized();
2711
3094
  return streamInsert(buildStreamingTransport(this.httpConfig), c, d);
@@ -2731,6 +3114,14 @@ var RestBackend = class {
2731
3114
  this.ensureInitialized();
2732
3115
  return recallEpisodicEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, e, k);
2733
3116
  }
3117
+ async recallRecentEvents(c, since) {
3118
+ this.ensureInitialized();
3119
+ return recallRecentEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, since);
3120
+ }
3121
+ async recallOlderThanEvents(c, before) {
3122
+ this.ensureInitialized();
3123
+ return recallOlderThanEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, before);
3124
+ }
2734
3125
  async storeProceduralPattern(c, p) {
2735
3126
  this.ensureInitialized();
2736
3127
  return storeProceduralPattern(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, p);
@@ -2768,15 +3159,29 @@ var AgentMemoryClient = class {
2768
3159
  async searchFacts(collection, embedding, k = 5) {
2769
3160
  return this.backend.searchSemanticMemory(collection, embedding, k);
2770
3161
  }
2771
- /** Record an episodic event. Returns the generated point ID. */
3162
+ /** Record an episodic event. Returns the point ID (string, u64-safe). */
2772
3163
  async recordEvent(collection, event) {
2773
3164
  return this.backend.recordEpisodicEvent(collection, event);
2774
3165
  }
2775
- /** Recall episodic events */
3166
+ /** Recall episodic events by vector similarity. */
2776
3167
  async recallEvents(collection, embedding, k = 5) {
2777
3168
  return this.backend.recallEpisodicEvents(collection, embedding, k);
2778
3169
  }
2779
- /** Store a procedural pattern. Returns the generated point ID. */
3170
+ /**
3171
+ * Recall episodic events most-recent-first, optionally bounded below by
3172
+ * `since` (inclusive unix-seconds). Mirrors core `episodic.recent(since)`.
3173
+ */
3174
+ async recallRecent(collection, since) {
3175
+ return this.backend.recallRecentEvents(collection, since);
3176
+ }
3177
+ /**
3178
+ * Recall episodic events strictly older than `before` (unix-seconds),
3179
+ * most-recent-first. Mirrors core `episodic.older_than(before)`.
3180
+ */
3181
+ async recallOlderThan(collection, before) {
3182
+ return this.backend.recallOlderThanEvents(collection, before);
3183
+ }
3184
+ /** Store a procedural pattern. Returns the point ID (string, u64-safe). */
2780
3185
  async learnProcedure(collection, pattern) {
2781
3186
  return this.backend.storeProceduralPattern(collection, pattern);
2782
3187
  }
@@ -2784,7 +3189,12 @@ var AgentMemoryClient = class {
2784
3189
  async recallProcedures(collection, embedding, k = 5) {
2785
3190
  return this.backend.matchProceduralPatterns(collection, embedding, k);
2786
3191
  }
2787
- /** Delete a memory entry (fact, event, or procedure) by its point ID. */
3192
+ /**
3193
+ * Delete a memory entry (fact, event, or procedure) by its point ID.
3194
+ *
3195
+ * Accepts the `string` ids returned by `recordEvent` / `learnProcedure`
3196
+ * (u64-safe decimal strings) as well as numeric ids.
3197
+ */
2788
3198
  async deleteMemory(collection, id) {
2789
3199
  return this.backend.delete(collection, id);
2790
3200
  }
@@ -2818,11 +3228,10 @@ function validateDocument(doc, config) {
2818
3228
  validateRestPointId(doc.id, config);
2819
3229
  }
2820
3230
  function validateRestPointId(id, config) {
2821
- if (config.backend === "rest" && (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > Number.MAX_SAFE_INTEGER)) {
2822
- throw new ValidationError(
2823
- `REST backend requires numeric u64-compatible document IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER})`
2824
- );
3231
+ if (config.backend !== "rest") {
3232
+ return;
2825
3233
  }
3234
+ parseRestPointId(id);
2826
3235
  }
2827
3236
 
2828
3237
  // src/client/search-methods.ts
@@ -2862,9 +3271,21 @@ function multiQuerySearch2(backend, collection, vectors, options) {
2862
3271
  }
2863
3272
  return backend.multiQuerySearch(collection, vectors, options);
2864
3273
  }
3274
+ function multiQuerySearchIds2(backend, collection, vectors, options) {
3275
+ if (!Array.isArray(vectors) || vectors.length === 0) {
3276
+ throw new ValidationError("Vectors must be a non-empty array");
3277
+ }
3278
+ for (const v of vectors) {
3279
+ requireVector(v, "Each vector");
3280
+ }
3281
+ return backend.multiQuerySearchIds(collection, vectors, options);
3282
+ }
2865
3283
  function trainPq2(backend, collection, options) {
2866
3284
  return backend.trainPq(collection, options);
2867
3285
  }
3286
+ function enableStreaming2(backend, collection, config) {
3287
+ return backend.enableStreaming(collection, config);
3288
+ }
2868
3289
  function streamInsert2(backend, config, collection, docs) {
2869
3290
  validateDocsBatch(docs, (doc) => {
2870
3291
  validateDocument(doc, config);
@@ -3003,6 +3424,40 @@ function graphSearch2(backend, collection, request2) {
3003
3424
  requireNonEmptyString(collection, "Collection");
3004
3425
  return backend.graphSearch(collection, request2);
3005
3426
  }
3427
+ function relate2(backend, collection, req) {
3428
+ requireNonEmptyString(collection, "Collection");
3429
+ if (!req.relType || typeof req.relType !== "string") {
3430
+ throw new ValidationError("Relation type is required and must be a string");
3431
+ }
3432
+ if (!isGraphNodeId(req.source) || !isGraphNodeId(req.target)) {
3433
+ throw new ValidationError("Source and target must be numbers or strings");
3434
+ }
3435
+ return backend.relate(collection, req);
3436
+ }
3437
+ function unrelate2(backend, collection, edgeId) {
3438
+ requireNonEmptyString(collection, "Collection");
3439
+ if (!isGraphNodeId(edgeId)) {
3440
+ throw new ValidationError("Edge ID must be a number or string");
3441
+ }
3442
+ return backend.unrelate(collection, edgeId);
3443
+ }
3444
+ function getRelations2(backend, collection, pointId) {
3445
+ requireNonEmptyString(collection, "Collection");
3446
+ if (!isGraphNodeId(pointId)) {
3447
+ throw new ValidationError("Point ID must be a number or string");
3448
+ }
3449
+ return backend.getRelations(collection, pointId);
3450
+ }
3451
+ function setTtlDurable2(backend, collection, pointId, ttlSeconds) {
3452
+ requireNonEmptyString(collection, "Collection");
3453
+ if (!isGraphNodeId(pointId)) {
3454
+ throw new ValidationError("Point ID must be a number or string");
3455
+ }
3456
+ if (typeof ttlSeconds !== "number" || ttlSeconds < 0) {
3457
+ throw new ValidationError("ttlSeconds must be a non-negative number");
3458
+ }
3459
+ return backend.setTtlDurable(collection, pointId, ttlSeconds);
3460
+ }
3006
3461
 
3007
3462
  // src/client.ts
3008
3463
  var VelesDB = class {
@@ -3099,6 +3554,24 @@ var VelesDB = class {
3099
3554
  });
3100
3555
  await this.backend.upsertBatch(collection, docs);
3101
3556
  }
3557
+ /**
3558
+ * Bulk upsert via the binary wire format (REST backend only).
3559
+ *
3560
+ * Encodes `(id, vector)` pairs into the deterministic VRB1 binary layout
3561
+ * and sends them as a single `application/octet-stream` request, avoiding
3562
+ * per-point JSON overhead. Payloads are not carried — use
3563
+ * {@link upsertBatch} when you need them. Throws a not-supported error on
3564
+ * the WASM backend.
3565
+ *
3566
+ * @returns the number of points the server reports as inserted.
3567
+ */
3568
+ async upsertBatchRaw(collection, docs) {
3569
+ this.ensureInitialized();
3570
+ validateDocsBatch(docs, (doc) => {
3571
+ validateDocument(doc, this.config);
3572
+ });
3573
+ return this.backend.upsertBatchRaw(collection, docs);
3574
+ }
3102
3575
  async delete(collection, id) {
3103
3576
  this.ensureInitialized();
3104
3577
  validateRestPointId(id, this.config);
@@ -3146,6 +3619,11 @@ var VelesDB = class {
3146
3619
  this.ensureInitialized();
3147
3620
  return multiQuerySearch2(this.backend, collection, vectors, options);
3148
3621
  }
3622
+ /** Multi-query fusion search returning only IDs and scores (no payloads). */
3623
+ async multiQuerySearchIds(collection, vectors, options) {
3624
+ this.ensureInitialized();
3625
+ return multiQuerySearchIds2(this.backend, collection, vectors, options);
3626
+ }
3149
3627
  /**
3150
3628
  * Pure sparse search against a named sparse index.
3151
3629
  *
@@ -3176,6 +3654,10 @@ var VelesDB = class {
3176
3654
  this.ensureInitialized();
3177
3655
  return trainPq2(this.backend, collection, options);
3178
3656
  }
3657
+ async enableStreaming(collection, config) {
3658
+ this.ensureInitialized();
3659
+ return enableStreaming2(this.backend, collection, config);
3660
+ }
3179
3661
  async streamInsert(collection, docs) {
3180
3662
  this.ensureInitialized();
3181
3663
  return streamInsert2(this.backend, this.config, collection, docs);
@@ -3300,6 +3782,22 @@ var VelesDB = class {
3300
3782
  this.ensureInitialized();
3301
3783
  return graphSearch2(this.backend, collection, request2);
3302
3784
  }
3785
+ async relate(collection, req) {
3786
+ this.ensureInitialized();
3787
+ return relate2(this.backend, collection, req);
3788
+ }
3789
+ async unrelate(collection, edgeId) {
3790
+ this.ensureInitialized();
3791
+ return unrelate2(this.backend, collection, edgeId);
3792
+ }
3793
+ async getRelations(collection, pointId) {
3794
+ this.ensureInitialized();
3795
+ return getRelations2(this.backend, collection, pointId);
3796
+ }
3797
+ async setTtlDurable(collection, pointId, ttlSeconds) {
3798
+ this.ensureInitialized();
3799
+ return setTtlDurable2(this.backend, collection, pointId, ttlSeconds);
3800
+ }
3303
3801
  // ========================================================================
3304
3802
  // Capabilities & Backend Info
3305
3803
  // ========================================================================