@wiscale/velesdb-sdk 1.17.0 → 2.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
@@ -60,7 +60,7 @@ var WASM_CAPABILITIES = Object.freeze({
60
60
  agentMemory: false,
61
61
  streamInsert: false,
62
62
  pqTraining: false,
63
- velesqlQuery: true,
63
+ velesqlQuery: false,
64
64
  collectionIntrospection: false
65
65
  });
66
66
 
@@ -298,20 +298,47 @@ async function wasmMultiQuerySearch(ctx, collectionName, vectors, options) {
298
298
  );
299
299
  return raw.map((r) => mapWasmResult(ctx, collection, r));
300
300
  }
301
- async function wasmQuery(ctx, collectionName, _queryString, params, _options) {
301
+ 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;
302
+ function parsePureNearQuery(queryString) {
303
+ const match = PURE_NEAR_QUERY.exec(queryString);
304
+ if (!match) {
305
+ throw new VelesDBError(
306
+ `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}`,
307
+ "NOT_SUPPORTED"
308
+ );
309
+ }
310
+ const parsed = { from: match[1], param: match[2] };
311
+ if (match[3] !== void 0) {
312
+ parsed.limit = Number(match[3]);
313
+ }
314
+ return parsed;
315
+ }
316
+ function resolveQueryK(limit, requestedK) {
317
+ if (limit !== void 0) {
318
+ return limit;
319
+ }
320
+ return typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
321
+ }
322
+ async function wasmQuery(ctx, collectionName, queryString, params, _options) {
302
323
  const collection = ctx.getCollection(collectionName);
303
324
  if (!collection) {
304
325
  throw new NotFoundError(`Collection '${collectionName}'`);
305
326
  }
306
- const paramsVector = params?.q;
327
+ const parsed = parsePureNearQuery(queryString);
328
+ if (parsed.from !== collectionName) {
329
+ throw new VelesDBError(
330
+ `Query targets collection '${parsed.from}' but was executed against '${collectionName}'.`,
331
+ "BAD_REQUEST"
332
+ );
333
+ }
334
+ const paramsVector = params?.[parsed.param];
307
335
  if (!Array.isArray(paramsVector) && !(paramsVector instanceof Float32Array)) {
308
336
  throw new VelesDBError(
309
- "WASM query() expects params.q to contain the query embedding vector.",
337
+ `WASM query() expects params.${parsed.param} to contain the query embedding vector.`,
310
338
  "BAD_REQUEST"
311
339
  );
312
340
  }
313
- const requestedK = params?.k;
314
- const k = typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
341
+ const k = resolveQueryK(parsed.limit, params?.k);
315
342
  const raw = collection.store.query(
316
343
  paramsVector instanceof Float32Array ? paramsVector : new Float32Array(paramsVector),
317
344
  k
@@ -811,6 +838,12 @@ async function wasmRecordEpisodicEvent(_collection, _event) {
811
838
  async function wasmRecallEpisodicEvents(_collection, _embedding, _k) {
812
839
  wasmNotSupported("Agent memory");
813
840
  }
841
+ async function wasmRecallRecentEvents(_collection, _since) {
842
+ wasmNotSupported("Agent memory");
843
+ }
844
+ async function wasmRecallOlderThanEvents(_collection, _before) {
845
+ wasmNotSupported("Agent memory");
846
+ }
814
847
  async function wasmStoreProceduralPattern(_collection, _pattern) {
815
848
  wasmNotSupported("Agent memory");
816
849
  }
@@ -858,6 +891,18 @@ function wasmUpsertNodePayload(_c, _id, _p) {
858
891
  function wasmGraphSearch(_c, _r) {
859
892
  return Promise.resolve(wasmNotSupported("Graph search"));
860
893
  }
894
+ function wasmRelate(_c, _req) {
895
+ return Promise.resolve(wasmNotSupported("Relation edges"));
896
+ }
897
+ function wasmUnrelate(_c, _id) {
898
+ return Promise.resolve(wasmNotSupported("Relation edge removal"));
899
+ }
900
+ function wasmGetRelations(_c, _id) {
901
+ return Promise.resolve(wasmNotSupported("Relation edges"));
902
+ }
903
+ function wasmSetTtlDurable(_c, _id, _ttl) {
904
+ return Promise.resolve(wasmNotSupported("Durable TTL"));
905
+ }
861
906
 
862
907
  // src/backends/wasm.ts
863
908
  var WasmBackend = class {
@@ -1212,6 +1257,14 @@ var WasmBackend = class {
1212
1257
  this.ensureInitialized();
1213
1258
  return wasmRecallEpisodicEvents(c, e, k);
1214
1259
  }
1260
+ async recallRecentEvents(c, since) {
1261
+ this.ensureInitialized();
1262
+ return wasmRecallRecentEvents(c, since);
1263
+ }
1264
+ async recallOlderThanEvents(c, before) {
1265
+ this.ensureInitialized();
1266
+ return wasmRecallOlderThanEvents(c, before);
1267
+ }
1215
1268
  async storeProceduralPattern(c, p) {
1216
1269
  this.ensureInitialized();
1217
1270
  return wasmStoreProceduralPattern(c, p);
@@ -1273,16 +1326,43 @@ var WasmBackend = class {
1273
1326
  this.ensureInitialized();
1274
1327
  return wasmSparseSearchNamed(c, q, idx, o);
1275
1328
  }
1329
+ async relate(c, req) {
1330
+ this.ensureInitialized();
1331
+ return wasmRelate(c, req);
1332
+ }
1333
+ async unrelate(c, edgeId) {
1334
+ this.ensureInitialized();
1335
+ return wasmUnrelate(c, edgeId);
1336
+ }
1337
+ async getRelations(c, pointId) {
1338
+ this.ensureInitialized();
1339
+ return wasmGetRelations(c, pointId);
1340
+ }
1341
+ async setTtlDurable(c, pointId, ttlSeconds) {
1342
+ this.ensureInitialized();
1343
+ return wasmSetTtlDurable(c, pointId, ttlSeconds);
1344
+ }
1276
1345
  };
1277
1346
 
1278
1347
  // src/backends/crud-backend.ts
1348
+ var U64_MAX = 18446744073709551615n;
1349
+ function coerceDecimalStringId(id) {
1350
+ if (!/^\d+$/.test(id)) return NaN;
1351
+ const big = BigInt(id);
1352
+ if (big > U64_MAX) return NaN;
1353
+ return big > BigInt(Number.MAX_SAFE_INTEGER) ? id : Number(id);
1354
+ }
1279
1355
  function parseRestPointId(id) {
1280
- if (typeof id !== "number" || !Number.isFinite(id) || id < 0 || !Number.isInteger(id) || id > Number.MAX_SAFE_INTEGER) {
1356
+ const coerced = typeof id === "string" ? coerceDecimalStringId(id) : id;
1357
+ if (typeof coerced === "string") {
1358
+ return coerced;
1359
+ }
1360
+ if (!Number.isFinite(coerced) || coerced < 0 || !Number.isInteger(coerced) || coerced > Number.MAX_SAFE_INTEGER) {
1281
1361
  throw new ValidationError(
1282
- `REST backend requires numeric u64-compatible IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER}). Received: ${String(id)}`
1362
+ `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
1363
  );
1284
1364
  }
1285
- return id;
1365
+ return coerced;
1286
1366
  }
1287
1367
  function sparseVectorToRestFormat(sv) {
1288
1368
  const result = {};
@@ -1676,16 +1756,13 @@ async function getEdgeCount(transport, collection) {
1676
1756
  return response.data?.count ?? 0;
1677
1757
  }
1678
1758
  async function listNodes(transport, collection) {
1679
- const response = await transport.requestJson(
1680
- "GET",
1681
- `${collectionPath(collection)}/graph/nodes`
1682
- );
1759
+ const response = await transport.requestJson("GET", `${collectionPath(collection)}/graph/nodes`);
1683
1760
  throwOnError(response, `Collection '${collection}'`);
1684
1761
  const data = response.data;
1685
1762
  return { nodeIds: data.node_ids, count: data.count };
1686
1763
  }
1687
- function idToNumber(id) {
1688
- return typeof id === "string" ? Number(id) : id;
1764
+ function idToGraphId(id) {
1765
+ return id;
1689
1766
  }
1690
1767
  async function getNodeEdges(transport, collection, nodeId, options) {
1691
1768
  const params = new URLSearchParams();
@@ -1696,9 +1773,9 @@ async function getNodeEdges(transport, collection, nodeId, options) {
1696
1773
  const response = await transport.requestJson("GET", url);
1697
1774
  throwOnError(response, `Collection '${collection}'`);
1698
1775
  return (response.data?.edges ?? []).map((e) => ({
1699
- id: idToNumber(e.id),
1700
- source: idToNumber(e.source),
1701
- target: idToNumber(e.target),
1776
+ id: e.id,
1777
+ source: e.source,
1778
+ target: e.target,
1702
1779
  label: e.label,
1703
1780
  properties: e.properties
1704
1781
  }));
@@ -1708,7 +1785,7 @@ async function getNodePayload(transport, collection, nodeId) {
1708
1785
  throwOnError(response, `Collection '${collection}'`);
1709
1786
  const data = response.data;
1710
1787
  return {
1711
- nodeId: idToNumber(data.node_id),
1788
+ nodeId: idToGraphId(data.node_id),
1712
1789
  payload: data.payload
1713
1790
  };
1714
1791
  }
@@ -1732,7 +1809,7 @@ async function graphSearch(transport, collection, request2) {
1732
1809
  throwOnError(response, `Collection '${collection}'`);
1733
1810
  const items = (response.data?.results ?? []).map(
1734
1811
  (r) => ({
1735
- id: idToNumber(r.id),
1812
+ id: idToGraphId(r.id),
1736
1813
  score: r.score,
1737
1814
  payload: r.payload
1738
1815
  })
@@ -1740,6 +1817,31 @@ async function graphSearch(transport, collection, request2) {
1740
1817
  return { results: items };
1741
1818
  }
1742
1819
 
1820
+ // src/backends/scroll-backend.ts
1821
+ async function scroll(transport, collection, request2) {
1822
+ const body = {};
1823
+ if (request2?.cursor !== void 0) {
1824
+ body.cursor = request2.cursor;
1825
+ }
1826
+ if (request2?.batchSize !== void 0) {
1827
+ body.batch_size = request2.batchSize;
1828
+ }
1829
+ if (request2?.filter !== void 0) {
1830
+ body.filter = request2.filter;
1831
+ }
1832
+ const response = await transport.requestJson(
1833
+ "POST",
1834
+ `${collectionPath(collection)}/points/scroll`,
1835
+ body
1836
+ );
1837
+ throwOnError(response, `Collection '${collection}'`);
1838
+ const data = response.data;
1839
+ return {
1840
+ points: data.points,
1841
+ nextCursor: data.next_cursor
1842
+ };
1843
+ }
1844
+
1743
1845
  // src/backends/agent-memory-backend.ts
1744
1846
  var _idCounter = 0;
1745
1847
  var _lastTimestamp = 0;
@@ -1757,18 +1859,28 @@ function generateUniqueId() {
1757
1859
  }
1758
1860
  return _lastTimestamp * 1e3 + _idCounter;
1759
1861
  }
1862
+ function memoryIdToString(id) {
1863
+ return String(id);
1864
+ }
1865
+ function nowUnixSeconds() {
1866
+ return Math.floor(Date.now() / 1e3);
1867
+ }
1760
1868
  async function storeSemanticFact(transport, collection, entry) {
1761
1869
  const response = await transport.requestJson(
1762
1870
  "POST",
1763
1871
  `${collectionPath(collection)}/points`,
1764
1872
  {
1765
1873
  points: [{
1766
- id: entry.id,
1874
+ id: parseRestPointId(entry.id),
1767
1875
  vector: entry.embedding,
1768
1876
  payload: {
1877
+ // Caller metadata is spread first so the reserved keys below
1878
+ // (`_memory_type`, `content`) always win and cannot be clobbered.
1879
+ ...entry.metadata,
1769
1880
  _memory_type: "semantic",
1770
- text: entry.text,
1771
- ...entry.metadata
1881
+ // `content` matches the core semantic store and the server/Python
1882
+ // payload field (BREAKING: was `text` before this change).
1883
+ content: entry.text
1772
1884
  }
1773
1885
  }]
1774
1886
  }
@@ -1779,7 +1891,8 @@ async function searchSemanticMemory(transport, collection, embedding, k = 5) {
1779
1891
  return transport.searchVectors(collection, embedding, k, { _memory_type: "semantic" });
1780
1892
  }
1781
1893
  async function recordEpisodicEvent(transport, collection, event) {
1782
- const id = generateUniqueId();
1894
+ const id = event.id !== void 0 ? parseRestPointId(event.id) : generateUniqueId();
1895
+ const timestamp = event.timestamp ?? nowUnixSeconds();
1783
1896
  const response = await transport.requestJson(
1784
1897
  "POST",
1785
1898
  `${collectionPath(collection)}/points`,
@@ -1788,42 +1901,83 @@ async function recordEpisodicEvent(transport, collection, event) {
1788
1901
  id,
1789
1902
  vector: event.embedding,
1790
1903
  payload: {
1904
+ // Caller-supplied data/metadata is spread first so the reserved
1905
+ // keys below (`_memory_type`, `event_type`, `timestamp`) always
1906
+ // win and cannot be clobbered.
1907
+ ...event.data,
1908
+ ...event.metadata,
1791
1909
  _memory_type: "episodic",
1792
1910
  event_type: event.eventType,
1793
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1794
- ...event.data,
1795
- ...event.metadata
1911
+ // NUMERIC unix-seconds, mirroring the core episodic store so
1912
+ // recallRecent/recallOlderThan can range-filter on it.
1913
+ timestamp
1796
1914
  }
1797
1915
  }]
1798
1916
  }
1799
1917
  );
1800
1918
  throwOnError(response);
1919
+ return memoryIdToString(id);
1801
1920
  }
1802
1921
  async function recallEpisodicEvents(transport, collection, embedding, k = 5) {
1803
1922
  return transport.searchVectors(collection, embedding, k, { _memory_type: "episodic" });
1804
1923
  }
1805
1924
  async function storeProceduralPattern(transport, collection, pattern) {
1806
- const id = generateUniqueId();
1925
+ const id = pattern.id !== void 0 ? parseRestPointId(pattern.id) : generateUniqueId();
1807
1926
  const response = await transport.requestJson(
1808
1927
  "POST",
1809
1928
  `${collectionPath(collection)}/points`,
1810
1929
  {
1811
1930
  points: [{
1812
1931
  id,
1932
+ vector: pattern.embedding,
1813
1933
  payload: {
1934
+ // Caller metadata is spread first so the reserved keys below
1935
+ // (`_memory_type`, `name`, `steps`) always win and cannot be
1936
+ // clobbered.
1937
+ ...pattern.metadata,
1814
1938
  _memory_type: "procedural",
1815
1939
  name: pattern.name,
1816
- steps: pattern.steps,
1817
- ...pattern.metadata
1940
+ steps: pattern.steps
1818
1941
  }
1819
1942
  }]
1820
1943
  }
1821
1944
  );
1822
1945
  throwOnError(response);
1946
+ return memoryIdToString(id);
1823
1947
  }
1824
1948
  async function matchProceduralPatterns(transport, collection, embedding, k = 5) {
1825
1949
  return transport.searchVectors(collection, embedding, k, { _memory_type: "procedural" });
1826
1950
  }
1951
+ function toEpisodicRecord(point) {
1952
+ const payload = point.payload ?? {};
1953
+ if (payload._memory_type !== "episodic") return void 0;
1954
+ if (typeof payload.timestamp !== "number") return void 0;
1955
+ return { id: String(point.id), timestamp: payload.timestamp, payload };
1956
+ }
1957
+ async function scrollEpisodicRecords(transport, collection) {
1958
+ const records = [];
1959
+ let cursor = null;
1960
+ do {
1961
+ const page = await scroll(transport, collection, {
1962
+ cursor: cursor ?? void 0,
1963
+ filter: { _memory_type: "episodic" }
1964
+ });
1965
+ for (const point of page.points) {
1966
+ const record = toEpisodicRecord(point);
1967
+ if (record !== void 0) records.push(record);
1968
+ }
1969
+ cursor = page.nextCursor;
1970
+ } while (cursor !== null && cursor !== void 0);
1971
+ return records;
1972
+ }
1973
+ async function recallRecentEvents(transport, collection, since) {
1974
+ const records = await scrollEpisodicRecords(transport, collection);
1975
+ return records.filter((r) => since === void 0 || r.timestamp >= since).sort((a, b) => b.timestamp - a.timestamp);
1976
+ }
1977
+ async function recallOlderThanEvents(transport, collection, before) {
1978
+ const records = await scrollEpisodicRecords(transport, collection);
1979
+ return records.filter((r) => r.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
1980
+ }
1827
1981
 
1828
1982
  // src/search-quality.ts
1829
1983
  function searchQualityToMode(quality) {
@@ -1973,15 +2127,29 @@ async function addEdge(transport, collection, edge) {
1973
2127
  throwOnError(response, `Collection '${collection}'`);
1974
2128
  }
1975
2129
  function toGraphEdge(e) {
1976
- const toNum = (v) => typeof v === "string" ? Number(v) : v;
1977
2130
  return {
1978
- id: toNum(e.id),
1979
- source: toNum(e.source),
1980
- target: toNum(e.target),
2131
+ id: e.id,
2132
+ source: e.source,
2133
+ target: e.target,
1981
2134
  label: e.label,
1982
2135
  properties: e.properties
1983
2136
  };
1984
2137
  }
2138
+ function toTraverseResponse(data) {
2139
+ return {
2140
+ results: data.results.map((r) => ({
2141
+ targetId: r.target_id,
2142
+ depth: r.depth,
2143
+ path: r.path
2144
+ })),
2145
+ nextCursor: data.next_cursor ?? void 0,
2146
+ hasMore: data.has_more,
2147
+ stats: {
2148
+ visited: data.stats.visited,
2149
+ depthReached: data.stats.depth_reached
2150
+ }
2151
+ };
2152
+ }
1985
2153
  async function getEdges(transport, collection, options) {
1986
2154
  const queryParams = options?.label ? `?label=${encodeURIComponent(options.label)}` : "";
1987
2155
  const response = await transport.requestJson(
@@ -2005,20 +2173,7 @@ async function traverseGraph(transport, collection, request2) {
2005
2173
  }
2006
2174
  );
2007
2175
  throwOnError(response, `Collection '${collection}'`);
2008
- const data = response.data;
2009
- return {
2010
- results: data.results.map((r) => ({
2011
- targetId: r.target_id,
2012
- depth: r.depth,
2013
- path: r.path
2014
- })),
2015
- nextCursor: data.next_cursor ?? void 0,
2016
- hasMore: data.has_more,
2017
- stats: {
2018
- visited: data.stats.visited,
2019
- depthReached: data.stats.depth_reached
2020
- }
2021
- };
2176
+ return toTraverseResponse(response.data);
2022
2177
  }
2023
2178
  async function getNodeDegree(transport, collection, nodeId) {
2024
2179
  const response = await transport.requestJson(
@@ -2053,21 +2208,66 @@ async function traverseParallel(transport, collection, request2) {
2053
2208
  }
2054
2209
  );
2055
2210
  throwOnError(response, `Collection '${collection}'`);
2056
- const data = response.data;
2211
+ return toTraverseResponse(response.data);
2212
+ }
2213
+ async function relate(transport, collection, req) {
2214
+ const response = await transport.requestJson(
2215
+ "POST",
2216
+ `${collectionPath(collection)}/relations`,
2217
+ {
2218
+ source: req.source,
2219
+ target: req.target,
2220
+ rel_type: req.relType,
2221
+ properties: req.properties ?? {}
2222
+ }
2223
+ );
2224
+ throwOnError(response, `Collection '${collection}'`);
2225
+ return { edgeId: response.data.edge_id };
2226
+ }
2227
+ async function unrelate(transport, collection, edgeId) {
2228
+ const response = await transport.requestJson(
2229
+ "DELETE",
2230
+ `${collectionPath(collection)}/relations/${encodeURIComponent(String(edgeId))}`
2231
+ );
2232
+ if (response.error !== void 0) {
2233
+ const { code, message } = response.error;
2234
+ const err = parseVelesError(code, message);
2235
+ if (err instanceof EdgeNotFoundError) {
2236
+ return false;
2237
+ }
2238
+ if (code === "NOT_FOUND") {
2239
+ return false;
2240
+ }
2241
+ throwOnError(response, `Collection '${collection}'`);
2242
+ }
2243
+ return true;
2244
+ }
2245
+ async function getRelations(transport, collection, pointId) {
2246
+ const response = await transport.requestJson(
2247
+ "GET",
2248
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(pointId))}/relations`
2249
+ );
2250
+ throwOnError(response, `Collection '${collection}'`);
2251
+ const raw = response.data;
2057
2252
  return {
2058
- results: data.results.map((r) => ({
2059
- targetId: r.target_id,
2060
- depth: r.depth,
2061
- path: r.path
2253
+ edges: raw.edges.map((e) => ({
2254
+ id: e.id,
2255
+ source: e.source,
2256
+ target: e.target,
2257
+ relType: e.rel_type,
2258
+ properties: e.properties
2062
2259
  })),
2063
- nextCursor: data.next_cursor ?? void 0,
2064
- hasMore: data.has_more,
2065
- stats: {
2066
- visited: data.stats.visited,
2067
- depthReached: data.stats.depth_reached
2068
- }
2260
+ count: raw.count
2069
2261
  };
2070
2262
  }
2263
+ async function setTtlDurable(transport, collection, pointId, ttlSeconds) {
2264
+ const response = await transport.requestJson(
2265
+ "PATCH",
2266
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(pointId))}/ttl`,
2267
+ { ttl_seconds: ttlSeconds }
2268
+ );
2269
+ throwOnError(response, `Collection '${collection}'`);
2270
+ }
2071
2271
 
2072
2272
  // src/backends/query-backend.ts
2073
2273
  function isLikelyAggregationQuery(queryString) {
@@ -2200,31 +2400,6 @@ async function collectionSanity(transport, collection) {
2200
2400
  };
2201
2401
  }
2202
2402
 
2203
- // src/backends/scroll-backend.ts
2204
- async function scroll(transport, collection, request2) {
2205
- const body = {};
2206
- if (request2?.cursor !== void 0) {
2207
- body.cursor = request2.cursor;
2208
- }
2209
- if (request2?.batchSize !== void 0) {
2210
- body.batch_size = request2.batchSize;
2211
- }
2212
- if (request2?.filter !== void 0) {
2213
- body.filter = request2.filter;
2214
- }
2215
- const response = await transport.requestJson(
2216
- "POST",
2217
- `${collectionPath(collection)}/points/scroll`,
2218
- body
2219
- );
2220
- throwOnError(response, `Collection '${collection}'`);
2221
- const data = response.data;
2222
- return {
2223
- points: data.points,
2224
- nextCursor: data.next_cursor
2225
- };
2226
- }
2227
-
2228
2403
  // src/backends/admin-backend.ts
2229
2404
  function mapStatsResponse(data) {
2230
2405
  let columnStats;
@@ -2410,9 +2585,17 @@ async function streamInsert(transport, collection, docs) {
2410
2585
  }
2411
2586
  }
2412
2587
  }
2588
+ function requireSafeRangeId(restId) {
2589
+ if (typeof restId === "string") {
2590
+ throw new ValidationError(
2591
+ `streamUpsertPoints requires ids in the JS safe integer range (0..${Number.MAX_SAFE_INTEGER}); use upsert/upsertBatch for string ids above it. Received: ${restId}`
2592
+ );
2593
+ }
2594
+ return restId;
2595
+ }
2413
2596
  async function streamUpsertPoints(transport, collection, docs) {
2414
2597
  const ndjsonLines = docs.map((doc) => {
2415
- const restId = transport.parseRestPointId(doc.id);
2598
+ const restId = requireSafeRangeId(transport.parseRestPointId(doc.id));
2416
2599
  const vector = toNumberArray(doc.vector);
2417
2600
  const point = {
2418
2601
  id: restId,
@@ -2603,6 +2786,22 @@ var RestBackend = class {
2603
2786
  this.ensureInitialized();
2604
2787
  return graphSearch(buildBaseTransport(this.httpConfig), c, r);
2605
2788
  }
2789
+ async relate(c, req) {
2790
+ this.ensureInitialized();
2791
+ return relate(buildCrudTransport(this.httpConfig), c, req);
2792
+ }
2793
+ async unrelate(c, edgeId) {
2794
+ this.ensureInitialized();
2795
+ return unrelate(buildCrudTransport(this.httpConfig), c, edgeId);
2796
+ }
2797
+ async getRelations(c, pointId) {
2798
+ this.ensureInitialized();
2799
+ return getRelations(buildCrudTransport(this.httpConfig), c, pointId);
2800
+ }
2801
+ async setTtlDurable(c, pointId, ttlSeconds) {
2802
+ this.ensureInitialized();
2803
+ return setTtlDurable(buildCrudTransport(this.httpConfig), c, pointId, ttlSeconds);
2804
+ }
2606
2805
  // Search
2607
2806
  async search(c, q, o) {
2608
2807
  this.ensureInitialized();
@@ -2735,6 +2934,14 @@ var RestBackend = class {
2735
2934
  this.ensureInitialized();
2736
2935
  return recallEpisodicEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, e, k);
2737
2936
  }
2937
+ async recallRecentEvents(c, since) {
2938
+ this.ensureInitialized();
2939
+ return recallRecentEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, since);
2940
+ }
2941
+ async recallOlderThanEvents(c, before) {
2942
+ this.ensureInitialized();
2943
+ return recallOlderThanEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, before);
2944
+ }
2738
2945
  async storeProceduralPattern(c, p) {
2739
2946
  this.ensureInitialized();
2740
2947
  return storeProceduralPattern(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, p);
@@ -2751,7 +2958,16 @@ var AgentMemoryClient = class {
2751
2958
  this.backend = backend;
2752
2959
  this.config = config;
2753
2960
  }
2754
- /** Configured embedding dimension (default: 384) */
2961
+ /**
2962
+ * Advisory embedding dimension passed at construction (default: 384).
2963
+ *
2964
+ * This value is **not** enforced and does not create or size any
2965
+ * collection — the dimension that actually governs storage and search
2966
+ * is the one fixed when the collection was created
2967
+ * (`db.createCollection(name, { dimension, metric: 'cosine' })`).
2968
+ * Embeddings you pass to `storeFact` / `recordEvent` / `learnProcedure`
2969
+ * must match that collection dimension.
2970
+ */
2755
2971
  get dimension() {
2756
2972
  return this.config?.dimension ?? 384;
2757
2973
  }
@@ -2763,15 +2979,29 @@ var AgentMemoryClient = class {
2763
2979
  async searchFacts(collection, embedding, k = 5) {
2764
2980
  return this.backend.searchSemanticMemory(collection, embedding, k);
2765
2981
  }
2766
- /** Record an episodic event */
2982
+ /** Record an episodic event. Returns the point ID (string, u64-safe). */
2767
2983
  async recordEvent(collection, event) {
2768
2984
  return this.backend.recordEpisodicEvent(collection, event);
2769
2985
  }
2770
- /** Recall episodic events */
2986
+ /** Recall episodic events by vector similarity. */
2771
2987
  async recallEvents(collection, embedding, k = 5) {
2772
2988
  return this.backend.recallEpisodicEvents(collection, embedding, k);
2773
2989
  }
2774
- /** Store a procedural pattern */
2990
+ /**
2991
+ * Recall episodic events most-recent-first, optionally bounded below by
2992
+ * `since` (inclusive unix-seconds). Mirrors core `episodic.recent(since)`.
2993
+ */
2994
+ async recallRecent(collection, since) {
2995
+ return this.backend.recallRecentEvents(collection, since);
2996
+ }
2997
+ /**
2998
+ * Recall episodic events strictly older than `before` (unix-seconds),
2999
+ * most-recent-first. Mirrors core `episodic.older_than(before)`.
3000
+ */
3001
+ async recallOlderThan(collection, before) {
3002
+ return this.backend.recallOlderThanEvents(collection, before);
3003
+ }
3004
+ /** Store a procedural pattern. Returns the point ID (string, u64-safe). */
2775
3005
  async learnProcedure(collection, pattern) {
2776
3006
  return this.backend.storeProceduralPattern(collection, pattern);
2777
3007
  }
@@ -2779,6 +3009,15 @@ var AgentMemoryClient = class {
2779
3009
  async recallProcedures(collection, embedding, k = 5) {
2780
3010
  return this.backend.matchProceduralPatterns(collection, embedding, k);
2781
3011
  }
3012
+ /**
3013
+ * Delete a memory entry (fact, event, or procedure) by its point ID.
3014
+ *
3015
+ * Accepts the `string` ids returned by `recordEvent` / `learnProcedure`
3016
+ * (u64-safe decimal strings) as well as numeric ids.
3017
+ */
3018
+ async deleteMemory(collection, id) {
3019
+ return this.backend.delete(collection, id);
3020
+ }
2782
3021
  };
2783
3022
 
2784
3023
  // src/client/validation.ts
@@ -2809,11 +3048,10 @@ function validateDocument(doc, config) {
2809
3048
  validateRestPointId(doc.id, config);
2810
3049
  }
2811
3050
  function validateRestPointId(id, config) {
2812
- if (config.backend === "rest" && (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > Number.MAX_SAFE_INTEGER)) {
2813
- throw new ValidationError(
2814
- `REST backend requires numeric u64-compatible document IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER})`
2815
- );
3051
+ if (config.backend !== "rest") {
3052
+ return;
2816
3053
  }
3054
+ parseRestPointId(id);
2817
3055
  }
2818
3056
 
2819
3057
  // src/client/search-methods.ts
@@ -2918,12 +3156,15 @@ function aggregate2(backend, queryString, params, options) {
2918
3156
  }
2919
3157
 
2920
3158
  // src/client/graph-methods.ts
3159
+ function isGraphNodeId(value) {
3160
+ return typeof value === "number" || typeof value === "string";
3161
+ }
2921
3162
  function addEdge2(backend, collection, edge) {
2922
3163
  if (!edge.label || typeof edge.label !== "string") {
2923
3164
  throw new ValidationError("Edge label is required and must be a string");
2924
3165
  }
2925
- if (typeof edge.source !== "number" || typeof edge.target !== "number") {
2926
- throw new ValidationError("Edge source and target must be numbers");
3166
+ if (!isGraphNodeId(edge.source) || !isGraphNodeId(edge.target)) {
3167
+ throw new ValidationError("Edge source and target must be numbers or strings");
2927
3168
  }
2928
3169
  return backend.addEdge(collection, edge);
2929
3170
  }
@@ -2931,8 +3172,8 @@ function getEdges2(backend, collection, options) {
2931
3172
  return backend.getEdges(collection, options);
2932
3173
  }
2933
3174
  function traverseGraph2(backend, collection, request2) {
2934
- if (typeof request2.source !== "number") {
2935
- throw new ValidationError("Source node ID must be a number");
3175
+ if (!isGraphNodeId(request2.source)) {
3176
+ throw new ValidationError("Source node ID must be a number or string");
2936
3177
  }
2937
3178
  if (request2.strategy && !["bfs", "dfs"].includes(request2.strategy)) {
2938
3179
  throw new ValidationError("Strategy must be 'bfs' or 'dfs'");
@@ -2943,6 +3184,9 @@ function traverseParallel2(backend, collection, request2) {
2943
3184
  if (!Array.isArray(request2.sources) || request2.sources.length === 0) {
2944
3185
  throw new ValidationError("At least one source node ID is required");
2945
3186
  }
3187
+ if (!request2.sources.every(isGraphNodeId)) {
3188
+ throw new ValidationError("Source node IDs must be numbers or strings");
3189
+ }
2946
3190
  return backend.traverseParallel(collection, request2);
2947
3191
  }
2948
3192
  function getNodeDegree2(backend, collection, nodeId) {
@@ -2988,6 +3232,40 @@ function graphSearch2(backend, collection, request2) {
2988
3232
  requireNonEmptyString(collection, "Collection");
2989
3233
  return backend.graphSearch(collection, request2);
2990
3234
  }
3235
+ function relate2(backend, collection, req) {
3236
+ requireNonEmptyString(collection, "Collection");
3237
+ if (!req.relType || typeof req.relType !== "string") {
3238
+ throw new ValidationError("Relation type is required and must be a string");
3239
+ }
3240
+ if (!isGraphNodeId(req.source) || !isGraphNodeId(req.target)) {
3241
+ throw new ValidationError("Source and target must be numbers or strings");
3242
+ }
3243
+ return backend.relate(collection, req);
3244
+ }
3245
+ function unrelate2(backend, collection, edgeId) {
3246
+ requireNonEmptyString(collection, "Collection");
3247
+ if (!isGraphNodeId(edgeId)) {
3248
+ throw new ValidationError("Edge ID must be a number or string");
3249
+ }
3250
+ return backend.unrelate(collection, edgeId);
3251
+ }
3252
+ function getRelations2(backend, collection, pointId) {
3253
+ requireNonEmptyString(collection, "Collection");
3254
+ if (!isGraphNodeId(pointId)) {
3255
+ throw new ValidationError("Point ID must be a number or string");
3256
+ }
3257
+ return backend.getRelations(collection, pointId);
3258
+ }
3259
+ function setTtlDurable2(backend, collection, pointId, ttlSeconds) {
3260
+ requireNonEmptyString(collection, "Collection");
3261
+ if (!isGraphNodeId(pointId)) {
3262
+ throw new ValidationError("Point ID must be a number or string");
3263
+ }
3264
+ if (typeof ttlSeconds !== "number" || ttlSeconds < 0) {
3265
+ throw new ValidationError("ttlSeconds must be a non-negative number");
3266
+ }
3267
+ return backend.setTtlDurable(collection, pointId, ttlSeconds);
3268
+ }
2991
3269
 
2992
3270
  // src/client.ts
2993
3271
  var VelesDB = class {
@@ -3285,6 +3563,22 @@ var VelesDB = class {
3285
3563
  this.ensureInitialized();
3286
3564
  return graphSearch2(this.backend, collection, request2);
3287
3565
  }
3566
+ async relate(collection, req) {
3567
+ this.ensureInitialized();
3568
+ return relate2(this.backend, collection, req);
3569
+ }
3570
+ async unrelate(collection, edgeId) {
3571
+ this.ensureInitialized();
3572
+ return unrelate2(this.backend, collection, edgeId);
3573
+ }
3574
+ async getRelations(collection, pointId) {
3575
+ this.ensureInitialized();
3576
+ return getRelations2(this.backend, collection, pointId);
3577
+ }
3578
+ async setTtlDurable(collection, pointId, ttlSeconds) {
3579
+ this.ensureInitialized();
3580
+ return setTtlDurable2(this.backend, collection, pointId, ttlSeconds);
3581
+ }
3288
3582
  // ========================================================================
3289
3583
  // Capabilities & Backend Info
3290
3584
  // ========================================================================